Skip to main content
REST surface for the agent registry-of-record. SQLite is the source of truth for who exists; an AgentSource syncs each upstream (the OpenClaw Gateway, the in-process native runtime) INTO SQLite. Reads serve SQLite, so the agent list, an agent record, and an agent’s files keep answering even when the Gateway connection is down; a stale flag marks that case. Writes (create), file PUTs, and live session lists delegate to the owning source and return 503 when the source needs a live upstream that is disconnected.
These routes return clawboo-native AgentRecord shapes, not OpenClaw protocol shapes. The OpenClawAgentSource adapts the Gateway in both directions; a native (clawboo-native) agent is owned by a peer source whose reads and writes are pure SQLite and always work offline. See agent-source internals for the sync discipline and agent model for the registry concept.
Per-agent routes are multi-source: each operation routes to the source that OWNS the row (its sourceId); an unknown id falls back to the default (OpenClaw) source so its 404 semantics hold. All POST/PUT routes read a JSON body parsed by express.json({ limit: '2mb' }). Static paths (sync, registry/health, cleanup-ghosts) are registered BEFORE /:agentId so the param does not swallow them.

Routes

The AgentRecord shape (returned by GET /api/agents, GET /api/agents/:agentId, and inside the create 201):
participantKind: 'human', runtime values beyond openclaw, and tenantId are documented future seams. Today every Gateway-synced record is participantKind: 'agent', runtime: 'openclaw', tenantId: null.

GET /api/agents

The primary fleet read. Aggregates listAgents() across EVERY registered source (OpenClaw + native), all SQLite-backed, so the list answers even when the Gateway connection is down. defaultId is the runtime-neutral Boo Zero (resolveBooZero: an explicit override, then the native Boo Zero, then the OpenClaw default), so a native-first install identifies its native Boo Zero. mainKey, stale, and lastSyncedAt stay OpenClaw-derived; stale is true whenever the server-side OpenClaw connection is not connected.
  • Path params: none.
  • Query params:
  • Request body: none.

Responses

200 OK: the merged agent list:
500 Internal Server Error: any failure listing or probing health:

Example


POST /api/agents

Creates an agent through a source. The default source is OpenClaw (Gateway create + agent-file writes + SQLite mirror), which returns 503 when the server-side connection is down. An optional sourceId routes the create to a peer source; clawboo-native writes are pure SQLite and always succeed.
  • Path/query params: none.
  • Request body:

Responses

201 Created: the agent was created:
400 Bad Request: missing or blank name:
400 Bad Request: sourceId does not name a registered source:
503 Service Unavailable: the source needs a live Gateway connection that is down:
500 Internal Server Error: any other failure:

Example


POST /api/agents/sync

Reconciles the OpenClaw source’s upstream into SQLite. With a body it runs the browser-fallback path: the browser, connected through the proxy, pushes its own agents.list result so SQLite warms WITHOUT the server’s own connection. With no body it runs the server-side sync(), which needs the connection and returns 503 when down.
  • Path/query params: none.
  • Request body (optional: present ⇒ browser-fallback upsert):
The fallback path triggers only when agents is an array. The upsert touches Gateway-owned columns only; SQLite-native columns (teamId, personalityConfig, execConfig, avatarSeed, etc.) are preserved.

Responses

200 OK: browser-fallback upsert (body with an agents array):
200 OK: server-side sync (no body):
archived counts agents present in SQLite but gone upstream (their archivedAt is set; the tombstone is reversible and cleared if the agent reappears). 503 Service Unavailable: the server-side sync needs a live connection that is down:
500 Internal Server Error: any other failure:

Example


GET /api/agents/registry/health

Reports the server-side OpenClaw connection state. Always returns 200 (it is a liveness surface, not gated by the connection). Registered before /:agentId so the param does not swallow registry.
  • Path/query params: none.
  • Request body: none.

Responses

200 OK: the OpenClaw source’s HealthResult:
500 Internal Server Error: a failure reading health:

Example


POST /api/agents/cleanup-ghosts

A one-shot sweep the client invokes after hydrating from the Gateway. The caller passes the IDs of all agents currently alive in the Gateway; the endpoint deletes every local OpenClaw-owned SQLite row NOT in that list, plus its FK-referenced cost / approval rows and its per-agent settings keys. Idempotent. The sweep is scoped to sourceId = 'openclaw'; the live-id list comes from the Gateway, so native (and any future peer-source) agents are never ghosts of it.
  • Path params: none.
  • Query params:
  • Request body:

Responses

400 Bad Request: liveAgentIds is missing or not an array:
400 Bad Request: liveAgentIds is empty and ?allowEmpty=true was not passed:
200 OK: no local rows were stale:
200 OK: stale rows were swept (remaining is the post-sweep agent count, or null):
500 Internal Server Error: a failure during the sweep:

Example


GET /api/agents/:agentId

Returns one agent record, routed to the source that owns the row.
  • Path params: agentId.
  • Request body: none.

Responses

200 OK: the record:
400 Bad Request: missing agentId segment:
404 Not Found: no agent with that id in its source:
500 Internal Server Error: a failure reading the record:

Example


DELETE /api/agents/:agentId

Archives the agent. The owning source’s archiveAgent deletes the upstream record THEN cleans the local SQLite row plus its FK-referenced children (cost_records, approval_history) and per-agent settings keys. If the server-side Gateway connection is down, it falls back to a SQLite-only cleanup so the local row never rots, disconnect tolerance.
The browser is responsible for the Gateway agents.delete RPC; this endpoint cleans up clawboo’s local metadata. Without it, deleted agents leave permanent ghost rows that inflate per-team agentCount.
  • Path params: agentId.
  • Request body: none.

Responses

200 OK: archived upstream and locally:
200 OK: the Gateway was disconnected, so only the local rows were cleaned:
400 Bad Request: missing agentId segment:
500 Internal Server Error: a non-disconnect failure during archive:

Example


GET /api/agents/:agentId/files/:name

Reads one agent file through the owning source. The :name segment must be one of the seven canonical agent file names; any other name is rejected before the read.
  • Path params: agentId, name.
:nameAGENTS.md, SOUL.md, IDENTITY.md, USER.md, TOOLS.md, HEARTBEAT.md, MEMORY.md.
  • Request body: none.

Responses

200 OK: the file content:
400 Bad Request: missing agentId segment:
400 Bad Request: :name is not a recognized agent file:
503 Service Unavailable: the owning source needs a live Gateway that is down:
500 Internal Server Error: any other failure:

Example


PUT /api/agents/:agentId/files/:name

Writes one agent file through the owning source. Same :name allowlist as the read; the body must carry a string content.
  • Path params: agentId, name (same allowlist as the GET).
  • Request body:

Responses

200 OK: the file was written (echoes what was stored):
400 Bad Request: missing agentId segment:
400 Bad Request: :name is not a recognized agent file:
400 Bad Request: content is missing or not a string:
503 Service Unavailable: the owning source needs a live Gateway that is down:
500 Internal Server Error: any other failure:

Example


GET /api/agents/:agentId/sessions

Lists the agent’s live sessions. The OpenClaw source delegates LIVE to the Gateway (sessions are runtime-volatile), so this route returns 503 when the connection is down.
  • Path params: agentId.
  • Request body: none.

Responses

200 OK: the session list:
400 Bad Request: missing agentId segment:
503 Service Unavailable: the source needs a live Gateway that is down:
500 Internal Server Error: any other failure:

Example


PATCH /api/agents/:agentId/model

Changes a native agent’s model. It rewrites the stored AgentConfig.primaryModel (the next run reads it), so it needs no Gateway. This route is native-only: an OpenClaw agent changes its model through the OpenClaw config path (PATCH /api/system/openclaw-config { agentModel }), so this route returns 404 for a non-native agent.
  • Path params: agentId.
  • Request body: { model: string } (a native-catalog model id, e.g. claude-sonnet-4-6).

Responses

200 OK: the model was saved:
400 Bad Request: missing agentId segment, or an empty model:
404 Not Found: unknown agent, or a non-native agent (model change via this route is native-only):
500 Internal Server Error: any other failure:

Example


POST /api/agents/:agentId/chat

Drives ONE conversational turn on a clawboo-native agent’s 1:1 personal chat (the Boo Zero personal chat, and any native agent’s chat). The handler persists the user turn under the session key agent:<agentId>:native, then starts the run detached and returns 202 immediately: the reply streams over the SSE route below and is persisted by the native driver, so it is not aborted when the client disconnects. OpenClaw agents keep the Gateway 1:1 path, so this route returns 404 for a non-native agent.
  • Path params: agentId.
  • Request body:

Responses

202 Accepted: the user turn was persisted and the reply run started:
400 Bad Request: missing agentId segment:
400 Bad Request: missing or blank message:
404 Not Found: the agent’s runtime is not clawboo-native:
500 Internal Server Error: any other failure:

Example


POST /api/agents/:agentId/chat/stop

Aborts the agent’s in-flight 1:1 turn (the composer’s Stop button). A native agent has at most one live 1:1 run, tracked by its session key; with nothing in flight the call is a no-op that still returns 200.
  • Path params: agentId.
  • Request body: none.

Responses

200 OK: the run was aborted, or there was nothing to abort:
400 Bad Request: missing agentId segment:
500 Internal Server Error: a failure during abort:

Example


GET /api/agents/:agentId/chat/stream

Server-Sent Events live-tail of the agent’s 1:1 session (agent:<agentId>:native). A pure reader: it never drives a turn. Two tiers: committed chat_messages rows polled every 750 ms past an id cursor (durable, carry an id: line, replayed on resume), and ephemeral in-memory bus frames for live tokens and run state (no id: line, never replayed). Resume from a known position via the standard EventSource Last-Event-ID header or the ?since=<id> query param.
  • Path params: agentId.
  • Query params:
  • Request body: none.
This is an SSE route, not request/response. There is no JSON response body; the catalog below describes the wire frames. A missing agentId ends the response with a bare 400 and no { error } envelope. Each polled batch reads up to 500 rows past the cursor. The stream is cleaned up on req/res close and closes no database handle (the tail reads through the process-wide shared connection).

Connection

On open, the handler writes HTTP/1.1 200 with:
then emits a : connected comment frame, flushes any rows already past the cursor, and replays the session’s last-published status per agent so a reconnecting client reconciles a badge left stale by a run that ended while no stream was open.

Event catalog

delta and status frames carry no id: line, so they never move the resume cursor: the committed rows are the source of truth, and the deltas only make a turn type out live.

Example


Error envelope

Every error response on these routes is the standard { error: string } envelope, except the SSE route (GET /api/agents/:agentId/chat/stream), whose only pre-stream failure is a bare 400 with no body. The disconnect case is a 503 with the literal { "error": "gateway_disconnected" } on the write/file/session routes; the read routes (GET /api/agents, GET /api/agents/:agentId) keep serving SQLite instead.

See also

Last modified on August 8, 2026