arizuko

arizuko › concepts › a turn, end to end

a turn, end to end

A turn is everything that happens between a person typing a message and the reply of the ant in their chat. Two daemons do the work: routd owns the conversation, runed owns the guest, and one HTTP call joins them.

The whole path fits in one figure

Read the figure from top to bottom. Each box names the function that does the work, so you can open the file and check it.

┌─────────┐ POST /v1/messages ┌──────────────────────────────────────────┐
│ adapter ├──────────────────►│ routd: PutMessage appends the row        │
└─────────┘                   │ Enqueue; pollOnce re-enqueues a routed   │
     ▲                        │ chat with rows past its agent_cursor     │
     │                        └────────────────────┬─────────────────────┘
     │                                             ▼
     │                        ┌──────────────────────────────────────────┐
     │                        │ GroupQueue: one worker per folder        │
     │                        │ resolve: direct ► routes ► engagement    │
     │                        │ steer: sticky, /command, @child          │
     │                        └────────────────────┬─────────────────────┘
     │                                             ▼
     │ Typing on              ┌──────────────────────────────────────────┐
     │◄───────────────────────┤ runTurn: budget gate, topic fork, prompt │
     │ beginProgress          │ turn_context, session id, MCP socket     │
     │                        │ bound, acl ► typed booleans              │
     │                        └────────────────────┬─────────────────────┘
     │                                             │ POST /v1/runs
     │                                             │ RunOutcome returns
     │                                             ▼
     │                        ┌──────────────────────────────────────────┐
     │                        │ runed Manager.Run: a live folder or a    │
     │                        │ full cap answers busy, else it claims    │
     │                        │ the spawns row                           │
     │                        └────────────────────┬─────────────────────┘
     │                                             ▼
     │                        ┌──────────────────────────────────────────┐
     │                        │ qemuRuntime.spawn: VM record, staging    │
     │                        │ tree, virtiofsd, tap + rules, egress,    │
     │                        │ vsock ports, MCP bridge, QEMU            │
     │                        └────────────────────┬─────────────────────┘
     │                                             │ ctl frame: env, stdin
     │                                             ▼
     │                        ┌──────────────────────────────────────────┐
     │                        │ guest-init, PID 1: mounts, socat from    │
     │                        │ /run/ipc/gated.sock to vsock, then ant   │
     │                        └────────────────────┬─────────────────────┘
     │                                             │ reply, send,
     │                                             │ submit_status,
     │                                             │ submit_turn
     │                                             ▼
     │ POST <url>/send        ┌──────────────────────────────────────────┐
     │ the reply rows and     │ bridge ► routd socket: mcpDeliver ►      │
     │ one edited status row  │ chanDeliverer.Send; submit_turn ►        │
     └────────────────────────┤ recordTurnResult (outcome only)          │
                              └──────────────────────────────────────────┘

routd decides and runed runs

The adapter posts the message to routd, and routd writes it with PutMessage. No other function appends a message. For this reason, one cursor per chat, agent_cursor, is enough to record what the agent has seen. A worker for each folder then picks the folder and builds the prompt. Before the worker calls runed, it binds the MCP socket of the folder and records the session id that the turn resumes. The order matters for two reasons. An agent whose first tool call finds no socket answers nothing. A turn that stops halfway must find its session on the next attempt.

runed answers POST /v1/runs in one of three ways:

The guest runs one program before the agent

The guest boots a pinned kernel with one small program as PID 1, guest-init. guest-init mounts the folder at /home/node, starts socat on /run/ipc/gated.sock and runs the agent. A tool call goes from the agent to that socket, then over vsock to a bridge on the host, then into routd. The host kernel stamps the vsock CID of the guest on every packet, and the bridge accepts only the CID of its own guest. That stamp is the credential of the turn, so the agent has no token that a shell command could leak.

guest-init builds the root of the guest with overlayfs. The release rootfs is the read-only lower layer, and a tmpfs of 512 MiB in RAM is the writable upper layer. The folder home at /home/node and the web directories come from the host and keep what the agent writes. A write anywhere else is gone when the guest powers off. [kernel/guest-init/main.go]

A child folder runs in its own guest

A turn runs in one folder, and a folder has at most one live guest. To give work to a sub-agent on a second machine, an agent uses a second folder:

The parent turn and the child turn run at the same time, in two guests. MAX_CONCURRENT_CONTAINERS, default 5, caps the live guests of the whole instance. Past the cap, runed answers busy and routd tries again.

Three more limits apply:

The reply goes out through the adapter

The agent calls reply or send, and routd delivers the message through the /send endpoint of the adapter. Only an explicit delivery tool (reply, send, send_file or send_voice) reaches the user. The text of the final result stays in the transcript. If an addressed turn ends without a delivery, routd asks the agent once more through the submit_turn response. While the agent works, the chat shows typing. On a channel that can edit messages, one status line changes in place. When the guest powers off, runed returns the outcome on the same HTTP call that started the turn.

A failed delivery stays as a pending row, and a sweep tries it again with a longer gap each time. A turn that stops without a reply runs again up to three times. The exception is a failure that runed marks terminal, because a retry cannot fix it. Examples are a VMM that does not start and an exhausted model quota.

Five pages explain the parts of the figure