Repository navigation
Map: v1.0 is a bridge over every Session the user starts #67
Description
Destination
v1.0 is a bridge: through the user's own claude / codex commands it covers every main Session the user starts on this Mac under his main account — the default config directory (~/.claude, Codex's default); the second config directory this machine runs, ~/.claude-b, is out of scope (Simon, 2026-08-26) — and lets the user — from the Live Call, the Companion Channel (Telegram) and the menu-bar Control Panel — know each Session's state and progress, Relay words into it, and be called when it stops needing a decision or an approval. Launching and closing Sessions are not on this map. Reached when the real-environment automated acceptance (docs/acceptance-design.md, reshaped for this journey) runs green on Simon's machine against the installed bundle. Like #58 and #49, this map carries execution.
Reached 2026-08-30. The exit ran green — v1.0 exit: real-environment acceptance run of record at 093c7be (a-account environment), run 20260829T233015Z, 19/19 PASS on the installed bundle — and Simon declared the exit the same day. No ticket remains open on this map, and Not yet specified is empty, so nothing graduates: the way to the destination is walked, not merely clear.
Supersedes Map: v1.0 launch with legacy as the behaviour spec, whose destination (repair the launch-centred v1.0 to legacy parity) was redrawn on 2026-08-25 after Simon cut scope.
Notes
Why this map exists (2026-08-25, Simon's call, grilled in the session that closed #58). Simon reduced v1.0 to its core: the bridge over all of his running Sessions — voice-launched or hand-launched, he does not distinguish — with launch/close set aside for a later map ("打磨开/关 Session 的交互"). Fact found on the way: Survey the open-source Claude Code / Codex session monitors: how do they discover running Sessions, and can one be our base or our discovery route? changed the initial upstream premise. Claude Code 2.1.241 now offers launch-independent live enumeration through claude agents --json; Codex 0.149.1 offers two-way control for threads owned by its shared app-server. Neither offers universal control of every already-running, unprepared Session. Legacy's command takeover remains a fallback to prototype for unresolved Claude foreground control and pre-existing Codex Embedded Sessions, not the assumed architecture.
Superseding fact (2026-08-25, #70 follow-up, verified against the official docs): the take-over is not needed on the Claude lane either. Claude reach is the Session's built-in inbox socket (Claude Code ≥ 2.1.224, on by default; idle → new turn, busy → queued) plus a user-scope PermissionRequest hook, both hot-applied to already-running Sessions; the Engine learns each Session's socket from a user-scope SessionStart hook (CLAUDE_CODE_MESSAGING_SOCKET/_TOKEN are exported before it runs) — legacy's hook-client registration model without its wrapper. Codex reach is the shared daemon. The bridge owns no transport of its own; #71 proved the combined route end to end on 2.1.245, so ADR 0006 and ADR 0007 are superseded — 0007 by ADR 0011. Every caveat that ticket carried is answered on it: peer-message authority is why the approval half has to exist at all, bypass-mode holding and the undocumented wire frame are met by honest receipts and no version pin, and the install boundary is ADR 0011.
Proven on the way (2026-08-25, Prototype the Claude lane, live on Claude Code 2.1.245). Relay into a hand-started bare claude works with no wrapper, no plugin and no channel — the bridge's central claim holds. Three facts from that run bind the rest of this map. (1) Coverage is per CLAUDE_CONFIG_DIR. This machine runs ~/.claude and ~/.claude-b; they are disjoint universes — neither lists the other's Sessions and neither can authenticate to the other. The fact stands; the inference it first carried does not. Simon scoped v1.0 to the main account on 2026-08-26, so "every Session the user starts" means every Session in the default config directory, one universe, and the acceptance carries no multi-directory case. What the fact still binds: discovery and installation are both scoped to that one directory by construction, and a Session started under any other CLAUDE_CONFIG_DIR is simply not a Session v1.0 covers. (2) Delivery is never inferred from a socket write. DELIVERED has exactly two honest sources: the held → delivered receipt, and the target's transcript entry whose origin.from is our own socket address. Held messages also expire after five minutes by default, which the receipt seam must report rather than imply delivery. (3) The approval half has no socket route at all, so ADR 0006/0007 are superseded for Relay only. Simon's call, same day: build on the socket route knowingly, on undocumented surface, with no version pin — when upstream breaks it, fix it then; the safeguard is honest receipts, not a pin.
Proven on the way (2026-08-25, the approval half of #71, live on 2.1.245). A permission dialog in a bare, hand-started claude was routed to an external engine and answered by it, with no wrapper, no --plugin-dir and no channel — so the bridge's second claim holds too. Three facts bind the rest of this map. (1) The install mechanism is a fingerprinted block in <config dir>/settings.json, not a plugin (ADR 0011, superseding ADR 0007). A plugin is cold: installed while a Session runs it never reaches that Session, only ones started later. The settings block is hot in both directions on a running Session, and an install that asks the user to restart every open terminal is not a bridge over the Sessions they already started. Both mechanisms uninstall byte-for-byte clean; legacy's merge (legacy@1d32845:bridge/hookconfig.py:68-125) is what keeps every foreign hook, proven against the two already in that file. (2) The registration hook earns its place on one field. SessionStart carries transcript_path, which Claude Code's own registry does not and which cannot be derived without guessing; it is one of the two honest sources of DELIVERED. (3) Scope stops being structural. A user-scope hook fires for every Session in its config directory, so ADR 0007's "no phantom events" guarantee is now two soft lines — the hook prints nothing when no engine holds that Session, and the engine refuses what it does not hold. Cost to a Session the bridge does not hold: ~33 ms, no socket, no writes. Untested and a real exposure in ~/.claude: concurrent writers of settings.json.
What the product is made of — three layers, each with a proven source:
- Session-side brain — registration of Sessions, reading what a Session stopped on (question / permission / tool summary / progress) from its transcript or rollout, delivery with a receipt, naming, the child-process rule, Duty bookkeeping. Source: legacy (
bridge/hook.py,hookconfig.py,transcript.py,claude.py,codex.py,labels.py, parts ofdaemon.py). Simon: the effect is what he wants; the code is not. - User-side surfaces — realtime Live Call, Telegram, menu-bar Control Panel,
bridgectlcontrol plane, switches. Source: v1.0, proven by the Build the real-environment acceptance harness before any further repair #60 acceptance run. - Skeleton — seams, Bridge Core as the one decision-maker, adapter layering, no tmux binding, no big files. Source: v1.0 (ADR 0001) — the three things Simon rewrote for. Not up for revision.
Standing rules for this map:
- Legacy is the behaviour spec (ADR 0010,
CLAUDE.md) — unchanged. Every brief citesfile:line. - "Port" means: understand the legacy code, then rebuild it inside the skeleton in the simplest form that keeps the effect, optimising what is found on the way (Simon, 2026-08-25). Never a verbatim copy; never a fresh invention where legacy already proved the behaviour. Legacy's durable ledgers (
store.py), the ChatGPT-app driver (host.py), tmux launching (terminal.py) and its 33k-line test file are the parts to leave behind. - The code-level port table comes before any build ticket. Parity audit: legacy as the behaviour spec — the gap table that scopes the repair #61 was a behaviour-level parity audit under the old "Session = what we launched" definition; this map's audit maps each wanted behaviour's legacy implementation to its landing point in the skeleton with port / adapt / simplify. Build tickets graduate from that table.
- The real-environment acceptance is the exit — green on Simon's machine, reshaped so the harness starts Sessions through the ordinary installed
claude/codexpath in a pty, not throughbridgectl launch. - The gen-1 remnants stay on disk until the port table and its tickets are done (carried from Map: v1.0 launch with legacy as the behaviour spec #58; ticket Dispose of the gen-1 bridge remnants #54 moves under this map).
- The published tree carries only what a user needs (carried from Map: v1.0 launch with legacy as the behaviour spec #58): process documents are local-only, decisions live on tickets.
- Launch/close code is parked, not deleted, and the parking ticket records where it returns: a voice launch runs the same installed command path a hand launch does, so both walk one path.
Decisions from the grilling that are settled, not re-derived here: v1.0 is for GitHub power users, one-command install and configure (Q1); voice and text loops both required, text first as the subset (Q2); every main Session regardless of launcher, child processes (crews, reviewers) visible in the roster but never conversed with and their stops never announced (Q3, Q13); menu-bar roster is view-only in v1.0 (Q12); both lanes equal (Q6); exit is acceptance green, no usage period (Q15); notice semantics, progress content and naming follow legacy — the port table decides their shape (Q10, Q11, Q14); Simon's budget: whatever it takes (Q7).
Inherited, still binding: ADR 0001–0008, 0010; the Telegram-token decision (#53 → #55); #41's RETIRE of the Notice Relay; merged repairs on main. ADR 0009 (a launch carries its Opening Instruction) is superseded by ticket "Redraw the domain". Open bug tickets #38, #39, #47, #55 are re-scoped by the port table; #39's launch half is parked with the launcher.
Tests/lint only via the project venv: .venv/bin/python -m pytest -q, .venv/bin/ruff check src tests. Skills: mattpocock-skills:grilling + mattpocock-skills:domain-modeling for grilling tickets; mattpocock-skills:research for research tickets; mattpocock-skills:diagnosing-bugs for disputed reproductions. Simon's standing rules (global CLAUDE.md) override everyone. Report to Simon in Chinese; artifacts in English.
Decisions so far
-
Survey the open-source Claude Code / Codex session monitors: how do they discover running Sessions, and can one be our base or our discovery route?: use Claude's official JSON roster; treat Codex shared-daemon state and control as authoritative only for attached threads; keep process/rollout discovery as a provenance-marked, uncontrollable fallback; do not adopt a surveyed monitor as a parallel product base.
-
Port table: each wanted legacy behaviour → its landing point in the skeleton, marked port / adapt / simplify — signed 2026-08-25: 14 rows, ~3k legacy lines cited (not 22k); binding constraints — one Session-inspection type on the Agent seam, P3–P5 one pure ≤300-line module, P9 a policy edit only, shape before code. Graduated into build tickets Build 1/7 — Discovery, roster and reachability: lock the one Session-inspection type #74–Build 7/7 — Duty bookkeeping: reconcile current state when Duty turns on #80; Build 1/7 — Discovery, roster and reachability: lock the one Session-inspection type #74 is the architecture gate. Amended 2026-08-25 after a Codex consistency review of the tickets: P10 self-report fallback dropped, P14/P15 (Parity audit: legacy as the behaviour spec — the gap table that scopes the repair #61 R5, C2) added to Build 4/7 — Delivery receipts and settlement: inbox receipt, Codex verified, UNKNOWN never resends #77; the review's blockers were folded into Build 1/7 — Discovery, roster and reachability: lock the one Session-inspection type #74's locked types (async verbs, exact-
SessionTargetkey, revised CoreSessionrow, harmonisedWaitingFor) and into Redraw the domain: a Session is one the user starts, children are seen not spoken to #68/Park launch and close off the v1.0 path, and write down where they return #72/Reshape the real-environment acceptance for the bridge journey #73 (no wrapper anywhere). -
Redraw the domain: a Session is one the user starts, children are seen not spoken to — 2026-08-25: a Session is any interactive
claude/codexrun, all of them reachable; Child Process seen not spoken to; no Reach/Provenance vocabulary (a failed route is a delivery failure with a reason — Build 1/7 — Discovery, roster and reachability: lock the one Session-inspection type #74 simplifies its types); Reply Window gates Answer Relay only, Answer Relay carries words not authority; Stop Notice outlet-neutral (ported from legacy); Session Name added; Opening Instruction/Session Launcher and ADR 0008/0009 removed; every ADR pruned under writing-for-agents. -
Prototype the Codex lane: shared daemon discovery and reach — 2026-08-25: Codex 0.149.1 shared daemon is the only Relay/Approval route; a macOS login LaunchAgent starts it before ordinary TUIs; roster rows require an exact Session ID; Embedded/process fallback adds no Reach/Provenance state and returns existing
FAILEDbefore the wire; Relay delivery requires uniqueclientUserMessageIdreadback and Approval delivery requiresserverRequest/resolved. -
Prototype the Claude lane: official roster, built-in inbox socket and user-scope hooks, with no wrapper — 2026-08-25: the whole lane proven live on Claude Code 2.1.245 with no wrapper, no plugin and no channel, both halves separately and then in one combined run whose three records — the
SessionStartregistration, the transcript entry whoseorigin.fromis our own socket, and thePermissionRequest— name a singlesession_id. Relay rides the built-in inbox socket, knowingly on private surface with no version pin, andDELIVEREDis never inferred from a socket write. Approval rides two user-scope hooks installed as a fingerprinted block in<config dir>/settings.json— ADR 0011, superseding ADR 0007, because a plugin is cold and cannot reach a Session that is already running. Coverage is perCLAUDE_CONFIG_DIRat both ends: the Session registry lives inside the config directory, so discovery is scoped by it exactly as installation is. The one exposure carried into the build, untested: concurrent writers ofsettings.jsonin~/.claude. -
Park launch and close off the v1.0 path, and write down where they return — 2026-08-25:
maincomposes without the launcher (1122 → 964 tests, parked tests deleted not skipped);PROTOCOL_VERSION3 → 4 dropslaunch,closeand three error codes. Parked at the tagparked/launch-close(7e39e70), with the file list on the ticket — reference material, not a resumption point: Simon rejected an ADR and a maintained branch, so rule 7's "the architecture shows where they come back" is deliberately relaxed and nothing composed points at the tag. Most of it does not return — the pty, tmux binding and destination claiming existed to own a Session the bridge had started, and Survey the open-source Claude Code / Codex session monitors: how do they discover running Sessions, and can one be our base or our discovery route? #70's route means a voice launch runs the ordinary command. Handed to Build 1/7 — Discovery, roster and reachability: lock the one Session-inspection type #74: The immediate Reply Window report emitted at registration is dropped because Bridge Core does not yet hold the Session, so an already-idle Session can stay stuck CLOSED #27's Reply-Window guard (_establish_reply_window) left with the launch transaction and must be rebuilt with its test. -
Build the installation boundary: one owner for the Claude settings block and the Codex LaunchAgent — 2026-08-26, merged as
4f9a415: installation had no owner in this repo at all, found while opening Install the Codex shared daemon as a macOS login LaunchAgent #83, whose scope told its worker to hang the LaunchAgent on a boundary that did not exist. ADR 0012: installation isgpt_voicecoding.installation, four small modules and not a seam — two artifacts is not two implementations of one interface, and there is no wire, so no vocabulary module and no protocol;__main__names the items the way the composition root names adapters. First launch is the install (Simon: installing the app should install this, not a second command), so the menu-bar shell runsbridge-install reconcilebefore it spawns the engine — it cannot go through the engine, which refuses to start without aconfig.tomlthe user writes by hand. The shell is a trigger only; the merge and fingerprint stay in Python because a second copy in Swift is The control-plane socket path is built independently in Swift and Python with no test holding them together, though the repo already applies that guard to the interpreter path #47's shape. Uninstall is honoured, so the intent is recorded in this product's own directory. Every write is atomic, read back and reported, which is what makes ADR 0011's untested concurrency exposure something a run says out loud. Built on the way, because without it the Claude item is theatre: the engine publishes its approval address in a file (ADR 0011 decided it; nothing had built it), andlocations.py, a leaf that spells the on-disk layout once because adapters may not importcore. ADR 0007'shook_plugin.py/plugin.pyand their two proof scripts are deleted. Only the Claude item and only itsPermissionRequesthook: the Codex LaunchAgent is Install the Codex shared daemon as a macOS login LaunchAgent #83's, and theSessionStartregistration hook waits for Build 1/7 — Discovery, roster and reachability: lock the one Session-inspection type #74/Build 4/7 — Delivery receipts and settlement: inbox receipt, Codex verified, UNKNOWN never resends #77 — installing a hook whose module does not exist would break every Session in the config directory. Known limitation, accepted knowingly: the round trip is exact only for asettings.jsonalready written the way this renders. Reviewed on the Codex lane, both axes, six findings, all real, all closed. -
Reshape the real-environment acceptance for the bridge journey — 2026-08-26: the harness starts each lane's Session the way the user does (the ordinary binary in a pty, no wrapper) and ran end to end, every step with a verdict (
20260825T215229Z, bundle byte-identical todceb01c). Both lanes red onrosterfor one product reason — each agent's own record names the Session, the engine reports none — which is exactly Build 1/7 — Discovery, roster and reachability: lock the one Session-inspection type #74's red line;0bred on the unchanged upstreamsession.modelrefusal. Nine steps, spelled as Build 1/7 — Discovery, roster and reachability: lock the one Session-inspection type #74–Build 7/7 — Duty bookkeeping: reconcile current state when Duty turns on #80 cite them. Corrected after the merge (Assert the roster contract #68 left, not the one #73's brief asked for #88):rosterfirst asserted theProvenance/Reach(relay, approval)vocabulary this ticket's brief and Build 1/7 — Discovery, roster and reachability: lock the one Session-inspection type #74's body still name — Redraw the domain: a Session is one the user starts, children are seen not spoken to #68 deleted it, so that was a red line Build 1/7 — Discovery, roster and reachability: lock the one Session-inspection type #74 could only clear by building types Simon had already removed. It now claims only that the Session is listed and addressable; unreachability has no row of its own and surfaces inrelayas a graded failure carrying a reason (seams/delivery.py:47-49will not build a non-delivered receipt without one). Build 1/7 — Discovery, roster and reachability: lock the one Session-inspection type #74's Shape section still needs the simplification its own comment asks for. the trust gate, Build the real-environment acceptance harness before any further repair #60's5bdeadline mismatch and The approval listener unlinks its socket but never removes the directory it created, leaving one empty /tmp/vc-approvals-<pid> per engine lifetime #44 are recorded, not graded. Two measurements changed the design: the environment must be scrubbed (aclaudeinheritingCLAUDE_CODE_*is a child session, absent fromclaude agents --json), and neither agent has a record until it takes a turn —codexwrites its rollout at the first turn, so the Codex oracle is the process the harness started, matched on pid, which is Build 1/7 — Discovery, roster and reachability: lock the one Session-inspection type #74's own process-fallback evidence. A real agent turn is finally measured (12.0s;waitingis the permission state in the official roster). Gen-1's wrapper and its four user-scope hooks in~/.claude/settings.json— two of them ADR 0011's own slots — are recorded on the verdict rather than refused, and handed to Build 1/7 — Discovery, roster and reachability: lock the one Session-inspection type #74. -
Install the Codex shared daemon as a macOS login LaunchAgent — 2026-08-26, merged as
3b6c755: the second item on ADR 0012's boundary, and the one that made the boundary carry a process action. A plist launchd has not been told about does nothing until the next logout, which would leave a freshly-installed product's Codex half dead on the one day "first launch is the install" exists to cover — so install renders, writes atomically, thenlaunchctl bootstrap gui/<uid>, reading back withlaunchctl printrather than the exit status, because bootstrapping an already-loaded job fails and that is the state it was reaching for. Nothing ever runsbootout, and the asymmetry is now a written rule: this product starts a daemon the user's Sessions will join, and never stops one they are attached to — by uninstall time theircodexTUIs are thin clients of it. So uninstall removes the plist and lets the daemon live out the login session, and a render that changes under a loaded job is written but not reloaded. Which means file and running job can disagree for one session, sostatusasks launchd whichprogramit is actually holding: a boolean "is it loaded" would report a stale job ascurrent, the exact lie ADR 0012's read-back exists to prevent. Nothing rendered is hard-coded — user, home,CODEX_HOMEand version all resolved from the environment, the binary through thecurrentsymlink Codex's own updater moves (The installed Session Channel plugin runs a developer checkout's interpreter, so the v0 bundle is not self-contained #38); the subprocess timeout is derived from the shell's reconcile ceiling and a test reads that number out ofInstallation.swiftrather than trusting a comment (The control-plane socket path is built independently in Swift and Python with no test holding them together, though the repo already applies that guard to the interpreter path #47's shape). Proven live on the ticket's stated equivalent — a real logout/login would have ended every terminal on the machine — withCLAUDE_CONFIG_DIRon a temporary directory so only the Codex item touched it: clean install, byte-identical idempotent reconcile, uninstall leaving the daemon up;cliVersionandappServerVersionboth0.149.1; a hand-started no-flagcodexin the daemon roster as01a03b9c-…, matching its own status line, with no embedded app-server child. Cold-start evidence is incidental and recorded as such — the control socket appeared between 12:49 and 12:51 during a test-suite accident; reproducing it deliberately would have meant stopping a daemon live Sessions were on. The pre-wireFAILEDhalf of the failure gate stays Build 1/7 — Discovery, roster and reachability: lock the one Session-inspection type #74's (daemon absent → discovery degraded) and Build 4/7 — Delivery receipts and settlement: inbox receipt, Codex verified, UNKNOWN never resends #77's, per the advisor ruling on the ticket, rather than growing a second implementation of the daemon route here. Legacy: the job render and thegui/<uid>launchd handling adapted (scripts/launch-agent.py:53-70,install.sh:174-195),KeepAliveand the wholestop_launch_agentpath dropped, the shared daemon itself dropped from porting because gen 1 had none. One incident worth keeping: two drafts reached the reallaunchctlfrom the test suite, the second installing the real login job and starting the real daemon —tests/conftest.pynow takes the real runner away from the whole suite, and any future boundary item that runs a subprocess belongs behind the same fixture. Reviewed on the Codex lane, both axes, three rounds; every substantive finding closed, the surviving scope objection ruled on by the advisor and recorded on the ticket rather than in code. -
Build 1/7 — Discovery, roster and reachability: lock the one Session-inspection type — 2026-08-26, merged as
7b0ced3: the architecture gate, androstergreen on both lanes (run20260826T013853Z, bundle byte-identical toa9576c0) wheremainwas red on both for one product reason — each agent's own record names the Session and the engine reported none; the other eight steps have a real verdict for the first time, and each red is another group's. Locked on the Agent seam:SessionInspection,WaitingFor,Progress,ChildClassification,LaneDiscovery,LaneUnavailable,derive_reply_window, with asyncdiscover()/inspect().Reach,ReachGradeandProvenanceare not built — Redraw the domain: a Session is one the user starts, children are seen not spoken to #68 deleted the vocabulary and this ticket's own Shape section had not caught up; a route that fails is aDeliveryReceiptcarrying a reason. Reply Window isstate == IDLE∧child == main, not{idle, waiting}:AwaitingApprovalalready locks that a dialog blocks every Relay, nobody has measured whether a Session waiting on a question consumes an inbox message, and a held Relay is recoverable where one delivered into a dialog is not.LaneDiscoverycarries three fields, not two — "no rows plus an error" cannot tell a lane that could not look from one that looked and found nothing, and only the second may end rows;LaneUnavailableis the same rule forinspect, which has no error field and so raises rather than answeringENDED.SessionTarget.session_idmay beNoneand the pid is a Codex row's identity — a TUI has no thread id until its first turn (Reshape the real-environment acceptance for the bridge journey #73) and keeps its pid across/new, soNonenever overwrites a known id and a different known id does;observetherefore returns what really ended, because a re-keyed row is not a dead one. TheSessionStarthook now reachesregister_sessionviaCLAUDE_PID— measured, not assumed — closing a gap where that method had no production caller on either lane since Park launch and close off the v1.0 path, and write down where they return #72 parked the launcher, which left_channelspermanently empty and every Claude Relay and approval refused; reports are keyed by exactSessionTargetbecause--resumeforks two processes under one id with two sockets. ADR 0011's untested exposure is now tested on the real~/.claude/settings.json: both foreign hooks in the sameSessionStartslot survived untouched, ten other slots and every top-level key unchanged — Build the installation boundary: one owner for the Claude settings block and the Codex LaunchAgent #86 had proven this only in a sandbox and only across different slots.restore()and the Session half ofcore/state.pyare deleted with their tests;PersistedState.VERSIONstays 1, because bumping it would have refused the switch state — Duty included — of every installed engine over a key no longer read. Reviewed on the codex lane, both axes: six real findings fixed, one rejected on measurement.stop noticeis Build 4/7 — Delivery receipts and settlement: inbox receipt, Codex verified, UNKNOWN never resends #77's, with evidence rather than a conclusion: the step opened its window 9 ms after the turn ended and closed it 292 ms later, against a 5 s discovery cadence — and the same transition did produce a Stop Notice 35 s on, correctly naming the Session, so the question is cadence, window definition or attribution, not whether notices are produced. -
v1.0 exit: real-environment acceptance run of record at
093c7be(a-account environment) — 2026-08-30, run20260829T233015Z, result PASS, 19/19: both lanes 9/9 and the0brealtime probe, against/Applications/GPT-VoiceCoding.apppreflight-certified byte-identical to093c7be,git statusclean before and after, nothing arranged to make a step pass. This is the map's exit condition, and Simon declared the exit on it the same day. Attempt 3: attempt 1 (20260828T074856Z) died on the codex lane and produced The engine refuses its own codex app-server socket at mode 0755: codex binds under the inherited umask and chmods afterwards, and the engine verifies at first sight #136; attempt 2 (20260828T115319Z) was red on codexapprovalandswitchesand skipped the probe, and all three causes were harness defects (Acceptance harness: reject run roots that bypass Codex permission triggers #140, Acceptance (codex lane): switches step FAIL at 0266039 — turn ends with roster waiting_for=none #141, Acceptance: realtime probe SKIPPED when run from a worktree — default legacy path not found #142) — this run was taken under the preconditions their fixes require (CLAUDE_CONFIG_DIRunset, primary checkout not a worktree, the documented run root, no/tmp), so the green is the product's. Three of this map's fog patches were answered by measurement rather than by a decision:0bis green at1837 remote audio frames in 30swhere 2026-08-26 re-confirmed0(an upstream-side change — nothing in093c7betouches it); a Codex approval can be raised in a trusted workspace, measured atsandbox 'workspace-write',approval_policy 'on-request',approvals_reviewer 'user'out of codex's ownturn_context, so Build 4/7 — Delivery receipts and settlement: inbox receipt, Codex verified, UNKNOWN never resends #77's step is green and observable, neither red nor unobservable; and a Claude subagent is visible from outside — listed in the roster under its parent, no notice naming it in 120s, Relay refused as a Child Process, the same shape on both lanes.gen1_hooks_in_user_settingsis now empty — the four gen-1 user-scope hooks Reshape the real-environment acceptance for the bridge journey #73 recorded rather than refused are gone. On relaunch both the shell and the engine came up, so attempt 2's unexplained engine-less relaunch did not recur on a bundle carrying Shell: engine blocked by preflight does not start on its own after the credentials file is fixed by hand #139.bridge-install statusreports the Codex LaunchAgentstale — applies at the next login, which is Install the Codex shared daemon as a macOS login LaunchAgent #83's written rule (neverbootouta daemon the user's Sessions are on), not a defect.
Not yet specified
Out of scope
-
Live Call auto-end on long silence (Parity audit: legacy as the behaviour spec — the gap table that scopes the repair #61 row V3) — was fog, ruled out of scope on 2026-08-30 when the exit was declared: Simon called it required, but the acceptance ran 19/19 green without it, so it is beyond this destination rather than unfinished inside it. Mechanism still undecided. Belongs to the next map, with launch/close interaction polish.
-
The Control Panel roster's Swift rendering — was fog, ruled out of scope on 2026-08-30, same reason. What it renders is settled (Build 1/7 — Discovery, roster and reachability: lock the one Session-inspection type #74's locked
SessionInspection: name, state, last activity, children) and the roster is view-only in v1.0 (Q12); only the Swift rendering was ever undecided, and the exit did not wait on it. Note the acceptance covers the shell byswift testonly, already out of scope below. -
Whether The installed Session Channel plugin runs a developer checkout's interpreter, so the v0 bundle is not self-contained #38 (bundle self-containment) and The control-plane socket path is built independently in Swift and Python with no test holding them together, though the repo already applies that guard to the interpreter path #47 (socket-path guard) close on verification alone — was fog, ruled out of scope on 2026-08-30. Both tickets stay open on the tracker; the exit run neither verified nor needed them, and Install the Codex shared daemon as a macOS login LaunchAgent #83 already resolved codex's binary through the
currentsymlink The installed Session Channel plugin runs a developer checkout's interpreter, so the v0 bundle is not self-contained #38 is about. They are ordinary open bugs now, not steps on a route. -
Sessions started under a non-default
CLAUDE_CONFIG_DIR— on this machine the~/.claude-buniverse theclaudeb/interviewb/motorhomebaliases open (~/.zshrc:218-229). Ruled out by Simon on 2026-08-26: v1.0 bridges the main account only. The Prototype the Claude lane: official roster, built-in inbox socket and user-scope hooks, with no wrapper #71 fact that the two directories are disjoint is what makes this a clean cut rather than a gap — coverage is per config directory, so scoping the product to one directory scopes it to one whole universe. Returns only if the destination is redrawn. -
Launching and closing Sessions, including their interaction polish (menu-bar pre-selection then voice, full-voice flow) — the next map. Parked, not deleted (rule 7).
-
Reverting to legacy as the code base — rejected again, sharper: the parts to leave behind outweigh the parts to keep, and the skeleton is what Simon rewrote for.
-
Sessions on remote hosts or headless /
-pruns started by other tools — not Sessions in v1.0. -
Real microphone/audio proof; CPU/memory/latency thresholds (Parity audit: legacy as the behaviour spec — the gap table that scopes the repair #61).
-
Shell (menu-bar) end-to-end coverage —
swift testonly. -
Post-launch polish carried from Map: repair phase to v1.0 minimal launch #49/Map: v1.0 launch with legacy as the behaviour spec #58: A restore that ends live Sessions does not write the result down #31, The drain-before-exit ordering guarantee is not enforced end to end #33, The supervision suite fails under load because every spawn now starts a login shell #36, Deferred: notify_when_idle as a push route for the Reply Window #17, Deferred: restore and the afterlife of a launch directory #29, Voice-first launch into a workspace Codex has never seen — the trust gate needs a sanctioned route #18.
Activity
28 remaining items
197b1d8#135Simon-AI-coding commentedon Aug 28, 2026
Exit attempt 1 (advisor, 2026-08-28): #135 run
20260828T074856Zat197b1d8— claude lane 9/9 + realtime probe green; codex lane refused at engine start. Cause measured: the engine's owncodex app-serversocket is born 0755 under the inherited umask and chmodded to 0600 by codex a moment later; the engine verifies at first sight and_spawnsets no umask — the bind→chmod window #116 closed for our own listeners, still open for the one we do not bind. Filed #136 (ready-for-agent,umask=0o077on the spawn, guard unchanged; legacy had neither umask nor guard — new, not ported). #135 re-runs in full after #136 lands. Wording defect recorded, not scheduled: the codex degradation line "a Session started by this CLI will not join that daemon" appeared on the green run of record too, and #105 measured the opposite. Also being investigated: the installed app on Simon's machine currently runs no engine (shell up, engine log last written 2026-08-24).Simon-AI-coding commentedon Aug 28, 2026
Found on the way (advisor, 2026-08-28): the installed product on Simon's machine has not run an engine since 2026-08-24. Two causes, in order, both measured: (1)
engine/config.tomlstill carried[adapters.settings.session_launcher], which #72 (1a62bcc) removed from the schema — the engine refused it at every launch, the shell burned its five attempts in 18 s and showed a bare red "stopped" (removed by hand today, backup beside the file); (2) with that gone, the engine refuses on$GPTVOICECODING_TELEGRAM_TOKENabsent from its environment — #55's defect verbatim: the token lived inlaunchctl setenv, which a reboot cleared, and re-setting it there no longer reaches an app launched through LaunchServices (variable set, absent from the shell's environment). Simon's calls today: #55 into v1.0 (Routing added, ready-for-agent); #137 filed and into v1.0 — the panel shows the engine's own refusal sentence instead of a failure count, legacy's fail-loud (terminal.py:893-915) ported, plus the config-migration line #72 never wrote. Two facts recorded, not scheduled:installation statusrun from a dev venv reportsclaude-hooks: stalebecause the render embedssys.executable— the bundle's own interpreter reportscurrent; and the sevenchannel.mjsprocesses on the machine are the hands-free-channel plugin's per-session children, not gen-1 remnants. Chain to exit: #136 → #137 → #55 → #135 full re-run.093c7be(a-account environment) #143Simon-AI-coding commentedon Aug 30, 2026
Exit declared — 2026-08-30, Simon. The map's destination is reached.
The destination was one sentence with one test in it: v1.0 is a bridge over every main Session the user starts under his main account, reached when the real-environment automated acceptance runs green on Simon's machine against the installed bundle. On 2026-08-30 it did — v1.0 exit: real-environment acceptance run of record at
093c7be(a-account environment), run20260829T233015Z, 19/19 PASS, both lanes 9/9 plus the0brealtime probe, against/Applications/GPT-VoiceCoding.appcertified byte-identical to093c7be,git statusclean before and after, nothing arranged by hand to make a step pass.What the map turned out to be about. It opened on a premise that did not survive contact: legacy's command takeover as the assumed architecture. Survey the open-source Claude Code / Codex session monitors and then Prototype the Claude lane replaced it: the bridge owns no transport of its own, reaches a hand-started bare
claudewith no wrapper, no plugin and no channel, and installs as a hot fingerprinted block insettings.jsonrather than a cold plugin — ADR 0011, superseding 0006/0007 — because a bridge that asks the user to restart every open terminal is not a bridge over the Sessions they already started. That single fact is why the product exists in the shape it does.Two scoping acts closed the frontier rather than advancing it:
~/.claude-bruled out on 2026-08-26 (coverage is per config directory, so scoping to one directory scopes to one whole universe — a clean cut, not a gap), and launch/close parked atparked/launch-closeas reference material, not a resumption point.Three fog patches were answered by measurement in the exit run itself, not by a decision — all three indexed in Decisions so far and detailed on #143:
0bis green where 2026-08-26 re-confirmed it dead (an upstream-side change; nothing in093c7betouches it); a Codex approval can be raised in a trusted workspace, measured out of codex's ownturn_context; and a Claude subagent is visible from outside, listed under its parent and refused for Relay as a Child Process.Three were not, and they are recorded as out of scope rather than as debt on a closed map — Live Call auto-end on long silence, the Control Panel roster's Swift rendering, and whether #38/#47 close on verification alone. The acceptance ran green without all three, and that is precisely what puts them past this destination. They do not graduate; the frontier stops at the destination. #38 and #47 stay open on the tracker as ordinary bugs.
Not yet specified is now empty and every child ticket is closed. Closing the map. The next map is the parked one: launching and closing Sessions, and the interaction polish around them — a fresh effort, not a resumption of this one.
Delete Bridge Core's delivered-wait ledger (#161)
ADR 0013 §3 stands, and the Voice says what a relayed answer is not
A live codex terminal no row answers to is named in the log, and the …
One codex on the machine: the shared app-server is the user's own (#272)