@junghanacs/entwurf 0.20.0 → 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 (112) hide show
  1. package/AGENTS.md +85 -190
  2. package/BASELINE.md +6 -5
  3. package/CHANGELOG.md +220 -14
  4. package/CONTRIBUTING.md +1 -1
  5. package/DELIVERY.md +202 -60
  6. package/README.md +54 -26
  7. package/VERIFY.md +37 -7
  8. package/docs/acp-backend-rail.md +31 -15
  9. package/docs/external-mcp-host.md +58 -35
  10. package/docs/fresh-cut-policy.md +3 -2
  11. package/docs/setup-clean-host.md +110 -17
  12. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +101 -109
  13. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/backend-adapter.js +2 -2
  14. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/tool-surface.js +6 -0
  15. package/mcp/entwurf-bridge/dist/pi-extensions/lib/codex-fresh-preflight.js +326 -0
  16. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-fact-provider.js +22 -2
  17. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-peers-render.js +3 -1
  18. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-contract.js +6 -7
  19. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-native-push.js +30 -17
  20. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-production.js +7 -1
  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 +50 -15
  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/meta-bridge-fresh-cut.js +6 -1
  30. package/mcp/entwurf-bridge/src/index.ts +115 -111
  31. package/mcp/entwurf-bridge/tsconfig.build.json +1 -0
  32. package/package.json +14 -5
  33. package/pi-extensions/entwurf-control.ts +71 -19
  34. package/pi-extensions/lib/acp/acp-client.ts +3 -3
  35. package/pi-extensions/lib/acp/backend-adapter.ts +3 -3
  36. package/pi-extensions/lib/acp/backend.ts +3 -3
  37. package/pi-extensions/lib/acp/event-mapper.ts +4 -4
  38. package/pi-extensions/lib/acp/tool-surface.ts +6 -0
  39. package/pi-extensions/lib/codex-fresh-preflight.ts +363 -0
  40. package/pi-extensions/lib/compaction-send-guard.ts +80 -0
  41. package/pi-extensions/lib/entwurf-fact-provider.ts +29 -3
  42. package/pi-extensions/lib/entwurf-peers-render.ts +4 -1
  43. package/pi-extensions/lib/entwurf-v2-contract.ts +6 -7
  44. package/pi-extensions/lib/entwurf-v2-native-push.ts +35 -18
  45. package/pi-extensions/lib/entwurf-v2-production.ts +10 -3
  46. package/pi-extensions/lib/entwurf-v2-surface.ts +7 -4
  47. package/pi-extensions/lib/meta-mailbox-body.ts +22 -13
  48. package/pi-extensions/lib/meta-sender-identity.ts +305 -0
  49. package/pi-extensions/lib/mux-fresh-call.ts +64 -19
  50. package/pi-extensions/lib/native-push/adapter.ts +21 -24
  51. package/pi-extensions/lib/native-push/codex-ws-client.ts +506 -0
  52. package/pi-extensions/lib/native-push/register.ts +7 -9
  53. package/pi-extensions/meta-bridge-hook-codex.ts +371 -0
  54. package/run.sh +251 -35
  55. package/scripts/check-acp-usage-accounting.ts +9 -9
  56. package/scripts/check-agy-sender-identity.ts +1 -1
  57. package/scripts/check-codex-birth-hook.ts +264 -0
  58. package/scripts/check-codex-bridge-identity.ts +179 -0
  59. package/scripts/check-codex-native-push.ts +386 -0
  60. package/scripts/check-codex-sender-identity.ts +495 -0
  61. package/scripts/check-compaction-send-guard.ts +130 -0
  62. package/scripts/check-copilot-receive-arm.ts +4 -1
  63. package/scripts/check-entwurf-fact-provider.ts +38 -0
  64. package/scripts/check-entwurf-peers-surface.ts +13 -1
  65. package/scripts/check-entwurf-self-address.ts +15 -16
  66. package/scripts/check-entwurf-v2-contract.ts +4 -3
  67. package/scripts/check-entwurf-v2-decider.ts +7 -5
  68. package/scripts/check-entwurf-v2-native-push.ts +35 -7
  69. package/scripts/check-entwurf-v2-production.ts +203 -11
  70. package/scripts/check-entwurf-v2-runner.ts +1 -1
  71. package/scripts/check-entwurf-v2-surface.ts +1 -1
  72. package/scripts/check-gate-qualification.ts +7 -4
  73. package/scripts/check-harness-admission-parity.ts +0 -1
  74. package/scripts/check-install-surface.ts +23 -7
  75. package/scripts/check-mux-launch-tmux.ts +47 -2
  76. package/scripts/check-native-push-adapter.ts +20 -16
  77. package/scripts/check-native-push-register.ts +5 -1
  78. package/scripts/check-release-gate-outcomes.ts +47 -1
  79. package/scripts/check-setup-qualification.sh +3 -1
  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-statusline-config.py +434 -0
  85. package/scripts/codex_toml_io.py +532 -0
  86. package/scripts/lib/codex-fresh-live-protocol.ts +113 -0
  87. package/scripts/lib/codex-fresh-source-receipts.ts +399 -0
  88. package/scripts/lib/launch-receipt-windows.ts +46 -0
  89. package/scripts/lib/tmux-coordinate-row.ts +58 -0
  90. package/scripts/meta-bridge-fresh-cut.ts +6 -1
  91. package/scripts/mutants/codex-native.json +838 -0
  92. package/scripts/mutants/compaction-send-guard.json +103 -0
  93. package/scripts/mutants/entwurf-peers.json +19 -0
  94. package/scripts/mutants/mux-fresh-call.json +19 -8
  95. package/scripts/mutants/omp-fresh.json +6 -4
  96. package/scripts/mutants/pi-package-ownership.json +26 -0
  97. package/scripts/mutants/release-gate.json +13 -0
  98. package/scripts/mutants/v2-surface.json +53 -1
  99. package/scripts/pi_settings_io.py +3 -1
  100. package/scripts/raw-async-delivery/README.md +2 -1
  101. package/scripts/raw-codex-measure/README.md +114 -46
  102. package/scripts/register-pi-package.py +38 -30
  103. package/scripts/register-pi-provider.py +3 -2
  104. package/scripts/smoke-acp-raw-turn-live.ts +1 -1
  105. package/scripts/smoke-agy-native-push-live.ts +3 -1
  106. package/scripts/smoke-codex-birth.sh +347 -0
  107. package/scripts/smoke-codex-config-state.sh +511 -0
  108. package/scripts/smoke-codex-fresh-live.ts +1186 -0
  109. package/scripts/smoke-codex-native-push-live.ts +75 -0
  110. package/scripts/smoke-setup-verdict.sh +123 -10
  111. package/scripts/smoke-user-scope-citizen.sh +199 -71
  112. 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,37 @@ 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
+
137
+ entwurf doctor-codex-birth
138
+ entwurf doctor-codex-mcp
139
+ entwurf doctor-codex-statusline
140
+ ```
141
+
142
+ The birth unit owns a `SessionStart` declaration in `$CODEX_HOME/hooks.json`, which the vendor
143
+ runs only after the operator trusts it once in a visible Codex; the other two units own
144
+ only `[mcp_servers.entwurf-bridge]` and the `thread-title` status-line member. The MCP
145
+ entry carries `ENTWURF_BRIDGE_NATIVE_HOST=codex`, which tells the bridge to require and
146
+ reconcile Codex request `_meta`. Do not add the anonymous hatch.
147
+
148
+ Native receive and visible fresh require the operator-owned default app-server. Entwurf
149
+ does not start or supervise it:
150
+
151
+ ```bash
152
+ CODEX_HOME="${CODEX_HOME:-$HOME/.codex}"
153
+ codex app-server --listen "unix://$CODEX_HOME/app-server-control/app-server-control.sock"
137
154
  ```
138
155
 
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).
156
+ Birth occurs on the first turn, not window open. It mints
157
+ `record.nativeSessionId = threadId` and sets the visible thread title to the garden id.
158
+ `entwurf_v2` probes the loaded-thread list and sends once through `codex queue`; it never
159
+ retries. `entwurf_fresh_call` accepts `backend: "codex"` after all three owned units and
160
+ the default socket pass preflight. Codex remains outside ACP and has no resume surface.
140
161
 
141
162
  #### Antigravity CLI (`agy`)
142
163
 
@@ -169,6 +190,7 @@ Use the managed install surface rather than editing omp's files by hand:
169
190
  ```bash
170
191
  entwurf install-omp-bridge # the BIRTH extension (a garden id per visible TUI session)
171
192
  entwurf install-omp-mcp # the MCP hand (this section)
193
+ entwurf install-omp-config # the operator setting (tools.xdev: false)
172
194
  entwurf install-omp-receive # the RECEIVER extension (mailbox watch + announce-only doorbell)
173
195
 
174
196
  entwurf doctor-omp-bridge
@@ -176,7 +198,7 @@ entwurf doctor-omp-mcp
176
198
  entwurf doctor-omp-receive
177
199
  ```
178
200
 
179
- Three units, not two. The receiver is what makes the citizen answerable at all — without it omp
201
+ Four units, not two. The receiver is what makes the citizen answerable at all — without it omp
180
202
  sends under its own garden id and every reply is refused as `mailbox-undeliverable` — and it is
181
203
  also a prerequisite of visible fresh: the fresh preflight refuses this host before touching tmux
182
204
  when the receive unit is missing, rather than opening a window that can never be reached.
@@ -244,7 +266,8 @@ bridge registered, **11 devices**: omp's own `ast_edit`, `debug`, `lsp`, `browse
244
266
  seven `entwurf_*`. So the default does not merely wrap entwurf — it wraps omp's own LSP and
245
267
  debugger too.
246
268
 
247
- **Set this on any omp host that is meant to work as a citizen:**
269
+ **`entwurf setup` writes this for a detected omp host, and `entwurf install-omp-config` is
270
+ the repair leaf. The value they own:**
248
271
 
249
272
  ```yaml
250
273
  # ~/.omp/agent/config.yml
@@ -22,7 +22,8 @@ the new generation.
22
22
  ## What certification checks
23
23
 
24
24
  `certifyActiveStore` is shared by the doctor and all identity writers: pi birth,
25
- Claude's `SessionStart`, agy's imprint, and `entwurf_register_native`. Every active
25
+ Claude's `SessionStart`, Copilot's first-prompt birth hook, OMP's birth extension,
26
+ agy's imprint, and `entwurf_register_native`. Every active
26
27
  record must be:
27
28
 
28
29
  - a regular, non-symlink `.meta.json` file;
@@ -43,7 +44,7 @@ are opened without following symlinks; implementation and race proofs live besid
43
44
 
44
45
  ## Running a cut
45
46
 
46
- First close every pi, Claude Code, and agy session that could own a citizen or
47
+ First close every pi, Claude Code, Copilot, OMP, and agy session that could own a citizen or
47
48
  transport artifact. Then run:
48
49
 
49
50
  ```bash
@@ -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 three operator-owned units (birth/MCP/status-line) and stays non-green until the operator trusts the birth declaration once in a visible Codex; visible fresh also requires an operator-owned existing tmux session named `codex`, with its app-server and supported TUIs seated there | 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,92 @@ 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 three atoms, not the harness and not a daemon:
408
+
409
+ ```bash
410
+ # All three 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
+
415
+ entwurf doctor-codex-birth
416
+ entwurf doctor-codex-mcp
417
+ entwurf doctor-codex-statusline
418
+ ```
419
+
420
+ The birth atom publishes a `SessionStart` declaration into `$CODEX_HOME/hooks.json` with its
421
+ launcher closure under `$XDG_DATA_HOME/entwurf/codex-birth`; it mints a V3 record on the
422
+ thread's first turn and sets the thread title to the garden id.
423
+
424
+ **One step is the operator's, and no command can do it for them.** The vendor will not run a
425
+ user-layer hook until it has been trusted once: open a visible plain Codex, answer
426
+ `Trust all and continue`, then send one first turn. `doctor-codex-birth` reads the receipt the
427
+ vendor writes for that declaration and is RED without it — entwurf never writes, pre-seeds or
428
+ recomputes that value, because it is the operator's security decision, not ours. After the one
429
+ answer, later sessions raise no prompt and are born automatically. The MCP writer owns
430
+ `[mcp_servers.entwurf-bridge]` in `$CODEX_HOME/config.toml`. Its exact `env_vars` allowlist is
431
+ `CODEX_HOME`, `ENTWURF_DIR`, `PI_CODING_AGENT_DIR`, `ENTWURF_META_SESSIONS_DIR`,
432
+ `ENTWURF_META_MAILBOX_DIR`, `ENTWURF_META_SENDERS_DIR`, `ENTWURF_META_RECEIVERS_DIR`,
433
+ `TMUX`, and `TMUX_PANE`.
434
+ The status-line writer owns only `thread-title`. Foreign or symlinked config is refused.
435
+
436
+ Native delivery requires the operator-owned default app-server and one existing tmux session
437
+ named exactly `codex`. Start the app-server from a pane there and seat supported Codex TUIs there:
438
+
439
+ ```bash
440
+ # Run these commands inside the operator-owned tmux session named exactly `codex`.
441
+ CODEX_HOME="${CODEX_HOME:-$HOME/.codex}"
442
+ mkdir -p "$CODEX_HOME/app-server-control"
443
+ codex app-server --listen "unix://$CODEX_HOME/app-server-control/app-server-control.sock"
444
+ codex --remote "unix://$CODEX_HOME/app-server-control/app-server-control.sock"
445
+ ```
446
+
447
+ Omitted Codex fresh placement resolves exact `codex` on the caller's tmux server. The app-server's
448
+ `TMUX`/`TMUX_PANE` is forwarded to its MCP child, so a Codex citizen's omitted outbound Pi placement
449
+ stays in that home. It is **not** an arbitrary attached-TUI pane join. Exact Codex 0.153.4 source
450
+ exposes no request→attached-TUI-seat carrier, so clients attached from other sessions have no
451
+ adjacency claim; that wider topology is unsupported and unclaimed. Entwurf does not guess panes,
452
+ create the tmux home, expose a generic app-server manager/API, or start/restart the server. Custom
453
+ `CODEX_HOME` and Entwurf roots cross the same explicit `env_vars` boundary. Strict request `_meta`
454
+ identifies the thread caller, not its seat. Delivery uses one `codex queue` invocation with no retry.
455
+
456
+ The 2026-09-11 loaded-thread run remains pre-amendment native-push/identity evidence. The final
457
+ first-admission acceptance passed on 2026-09-12: real initial Pi outside `codex`, then omitted-placement
458
+ Codex and outbound Pi inside it, with exact callbacks and addressed delivery both ways. `DELIVERY.md`
459
+ owns the 57-assertion receipt, four coordinates, and digest.
460
+
461
+ `entwurf_fresh_call` accepts backend `codex` — supported in 0.21.0 — and requires an explicit model. Its
462
+ preflight must prove the safe birth closure, exact MCP/env atom, `thread-title`, and app-server
463
+ socket before tmux mutation. The sibling's garden id comes from the callback sender envelope,
464
+ never the launch receipt. There is no Codex resume surface, watcher, supervisor, or app-server
465
+ lifecycle ownership. The accepted LIVE used a real record-backed visible Pi for the first leg;
466
+ release-gate stripped ambient `PI_SESSION_ID`/`PI_AGENT_ID`, and the fixture/self-fetch citizen only
467
+ collected receipts. The smoke reported initial Pi, app-server, fresh Codex, and outbound Pi
468
+ coordinates separately: the initial Pi differed and the latter three matched the exact `codex` home.
469
+ Qualification and the frozen full floor are complete for this lane: `check-gate-qualification`
470
+ killed 475/475 across 43 lanes with origin purity green, `check:full` exited 0, and the LIVE release
471
+ gate reported MUST 24/0/0 with `cut: OK`. The bounds above are unchanged — no request→arbitrary
472
+ attached-TUI seat join, no resume surface, and macOS remains NOT CERTIFIED pending a physical host.
473
+
474
+ Oracle runs the first-admission LIVE with explicit ownership/model selectors:
475
+
476
+ ```bash
477
+ LIVE=1 \
478
+ ENTWURF_CODEX_APP_SERVER_PID=<existing-app-server-pid> \
479
+ ENTWURF_CODEX_FRESH_MODEL=<codex-model> \
480
+ ENTWURF_CODEX_FRESH_PI_MODEL=<pi-model> \
481
+ entwurf smoke-codex-fresh-live
482
+ ```
483
+
484
+ Run this from a tmux session other than `codex`. The entrypoint name does not waive the contract
485
+ above. A fixture/self-fetch citizen may collect receipts but cannot replace the real visible Pi leg;
486
+ a Pi that starts inside the Codex home proves only the weaker shared-seat mechanism.
487
+
399
488
  ## 5. Optional Antigravity native citizen
400
489
 
401
490
  This rail is CERTIFIED on Linux; on macOS it is NOT CERTIFIED — pending
@@ -464,7 +553,7 @@ rail receipt. Doctors refuse on Darwin until a physical-host receipt
464
553
  exists.
465
554
 
466
555
  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
556
+ records by hand. Close pi, Claude, Copilot, OMP, agy, and Codex sessions first, run
468
557
  `entwurf meta-bridge-fresh-cut`, and read its exit status before any install. Then choose the
469
558
  installation mode you actually own:
470
559
 
@@ -496,6 +585,7 @@ PASS/SKIP/FAIL summary. The complete quiescence, archive, and exit-code contract
496
585
  - `scripts/raw-macos-measure/probe.sh`: physical-Darwin host facts (~3.5s,
497
586
  no install, no login). Measurement, not a certification path.
498
587
  - `doctor-meta-bridge`: one installed real Claude host, only with a new live session.
588
+ - `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
589
  - `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
590
 
501
591
  Keep these verdicts separate. Current protocol is [VERIFY.md](../VERIFY.md); recorded
@@ -519,6 +609,9 @@ entwurf uninstall-copilot-bridge
519
609
  entwurf uninstall-agy-hooks
520
610
  entwurf uninstall-agy-statusline
521
611
  entwurf uninstall-agy-bridge
612
+ entwurf uninstall-codex-statusline
613
+ entwurf uninstall-codex-mcp
614
+ entwurf uninstall-codex-birth
522
615
  entwurf remove ~/entwurf-smoke
523
616
  # only when no other project uses the shared user-scope pi registration:
524
617
  entwurf remove-user-scope