@junghanacs/entwurf 0.20.1 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/AGENTS.md +85 -190
  2. package/BASELINE.md +4 -3
  3. package/CHANGELOG.md +195 -14
  4. package/CONTRIBUTING.md +1 -1
  5. package/DELIVERY.md +202 -60
  6. package/README.md +49 -22
  7. package/VERIFY.md +37 -7
  8. package/docs/acp-backend-rail.md +0 -1
  9. package/docs/external-mcp-host.md +54 -33
  10. package/docs/setup-clean-host.md +110 -17
  11. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +101 -109
  12. package/mcp/entwurf-bridge/dist/pi-extensions/lib/codex-fresh-preflight.js +326 -0
  13. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-fact-provider.js +22 -2
  14. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-peers-render.js +3 -1
  15. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-contract.js +6 -7
  16. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-native-push.js +30 -17
  17. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-production.js +7 -1
  18. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-surface.js +7 -4
  19. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-mailbox-body.js +20 -11
  20. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-sender-identity.js +227 -1
  21. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +50 -15
  22. package/mcp/entwurf-bridge/dist/pi-extensions/lib/native-push/adapter.js +10 -7
  23. package/mcp/entwurf-bridge/dist/pi-extensions/lib/native-push/codex-ws-client.js +403 -0
  24. package/mcp/entwurf-bridge/dist/pi-extensions/lib/native-push/register.js +4 -4
  25. package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-hook-codex.js +325 -0
  26. package/mcp/entwurf-bridge/dist/scripts/meta-bridge-fresh-cut.js +6 -1
  27. package/mcp/entwurf-bridge/src/index.ts +115 -111
  28. package/mcp/entwurf-bridge/tsconfig.build.json +1 -0
  29. package/package.json +13 -4
  30. package/pi-extensions/entwurf-control.ts +71 -19
  31. package/pi-extensions/lib/codex-fresh-preflight.ts +363 -0
  32. package/pi-extensions/lib/compaction-send-guard.ts +80 -0
  33. package/pi-extensions/lib/entwurf-fact-provider.ts +29 -3
  34. package/pi-extensions/lib/entwurf-peers-render.ts +4 -1
  35. package/pi-extensions/lib/entwurf-v2-contract.ts +6 -7
  36. package/pi-extensions/lib/entwurf-v2-native-push.ts +35 -18
  37. package/pi-extensions/lib/entwurf-v2-production.ts +10 -3
  38. package/pi-extensions/lib/entwurf-v2-surface.ts +7 -4
  39. package/pi-extensions/lib/meta-mailbox-body.ts +22 -13
  40. package/pi-extensions/lib/meta-sender-identity.ts +305 -0
  41. package/pi-extensions/lib/mux-fresh-call.ts +64 -19
  42. package/pi-extensions/lib/native-push/adapter.ts +21 -24
  43. package/pi-extensions/lib/native-push/codex-ws-client.ts +506 -0
  44. package/pi-extensions/lib/native-push/register.ts +7 -9
  45. package/pi-extensions/meta-bridge-hook-codex.ts +371 -0
  46. package/run.sh +218 -28
  47. package/scripts/check-agy-sender-identity.ts +1 -1
  48. package/scripts/check-codex-birth-hook.ts +264 -0
  49. package/scripts/check-codex-bridge-identity.ts +179 -0
  50. package/scripts/check-codex-native-push.ts +386 -0
  51. package/scripts/check-codex-sender-identity.ts +495 -0
  52. package/scripts/check-compaction-send-guard.ts +130 -0
  53. package/scripts/check-copilot-receive-arm.ts +4 -1
  54. package/scripts/check-entwurf-fact-provider.ts +38 -0
  55. package/scripts/check-entwurf-peers-surface.ts +13 -1
  56. package/scripts/check-entwurf-self-address.ts +15 -16
  57. package/scripts/check-entwurf-v2-contract.ts +4 -3
  58. package/scripts/check-entwurf-v2-decider.ts +7 -5
  59. package/scripts/check-entwurf-v2-native-push.ts +35 -7
  60. package/scripts/check-entwurf-v2-production.ts +203 -11
  61. package/scripts/check-entwurf-v2-runner.ts +1 -1
  62. package/scripts/check-entwurf-v2-surface.ts +1 -1
  63. package/scripts/check-gate-qualification.ts +6 -3
  64. package/scripts/check-harness-admission-parity.ts +0 -1
  65. package/scripts/check-mux-launch-tmux.ts +47 -2
  66. package/scripts/check-native-push-adapter.ts +20 -16
  67. package/scripts/check-native-push-register.ts +5 -1
  68. package/scripts/check-release-gate-outcomes.ts +47 -1
  69. package/scripts/check-setup-qualification.sh +3 -1
  70. package/scripts/codex-birth-doctor.sh +276 -0
  71. package/scripts/codex-birth-install.sh +414 -0
  72. package/scripts/codex-birth-uninstall.sh +170 -0
  73. package/scripts/codex-mcp-config.py +435 -0
  74. package/scripts/codex-statusline-config.py +434 -0
  75. package/scripts/codex_toml_io.py +532 -0
  76. package/scripts/lib/codex-fresh-live-protocol.ts +113 -0
  77. package/scripts/lib/codex-fresh-source-receipts.ts +399 -0
  78. package/scripts/lib/launch-receipt-windows.ts +46 -0
  79. package/scripts/lib/tmux-coordinate-row.ts +58 -0
  80. package/scripts/meta-bridge-fresh-cut.ts +6 -1
  81. package/scripts/mutants/codex-native.json +838 -0
  82. package/scripts/mutants/compaction-send-guard.json +103 -0
  83. package/scripts/mutants/entwurf-peers.json +19 -0
  84. package/scripts/mutants/mux-fresh-call.json +19 -8
  85. package/scripts/mutants/omp-fresh.json +6 -4
  86. package/scripts/mutants/release-gate.json +13 -0
  87. package/scripts/mutants/v2-surface.json +53 -1
  88. package/scripts/raw-async-delivery/README.md +2 -1
  89. package/scripts/raw-codex-measure/README.md +114 -46
  90. package/scripts/smoke-agy-native-push-live.ts +3 -1
  91. package/scripts/smoke-codex-birth.sh +347 -0
  92. package/scripts/smoke-codex-config-state.sh +511 -0
  93. package/scripts/smoke-codex-fresh-live.ts +1186 -0
  94. package/scripts/smoke-codex-native-push-live.ts +75 -0
  95. package/scripts/smoke-setup-verdict.sh +123 -10
  96. package/scripts/tsconfig.json +1 -0
package/CHANGELOG.md CHANGED
@@ -4,6 +4,189 @@ All notable changes to this project will be documented here. Format follows [Kee
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 0.21.0 - 2026-09-13
8
+
9
+ ### Added
10
+
11
+ - **This is a minor release because Codex support adds a new visible citizen axis, not merely a
12
+ patch to an existing rail.** The operator-owned exact `codex` tmux home gives the app-server and
13
+ supported Codex TUIs a stable visible place without making Entwurf their lifecycle manager. UX
14
+ symmetry means each backend remains a sibling with its own transcript, auth, native tools, and
15
+ visible seat—not that unlike transports are relabeled as identical. The release also carries the
16
+ independent #111 and #112 bug repairs.
17
+ - **OpenAI Codex CLI becomes an app-server-backed native-push citizen (#95).**
18
+ A user-scope `SessionStart` hook, declared in the operator's own `$CODEX_HOME/hooks.json`,
19
+ mints the V3 record on the first turn and sets the visible thread title. It needs no root:
20
+ the installer refuses to run as uid 0, and the declaration is trust-gated rather than
21
+ prompt-free — the operator answers the vendor's prompt with "Trust all" ONCE in their own
22
+ visible Codex, and the vendor records that decision itself. Entwurf never computes, writes,
23
+ pre-seeds or reads a `trusted_hash`, and never touches `config.toml`. Because the vendor
24
+ hashes the declaration identity (event, matcher, handler path) and not the launcher's
25
+ contents, the payload closure can be upgraded on later releases without asking again. The bridge strictly joins each request's Codex metadata to
26
+ that record, so multiple visible TUIs may share one app-server without sharing a
27
+ process marker. The purpose is to preserve Codex's native tools, delegation, and work
28
+ context as a citizen — not to add an ACP Codex backend or duplicate GPT access.
29
+ - **Codex gets three independently owned install/doctor/inverse atoms.**
30
+ `install-codex-birth` owns only the fixed `$CODEX_HOME/hooks.json` declaration plus its
31
+ launcher/payload closure under `${XDG_DATA_HOME:-~/.local/share}/entwurf/codex-birth`, with a
32
+ digest inventory that licenses an exact inverse and refuses drifted bytes;
33
+ `install-codex-mcp` owns only `[mcp_servers.entwurf-bridge]`; and
34
+ `install-codex-statusline` owns only `thread-title`. The MCP atom also owns the exact
35
+ `env_vars` names that forward `CODEX_HOME`, Entwurf garden/control roots, and the
36
+ operator-owned app-server's `TMUX`/`TMUX_PANE`. `setup` never escalates or starts it.
37
+ - **`entwurf_fresh_call` accepts `backend: "codex"` on both public tool surfaces and the
38
+ operator skill.** It requires an explicit model and uses the measured
39
+ `codex --remote unix://<default-socket> --model … --dangerously-bypass-approvals-and-sandbox`
40
+ shape. The birth unit, MCP/env boundary, visible title, and the operator-owned app-server
41
+ socket are checked before tmux mutation. With placement omitted, Codex resolves the exact
42
+ existing `codex` tmux home; a missing home rejects without creating one. Explicit placement
43
+ remains an expert override. This does not infer an attached TUI seat from request identity.
44
+ - **Omitted placement becomes a backend-selected policy, and Codex is its first implementation.**
45
+ Every backend could already be sent to an operator-owned existing seat through explicit
46
+ `placement.tmuxSession`; that is unchanged, and named `claude`/`pi` seats stay available that
47
+ way without becoming omitted-placement defaults. What is new is that omitting placement no
48
+ longer means one thing for every backend: `pi`, `claude-code`, `copilot` and `omp` still open
49
+ in the caller's seat, while `codex` resolves its exact operator-owned home. tmux remains the
50
+ visible launch seat, never an address — the garden id is the address and arrives in the
51
+ callback envelope. Sibling symmetry is transcript, auth, native tools and visibility; it is
52
+ not a claim that the transports are the same.
53
+ - **Why a client-side home rather than a vendor seat carrier.** Upstream
54
+ [openai/codex#44774](https://github.com/openai/codex/issues/44774) records the maintainer's
55
+ preference for client-side terminal/window management, citing multi-client steer semantics and
56
+ network propagation. Rather than wait for a request→pane carrier or guess one, 0.21.0 takes the
57
+ client-side route the exact `rust-v0.153.4` source supports: an operator-owned exact home plus
58
+ explicit placement, with the wider attached-TUI topology left unsupported and unclaimed.
59
+
60
+ ### Changed
61
+
62
+ - **The native-push rail is adapter-specific instead of Antigravity-shaped.** Codex liveness
63
+ is `thread/loaded/list` over the app-server WebSocket UDS; delivery is the measured one-shot
64
+ `codex queue --remote … --thread … --message …`. It has no mailbox, receiver marker, or
65
+ resume surface. Unlike Antigravity, Codex delivery is never retried because vendor acceptance
66
+ may precede the CLI receipt; replay would duplicate the user's message.
67
+ - **Codex remains outside ACP and Entwurf remains outside Codex lifecycle ownership.**
68
+ Entwurf does not install Codex, supply auth, expose a generic app-server API/manager, or
69
+ start/supervise/restart the app-server or create its tmux home. The supported topology is
70
+ one operator-owned existing session named `codex` containing the app-server and supported
71
+ Codex TUIs. N arbitrary attached TUIs across sessions still have no request→TUI seat join;
72
+ that wider topology is unsupported and unclaimed rather than a blocker to the explicit home.
73
+ - **`entwurf_peers` bounds expensive historical observation to its 32 rendered rows (#112).**
74
+ Full-store parsing, duplicate/conflict authority, socket liveness, diagnostics, and all 1,012
75
+ measured payload rows remain intact; older receiver/transcript cells say `unobserved` rather
76
+ than fabricating facts. On the 1,012-record fixture, repeated warm composition fell from about
77
+ 3.2s to 70–103ms. Generic fact-provider callers remain unbounded unless they explicitly opt in.
78
+ - **Version-coherence gates no longer require concise `AGENTS.md` to duplicate package pins.**
79
+ The owning runtime/package documents remain checked; removing redundant pin prose from the
80
+ maintainer guide no longer makes an otherwise coherent floor fail.
81
+
82
+ ### Upgrade note
83
+
84
+ Codex native delivery requires three user-scope atoms and one operator trust answer. No step
85
+ needs root:
86
+
87
+ ```bash
88
+ entwurf install-codex-birth
89
+ entwurf install-codex-mcp
90
+ entwurf install-codex-statusline
91
+
92
+ # Then, ONCE, in a visible plain Codex: answer the hook prompt with "Trust all and continue"
93
+ # and send one first turn. Until the vendor records that receipt the hook is declared and
94
+ # never runs, and `doctor-codex-birth` / `setup` stay non-green and say so.
95
+
96
+ # First create/open the operator-owned tmux session named exactly `codex`.
97
+ # From a pane in that session:
98
+ CODEX_HOME="${CODEX_HOME:-$HOME/.codex}"
99
+ mkdir -p "$CODEX_HOME/app-server-control"
100
+ codex app-server --listen "unix://$CODEX_HOME/app-server-control/app-server-control.sock"
101
+ # Seat supported Codex TUIs in this same `codex` session.
102
+ ```
103
+
104
+ Then restart/open Codex and send its first turn. `entwurf setup <project>` never runs sudo and
105
+ never starts the app-server.
106
+
107
+ ### Verification
108
+
109
+ - The GPT-6 review reopened five surfaces in the candidate: terminal parser, explicit
110
+ env-name boundary, setup acceptance, fresh preflight safety, and clause-7 composition.
111
+ The prior “source-complete; only host ownership remains” wording is withdrawn.
112
+ - The 2026-09-11 Codex CLI 0.153.4 run remains pre-amendment evidence: native-push and a
113
+ public MCP call woke one visible loaded thread; authenticated request identity resolved
114
+ `agentId=meta-session/codex`, `replyable=true`; and a source hook minted a record in an
115
+ isolated store. It does not qualify the amended candidate.
116
+ - The 2026-09-12 Codex 0.153.4 explicit-home LIVE passed 57 assertions. A real initial Pi
117
+ ran at `$150/@397`; the operator app-server remained at `$158/@390/%390`; omitted-placement
118
+ Codex opened at `$158/@398`; and Codex opened outbound Pi at `$158/@399`. Exact callbacks
119
+ and addressed delivery succeeded both ways. `DELIVERY.md` carries the garden ids, thread,
120
+ nonces, preserved log path, and SHA-256.
121
+ - Two red attempts repaired the acceptance itself. “Wait” had let Codex call one-hour
122
+ `wait_agent`; the task now ends its turn and forbids wait tools. A model also reformatted
123
+ a copied LAUNCH receipt; the gate compares source-owned tool results rather than treating that prose
124
+ as raw evidence. Failure cleanup interrupts only the exact smoke-owned active Codex turn and removes
125
+ only source-receipt-named windows.
126
+ - The second full prepare P5 remained honestly red at **MUST 23 PASS / 1 FAIL / 0 SKIP**. The initial
127
+ Pi's source transcript proved Codex launch, exact callback, native-push delivery, outbound Pi launch
128
+ and callback, and Codex's final mailbox enqueue; it also showed the missing observation's direct
129
+ cause: the model shortened expected launch-report token `…1G5XEKQH` to `…1G5XEKQ`. Enqueue did not
130
+ prove the fixture read, and cleanup had removed the mailbox needed to distinguish every downstream
131
+ observer hypothesis. The amendment removes intermediate model coordinate reports entirely: Pi JSONL
132
+ call/result rows are joined by call id, exact tool name/arguments, non-error result, and uniqueness;
133
+ Codex uses the corresponding structured app-server MCP items; raw callback events supply garden ids.
134
+ Mailbox prose can never authorize a window kill. Every drain now persists filename, byte length,
135
+ SHA-256, observed token candidates, phase-specific selection/exclusion reason and a 0600 body artifact
136
+ immediately, plus resolved-root/run/source-path manifest and a mandatory fixture/record/transcript/thread
137
+ snapshot before destructive cleanup on every outcome. That snapshot uses the record owner's `.meta.json`
138
+ filename, records missing expected sources, and still copies known raw files when optional identity recovery
139
+ throws; final acceptance also requires known `completed` Codex turn/items state and logs payload validation
140
+ separately from token/sender candidate selection. The preserved failed-rollout extraction
141
+ (`c3a28938…`) confirms raw outbound launch → callback → final enqueue but still not fixture read; its tracked
142
+ positive fixture is explicitly labelled a vendor-source-derived v2 projection, not a captured `thread/read`
143
+ response. A second defect the amendment itself introduced was then measured and closed: the smoke read the
144
+ initial Pi's `transcriptPath` once at callback correlation, and a record that gained that path 34s later
145
+ left every subsequent source observation permanently empty — one 300s red whose chain had in fact run to
146
+ completion, proven by the Codex thread's own completed final `entwurf_v2` result. The resolution is a pure
147
+ leaf that re-reads the record while its cached path is empty (an unreadable record stays fail-loud; only
148
+ "no path yet" is pending), its own `CODEX-LIVE-SOURCE-PATH-RE-RESOLVED` claim and mutant, and an explicit
149
+ `no unverified child window killed` line so a run that never obtains source evidence names its
150
+ unrecoverable windows instead of leaking them. Declared inventory is now 475 mutants across 43 lanes.
151
+ The amended standalone LIVE then passed: **48 assertions, exit 0**, final source audit `initial-pi=3/3`
152
+ and `codex=3/3` completed exact, all three opened windows reclaimed by their own receipts, artifact
153
+ `.probe-artifacts/codex-fresh-live-fZccoK/`. Assertion count is not comparable with the pre-amendment 57:
154
+ that contract counted model coordinate reports this one removes.
155
+ - **Release acceptance for this candidate: `check:full` exit 0 in 502s, then the LIVE release gate
156
+ `--cut` at MUST PASS=24 FAIL=0 SKIP=0, BEHAVIOR PASS=1 FAIL=0 SKIP=0, `cut: OK`.** Its
157
+ `check-gate-qualification` MUST step killed 475/475 across 43 lanes with the origin HEAD and
158
+ work-surface hash identical before and after. The aggregate's own Codex leg repeated the
159
+ 48-assertion source-owned acceptance (artifact `.probe-artifacts/codex-fresh-live-2oId4C/`), and the
160
+ mux lifecycle MUST passed in the same run. Scratch log sha256 `942d5fa0…`.
161
+ - **Observation, not a repaired defect.** The first acceptance attempt at the identical source
162
+ fingerprint failed two model-in-loop MUST steps: a callback nonce arrived one character short
163
+ (`…c70cfb` for `…c70cfb2`) and an addressed instruction carried the wrong payload. Both oracles
164
+ refused fail-closed and named their cause, and the joined source receipts proved delivery and rails
165
+ were correct, so nothing in the product was changed. The same candidate, model, and environment then
166
+ passed. That leaves an intermittent exact-byte transfer failure in the model lane as a recorded
167
+ observation; the retry does not establish a cause for it.
168
+ - The first 460-mutant qualification honestly stopped at 454/460. One wait-task mutant was
169
+ stale after the prompt repair; the birth safe-umask mutant was killed earlier by the new
170
+ package-root mode doctor but carried the wrong assertion label; and four setup claims shared
171
+ one control-red because `smoke-setup-verdict` ran real pnpm inside the qualifier's
172
+ node_modules-symlink snapshot. The manifest was re-pinned, safe-umask attribution now has one
173
+ QK helper at both valid failure leaves, and the setup behavior gate uses a strict fixture
174
+ pnpm accepting only a no-op `install --frozen-lockfile` or the source-owned `build-bridge`
175
+ emit, whose snapshot-local dist is removed on exit. The next run killed all 460 mutants but
176
+ correctly rejected its own impure snapshot: the fixture had called the production dist-lock
177
+ wrapper, which removed its lock but left the ignored `.tmp-verify` parent. The fixture now
178
+ invokes the process-exclusive snapshot emit directly; a focused pre/post tree-manifest replay
179
+ was byte-identical. The fresh run then killed **460/460** mutants across 43 lanes in 53m42s
180
+ with the origin and snapshot surfaces pure. Biome only collapsed the new manifest arrays onto
181
+ one line afterward (claim/subject/find/replace bytes and semantics unchanged), and the frozen
182
+ full floor passed in 506s. The receipt-bearing final candidate passed the full floor again
183
+ in 501s before the implementation commit and local-main merge.
184
+
185
+ ### Fixed
186
+
187
+ - **The Copilot receiver parent-death mutant no longer leaves one PID-1 fixture child per qualification run.** P9 found ten reparented Node stubs from ten qualifications. The gate did detect the mutant, but its failing assertion threw before the cleanup statement below it; cleanup now kills the surviving mutant child before asserting, preserving the same attributed red while leaving no process behind.
188
+ - **Control-socket send during Pi compaction is no longer a false `delivered:true` (#111).** A compacting Pi citizen is neither idle nor an ordinary streaming turn, but the receiver only sampled `ctx.isIdle()` and forwarded every non-idle send into `pi.sendMessage({ triggerTurn: true })`. Installed Pi 0.85.1 `sendCustomMessage` then starts `_runAgentPrompt` when not streaming — racing `compact()`'s session rewrite, which is the field path that can make compaction "풀린다". There is no public `ExtensionContext.isCompacting()`. The resident arms on `session_before_compact` and refuses as `compacting`; quiet unknown non-idle (`!idle && ctx.signal === undefined`) is fail-closed as `busy`, not `compacting`, because `ctx.signal` is `agent.activeRun?.abortController.signal` and is already gone during `_handlePostAgentRun` retry/continuation while `_isAgentRunActive` is still true — that cell also covers the manual-compact start race and branch summary, which the public API cannot tell apart. Either token: no `pi.sendMessage`, no `delivered:true`. Idle and live-run steer/followUp are unchanged. Frozen v2 reject taxonomy is untouched — this is an in-band RPC refusal.
189
+
7
190
  ## 0.20.1 - 2026-09-10
8
191
 
9
192
  ### Fixed
@@ -124,17 +307,15 @@ Hosts with no detected harness, and every Linux host, see no new prompt.
124
307
  by the promotion above; it is where a borrowed Mac produces the physical-host
125
308
  evidence the CI runner cannot.
126
309
  - **`scripts/raw-codex-measure/` — Codex 0.153.4 vendor measurement (#95 step 1).**
127
- Nothing is installed: no birth hook, no record, no marker, no doctor, and
128
- `entwurf_fresh_call` still cannot open a codex sibling. GLG reversed the
129
- 2026-08-01 decline on 2026-09-08; live claims that still said otherwise
130
- were repaired. The ledger closes step 1: SessionStart fires on the first
131
- turn, not at window open; a fourth callback spelling
132
- (`mcp__entwurf_bridge__entwurf_v2`); the delivery-capable app-server join
133
- cannot use parent-pid as a citizen key; hook-trust is an ownership cost
134
- (`/etc/codex`, root-owned), not a capability gap; clause 4 is
135
- `thread/name/set` plus a status_line config. Step 2 is not opened.
136
- `PIN_CODEX_MINOR` 0.144 → 0.153 is a re-verification of the archived drift
137
- probe, not a product pin.
310
+ **At the measurement landing on 2026-09-08**, no Entwurf Codex unit had been
311
+ installed or built, no product record/doctor existed, and fresh did not include
312
+ Codex. Those are dated step-1 facts, not the current candidate state. The ledger
313
+ established first-turn `SessionStart`, callback spelling
314
+ `mcp__entwurf_bridge__entwurf_v2`, the shared-app-server refutation of a pid-keyed
315
+ citizen join, root-owned prompt-free hooks, and `thread/name/set`. Later #95 work
316
+ added the record/native-push/fresh candidate described under Unreleased.
317
+ `PIN_CODEX_MINOR` 0.144 0.153 was a re-verification of the archived drift probe,
318
+ not a product pin.
138
319
 
139
320
  ### Changed
140
321
 
@@ -261,9 +442,9 @@ Lane receipts (oracle, 2026-09-09) plus prepare P4/P5 on this host. There is
261
442
  CommandLineTools stub. Both are cells the borrowed-Mac probe
262
443
  (`scripts/raw-macos-measure/probe.sh`, M6 and M3) is built to answer. Do
263
444
  not fill them from a CI image spec.
264
- - **Codex is not a garden backend in this cut.** Step 1 of #95 closed as
265
- measurement; step 2 is not opened. `entwurf_fresh_call` still cannot open a
266
- codex sibling.
445
+ - **On the 0.20.0 release date (2026-09-09), Codex was not a garden backend in that
446
+ cut.** Step 1 of #95 was measurement only and `entwurf_fresh_call` did not include
447
+ Codex. This is release archaeology; 0.21.0 support supersedes it.
267
448
 
268
449
  ## 0.19.0 - 2026-09-07
269
450
 
package/CONTRIBUTING.md CHANGED
@@ -4,7 +4,7 @@ This is a daily-driver bridge. Correctness beats feature breadth. Read this befo
4
4
 
5
5
  ## What this repo is
6
6
 
7
- `entwurf` is a **garden-citizen dispatch bridge** — entwurf-core (v2 dispatch) + a meta-bridge + a pi adapter + an **ACP plugin on a two-backend adapter rail** — that lets already-running harnesses address one another by garden id; pi is one adapter, not the subject. The ACP plugin borrows the backend's identity (system prompt preset, model behavior, tool implementations) and shapes the *operating surface* — tools, MCP, skills, permissions — to match pi's own policy **wherever that backend exposes a knob for it**. Where it does not, the plugin does not fake one: a cortex session keeps its own native tool surface and receives MCP through an overlay projection, so "shaped to pi's policy" is a Claude-strength claim, not a universal one. Claude is the reference ACP backend and Snowflake Cortex Code is the second (landed 0.13.0, `cortex-` prefixed ids — [docs/acp-backend-rail.md](./docs/acp-backend-rail.md#cortex-code-audit-d1d10)); Codex has native delivery-probe evidence but no managed citizen lane, and Gemini is not a shipped backend. That is the entire scope.
7
+ `entwurf` is a **garden-citizen dispatch bridge** — entwurf-core (v2 dispatch) + a meta-bridge + a pi adapter + an **ACP plugin on a two-backend adapter rail** — that lets already-running harnesses address one another by garden id; pi is one adapter, not the subject. The ACP plugin borrows the backend's identity (system prompt preset, model behavior, tool implementations) and shapes the *operating surface* — tools, MCP, skills, permissions — to match pi's own policy **wherever that backend exposes a knob for it**. Where it does not, the plugin does not fake one: a cortex session keeps its own native tool surface and receives MCP through an overlay projection, so "shaped to pi's policy" is a Claude-strength claim, not a universal one. Claude is the reference ACP backend and Snowflake Cortex Code is the second (landed 0.13.0, `cortex-` prefixed ids — [docs/acp-backend-rail.md](./docs/acp-backend-rail.md#cortex-code-audit-d1d10)); Codex is not on this rail at all — it is an unreleased native-push candidate with record birth and delivery-probe evidence (see AGENTS.md, DELIVERY.md), never ACP-managed and Gemini is not a shipped backend. That is the entire ACP-rail scope.
8
8
 
9
9
  **How a backend joins the rail** (the shape a PR must take): one adapter object in `pi-extensions/lib/acp/backend-adapter.ts` + its own curated rows/overlay modules + its own `check-acp-*` gate and mutant lane. Backend-specific *behavior* must stay behind the adapter, and backend-specific settings ride the opaque `adapterSettings` seam rather than growing the common config. That is not a ban on ever touching the common layer (`backend.ts`'s turn loop, `acp-client.ts`, `event-mapper.ts`, `session-store.ts`, `config.ts`) — cortex's landing did change `backend.ts` to pass the authoritative session key through the generic `ensureOverlay` seam. The rule is narrower and stricter: a common-layer change must be **backend-invariant** (it reads no backend name and branches on no backend) and **separately gated**. A common file that grows an `if (backend === …)` is the thing to reject.
10
10
 
package/DELIVERY.md CHANGED
@@ -88,8 +88,8 @@ physical host. Do not read a D-level cell as a Darwin receipt.
88
88
  | **pi native Entwurf** | shipped | D7; D8 partial | Record-addressed Unix control socket. A record-less socket is diagnostic only and never dispatched. |
89
89
  | **Claude Code interactive `>=2.1.217`** | shipped; Linux certified | D6; D7/D8 partial | Per-session mailbox + exec-form `FileChanged`/`asyncRewake`. B2 proved idle wake and same-session continuity on one NixOS host. |
90
90
  | **Antigravity / agy** | shipped | D6; D7 partial | Record-backed native-push through LS gRPC `agentapi send-message`; no mailbox or receiver marker. Admitted before the #82 step 9 contract and not re-evaluated under it, so it is legacy citizen evidence, not a step-9 supported harness: `entwurf_fresh_call` cannot open an agy sibling, and nothing here should be read as claiming visible lifecycle parity. |
91
- | **Codex app-server-backed TUI** | verified probe, re-verified at 0.153.4 | D7; D8 unproven | WebSocket-over-UDS `turn/start` into a live `threadId`; status events expose completion. Re-run as shipped on 2026-09-08 at codex-cli **0.153.4** (garden lane #95): idle plain `codex` auto-attached to a bare `codex app-server --listen`, woke with zero typing, and the model replied in the same visible session `{"ok": true, "threadId": "01a08145-ce6d-77d0-b7a4-cfb57c608282", "turnId": "01a08146-7c06-7b83-9e65-32e3814ee490", "status_seen": ["active","active","idle"]}`. No owned native-citizen install/invocation lane. Step 1 measurement only the lane is open (#95) but nothing is installed, so this row stays the declared pre-#82 exception `check-harness-admission-parity` reads. |
92
- | **Codex embedded TUI** | deferred | D0 partial | No supported receive socket/hook on the measured standalone shape. Re-checked in source at 0.153.4: the hook vocabulary grew to 12 events but still has no `watchPaths`/`FileChanged`/`asyncRewake` analogue, so the negative stands. |
91
+ | **Codex CLI app-server citizen** | supported in 0.21.0; explicit-home acceptance green on Linux at 0.153.4 | D6 accepted; D7 partial; D8 unproven | Vendor-trusted user-scope `SessionStart` birth plus strict per-request metadata join; no shared-pid sender marker. `thread/loaded/list` probes the operator-owned app-server UDS and one-shot `codex queue` delivers with no retry. The supported visible topology is one operator-owned existing tmux session named `codex`: the app-server and supported Codex TUIs sit there, omitted Codex fresh placement selects it, and Codex-opened Pi stays beside its caller. Missing home rejects; Entwurf creates/supervises none of it. Exact 0.153.4 source bounds unrestricted attached-TUI placement as unsupported, not as a blocker to this explicit topology. |
92
+ | **Codex embedded TUI** | deferred | D0 partial | At the 2026-09-08 Codex 0.153.4 measurement, standalone mode had no `watchPaths`/`FileChanged`/`asyncRewake` analogue or supported idle receive route. This is dated vendor evidence, not a claim that the current candidate lacks records or fresh. |
93
93
  | **Copilot CLI first-party extension** | raw transport probe; superseded by the owned product unit | D7 path observed; D3 control receipt incomplete; D8 unproven | CLI-spawned extension over stdio JSON-RPC; `joinSession()` + documented `fs.watch` → `session.send({mode:"enqueue"})`. Idle wake, exact-marker reply, and completion passed on 2026-08-23 (CLI 1.0.80, L4, one Linux host). Two-process isolation was observed but its decisive B log was not preserved. Kept as the transport receipt the owned receive unit was built on; the shipped unit differs deliberately — it announces the inbox instead of injecting the body. |
94
94
  | **Copilot CLI garden citizen** | shipped in 0.15.0; send + receive + visible fresh accepted on one host | D6; D7 partial; D3 pending; D8 unproven | Birth, garden id, MCP hand and record-backed sender identity are accepted; the RECEIVER is an installed first-party extension that binds to the V3 record, writes a receiver marker owned by the WATCHER pid, and rings a doorbell the model drains with `entwurf_inbox_read`. `wakeMode` is `self-fetch`, so dispatch reaches the mailbox rail: armed → delivered, unarmed/stale → the honest `mailbox-undeliverable` refusal. **D6 is the owned-invocation LIVE acceptance of 2026-08-23** — garden `20260823T181316-d9f6ba`, native `20fe30c8-b2bc-4600-91a0-8a409131be51`, CLI 1.0.80: receive log `joined`→`armed`→`doorbell fresh=1`→`rang`, mailbox `lastEnqueuedAt 09:23:41.235Z` / `lastReadAt 09:23:56.480Z`, and a model reply on the same record/native/gid chain. **Visible fresh (step 9 clause 7) is a separate LIVE, 2026-08-25** — launch window `@89`/`%89` nonce `mux-fresh-call-690529ae99f99faa2252aefb`; exact-callback garden `20260825T085721-f68be0`; one `entwurf_v2` → `meta-mailbox → enqueued`; same garden `lastReadAt 2026-08-24T23:57:47.784Z` plus same-gid reply; GLG saw footer garden id and a healthy multi-turn window. Those rows stay unmerged. D7 is PARTIAL: reply and read receipt were observed, the completion taxonomy and long-haul operation were not. D3 (second-session isolation of an owned invocation) is PENDING — observed once, decisive log lost to scratch cleanup. Evidence level L4: one host. Launch through the owned invocation `entwurf copilot`, which sets `COPILOT_CLI_ENABLED_FEATURE_FLAGS=EXTENSIONS` for that one process; `doctor-copilot-receive` reads live CLI environments because a session launched without it is silently inert. Visible fresh is operator-metered and is not a release-gate MUST. |
95
95
  | **OMP (`omp`) garden citizen** | shipped in 0.16.0; send + receive + visible fresh accepted on one host | D6; D3 proven; D7 partial; D8 partial | Two in-process extensions in the operator's own TUI: birth mints the `mode === "tui"` host (subagents mint nothing) and names it as sender; the RECEIVER unit joins that citizen in the SAME process, holds an `fs.watch` on its mailbox signal, and rings an announce-only doorbell through the vendor's `pi.sendUserMessage` — measured to be on the FACTORY object, not the event ctx, and measured to start a turn on an idle host with zero typing (`agent_start` +31ms). `wakeMode` is `self-fetch`: the model drains with `entwurf_inbox_read` and THAT read is the receipt. **D6 is the LIVE acceptance of 2026-08-30** (oracle, omp 18.0.0) — garden `20260830T140819-116f6a`, `lastEnqueuedAt 05:08:20.555Z` / `lastReadAt 05:08:23.958Z`, and the citizen's own transcript carrying `mcp__entwurf_bridge_entwurf_inbox_read` for its own garden id. **D3 is PROVEN, not pending**: with two live omp citizens armed, one addressed enqueue rang exactly one doorbell and the sibling persisted no transcript and kept an empty mailbox. D7 is PARTIAL (`lastReadAt` needs no scraping; the reply does). D8 is PARTIAL — dedupe and every stale-handling path are implemented and hermetically pinned, ordering/loop-guard/crash-recovery are not. The `/new` unarm is the rail-specific one: the watch lives in the operator's TUI, so pid + start-key cannot see a citizen change underneath a living process, and without an explicit unarm the previous garden id would keep reading deliverable. Requires `tools: xdev: false` in `~/.omp/agent/config.yml` — the vendor default hides MCP tool schemas from the prompt, so the doorbell would name a tool the model cannot call. The decisive receipt lines — the ordering probe, the D6 chain, the D3 isolation and the `/new` unarm — are pasted into `scripts/raw-omp-measure/README.md` §M7 rather than left in a host-local `/tmp` path. Evidence level L4: one Linux host, ARM. **Visible fresh (step 9) is ACCEPTED — the clause 7 LIVE went green on 2026-08-30:** `entwurf_fresh_call` opens omp on all three public surfaces through the bare `omp` runtime with an explicit `--approval-mode yolo` width and the `mcp__entwurf_bridge_entwurf_v` callback name, behind a five-axis pre-mutation preflight whose fifth axis is omp-specific (`tools.xdev !== true`, without which the model cannot call the callback tool at all). **The first turn is a TWO-STAGE BOOTSTRAP rather than a positional prompt, and that is a measured correction, not a preference.** `[LIVE 2026-08-30]` the positional-prompt candidate opened its window and minted its citizen (garden `20260830T181342-452167`, native `01a051f2-3107-7147-8806-fa2a6f527610`), delivered the byte-identical framing as a user message at `09:13:42.413Z`, and the model answered the literal text `ACK` at `09:13:47.105Z` with ZERO tool calls; the caller timed out at 240s. `[source]` the interactive UI defers MCP discovery (`sdk.ts:1847-1855`, `:1881-1905`) while the positional `initialMessage` prompts straight after `mode.init()` (`main.ts:540-565`), and `[측정]` an observer on the same runtime saw `turn_start` at +654ms with the entwurf tools absent and the callback tool present only at +1484ms. So the launcher now carries `{v,target,nonce,task}` on the fixed registered flag `--entwurf-bootstrap`, and the in-process birth extension polls `getAllTools`(`source:"mcp"`) AND `getActiveTools` for the exact callback name, sends a callback-ONLY prompt, arms the task only on a `tool_result` whose stored `toolCallId`, tool name, target, nonce and `isError === false` all match, and DELIVERS it at the next `turn_end` of that same session. `[LIVE 2026-08-30]` that last boundary is itself a measured correction: a first attempt sent the task from inside the `tool_result` handler with an explicit `deliverAs: "followUp"`, the hook log showed the full chain (`bootstrap-armed` → `bootstrap-ready` +440ms → `bootstrap-callback-observed` → `bootstrap-released`), and the task still never appeared in the session — `[source]` an explicit `deliverAs` queues without starting a turn in either state, while the omitted form starts one when idle (`agent-session.ts:6511-6513`), which the same transcript confirmed three seconds later when the Bundle B doorbell's omitted-option send landed and started a turn. `[측정 2026-08-30]` the callback-only half is what was proven to work: model `openai-codex/gpt-5.6-sol`, tool live at +1105ms, prompt injected at +1107ms, and the sibling calling `mcp__entwurf_bridge_entwurf_v` with the exact nonce (`omp-cb-btkvva4r87` → `20260830T184054-1aa1f2`, `meta-mailbox → enqueued`). **The clause 7 acceptance, `smoke-omp-fresh-live` (release-gate MUST), 2026-08-30, 21 assertions, omp 18.0.0 / `openai-codex/gpt-5.6-sol`:** launch through `tools/call entwurf_fresh_call` with nonce `mux-fresh-call-34faea228f3c3638684b40ff`; sibling garden `20260830T192913-df52b9` taken from the CALLBACK's sender envelope and never from the launch receipt; exactly one new citizen (§3.5); addressed `entwurf_v2` → `meta-mailbox → enqueued`; `lastEnqueuedAt 10:29:19.179Z` / `lastReadAt 10:29:24.673Z`; the drain visible in that session's own transcript for its own garden id. **Both bootstrap stages are in that transcript in order** — the callback-only prompt at offset 970, the caller's task at 4732, no four-backend positional framing anywhere, and an assistant message after the task proving it started a turn rather than sitting in a queue. The unit's own log carries the state machine: `bootstrap-armed 10:29:13.399Z` → `bootstrap-ready 10:29:13.819Z` (+420ms) → `bootstrap-callback-observed 10:29:18.663Z` (`toolCallId=call_KZJtkxfzy1IEE4ejKutCPWcm|fc_0858…`) → `bootstrap-released 10:29:18.731Z` → `bootstrap-task-sent 10:29:18.759Z`. Evidence level L4: one Linux host, ARM, one run. `check-harness-admission-parity` keeps the deterministic half of the admission from being a matter of memory. What this does NOT establish: multi-host, multi-model, or repeated fresh calls in one process. **Cross-harness leg, 2026-08-31 (first bidirectional live-turn roundtrip on the committed bytes + operator redeploy):** a claude-code citizen (`20260831T090152-f998ac`) opened a fresh omp sibling through the public surface (nonce `mux-fresh-call-807cb693b1913ce98ffdbfd1`, callback sender garden `20260831T124226-eac41a`), dispatched an addressed `entwurf_v2` → `meta-mailbox → enqueued`, and the omp citizen's own live turn drained and replied into the claude mailbox — its self-report (doorbell → `entwurf_inbox_read`; callback-only first user message, task second) agrees with every receipt on the chain. |
@@ -130,64 +130,206 @@ the pid/start-key sender marker would be last-writer-wins. Current operator chec
130
130
  in [BASELINE.md](./BASELINE.md); deterministic ownership and sender gates run in
131
131
  `pnpm run check:full`.
132
132
 
133
- ### Codex: launch mode is part of the capability
134
-
135
- Do not describe “Codex” as one delivery shape. The measured app-server-backed TUI can
136
- accept `turn/start` for a live thread and report completion; the standalone embedded
137
- TUI exposed no equivalent receive route. `turn/steer` is active-turn steering, not idle wake.
138
-
139
- **The 2026-08-01 decline is reversed.** GLG re-opened the Codex native lane on 2026-09-08
140
- (“코덱스 레일 들어가자”) as one of the two 사용자층 확보 axes; the lane is
141
- `#95` and the pi-already-supplies-GPT argument no longer settles it. Nothing here is a
142
- support claim: no codex record has ever been minted, there is no birth hook, no sender or
143
- receiver marker, and `entwurf_fresh_call` cannot open a codex sibling.
144
-
145
- **But step 2 is already spent, and it is spent WRONG.** `codex` is in `META_BACKENDS`
146
- (`meta-session.ts:84`) and `META_CITIZEN_BACKENDS` (`:308`), and `pi/entwurf-capabilities.json`
147
- grades it `wakeMode: "direct-inject"`, `deliveryLevel: "D6"`, `nativeIdLabel: "threadId"`.
148
- That D6 is a **registry promise with no channel behind it** — precisely the shape
149
- `docs/adding-a-harness.md` step 8(c) names and it predates this lane. Correcting the grade
150
- is step 8 work; it is recorded here so the next reader does not inherit a D6 as evidence.
151
-
152
- **Current coordinates are 0.153.4** (source `~/repos/3rd/codex` at `rust-v0.153.4`), measured
153
- 2026-09-08 in `scripts/raw-codex-measure/` step 1 of `docs/adding-a-harness.md` only. The
154
- 0.136.0 rows above are preserved history. What the re-measurement changed:
155
-
156
- - **Delivery survives 17 minor lines unchanged.** The shipped raw probe ran as-is (receipt in
157
- the matrix row above). One method note: at 0.153.4 the rollout JSONL is materialized lazily,
158
- so `thread/loaded/list` not newest-rollout scraping is the reliable `threadId` source.
159
- - **Birth timing is first-turn, not window-open**, and the gap is ~47s wide on a live TUI.
160
- - **A subagent raises `SubagentStart`, never `SessionStart`**, so §3.5's top-level predicate is
161
- vendor-authoritative and costs nothing.
162
- - **The callback dialect is `mcp__entwurf_bridge__entwurf_v2`** — a fourth spelling; the
163
- sanitizer maps `-` to `_` but keeps the digit that omp's charset eats.
164
- - **The step-6 join holds in both launch modes, but its owner moves — and in the
165
- delivery-capable mode it cannot separate citizens.** Embedded:
166
- `hook.ppid == mcp.ppid == the TUI`. App-server-attached: both resolve to the app-server, and
167
- that is now measured rather than inferred two live threads on one app-server, two separate
168
- visible windows, **one `ppid` for every hook and every MCP child of both**, neither TUI in
169
- the chain. A parent-pid sender marker would be one marker for N citizens, which Hard Rule 7's
170
- `nativeSessionId` uniqueness forbids. **A different key was then found, and it is not a
171
- marker at all:** every `tools/call` carries `_meta.threadId` plus an
172
- `x-codex-turn-metadata.{session_id,thread_id,turn_id}` block, so the caller names itself on
173
- each call — and that id is byte-identical to the hook's `session_id`, so birth, delivery and
174
- tool calls share ONE identifier and `record.nativeSessionId = threadId` needs no mapping.
175
- Both halves are vendor-traced, not just captured: `core/src/mcp_tool_call.rs:1328-1349`
176
- inserts `threadId` unconditionally (constant at `:1184`) from the call site at `:506-516`,
177
- and `:1238-1263` carries the turn-metadata block.
178
- (The registry's pre-existing `nativeIdLabel: "threadId"` is right on that axis; its `D6` on
179
- the same row still is not.) Consuming `_meta` is new bridge code and is step 6, unstarted.
180
- - **Both admission questions this lane opened have since been CLOSED by measurement**, and
181
- neither cost a design decision. The **hook trust prompt** has a real non-interactive path: a
182
- hook in the managed `/etc/codex/config.toml` layer runs with no prompt, in both launch modes,
183
- with `turn/start` wake intact while `--dangerously-bypass-hook-trust` runs the hook but
184
- kills auto-attach and is therefore unusable here. The remaining constraint is ownership:
185
- `/etc/codex/` is root-owned, so that is a root-level operator step, not something `setup`
186
- writes (Hard Rule 17). **Clause 4** has a working carrier: `thread/name/set` plus
187
- `[tui] status_line = ["thread-title", …]` renders a garden id and survives turns in both
188
- orderings, because the vendor's auto-titler is guarded on the thread being unnamed
189
- (`tui/src/app/thread_routing.rs:1841`). It is reachable only over the app-server, and the
190
- config key needs a writer that owns exactly it.
133
+ ### Codex: one app-server-backed native-push shape
134
+
135
+ Codex support is deliberately narrower than “drive Codex” and ships that way in
136
+ **0.21.0**. Its purpose is to keep the operator's real Codex session
137
+ native tools, delegation, auth, transcript, and work context addressable through
138
+ the app-server-backed shape measured at Codex CLI **0.153.4**. It is not ACP and does
139
+ not duplicate GPT access:
140
+
141
+ ```text
142
+ operator starts app-server in existing exact tmux session `codex`
143
+ its configured MCP env_vars forward that home coordinate and explicit roots
144
+ → visible codex TUI attaches with --remote unix://<default socket>
145
+ first turn runs the vendor-trusted user-scope SessionStart birth
146
+ V3 record(nativeSessionId = threadId)
147
+ thread/name/set(gardenId)
148
+ MCP tools/call carries strict request metadata
149
+ entwurf_self / entwurf_v2 resolve that exact record
150
+ ```
151
+
152
+ The standalone embedded TUI remains outside this rail: the 2026-09-08 measurement
153
+ found no idle receive route equivalent to the app-server. `turn/steer` is active-turn
154
+ steering, not idle wake. Entwurf does not start, stop, supervise, or health-loop the
155
+ app-server. The operator starts it **inside the existing exact tmux session `codex`**:
156
+
157
+ ```bash
158
+ # Run from a pane in the operator-owned tmux session named exactly `codex`.
159
+ CODEX_HOME="${CODEX_HOME:-$HOME/.codex}"
160
+ mkdir -p "$CODEX_HOME/app-server-control"
161
+ codex app-server --listen "unix://$CODEX_HOME/app-server-control/app-server-control.sock"
162
+ ```
163
+
164
+ This session is the supported home for the app-server and Codex TUIs. Omitted Codex
165
+ fresh placement resolves its exact name from the caller's tmux server; Codex's MCP child
166
+ then inherits the app-server's own `TMUX`/`TMUX_PANE`, so omitted outbound Pi placement
167
+ stays in the home. An attached TUI does not lend its pane to the server, and N arbitrary
168
+ clients in different sessions have no adjacency claim. Missing home/app-server rejects
169
+ rather than guessing a client pane or creating/supervising either resource.
170
+
171
+ Three ownership atoms remain separate:
172
+
173
+ 1. `entwurf install-codex-birth` owns `$CODEX_HOME/hooks.json` one whole file, ours or
174
+ absent plus its launcher and import closure under `$XDG_DATA_HOME/entwurf/codex-birth`.
175
+ No root: every path belongs to the operator. The vendor gates a user-layer declaration on
176
+ ONE interactive "Trust all", which entwurf never answers, pre-seeds or computes; the
177
+ receipt is read as its own doctor axis, and until it exists setup is honestly non-green.
178
+ 2. `entwurf install-codex-mcp` owns `[mcp_servers.entwurf-bridge]` in
179
+ `$CODEX_HOME/config.toml`, including exact `env_vars` forwarding for `CODEX_HOME`,
180
+ Entwurf garden/control roots, and `TMUX`/`TMUX_PANE`. This explicit name boundary
181
+ keeps custom roots and the app-server seat intact without storing their values.
182
+ 3. `entwurf install-codex-statusline` owns only `thread-title` within
183
+ `tui.status_line`. Birth calls `thread/name/set`, so the visible title is the garden id.
184
+
185
+ All three have state-backed doctors and inverses. Symlinked or foreign config is a
186
+ named refusal, not an adoption. Entwurf never installs Codex or its credentials.
187
+
188
+ Sender identity is request-scoped. The bridge requires the complete Codex metadata
189
+ tuple — `_meta.threadId`, `_meta.x-codex-turn-metadata.session_id`, and
190
+ `_meta.x-codex-turn-metadata.thread_id` to agree, then joins that native id through
191
+ the addressable V3 record reader. Missing or conflicting fields refuse; there is no
192
+ parent-pid fallback. A complete pi env claim or other native claim must also agree.
193
+ This is load-bearing because every attached TUI shares the app-server ancestry.
194
+
195
+ The native-push adapter probes `thread/loaded/list`. A missing socket, malformed
196
+ handshake, protocol failure, or absent loaded target is `native-push-probe-indeterminate`
197
+ or dead as defined by the probe result; no mailbox fallback exists. Delivery uses the
198
+ measured one-shot `codex queue` command. Unlike Antigravity, Codex delivery has **zero
199
+ retry**: losing stdout after vendor acceptance must not replay the user's message.
200
+
201
+ Direct injection carries the **mailbox-serialized trusted sender envelope**, not the raw
202
+ message. This rail has neither the control socket's RPC framing nor a mailbox file, so the
203
+ envelope rides in the body through the same `formatMetaMailboxBody` SSOT the mailbox rail
204
+ uses — one rail for both native-push backends, no per-backend branch. It is rendered once,
205
+ before delivery, so Antigravity's one permitted re-probe retry replays byte-identical
206
+ content. A caller with no authoritative sender still injects the raw message; an envelope is
207
+ never fabricated. #95 measured the cost of the old raw behaviour: a fresh sibling's
208
+ nonce-only callback landed in a Codex thread as bare text, so the Codex citizen could not
209
+ name its caller and the callback was uncorrelatable.
210
+
211
+ Visible fresh runs:
212
+
213
+ ```text
214
+ existing tmux session: codex
215
+ operator-owned app-server + supported Codex TUIs
216
+ codex --remote unix://<default socket> --model <explicit model>
217
+ --dangerously-bypass-approvals-and-sandbox <callback-first prompt>
218
+ ```
219
+
220
+ With `placement` omitted, the fresh composition resolves the exact existing `codex` session name
221
+ to its native `$id` before mutation. Other backends retain caller-session default placement; an
222
+ explicit seat remains an expert override. The amended preflight must certify the state-backed birth
223
+ closure digests, vendor trust receipt, exact MCP/env boundary, `thread-title`, and app-server socket
224
+ before tmux mutation. The callback spelling is `mcp__entwurf_bridge__entwurf_v2`; the new garden id
225
+ comes only from its sender envelope. There is no Codex resume, watcher, session/app-server creator,
226
+ or lifecycle supervisor.
227
+
228
+ The 2026-09-11 Linux run used Codex 0.153.4 and the default app-server: the production
229
+ probe found loaded thread `01a08bff-4efa-7071-9cac-b6d29da5f126`; native-push and a
230
+ public MCP `entwurf_v2` call woke its visible TUI; a real authenticated request returned
231
+ `agentId=meta-session/codex`, `replyable=true`; and a source birth hook minted garden
232
+ `20260911T005221-fc7c47` in an isolated store. Those receipts are preserved as
233
+ **pre-amendment focused delivery/identity evidence**, not qualification of the current
234
+ candidate.
235
+
236
+ The amended same-session acceptance (**A**) passed on local candidate `6e28c9e` at Codex
237
+ 0.153.4. Receipt `.probe-artifacts/a95-live-20260911T162152-6e28c9e.log` (SHA-256
238
+ `e159f8fd1cf23cb1abd7782dc9e24cf9604add6b61ae24b7fd5260f16dfc2795`) records 50
239
+ assertions and exit 0: real initial Pi `20260911T162155-0db222` opened and addressed Codex
240
+ `20260911T162210-a974fe` / thread `01a08f58-588b-7620-a67c-cc0f6c62a79e`; Codex then
241
+ opened outbound Pi `20260911T162238-7dcdaa` and correlated its exact callback nonce and
242
+ sender garden id. The operator app-server, initial Pi, Codex, and outbound Pi all resolved to
243
+ tmux session `$158`. Cleanup removed only smoke windows `@355`, `@356`, and `@357`,
244
+ preserved all three records/transcripts, and left operator app-server PID `155166` and
245
+ `$158/@351/%351` alive.
246
+
247
+ That receipt established the mechanism of the now-selected deployment, but its initial Pi also sat
248
+ in the app-server session and predates the fixed-home default. The stronger explicit-home acceptance
249
+ passed on 2026-09-12. Receipt `.probe-artifacts/20260912T140745-codex-home-live-green.log`
250
+ (SHA-256 `09e79bd1b62962f8a11d647ee456a71b11965b6792c7972d97ac4992119c91d7`)
251
+ records 57 assertions and exit 0. The operator app-server stayed at `$158/@390/%390`; real initial Pi
252
+ `20260912T140748-355654` opened outside the home at `$150/@397`, omitted-placement Codex
253
+ `20260912T140800-8bc8d9` / thread `01a09403-e232-76b0-af9a-5cbaed8d94a9` opened at `$158/@398`,
254
+ and Codex opened outbound Pi `20260912T140829-a08178` at `$158/@399`. The callbacks preserved exact
255
+ nonces `mux-fresh-call-5d28f8e5bc2acf275ded30ec` and
256
+ `mux-fresh-call-7a5fb4960dbb172843eb14fe`; initial Pi → Codex reported
257
+ `entwurf_v2 native-push → delivered`, Codex read back its live garden-id title through `thread/read`,
258
+ and every smoke-owned window was removed while the operator app-server remained alive.
259
+
260
+ Two red attempts tightened the acceptance rather than weakening it. The first reached correct home
261
+ placement but Codex interpreted “wait” as `wait_agent(timeout_ms=3600000)`, leaving its first turn
262
+ active; the prompt now requires ending the turn and forbids every wait tool. The second proved that a
263
+ model may reformat a receipt while copying it. That accepted run still travelled coordinate reports
264
+ between citizens and checked them against Pi/Codex source results. A later prepare run exposed the
265
+ remaining flaw by truncating a report token: the amended gate removes intermediate model reports,
266
+ joins exact Pi toolCall/toolResult rows and structured Codex `thread/read` MCP items directly, and
267
+ reads garden ids only from raw callback events. Failure cleanup recovers the exact Codex thread from
268
+ its nonce callback, interrupts only its still-running smoke-owned turn, and closes only windows named
269
+ by joined source fresh-call results.
270
+
271
+ The amendment's own standalone acceptance then passed on 2026-09-12 (Codex 0.153.4): **48 assertions,
272
+ exit 0**, final source audit `initial-pi=3/3 completed exact` and `codex=3/3 completed exact`. Fixture
273
+ `20260912T232054-14c8e8` at `$150/@430`; initial Pi `20260912T232056-c0be9e` at `$150/@431`;
274
+ operator-owned app-server `1693273` at `$158/@390/%390`; omitted-placement Codex `20260912T232110-1cd889`
275
+ (thread `01a095fe-4f1a-7d93-8c5a-ed7539cf8930`) at `$158/@432`; Codex-opened Pi `20260912T232133-4ac684`
276
+ at `$158/@433`; final token `CODEX-PI-FINAL-6QWDSELJ` carried `PI_CALLBACK_FROM=20260912T232133-4ac684`,
277
+ `PI_SESSION_ID=$158`, `PI_WINDOW_ID=@433` into the fixture mailbox, which read it. Cleanup reclaimed
278
+ `@433`, `@432`, `@431` — every one named by a joined source fresh-call result, with no tmux inventory scan
279
+ and no mailbox prose promoted to authority. Artifact `.probe-artifacts/codex-fresh-live-fZccoK/`
280
+ (`run-manifest.json` sha256 `05f20f00…`). The 57-assertion first-admission receipt above is preserved as
281
+ its own pre-amendment axis; the two counts are different contracts, not a regression.
282
+
283
+ That run also closed a defect the amendment had introduced. A source path read once at callback
284
+ correlation froze out a record that gained `transcriptPath` 34s later, so the observer saw nothing while
285
+ the chain actually completed — the preceding red run's Codex thread holds a completed final `entwurf_v2`
286
+ result naming `PI_WINDOW_ID=@426`, and its two orphan windows were reclaimed only after their own joined
287
+ source receipts named them (`@425` backend codex from the initial Pi, `@426` backend pi from the Codex
288
+ thread; cleanup log preserved at `.probe-artifacts/codex-fresh-live-FblYLs/source-authorized-cleanup.log`,
289
+ sha256 `07fe18fc…`, alongside `run-manifest.json` sha256 `62a538fd…`). The path is now re-read while the
290
+ cache is empty. Note honestly what the green run does and does not prove: its manifest carries neither
291
+ `initialPiTranscriptResolvedLate` nor `sourcePathUnresolved`, so the late-write timing did not recur and
292
+ the repair is proven by its deterministic claim, not by this LIVE.
293
+
294
+ **Repeat acceptance inside the aggregate release gate (2026-09-13).** The same acceptance ran again as
295
+ the `smoke-codex-fresh-live` MUST step of `release-gate --cut` and passed at 48 assertions: fixture
296
+ `20260913T065136-59d143` at `$187/@442`, initial Pi `20260913T065138-ad5f57` at `$187/@447`,
297
+ omitted-placement Codex `20260913T065151-4b6078` (thread `01a0979a-edc4-7570-a29b-50f7e48cf298`) at
298
+ `$158/@448`, Codex-opened Pi `20260913T065213-0811e6` at `$158/@449`, against the same operator-owned
299
+ app-server at `$158/@390/%390`. Artifact `.probe-artifacts/codex-fresh-live-2oId4C/` (`run-manifest.json`
300
+ sha256 `2cff6f08…`). Note the fixture seat differs from the standalone run: the aggregate builds its own
301
+ tmux session (`$187`), while the Codex legs still resolve to the exact `codex` home. That gate reported
302
+ MUST PASS=24 FAIL=0 SKIP=0 and `cut: OK`. Keep the three Codex acceptances apart — 57-assertion
303
+ pre-amendment first admission, 48-assertion standalone amended (`fZccoK`), and this 48-assertion
304
+ aggregate repeat (`2oId4C`).
305
+
306
+ An earlier attempt at the identical source fingerprint failed two model-in-loop MUST steps and is kept
307
+ as an observation rather than a defect: a callback nonce arrived one character short and an addressed
308
+ instruction carried the wrong payload. Joined source receipts showed target, rail, and delivery were all
309
+ correct, so both oracles refused for the right reason and nothing in the product was changed.
310
+
311
+ Unrestricted attached-TUI parity (**B**) is explicitly outside this support claim. The exact vendor
312
+ checkout `rust-v0.153.4` at `3d2ee51ca2d5db578f328aa75e20aa22c0197c9a` found no public
313
+ request→attached-TUI-seat carrier: process-local `ConnectionId` does not cross into thread/core/MCP/hook
314
+ state, and the TUI-local dynamic task MCP is a closed vendor namespace. That source result now bounds
315
+ the topology instead of blocking the explicit home. Manual pane/process guessing and hidden manager
316
+ behaviour remain invalid; explicit `placement.tmuxSession` is an operator-named expert override, not
317
+ an inferred seat. The gate strips ambient `PI_SESSION_ID`/`PI_AGENT_ID`; a fixture may preserve receipts
318
+ but cannot substitute for the initial visible record-backed Pi turn.
319
+
320
+ Run the clause-7 invocation from a tmux session other than `codex`; the explicit app-server PID must belong to the operator-owned app-server seated in `codex`, and both models are explicit:
321
+
322
+ ```bash
323
+ LIVE=1 \
324
+ ENTWURF_CODEX_APP_SERVER_PID=<existing-app-server-pid> \
325
+ ENTWURF_CODEX_FRESH_MODEL=<codex-model> \
326
+ ENTWURF_CODEX_FRESH_PI_MODEL=<pi-model> \
327
+ ./run.sh smoke-codex-fresh-live
328
+ ```
329
+
330
+ This is the intended gate entrypoint, not acceptance by command name. The accepted receipt above
331
+ has a fixture only collecting evidence: a real visible record-backed Pi opens Codex from outside the
332
+ home, and the later exact callback and bidirectional delivery receipts close the topology claim.
191
333
 
192
334
  ### Copilot CLI: one citizen, two rails, one pending receipt
193
335