serve api and serve chat
serve api is the native control plane, with sessions and an SSE run stream on port 8080, and serve chat is the OpenAI-compatible subset on port 8083.
Your agent file answers well in a terminal, and now a web page and a CI job must call it over HTTP. The page wants every event of a turn, and the CI job speaks only the OpenAI chat format.
When you finish this section, you can start both servers on one agent file, run one turn through each, and read the event stream.
One agent file, five servers
The a2a, acp, api, and mcp commands moved under serve in v1.23.4, and v1.53.0 added chat. The help of v1.149.0 lists all five. This part serves one small file all five ways. Its one agent, root, answers every message with the word pong:
version: "16"
agents:
root:
model: local
description: Answers every message with the word pong.
instruction: Reply with exactly the word pong and nothing else.
models:
local:
provider: dmr
model: ai/qwen3:4b
temperature: 0serve api: a session, then a run
serve api: the command that serves your agents through a REST-style API with Server-Sent Events (SSE) streaming. It listens on 127.0.0.1:8080, and an empty --auth-token means no authentication. --max-request-size rejects a body over 1 MiB with HTTP 413, and --session-workingdir-root confines the working_dir of new sessions.
The session database has a different default here. serve api -s writes session.db in the current directory, while run, serve a2a, and serve acp use <data-dir>/session.db (conflict C63). The capture passed -s work/api-session.db and --fake work/cassettes/23-api, so a recorded cassette gave the model answer (the capture).
…
"name": "pong",
"description": "Answers every message with the word pong.",
"multi": false
…
…
{}
…
"id": "<uuid>",
"origin": "run",
…
"tools_approved": false,
…
…
Accept: text/event-stream
…
"role": "user",
"content": "ping"
…
…
Content-Type: text/event-streamThe agent identifier in the path is the file name without .yaml, so pong, while the events name the agent root. The run body is a messages array with an optional model field, which sets a model override for that agent in the session. A new session has tools_approved: false. A tool call then raises tool_call_confirmation, and the client answers with POST /api/sessions/:id/resume.
The run stream
{"agent_name": "root", "available_agents": [{"description": "Answers every message with the word pong.", "model": "ai/qwen3:4b", "name": "root", "provider": "dmr"}], "current_agent": "root", "timestamp": "<ts>", "type": "team_info"}
{"agent_name": "root", "available_tools": 0, "loading": false, "timestamp": "<ts>", "type": "toolset_info"}
{"message": "ping", "session_id": "<uuid>", "session_position": 0, "timestamp": "<ts>", "type": "user_message"}
{"agent_name": "root", "session_id": "<uuid>", "timestamp": "<ts>", "type": "stream_started"}
{"agent_name": "root", "available_tools": 0, "loading": false, "timestamp": "<ts>", "type": "toolset_info"}
{"agent_name": "root", "description": "Answers every message with the word pong.", "model": "dmr/ai/qwen3:4b", "timestamp": "<ts>", "type": "agent_info"}
{"agent_name": "root", "content": "pong", "message_id": "<uuid>", "session_id": "<uuid>", "timestamp": "<ts>", "type": "agent_choice"}
{"agent_name": "root", "session_id": "<uuid>", "timestamp": "<ts>", "type": "message_added"}
{"agent_name": "root", "session_id": "<uuid>", "timestamp": "<ts>", "type": "token_usage", …}
{"agent_name": "root", "finish_reason": "stop", "reason": "normal", "session_id": "<uuid>", "timestamp": "<ts>", "type": "stream_stopped"}The docs list eight event types, from stream_started to error. The capture adds six more: team_info, toolset_info, user_message, agent_info, message_added, and token_usage. The pong agent has no tools, so no tool_call frame appears. The last request of the file reads the session back, and the stored assistant message keeps the model's reasoning_content beside the answer (the capture). Figure 6.1 draws the whole exchange.
A run is one request, and the session outlives it. /steer injects messages into a running turn, /followup queues them with an optional Idempotency-Key, and /fork copies a session up to a user message. GET /api/sessions/:id/events is a session-wide stream that resumes from Last-Event-ID or ?since=. An interactive docker-agent run --listen ADDR serves the same control plane, with a fixed 1 MiB body limit and no --auth-token. The help does not list that flag, and each such run writes <data-dir>/runs/<pid>.json for discovery.
serve chat: the OpenAI-compatible subset
serve chat: the command that serves the agent through an OpenAI-compatible API at /v1/chat/completions and /v1/models. The model id is the agent name, so the capture lists root, the name inside the file, where serve api used pong, the file name.
…
Authorization: Bearer m101-chat-key
…
"id": "root",
…
"owned_by": "docker-agent",
…
…
"role": "assistant",
"content": "pong"
…
…
"message": "missing or invalid bearer token",By default the server keeps no conversation, so each request carries the full history. --conversations-max caches up to N conversations keyed by X-Conversation-Id, and --conversation-ttl evicts them after 30 minutes. A failed turn leaves a cached conversation unchanged, so a client can retry with the same id. With stream: true in the body, the reply is an SSE stream of chat.completion.chunk deltas. --max-idle-runtimes keeps four idle runtimes per agent, and --request-timeout bounds each request at five minutes, model and tool calls included. A listener that is not on loopback needs --api-key, --api-key-env, or --insecure-no-auth.
serve api | serve chat | |
|---|---|---|
| Default address | 127.0.0.1:8080 | 127.0.0.1:8083 |
| Token flag | --auth-token | --api-key or --api-key-env |
| Agent id in the capture | pong, the file name | root, the agent name |
| State | sessions in -s, default session.db | none, or a cache keyed by X-Conversation-Id |
| Tool approval | tool_call_confirmation, then resume | --safety, default restricted |
| Cassettes | --fake and --record | none |
Docker Sandboxes (sbx) and Docker Agent (docker-agent) sbx 0.47.0, docker-agent 1.149.0 · bf4169c · 2026-10-07 · Edition 2026.10