two new ways to embed the zot agent runtime in third-party apps:
1. pkg/zotcore - public Go SDK
- Runtime type: New(Config), Prompt(ctx,text,imgs)->chan Event,
Cancel, Compact, SetModel, State, Messages, Cost, ListModels,
Close. Concurrent-safe; one prompt at a time per Runtime,
ErrBusy if you try to overlap. Spawn multiple Runtimes for
multiple projects.
- Public types mirror the JSON-RPC wire schema 1:1 so consumers
can share parsing code with the out-of-process clients.
- Internal core/agent/provider stay internal; SDK is a thin
facade that exposes only what's stable.
2. zot rpc subcommand - newline-delimited JSON on stdin/stdout
- 'zot rpc' (or 'zot --rpc') turns the agent runtime into a
subprocess that any language can drive via pipes.
- Commands: hello, prompt, abort, compact, get_state,
get_messages, clear, set_model, get_models, ping. Each
optionally carries an id; the matching response echoes it.
- Stream notifications: turn_start, user_message,
assistant_start, text_delta, tool_call, tool_progress,
tool_result, assistant_message, usage, turn_end, done,
error, compact_done. Same shape as the existing --json mode
events (modes.EventToJSON / ContentToJSON were exported
for reuse).
- Auth: optional ZOTCORE_RPC_TOKEN env var; first command
must be hello {token: ...} when set. Without the env var
the spawning process is implicitly trusted.
- Concurrency: one prompt or compact at a time per process,
enforced by a turnMu mutex. abort fires immediately
regardless. Stdin close exits the process.
3. docs/rpc.md - full schema reference
4. examples/rpc/{python,node,shell,go} - reference clients
5. examples/sdk - in-process Go embedding example
6. README updated with a new modes entry and an embedding section
7.3 KiB
zot RPC
zot rpc runs the agent runtime as a subprocess that speaks newline-delimited JSON on stdin and stdout. Use it from any language that can spawn a process and read/write its pipes — Go, TypeScript, Python, Rust, shell, anything.
For a Go program embedding the runtime in-process, use the pkg/zotcore SDK instead. The wire format below mirrors the SDK's types one-for-one so consumers can share parsing code.
Quick start
# spawn zot rpc; talk to it from a shell
( echo '{"id":"1","type":"prompt","message":"hello"}'; sleep 5 ) \
| zot rpc --provider anthropic
You'll see one JSON object per line on stdout: a response acknowledging the prompt, a stream of events (text_delta, tool_call, tool_result, usage), then done.
Process model
- One
zot rpcprocess serves one cwd, one model, one session. - For multiple projects, spawn multiple processes.
- Concurrency: at most one prompt or compact in flight at a time. A second one queues until the first finishes; aborting fires immediately.
- The process exits when stdin closes.
Flags
zot rpc accepts the same flags as the other modes: --provider, --model, --cwd, --api-key, --base-url, --system-prompt, --append-system-prompt, --reasoning, --max-steps, --no-tools, --tools. Sessions are disabled by default in RPC mode — the embedding application owns persistence.
Auth
If the environment variable ZOTCORE_RPC_TOKEN is set on the spawned process, the first line on stdin must be a hello command containing the matching token:
{"id":"0","type":"hello","token":"shared-secret"}
If absent or wrong, the response carries success:false and the process exits. Without ZOTCORE_RPC_TOKEN set, no auth is required (the spawning process is implicitly trusted; if it can spawn zot it can also read your auth.json directly).
Wire format
Every line in either direction is one JSON object terminated by \n. Object boundaries follow newline boundaries — no multi-line JSON.
Frame types
type |
Direction | Description |
|---|---|---|
any command (prompt, abort, …) |
client → server | Request |
response |
server → client | Reply to one command, correlated by id |
any event (text_delta, tool_call, …) |
server → client | Stream notification (no id) |
Commands
All commands share an optional id field; if present, the matching response echoes it. Use id to correlate replies with requests, especially when several requests are in flight.
hello
{"id":"0","type":"hello","token":"shared-secret"}
Response:
{"type":"response","id":"0","command":"hello","success":true,
"data":{"protocol_version":1,"version":"0.0.4","provider":"anthropic","model":"claude-opus-4-5"}}
Required as the first message when ZOTCORE_RPC_TOKEN is set; optional otherwise.
prompt
{"id":"1","type":"prompt","message":"fix the failing test","images":[]}
Optional images is [{"mime_type":"image/png","data":"<base64>"}].
Response is immediate (the turn is starting):
{"type":"response","id":"1","command":"prompt","success":true,"data":{"started":true}}
Then a stream of event objects (see below) until the turn ends with {"type":"done"}.
abort
Cancel the active prompt or compact.
{"id":"2","type":"abort"}
Response: {"type":"response","id":"2","command":"abort","success":true}.
If the turn was streaming, the next events you see will be a turn_end with stop:"aborted" then done.
compact
Summarise the current transcript into one synthetic user message. Same lifecycle as prompt (immediate response, then events).
{"id":"3","type":"compact"}
Final event: {"type":"compact_done","summary":"<text>"}.
get_state
Snapshot of the runtime.
{"id":"4","type":"get_state"}
Response data:
{
"provider": "anthropic",
"model": "claude-opus-4-5",
"cwd": "/Users/pat/Developer/zot",
"message_count": 12,
"busy": false,
"usage": {"input": 1234, "output": 567, "cache_read": 890, "cache_write": 0, "cost_usd": 0.0123}
}
get_messages
Full transcript.
{"id":"5","type":"get_messages"}
Response data: {"messages": [<message>, ...]}. See message shape below.
clear
Drop the entire transcript. Equivalent to the /clear slash command.
{"id":"6","type":"clear"}
set_model
Switch model within the same provider.
{"id":"7","type":"set_model","model":"claude-sonnet-4-5"}
Cross-provider swaps require relaunching zot rpc with the new --provider.
get_models
List models known for the current provider.
{"id":"8","type":"get_models"}
Response data: {"models":[{"id":"...","provider":"...","context_window":200000,"max_output":8192,"reasoning":true}, ...]}.
ping
Health check.
{"id":"9","type":"ping"}
Response: {"type":"response","id":"9","command":"ping","success":true,"data":{"pong":true}}.
Events
Stream notifications during a prompt or compact. None carry an id.
type |
Fields | Meaning |
|---|---|---|
turn_start |
step |
Beginning of one model call (max-steps loop iteration) |
user_message |
content, time |
The submitted prompt as it was added to the transcript |
assistant_start |
(none) | About to receive assistant streaming |
text_delta |
delta |
Partial assistant text. Concatenate to build the full reply |
tool_call |
id, name, args |
The model wants to call a tool |
tool_progress |
id, text |
Optional progress line from the tool while it runs |
tool_result |
id, is_error, content |
Tool finished |
assistant_message |
content, time |
Final assistant message after the model turn ends |
usage |
input, output, cache_read, cache_write, cost_usd, cumulative |
Per-turn + cumulative tokens / cost |
turn_end |
stop, optional error |
One model call finished. stop is end_turn, tool_use, length, error, or aborted |
done |
(none) | The whole prompt/compact completed (success or error) |
error |
message |
Non-fatal error message |
compact_done |
summary |
Compaction finished, summary text included |
Message shape
Used by get_messages and inside user_message / assistant_message events.
{
"role": "user",
"content": [<content_block>...],
"time": "2026-04-19T11:30:00Z"
}
Content block types
{"type": "text", "text": "..."}
{"type": "image", "mime_type": "image/png", "bytes": 12345}
{"type": "tool_call", "id": "toolu_xyz", "name": "read", "args": {"path": "..."}}
{"type": "tool_result", "call_id": "toolu_xyz", "is_error": false, "content": [<content_block>...]}
Image bytes are reported as a count rather than embedded base64 to keep transcript dumps small. Tool results may nest text and image blocks.
Reference clients
See examples/rpc/ for working implementations in:
shell—bash+jqone-linerpython—subprocess.Popen+json.loadsper linenode—child_process.spawn+readlinego— direct subprocess wrapper (thepkg/zotcoreSDK is the in-process Go API)
Versioning
The protocol_version field in the hello response is the major version of this schema. Backwards-incompatible changes bump it. The set of supported events and commands within a major version only grows.