Skip to main content

Overview

The Claude adapter connects Broker to a Claude Code CLI subprocess running inside your Devbox. It launches Claude Code with --output-format stream-json and forwards streaming JSONL messages between Broker and the CLI. For the full wire protocol specification, see the Protocol Spec.

Getting started

To try out fully working code examples, check out the axon-broker-agents example repo. Clone the repo and run the test commands. The example will create a Devbox with Claude and start a “hello world” conversation by publishing an Axon event.
git clone https://github.com/runloopai/runloop-examples \
  && cd runloop-examples/axon-broker-agents \
  && uv run axon_claude_docs.py

Broker mount configuration

{
  "type": "broker_mount",
  "axon_id": "<axon-id>",
  "protocol": "claude_json"
}
FieldTypeDescription
typestringMust be broker_mount
axon_idstringRequired. The Axon stream Broker reads from and writes to
protocolstringMust be claude_json

How Broker uses Claude JSONL

  • Broker launches Claude Code with --output-format stream-json when the Devbox starts
  • Claude initializes and sets up the session
  • Publish query to the Axon to start a turn
  • Broker translates that event into Claude’s user JSONL input
  • Claude output is republished to Axon. Event event_type represents the Claude message type to deserialize.

Send a simple message to Claude

Create a Devbox with a Claude Broker mount and send a ‘hello world’ message to Claude.
1

Create Devbox with Broker

Create an Axon for communication and launch a Devbox with Claude Code mounted via Broker:
# Create Axon for agent communication
axon = await sdk.axon.create(name="claude-session")

broker_mount: BrokerMount = {
    "type": "broker_mount",
    "axon_id": axon.id,
    "protocol": "claude_json",
    "launch_args": [],
}

# Create a Devbox with Claude Code agent
devbox = await sdk.devbox.create(
    mounts=[broker_mount],
    launch_parameters={
        "launch_commands": [
            "curl -fsSL https://claude.ai/install.sh | bash && echo 'export PATH=\"$HOME/.local/bin:$PATH\"' >> ~/.bash_profile",
        ],
    },
    environment_variables={
        "PATH": "/home/user/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
        "ANTHROPIC_API_KEY": os.getenv("ANTHROPIC_API_KEY") or "",
    },
)
2

Publish User Prompt

Send a user message to Claude Code by publishing to the Axon:
await axon.publish(
    event_type="query",
    origin="USER_EVENT",
    source="axon_claude",
    payload=json.dumps(
        {
            "type": "user",
            "message": {
                "role": "user",
                "content": [{"type": "text", "text": user_prompt}],
            },
            "session_id": axon.id,
        }
    ),
)
3

Print Assistant Response

Subscribe to the Axon stream and print agent responses:
async with await axon.subscribe_sse() as stream:
    async for ev in stream:
        # Print assistant text content and finish
        if ev.event_type == "assistant":
            payload = json.loads(ev.payload)
            content = payload.get("message", {}).get("content", [])
            for block in content:
                if block.get("type") == "text":
                    print(block.get("text", ""))

Handling control requests from Claude

During a session, Claude may ask the user a question or request permission before taking an action - these arrive as control request events on the Axon stream. When you receive one, you can either prompt your user for input or respond automatically by publishing a control response.
{
  "type": "control_request",
  "request_id": "b0225af1-13c1-4897-9197-49a9f776fe20",
  "request": {
    "subtype": "can_use_tool",
    "tool_name": "AskUserQuestion",
    "input": {
      "questions": [
        {
          "header": "Fave foods",
          "multiSelect": true,
          "options": [
            { "label": "Pizza", "description": "A classic choice — hard to go wrong" },
            { "label": "Sushi", "description": "Fresh fish and rice, endless variety" },
            { "label": "Tacos", "description": "Versatile, flavorful, always a good time" },
            { "label": "Pasta", "description": "Comfort food at its finest" }
          ],
          "question": "What are your favorite foods?"
        }
      ]
    },
    "permission_suggestions": [],
    "tool_use_id": "toolu_019EVn3FjnenHfCsPFhFLFAR"
  }
}
There are many types of control responses for responding to Claude. See the Protocol Spec for complete details on all available response types and structures.

Interrupt a turn

Stop Claude’s current response by sending an interrupt control request:
await axon.publish(
    event_type="control_request",
    origin="USER_EVENT",
    source="axon_claude",
    payload=json.dumps(
        {
            "type": "control_request",
            "request_id": str(uuid.uuid4()),
            "request": {
                "subtype": "interrupt"
            }
        }
    ),
)

Building a complete integration

The examples above cover basic messaging and interrupts. To build a production-ready integration, you should become familiar with the full set of Claude control request and response types — these are the building blocks for handling every interaction Claude can initiate during a session. Claude may emit control requests for a variety of reasons, including:
  • Permission requests — Claude asks for approval before executing commands, writing files, or taking other actions
  • User questions — Claude asks the user for input or clarification
  • MCP server interactions — Claude requests access to MCP tool servers
Each control request type has a corresponding response structure that your application needs to handle. You can respond automatically (e.g., auto-approving safe commands) or route requests to a human for review. See the Claude JSONL Protocol Spec for the complete reference on all control request and response types, output message formats, and session lifecycle events.