Reference·RPCIndex

RPC

Drive Nodecode from a program: one command a line on stdin, one frame a line on stdout.

.mdllms.txt

A program spawns nodecode rpc and keeps its stdin open. Nothing here is a second protocol: a line in is the gateway's own command, and a line out is the gateway's own frame.

Start it

a shell
nodecode rpc [SESSION-ID [--cursor N]] [--store PATH | --ephemeral]

It finds the organism serving your home, or starts one. It presents the operator token and subscribes to a session: the one you name, or a fresh one. Your program never handles a port, a token or a handshake. The first line out is . Then come , and .

nodecode rpc
0.00{"type":"start_turn","request":{"prompt":"say pong"}}
0.12{"sequence":1,"session_id":"s-GW3YSGSZ","payload":{"type":"session_stream_resumed","session_id":"s-GW3YSGSZ","accumulated_frames":[],"admission_state":"ready","replay_chunk_index":0,"replay_chunk_final":true},"cursor":0}
0.12{"sequence":0,"payload":{"sessions":[],"type":"organism.activity"}}
0.12
{"sequence":2,…"event":{"type":"session.created"…"type":"session_stream_event"},"cursor":2,"durable":true}{"sequence":2,"session_id":"s-GW3YSGSZ","payload":{"frame":{"seq":2,"session_id":"s-GW3YSGSZ","turn_id":false,"event":{"type":"session.created","payload":{"id":"s-GW3YSGSZ","cwd":"/home/you/project","parent":false,"created-at":"2026-10-09T02:53:33.203Z"},"metadata":{"event_id":"event-1U5SO19-6ME7O12TOP","log_position":2,"turn_id":false,"command_id":"command-1U5SO19-DVTNEZ5SG4","occurred_at":"2026-10-09T02:53:33.203Z"}}},"type":"session_stream_event"},"cursor":2,"durable":true}
0.12
{"sequence":3,…"event":{"type":"turn.input_committed"…"type":"session_stream_event"},"cursor":3,"durable":true}{"sequence":3,"session_id":"s-GW3YSGSZ","payload":{"frame":{"seq":3,"session_id":"s-GW3YSGSZ","turn_id":"turn-1U5SO19-KHY4SY503B","event":{"type":"turn.input_committed","payload":{"role":"user","message":"say pong","prompt-id":"command-1U5SO19-DFKDHVX7KL","sequence":1,"disposition":"started"},"metadata":{"event_id":"event-1U5SO19-QZP020Q1FF","log_position":3,"turn_id":"turn-1U5SO19-KHY4SY503B","command_id":"command-1U5SO19-DFKDHVX7KL","occurred_at":"2026-10-09T02:53:33.207Z"}}},"type":"session_stream_event"},"cursor":3,"durable":true}
0.12
{"sequence":4,…"event":{"type":"turn.started"…"type":"session_stream_event"},"cursor":4,"durable":true}{"sequence":4,"session_id":"s-GW3YSGSZ","payload":{"frame":{"seq":4,"session_id":"s-GW3YSGSZ","turn_id":"turn-1U5SO19-KHY4SY503B","event":{"type":"turn.started","payload":{"input":"say pong"},"metadata":{"event_id":"event-1U5SO19-OOOH5SCVD9","log_position":4,"turn_id":"turn-1U5SO19-KHY4SY503B","command_id":"command-1U5SO19-DFKDHVX7KL","occurred_at":"2026-10-09T02:53:33.208Z"}}},"type":"session_stream_event"},"cursor":4,"durable":true}
0.12{"sequence":0,"payload":{"sessions":[{"id":"s-GW3YSGSZ","started_at":"2026-10-09T02:53:33.208Z"}],"type":"organism.activity"}}
0.12
{"sequence":0,"payload":{"type":"gateway_turn_command_result"…"command":"start_turn","status":"accepted","message":"turn admitted"…{"sequence":0,"payload":{"type":"gateway_turn_command_result","result":{"command_id":"command-1U5SO19-DFKDHVX7KL","correlation_id":"command-1U5SO19-DFKDHVX7KL","session_id":"s-GW3YSGSZ","command":"start_turn","status":"accepted","message":"turn admitted","response":{"turn_id":"turn-1U5SO19-KHY4SY503B","queue_depth":0},"message_id":"command-1U5SO19-DFKDHVX7KL","sequence":1}},"session_id":"s-GW3YSGSZ"}
0.81{"sequence":5,"session_id":"s-GW3YSGSZ","payload":{"turn_id":"turn-1U5SO19-KHY4SY503B","delta":{"type":"text","text":"pon"},"type":"item_delta"}}
0.83{"sequence":6,"session_id":"s-GW3YSGSZ","payload":{"turn_id":"turn-1U5SO19-KHY4SY503B","delta":{"type":"text","text":"g"},"type":"item_delta"}}
0.86
{"sequence":7,…"event":{"type":"turn.assistant_message_completed"…"type":"session_stream_event"},"cursor":9,"durable":true}{"sequence":7,"session_id":"s-GW3YSGSZ","payload":{"frame":{"seq":9,"session_id":"s-GW3YSGSZ","turn_id":"turn-1U5SO19-KHY4SY503B","event":{"type":"turn.assistant_message_completed","payload":{"role":"assistant","message":{"role":"assistant","content":"pong"}},"metadata":{"event_id":"event-1U5SO19-8SQB2KSYCH","log_position":9,"turn_id":"turn-1U5SO19-KHY4SY503B","command_id":"command-1U5SO19-DFKDHVX7KL","occurred_at":"2026-10-09T02:53:33.950Z"}}},"type":"session_stream_event"},"cursor":9,"durable":true}
0.87
{"sequence":8,…"event":{"type":"turn.usage"…"type":"session_stream_event"},"cursor":10,"durable":true}{"sequence":8,"session_id":"s-GW3YSGSZ","payload":{"frame":{"seq":10,"session_id":"s-GW3YSGSZ","turn_id":"turn-1U5SO19-KHY4SY503B","event":{"type":"turn.usage","payload":{"input-tokens":0,"output-tokens":8,"cached-input-tokens":940,"total-tokens":948,"cost_known":false,"cost_usd":0.0,"finish-reason"…192 characters…t-1U5SO19-2LCDPPIEOV","log_position":10,"turn_id":"turn-1U5SO19-KHY4SY503B","command_id":"command-1U5SO19-DFKDHVX7KL","occurred_at":"2026-10-09T02:53:33.952Z"}}},"type":"session_stream_event"},"cursor":10,"durable":true}
0.87
{"sequence":9,…"event":{"type":"turn.completed"…"type":"session_stream_event"},"cursor":11,"durable":true}{"sequence":9,"session_id":"s-GW3YSGSZ","payload":{"frame":{"seq":11,"session_id":"s-GW3YSGSZ","turn_id":"turn-1U5SO19-KHY4SY503B","event":{"type":"turn.completed","payload":{"turn-number":1,"message-count":2},"metadata":{"event_id":"event-1U5SO19-VFATPLPM63","log_position":11,"turn_id":"turn-1U5SO19-KHY4SY503B","command_id":"command-1U5SO19-DFKDHVX7KL","occurred_at":"2026-10-09T02:53:33.954Z"}}},"type":"session_stream_event"},"cursor":11,"durable":true}
0.87stdin closed
0.87{"sequence":0,"payload":{"sessions":[],"type":"organism.activity"}}
→ stdin   ← stdoutexit 0 · 0.88 s

One line in, thirteen lines out. The command's result came at 0.12 s, the turn's text at 0.81 s.

Reading the port from the store's .owner file and doing the WebSocket handshake yourself

The challenge, the operator token, the session and the subscription are what rpc does for you.

Instead: Spawn nodecode rpc and write commands.

nodecode rpc serve

Exit 2: rpc is a command of its own: nodecode [-p NAME] rpc [SESSION-ID [--cursor N]] [--store PATH | --ephemeral]

Instead: Run nodecode serve, or nodecode gateway on, by itself. nodecode rpc attaches to it.

Spawning one nodecode rpc per prompt

With no organism running, each one starts an organism and stops it when its stdin ends. Every start after a stop sends the model a "running again" note: 6 of the 20 model calls in the runs on this page.

Instead: Keep one nodecode rpc open for your program's life, or run nodecode gateway on so each one attaches.

Lines in

Each line is one command object. A line that names no command_id gets one, and a command that names no session_id goes to rpc's session. A line that is not a command gets , and the next line is read.

TypeTakesDoes
start_turnrequest.promptStarts a turn. One already running queues it.
steer_turnrequest.prompt, request.expected_turn_idThe prompt runs at the running turn's next round boundary.
cancel_turnturn_id, retractStops the running turn. retract also takes it back when it produced nothing.
withdraw_promptprompt_idTakes a queued prompt back.
interrupt_execexec_idStops a background evaluation.
navigate_checkpointtarget_turn_id or directionMoves the session's head. Every client is told to subscribe again.
get_statenothingThe session as it stands, in the result.
set_session_subscriptionssubscriptionsThe sessions this client follows.
nodecode rpc s-LINES
0.17{"sequence":1,"session_id":"s-LINES","payload":{"type":"session_stream_resumed","session_id":"s-LINES","accumulated_frames":[],"admission_state":"ready","replay_chunk_index":0,"replay_chunk_final":true},"cursor":0}
0.17{"sequence":0,"payload":{"sessions":[],"type":"organism.activity"}}
0.17say pong
0.17{"sequence":0,"payload":{"code":"invalid-json","message":"invalid JSON: malformed JSON at byte 0: #\\s begins no value","permanent":false,"type":"sync_protocol_error"}}
0.67{"type":"start_turn","prompt":"say pong"}
0.67{"sequence":0,"payload":{"code":"invalid-envelope","message":"request must be an object","permanent":false,"type":"sync_protocol_error"}}
1.17{"prompt":"say pong"}
1.17{"sequence":0,"payload":{"code":"invalid-envelope","message":"type must be a non-empty string","permanent":false,"type":"sync_protocol_error"}}
1.67{"type":"start_turn","request":{"prompt":"say pong"}}
1.68
{"sequence":2,…"event":{"type":"session.created"…"type":"session_stream_event"},"cursor":43,"durable":true}{"sequence":2,"session_id":"s-LINES","payload":{"frame":{"seq":43,"session_id":"s-LINES","turn_id":false,"event":{"type":"session.created","payload":{"id":"s-LINES","cwd":"/home/you/project","parent":false,"created-at":"2026-10-09T02:53:40.704Z"},"metadata":{"event_id":"event-1U5SO1G-KBPC0G11T5","log_position":43,"turn_id":false,"command_id":"command-1U5SO1G-HG053PED2F","occurred_at":"2026-10-09T02:53:40.704Z"}}},"type":"session_stream_event"},"cursor":43,"durable":true}
1.68
{"sequence":3,…"event":{"type":"turn.input_committed"…"type":"session_stream_event"},"cursor":44,"durable":true}{"sequence":3,"session_id":"s-LINES","payload":{"frame":{"seq":44,"session_id":"s-LINES","turn_id":"turn-1U5SO1G-4W89TJM6HX","event":{"type":"turn.input_committed","payload":{"role":"user","message":"say pong","prompt-id":"command-1U5SO1G-D5YKUGNF62","sequence":1,"disposition":"started"},"metadata":{"event_id":"event-1U5SO1G-DQDU9HH7V3","log_position":44,"turn_id":"turn-1U5SO1G-4W89TJM6HX","command_id":"command-1U5SO1G-D5YKUGNF62","occurred_at":"2026-10-09T02:53:40.705Z"}}},"type":"session_stream_event"},"cursor":44,"durable":true}
1.68
{"sequence":4,…"event":{"type":"turn.started"…"type":"session_stream_event"},"cursor":45,"durable":true}{"sequence":4,"session_id":"s-LINES","payload":{"frame":{"seq":45,"session_id":"s-LINES","turn_id":"turn-1U5SO1G-4W89TJM6HX","event":{"type":"turn.started","payload":{"input":"say pong"},"metadata":{"event_id":"event-1U5SO1G-KXBB7XVR6V","log_position":45,"turn_id":"turn-1U5SO1G-4W89TJM6HX","command_id":"command-1U5SO1G-D5YKUGNF62","occurred_at":"2026-10-09T02:53:40.705Z"}}},"type":"session_stream_event"},"cursor":45,"durable":true}
1.68{"sequence":0,"payload":{"sessions":[{"id":"s-LINES","started_at":"2026-10-09T02:53:40.705Z"}],"type":"organism.activity"}}
1.68
{"sequence":0,"payload":{"type":"gateway_turn_command_result"…"command":"start_turn","status":"accepted","message":"turn admitted"…{"sequence":0,"payload":{"type":"gateway_turn_command_result","result":{"command_id":"command-1U5SO1G-D5YKUGNF62","correlation_id":"command-1U5SO1G-D5YKUGNF62","session_id":"s-LINES","command":"start_turn","status":"accepted","message":"turn admitted","response":{"turn_id":"turn-1U5SO1G-4W89TJM6HX","queue_depth":0},"message_id":"command-1U5SO1G-D5YKUGNF62","sequence":1}},"session_id":"s-LINES"}
2.32{"sequence":5,"session_id":"s-LINES","payload":{"turn_id":"turn-1U5SO1G-4W89TJM6HX","delta":{"type":"text","text":"pon"},"type":"item_delta"}}
2.34{"sequence":6,"session_id":"s-LINES","payload":{"turn_id":"turn-1U5SO1G-4W89TJM6HX","delta":{"type":"text","text":"g"},"type":"item_delta"}}
2.37
{"sequence":7,…"event":{"type":"turn.assistant_message_completed"…"type":"session_stream_event"},"cursor":50,"durable":true}{"sequence":7,"session_id":"s-LINES","payload":{"frame":{"seq":50,"session_id":"s-LINES","turn_id":"turn-1U5SO1G-4W89TJM6HX","event":{"type":"turn.assistant_message_completed","payload":{"role":"assistant","message":{"role":"assistant","content":"pong"}},"metadata":{"event_id":"event-1U5SO1H-CULFDF3HVM","log_position":50,"turn_id":"turn-1U5SO1G-4W89TJM6HX","command_id":"command-1U5SO1G-D5YKUGNF62","occurred_at":"2026-10-09T02:53:41.399Z"}}},"type":"session_stream_event"},"cursor":50,"durable":true}
2.37
{"sequence":8,…"event":{"type":"turn.usage"…"type":"session_stream_event"},"cursor":51,"durable":true}{"sequence":8,"session_id":"s-LINES","payload":{"frame":{"seq":51,"session_id":"s-LINES","turn_id":"turn-1U5SO1G-4W89TJM6HX","event":{"type":"turn.usage","payload":{"input-tokens":0,"output-tokens":8,"cached-input-tokens":940,"total-tokens":948,"cost_known":false,"cost_usd":0.0,"finish-reason":"stop…186 characters…t-1U5SO1H-YIUWCGCZ8E","log_position":51,"turn_id":"turn-1U5SO1G-4W89TJM6HX","command_id":"command-1U5SO1G-D5YKUGNF62","occurred_at":"2026-10-09T02:53:41.401Z"}}},"type":"session_stream_event"},"cursor":51,"durable":true}
2.37
{"sequence":9,…"event":{"type":"turn.completed"…"type":"session_stream_event"},"cursor":52,"durable":true}{"sequence":9,"session_id":"s-LINES","payload":{"frame":{"seq":52,"session_id":"s-LINES","turn_id":"turn-1U5SO1G-4W89TJM6HX","event":{"type":"turn.completed","payload":{"turn-number":1,"message-count":2},"metadata":{"event_id":"event-1U5SO1H-JYVW3AHNT","log_position":52,"turn_id":"turn-1U5SO1G-4W89TJM6HX","command_id":"command-1U5SO1G-D5YKUGNF62","occurred_at":"2026-10-09T02:53:41.404Z"}}},"type":"session_stream_event"},"cursor":52,"durable":true}
2.37stdin closed
2.37{"sequence":0,"payload":{"sessions":[],"type":"organism.activity"}}
→ stdin   ← stdoutexit 0 · 2.40 s

Three wrong lines, three refusals, and the fourth line ran.

Mistake
nodecode rpc s-CANCEL
1.20{"type": "cancel_turn", "request": {"turn_id": "turn-1U5SO2H-ROB7B7VNHO"}}
1.20…"session_id":"s-CANCEL","command":"cancel_turn","status":"rejected","message":"session_id and turn_id are required","code":"invalid_request"}},"session_id":"s-CANCEL"}
1.20{"sequence":9,"session_id":"s-CANCEL","payload":{"turn_id":"turn-1U5SO2H-ROB7B7VNHO","delta":{"type":"text","text":"a l"},"type":"item_delta"}}
→ stdin   ← stdoutexit 0 · 1.63 s
Instead
nodecode rpc s-CANCEL
1.60{"type": "cancel_turn", "turn_id": "turn-1U5SO2H-ROB7B7VNHO"}
1.60…"session_id":"s-CANCEL","command":"cancel_turn","status":"accepted","message":"cancellation requested","response":{"turn_id":"turn-1U5SO2H-ROB7B7VNHO"…
1.61…"turn_id":"turn-1U5SO2H-ROB7B7VNHO","event":{"type":"turn.cancelled","payload":{"reason":"stopped by the operator"}…
→ stdin   ← stdoutexit 0 · 1.63 s

cancel_turn takes turn_id beside type. Under request it is refused, and the turn keeps streaming.

Mistake
nodecode rpc s-QUEUE
0.76{"type":"start_turn","request":{"prompt":"say pong"}}
0.76…"session_id":"s-QUEUE","command":"start_turn","status":"queued","message":"turn input queued","response":{"turn_id":"turn-1U5SO1H-NFFNSP0HIP"…
→ stdin   ← stdoutexit 0 · 1.53 s
Instead
nodecode rpc s-STEER
1.32{"type": "steer_turn", "request": {"prompt": "say pong", "expected_turn_id": "turn-1U5SO1J-SQ15L3N450"}}
1.32…"session_id":"s-STEER","command":"steer_turn","status":"accepted","message":"turn steered","response":{"turn_id":"turn-1U5SO1J-SQ15L3N450"…
→ stdin   ← stdoutexit 0 · 34.53 s

A second start_turn waits behind the first. steer_turn reaches the running turn at its next round boundary.

Writing the prompt as text

invalid JSON: malformed JSON at byte 0: #\s begins no value

Instead: {"type":"start_turn","request":{"prompt":"say pong"}}

Putting prompt beside type

request must be an object

Instead: The prompt goes under request.

Leaving out type

type must be a non-empty string

Instead: Every line names its type.

Steering without expected_turn_id

Rejected: session_id, expected_turn_id, and non-empty prompt are required

Instead: Send the turn_id from your start_turn result.

Lines out

Each line is one frame, exactly as the gateway sent it. A turn ends with , turn.failed or turn.cancelled.

FrameIs
Always first. The session's replay, in accumulated_frames.
A fact as it lands. It carries "durable":true and a cursor, its place in the log.
Text or a tool call while it streams. No cursor.
A command's answer: accepted, queued or rejected, with the turn_id.
The organism's own frames. sequence 0 and no session.
sync_protocol_errorA line that is not a command.
nodecode rpc s-DOCS
0.00{"type": "start_turn", "request": {"prompt": "how many lisp files"}}
0.11{"sequence":2,"session_id":"s-DOCS","payload":{"type":"session_stream_resumed","session_id":"s-DOCS","accumulated_frames":[…6 replayed frames…],"admission_state":"ready","replay_chunk_index":0,"replay_chunk_final":true},"cursor":22}
0.11{"sequence":0,"payload":{"sessions":[],"type":"organism.activity"}}
0.11
{"sequence":3,…"event":{"type":"turn.input_committed"…"type":"session_stream_event"},"cursor":24,"durable":true}{"sequence":3,"session_id":"s-DOCS","payload":{"frame":{"seq":24,"session_id":"s-DOCS","turn_id":"turn-1U5SO1A-E37X7WSI6","event":{"type":"turn.input_committed","payload":{"role":"user","message":"how many lisp files","prompt-id":"command-1U5SO1A-INEOFW90L2","sequence":1,"disposition":"started"},"metadata":{"event_id":"event-1U5SO1A-3Y4MN9JLPB","log_position":24,"turn_id":"turn-1U5SO1A-E37X7WSI6","command_id":"command-1U5SO1A-INEOFW90L2","occurred_at":"2026-10-09T02:53:34.915Z"}}},"type":"session_stream_event"},"cursor":24,"durable":true}
0.11
{"sequence":4,…"event":{"type":"turn.started"…"type":"session_stream_event"},"cursor":25,"durable":true}{"sequence":4,"session_id":"s-DOCS","payload":{"frame":{"seq":25,"session_id":"s-DOCS","turn_id":"turn-1U5SO1A-E37X7WSI6","event":{"type":"turn.started","payload":{"input":"how many lisp files","parent-turn-id":"turn-1U5SO1A-2HJ0FRDIE2"},"metadata":{"event_id":"event-1U5SO1A-D49127X4RP","log_position":25,"turn_id":"turn-1U5SO1A-E37X7WSI6","command_id":"command-1U5SO1A-INEOFW90L2","occurred_at":"2026-10-09T02:53:34.916Z"}}},"type":"session_stream_event"},"cursor":25,"durable":true}
0.11{"sequence":0,"payload":{"sessions":[{"id":"s-DOCS","started_at":"2026-10-09T02:53:34.916Z"}],"type":"organism.activity"}}
0.11
{"sequence":0,"payload":{"type":"gateway_turn_command_result"…"command":"start_turn","status":"accepted","message":"turn admitted"…{"sequence":0,"payload":{"type":"gateway_turn_command_result","result":{"command_id":"command-1U5SO1A-INEOFW90L2","correlation_id":"command-1U5SO1A-INEOFW90L2","session_id":"s-DOCS","command":"start_turn","status":"accepted","message":"turn admitted","response":{"turn_id":"turn-1U5SO1A-E37X7WSI6","queue_depth":0},"message_id":"command-1U5SO1A-INEOFW90L2","sequence":1}},"session_id":"s-DOCS"}
0.11{"sequence":0,"payload":{"type":"commands_changed"}}
0.71{"sequence":0,"payload":{"text":"ok","type":"organism.note"}}
0.91{"sequence":5,"session_id":"s-DOCS","payload":{"turn_id":"turn-1U5SO1A-E37X7WSI6","delta":{"type":"tool_call","index":0,"name":"eval","arguments":""},"type":"item_delta"}}
0.91{"sequence":6,"session_id":"s-DOCS","payload":{"turn_id":"turn-1U5SO1A-E37X7WSI6","delta":{"type":"tool_call","index":0,"name":"eval","arguments":"{\"for"},"type":"item_delta"}}
+9 more item_delta frames
1.15
{"sequence":16,…"event":{"type":"turn.assistant_message_completed"…"type":"function"…"type":"session_stream_event"},"cursor":28,"durable":true}{"sequence":16,"session_id":"s-DOCS","payload":{"frame":{"seq":28,"session_id":"s-DOCS","turn_id":"turn-1U5SO1A-E37X7WSI6","event":{"type":"turn.assistant_message_completed","payload":{"role":"assistant","message":{"role":"assistant","content":null,"tool_calls":[{"id":"call_ec27431c907f415a8c40","ty…146 characters…nt-1U5SO1B-2G2BBRJOZP","log_position":28,"turn_id":"turn-1U5SO1A-E37X7WSI6","command_id":"command-1U5SO1A-INEOFW90L2","occurred_at":"2026-10-09T02:53:35.959Z"}}},"type":"session_stream_event"},"cursor":28,"durable":true}
1.15
{"sequence":17,…"event":{"type":"turn.usage"…"type":"session_stream_event"},"cursor":29,"durable":true}{"sequence":17,"session_id":"s-DOCS","payload":{"frame":{"seq":29,"session_id":"s-DOCS","turn_id":"turn-1U5SO1A-E37X7WSI6","event":{"type":"turn.usage","payload":{"input-tokens":740,"output-tokens":14,"cached-input-tokens":226,"total-tokens":980,"cost_known":false,"cost_usd":0.0,"finish-reason":"too…192 characters…nt-1U5SO1B-X4RWS89GB3","log_position":29,"turn_id":"turn-1U5SO1A-E37X7WSI6","command_id":"command-1U5SO1A-INEOFW90L2","occurred_at":"2026-10-09T02:53:35.960Z"}}},"type":"session_stream_event"},"cursor":29,"durable":true}
1.15
{"sequence":18,…"event":{"type":"turn.tool_call_started"…"type":"session_stream_event"},"cursor":30,"durable":true}{"sequence":18,"session_id":"s-DOCS","payload":{"frame":{"seq":30,"session_id":"s-DOCS","turn_id":"turn-1U5SO1A-E37X7WSI6","event":{"type":"turn.tool_call_started","payload":{"call-id":"call_ec27431c907f415a8c40","tool-name":"eval","arguments":"{\"form\": \"(length (directory \\\"src/**/*.lisp\\\"))\"}"},"metadata":{"event_id":"event-1U5SO1B-Y4B15O9AQZ","log_position":30,"turn_id":"turn-1U5SO1A-E37X7WSI6","command_id":"command-1U5SO1A-INEOFW90L2","occurred_at":"2026-10-09T02:53:35.962Z"}}},"type":"session_stream_event"},"cursor":30,"durable":true}
1.20
{"sequence":19,…"event":{"type":"turn.tool_result"…"type":"session_stream_event"},"cursor":31,"durable":true}{"sequence":19,"session_id":"s-DOCS","payload":{"frame":{"seq":31,"session_id":"s-DOCS","turn_id":"turn-1U5SO1A-E37X7WSI6","event":{"type":"turn.tool_result","payload":{"call-id":"call_ec27431c907f415a8c40","tool-name":"eval","result":"3","duration-ms":37,"failed":false},"metadata":{"event_id":"event-1U5SO1C-8NX8NVLEJ6","log_position":31,"turn_id":"turn-1U5SO1A-E37X7WSI6","command_id":"command-1U5SO1A-INEOFW90L2","occurred_at":"2026-10-09T02:53:36.007Z"}}},"type":"session_stream_event"},"cursor":31,"durable":true}
1.29
{"sequence":20,…"exit_code":0},"type":"session_background_process_updated"}}{"sequence":20,"session_id":"s-DOCS","payload":{"session_id":"s-DOCS","process":{"shell_session_id":1000,"tool_call_id":"call_ec27431c907f415a8c40","command":"(length (directory \"src/**/*.lisp\"))","status":"exited","output_b64":"","offset":0,"exit_code":0},"type":"session_background_process_updated"}}
1.50{"sequence":21,"session_id":"s-DOCS","payload":{"turn_id":"turn-1U5SO1A-E37X7WSI6","delta":{"type":"text","text":"The"},"type":"item_delta"}}
1.52{"sequence":22,"session_id":"s-DOCS","payload":{"turn_id":"turn-1U5SO1A-E37X7WSI6","delta":{"type":"text","text":"re "},"type":"item_delta"}}
+10 more item_delta frames
1.76
{"sequence":33,…"event":{"type":"turn.assistant_message_completed"…"type":"session_stream_event"},"cursor":34,"durable":true}{"sequence":33,"session_id":"s-DOCS","payload":{"frame":{"seq":34,"session_id":"s-DOCS","turn_id":"turn-1U5SO1A-E37X7WSI6","event":{"type":"turn.assistant_message_completed","payload":{"role":"assistant","message":{"role":"assistant","content":"There are 3 Lisp files under src/."}},"metadata":{"event_id":"event-1U5SO1C-SRRGVGMD65","log_position":34,"turn_id":"turn-1U5SO1A-E37X7WSI6","command_id":"command-1U5SO1A-INEOFW90L2","occurred_at":"2026-10-09T02:53:36.568Z"}}},"type":"session_stream_event"},"cursor":34,"durable":true}
1.76
{"sequence":34,…"event":{"type":"turn.usage"…"type":"session_stream_event"},"cursor":35,"durable":true}{"sequence":34,"session_id":"s-DOCS","payload":{"frame":{"seq":35,"session_id":"s-DOCS","turn_id":"turn-1U5SO1A-E37X7WSI6","event":{"type":"turn.usage","payload":{"input-tokens":87,"output-tokens":10,"cached-input-tokens":966,"total-tokens":1063,"cost_known":false,"cost_usd":0.0,"finish-reason":"sto…186 characters…nt-1U5SO1C-FSPCTWEG8I","log_position":35,"turn_id":"turn-1U5SO1A-E37X7WSI6","command_id":"command-1U5SO1A-INEOFW90L2","occurred_at":"2026-10-09T02:53:36.568Z"}}},"type":"session_stream_event"},"cursor":35,"durable":true}
1.76
{"sequence":35,…"event":{"type":"turn.completed"…"type":"session_stream_event"},"cursor":36,"durable":true}{"sequence":35,"session_id":"s-DOCS","payload":{"frame":{"seq":36,"session_id":"s-DOCS","turn_id":"turn-1U5SO1A-E37X7WSI6","event":{"type":"turn.completed","payload":{"turn-number":2,"message-count":6},"metadata":{"event_id":"event-1U5SO1C-9KUUK9H47E","log_position":36,"turn_id":"turn-1U5SO1A-E37X7WSI6","command_id":"command-1U5SO1A-INEOFW90L2","occurred_at":"2026-10-09T02:53:36.569Z"}}},"type":"session_stream_event"},"cursor":36,"durable":true}
1.76stdin closed
1.76{"sequence":0,"payload":{"sessions":[],"type":"organism.activity"}}
→ stdin   ← stdoutexit 0 · 1.77 s

A second turn on a named session, with one evaluation. The replay holds the first turn, its turn.completed included.

Mistake
nodecode rpc s-GREP
0.00{"type":"start_turn","request":{"prompt":"say pong"}}
0.13…"turn_id":"turn-1U5SO2J-4SA7KWPKBW","event":{"type":"turn.completed","payload":{"turn-number":1,"message-count":2}…
0.13stdin closed
→ stdin   ← stdoutexit 0 · 0.29 s
Instead
nodecode rpc s-DOCS
0.00{"type": "start_turn", "request": {"prompt": "how many lisp files"}}
0.11…"status":"accepted","message":"turn admitted","response":{"turn_id":"turn-1U5SO1A-E37X7WSI6","queue_depth":0},"message_id":"command-1U5SO1A-INEOFW90L2"…
1.76…"payload":{"frame":{"seq":36,"session_id":"s-DOCS","turn_id":"turn-1U5SO1A-E37X7WSI6","event":{"type":"turn.completed","payload":{"turn-number":2…
→ stdin   ← stdoutexit 0 · 1.77 s

The replay carries the last turn's turn.completed. Wait for the end whose turn_id is the one your start_turn result carried.

Taking accepted for the answer

turn admitted came at 0.11 s. The answer's first word came at 1.50 s.

Instead: Read on to the turn's own turn.completed.

Keeping item_delta text as the answer

Deltas are not facts. They have no cursor, and a resume does not send them again.

Instead: The answer is in turn.assistant_message_completed.

Reading sequence as a place in the log

sequence counts the frames sent to this client, and it is 0 on the organism's own frames.

Instead: The place in the log is cursor.

Ending

WhenrpcThe organism
stdin endsExits 0Stops if rpc started it, and a running turn stops with it. Keeps running if it was already serving.
The gateway endsExits 1, why on stderrGone.
A bad argumentExits 2Never started.
Mistake
echo '…' | nodecode rpc s-ECHO
0.00{"type":"start_turn","request":{"prompt":"say pong"}}
0.00stdin closed
→ stdin   ← stdoutexit 0 · 0.21 s
Instead
nodecode rpc
0.00{"type":"start_turn","request":{"prompt":"say pong"}}
0.12…"session_id":"s-GW3YSGSZ","command":"start_turn","status":"accepted","message":"turn admitted","response":{"turn_id":"turn-1U5SO19-KHY4SY503B"…
0.87…"turn_id":"turn-1U5SO19-KHY4SY503B","event":{"type":"turn.completed","payload":{"turn-number":1,"message-count":2}…
0.87stdin closed
→ stdin   ← stdoutexit 0 · 0.88 s

Closing stdin stops the organism rpc started, and the turn with it. The next start picks the turn up. Close stdin after your turn's end.

nodecode rpc s-SERVED
0.07{"sequence":2,"session_id":"s-SERVED","payload":{"type":"session_stream_resumed","session_id":"s-SERVED","accumulated_frames":[…6 replayed frames…],"admission_state":"ready","replay_chunk_index":0,"replay_chunk_final":true},"cursor":159}
0.07{"sequence":0,"payload":{"text":"ok","type":"organism.note"}}
0.07{"sequence":0,"payload":{"sessions":[],"type":"organism.activity"}}
0.07kill -TERM the serve
0.57{"sequence":0,"payload":{"type":"commands_changed"}}
0.57nodecode: the gateway closed the connection
→ stdin   ← stdoutexit 1 · 0.58 s

Attached to a nodecode serve, which was then stopped.

Reading exit 0 as "the turn finished"

Exit 0 means stdin ended. It says nothing about any turn.

Instead: The turn's end is a frame: turn.completed, turn.failed or turn.cancelled.

Expecting rpc's exit to stop a nodecode serve it attached to

An organism that was already serving keeps running, and so does its turn.

Instead: Stop it where you started it, or with nodecode gateway off.

Resume

Name the session and the newest you read. The replay then carries only what landed past it, and says .

a shell
nodecode rpc s-DOCS --cursor 22
nodecode rpc s-DOCS --cursor 22
0.11{"sequence":3,"session_id":"s-DOCS","payload":{"type":"session_stream_resumed","session_id":"s-DOCS","accumulated_frames":[…9 replayed frames…],"admission_state":"ready","replay_chunk_index":0,"replay_chunk_final":true,"resumed_after":22},"cursor":36}
0.11{"sequence":0,"payload":{"sessions":[],"type":"organism.activity"}}
0.11{"sequence":0,"payload":{"type":"commands_changed"}}
0.41{"type":"get_state"}
0.41
{"sequence":0,"payload":{"type":"gateway_turn_command_result"…"command":"get_state","status":"accepted","message":"session state"…{"sequence":0,"payload":{"type":"gateway_turn_command_result","result":{"command_id":"command-1U5SO1D-9721LB40BB","correlation_id":"command-1U5SO1D-9721LB40BB","session_id":"s-DOCS","command":"get_state","status":"accepted","message":"session state","response":{"queue_depth":0,"session":{"session_id…287 characters…axolotl-cyber"},"pin":{"provider":"nodecode","model":"nodecode-axolotl-cyber"},"effort":"max","efforts":["off","minimal","low","medium","high","xhigh","max"],"refusal":null},"pending_prompts":[]}}},"session_id":"s-DOCS"}
0.41stdin closed
→ stdin   ← stdoutexit 0 · 0.45 s

Nine facts past 22: the second turn alone. Without --cursor, the same session replays all fifteen.

You runYou get
rpc SESSION-ID --cursor Nresumed_after N and the facts past N. Add them to what you hold.
rpc SESSION-IDThe whole replay, no resumed_after. Replace what you hold.
--cursor past the log, or the head moved sinceThe whole replay, no resumed_after.
rpcA fresh session and an empty replay.
Mistake
nodecode rpc s-DOCS --cursor 9
0.11{"sequence":4,"session_id":"s-DOCS","payload":{"type":"session_stream_resumed","session_id":"s-DOCS","accumulated_frames":[…15 replayed frames…],"admission_state":"ready","replay_chunk_index":0,"replay_chunk_final":true,"resumed_after":9},"cursor":36}
→ stdin   ← stdoutexit 0 · 0.44 s
Instead
nodecode rpc s-DOCS --cursor 22
0.11{"sequence":3,"session_id":"s-DOCS","payload":{"type":"session_stream_resumed","session_id":"s-DOCS","accumulated_frames":[…9 replayed frames…],"admission_state":"ready","replay_chunk_index":0,"replay_chunk_final":true,"resumed_after":22},"cursor":36}
→ stdin   ← stdoutexit 0 · 0.45 s

--cursor takes a frame's cursor. A sequence number resumes from far back and sends what you already hold.

Mistake
nodecode rpc
0.18{"sequence":1,"session_id":"s-ANCDJG21","payload":{"type":"session_stream_resumed","session_id":"s-ANCDJG21","accumulated_frames":[]…
→ stdin   ← stdoutexit 0 · 0.50 s
Instead
nodecode rpc s-DOCS
0.23{"sequence":5,"session_id":"s-DOCS","payload":{"type":"session_stream_resumed","session_id":"s-DOCS","accumulated_frames":[…15 replayed frames…],"admission_state":"ready","replay_chunk_index":0,"replay_chunk_final":true},"cursor":36}
→ stdin   ← stdoutexit 0 · 0.57 s

With no session id you get a new, empty session. Pass the session_id from the first frame.

Adding every replay to what you hold

With no resumed_after, the replay is whole: --cursor 9999 got all fifteen facts.

Instead: Replace your copy when resumed_after is absent.

nodecode rpc --cursor 5

Exit 2: --cursor resumes a session past a log position: nodecode rpc SESSION-ID --cursor N

Instead: Name the session first.

--cursor five

Exit 2: --cursor takes a log position, not "five"

Instead: A whole number, from a frame's cursor.

get_state

{"type":"get_state"} answers with the session as it stands. It needs no subscription.

it prints
{
  "queue_depth": 0,
  "session": {
    "session_id": "s-DOCS",
    "cwd": "/home/you/project",
    "parent": false,
    "created_at": "2026-10-09T02:53:34.076Z"
  },
  "title": "say pong",
  "admission_state": "ready",
  "cursor": 36,
  "target": {
    "provider": "nodecode",
    "model": "nodecode-axolotl-cyber",
    "unconfigured": false,
    "default": {
      "provider": "nodecode",
      "model": "nodecode-axolotl-cyber"
    },
    "pin": {
      "provider": "nodecode",
      "model": "nodecode-axolotl-cyber"
    },
    "effort": "max",
    "efforts": [
      "off",
      "minimal",
      "low",
      "medium",
      "high",
      "xhigh",
      "max"
    ],
    "refusal": null
  },
  "pending_prompts": []
}
FieldIs
turn_idThe running turn. Absent when none runs.
queue_depthPrompts waiting behind it.
admission_stateready or active.
cursorThe newest place in the log: the one to resume from.
targetThe provider, model and effort the next turn uses.
pending_promptsThe queued prompts.

The schema

Every command and every frame, both ways, is src/src/wire/protocol.schema.json, JSON Schema 2020-12. Of the 596 JSON lines the runs behind this page sent and got back, 591 check against it. The other five are wrong commands sent on purpose. Check yours before you send them.

See Troubleshooting for a line you got, and The gateway for keeping an organism running.

Esc

    Loading the index…

    ↑↓ moveEnter openEsc close