Skip to main content

Module subprocess

Module subprocess 

Source
Expand description

SubprocessController: manages agent CLI as stateless per-turn subprocess invocations.

Architecture: Each user message spawns a fresh claude -p process. Multi-turn continuity uses --resume <session-id>. The process reads one JSON message from stdin, runs the agentic loop, streams NDJSON on stdout, then exits.

State machine: INIT ─(spawn)─> RUNNING ─(process exits)─> DONE DONE ─(new message)─> RUNNING (re-spawn with –resume)

I/O model (2 async tasks per turn):

  1. stdout_reader: piped stdout → .jsonl persistence + MPS blockfile events on “output” subject
  2. process_waiter: wait for exit, update status, publish lifecycle event

§Module layout

This module was split from a single ~1770-line subprocess file into a directory so each piece of the (large, deliberately-not-decomposed-further) turn state machines could live in its own file:

  • mod.rs (this file): spawn config, inner state, controller struct + bookkeeping (new/lock helpers/status snapshot/publish), and impl Controller for SubprocessController.
  • session: session-id capture/hydration state machine.
  • host_spawn: spawn_turn — host-subprocess turn via std::process.
  • container_spawn: spawn_container_turn — Docker-exec turn via bollard, plus its publish_line output helper.
  • tests (cfg(test)): unit tests, mostly session-id focused.

host_spawn and container_spawn are impl SubprocessController blocks in their own files — Rust allows multiple impl <Type> blocks across files/modules for the same type. Each turn method is moved WHOLE into its file rather than decomposed further: both are continuous, non-trivially- ordered state machines where splitting the body would make the control flow harder to follow, not easier.

Modules§

argv 🔒
Provider-aware construction of per-turn subprocess arguments.
container_spawn 🔒
Container-exec turn spawn (spawn_container_turn) for SubprocessController.
host_spawn 🔒
Host-subprocess turn spawn (spawn_turn) for SubprocessController.
session 🔒
Session-id capture and hydration state machine for SubprocessController.

Structs§

SubprocessController
SubprocessController manages per-turn subprocess lifecycle for agent blocks.
SubprocessControllerInner 🔒
Inner state protected by mutex.
SubprocessSpawnConfig
Configuration for spawning a subprocess turn.
UserRecordSink 🔒
Writes the user’s message to the transcript, as the record the persistent Claude controller writes for each stdin line (Phase 5a-3c): a {"type":"user","message":{...}} line in the block’s file and the agent’s global zone, published as an echo: "stdin" event — the pane already shows the message (its agent-message-accepted node), so the event only keeps its stream gap-free. Without it these CLIs’ transcripts hold no trace of what the user sent: gone on reload, absent from History, and a turn the live feed can’t roll off (SPEC_AGENT_PANE_BOUNDED_LIVE_WINDOW_MIGRATION_2026_09_23.md §6.9).

Constants§

BLOCK_CONTROLLER_SUBPROCESS
SUBPROCESS_OUTPUT_SUBJECT
MPS file subject name for subprocess output (replaces “term” from PTY).

Functions§

persists_user_record 🔒
Output formats whose CLI never writes the user’s prompt into its own output, so the transcript would otherwise hold no record of it. Gemini’s CLI echoes the prompt itself (rendered since #3620) — a second record would show the message twice on replay. ACP and the Codex App Server run through other controllers.
user_record 🔒
The transcript record for a user message: the same shape the persistent Claude controller writes, which every replay translator reads.