> ## Documentation Index
> Fetch the complete documentation index at: https://docs.runloop.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Axons

> Persistent event streams for agent lifecycle management, state recovery, and turn-based interactions

<Note>
  Install `@runloop/remote-agents-sdk` to follow along. See the [SDK
  repository](https://github.com/runloopai/remote-agents-sdk) and [full SDK
  documentation](https://runloopai.github.io/remote-agents-sdk/).
</Note>

## Overview

Working with agents means sending messages and managing sessions through an event-oriented model. Devboxes provide the security and isolation agents need, but there is no built-in infrastructure for managing the lifecycle of the Devbox and the agent running inside it.

Axons solve this. They give agents a persistent event stream that lets them suspend when idle, wake on demand, recover state, and hand off between agents — without you building the plumbing.

Under the hood, an Axon is an append-only event stream that assigns each event a monotonic sequence number and exposes the stream to publishers, subscribers, and brokers.

<CardGroup cols={3}>
  <Card title="Broker" icon="arrows-turn-right" href="/docs/axons/broker">
    Bridges Axons to agents running in Devboxes, forwarding events one turn at a
    time.
  </Card>

  <Card title="Shared Journal" icon="book" href="#how-it-works">
    Coordinates work across users, agents, and external systems via an ordered
    event stream.
  </Card>

  <Card title="SQL Database" icon="database" href="/docs/axons/sql">
    Embedded SQLite for structured state such as configuration, task queues, or
    key-value pairs.
  </Card>
</CardGroup>

<Tip>
  For a step-by-step Broker + ACP walkthrough with runnable TypeScript, see the
  [Axon + ACP tutorial](/docs/tutorials/axon-acp-broker). Protocol details and
  mount configuration are in [ACP protocol adapter](/docs/axons/broker/acp).
</Tip>

## Quick Start

### Step 1: Create an Axon

```typescript theme={null}
import { RunloopSDK } from "@runloop/api-client";

const runloop = new RunloopSDK();
const axon = await runloop.axon.create({ name: "my-channel" });
console.log(`Created axon: ${axon.id}`);
```

The `name` parameter is optional. If omitted, an unnamed axon is created.

### Step 2: Publish Events

```typescript theme={null}
const result = await axon.publish({
  source: "app",
  event_type: "user.message",
  origin: "USER_EVENT",
  payload: JSON.stringify({
    content: "Review the latest failing build and suggest a fix.",
  }),
});

console.log(`Published with sequence: ${result.sequence}`);
```

Each publish call returns a `PublishResultView` with the assigned `sequence` number and `timestamp_ms`. Sequence numbers are monotonically increasing, so you can use them to track ordering.

### Step 3: Subscribe to Events

```typescript theme={null}
const stream = await axon.subscribeSse();

for await (const event of stream) {
  console.log(`Seq ${event.sequence}: [${event.source}] ${event.event_type}`);
  console.log(`  Origin: ${event.origin}`);
  console.log(`  Payload: ${event.payload}`);
}
```

The SSE stream delivers `AxonEventView` objects in sequence order. The stream stays open until you break out of the loop or the connection is closed.

## How It Works

```mermaid theme={null}
sequenceDiagram
    participant Client as External Client
    participant Axon as Axon
    participant Broker as Broker
    participant Agent as Agent

    Client->>Axon: publish(user.message)
    Note over Axon: Appends event, assigns sequence number
    Axon->>Broker: Deliver inbound event
    Broker->>Agent: Forward one turn
    Agent-->>Broker: Stream output
    Broker-->>Axon: publish(turn.message_chunk, turn.completed)
    Axon-->>Client: SSE subscription stream
```

1. **Create an Axon** — each Axon is a named event stream scoped to your account.
2. **Publish input events** — users, orchestrators, webhooks, and other systems append structured events to the stream.
3. **Bridge through Broker** — Broker reads incoming events and forwards them to the agent one turn at a time.
4. **Record agent output** — broker-emitted events such as `turn.message_chunk` and `turn.completed` are appended back to the same Axon.
5. **Subscribe via SSE** — clients observe the stream in order using sequence numbers.

## Event Structure

Each event carries:

* A **source** identifying where it came from (e.g. `github`, `slack`, `my-agent`)
* An **event type** describing what happened (e.g. `push`, `task_complete`, `review_requested`)
* An **origin** classifying who produced it (`EXTERNAL_EVENT`, `AGENT_EVENT`, `USER_EVENT`)
* A **payload** containing the event data as a JSON string
* A monotonic **sequence number** for ordering

Each event delivered via SSE includes the full event metadata stored in the Axon stream:

| Field          | Type     | Description                                                  |
| -------------- | -------- | ------------------------------------------------------------ |
| `sequence`     | `number` | Monotonically increasing sequence number                     |
| `axon_id`      | `string` | The axon this event belongs to                               |
| `timestamp_ms` | `number` | Timestamp in milliseconds since epoch                        |
| `origin`       | `string` | Event origin classification (see below)                      |
| `source`       | `string` | Event source identifier (e.g. `github`, `slack`, `my-agent`) |
| `event_type`   | `string` | Event type identifier (e.g. `push`, `task_complete`)         |
| `payload`      | `string` | JSON-encoded event payload                                   |

### Origins

Origins classify who produced the event. When **publishing**, you can use three origin types:

| Origin           | Description                                                       | Example                                                    |
| ---------------- | ----------------------------------------------------------------- | ---------------------------------------------------------- |
| `EXTERNAL_EVENT` | Events from external systems (webhooks, CI, third-party services) | GitHub push, Slack message                                 |
| `AGENT_EVENT`    | Events produced on behalf of an agent                             | Broker-published agent output such as `turn.message_chunk` |
| `USER_EVENT`     | Events produced by a human user or user-facing application        | `user.message`, manual approval                            |

When **subscribing**, you may also receive:

| Origin         | Description                                                  |
| -------------- | ------------------------------------------------------------ |
| `SYSTEM_EVENT` | Events generated by the Runloop platform or broker lifecycle |

## Use Cases

### User-To-Agent Turns

Publish user input into an Axon, then subscribe for broker-published turn output from the attached agent. The Broker handles forwarding messages to the agent and streaming responses back through the Axon.

For a complete walkthrough with code, see [Send a simple message to an ACP agent](/docs/axons/broker/acp#send-a-simple-message-to-an-acp-agent).

### Webhook and Automation Fan-In

Use a single Axon as the shared journal for events coming from external systems, then let downstream subscribers and brokers react in order.

```typescript theme={null}
import { RunloopSDK } from "@runloop/api-client";

const runloop = new RunloopSDK();
const axon = await runloop.axon.create({ name: "repo-automation" });

await axon.publish({
  source: "github",
  event_type: "push",
  origin: "EXTERNAL_EVENT",
  payload: JSON.stringify({ ref: "refs/heads/main", repository: "runloop" }),
});

await axon.publish({
  source: "slack",
  event_type: "incident.created",
  origin: "EXTERNAL_EVENT",
  payload: JSON.stringify({ channel: "#alerts", severity: "high" }),
});

const stream = await axon.subscribeSse();
for await (const event of stream) {
  console.log(`${event.sequence}: ${event.source} -> ${event.event_type}`);
}
```

### Reconnecting to an Existing Axon

If you already have an axon ID (e.g. stored from a previous session), you can reconnect without creating a new one.

```typescript theme={null}
import { RunloopSDK } from "@runloop/api-client";

const runloop = new RunloopSDK();
const axon = runloop.axon.fromId("axn_abc123");

const info = await axon.getInfo();
console.log(`Axon: ${info.name}, created: ${new Date(info.created_at_ms)}`);

const stream = await axon.subscribeSse();
for await (const event of stream) {
  console.log(event);
}
```

## Managing Axons

List active Axons or retrieve an existing one by ID:

```typescript theme={null}
import { RunloopSDK } from "@runloop/api-client";

const runloop = new RunloopSDK();

// List all active Axons
const axons = await runloop.axon.list();
for (const axon of axons) {
  const info = await axon.getInfo();
  console.log(`${info.id}: ${info.name ?? "(unnamed)"}`);
}

// Retrieve a specific Axon by ID
const axon = runloop.axon.fromId("axn_abc123");
const info = await axon.getInfo();
console.log(`Name: ${info.name}, Created: ${new Date(info.created_at_ms)}`);
```

## Limitations

* Axon events are immutable. Once an event is published, it cannot be modified or deleted.
* Axons are currently limited to 10GB of event data.

## Related Documentation

* [Remote Agents SDK](/docs/axons/sdk) — TypeScript client for interacting with remote agents over Axon
* [SQL Database](/docs/axons/sql) — Embedded SQLite for structured state within an Axon
* [Broker](/docs/axons/broker) — Learn how Runloop bridges Axons to agents running in Devboxes
* [Devbox Overview](/docs/devboxes/overview) — Learn about Runloop's isolated development environments
* [Tunnels](/docs/devboxes/tunnels) — Expose services running in a Devbox
* [Agent Gateways](/docs/devboxes/agent-gateways) — Securely proxy API requests
