Files
OpenResearch/src
Daniel Kim d0388778ae One persistent Claude Code child per chat session (#112)
* Persistent Claude Code child per chat session

Replace the spawn-per-turn + --resume fork with one long-lived
`claude --print --input-format stream-json` child per chat session
(new src/local/claude.rs ClaudeHost/ClaudeClient, mirroring codex.rs),
collapsing ~3-6s of per-turn spawn/config/MCP-handshake overhead.

- Respawn policy: child keyed by SpawnConfig; permission-mode/effort/
  bridge change or a cleared model -> kill + respawn with --resume;
  model-only change -> set_model control request; identical -> reuse.
- run_turn rewritten around a pure, store-free apply_event(ctx, state,
  event) folded over the child's stream-json output; native-session-id
  and flush stay in the caller.
- Gate token minted only at spawn (re-minting would strand a live
  bridge child); process_group(0) so terminal SIGINT can't kill a
  resident child mid-turn.
- ChatHost gains a claude host; interrupt/delete/shutdown and up.rs's
  project-delete loop kill the resident child (it survives task-abort).
- Tests: child_action matrix, classify_line variants, apply_event
  fixture tests over hand-written stream-json.

* Review round 1 fixes: drop ClaudeHost back-ref, fix achieved bridge_active, prune dead code

- spawn_client: force config.bridge_active = false before the plan block so a
  failed bridge write records false (was inheriting wanted=true, which both
  suppressed plan cards with no bridge to hold them and defeated the
  respawn-to-retry recovery)
- SpawnSpec carries the ChatHost directly (transient, no cycle); deletes the
  Weak attach/OnceLock apparatus and the duplicated up_port
- drop dead ClaudeHost::client_for; claude_effort back to private
- truthful docs: write_mcp_config and gate_tokens are per-spawn/per-child now
- new fixture tests: result without is_error falls back to subtype; errored
  tool_result flips the tool part to error

* Comment nit from clean review round: gate token is per-child, not per-turn

* Stream partial text/thinking deltas into the transcript

Without --include-partial-messages the CLI only emits complete assistant
messages, so each text block landed at once instead of streaming. Deltas
build the part under the same {mid}-{index} id the final assistant event
upserts, so the authoritative text overwrites the accumulated one; a
subagent's nested stream (parent_tool_use_id) is ignored.

* Note the stream-protocol invariants the delta overwrite leans on
2026-07-22 12:15:21 -07:00
..
2026-06-07 00:35:02 -07:00
2026-06-07 00:35:02 -07:00