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 packages/agent/sdk 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 rpc process 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, --no-context-files. 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).
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, "reasoning": 123, "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.
set_reasoning
Set the reasoning level for subsequent prompts without restarting the process. Accepted values are off, minimum, low, medium, high, xhigh, and max.
{"id":"8","type":"set_reasoning","reasoning":"max"}
The max tier is sent natively to supported models and clamped for other backends.
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, reasoning, cache_read, cache_write, cost_usd, cumulative |
Per-turn + cumulative tokens / cost. reasoning is null when unavailable. |
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 + jq one-liner
python — subprocess.Popen + json.loads per line
node — child_process.spawn + readline
go — direct subprocess wrapper (the packages/agent/sdk SDK 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.
source
1# zot RPC
2
3`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.
4
5For a Go program embedding the runtime in-process, use the `packages/agent/sdk` SDK instead. The wire format below mirrors the SDK's types one-for-one so consumers can share parsing code.
6
7## Quick start
8
9```bash
10# spawn zot rpc; talk to it from a shell
11( echo '{"id":"1","type":"prompt","message":"hello"}'; sleep 5 ) \
12 | zot rpc --provider anthropic
13```
14
15You'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`.
16
17## Process model
18
19- One `zot rpc` process serves **one cwd, one model, one session**.
20- For multiple projects, spawn multiple processes.
21- Concurrency: at most one prompt or compact in flight at a time. A second one queues until the first finishes; aborting fires immediately.
22- The process exits when stdin closes.
23
24## Flags
25
26`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`, `--no-context-files`. Sessions are disabled by default in RPC mode; the embedding application owns persistence.
27
28## Auth
29
30If 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:
31
32```json
33{"id":"0","type":"hello","token":"shared-secret"}
34```
35
36If 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).
37
38## Wire format
39
40Every line in either direction is one JSON object terminated by `\n`. Object boundaries follow newline boundaries — no multi-line JSON.
41
42### Frame types
43
44| `type` | Direction | Description |
45|---|---|---|
46| any command (`prompt`, `abort`, ...) | client → server | Request |
47| `response` | server → client | Reply to one command, correlated by `id` |
48| any event (`text_delta`, `tool_call`, ...) | server → client | Stream notification (no `id`) |
49
50## Commands
51
52All 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.
53
54### `hello`
55
56```json
57{"id":"0","type":"hello","token":"shared-secret"}
58```
59
60Response:
61
62```json
63{"type":"response","id":"0","command":"hello","success":true,
64 "data":{"protocol_version":1,"version":"0.0.4","provider":"anthropic","model":"claude-opus-4-5"}}
65```
66
67Required as the first message when `ZOTCORE_RPC_TOKEN` is set; optional otherwise.
68
69### `prompt`
70
71```json
72{"id":"1","type":"prompt","message":"fix the failing test","images":[]}
73```
74
75Optional `images` is `[{"mime_type":"image/png","data":"<base64>"}]`.
76
77Response is immediate (the turn is starting):
78
79```json
80{"type":"response","id":"1","command":"prompt","success":true,"data":{"started":true}}
81```
82
83Then a stream of event objects (see below) until the turn ends with `{"type":"done"}`.
84
85### `abort`
86
87Cancel the active prompt or compact.
88
89```json
90{"id":"2","type":"abort"}
91```
92
93Response: `{"type":"response","id":"2","command":"abort","success":true}`.
94
95If the turn was streaming, the next events you see will be a `turn_end` with `stop:"aborted"` then `done`.
96
97### `compact`
98
99Summarise the current transcript into one synthetic user message. Same lifecycle as `prompt` (immediate response, then events).
100
101```json
102{"id":"3","type":"compact"}
103```
104
105Final event: `{"type":"compact_done","summary":"<text>"}`.
106
107### `get_state`
108
109Snapshot of the runtime.
110
111```json
112{"id":"4","type":"get_state"}
113```
114
115Response data:
116
117```json
118{
119 "provider": "anthropic",
120 "model": "claude-opus-4-5",
121 "cwd": "/Users/pat/Developer/zot",
122 "message_count": 12,
123 "busy": false,
124 "usage": {"input": 1234, "output": 567, "reasoning": 123, "cache_read": 890, "cache_write": 0, "cost_usd": 0.0123}
125}
126```
127
128### `get_messages`
129
130Full transcript.
131
132```json
133{"id":"5","type":"get_messages"}
134```
135
136Response data: `{"messages": [<message>, ...]}`. See **message shape** below.
137
138### `clear`
139
140Drop the entire transcript. Equivalent to the `/clear` slash command.
141
142```json
143{"id":"6","type":"clear"}
144```
145
146### `set_model`
147
148Switch model within the same provider.
149
150```json
151{"id":"7","type":"set_model","model":"claude-sonnet-4-5"}
152```
153
154Cross-provider swaps require relaunching `zot rpc` with the new `--provider`.
155
156### `set_reasoning`
157
158Set the reasoning level for subsequent prompts without restarting the process. Accepted values are `off`, `minimum`, `low`, `medium`, `high`, `xhigh`, and `max`.
159
160```json
161{"id":"8","type":"set_reasoning","reasoning":"max"}
162```
163
164The `max` tier is sent natively to supported models and clamped for other backends.
165
166### `get_models`
167
168List models known for the current provider.
169
170```json
171{"id":"8","type":"get_models"}
172```
173
174Response data: `{"models":[{"id":"...","provider":"...","context_window":200000,"max_output":8192,"reasoning":true}, ...]}`.
175
176### `ping`
177
178Health check.
179
180```json
181{"id":"9","type":"ping"}
182```
183
184Response: `{"type":"response","id":"9","command":"ping","success":true,"data":{"pong":true}}`.
185
186## Events
187
188Stream notifications during a `prompt` or `compact`. None carry an `id`.
189
190| `type` | Fields | Meaning |
191|---|---|---|
192| `turn_start` | `step` | Beginning of one model call (max-steps loop iteration) |
193| `user_message` | `content`, `time` | The submitted prompt as it was added to the transcript |
194| `assistant_start` | (none) | About to receive assistant streaming |
195| `text_delta` | `delta` | Partial assistant text. Concatenate to build the full reply |
196| `tool_call` | `id`, `name`, `args` | The model wants to call a tool |
197| `tool_progress` | `id`, `text` | Optional progress line from the tool while it runs |
198| `tool_result` | `id`, `is_error`, `content` | Tool finished |
199| `assistant_message` | `content`, `time` | Final assistant message after the model turn ends |
200| `usage` | `input`, `output`, `reasoning`, `cache_read`, `cache_write`, `cost_usd`, `cumulative` | Per-turn + cumulative tokens / cost. `reasoning` is null when unavailable. |
201| `turn_end` | `stop`, optional `error` | One model call finished. `stop` is `end_turn`, `tool_use`, `length`, `error`, or `aborted` |
202| `done` | (none) | The whole prompt/compact completed (success or error) |
203| `error` | `message` | Non-fatal error message |
204| `compact_done` | `summary` | Compaction finished, summary text included |
205
206## Message shape
207
208Used by `get_messages` and inside `user_message` / `assistant_message` events.
209
210```json
211{
212 "role": "user",
213 "content": [<content_block>...],
214 "time": "2026-04-19T11:30:00Z"
215}
216```
217
218### Content block types
219
220```json
221{"type": "text", "text": "..."}
222{"type": "image", "mime_type": "image/png", "bytes": 12345}
223{"type": "tool_call", "id": "toolu_xyz", "name": "read", "args": {"path": "..."}}
224{"type": "tool_result", "call_id": "toolu_xyz", "is_error": false, "content": [<content_block>...]}
225```
226
227Image bytes are reported as a count rather than embedded base64 to keep transcript dumps small. Tool results may nest text and image blocks.
228
229## Reference clients
230
231See `examples/rpc/` for working implementations in:
232
233- `shell` — `bash` + `jq` one-liner
234- `python` — `subprocess.Popen` + `json.loads` per line
235- `node` — `child_process.spawn` + `readline`
236- `go` — direct subprocess wrapper (the `packages/agent/sdk` SDK is the in-process Go API)
237
238## Versioning
239
240The `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.