Commit Graph
6 Commits
Author SHA1 Message Date
hh 621da90623 feat(stream): warm-up turn, because MCP servers aren't up on turn one
The CLI starts connecting its MCP servers when a turn starts and does
not wait for them, so the first turn of a session sees only built-in
tools. Measured on the pi with two HTTP MCP servers: turn 1 reports both
`pending` with 29 tools available, turn 2 reports both `connected` with
90. For an agent whose job is those tools, that first reply is silently
wrong — the model doesn't see them and answers as best it can.

The PTY transport hid this: its multi-second wait for the TUI to settle
happened to cover the connect. Nothing about the headless path does, and
nothing cheap fixes it — an 8s pause before the first prompt changed
nothing, a `/status` slash command returns in 134ms without touching the
MCP client, and a `control_request`/`initialize` handshake answers with
the command list and leaves the servers pending. Only a real turn does
it, so `warmup_turn` spends one deliberately: a few tokens and a couple
of seconds, once per session, against sessions that are pooled for the
whole conversation.

The price is two short messages at the head of the transcript, which is
why the default prompt reads as procedural rather than conversational.

Also log, at WARNING, any MCP server a turn starts without — this is a
failure with no other symptom, and it should be one grep away rather
than a mystery about the agent forgetting a tool it has.
2026-07-28 03:22:30 +02:00
hh 76799179d9 feat(stream): headless claude -p transport behind a transport flag
The PTY transport stands in for a protocol that did not exist when it
was written: it pastes bracketed text into claude's TUI, guesses when
the Ink render loop has settled, re-presses Enter when the paste is
swallowed, and tails the session JSONL at 100ms. `claude -p` with
stream-json on both pipes is that protocol, so add it as a second
transport and let callers pick with `BackendOptions.transport`.

Measured on one host, same model and prompt, cold single-reply turn:
pty burns 2.3s on TUI readiness before the prompt is even submitted
(first event 2.5s, turn 5.2s); stream_json burns none (first event
0.3s, first token 1.9s, turn 2.7s). The gap is what `startup_delay`'s
60s cap exists to survive on slow hardware.

Everything the old transport relies on carries over unchanged and was
verified against a real CLI: multi-turn over one live process, MCP via
--mcp-config, --resume, and `native_jsonl` history seeding. Two things
are new rather than equal: `result` is native (so usage is the turn's
aggregate and durations are real, not synthesized), and
`include_partial_messages` yields token-level `StreamEvent`s — which
the JSONL, holding only finished blocks, could never provide.

Notably, assistant records carry `stop_reason: null` in this mode even
at the end of a turn, so `result` is not just tidier than the PTY
path's terminal-stop_reason heuristic, it is the only correct signal.

Default stays `pty`; nothing changes for existing callers.

Also: extract process-group bookkeeping into `procgroup` so both
transports sweep claude's children on shutdown, and fix a stale
assertion in test_backend that still expected the `is_error: None` that
`_block_to_dict` deliberately stopped emitting.
2026-07-28 02:56:17 +02:00
hh 4f6585b1ac fix: shows live sessions for pty view 2026-06-04 22:35:39 +02:00
hh b5e0166c48 feat: better injection, pty snapshots 2026-05-23 00:12:15 +02:00
hh 1f20cef7d4 feat: add synthesize_turn_messages 2026-05-21 12:21:03 +02:00
hh bf6116dc8b feat: vibed out some slop over here also 2026-05-19 11:20:14 +02:00