oo-claude v0.1.0 — the Claude-house transport (inbox doorman, send, resume, start, read) #1

Merged
claude merged 13 commits from oss-oo/oo-claude-v0.1.0 into main 2026-10-30 21:59:27 +00:00
Member

What

oo-claude v0.1.0 — the Claude house's transport (the counterpart of oo-appserver for the Codex house): steer a running Claude Code session, resume a sleeping one for one headless turn, start a new one the family can steer while it works, read transcripts, list sessions with riders (sub-agents), inboxes and lineage. One helper oo-claude, modules in libexec/, Python 3.12 stdlib only.

The law it builds on

Claude Code (≥ 2.1.308) keeps one Unix-socket door per running session, but the receiver carries a line to the model only when the sending process descends from the receiving session's own claude process (measured 2026-10-28/30, PINGPONG-20261030.md). So senders only append a line to the session's inbox file, and the session's own doorman — its MCP stdio server, a child of its claude process — delivers it through the door. Verified live in this round (preflight + proof B): the line surfaces mid-turn, the transcript records origin.kind: peer with verifiedPeerPid = the doorman, absorbed_mid_turn, and the session quotes it.

Contract

Implements the inbox file contract v1 (~/.zcode/tmp/claude-inbox/CONTRACT-v1.md, quoted verbatim in the README) — paths, heartbeat (fresh < 15 s), ack JSON with the sha256 of the exact line bytes, receiver offset, sender classes and exit codes — and the lineage ledger v0. Extensions are extra keys only (ack line_offset/inode/from/…, heartbeat state/peer_pid/…, ack class refused for undeliverable lines, a .doorman.lock).

Verbs, classes, exit codes

Verb Classes (exit)
send SESSION "line" [--from] [--json] [--wait] [--min-interval] [--no-tag] delivered-steer 0 · delivered-start 0 · refused 1 · queued-no-doorman 3 · queued-offline 3 · timeout 5 · dropped 6
doorman --mcp (registration, start) · doorman --allow-foreground [--session] [--once] MCP 2025-11-25 over stdio (Claude Code's server/discover → -32601 → initialize); foreground exit 0 / 2 no --allow-foreground / 3 not deliverable here / 4 delivery failed, line waits
resume SID "line" [--model] [--max-turns] [--budget] [--tools] [--cwd] [--timeout] [--json] delivered-resume 0 · refused 1 · held-by-session 3 · error 4 · timeout 5
start "prompt" [...] [--systemd] [--no-doorman | --user-mcp] completed 0 (started 0 with --systemd) · refused 1 · failed 4 · timeout 5; the doorman is injected by default
read SESSION [--turns N] [--json] peer lines marked with from + msg_id
sessions / riders [SESSION] [--json] [--all] [--write] observatory schema oo-claude/sessions/v0 (reference field names kept) incl. riders, inbox, host_session/parent_*/children
lineage register --parent … / lineage show ledger v0, idempotent on (child, parent), by child|parent|dispatcher
doctor findings, exit 1 when any

Doorman safety: the owner is the nearest ancestor with a live registry row (else a claude executable); an env socket is used only when served by the owner (<pid>.sock name or SO_PEERCRED), every delivery checks the listener with SO_PEERCRED (measured: the session's claude pid) — an inherited socket variable of a grand-parent session or a claude mcp list health check never carries a line into the wrong session. One deliverer per inbox (flock), no double delivery on replay, monitor-era inboxes adopted without replaying history.

Tests

Hermetic suite (fake registry, fake door socket, fake claude, fake systemd-run, a listener outside the test's process tree, a claude-named wrapper; subprocesses never see the calling session's door variables, but keep every other CLAUDE* name plus a fake desktop identity so an env leak shows): 104 tests, locally python3 -m unittest discover -s tests → Ran 104 tests … OK (pytest is not installed on the station); CI ci / check (pull_request) green on 0cf2232 with python3 -m pytest -q tests → 104 passed, plus py_compile, bash -n and an install into a scratch HOME with --check. Mutation check: the per-delivery SO_PEERCRED check, the env-socket ownership check and the explicit---session rule each fail the suite when disabled (evidence/proof-A2/mutants.txt).

Scope addenda (coordinator, 2026-10-30 20:4x–20:5x)

Riders (oo-claude riders, riders column in sessions), the observatory schema oo-claude/sessions/v0 with --write (field names of claude-riders.py kept, fields only added; riding = changed within 600 s and not ended on a final answer), and the lineage ledger v0 (lineage register|show, host_session/parent_host/parent_session/children in sessions --json, children indented; start records the calling session as parent per the contract).

Live proofs (2026-10-30, Claude Code 2.1.308, claude-haiku-5-5; evidence ~/.zcode/tmp/OSS-OO/oo-claude/evidence/)

Proof Result Evidence
preflight probe MCP child wrote one line through the door of a running claude -p; the session answered with it (0.014 USD) preflight/
B steer into a running turn delivered-steer, ack after 0.6 s; transcript peer row with verifiedPeerPid = doorman pid 877283, absorbed_mid_turn; answer quotes the line (0.015 USD). The requested sleep 25 was refused by the session's harness ("standalone sleep"), the turn ran ~11 s of tool rounds and the line landed between them proof-B/send-receipt.json, session-A-result.json, transcript-rows.jsonl, session-A-debug-excerpt.txt
C idle start not provable with -p (no idle state) — the coordinator proves it in a desktop session after the install —
D resume delivered-resume, result PONG (0.028 USD); second running session → held-by-session (registry row + process scan), untouched proof-D/resume-receipt.json, held-receipt.json
E start completed READY (0.015 USD); --systemd returned at once with unit oo-claude-ffde609f, completed in 4 s (0.001 USD) proof-E/
F sessions, riders, read, doctor, lineage show outputs proof-F/
B2, E2 on head 89ad161 after the post-proof fixes: B2 delivered-steer (ack 1.0 s, peer row verifiedPeerPid = doorman, absorbed_mid_turn, quoted; 0.014 USD); E2 --systemd completed READY, result file names the unit at return and keeps the lineage block (0.012 USD) proof-B2-head/, proof-E2-head/

| B3, D2, E3 on head a3825ec (after the fold) | B3 delivered-steer (ack 0.8 s; the line arrived 0.23 s after the last tool round and was answered as the next turn, turnOrigin: peer); D2 delivered-resume PONG, resumed rows entrypoint: sdk-cli; E3 foreground + --systemd completed READY, doorman: injected, doorman_heartbeat = the injected doorman | proof-B3-fold/, proof-D2-fold/, proof-E3-fold/ |

| R1 measurement on 96f0c3a | start --max-turns 1 --budget 0.05, one line steered in during turn 1: a follow-up turn ran after max_turns_reached (--max-turns per turn); its budget used carried over (--max-budget-usd per process); the single result said success / STEERED / num_turns 1 (turn 1's outcome not in it), total_cost_usd = the process total; doorman_delivered 1 + note | r1-measure/MEASUREMENT.txt |

Total model cost of all live runs: ≈ 0.20 USD. Proofs B, D, E ran on the working tree before three small fixes (start's result file keeps unit + lineage, sender-name check + upper-case ids in send, a type guard for rider meta); B2/E2 repeat B and the systemd start on the pushed head 89ad161. bf15561 added two hardening changes covered by the suite (single-write JSONL appends; a heartbeat whose state is not following is not fresh); the review fold is 4bc135d..c536951.

Review v1 fold (REVIEW-OO-CLAUDE-v010-v1: no must-fix, six should-fix, 17 nits)

Item Fix
S1 env leak into child claudes resume/start (foreground) and doctor drop every CLAUDE* variable except CLAUDE_CONFIG_DIR + provider switches; --systemd uses the same keep-list. Live: resumed/started turns record entrypoint: sdk-cli (D2, E3)
S2 heartbeat gap during a drain heartbeat refreshed inside the replay loop; door settle 0.25 s; test: 16 lines at 0.5 s each, heartbeat never older than 5 s
S3 line over the 1 MiB chunk blocks the inbox acked refused (sha256 over the whole line), offset moved past it; test
S4 SO_PEERCRED checks untested both checks accept only the owner itself; tests with a double-forked foreign listener (registry socket → peer-mismatch, nothing written) and a free-named env socket (→ socket-not-mine); mutants killed
S5 --session without a registry row session-unverifiable (an explicit id is never trusted on the environment alone); test via a claude-named owner without a row
S6 no warning README/docstring/help: never run a doorman from a session's or a rider's tool; supported: the MCP registration and start's injection; the foreground mode needs --allow-foreground
Q1 throttle stays 30 s, keyed on (target, sender); a dropped line is not written and must be resent (reason/note say so)
Q3 doorman injection start injects --mcp-config holding only /usr/bin/python3 -I <libexec>/doorman.py --mcp + --strict-mcp-config; --no-doorman, --user-mcp; result JSON doorman + doorman_heartbeat (E3: the injected doorman ran)
Q4 Known gaps: the idle-exit ack gap and its two v0.2 mitigations
Nits folded N1 owner rule doc = code; N2 pending_bytes; N3 at-least-once wording; N4 echo 0 > <inbox>.offset + retire the Monitor tail; N5 -32603 per-message guard + thread_alive; N7 name fragments ≥ 4 chars; N8 --from / structured from sanitised; N9 per-session resume lock; N11 lineage idempotent against the newest link; N12 parent from the nearest registered ancestor; N13 no inbox GC (documented); N14 /usr/bin/python3 -I recipe
Nits skipped N6 (running is defined by the registry; doorman freshness is in doorman.fresh), N10 (documented under Known gaps instead of a --force flag), N15 (Claude Code's own labelling of MCP stderr), N16 (the not-descendant branch needs a tree without any claude ancestor — CI covers it; the new wrapper tests make the other identity branches deterministic), N17 (the doorman never changes files it did not create; the dir is 0700)

Review v2 fold (re-review of c536951: one should-fix, three nits)

Item Fix
R1 start records only the final turn result JSON doorman_delivered + a note when > 0; README Known gaps + start --help; measured once (row above): --max-turns per turn, --max-budget-usd per process, total_cost_usd = process total (corrects the reading of B3: its 0.0148 is the process total, turn 2 cost about 0.0009) — only result/subtype/num_turns describe the final turn; v0.2: --output-format stream-json, keep every result
R2 keep-list the exact 18 provider/auth names of the 2.1.308 binary (README lists them); a credential descriptor is passed to the child; --systemd never passes tokens or descriptors (nor companion variables — documented)
R3 --no-doorman and --user-mcp are mutually exclusive (exit 2); test
R4 an over-long line is refused once per inode/offset/sha256, also after a crash between ack and offset write; the scan of an unterminated long line resumes where it stopped; tests

Not done (the coordinator's steps)

  • idle-start proof (C) in a desktop session; user-scope MCP registration (claude mcp add --scope user claude-inbox -- python3 /home/claudiu/tools/oo-claude/libexec/doorman.py --mcp) and claude mcp list ✔ Connected; install from the tag; merge and release.

Open questions

None open from round 1 — the coordinator decided Q1–Q4 (table above).

🤖 Generated with Claude Code

## What **oo-claude v0.1.0** — the Claude house's transport (the counterpart of `oo-appserver` for the Codex house): steer a **running** Claude Code session, resume a **sleeping** one for one headless turn, start a **new** one the family can steer while it works, read transcripts, list sessions with riders (sub-agents), inboxes and lineage. One helper `oo-claude`, modules in `libexec/`, Python 3.12 stdlib only. ## The law it builds on Claude Code (≥ 2.1.308) keeps one Unix-socket door per running session, but **the receiver carries a line to the model only when the sending process descends from the receiving session's own `claude` process** (measured 2026-10-28/30, `PINGPONG-20261030.md`). So senders only append a line to the session's inbox file, and the session's own **doorman** — its MCP stdio server, a child of its `claude` process — delivers it through the door. Verified live in this round (preflight + proof B): the line surfaces mid-turn, the transcript records `origin.kind: peer` with `verifiedPeerPid` = the doorman, `absorbed_mid_turn`, and the session quotes it. ## Contract Implements the inbox file contract v1 (`~/.zcode/tmp/claude-inbox/CONTRACT-v1.md`, quoted verbatim in the README) — paths, heartbeat (fresh < 15 s), ack JSON with the sha256 of the exact line bytes, receiver offset, sender classes and exit codes — and the lineage ledger v0. Extensions are extra keys only (ack `line_offset`/`inode`/`from`/…, heartbeat `state`/`peer_pid`/…, ack class `refused` for undeliverable lines, a `.doorman.lock`). ## Verbs, classes, exit codes | Verb | Classes (exit) | |---|---| | `send SESSION "line" [--from] [--json] [--wait] [--min-interval] [--no-tag]` | delivered-steer 0 · delivered-start 0 · refused 1 · queued-no-doorman 3 · queued-offline 3 · timeout 5 · dropped 6 | | `doorman --mcp` (registration, `start`) · `doorman --allow-foreground [--session] [--once]` | MCP 2025-11-25 over stdio (Claude Code's `server/discover` → -32601 → `initialize`); foreground exit 0 / 2 no `--allow-foreground` / 3 not deliverable here / 4 delivery failed, line waits | | `resume SID "line" [--model] [--max-turns] [--budget] [--tools] [--cwd] [--timeout] [--json]` | delivered-resume 0 · refused 1 · held-by-session 3 · error 4 · timeout 5 | | `start "prompt" [...] [--systemd] [--no-doorman \| --user-mcp]` | completed 0 (started 0 with --systemd) · refused 1 · failed 4 · timeout 5; the doorman is injected by default | | `read SESSION [--turns N] [--json]` | peer lines marked with `from` + `msg_id` | | `sessions` / `riders [SESSION]` `[--json] [--all] [--write]` | observatory schema `oo-claude/sessions/v0` (reference field names kept) incl. riders, inbox, `host_session`/`parent_*`/`children` | | `lineage register --parent … / lineage show` | ledger v0, idempotent on (child, parent), `by` child\|parent\|dispatcher | | `doctor` | findings, exit 1 when any | Doorman safety: the owner is the nearest ancestor with a live registry row (else a `claude` executable); an env socket is used only when served by the owner (`<pid>.sock` name or `SO_PEERCRED`), every delivery checks the listener with `SO_PEERCRED` (measured: the session's `claude` pid) — an inherited socket variable of a grand-parent session or a `claude mcp list` health check never carries a line into the wrong session. One deliverer per inbox (flock), no double delivery on replay, monitor-era inboxes adopted without replaying history. ## Tests Hermetic suite (fake registry, fake door socket, fake `claude`, fake `systemd-run`, a listener outside the test's process tree, a `claude`-named wrapper; subprocesses never see the calling session's door variables, but keep every other `CLAUDE*` name plus a fake desktop identity so an env leak shows): **104 tests**, locally `python3 -m unittest discover -s tests` → `Ran 104 tests … OK` (pytest is not installed on the station); CI `ci / check (pull_request)` green on `0cf2232` with `python3 -m pytest -q tests` → `104 passed`, plus `py_compile`, `bash -n` and an install into a scratch HOME with `--check`. Mutation check: the per-delivery `SO_PEERCRED` check, the env-socket ownership check and the explicit-`--session` rule each fail the suite when disabled (`evidence/proof-A2/mutants.txt`). ## Scope addenda (coordinator, 2026-10-30 20:4x–20:5x) Riders (`oo-claude riders`, riders column in `sessions`), the observatory schema `oo-claude/sessions/v0` with `--write` (field names of `claude-riders.py` kept, fields only added; riding = changed within 600 s and not ended on a final answer), and the lineage ledger v0 (`lineage register|show`, `host_session`/`parent_host`/`parent_session`/`children` in `sessions --json`, children indented; `start` records the calling session as parent per the contract). ## Live proofs (2026-10-30, Claude Code 2.1.308, `claude-haiku-5-5`; evidence `~/.zcode/tmp/OSS-OO/oo-claude/evidence/`) | Proof | Result | Evidence | |---|---|---| | preflight | probe MCP child wrote one line through the door of a running `claude -p`; the session answered with it (0.014 USD) | `preflight/` | | B steer into a running turn | `delivered-steer`, ack after 0.6 s; transcript peer row with `verifiedPeerPid` = doorman pid 877283, `absorbed_mid_turn`; answer quotes the line (0.015 USD). The requested `sleep 25` was refused by the session's harness ("standalone sleep"), the turn ran ~11 s of tool rounds and the line landed between them | `proof-B/send-receipt.json`, `session-A-result.json`, `transcript-rows.jsonl`, `session-A-debug-excerpt.txt` | | C idle start | not provable with `-p` (no idle state) — the coordinator proves it in a desktop session after the install | — | | D resume | `delivered-resume`, result `PONG` (0.028 USD); second running session → `held-by-session` (registry row + process scan), untouched | `proof-D/resume-receipt.json`, `held-receipt.json` | | E start | `completed` `READY` (0.015 USD); `--systemd` returned at once with unit `oo-claude-ffde609f`, completed in 4 s (0.001 USD) | `proof-E/` | | F | `sessions`, `riders`, `read`, `doctor`, `lineage show` outputs | `proof-F/` | | B2, E2 on head `89ad161` | after the post-proof fixes: B2 `delivered-steer` (ack 1.0 s, peer row `verifiedPeerPid` = doorman, `absorbed_mid_turn`, quoted; 0.014 USD); E2 `--systemd` `completed` `READY`, result file names the unit at return and keeps the lineage block (0.012 USD) | `proof-B2-head/`, `proof-E2-head/` | | B3, D2, E3 on head `a3825ec` (after the fold) | B3 `delivered-steer` (ack 0.8 s; the line arrived 0.23 s after the last tool round and was answered as the next turn, `turnOrigin: peer`); D2 `delivered-resume` `PONG`, resumed rows `entrypoint: sdk-cli`; E3 foreground + `--systemd` `completed` `READY`, `doorman: injected`, `doorman_heartbeat` = the injected doorman | `proof-B3-fold/`, `proof-D2-fold/`, `proof-E3-fold/` | | R1 measurement on `96f0c3a` | `start --max-turns 1 --budget 0.05`, one line steered in during turn 1: a follow-up turn ran after `max_turns_reached` (`--max-turns` per turn); its budget `used` carried over (`--max-budget-usd` per process); the single result said `success` / `STEERED` / `num_turns 1` (turn 1's outcome not in it), `total_cost_usd` = the process total; `doorman_delivered 1` + note | `r1-measure/MEASUREMENT.txt` | Total model cost of all live runs: ≈ 0.20 USD. Proofs B, D, E ran on the working tree before three small fixes (start's result file keeps unit + lineage, sender-name check + upper-case ids in `send`, a type guard for rider meta); B2/E2 repeat B and the systemd start on the pushed head `89ad161`. `bf15561` added two hardening changes covered by the suite (single-write JSONL appends; a heartbeat whose state is not `following` is not fresh); the review fold is `4bc135d`..`c536951`. ## Review v1 fold (REVIEW-OO-CLAUDE-v010-v1: no must-fix, six should-fix, 17 nits) | Item | Fix | |---|---| | S1 env leak into child claudes | `resume`/`start` (foreground) and `doctor` drop every `CLAUDE*` variable except `CLAUDE_CONFIG_DIR` + provider switches; `--systemd` uses the same keep-list. Live: resumed/started turns record `entrypoint: sdk-cli` (D2, E3) | | S2 heartbeat gap during a drain | heartbeat refreshed inside the replay loop; door settle 0.25 s; test: 16 lines at 0.5 s each, heartbeat never older than 5 s | | S3 line over the 1 MiB chunk blocks the inbox | acked `refused` (sha256 over the whole line), offset moved past it; test | | S4 SO_PEERCRED checks untested | both checks accept only the owner itself; tests with a double-forked foreign listener (registry socket → `peer-mismatch`, nothing written) and a free-named env socket (→ `socket-not-mine`); mutants killed | | S5 `--session` without a registry row | `session-unverifiable` (an explicit id is never trusted on the environment alone); test via a `claude`-named owner without a row | | S6 no warning | README/docstring/help: never run a doorman from a session's or a rider's tool; supported: the MCP registration and `start`'s injection; the foreground mode needs `--allow-foreground` | | Q1 throttle | stays 30 s, keyed on (target, sender); a dropped line is not written and must be resent (reason/note say so) | | Q3 doorman injection | `start` injects `--mcp-config` holding only `/usr/bin/python3 -I <libexec>/doorman.py --mcp` + `--strict-mcp-config`; `--no-doorman`, `--user-mcp`; result JSON `doorman` + `doorman_heartbeat` (E3: the injected doorman ran) | | Q4 | Known gaps: the idle-exit ack gap and its two v0.2 mitigations | | Nits folded | N1 owner rule doc = code; N2 `pending_bytes`; N3 at-least-once wording; N4 `echo 0 > <inbox>.offset` + retire the Monitor tail; N5 `-32603` per-message guard + `thread_alive`; N7 name fragments ≥ 4 chars; N8 `--from` / structured `from` sanitised; N9 per-session resume lock; N11 lineage idempotent against the newest link; N12 parent from the nearest registered ancestor; N13 no inbox GC (documented); N14 `/usr/bin/python3 -I` recipe | | Nits skipped | N6 (`running` is defined by the registry; doorman freshness is in `doorman.fresh`), N10 (documented under Known gaps instead of a `--force` flag), N15 (Claude Code's own labelling of MCP stderr), N16 (the not-descendant branch needs a tree without any `claude` ancestor — CI covers it; the new wrapper tests make the other identity branches deterministic), N17 (the doorman never changes files it did not create; the dir is 0700) | ## Review v2 fold (re-review of c536951: one should-fix, three nits) | Item | Fix | |---|---| | R1 `start` records only the final turn | result JSON `doorman_delivered` + a `note` when > 0; README Known gaps + `start --help`; measured once (row above): `--max-turns` per turn, `--max-budget-usd` per process, `total_cost_usd` = process total (corrects the reading of B3: its 0.0148 is the process total, turn 2 cost about 0.0009) — only `result`/`subtype`/`num_turns` describe the final turn; v0.2: `--output-format stream-json`, keep every result | | R2 keep-list | the exact 18 provider/auth names of the 2.1.308 binary (README lists them); a credential descriptor is passed to the child; `--systemd` never passes tokens or descriptors (nor companion variables — documented) | | R3 | `--no-doorman` and `--user-mcp` are mutually exclusive (exit 2); test | | R4 | an over-long line is refused once per inode/offset/sha256, also after a crash between ack and offset write; the scan of an unterminated long line resumes where it stopped; tests | ## Not done (the coordinator's steps) - idle-start proof (C) in a desktop session; user-scope MCP registration (`claude mcp add --scope user claude-inbox -- python3 /home/claudiu/tools/oo-claude/libexec/doorman.py --mcp`) and `claude mcp list` ✔ Connected; install from the tag; merge and release. ## Open questions None open from round 1 — the coordinator decided Q1–Q4 (table above). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
The door of a running Claude Code session shows the model only lines from
processes that descend from the session's own claude process (measured
2026-10-28/30). The doorman is such a descendant — normally the session's MCP
stdio server — and carries the lines the family appends to the session's
inbox file through the session's door.

- claude_station: paths and env seams, registry rows (live = pid alive,
  procStart and pid namespace match), process ancestry, transcript lookup and
  Claude Code's project-dir encoding, receipts, child env without the calling
  session's door/token/id
- claude_inbox: contract v1 file logic (line validation, sender tags,
  structured lines, locked appends with offsets, heartbeat freshness, acks
  matched by sha256 + line_offset, offsets, the observatory's inbox view)
- claude_door: one door write; SO_PEERCRED gate on the listener
- doorman: identity from ancestry + registry (session switch, explicit
  --session mismatch, env socket only when served by the owner), one
  deliverer per inbox (flock), replay without double delivery, truncation and
  replacement, monitor-era inboxes adopted without replaying history, retry
  with backoff, refused acks for undeliverable lines, heartbeat, clean stop on
  EOF/SIGINT/SIGTERM, OO_CLAUDE_DOORMAN=0; MCP 2025-11-25 over stdio
  (server/discover -> -32601, initialize, ping, tools/list, tools/call)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- sessions/riders emit oo-claude/sessions/v0 (the reference script's field
  names kept, fields only added; --write atomic): live sessions, riders from
  <sessionId>/subagents/agent-*.jsonl + meta.json (riding = changed within
  600 s and not ended on a final answer), riders column, inbox and doorman
  state, offline inboxes with waiting lines
- lineage ledger v0: register (idempotent on child + parent, ids filled from
  the registry, by child|parent|dispatcher) and show; sessions joins
  host_session, parent_host, parent_session, children and indents children
- doctor: binary, registry, inbox dir mode, state dir, the doorman's MCP
  registration, this session's doorman, offline inboxes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- send: append one line, then the contract's verdict from heartbeat and
  ack — delivered-steer / delivered-start / refused / queued-no-doorman /
  queued-offline / timeout / dropped (exit 0/0/1/3/3/5/6); sender tags,
  sender-name check, per-target throttle that refusals never consume,
  receipts without tokens
- resume: one headless turn into a session that is not running; refuses
  held-by-session (live registry row, fresh doorman, a claude process naming
  the session); cwd from the transcript's recorded cwd that encodes to its
  project dir; classes delivered-resume / refused / held-by-session / error
  (subtype, terminal_reason, cost) / timeout
- start: a new headless session with the MCP config kept (the user-scope
  doorman steers it), inbox created first, result JSON started ->
  completed/failed/timeout; --systemd detaches with -E NAME passthrough
  only and the prompt in a 0600 file; the calling session is recorded as
  parent in the lineage ledger
- read: the last exchanges of a transcript, peer lines (mid-turn
  queued_command and turn-starting peer prompts) marked with from + msg_id

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- oo-claude: one entry for sessions, riders, send, doorman, resume, start,
  read, lineage, doctor, version (each verb runs its libexec module with
  python3 -I)
- install.sh --target DIR [--check] [--live]: installs the helper and every
  libexec module (stale ones removed), links ~/.local/bin/oo-claude,
  py_compiles where it lands; --check is the byte-diff of the installed
  copy against the checkout plus the link
- CI (ci / check): py_compile, the hermetic pytest suite, bash -n, an
  install into a scratch HOME with --check

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fake registry rows (this test process's pid, real procStart), a fake door
socket that records auth and user frames, a fake claude binary and a fake
systemd-run; every subprocess runs without the calling session's door
variables. Covers contract v1 file logic, the doorman (replay, follow,
acks, offsets, heartbeat, truncation, replacement, dedupe, refusals,
standby, the descendant and socket-ownership rules, monitor-era adoption),
the MCP handshake incl. a line delivered while the conversation runs, every
send verdict and exit code, resume and start (argv, clean child env,
errors, timeouts, held-by-session, systemd detach), read, sessions/riders
(schema v0), lineage, doctor and install.sh.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
hardening: single-write JSONL appends, only a following doorman is fresh
All checks were successful
ci / check (pull_request) Successful in 30s
bf15561cf9
- write_jsonl appends a record with one write(2) on an O_APPEND descriptor,
  so concurrent appenders (senders, a doorman) never interleave records in
  receipts, acks or the lineage ledger
- heartbeat_view: a heartbeat whose state is set and not `following`
  (stopped, switched) is not fresh — a sender to a session the doorman just
  left gets queued-no-doorman at once instead of a timeout
- README: the head re-checks B2/E2

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Should-fix items of the fresh review (REVIEW-OO-CLAUDE-v010-v1):
- S1 child_env drops every CLAUDE* variable of the calling session except
  CLAUDE_CONFIG_DIR and the provider switches (no CLAUDECODE, entrypoint,
  child-session, attended, host-auth-refresh, host session id, pids, SDK
  vars); --systemd passes the same keep-list. The suite keeps those names
  (and adds a fake desktop identity) so a leak is visible.
- S2 the doorman refreshes its heartbeat inside a long replay; the door
  settle is 0.25 s (OO_CLAUDE_DOOR_SETTLE_S), connect timeout 3 s.
- S3 a line longer than the 1 MiB read chunk is acked refused (sha256 over
  the whole line) and skipped instead of blocking the inbox.
- S4 both socket checks accept only the owner itself (SO_PEERCRED pid ==
  owner); tests with a listener outside the process tree and a free-named
  env socket kill the two mutants that survived before.
- S5 an explicit --session needs the owner's registry row
  (session-unverifiable otherwise).
- S6 the foreground mode needs --allow-foreground; the help warns never to
  run a doorman from a session's or a rider's own tool.

Coordinator decisions and nits: start injects the doorman by default
(--mcp-config with only /usr/bin/python3 -I <libexec>/doorman.py --mcp plus
--strict-mcp-config; --no-doorman, --user-mcp; result JSON doorman +
doorman_heartbeat); the lineage parent comes from the nearest registered
ancestor; send throttles per (target, sender) and says a dropped line was
not written; sender names 1-80 chars without brackets/control chars, the
structured from sanitised; name fragments need >= 4 characters; resume takes
a per-session lock; lineage is idempotent against the child's newest link;
one bad MCP message answers -32603, the status reports thread_alive.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs: the review v1 fold in README and CHANGELOG
All checks were successful
ci / check (pull_request) Successful in 38s
a3825ecd3a
Supported doorman deployments and the never-from-a-tool warning, the
/usr/bin/python3 -I registration recipe, echo 0 > <inbox>.offset, the owner
rule as coded, at-least-once wording, the child env scrub, start's doorman
injection, the per-(target, sender) throttle, no inbox GC, the idle-exit
ack gap with its v0.2 mitigations.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs: live re-runs B3, D2, E3 on the review fold
All checks were successful
ci / check (pull_request) Successful in 38s
c536951beb
B3 shows a steer that reached the door just after the turn's last tool round
being answered as the next turn; D2/E3 show the scrubbed child env
(entrypoint sdk-cli) and the injected doorman's heartbeat in start's result.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- R1 start: the result JSON carries doorman_delivered (lines the doorman
  acked into the session) and, when > 0, a note that claude -p reports the
  final turn only (the result may be the reply to a steer line; num_turns and
  total_cost_usd are not summed across turns); start --help says so.
- R2 the child keep-list is the exact provider/auth set the 2.1.308 binary
  knows (7 provider switches, 6 SKIP_*_AUTH, OAuth token/refresh token, the
  token and API-key file descriptors, plus CLAUDE_CONFIG_DIR); a descriptor
  named there is passed to the child; --systemd never passes tokens or
  descriptors.
- R3 --no-doorman and --user-mcp are mutually exclusive (usage error).
- R4 an over-long line is never acked refused twice for the same inode,
  offset and sha256 (crash between ack and offset write); the scan of an
  unterminated long line resumes where it stopped instead of re-reading it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Measured once (evidence r1-measure): --max-turns applies per turn,
--max-budget-usd per process, and total_cost_usd is the process total —
only result, subtype and num_turns describe the final turn. The note and
start --help say exactly that; the --allowed-tools example uses the 2.1.308
rule form.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs: the review v2 fold and the R1 measurement in README and CHANGELOG
All checks were successful
ci / check (pull_request) Successful in 39s
0cf2232ab6
Known gaps: steered start sessions report their final turn (per-turn
--max-turns, per-process budget, process-total cost); the exact 18-name
child keep-list; --systemd without credentials or companion variables;
refused-once over-long lines; the evidence row of the measurement.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent changed title from WIP: oo-claude v0.1.0 — the Claude-house transport (inbox doorman, send, resume, start, read) to oo-claude v0.1.0 — the Claude-house transport (inbox doorman, send, resume, start, read) 2026-10-30 21:59:27 +00:00
claude agent merged commit c47871508c into main 2026-10-30 21:59:27 +00:00
claude agent deleted branch oss-oo/oo-claude-v0.1.0 2026-10-30 21:59:27 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
OSS-OO/oo-claude!1
No description provided.