@junghanacs/entwurf 0.20.1 → 0.22.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 (111) hide show
  1. package/AGENTS.md +85 -190
  2. package/BASELINE.md +6 -3
  3. package/CHANGELOG.md +473 -14
  4. package/CONTRIBUTING.md +1 -1
  5. package/DELIVERY.md +332 -60
  6. package/README.md +100 -22
  7. package/VERIFY.md +93 -7
  8. package/docs/acp-backend-rail.md +0 -1
  9. package/docs/external-mcp-host.md +64 -33
  10. package/docs/setup-clean-host.md +151 -17
  11. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +130 -114
  12. package/mcp/entwurf-bridge/dist/pi-extensions/lib/codex-caller-seat.js +174 -0
  13. package/mcp/entwurf-bridge/dist/pi-extensions/lib/codex-fresh-preflight.js +519 -0
  14. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-fact-provider.js +22 -2
  15. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-peers-render.js +3 -1
  16. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-contract.js +6 -7
  17. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-native-push.js +30 -17
  18. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-production.js +7 -1
  19. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-runner.js +3 -2
  20. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-send.js +8 -4
  21. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-surface.js +7 -4
  22. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-mailbox-body.js +20 -11
  23. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-sender-identity.js +227 -1
  24. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +189 -19
  25. package/mcp/entwurf-bridge/dist/pi-extensions/lib/native-push/adapter.js +10 -7
  26. package/mcp/entwurf-bridge/dist/pi-extensions/lib/native-push/codex-ws-client.js +403 -0
  27. package/mcp/entwurf-bridge/dist/pi-extensions/lib/native-push/register.js +4 -4
  28. package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-hook-codex.js +325 -0
  29. package/mcp/entwurf-bridge/dist/scripts/codex-socket-path.js +30 -0
  30. package/mcp/entwurf-bridge/dist/scripts/meta-bridge-fresh-cut.js +6 -1
  31. package/mcp/entwurf-bridge/src/index.ts +154 -116
  32. package/mcp/entwurf-bridge/tsconfig.build.json +10 -0
  33. package/package.json +13 -4
  34. package/pi-extensions/entwurf-control.ts +71 -19
  35. package/pi-extensions/lib/codex-caller-seat.ts +204 -0
  36. package/pi-extensions/lib/codex-fresh-preflight.ts +580 -0
  37. package/pi-extensions/lib/compaction-send-guard.ts +80 -0
  38. package/pi-extensions/lib/entwurf-fact-provider.ts +29 -3
  39. package/pi-extensions/lib/entwurf-peers-render.ts +4 -1
  40. package/pi-extensions/lib/entwurf-v2-contract.ts +6 -7
  41. package/pi-extensions/lib/entwurf-v2-native-push.ts +35 -18
  42. package/pi-extensions/lib/entwurf-v2-production.ts +10 -3
  43. package/pi-extensions/lib/entwurf-v2-runner.ts +3 -2
  44. package/pi-extensions/lib/entwurf-v2-send.ts +16 -11
  45. package/pi-extensions/lib/entwurf-v2-surface.ts +7 -4
  46. package/pi-extensions/lib/meta-mailbox-body.ts +22 -13
  47. package/pi-extensions/lib/meta-sender-identity.ts +305 -0
  48. package/pi-extensions/lib/mux-fresh-call.ts +233 -29
  49. package/pi-extensions/lib/native-push/adapter.ts +21 -24
  50. package/pi-extensions/lib/native-push/codex-ws-client.ts +506 -0
  51. package/pi-extensions/lib/native-push/register.ts +7 -9
  52. package/pi-extensions/meta-bridge-hook-codex.ts +371 -0
  53. package/run.sh +342 -28
  54. package/scripts/check-agy-sender-identity.ts +1 -1
  55. package/scripts/check-codex-app-server-launch.ts +445 -0
  56. package/scripts/check-codex-birth-hook.ts +264 -0
  57. package/scripts/check-codex-bridge-identity.ts +179 -0
  58. package/scripts/check-codex-native-push.ts +386 -0
  59. package/scripts/check-codex-sender-identity.ts +495 -0
  60. package/scripts/check-compaction-send-guard.ts +130 -0
  61. package/scripts/check-copilot-receive-arm.ts +4 -1
  62. package/scripts/check-entwurf-fact-provider.ts +38 -0
  63. package/scripts/check-entwurf-peers-surface.ts +13 -1
  64. package/scripts/check-entwurf-self-address.ts +15 -16
  65. package/scripts/check-entwurf-v2-contract.ts +4 -3
  66. package/scripts/check-entwurf-v2-decider.ts +7 -5
  67. package/scripts/check-entwurf-v2-native-push.ts +35 -7
  68. package/scripts/check-entwurf-v2-production.ts +245 -12
  69. package/scripts/check-entwurf-v2-runner.ts +1 -1
  70. package/scripts/check-entwurf-v2-send.ts +26 -7
  71. package/scripts/check-entwurf-v2-surface.ts +1 -1
  72. package/scripts/check-gate-qualification.ts +8 -3
  73. package/scripts/check-harness-admission-parity.ts +0 -1
  74. package/scripts/check-mux-launch-tmux.ts +345 -4
  75. package/scripts/check-native-push-adapter.ts +20 -16
  76. package/scripts/check-native-push-register.ts +5 -1
  77. package/scripts/check-release-gate-outcomes.ts +47 -1
  78. package/scripts/check-setup-qualification.sh +3 -1
  79. package/scripts/codex-app-server-launch.sh +275 -0
  80. package/scripts/codex-birth-doctor.sh +276 -0
  81. package/scripts/codex-birth-install.sh +414 -0
  82. package/scripts/codex-birth-uninstall.sh +170 -0
  83. package/scripts/codex-mcp-config.py +435 -0
  84. package/scripts/codex-socket-path.ts +33 -0
  85. package/scripts/codex-statusline-config.py +434 -0
  86. package/scripts/codex-terminal-title-config.py +500 -0
  87. package/scripts/codex_toml_io.py +653 -0
  88. package/scripts/lib/codex-fresh-live-protocol.ts +121 -0
  89. package/scripts/lib/codex-fresh-source-receipts.ts +426 -0
  90. package/scripts/lib/launch-receipt-windows.ts +46 -0
  91. package/scripts/lib/tmux-coordinate-row.ts +58 -0
  92. package/scripts/meta-bridge-fresh-cut.ts +6 -1
  93. package/scripts/mutants/codex-app-server-launch.json +157 -0
  94. package/scripts/mutants/codex-caller-seat.json +336 -0
  95. package/scripts/mutants/codex-native.json +838 -0
  96. package/scripts/mutants/compaction-send-guard.json +103 -0
  97. package/scripts/mutants/entwurf-peers.json +19 -0
  98. package/scripts/mutants/mux-fresh-call.json +93 -10
  99. package/scripts/mutants/omp-fresh.json +6 -4
  100. package/scripts/mutants/release-gate.json +13 -0
  101. package/scripts/mutants/v2-surface.json +75 -1
  102. package/scripts/raw-async-delivery/README.md +2 -1
  103. package/scripts/raw-codex-measure/README.md +114 -46
  104. package/scripts/smoke-agy-native-push-live.ts +3 -1
  105. package/scripts/smoke-codex-birth.sh +347 -0
  106. package/scripts/smoke-codex-config-state.sh +700 -0
  107. package/scripts/smoke-codex-fresh-live.ts +1426 -0
  108. package/scripts/smoke-codex-native-push-live.ts +75 -0
  109. package/scripts/smoke-entwurf-chain-live.ts +50 -0
  110. package/scripts/smoke-setup-verdict.sh +125 -10
  111. package/scripts/tsconfig.json +1 -0
@@ -6,47 +6,45 @@ the one-line registration; everything an operator needs to actually wire a host
6
6
 
7
7
  `entwurf-bridge` can also be registered in a separate MCP-aware harness (Claude Code, Codex CLI, Antigravity/`agy`, GitHub Copilot CLI, OMP, …). That host does **not** become a pi session and does **not** need to be ACP-backed. There are now two honest cases:
8
8
 
9
- - **plain external MCP host**: no garden meta-record / sender marker. It can call the read surfaces (`entwurf_peers`, `entwurf_inbox_read`), but `entwurf_v2` sends are **refused by default** (#50 C4: "if we don't know who sent it, we don't send it"). The operator may wire the explicit hatch below; the send then goes out external/non-replyable.
10
- - **garden-native native session**: a trusted lifecycle hook minted a garden id and sender marker — `SessionStart` for Claude Code, `PreInvocation` for agy, `userPromptSubmitted`/`sessionStart` for GitHub Copilot CLI, and for OMP an in-process extension on `session_start`/`session_switch` that mints only a `mode === "tui"` host. It is not a pi control-socket session, but it can be replyable by garden id when its own mailbox/probe rail says so.
9
+ - **plain external MCP host**: no garden meta-record or authoritative identity carrier. It can call the read surfaces (`entwurf_peers`, `entwurf_inbox_read`), but `entwurf_v2` sends are **refused by default** (#50 C4: "if we don't know who sent it, we don't send it"). The operator may wire the explicit hatch below; the send then goes out external/non-replyable.
10
+ - **garden-native native session**: a trusted lifecycle event minted a garden id — `SessionStart` for Claude Code and Codex, `PreInvocation` for agy, `userPromptSubmitted`/`sessionStart` for GitHub Copilot CLI, and for OMP an in-process extension on `session_start`/`session_switch` that mints only a `mode === "tui"` host. Claude/Copilot/agy/OMP then use record-backed process markers; Codex instead carries its matching native `threadId` on every MCP request. It is not a pi control-socket session, but it can be replyable by garden id when its own mailbox/probe rail says so.
11
11
 
12
12
  **Being garden-native is not the same as being replyable, and Copilot is the case that separates them.** Its hook writes a sender marker, so an `entwurf_v2` send carries its own garden id and the receiver learns who wrote — measured 2026-08-21 on Copilot CLI 1.0.80, where a live send arrived under its own garden id with `origin: "meta-session"` and `replyable: false`. Replyability arrived later and through a different process: a first-party extension (`run.sh install-copilot-receive`) that the CLI forks, which binds to the same V3 record and writes a receiver marker owned by its own pid (#82 RAIL 5). So a Copilot citizen is `replyable: true` exactly while that extension is armed for it, and `replyable: false` — honestly, with a real garden identity — when it is not installed, not launched with `COPILOT_CLI_ENABLED_FEATURE_FLAGS=EXTENSIONS` (which is what `entwurf copilot` sets for one invocation), not yet born, or gone. Read the two facts off different rails: the sender marker answers *who sent this*; the receiver marker answers *can a reply land*. The onboarding obligations are in [`adding-a-harness.md`](./adding-a-harness.md) step 7, and the evidence boundary — the managed LIVE acceptance that moved receive to D6, and what D7-partial / D3-pending still exclude — is in [`DELIVERY.md`](../DELIVERY.md).
13
13
 
14
- **Which verb an external agent should reach for:** to deliver to / reply to a garden id, use **`entwurf_v2`** — it is the canonical delivery surface and the only one that reads whether the target is live pi, dormant pi, a mailbox-backed self-fetch citizen (Claude Code, Copilot, OMP), or native-push Antigravity and routes correctly. Discover targets with `entwurf_peers`, confirm your own identity with `entwurf_self`, drain a mailbox with `entwurf_inbox_read`, and use `entwurf_register_native` only as the explicit/manual fallback for binding an already-running agy conversation (normal agy birth is automatic through the installed hook). Open a NEW sibling with **`entwurf_fresh_call {backend, model, task, cwd?, placement?}`**, and reopen a DORMANT pi citizen under its own garden id with **`entwurf_resume_call {target}`**. Fresh call accepts one optional literal absolute `cwd`: omit it or pass `""` for the caller's cwd; otherwise it must name an existing directory and may not contain `#`. Use that input for a new cross-repository sibling — resume preserves a dormant Pi citizen's recorded continuity and is not a cwd substitute. Fresh call also accepts one optional `placement.tmuxSession`: the exact name of an EXISTING session on the caller's own tmux server, so the sibling opens in the operator's project seat rather than beside the caller. It creates nothing — an absent session is refused as `tmux-session-missing`, a name outside `[A-Za-z0-9][A-Za-z0-9_-]*` as `tmux-session-name-invalid`, and no window and no session are left behind either way. The seat and the `cwd` never infer each other: with `cwd` omitted a seated sibling starts in the CALLING agent's own directory, not the target session's path. Fresh call needs its selected runtime on the server's PATH (`pi`, `claude`, `entwurf` or `omp` — Copilot opens through the managed `entwurf copilot` invocation, never the bare vendor, while OMP is opened as the bare `omp` runtime); resume call always needs `pi`. Both start a runtime, while delivery does not. (The old v1 verbs `entwurf` / `entwurf_resume` / `entwurf_send` are gone.)
14
+ **Which verb an external agent should reach for:** to deliver to / reply to a garden id, use **`entwurf_v2`** — it is the canonical delivery surface and the only one that reads whether the target is live pi, dormant pi, a mailbox-backed self-fetch citizen (Claude Code, Copilot, OMP), or native-push Antigravity/Codex and routes correctly. Discover targets with `entwurf_peers`, confirm your own identity with `entwurf_self`, drain a mailbox with `entwurf_inbox_read`, and use `entwurf_register_native` only as the explicit/manual fallback for binding an already-running agy conversation. Open a NEW sibling with **`entwurf_fresh_call {backend, model, task, cwd?, placement?}`**, and reopen a DORMANT pi citizen under the same id with **`entwurf_resume_call {target}`**.
15
15
 
16
- Observed: Claude Code, Codex CLI, Antigravity CLI, GitHub Copilot CLI and OMP all reach the read surfaces through this MCP bridge from a plain external host — `entwurf_peers` is a pure fact projection, while `entwurf_inbox_read` is a **mutating drain** (it archives the messages and stamps the read-receipt), so "read" here does not mean side-effect-free; **sending** needs an identity lane. Claude, Copilot and OMP become symmetric/replyable through a mailbox-backed meta-session Claude's watch armed by its own hook, Copilot's by the forked extension it installs, OMP's by an extension running inside the operator's own TUI process; agy becomes symmetric/replyable through its record-backed sender marker plus live native-push probe. Codex has no managed citizen lifecycle yet, so a Codex host cannot send without the explicit anonymous hatch below.
16
+ Observed: Claude Code, Codex CLI, Antigravity CLI, GitHub Copilot CLI and OMP all reach this MCP bridge. Claude, Copilot and OMP become symmetric/replyable through mailbox-backed meta-sessions; agy and Codex use native-push. Codex's identity is request-scoped `_meta.threadId`, and its replyability requires that exact record plus a loaded app-server thread. Plain external hosts still need an identity lane to send.
17
17
 
18
18
  Prerequisites on the host running the external MCP client:
19
19
 
20
20
  - A live pi session launched with `--entwurf-control` populates `~/.pi/entwurf-control/<gardenId>.sock` — the key is the **record's** garden id, never a transcript/session id (`PI_SESSION_ID` only carries the id record birth already established). Required for `entwurf_v2` control-socket dispatch and `entwurf_peers`.
21
21
 
22
- > **PATH boundary.** MCP servers are often launched by GUI/editor daemons and may not inherit the interactive shell's PATH. No `entwurf_v2` rail launches a process, so this does not affect delivery — but `entwurf_fresh_call` and `entwurf_resume_call` do open a fixed runtime. If that runtime works in your terminal but an external-host call fails with `spawn pi ENOENT`, `spawn claude ENOENT`, `spawn entwurf ENOENT`, or `spawn omp ENOENT`, pass a full PATH in the MCP server `env`, set `ENTWURF_BRIDGE_ENV_FILE` to a small shell file that exports PATH, or point the host at a wrapper that can find the runtime. `start.sh` sources only the explicit `ENTWURF_BRIDGE_ENV_FILE`; it never reads personal dotfiles automatically.
23
-
24
- > **Identity-carrier boundary.** A complete `PI_SESSION_ID` + `PI_AGENT_ID` pair wins before a
25
- > native sender marker in the bridge's authoritative-self resolution. Those variables are correct
26
- > inside the pi process that planted them from record birth; they are foreign identity in another
27
- > native harness. Starting that harness from a pi citizen's bash can otherwise make the new host —
28
- > and any internal agents borrowing its MCP manager speak under the parent pi garden id. Every
29
- > managed native launcher therefore removes both variables before exec and lets the launched
30
- > harness establish its own identity through its trusted birth marker. Clearing only one is not a
31
- > repair: an incomplete pair merely changes the failure wording, while retaining either carrier
32
- > invites future partial-reader drift. An unmanaged launch that inherits them is unsupported. A
33
- > native-harness admission made under this contract must add a doctor cell that names this
34
- > contamination rather than silently preferring the marker.
22
+ > **PATH boundary.** MCP servers are often launched by GUI/editor daemons and may not inherit the interactive shell's PATH. No `entwurf_v2` rail launches a process, so this does not affect delivery — but `entwurf_fresh_call` and `entwurf_resume_call` do open a fixed runtime. If that runtime works in your terminal but an external-host call fails with `spawn pi ENOENT`, `spawn claude ENOENT`, `spawn entwurf ENOENT`, `spawn omp ENOENT`, or `spawn codex ENOENT`, pass a full PATH in the MCP server `env`, set `ENTWURF_BRIDGE_ENV_FILE` to a small shell file that exports PATH, or point the host at a wrapper that can find the runtime. `start.sh` sources only the explicit `ENTWURF_BRIDGE_ENV_FILE`; it never reads personal dotfiles automatically.
23
+
24
+ > **Identity-carrier boundary.** The bridge reconciles every authoritative claim available to
25
+ > the request: the complete `PI_SESSION_ID` + `PI_AGENT_ID` pair, a native sender marker, and
26
+ > Codex request `_meta`. Zero claims is refused; multiple claims must name the same garden id or
27
+ > the call throws. Nothing silently wins. Those pi variables are correct only inside the pi
28
+ > process that planted them from record birth; they are foreign identity in another native
29
+ > harness. Every managed fresh launch therefore empties both variables at the tmux seam and lets
30
+ > the launched harness establish its own identity. Clearing only one is not a repair: an
31
+ > incomplete pair is itself invalid, while retaining either carrier invites future reader drift.
32
+ > An unmanaged launch that inherits them is unsupported. A native-harness admission must add a
33
+ > doctor or request-level contract that names contamination rather than trusting precedence.
35
34
  >
36
- > **Certified so far: Copilot only** (#82 RAIL 9). `scripts/copilot-launch.sh` unsets both before
37
- > `exec`, `check-copilot-launch` asserts their absence from the vendor's OWN report of its
38
- > environment (and that clearing one alone does not pass), and `doctor-copilot-receive` reports any
39
- > live Copilot CLI still carrying either as RED on its own axis — a session can be perfectly armed
40
- > and still speak under the parent's garden id, so the flag verdict must not absorb it. The other
41
- > native launchers have NOT been certified against this failure mode; do not read Copilot's cells
42
- > as coverage for them.
35
+ > **The defenses are layered.** Copilot's managed launcher unsets both carriers before `exec`,
36
+ > and `doctor-copilot-receive` detects a contaminated live CLI. The shared fresh-call tmux seam
37
+ > empties them for every backend. OMP retains its process-environment doctor for manually opened
38
+ > sessions. Codex's request-level reconciler adds a different last line: a foreign pi claim and
39
+ > `_meta.threadId` claim cannot coexist under different garden ids. These are complementary;
40
+ > no backend's cell is evidence for another's unmanaged launch.
43
41
  >
44
42
  > **OMP has both halves, and the strip one is not an omp launcher** (#87 Bundle C). entwurf still
45
43
  > owns no managed omp invocation — the bare vendor runtime IS this harness's clause 1 answer, and
46
44
  > nothing here argues for a wrapper. The strip sits one level up instead, at the shared launch seam
47
45
  > every `entwurf_fresh_call` backend passes through: `SCRUBBED_INHERITED_ENV` empties
48
46
  > `PI_SESSION_ID` and `PI_AGENT_ID` on the `new-window` argv itself
49
- > (`pi-extensions/lib/mux-fresh-call.ts:402`), for all four backends rather than only the one whose
47
+ > (`pi-extensions/lib/mux-fresh-call.ts`), for all five backends rather than only the one whose
50
48
  > measurement surfaced it, because the leak is a property of tmux and not of a vendor. `-e VAR=`
51
49
  > sets the variable empty rather than unsetting it — tmux has no per-window unset — and every
52
50
  > carrier reader trims and tests truthiness, so empty and absent are the same answer by
@@ -78,16 +76,16 @@ Then add it to the external MCP config:
78
76
  }
79
77
  ```
80
78
 
81
- **Anonymous sender hatch (explicit, documented — never a default).** The bridge refuses an `entwurf_v2` send when the process has neither pi-session env (`PI_SESSION_ID` + `PI_AGENT_ID`) nor a trusted meta-sender marker (#50 C4). A deliberately-anonymous external host — e.g. a Codex CLI wiring, which has no managed citizen lifecycle — may opt out by adding `"ENTWURF_BRIDGE_ALLOW_ANONYMOUS_SENDER": "1"` to the MCP server `env`. The cost is honest and fixed: the send lands with `origin: "external-mcp"`, `replyable: false` (there is no reply address), and `wants_reply: true` stays pointless. The retired opt-in `ENTWURF_BRIDGE_REQUIRE_META_SENDER` is no longer read its demand became the default, so a stale copy in an old install env is inert.
79
+ **Anonymous sender hatch (explicit, documented — never a default).** The bridge refuses an `entwurf_v2` send when the process has neither pi-session env nor a trusted native identity claim. A deliberately-anonymous plain external host may opt out by adding `"ENTWURF_BRIDGE_ALLOW_ANONYMOUS_SENDER": "1"` to the MCP server `env`. The cost is honest and fixed: the send lands with `origin: "external-mcp"`, `replyable: false`, and `wants_reply: true` stays pointless. A managed Codex citizen does **not** use this hatch; request `_meta` supplies its authoritative identity.
82
80
 
83
81
  Emergency/manual workaround when the MCP server environment is wrong but an existing entwurf session must be resumed: run `pi --session /path/to/entwurf.jsonl ...` from an interactive shell whose PATH is known-good. Treat this as a debug escape hatch, not a replacement for fixing the MCP launch environment.
84
82
 
85
83
  External/meta-session semantics:
86
84
 
87
85
  - `entwurf_v2` from a plain external host is **refused by default** (no authoritative sender — #50 C4). With the explicit `ENTWURF_BRIDGE_ALLOW_ANONYMOUS_SENDER=1` hatch it delivers with `origin: "external-mcp"` / `replyable: false`; there is still no reply address.
88
- - `entwurf_v2` from a trusted meta-session delivers with `origin: "meta-session"`, and `replyable` is **derived from that sender's own rail — not granted by being trusted**: a self-fetch sender (Claude Code, Copilot, OMP) is replyable only while its receiver is live and armed, and a native-push sender (Antigravity) only while its adapter probe finds the live conversation. Identity survives either way; only `replyable` drops to `false`. When it is `true`, `wants_reply: true` is allowed and the receiver can reply to the sender's garden id.
86
+ - `entwurf_v2` from a trusted meta-session delivers with `origin: "meta-session"`, and `replyable` is **derived from that sender's own rail — not granted by being trusted**: a self-fetch sender (Claude Code, Copilot, OMP) is replyable only while its receiver is live and armed; Antigravity requires a live native probe; Codex requires a matching request-scoped record and loaded app-server thread. Identity survives either way; only `replyable` drops to `false`.
89
87
  - `entwurf_v2` never launches a process, so no delivery path needs `pi` on PATH. A dormant pi target is refused as `dormant-fire-forget-unsupported`: the hidden background resume that used to answer there was withdrawn under the visible-first rule, and re-opening the session is the separate `entwurf_resume_call` verb — which DOES need `pi` on PATH, because it starts one.
90
- - `entwurf_self` returns the same authoritative identity for pi sessions **and** trusted meta-sessions. A plain external host with no pi env and no trusted sender marker still fails because there is no reply address to report.
88
+ - `entwurf_self` returns the same authoritative identity for pi sessions **and** trusted meta-sessions. A plain external host with no pi env, native marker, or Codex request claim still fails because there is no reply address to report.
91
89
 
92
90
  #### Claude Code
93
91
 
@@ -129,14 +127,47 @@ Claude Code reads `~/.mcp.json` in addition to `~/.claude.json`'s top-level `mcp
129
127
 
130
128
  #### Codex CLI
131
129
 
132
- Add the server to `~/.codex/config.toml`:
130
+ Use the owned surfaces rather than editing `~/.codex/config.toml`:
133
131
 
134
- ```toml
135
- [mcp_servers.entwurf-bridge]
136
- command = "/absolute/path/to/entwurf/mcp/entwurf-bridge/start.sh"
132
+ ```bash
133
+ entwurf install-codex-birth
134
+ entwurf install-codex-mcp
135
+ entwurf install-codex-statusline
136
+ entwurf install-codex-terminal-title
137
+
138
+ entwurf doctor-codex-birth
139
+ entwurf doctor-codex-mcp
140
+ entwurf doctor-codex-statusline
141
+ entwurf doctor-codex-terminal-title
142
+ ```
143
+
144
+ The birth unit owns a `SessionStart` declaration in `$CODEX_HOME/hooks.json`, which the vendor
145
+ runs only after the operator trusts it once in a visible Codex; the other three units own
146
+ only `[mcp_servers.entwurf-bridge]`, the `thread-title` status-line member, and the `thread-id`
147
+ terminal-title member (that last one is the CALLER seat input: without it a Codex citizen opening
148
+ a sibling is refused with `codex-caller-title-missing`). The MCP
149
+ entry carries `ENTWURF_BRIDGE_NATIVE_HOST=codex`, which tells the bridge to require and
150
+ reconcile Codex request `_meta`. Do not add the anonymous hatch.
151
+
152
+ Native receive and visible fresh require the operator-owned default app-server. Entwurf
153
+ does not start or supervise it:
154
+
155
+ ```bash
156
+ entwurf codex-app-server
137
157
  ```
138
158
 
139
- Codex has no managed citizen lifecycle (no sender marker), so this wiring can read `entwurf_peers`/`entwurf_inbox_read` but `entwurf_v2` sends are refused by default (#50 C4). To send anonymously anyway, add the explicit hatch to the same block: `env = { ENTWURF_BRIDGE_ALLOW_ANONYMOUS_SENDER = "1" }` — the send is then marked external/non-replyable (see the hatch paragraph above).
159
+ `entwurf codex-app-server` owns the spelling of that one vendor command it `exec`s
160
+ `codex app-server --listen unix://<default socket>` in the terminal it was typed in, resolving
161
+ the address through the same leaf delivery reads. It never supervises, restarts or daemonizes
162
+ what it becomes.
163
+
164
+ Birth occurs on the first turn, not window open. It mints
165
+ `record.nativeSessionId = threadId` and sets the visible thread title to the garden id.
166
+ `entwurf_v2` probes the loaded-thread list and sends once through `codex queue`; it never
167
+ retries. `entwurf_fresh_call` accepts `backend: "codex"` after the birth, MCP and status-line units
168
+ and the default socket pass preflight; the fourth unit, `install-codex-terminal-title`, is what a
169
+ Codex CALLER needs to open a sibling at all (without it the caller has no resolvable seat and the
170
+ call is refused as `codex-caller-title-missing`). Codex remains outside ACP and has no resume surface.
140
171
 
141
172
  #### Antigravity CLI (`agy`)
142
173
 
@@ -8,8 +8,8 @@ evidence split, and its three states must not collapse into "macOS is supported"
8
8
  (`macos-install-surface`). Weaker than a physical-host doctor green. §1 is
9
9
  this surface.
10
10
  - **NOT CERTIFIED — pending physical host** — **on macOS**, every garden-native
11
- harness rail in §2–§6 (pi, Claude, Copilot, OMP, agy), marker join, ACP turn,
12
- mux. Wiring may be written; that is not a rail receipt. The remaining
11
+ harness rail in §2–§6 (pi, Claude, Copilot, OMP, agy, Codex), marker/request join,
12
+ ACP turn, mux. Wiring may be written; that is not a rail receipt. The remaining
13
13
  unmeasured Darwin doctor reason is per-process environment DISCOVERY
14
14
  (start-key is portable).
15
15
  - **UNSUPPORTED** — native Windows only. macOS is never this.
@@ -20,13 +20,14 @@ evidence split, and its three states must not collapse into "macOS is supported"
20
20
  |---|---|---|
21
21
  | Node | **`>=24.0.0`** | package and bridge runtime |
22
22
  | npm/pnpm | npm is bundled with Node; pnpm is required for source setup | package or source installation |
23
- | Python 3 | required by `setup`/`install` (project-path normalization + settings writers); `--help`/`check-bridge` stay Python-free | pi/Claude/agy/Copilot wiring writers |
23
+ | Python 3 | required by `setup`/`install` (project-path normalization + settings writers); `--help`/`check-bridge` stay Python-free | pi/Claude/agy/Copilot/Codex wiring writers |
24
24
  | entwurf | global/project-local `@junghanacs/entwurf`, or a source checkout | operator command and garden capability |
25
25
  | pi | optional-by-presence, `>=0.85.1 <0.86` — absent is an explicit setup SKIP, below-floor is a named FAIL | ACP provider, control sockets |
26
26
  | Claude Code | optional, **`>=2.1.217`** — the exec-form hook floor | Claude ACP auth/runtime and mailbox-backed native citizen |
27
27
  | GitHub Copilot CLI | optional-by-presence, operator-installed and authenticated — absent is an explicit setup SKIP; detected composes all four units (birth/MCP/receiver/footer) | self-fetch citizen and visible fresh |
28
28
  | OMP (`omp`) | optional-by-presence, operator-installed — absent is an explicit setup SKIP; detected composes all four units (birth/MCP/`tools.xdev` setting/receiver) | self-fetch citizen and visible fresh (accepted on one host — see §4b) |
29
29
  | Antigravity `agy` | optional, operator-installed and authenticated | native-push citizen |
30
+ | OpenAI Codex CLI | optional-by-presence, operator-installed and authenticated; detected composes all four operator-owned units (birth/MCP/status-line/terminal-title) and stays non-green until the operator trusts the birth declaration once in a visible Codex; visible fresh also requires the operator-owned app-server, started with `entwurf codex-app-server` in a tmux session of the operator's choosing (#95 D1 retired the fixed `codex` home) | native-push and visible fresh, supported in 0.21.0 |
30
31
  | Cortex Code | optional, operator-installed and authenticated | Cortex ACP backend |
31
32
 
32
33
  Claude Code >=2.1.217 is required for the managed exec-hook lifecycle. The package
@@ -52,9 +53,9 @@ harness. The wiring is written; the row is FAIL:
52
53
  > platform. Tracking: #78.
53
54
 
54
55
  That FAIL is honest: setup runs no doctor, and no physical-host rail
55
- receipt exists — it is not an install failure. An Entwurf-only host (no
56
- harness on PATH) stays green (five SKIP, `bins: PASS`, `core: PASS`,
57
- `result: green`), which is what CERTIFIED (CI) observed.
56
+ receipt exists — it is not an install failure. An Entwurf-only host with no
57
+ harness on PATH stays green (six SKIP, `bins: PASS`, `core: PASS`,
58
+ `result: green`), which is what CERTIFIED (CI) observes.
58
59
 
59
60
  A physical Mac can send host facts without installing entwurf or logging
60
61
  into a harness: `scripts/raw-macos-measure/probe.sh` (`/bin/sh`, ~3.5s,
@@ -91,10 +92,10 @@ npx entwurf check-bridge
91
92
  `check-bridge` is auth-free. It proves the installed prebuilt MCP server boots and
92
93
  lists the seven garden tools; it does not prove a backend model turn or native hook.
93
94
 
94
- Neither npm form installs a harness runtime. `pi`, Claude Code, Copilot CLI, agy, Cortex and their
95
- authentication remain operator-owned optional prerequisites for the integrations that use them;
96
- all may be absent on an Entwurf-only host. A source checkout's pinned Pi development packages are
97
- for building and testing this repo, not a transitive product installation promise.
95
+ Neither npm form installs a harness runtime. `pi`, Claude Code, Copilot CLI, Codex CLI, agy,
96
+ Cortex, and their authentication remain operator-owned optional prerequisites for the
97
+ integrations that use them; all may be absent on an Entwurf-only host. A source checkout's
98
+ pinned Pi development packages are for building and testing this repo, not a transitive product installation promise.
98
99
 
99
100
  Maintainers using a source checkout do not install a second global entwurf package. Full source
100
101
  setup currently requires Node 24, pnpm, and Python 3 on PATH; every harness — including pi — is
@@ -109,12 +110,14 @@ cd ~/repos/gh/entwurf
109
110
 
110
111
  This owns `~/.local/bin/entwurf` as a symlink to that checkout's `run.sh` and fails if the
111
112
  link is foreign, outside PATH, or shadowed by another command. It detects and wires
112
- pi/Claude/agy/Copilot/OMP by presence and prints a computed per-component PASS/SKIP/FAIL summary — a
113
- detected harness that cannot be completed makes setup exit nonzero. A detected `copilot`
114
- composes all four native units (birth MCP receiver visible footer) with independent
115
- per-unit verdicts (#86 C3b), and a detected `omp` composes its own four (birth → MCP →
116
- `tools.xdev` setting receiver) the same way; §4 and §4b keep the explicit per-unit
117
- install/doctor/inverse surfaces for repair.
113
+ pi/Claude/agy/Copilot/OMP/Codex by presence and prints a computed per-component
114
+ PASS/SKIP/FAIL summary — a detected harness that cannot be completed makes setup exit nonzero.
115
+ A detected `copilot` composes all four native units, and a detected `omp` composes its own four.
116
+ A detected Codex install is a supported 0.21.0 path: setup composes its user-scope birth,
117
+ MCP, and status-line atoms but never escalates or starts the app-server. It reports independent
118
+ component outcomes, preserves exact env forwarding and idempotence, and returns a named
119
+ `codex-birth FAIL` until the vendor trust receipt exists.
120
+ §4, §4b, and §4c keep the explicit per-unit install/doctor/inverse surfaces.
118
121
 
119
122
  ### 1.1 User-scope ownership (one shared registration, one recorded owner)
120
123
 
@@ -396,6 +399,133 @@ The MCP entry deliberately uses the same server key as any Claude Code import so
396
399
  SHADOWS it; see [`external-mcp-host.md`](./external-mcp-host.md) for why that key is pinned and
397
400
  why `disabledServers` is never the way to hide an import.
398
401
 
402
+ ## 4c. Optional OpenAI Codex CLI native citizen
403
+
404
+ Supported in 0.21.0 with Linux-focused evidence; on macOS it is NOT CERTIFIED —
405
+ pending physical host. It is native, not ACP: the purpose is to preserve the operator's
406
+ Codex tools, delegation, and work context rather than provide another GPT access path.
407
+ Entwurf owns four atoms, not the harness and not a daemon:
408
+
409
+ ```bash
410
+ # All four are operator-scope; `entwurf setup` runs them when it detects Codex. No root.
411
+ entwurf install-codex-birth
412
+ entwurf install-codex-mcp
413
+ entwurf install-codex-statusline
414
+ entwurf install-codex-terminal-title
415
+
416
+ entwurf doctor-codex-birth
417
+ entwurf doctor-codex-mcp
418
+ entwurf doctor-codex-statusline
419
+ entwurf doctor-codex-terminal-title
420
+ ```
421
+
422
+ The first three make a Codex session a citizen others can reach. The fourth makes it a CALLER:
423
+ `thread-id` in `[tui].terminal_title` is the only thing that tells the multiplexer which pane is
424
+ this thread's, so without it a Codex citizen opening a sibling is refused with
425
+ `codex-caller-title-missing` (an explicit `placement.tmuxSession` skips that check entirely).
426
+
427
+ **One thing here is NOT an entwurf atom, and installing everything above does not supply it: the
428
+ directory a sibling starts in must be one this Codex has an answer for.** A DIRECT decision is
429
+ recorded per exact directory on this rail — no parent, no git root — and a directory with no
430
+ answer anywhere opens a consent screen instead of running a first turn, which unattended looks
431
+ exactly like a sibling that never called back; `entwurf_fresh_call` prints
432
+ `codex-launch-cwd-undecided` and opens the window anyway, because that screen is self-repairing and
433
+ answering it once is the whole fix. That is the ordinary outcome and not the only one. Either recorded
434
+ answer passes: a deliberate `untrusted` is skipped by the vendor on this rail and its turn starts.
435
+ A project `.codex` layer can consent with no entry at all. A directory INSIDE an explicitly
436
+ `untrusted` project is noted under a different name (`codex-launch-cwd-untrusted-ancestor`)
437
+ because the vendor answers that with `pass the repository root explicitly with --cd` rather than a
438
+ prompt — launch at that root instead.
439
+
440
+ The check is a narrow LOCAL read of your own `config.toml`, not the vendor's verdict: the vendor
441
+ merges system, managed and cloud layers around that file, so everything this check cannot see is
442
+ allowed through rather than guessed at. The repair for the ordinary case is the vendor's own, done
443
+ once per directory you launch siblings into: open a plain `codex -C <dir>` there and answer the
444
+ prompt. Name that directory literally — a `$TMPDIR`-style
445
+ spelling silently becomes a different path on a host where `TMPDIR` is unset, and the vendor keys
446
+ its consent to the exact string.
447
+
448
+ The birth atom publishes a `SessionStart` declaration into `$CODEX_HOME/hooks.json` with its
449
+ launcher closure under `$XDG_DATA_HOME/entwurf/codex-birth`; it mints a V3 record on the
450
+ thread's first turn and sets the thread title to the garden id.
451
+
452
+ **One step is the operator's, and no command can do it for them.** The vendor will not run a
453
+ user-layer hook until it has been trusted once: open a visible plain Codex, answer
454
+ `Trust all and continue`, then send one first turn. `doctor-codex-birth` reads the receipt the
455
+ vendor writes for that declaration and is RED without it — entwurf never writes, pre-seeds or
456
+ recomputes that value, because it is the operator's security decision, not ours. After the one
457
+ answer, later sessions raise no prompt and are born automatically. The MCP writer owns
458
+ `[mcp_servers.entwurf-bridge]` in `$CODEX_HOME/config.toml`. Its exact `env_vars` allowlist is
459
+ `CODEX_HOME`, `ENTWURF_DIR`, `PI_CODING_AGENT_DIR`, `ENTWURF_META_SESSIONS_DIR`,
460
+ `ENTWURF_META_MAILBOX_DIR`, `ENTWURF_META_SENDERS_DIR`, `ENTWURF_META_RECEIVERS_DIR`,
461
+ `TMUX`, and `TMUX_PANE`.
462
+ The status-line writer owns only `thread-title`. Foreign or symlinked config is refused.
463
+
464
+ Native delivery requires the operator-owned default app-server. The operator chooses the tmux
465
+ session that holds it: #95 D1 retired the requirement that the session be named `codex`, and Entwurf
466
+ neither creates nor supervises it. For the LIVE acceptance that session must NOT be the one the
467
+ Pi/Codex pair runs in.
468
+
469
+ ```bash
470
+ # Run from a pane in the operator-owned tmux session that will hold the app-server. One
471
+ # command: entwurf spells the vendor's `app-server --listen unix://<default socket>` for you,
472
+ # creates the control directory, and exec()s it here. Ctrl-C is yours; nothing supervises it.
473
+ entwurf codex-app-server
474
+
475
+ # Attach your own visible Codex TUI from wherever you work — any session, not a reserved room.
476
+ CODEX_HOME="${CODEX_HOME:-$HOME/.codex}"
477
+ codex --remote "unix://$CODEX_HOME/app-server-control/app-server-control.sock"
478
+ ```
479
+
480
+ Omitted fresh placement follows the CALLER on the caller's own tmux server: a Codex citizen opens
481
+ beside its own TUI pane, matched by the `thread-id` that pane's terminal title carries, with 0 or 2+
482
+ matching panes refused and no fallback. The app-server's `TMUX`/`TMUX_PANE` is still forwarded to its
483
+ MCP child, but it locates stores rather than a seat. It is **not** an arbitrary attached-TUI pane
484
+ join: exact Codex 0.153.4 source exposes no request→attached-TUI-seat carrier, so a pane whose thread
485
+ nobody named has no adjacency claim, and that wider topology stays unsupported and unclaimed. Entwurf
486
+ does not guess panes, create a tmux session, expose a generic app-server manager/API, or
487
+ start/restart the server. Custom
488
+ `CODEX_HOME` and Entwurf roots cross the same explicit `env_vars` boundary. Strict request `_meta`
489
+ identifies the thread caller, not its seat. Delivery uses one `codex queue` invocation with no retry.
490
+
491
+ The 2026-09-11 loaded-thread run remains pre-amendment native-push/identity evidence. The
492
+ first-admission acceptance passed on 2026-09-12 under the fixed-home topology #95 D1 later retired:
493
+ real initial Pi outside `codex`, then omitted-placement Codex and outbound Pi inside it, with exact
494
+ callbacks and addressed delivery both ways. `DELIVERY.md` owns that 57-assertion receipt, its four
495
+ coordinates, and its digest as history; the caller-seat re-run is pending.
496
+
497
+ `entwurf_fresh_call` accepts backend `codex` — supported in 0.21.0 — and requires an explicit model. Its
498
+ preflight must prove the safe birth closure, exact MCP/env atom, `thread-title`, and app-server
499
+ socket before tmux mutation. The sibling's garden id comes from the callback sender envelope,
500
+ never the launch receipt. There is no Codex resume surface, watcher, supervisor, or app-server
501
+ lifecycle ownership. The accepted LIVE used a real record-backed visible Pi for the first leg;
502
+ release-gate stripped ambient `PI_SESSION_ID`/`PI_AGENT_ID`, and the fixture/self-fetch citizen only
503
+ collected receipts. The smoke reported initial Pi, app-server, fresh Codex, and outbound Pi
504
+ coordinates separately: the initial Pi differed and the latter three matched the exact `codex` home,
505
+ which was the contract at that date. #95 D1 (2026-09-16) retired that room — an omitted seat is now the
506
+ caller's own session for every backend, and the reshaped card requires the app-server to sit in a
507
+ DIFFERENT session from the Pi/Codex pair, because that is what tells the caller-pane anchor apart from
508
+ the app-server's inherited environment.
509
+ Qualification and the frozen full floor are complete for this lane: `check-gate-qualification`
510
+ killed 475/475 across 43 lanes with origin purity green, `check:full` exited 0, and the LIVE release
511
+ gate reported MUST 24/0/0 with `cut: OK`. The bounds above are unchanged — no request→arbitrary
512
+ attached-TUI seat join, no resume surface, and macOS remains NOT CERTIFIED pending a physical host.
513
+
514
+ Oracle runs the first-admission LIVE with explicit ownership/model selectors:
515
+
516
+ ```bash
517
+ LIVE=1 \
518
+ ENTWURF_CODEX_APP_SERVER_PID=<existing-app-server-pid> \
519
+ ENTWURF_CODEX_FRESH_MODEL=<codex-model> \
520
+ ENTWURF_CODEX_FRESH_PI_MODEL=<pi-model> \
521
+ entwurf smoke-codex-fresh-live
522
+ ```
523
+
524
+ Run this from a tmux session OTHER than the one the app-server is in. The entrypoint name does not
525
+ waive the contract above. A fixture/self-fetch citizen may collect receipts but cannot replace the real
526
+ visible Pi leg; a Pi that starts in the app-server's own session proves only the weaker shared-seat
527
+ mechanism, because both the anchor and the env fallback would answer the same room.
528
+
399
529
  ## 5. Optional Antigravity native citizen
400
530
 
401
531
  This rail is CERTIFIED on Linux; on macOS it is NOT CERTIFIED — pending
@@ -464,7 +594,7 @@ rail receipt. Doctors refuse on Darwin until a physical-host receipt
464
594
  exists.
465
595
 
466
596
  If install or doctor reports an unreadable/old active citizen generation, do not edit
467
- records by hand. Close pi, Claude, Copilot, and agy sessions first, run
597
+ records by hand. Close pi, Claude, Copilot, OMP, agy, and Codex sessions first, run
468
598
  `entwurf meta-bridge-fresh-cut`, and read its exit status before any install. Then choose the
469
599
  installation mode you actually own:
470
600
 
@@ -496,6 +626,7 @@ PASS/SKIP/FAIL summary. The complete quiescence, archive, and exit-code contract
496
626
  - `scripts/raw-macos-measure/probe.sh`: physical-Darwin host facts (~3.5s,
497
627
  no install, no login). Measurement, not a certification path.
498
628
  - `doctor-meta-bridge`: one installed real Claude host, only with a new live session.
629
+ - `doctor-codex-{birth,mcp,statusline}` plus `LIVE=1 CODEX_LIVE_THREAD_ID=<id> entwurf smoke-codex-native-push-live`: one installed Codex host; the aggregate does not own a loaded thread.
499
630
  - `LIVE=1 entwurf release-gate /path/to/scratch --cut`: aggregate runtime acceptance (`--cut` makes any MUST SKIP red; without it the run is a diagnostic pass).
500
631
 
501
632
  Keep these verdicts separate. Current protocol is [VERIFY.md](../VERIFY.md); recorded
@@ -519,6 +650,9 @@ entwurf uninstall-copilot-bridge
519
650
  entwurf uninstall-agy-hooks
520
651
  entwurf uninstall-agy-statusline
521
652
  entwurf uninstall-agy-bridge
653
+ entwurf uninstall-codex-statusline
654
+ entwurf uninstall-codex-mcp
655
+ entwurf uninstall-codex-birth
522
656
  entwurf remove ~/entwurf-smoke
523
657
  # only when no other project uses the shared user-scope pi registration:
524
658
  entwurf remove-user-scope