v0.2.0: send — one entry, the path chooses itself, headless when the app is closed #2
Loading…
Reference in a new issue
No description provided.
Delete branch "oss-oo/appserver-send-headless"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
v0.2.0 —
oo-appserver sendThe owner's order (2026-10-27, ~21:55): a line must reach a Codex thread with and without the ChatGPT app, the way ZCode sessions are reached without the ZCode app. Design agreed with Z (steer 36/37): one entry whose path chooses itself, verb-first receipt classes that live only here, heal-when-safe only while the app is closed,
send --jsonas the transport contract for the oo-dispatchappserverbackend (lands as v1.0.94 there,claude-sessionas v1.0.95). The neighbour session's handoff (~/.zcode/tmp/claude/appserver-resume-measure-20261027/HANDOFF.md: the owner's nine statements, the measurements, nine proposals) is folded in.What ships
oo-appserver send <thread> "<line>"(libexec/appserver_send.py): observes the station once, then — app closed → headless (the shared server resumes the thread from its rollout withexcludeTurnsand a timeout scaled to the rollout size, thenturn/start, orturn/steerwhen a headless turn still runs); app attached →turn/starton an idle thread /turn/steerinto a running turn; app in stdio fallback or not attached → refused asheld-by-appwithout any network contact (never a second writer).healed/heal-skippedwith the why inskipped_because/heal-failed). A hung unit is restarted, a failed one reset first;oo-appserver healruns the same decision without sending; a disabled unit is honoured (door-closed).delivered-headless,delivered-app-start,delivered-app-steer,held-by-app,heal-skipped, plusrefused(incl.reason: unloaded-sub-agent),loading,timeout,door-closed,no-active-turn,dropped. Exit 0 only when delivered (1 refused · 2 usage · 3 held-by-app / no-active-turn · 4 door-closed · 5 timeout · 6 dropped · 7 loading).--jsonprints exactly one receipt line; every send row carries the same keys (unused ones null).excludeTurns5.5 KB; the first load of a thread the history does not know costs 0.011–0.02 s/MB (7.6 s warm / 14.1 s cold for 719 MB), later loads 0.14–0.28 s regardless of size. Resume timeoutmax(10 s, MB × 0.05)capped at 120 s (the 719 MB thread gets 36 s); past it the sender waits up to 60 s for the thread to appear inthread/loaded/list(the server finishes a load even when the client gives up; every call inside that wait is bounded) and resumes again; only thenloading— not an error, retry later, never resend, no throttle consumed.--from/--tag(sender tag[NAME 📡]),--fyi(the fleet's FYI template),--mode auto|start|steer,--wait,--min-interval(advisory per-thread throttle, marked right after the ack;0leaves no mark),--no-heal,--dry-run.doctortreats "ChatGPT not running" as the headless path, prints the headless readiness line and words the stale finding for the closed-app case.libexec/appserver_station.pyand the stdlib WebSocket clientlibexec/appserver_ws.py(bytearray buffer, bulk unmask, reply = id withoutmethod, deadline enforced inside a frame);appserver_turn.py/appserver_probe.pyunchanged (the README marks the old client as slow on big threads).tests/test_send.py: 62 legs against a fake app-server (tests/fake_appserver.py: readyz + JSON-RPC, slow first load in the background, slow loaded/list answers, turns in the reply, sub-agents, id collisions, stray completions, cut connections) with a canned station and a stand-in systemctl. Wired into CI;install.shinstalls the new modules and compiles them where they land.loading, receipts, backend contract, known gaps (two unmeasured edges named), the Claude-session parity target flag.Live proofs (station, app closed)
oo-appserver send 01a17dae-8f23-7e70-b236-26d45dc8a6e1 … --from claude --tag --fyi --wait 90(commitf23f2cd): healrestartin 941 ms (running codex-cli 0.164.0-alpha.11.1 → 0.165.0-alpha.1.2; gates: app closed, 0 clients, no active turn),thread/resume1 457 ms,turn/startacknowledged after 1 487 ms,turn/completedafter 7 185 ms with the answerok. Receiptdelivered-headless, heal rowhealed. The stale shared service noted in the handoff is thereby healed.1fcff63: slim resume (excludeTurns) 185 ms, ack 346 ms, completed 6 582 ms,ok,loaded_before: true(the thread stayed loaded since proof 1), no heal due.statuscounts the loaded thread,doctorreports no findings.Evidence:
~/.zcode/tmp/claude-adapter-design-20261027/v020-headless-proof/on the station; the measurements under~/.zcode/tmp/claude/appserver-resume-measure-20261027/.Independent reviews
Review 1 (fresh read-only subagent on
f23f2cd): 41/41 legs confirmed; should-fix —--min-interval 0wiped other threads' throttle memory and the temp path raced; replies matched by id only;statuscounted loaded threads wrongly; refusal rows lacked the contract keys and an unwritable receipt file hid the verdict; door closed with a hung or failed unit was not healed; re-read the client count right before the restart; quadratic read buffer; a throttle test that proved nothing. Folded in1fcff63(41 → 55 legs).Review 2 (second fresh read-only subagent on the delta f23f2cd..2685f62): all nine delta claims verified, 55/55 twice without flakiness, no path restarts with a client or turn present, no double send possible; should-fix — inside the loading wait,
thread/loaded/listand the second resume were unbounded by the deadline and could end astimeoutinstead ofloading; nits — throttle directory for nested paths and a temp-file leak, undocumented throttle semantics, receipt key notes, the newest-first fallback applied to a resume reply that carries turns, FYI wording, untested late-client and throttle-error paths, tight timing margins. Folded in1871c1e(55 → 60 legs). Confirmation of that fold-in by the same reviewer: "delta clean — mergeable as is" (the stalled-loaded/list probe now ends asloading); two nits — a leg that proved nothing because the fake honouredexcludeTurns, and the throttle mark landing after the completion wait — folded indde4b54(60 → 62 legs: turns in the reply by status, active status without a running turn, early mark).Review texts:
v020-review/in the evidence folder. CI green on every commit (runs 3–7).Stage
Code · offline tests green (62/62) · headless path live verified twice · app-attached paths unchanged in protocol terms from v0.1.0 (T7) and covered by the suite.
🤖 Generated with Claude Code
send— one entry, the path chooses itself, headless when the app is closedloading, safer throttle and healMeasurements (neighbour session's rig, copies of the station's rollouts, 2026-10-27): a full `thread/resume` of the 719 MB thread answers 143 MB (164 s in the v0.1.0 client); with `excludeTurns` 5.5 KB. The first load of a thread the history does not know costs 0.011–0.02 s/MB (7.6 s warm / 14.1 s cold for 719 MB), later loads 0.14–0.28 s regardless of size; the server finishes a load even when the client gives up; an unloaded multi-agent v2 sub-agent cannot be resumed through its parent. - resume with `excludeTurns: true`; a running turn is `thread.status.type == "active"`, its id comes from `thread/turns/list {limit: 1}`; `loaded_before` in the receipt. - resume timeout defaults 10 s / 0.05 s per MB / 120 s (the 719 MB thread gets 36 s, 2.5x its worst first load); past the deadline the sender waits up to 60 s for the thread to appear in `thread/loaded/list` and resumes again; only then the receipt says `loading` (exit 7): not an error, retry later, never resend — and it does not consume the throttle window. - `refused` names `unloaded-sub-agent` with the server's rule as the note. - WebSocket client: bytearray buffer, bulk unmask, a reply is matched by id AND no `method` (server→client requests can never pass as the ack); late replies to timed-out calls are dropped. Independent read-only review off23f2cd(all findings folded): - throttle: `--min-interval 0` no longer wipes other threads' memory (no mark at all), the mark re-reads and merges under flock and writes through mkstemp; a failing mark or an unwritable receipt file never hides the verdict (`throttle_mark_error`, `receipt_write_error` in the row). - every send row carries the same contract keys (`SEND_KEYS`, unused ones null), refusals included. - heal: door closed with a hung (`active`) unit → `restart`, with a `failed` unit → `reset-failed` + `start`; the client count is read once more right before the restart (`client-connected-late`); the receipt keeps the observed station, not a patched state. - `oo-appserver status` counts the loaded threads from the probe's "N entries" line. - `--fyi` matches case-insensitively; docstrings and usage lines corrected. - suite 41 → 55 legs: id collision, ack timeout, heal-failed → door-closed, hung/failed unit actions, loaded-check cut, stray turn/completed, throttle memory with `--min-interval 0`, refusal rows' keys, slow first load awaited, `loading`, sub-agent refusal, unwritable receipt file; the throttle-window leg now refuses on the same thread; rigs clean up their temp dirs. - README: measurements, `loading`, heal actions, receipt keys, the unanswered server requests. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>