v0.3.95 beceda1

rpc.md RawHistoryFolder

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.

Extension system prompts

Extensions can subscribe to before_agent_start to inspect and replace the complete system prompt before the first agent model call. This uses the extension subprocess protocol, not a new RPC client command. The result is reused for ordinary follow-up prompts and tool-loop steps; clear and a model change invalidate it for the next prompt. Failures keep the current prompt and surface as ext_notify warnings. See System prompt replacement for chaining, deadlines, size limits, and privacy considerations.

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, "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.

Runtime mutations while busy

clear, set_model, and set_reasoning require an idle agent. During prompt preparation (including extension hooks), model/tool execution, or compaction, they return success:false with an agent is busy error and leave state unchanged. They are not queued. Wait for the active operation to finish, or send abort, wait for completion, and retry. The RPC read loop stays responsive to abort while an operation is busy.

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## Extension system prompts
29
30Extensions can subscribe to `before_agent_start` to inspect and replace the
31complete system prompt before the first agent model call. This uses the
32extension subprocess protocol, not a new RPC client command. The result is
33reused for ordinary follow-up prompts and tool-loop steps; `clear` and a model
34change invalidate it for the next prompt. Failures keep the current prompt and
35surface as `ext_notify` warnings. See [System prompt replacement](extensions.md#system-prompt-replacement)
36for chaining, deadlines, size limits, and privacy considerations.
37
38## Auth
39
40If 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:
41
42```json
43{"id":"0","type":"hello","token":"shared-secret"}
44```
45
46If 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).
47
48## Wire format
49
50Every line in either direction is one JSON object terminated by `\n`. Object boundaries follow newline boundaries — no multi-line JSON.
51
52### Frame types
53
54| `type` | Direction | Description |
55|---|---|---|
56| any command (`prompt`, `abort`, ...) | client → server | Request |
57| `response` | server → client | Reply to one command, correlated by `id` |
58| any event (`text_delta`, `tool_call`, ...) | server → client | Stream notification (no `id`) |
59
60## Commands
61
62All 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.
63
64### `hello`
65
66```json
67{"id":"0","type":"hello","token":"shared-secret"}
68```
69
70Response:
71
72```json
73{"type":"response","id":"0","command":"hello","success":true,
74 "data":{"protocol_version":1,"version":"0.0.4","provider":"anthropic","model":"claude-opus-4-5"}}
75```
76
77Required as the first message when `ZOTCORE_RPC_TOKEN` is set; optional otherwise.
78
79### `prompt`
80
81```json
82{"id":"1","type":"prompt","message":"fix the failing test","images":[]}
83```
84
85Optional `images` is `[{"mime_type":"image/png","data":"<base64>"}]`.
86
87Response is immediate (the turn is starting):
88
89```json
90{"type":"response","id":"1","command":"prompt","success":true,"data":{"started":true}}
91```
92
93Then a stream of event objects (see below) until the turn ends with `{"type":"done"}`.
94
95### `abort`
96
97Cancel the active prompt or compact.
98
99```json
100{"id":"2","type":"abort"}
101```
102
103Response: `{"type":"response","id":"2","command":"abort","success":true}`.
104
105If the turn was streaming, the next events you see will be a `turn_end` with `stop:"aborted"` then `done`.
106
107### `compact`
108
109Summarise the current transcript into one synthetic user message. Same lifecycle as `prompt` (immediate response, then events).
110
111```json
112{"id":"3","type":"compact"}
113```
114
115Final event: `{"type":"compact_done","summary":"<text>"}`.
116
117### `get_state`
118
119Snapshot of the runtime.
120
121```json
122{"id":"4","type":"get_state"}
123```
124
125Response data:
126
127```json
128{
129  "provider": "anthropic",
130  "model": "claude-opus-4-5",
131  "cwd": "/Users/pat/Developer/zot",
132  "message_count": 12,
133  "busy": false,
134  "usage": {"input": 1234, "output": 567, "reasoning": 123, "cache_read": 890, "cache_write": 0, "cost_usd": 0.0123}
135}
136```
137
138### `get_messages`
139
140Full transcript.
141
142```json
143{"id":"5","type":"get_messages"}
144```
145
146Response data: `{"messages": [<message>, ...]}`. See **message shape** below.
147
148### Runtime mutations while busy
149
150`clear`, `set_model`, and `set_reasoning` require an idle agent. During prompt
151preparation (including extension hooks), model/tool execution, or compaction,
152they return `success:false` with an `agent is busy` error and leave state
153unchanged. They are not queued. Wait for the active operation to finish, or
154send `abort`, wait for completion, and retry. The RPC read loop stays responsive
155to `abort` while an operation is busy.
156
157### `clear`
158
159Drop the entire transcript. Equivalent to the `/clear` slash command.
160
161```json
162{"id":"6","type":"clear"}
163```
164
165### `set_model`
166
167Switch model within the same provider.
168
169```json
170{"id":"7","type":"set_model","model":"claude-sonnet-4-5"}
171```
172
173Cross-provider swaps require relaunching `zot rpc` with the new `--provider`.
174
175### `set_reasoning`
176
177Set the reasoning level for subsequent prompts without restarting the process. Accepted values are `off`, `minimum`, `low`, `medium`, `high`, `xhigh`, and `max`.
178
179```json
180{"id":"8","type":"set_reasoning","reasoning":"max"}
181```
182
183The `max` tier is sent natively to supported models and clamped for other backends.
184
185### `get_models`
186
187List models known for the current provider.
188
189```json
190{"id":"8","type":"get_models"}
191```
192
193Response data: `{"models":[{"id":"...","provider":"...","context_window":200000,"max_output":8192,"reasoning":true}, ...]}`.
194
195### `ping`
196
197Health check.
198
199```json
200{"id":"9","type":"ping"}
201```
202
203Response: `{"type":"response","id":"9","command":"ping","success":true,"data":{"pong":true}}`.
204
205## Events
206
207Stream notifications during a `prompt` or `compact`. None carry an `id`.
208
209| `type` | Fields | Meaning |
210|---|---|---|
211| `turn_start` | `step` | Beginning of one model call (max-steps loop iteration) |
212| `user_message` | `content`, `time` | The submitted prompt as it was added to the transcript |
213| `assistant_start` | (none) | About to receive assistant streaming |
214| `text_delta` | `delta` | Partial assistant text. Concatenate to build the full reply |
215| `tool_call` | `id`, `name`, `args` | The model wants to call a tool |
216| `tool_progress` | `id`, `text` | Optional progress line from the tool while it runs |
217| `tool_result` | `id`, `is_error`, `content` | Tool finished |
218| `assistant_message` | `content`, `time` | Final assistant message after the model turn ends |
219| `usage` | `input`, `output`, `reasoning`, `cache_read`, `cache_write`, `cost_usd`, `cumulative` | Per-turn + cumulative tokens / cost. `reasoning` is null when unavailable. |
220| `turn_end` | `stop`, optional `error` | One model call finished. `stop` is `end_turn`, `tool_use`, `length`, `error`, or `aborted` |
221| `done` | (none) | The whole prompt/compact completed (success or error) |
222| `error` | `message` | Non-fatal error message |
223| `compact_done` | `summary` | Compaction finished, summary text included |
224
225## Message shape
226
227Used by `get_messages` and inside `user_message` / `assistant_message` events.
228
229```json
230{
231  "role": "user",
232  "content": [<content_block>...],
233  "time": "2026-04-19T11:30:00Z"
234}
235```
236
237### Content block types
238
239```json
240{"type": "text", "text": "..."}
241{"type": "image", "mime_type": "image/png", "bytes": 12345}
242{"type": "tool_call", "id": "toolu_xyz", "name": "read", "args": {"path": "..."}}
243{"type": "tool_result", "call_id": "toolu_xyz", "is_error": false, "content": [<content_block>...]}
244```
245
246Image bytes are reported as a count rather than embedded base64 to keep transcript dumps small. Tool results may nest text and image blocks.
247
248## Reference clients
249
250See `examples/rpc/` for working implementations in:
251
252- `shell` — `bash` + `jq` one-liner
253- `python` — `subprocess.Popen` + `json.loads` per line
254- `node` — `child_process.spawn` + `readline`
255- `go` — direct subprocess wrapper (the `packages/agent/sdk` SDK is the in-process Go API)
256
257## Versioning
258
259The `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.

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 476

↑↓ moveenter openesc close