Skip to main content

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.

Install @runloop/agent-axon-client to follow along. See the SDK repository and full SDK documentation.

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.

Broker

Bridges Axons to agents running in Devboxes, forwarding events one turn at a time.

Shared Journal

Coordinates work across users, agents, and external systems via an ordered event stream.

SQL Database

Embedded SQLite for structured state such as configuration, task queues, or key-value pairs.
For a step-by-step Broker + ACP walkthrough with runnable TypeScript, see the Axon + ACP tutorial. Protocol details and mount configuration are in ACP protocol adapter.

Quick Start

Step 1: Create an Axon

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

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

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

  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:
FieldTypeDescription
sequencenumberMonotonically increasing sequence number
axon_idstringThe axon this event belongs to
timestamp_msnumberTimestamp in milliseconds since epoch
originstringEvent origin classification (see below)
sourcestringEvent source identifier (e.g. github, slack, my-agent)
event_typestringEvent type identifier (e.g. push, task_complete)
payloadstringJSON-encoded event payload

Origins

Origins classify who produced the event. When publishing, you can use three origin types:
OriginDescriptionExample
EXTERNAL_EVENTEvents from external systems (webhooks, CI, third-party services)GitHub push, Slack message
AGENT_EVENTEvents produced on behalf of an agentBroker-published agent output such as turn.message_chunk
USER_EVENTEvents produced by a human user or user-facing applicationuser.message, manual approval
When subscribing, you may also receive:
OriginDescription
SYSTEM_EVENTEvents 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.

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.
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.
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:
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.
  • Client SDK — TypeScript client for interacting with agents over Axon
  • SQL Database — Embedded SQLite for structured state within an Axon
  • Broker — Learn how Runloop bridges Axons to agents running in Devboxes
  • Devbox Overview — Learn about Runloop’s isolated development environments
  • Tunnels — Expose services running in a Devbox
  • Agent Gateways — Securely proxy API requests