START HERE

Quickstart.

Get a running Actae and your first recorded, replayed, and live-delivered event in a few minutes.

1. Start Actae

Actae is one server: the HTTP API, WebSocket endpoint, health checks, and dashboard all run on a single port. Pick a setup that matches where you are.

Self-hosted community binary (recommended to try)

The free community build is one self-contained file with the API, dashboard, and PostgreSQL baked in — no Docker, no external database. Download it from actae.dev/download, extract, and run it. The startup banner prints your dashboard URL and a one-time API key.

$ tar -xzf actae-<version>-<platform>.tar.gz
$ chmod +x ./actae
# macOS only, if you downloaded in a browser:
$ xattr -dr com.apple.quarantine ./actae

# starts the API + dashboard + a managed PostgreSQL cluster
$ ./actae

Actae Cloud

After subscribing in the portal, your instance URL is shown in the dashboard — e.g. https://your-org.actae.app. Create an API key in the portal (Settings → API keys); the key prints once, so store it immediately.

Source checkout (embedded dev binary)

The dev binary is self-contained: it embeds a managed PostgreSQL instance and needs no Docker or external services. Data persists across restarts. This is the source-tree path (the fixed sk-dev-… key below); the downloaded community binary generates its own key.

# from the actae checkout
$ cargo run

# the banner prints the endpoint, port, and dev API key
$ http://localhost:8002 · api key sk-dev-0000000000000000000000

Self-hosted (docker compose)

The Docker production stack runs the same PostgreSQL-backed server with TLS termination, API-key authentication, rate limiting, persistent volumes, and certificate rotation. Release binaries can also run fully offline with embedded PostgreSQL.

$ docker compose up -d --build

2. Get credentials

You need two things: the endpoint (where Actae runs) and an API key (who you are).

SetupEndpointAPI key
Community binaryhttp://127.0.0.1:<port> from the bannerthe sk-… printed once in the banner
Actae CloudYour instance URL from the portalsk-… created in the portal
Source checkout (cargo run)http://localhost:8002sk-dev-0000000000000000000000
Docker composehttps://your-server:8002Provisioned ACTAE_API_KEY

The API key is the only credential most users touch. Every HTTP call and the WebSocket authentication use it.

Optional: link a free account

The community binary is fully anonymous and complete on its own. If you create a free account you can link the instance from the portal (actae enroll <key>) to manage API keys, get release and security notices, and see the instance in the portal. Linking is opt-in: an unlinked instance sends nothing. A linked instance reports only the installation identity and enrollment credential, API-key acknowledgements, subscription/license status, and aggregate usage counters (events, ingress bytes, durable-written bytes, and fork operations) — never event payloads, saved state, tool arguments, or model I/O.

3. Install an SDK

SDKInstallRequirements
Pythonpip install actae-clientPython 3.9+, aiohttp
Gogo get github.com/BViganotti/actae-goGo 1.25+
TypeScriptnpm install @actae/sdkNode 20+ (ESM)

4. Record and replay

Channels are named event streams; events are immutable JSON blobs with a monotonic, per-channel cursor; replay reads them back. That is the whole core loop.

python
import asyncio
from actae_client import ActaeClient

async def main():
    async with ActaeClient(
        endpoint="http://localhost:8002",
        api_key="sk-dev-0000000000000000000000",
    ) as client:
        event = await client.record(
            "my-channel", "tool.completed",
            payload={"result": "success"}, actor="my-agent",
        )
        print(f"Recorded: cursor={event.cursor}")

events = await client.replay("my-channel", cursor=0)
for e in events:
print(f"  [{e.cursor}] {e.event_type}")

asyncio.run(main())

Replay uses exclusive cursor semantics: events with a cursor strictly greater than the given value are returned. Cursors are per-channel and gapless, so cursor is always relative to a single channel.

5. Go live

Subscribe to a channel to catch up from a cursor and then receive live delivery — no application-built handoff between replay and live.

python
async with ActaeClient(endpoint=..., api_key=..., echo_self=True) as client:
    client.on_message(lambda topic, event: print(topic, event.event_type))
    await client.subscribe("my-channel")
    await client.publish("my-channel", {"hello": "world"})
    await asyncio.sleep(1)

Echo gotcha

The server never broadcasts a publish back to the connection that sent it. For single-client demos, construct the client with echo_self=True — then your own publishes are delivered to your callbacks too.

6. Kill it, resume it, fork it

Recording events is the setup. The reason to run Actae is what happens when a long agent dies mid-run: you resume from the last durable step without re-running the expensive prefix, and you can fork at a saved boundary with a different next decision. AgentSession records each step and its state; the three runs below are the whole point.

python
import asyncio
from actae_client import ActaeClient
from actae_client.session import AgentSession

actae = ActaeClient(endpoint="http://localhost:8002", api_key="sk-dev-0000000000000000000000")
memory = []

def state_fn():
    return {"steps_done": list(memory)}

async def main():
    # 1. A run that dies after step 3 — the expensive prefix.
    try:
        async with AgentSession(actae, name="run-1", state_fn=state_fn) as s:
            for i in range(1, 4):
                memory.append(i)
                await s.step("inference", input=i, output=i * 10)
            raise RuntimeError("worker died")   # simulate the crash
    except RuntimeError:
        pass

    # 2. Resume: continues at step 4. Steps 1-3 are NOT re-invoked.
    async with (await AgentSession.resume(actae, "run-1")) as s:
        memory.append(4)
        await s.step("inference", input=4, output=40)

    # 3. Fork at step 3 with a different decision: only the tail runs.
    fork = await AgentSession.resume(
        actae, "run-1", fork_at_step=3, name="run-1-alt", params={"model": "gpt-5"},
    )
    async with fork:
        inherited = fork.inherited_state or {}   # state saved at step 3
        await fork.step("inference", input=4, output=100,
                        context={"model": "gpt-5", "inherited": inherited})

asyncio.run(main())

What just happened

The resume replayed steps 1–3 from durable history and continued at 4 — the prefix was not recomputed. The fork inherited the state committed at step 3 and ran only its tail, on a new channel (run-1-alt) with immutable lineage back to run-1. That fork is what the Actae server meters as the differentiated primitive; see forking & recovery.

7. What next

Once you have events flowing and a fork under your belt, the core concepts page explains state transitions, guards, and idempotency. From there: forking & recovery for the full fork/resume reference, coordination for durable workers and coordinated tool retries, and sessions & adapters to instrument entire agent runs.