@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/README.md CHANGED
@@ -10,7 +10,7 @@ npm package: <https://www.npmjs.com/package/@junghanacs/entwurf>
10
10
 
11
11
  Legacy package: [`@junghanacs/pi-shell-acp`](https://www.npmjs.com/package/@junghanacs/pi-shell-acp). `entwurf` is its 0.12+ successor line: the same work renamed around the garden-citizen dispatch substrate rather than the pi adapter.
12
12
 
13
- > **Repository shape.** This repo is **entwurf-core (v2 dispatch) + native-harness bridges + a pi adapter + an ACP plugin**. Pi is one supported harness adapter — important because it supplies control sockets and hosts the ACP plugin today — but it is not the project subject. Claude Code and GitHub Copilot CLI are shipped as mailbox-backed self-fetch meta-sessions; Antigravity (`agy`) is shipped as a native-push citizen with automatic `PreInvocation` birth, ambient garden-id status, and a managed MCP/permission install surface. OMP (`omp`) is a self-fetch citizen on the same rail as Claude and Copilot, opened by `entwurf_fresh_call` and accepted under the step 9 visible-fresh contract on 2026-08-30 its first turn is a two-stage in-process bootstrap rather than an argv prompt, because the vendor connects its MCP tools in the background after the session starts. Codex has a launch-mode-specific verified delivery probe documented in [DELIVERY.md](./DELIVERY.md), but no managed native-citizen install lane yet. The ACP plugin ships two backends through one adapter rail: Claude (the reference) and Snowflake Cortex Code (landed in 0.13.0 under the measured contract in [docs/acp-backend-rail.md](./docs/acp-backend-rail.md#cortex-code-audit-d1d10)).
13
+ > **Repository shape.** This repo is **entwurf-core (v2 dispatch) + native-harness bridges + a pi adapter + an ACP plugin**. Pi is one adapter, not the project subject. Claude Code, GitHub Copilot CLI, and OMP (`omp`) are shipped self-fetch citizens; Antigravity (`agy`) is shipped native-push. Codex CLI is a **native-push citizen, supported in 0.21.0 on Linux** over the operator-owned app-server: vendor-trusted user-scope birth, strict request-scoped identity, loaded-thread probe, one-shot queue delivery, and visible fresh in one explicit operator-owned tmux home named `codex`. Unrestricted attached-TUI seat inference is not supported or claimed; that non-goal no longer blocks the explicit home topology. Codex remains native so it keeps its vendor tools, delegation, and work context; this is not another way to buy or expose GPT through ACP.
14
14
 
15
15
  ```text
16
16
  Claude Code / Copilot / Codex / agy / omp / pi
@@ -19,7 +19,7 @@ Claude Code / Copilot / Codex / agy / omp / pi
19
19
  → control-socket | meta-mailbox | native-push
20
20
  ```
21
21
 
22
- [`entwurf_v2`](#entwurf_v2--canonical-dispatch-verb) is the canonical dispatch surface over *existing* garden citizens — live control-socket send, meta-mailbox enqueue, and native-push into a live Antigravity conversation. It starts no process on any rail: the hidden background resume that used to answer a dormant target was withdrawn under the visible-first rule, so a dormant citizen rejects honestly here and is reopened by a separate lifecycle verb, `entwurf_resume_call`. The meta-record is the sole address authority (#50 C4): a record-less control socket is refused as a `record-less-socket` diagnostic, never dispatched. The v1 entwurf verbs are gone. Fresh siblings and resumes are separate verbs — `entwurf_fresh_call` opens a NEW sibling in the operator's tmux the caller's own session, or one existing named session on the same server — and learns its garden id from the callback it makes, while `entwurf_resume_call` reopens a DORMANT pi citizen under its own garden id in a visible window without running a turn; the non-Claude ACP lane landed earlier Snowflake Cortex Code became the second backend in 0.13.0.
22
+ [`entwurf_v2`](#entwurf_v2--canonical-dispatch-verb) is the canonical dispatch surface over *existing* garden citizens — live control-socket send, meta-mailbox enqueue, and native-push into a live Antigravity conversation or loaded Codex thread. It starts no process on any rail. `entwurf_fresh_call` is separate lifecycle. With Codex placement omitted, it resolves the exact existing tmux session named `codex`; that operator-owned home holds the app-server and supported Codex TUIs. Missing home or app-server rejects before launch, and Entwurf never creates or supervises either. An explicit `placement.tmuxSession` remains an expert override. Exact 0.153.4 source confirms that arbitrary attached-TUI request→seat inference is unavailable, so Entwurf makes no such claim. Codex has no resume surface.
23
23
 
24
24
  **Garden id is deliberate vocabulary.** It is not a decorative synonym for session id, worker, delegate, or subagent. The unfamiliar word is a guard: each harness keeps its own identity and transcript, while `entwurf` supplies a narrow addressable surface between siblings.
25
25
 
@@ -60,10 +60,9 @@ native Antigravity / agy
60
60
  ```
61
61
 
62
62
  Claude's `install-meta-bridge`, Copilot's four `install-copilot-*` surfaces, agy's
63
- `install-agy-{bridge,statusline,hooks}` and OMP's four `install-omp-{bridge,mcp,config,receive}`
64
- units are distinct because their lifecycle and delivery transports are
65
- genuinely different. Codex remains verified probe evidence, not a shipped managed native-citizen
66
- lane; see [DELIVERY.md](./DELIVERY.md).
63
+ `install-agy-{bridge,statusline,hooks}`, OMP's four `install-omp-{bridge,mcp,config,receive}`,
64
+ and Codex's birth, MCP and statusline units are distinct because their
65
+ lifecycle and delivery transports are genuinely different. See [DELIVERY.md](./DELIVERY.md).
67
66
 
68
67
  > **Direction.** Inverse of [`pi-acp`](https://github.com/svkozak/pi-acp). `pi-acp` lets external ACP clients talk *to* pi; `entwurf` lets garden citizens talk across harness boundaries — with pi as one adapter, not the center.
69
68
 
@@ -72,7 +71,7 @@ lane; see [DELIVERY.md](./DELIVERY.md).
72
71
  A few words that look unusual for a coding tool.
73
72
 
74
73
  - **Entwurf** (기투, projection-of-self) — sibling sessions with their own runtime boundary. Not "delegate," not "worker," not "sub-agent." Opening a visible sibling (`entwurf_fresh_call`), live peer messaging (`entwurf_v2`) and reopening a dormant one (`entwurf_resume_call`) are first-class; the hidden background resume that preceded the last of those was withdrawn under the visible-first rule.
75
- - **Garden / garden id** — the garden is the shared address space where independent harness sessions become citizens without losing their own runtime or transcript. A garden id is the stable address of one such citizen (for pi, a garden-native session id like `YYYYMMDDTHHMMSS-<6hex>`; for native harnesses, a meta-session id minted from an authoritative lifecycle hook — Claude `SessionStart`, Copilot's first-prompt birth hook, agy `PreInvocation`, and for OMP an in-process extension bound to both session edges that mints only the visible `mode === "tui"` host). It is not a worker name and not proof that pi owns the session. The same-looking id may name a live control socket, a dormant pi record, a mailbox-backed native session, or a native-push conversation, so callers discover facts with `entwurf_peers` and deliver with `entwurf_v2` instead of choosing a transport by hand.
74
+ - **Garden / garden id** — the garden is the shared address space where independent harness sessions become citizens without losing their own runtime or transcript. A garden id is the stable address of one such citizen (for pi, a garden-native session id like `YYYYMMDDTHHMMSS-<6hex>`; for native harnesses, a meta-session id minted from an authoritative lifecycle hook — Claude `SessionStart`, Codex `SessionStart`, Copilot's first-prompt birth hook, agy `PreInvocation`, and for OMP an in-process extension bound to both session edges that mints only the visible `mode === "tui"` host). It is not a worker name and not proof that pi owns the session. The same-looking id may name a live control socket, a dormant pi record, a mailbox-backed native session, or a native-push conversation/thread, so routing always reads the record and live rail facts first.
76
75
  - **Engraving** — optional short operator text delivered through each backend's native identity carrier. Not a giant hidden prompt, not a tool catalog.
77
76
  - **MCP** — in this repo, MCP is just the transport by which ACP-backed sessions receive pi capabilities that native pi exposes directly as extensions. It is not a general MCP platform. Explicit `entwurfProvider.mcpServers` only; no ambient `~/.mcp.json` scanning, no automatic retrieval. The same `entwurf-bridge` entry can also be wired into another host's MCP catalog (Claude Code, Copilot, Codex, Antigravity, OMP, …) when the operator chooses. `entwurf_self` returns an authoritative pi-session or trusted meta-session identity envelope; `entwurf_v2` requires an authoritative sender by default (#50 C4) — a plain external MCP host with no identity lane is refused unless the operator explicitly wires the documented anonymous hatch, and even then it is never replyable.
78
77
  - **Session persistence** — re-attaches pi to the same remote ACP session. Does not hydrate backend transcripts into pi history.
@@ -81,9 +80,15 @@ A few words that look unusual for a coding tool.
81
80
 
82
81
  **Platform evidence, in one breath.** The Entwurf-only install surface on
83
82
  macOS is CERTIFIED (CI) (`macos-install-surface`). On macOS every
84
- garden-native harness rail (pi, Claude, Copilot, OMP, agy), marker join,
85
- ACP turn, and mux is NOT CERTIFIED pending physical host; on Linux those
86
- same rails are the certified axis. native Windows is UNSUPPORTED.
83
+ garden-native harness rail, marker/request join, ACP turn, and mux is NOT CERTIFIED —
84
+ pending physical host. On Linux the released harness rails are the certified axis;
85
+ Codex joined them in 0.21.0. Its explicit-home first admission was measured on 2026-09-12 — a real Pi
86
+ outside `codex` opened Codex and outbound Pi inside it, with exact callbacks and delivery both ways —
87
+ and the release acceptance that followed closed `check:full`, the LIVE release gate at MUST 24/0/0,
88
+ and a 475/475 qualification.
89
+ Unrestricted attached-TUI placement
90
+ is unsupported rather than a release blocker.
91
+ Native Windows is UNSUPPORTED.
87
92
  CERTIFIED (CI) is weaker than a physical-host doctor green.
88
93
  The npm package has no `os` restriction; that is installability, not a
89
94
  support claim.
@@ -243,15 +248,18 @@ SKIP while the detected harnesses are composed.
243
248
  ### Native harness repair and doctors
244
249
 
245
250
  A plain MCP registration exposes the bridge tools; a **garden-native** session also
246
- needs entwurf's lifecycle hook and identity marker. `setup` already composes all of that
247
- for every harness it detects you do not paste this list to install. This is the repair
248
- surface: each unit has its own installer, its own doctor with a named refusal, and its own
249
- inverse, so a single broken unit can be redone without touching the rest.
251
+ needs entwurf's lifecycle and identity unit. `setup` composes every user-scope unit
252
+ for each detected harness, including Codex's birth hook its paths are the operator's
253
+ own, so nothing here asks for root. What setup cannot do is answer the vendor's one-time
254
+ hook-trust prompt: only the operator can, in their own visible Codex, so a first setup on
255
+ a Codex host writes every byte and stays honestly non-green until they have. This is the
256
+ repair surface: each unit has its own installer, doctor, and inverse.
250
257
 
251
258
  - **Claude Code** (Linux CERTIFIED; macOS NOT CERTIFIED — pending physical host) — `install-meta-bridge`, `doctor-meta-bridge`.
252
259
  - **Antigravity / agy** (Linux CERTIFIED; macOS NOT CERTIFIED — pending physical host) — `install-agy-bridge`, `install-agy-statusline`, `install-agy-hooks`, each with a matching `doctor-agy-*`.
253
260
  - **GitHub Copilot CLI** (Linux CERTIFIED; macOS NOT CERTIFIED — pending physical host) — four independent units, four independent failure modes: `install-copilot-bridge` (birth: garden id + who-sent, on the first prompt), `install-copilot-mcp` (the entwurf tool hand, where `entwurf_inbox_read` lives), `install-copilot-receive` (the receiver extension: doorbell + receiver marker), `install-copilot-statusline` (optional for a manual citizen, required for supported fresh) — each with a matching `doctor-copilot-*` and `uninstall-copilot-*`.
254
261
  - **OMP (`omp`)** (Linux CERTIFIED; macOS NOT CERTIFIED — pending physical host) — four units, in-process extensions rather than launchers: `install-omp-bridge` (birth: the `mode === "tui"` visible host, its garden id on the status line, and who-sent), `install-omp-mcp` (the omp-native `entwurf-bridge` entry), `install-omp-config` (the one operator setting `tools: xdev: false`, without which the vendor mounts MCP tools as `xd://` devices the model cannot call), `install-omp-receive` (the receiver extension: mailbox watch + announce-only doorbell) — each with a matching `uninstall-omp-*`, and a `doctor-omp-*` for all but the setting, whose runtime axis `doctor-omp-mcp` owns. The setting writer owns exactly the lines it adds and refuses an explicit operator `tools: xdev: true` by name rather than overwriting it.
262
+ - **OpenAI Codex CLI** (supported in 0.21.0 on Linux; macOS NOT CERTIFIED — pending physical host) — `install-codex-birth` publishes the `SessionStart` declaration into `$CODEX_HOME/hooks.json` with its launcher closure under `$XDG_DATA_HOME/entwurf/codex-birth`, all operator-owned; the vendor trust receipt for that declaration is the operator's single answer, and `doctor-codex-birth` reports it as its own axis (present, or red with the exact instruction — never computed, never written); `install-codex-mcp` owns `[mcp_servers.entwurf-bridge]`, including the `env_vars` boundary for `CODEX_HOME`, Entwurf garden/control roots, and the app-server's `TMUX`/`TMUX_PANE`; `install-codex-statusline` owns `thread-title`. Each has a matching doctor and inverse. The operator owns one existing tmux session named `codex`, starts the app-server there, and seats supported Codex TUIs there. Omitted Codex fresh placement selects that home; Entwurf never creates or supervises the session/app-server and never discovers arbitrary attached-TUI seats. An explicit placement is an expert override. Exact 0.153.4 source's missing per-client carrier bounds the unsupported topology; it does not block this explicit home deployment.
255
263
 
256
264
  Run them as `entwurf <command>`. Which unit a doctor's refusal names, and the clean-host
257
265
  walk-through for each harness, live in [docs/setup-clean-host.md](./docs/setup-clean-host.md).
@@ -336,9 +344,9 @@ host evidence boundaries are [VERIFY.md](./VERIFY.md) and [BASELINE.md](./BASELI
336
344
 
337
345
  The curated model registry exposes unprefixed Claude ids — `claude-opus-5`, `claude-sonnet-5`,
338
346
  `claude-fable-5-1` — plus the `cortex-` rows below.
339
- Codex is not an ACP backend or a shipped managed citizen lane: it has verified
340
- native-delivery probe evidence only. No managed Codex support is claimed; lifecycle,
341
- identity, installation, and doctors remain prerequisites for any such lane.
347
+ Codex is not an ACP backend. Its native-citizen lane, supported in 0.21.0, is independent so the
348
+ session retains Codex's native tools, delegation, and work context: trust-gated hook birth, strict
349
+ request `_meta` identity, app-server probe, one-shot native-push, and visible fresh.
342
350
 
343
351
  **Snowflake Cortex Code is the second ACP backend** (contract and audit:
344
352
  [docs/acp-backend-rail.md](./docs/acp-backend-rail.md#cortex-code-audit-d1d10)). Curated ids are
@@ -444,8 +452,10 @@ The Claude ACP backend keeps its native model / API / tools; entwurf shapes only
444
452
 
445
453
  **Claude** uses `_meta.systemPrompt` for the engraving carrier (kept short and pure — billing-safe; rich operator context rides the first user message instead, see [Context carriers](#context-carriers)) and `CLAUDE_CONFIG_DIR` for a whitelist overlay so auth/runtime entries stay available while operator memory, hooks, agents, history, local settings, and project memory remain hidden. The overlay writes an explicit empty `hooks: {}` because Claude SDK organic compaction needs the configured-empty shape; no operator hook definitions are inherited. It also pins `permissions.defaultMode: "bypassPermissions"` so an unattended ACP turn cannot suspend on an interactive permission prompt; explicit `tools` / `disallowedTools` still constrain the callable surface and backend authentication remains the operator's. The four-tool baseline is `Read`, `Bash`, `Edit`, and `Write`; `permissionAllow` carries their allow declarations, and `Skill` is added automatically when `skillPlugins` is non-empty. Operator context cap override: `ENTWURF_ACP_CLAUDE_CONTEXT=<int>`.
446
454
 
447
- Codex is not an ACP backend here. Its native delivery probe remains separate from
448
- the governed ACP adapter rail and does not yet constitute a managed garden citizen.
455
+ Codex is not an ACP backend here. Its native-push lane, supported in 0.21.0, uses `SessionStart`
456
+ for birth, strict request `_meta` for who-sent, and a loaded app-server thread as the live route.
457
+ Its supported visible topology is one operator-owned existing tmux home named `codex`; omitted
458
+ Codex fresh placement selects it, while unrestricted attached-TUI seat inference is not claimed.
449
459
 
450
460
  Antigravity is also not an ACP backend. It is a native-push citizen: `PreInvocation` supplies birth/sender identity, `entwurf_v2` probes and direct-injects replies into the live conversation, and no mailbox/receiver marker is involved.
451
461
 
@@ -461,6 +471,9 @@ pnpm run check:full # full deterministic floor (adds the her
461
471
  ./run.sh smoke-agy-statusline-state # agy ambient garden-id install surface
462
472
  ./run.sh smoke-agy-hooks-state # agy PreInvocation birth hook
463
473
  ./run.sh check-agy-sender-identity # record-backed pid/start-key sender identity
474
+ ./run.sh check-codex-native-push # deterministic Codex UDS probe + no-replay queue contract
475
+ ./run.sh smoke-codex-config-state # Codex MCP/status-line install, doctor, and inverse
476
+ ./run.sh check-codex-birth-hook # sandboxed payload + declaration: one V3 per top-level thread, the envelope's own axes, no markers
464
477
 
465
478
  # source-maintainer only — qualification snapshots the git work surface, and both
466
479
  # commands are source-contract gates rather than installed operator checks:
@@ -471,6 +484,20 @@ pnpm run check:full # full deterministic floor (adds the her
471
484
  # agy LIVE acceptance — requires an already-running conversation:
472
485
  LIVE=1 AGY_CONVERSATION_ID=<id> ./run.sh smoke-agy-native-push-live
473
486
 
487
+ # Codex loaded-thread probe (pre-amendment evidence; not first-release acceptance):
488
+ LIVE=1 CODEX_LIVE_THREAD_ID=<threadId> ./run.sh smoke-codex-native-push-live
489
+
490
+ # Codex explicit-home acceptance. Run from a DIFFERENT tmux session; the operator-owned
491
+ # app-server and supported Codex TUIs sit in the existing exact `codex` session. Record
492
+ # initial Pi, app-server, fresh Codex, and outbound Pi coordinates separately:
493
+ LIVE=1 ENTWURF_CODEX_APP_SERVER_PID=<existing-app-server-pid> \
494
+ ENTWURF_CODEX_FRESH_MODEL=<codex-model> \
495
+ ENTWURF_CODEX_FRESH_PI_MODEL=<pi-model> \
496
+ ./run.sh smoke-codex-fresh-live
497
+ # This accepts only a real visible Pi → visible Codex → visible Pi run: initial Pi outside
498
+ # the Codex home, then Codex and its outbound Pi inside it. A fixture or self-fetch first leg
499
+ # may collect receipts but does not satisfy the topology. No arbitrary attached-TUI parity is claimed.
500
+
474
501
  # ACP plugin LIVE acceptance — need the operator's local Claude auth/credit:
475
502
  LIVE=1 ./run.sh smoke-acp-socket-citizen-live # turn-free socket citizenship (S1)
476
503
  LIVE=1 ./run.sh smoke-acp-raw-turn-live # pinned ACP pipe + raw 1 turn (S2a)
@@ -545,7 +572,7 @@ A two-pane recording covers the pre-0.12 v1 surface end-to-end — sibling resum
545
572
 
546
573
  Live peer messaging carries a sender envelope `{ sessionId, agentId, cwd, timestamp, origin?, replyable? }`; `entwurf_self` returns that authoritative envelope for the current pi session or trusted meta-session. Plain external MCP hosts are non-replyable. A garden-native meta-session carries a trusted `meta-session` envelope, but **`replyable` is a fact its own rail decides, not a consequence of being trusted** — a self-fetch citizen needs a live armed receiver, a native-push citizen needs an alive adapter probe, and a pi session needs its control socket. `entwurf_self` also reports which rail a meta-session reply would ride, because a native-push citizen has no mailbox to name. `wants_reply` is an etiquette marker rendered as a `(wants reply)` badge — not a transport contract, no wait, no polling. **v2 never gates on it:** a `wants_reply` from an external/non-replyable caller is passed through and surfaced honestly beside that sender's `replyable: false`, not rejected — the decider routes on target + intent, never on sender replyability. (The retired v1 `entwurf_send` did reject it; that behaviour went with the verb.)
547
574
 
548
- In ACP-backed and external native-harness sessions, `entwurf-bridge` exposes seven tools: `entwurf_v2`, `entwurf_peers`, `entwurf_fresh_call`, `entwurf_resume_call`, `entwurf_self`, `entwurf_inbox_read`, and the explicit/manual `entwurf_register_native` fallback. Native pi exposes the shared capability directly through the extension surface (`entwurf_v2`, `entwurf_peers`, `entwurf_fresh_call`, `entwurf_resume_call` tools; the socket-scan `/entwurf-sessions` command is gone — #50 C4). **For garden-id delivery/reply use `entwurf_v2`** — the canonical surface that classifies the target and routes to live-pi / self-fetch meta-mailbox (Claude Code, Copilot, OMP) / Antigravity-native-push, and refuses a dormant target instead of waking it. **To open a sibling that does not exist yet use `entwurf_fresh_call`** — it launches one fixed backend (`pi`, `claude-code`, `copilot`, or `omp`) as a visible window on the operator's own tmux server, requires an explicit model, accepts one optional literal absolute `cwd` (omit it or pass `""` to use the caller's cwd) and one optional `placement.tmuxSession` naming an EXISTING session on that server to open the sibling in instead of the caller's own (the operator's project seat — nothing is ever created: an absent session is `tmux-session-missing` and a name outside the addressed grammar `[A-Za-z0-9][A-Za-z0-9_-]*` is `tmux-session-name-invalid`, with no window and no session left behind. The two inputs never infer each other, and the positive form of that matters more than the prohibition: **with `cwd` omitted, a seated sibling starts in THIS agent's own working directory**, measured — it does not inherit the target session's path or its active pane, so naming the `org` seat does not put the sibling in the `org` project. Pass `cwd` when you want a different directory. entwurf never resolves that path: it hands the literal string to tmux, so with a SYMLINKED directory the receipt echoes what you asked for while the record, the transcript path, the callback envelope and `pane_current_path` all show the path the OS resolved it to — two spellings of one place, and the resolved one is what `entwurf_resume_call` later reopens), passes the model and selected directory through the runtime's visible launch path, and hands it a first task; a `copilot` launch goes through entwurf's own managed invocation and is refused before any window opens if this host lacks the Copilot birth, MCP, receiver or visible-footer units, and an `omp` launch carries its task in the `--entwurf-bootstrap` payload instead of an argv prompt because that vendor connects its MCP tools after the session has already started. The sibling's first action is a nonce callback whose sender envelope carries its garden id. The launch receipt records the requested model/cwd/seat plus tmux coordinates — including the RESOLVED target session id, which is where the window actually is — and never claims that the runtime accepted them or completed delivery. (The v1 verbs `entwurf` / `entwurf_resume` / `entwurf_send` are gone.) Garden-native operator commands require `--entwurf-control`. There is no spawn target allowlist — the target registry is gone (#50 C3): `entwurf_v2` and `entwurf_resume_call` address an existing record-backed citizen, while `entwurf_fresh_call` takes its explicit backend/model/task and optional cwd directly rather than resolving a model tuple from a registry file. **To reopen a DORMANT pi citizen use `entwurf_resume_call {target}`** — the record supplies transcript, model, provider and cwd, so it takes no prompt, no task and no model override; it runs no turn, returns a LAUNCH receipt and a separate OBSERVATION receipt (only the second says the citizen is back), refuses a non-pi target as `target-not-pi`, and on an unobserved socket leaves the visible window open and releases its lock rather than retrying.
575
+ In ACP-backed and external native-harness sessions, `entwurf-bridge` exposes seven tools: `entwurf_v2`, `entwurf_peers`, `entwurf_fresh_call`, `entwurf_resume_call`, `entwurf_self`, `entwurf_inbox_read`, and the explicit/manual `entwurf_register_native` fallback. Native pi exposes the shared capability directly through the extension surface (`entwurf_v2`, `entwurf_peers`, `entwurf_fresh_call`, `entwurf_resume_call` tools; the socket-scan `/entwurf-sessions` command is gone — #50 C4). **For garden-id delivery/reply use `entwurf_v2`** — the canonical surface that classifies the target and routes to live-pi / self-fetch meta-mailbox (Claude Code, Copilot, OMP) / native-push (Antigravity, Codex), and refuses a dormant target instead of waking it. **To open a sibling that does not exist yet use `entwurf_fresh_call`** — it launches one of five fixed backends (`pi`, `claude-code`, `copilot`, `omp`, `codex`) as a visible window on the operator's own tmux server, requires an explicit model, accepts one optional literal absolute `cwd` (omit it or pass `""` to use the caller's cwd) and one optional `placement.tmuxSession` naming an EXISTING session on that server to open the sibling in instead of the caller's own (the operator's project seat — nothing is ever created: an absent session is `tmux-session-missing` and a name outside the addressed grammar `[A-Za-z0-9][A-Za-z0-9_-]*` is `tmux-session-name-invalid`, with no window and no session left behind. The two inputs never infer each other, and the positive form of that matters more than the prohibition: **with `cwd` omitted, a seated sibling starts in THIS agent's own working directory**, measured — it does not inherit the target session's path or its active pane, so naming the `org` seat does not put the sibling in the `org` project. Pass `cwd` when you want a different directory. entwurf never resolves that path: it hands the literal string to tmux, so with a SYMLINKED directory the receipt echoes what you asked for while the record, the transcript path, the callback envelope and `pane_current_path` all show the path the OS resolved it to — two spellings of one place, and the resolved one is what `entwurf_resume_call` later reopens), passes the model and selected directory through the runtime's visible launch path, and hands it a first task; a `copilot` launch goes through entwurf's own managed invocation and is refused before any window opens if this host lacks the Copilot birth, MCP, receiver or visible-footer units, and an `omp` launch carries its task in the `--entwurf-bootstrap` payload instead of an argv prompt because that vendor connects its MCP tools after the session has already started. Omitted placement is now a per-backend policy rather than one rule: `pi`, `claude-code`, `copilot` and `omp` open in the CALLER's own seat, while `codex` resolves the exact existing operator-owned `codex` session (a missing home rejects; nothing is created). 0.21.0 is the first release to carry such a backend-selected default — every backend could already be sent to an operator-owned existing seat with an explicit `placement.tmuxSession`, and named `claude`/`pi` seats remain available that way, but they are not omitted-placement defaults. Read tmux here as the VISIBLE LAUNCH SEAT, never as an address: the garden id is the address, and it arrives in the callback envelope. Sibling symmetry means each backend keeps its own transcript, auth, native tools and visibility — not that the transports are made identical. The sibling's first action is a nonce callback whose sender envelope carries its garden id. The launch receipt records the requested model/cwd/seat plus tmux coordinates — including the RESOLVED target session id, which is where the window actually is — and never claims that the runtime accepted them or completed delivery. (The v1 verbs `entwurf` / `entwurf_resume` / `entwurf_send` are gone.) Garden-native operator commands require `--entwurf-control`. There is no spawn target allowlist — the target registry is gone (#50 C3): `entwurf_v2` and `entwurf_resume_call` address an existing record-backed citizen, while `entwurf_fresh_call` takes its explicit backend/model/task and optional cwd directly rather than resolving a model tuple from a registry file. **To reopen a DORMANT pi citizen use `entwurf_resume_call {target}`** — the record supplies transcript, model, provider and cwd, so it takes no prompt, no task and no model override; it runs no turn, returns a LAUNCH receipt and a separate OBSERVATION receipt (only the second says the citizen is back), refuses a non-pi target as `target-not-pi`, and on an unobserved socket leaves the visible window open and releases its lock rather than retrying.
549
576
 
550
577
  ### `entwurf_v2` — canonical dispatch verb
551
578
 
@@ -562,9 +589,9 @@ In ACP-backed and external native-harness sessions, `entwurf-bridge` exposes sev
562
589
  | dead / indeterminate native-push conversation | fire-and-forget | **reject** (`native-push-target-dead` / `native-push-probe-indeterminate`) |
563
590
  | record-less control socket (no meta-record) | any | **reject** (`record-less-socket` — pre-probe; diagnostic state, #50 C4) |
564
591
 
565
- **`entwurf_v2` is the canonical surface for garden-id delivery.** When you have a garden id and want to reach whoever it names — message, reply, or hand-off — `entwurf_v2` is the one surface that reads whether the target is live pi, dormant pi, mailbox-backed Claude Code, Copilot or OMP, or native-push Antigravity and routes correctly; *when unsure which transport, use `entwurf_v2`*. This prevents callers from guessing a rail from the shape of an id.
592
+ **`entwurf_v2` is the canonical surface for garden-id delivery.** When you have a garden id and want to reach whoever it names — message, reply, or hand-off — `entwurf_v2` is the one surface that reads whether the target is live pi, dormant pi, mailbox-backed Claude Code, Copilot or OMP, or native-push Antigravity or Codex, and routes correctly; *when unsure which transport, use `entwurf_v2`*. This prevents callers from guessing a rail from the shape of an id.
566
593
 
567
- What v2 provides is a **deterministic dispatch substrate** that moves the "which transport?" decision out of the fallible caller/model and into the decider, with transport-appropriate locking and an honest reject (no `✓ delivered`, no `.msg` garbage) when a target cannot receive. It still does **not** mint siblings, and it does not relaunch one either: every row above either reaches a citizen that is already running or refuses. Reopening a dormant pi citizen is `entwurf_resume_call`, a separate lifecycle verb that never routes through this decider. Fresh creation is the separate `entwurf_fresh_call` verb. It opens one fixed Pi, Claude Code, Copilot or OMP runtime visibly on the caller's tmux server its own session by default, or one EXISTING named session via the optional `placement.tmuxSession` with a required explicit model passed in that runtime's measured CLI dialect and one optional literal absolute `cwd`; omitted or `""` means the caller's cwd. Copilot opens through `entwurf copilot`, never the bare vendor; OMP is the opposite — the bare `omp` runtime with no positional prompt at all, because that vendor connects its MCP tools after the session has started, so the task rides a two-stage `--entwurf-bootstrap` payload the installed birth extension releases once the callback tool has actually answered. It returns only a synchronous launch receipt and lets the sibling report its new address asynchronously through the sender envelope of a nonce callback. Use this cwd input for a new cross-repository sibling; do not resume a dormant citizen as a cwd substitute. The meta-mailbox row requires an **active** self-fetch receiver; native-push requires a record-backed, probe-alive native conversation and never borrows mailbox state. The [mux launch lane](./docs/mux-launch-rail.md) owns placement, fixed-runtime launch, and the two narrow compositions above it (fresh-call and resume-call placement); delivery does not import launch, and mux is not a delivery transport.
594
+ What v2 provides is a **deterministic dispatch substrate** that moves the "which transport?" decision out of the fallible caller/model and into the decider, with transport-appropriate locking and an honest reject (no `✓ delivered`, no `.msg` garbage) when a target cannot receive. It still does **not** mint siblings, and it does not relaunch one either: every row above either reaches a citizen that is already running or refuses. Reopening a dormant pi citizen is `entwurf_resume_call`, a separate lifecycle verb that never routes through this decider. Fresh creation is the separate `entwurf_fresh_call` verb. It opens one of five fixed runtimes — Pi, Claude Code, Copilot, OMP or Codex visibly on the caller's tmux server. Omitting placement is a per-backend policy: the first four open in the caller's own session, while Codex resolves the exact EXISTING operator-owned `codex` home and rejects if it is absent. An explicit `placement.tmuxSession` sends ANY of the five to an existing named session instead (see the `entwurf_fresh_call` paragraph above for the owning detail). Each launch carries a required explicit model passed in that runtime's measured CLI dialect and one optional literal absolute `cwd`; omitted or `""` means the caller's cwd. Copilot opens through `entwurf copilot`, never the bare vendor; OMP is the opposite — the bare `omp` runtime with no positional prompt at all, because that vendor connects its MCP tools after the session has started, so the task rides a two-stage `--entwurf-bootstrap` payload the installed birth extension releases once the callback tool has actually answered. It returns only a synchronous launch receipt and lets the sibling report its new address asynchronously through the sender envelope of a nonce callback. Use this cwd input for a new cross-repository sibling; do not resume a dormant citizen as a cwd substitute. The meta-mailbox row requires an **active** self-fetch receiver; native-push requires a record-backed, probe-alive native conversation and never borrows mailbox state. The [mux launch lane](./docs/mux-launch-rail.md) owns placement, fixed-runtime launch, and the two narrow compositions above it (fresh-call and resume-call placement); delivery does not import launch, and mux is not a delivery transport.
568
595
 
569
596
  A live pi target is *reached* over its control socket, but the socket is dispatch-internal transport, never identity (#50 C4). A control socket that no meta-record claims — a pre-record-era resident, an unreadable store, or a stale/planted file — is refused for **every** intent as `record-less-socket`, and the reject names the fix (restart the resident so `session_start` births its record, or quiesce and run the fresh-cut). `entwurf_peers` reports the same state as an aggregated `record-less-socket` diagnostic rather than a peer row.
570
597
 
package/VERIFY.md CHANGED
@@ -7,8 +7,14 @@ and the manual judgements a gate cannot make.
7
7
  > **Current surface.** `entwurf-bridge` exposes `entwurf_v2`, `entwurf_peers`,
8
8
  > `entwurf_fresh_call`, `entwurf_resume_call`, `entwurf_self`, `entwurf_inbox_read`, and `entwurf_register_native`. The ACP
9
9
  > backends are Claude and Snowflake Cortex Code. Antigravity is a separate shipped
10
- > native-push citizen lane; Codex has delivery-probe evidence but no managed citizen
11
- > lane. Retired v1 verbs and bridge implementations belong only in CHANGELOG/git.
10
+ > native-push citizen lane. Codex is a native-push citizen supported in 0.21.0, whose birth hook is
11
+ > operator-owned and gated on one vendor trust receipt, with strict request-scoped identity and
12
+ > visible fresh; it remains outside ACP and has no resume.
13
+ > The amended candidate carries its own full acceptance: standalone LIVE 48 assertions exit 0,
14
+ > `check:full` exit 0, and the LIVE release gate `--cut` at MUST 24/0/0 with `check-gate-qualification`
15
+ > killing 475/475 and both purity axes green. Earlier focused, 460-mutant, and 57-assertion receipts
16
+ > stay their own pre-amendment axes and are not promoted across it.
17
+ > Retired v1 verbs and bridge implementations belong only in CHANGELOG/git.
12
18
 
13
19
  This is a working protocol, not a metrics ledger. Per-run counts, digests, and release
14
20
  chronology belong in [BASELINE.md](./BASELINE.md), CHANGELOG, and release artifacts.
@@ -64,6 +70,27 @@ Verification here is not a benchmark. In production we exchange short turns and
64
70
  >
65
71
  > The aggregate release gate does not own a live agy conversation id, so agy's real native-push round trip is a separate acceptance axis: three fail-loud doctors plus `LIVE=1 AGY_CONVERSATION_ID=<id> ./run.sh smoke-agy-native-push-live`, followed by a fresh-conversation sender/reply check after package install. Its deterministic install/sender gates are already inside `pnpm run check:full`; do not misreport the aggregate gate as live agy evidence. **Cost fence:** the agy conversation this smoke drives runs on a free account, so open it on `gemini-3.6-flash` — never a Pro tier. The model is the operator's choice at conversation-open time; entwurf never selects it, and no assertion reads it (see the shipped-lane note: model display is not part of the agy contract).
66
72
  >
73
+ > Aggregate release-gate does not own a loaded Codex thread. Codex native-push remains an
74
+ > on-demand host axis. The final first-admission acceptance passed on 2026-09-12 with the
75
+ > explicit home topology; `DELIVERY.md` owns its 57-assertion receipt and digest. Re-runs start
76
+ > from a tmux session other than `codex`; the operator-owned
77
+ > app-server and supported Codex TUIs sit in the existing exact `codex` session; omitted Codex
78
+ > placement resolves there; and the Codex-opened Pi stays there. Exact `rust-v0.153.4` source
79
+ > exposes no arbitrary request→attached-TUI-seat value, so that wider topology is unsupported
80
+ > and unclaimed rather than a blocker to the explicit home.
81
+ >
82
+ > The A cell takes no inferred server or models:
83
+ > `LIVE=1 ENTWURF_CODEX_APP_SERVER_PID=<existing-app-server-pid>
84
+ > ENTWURF_CODEX_FRESH_MODEL=<codex-model>
85
+ > ENTWURF_CODEX_FRESH_PI_MODEL=<pi-model> ./run.sh smoke-codex-fresh-live`.
86
+ >
87
+ > The command name is not proof. The first leg must be a real record-backed visible Pi
88
+ > citizen outside the Codex home opening Codex; if a fixture or self-fetch receipt collector
89
+ > is what OPENS Codex, the gate does not satisfy the topology. A fixture that only COLLECTS
90
+ > receipts beside that real Pi leg is not the disqualifier — substituting for the leg is (see
91
+ > `docs/adding-a-harness.md`, the same wording). The receipt must separately name the initial
92
+ > Pi, app-server, fresh Codex, and outbound Pi coordinates.
93
+ >
67
94
  > Authoritative per-cut counts and digests live in BASELINE/CHANGELOG, not inline
68
95
  > here; embedding them in the protocol makes a correct guide stale after every cut.
69
96
 
@@ -81,6 +108,7 @@ Do not collapse source, package, fixture, and native-host evidence into one “g
81
108
  | LIVE runtime | `LIVE=1 ./run.sh release-gate <scratch> --cut` plus any shipped on-demand backend axis | `--cut` enforces `SKIP=0`; a red wired gate blocks the cut. |
82
109
  | Native Claude host | installed strict doctor against a new real session | Missing live join is `NOT CERTIFIED`, not a fixture PASS. |
83
110
  | Native agy host | three doctors plus conversation-id-gated native-push round trip | Aggregate release-gate does not own an agy conversation id. |
111
+ | Native Codex host | explicit-home first admission accepted 2026-09-12 | Operator-owned birth/MCP/statusline atoms and the on-demand four-coordinate chain are measured on Linux at Codex 0.153.4: initial Pi outside the existing exact `codex` home; app-server, omitted-placement fresh Codex, and outbound Pi inside it; exact callbacks and addressed delivery both ways. `DELIVERY.md` owns the 57-assertion receipt and digest. Arbitrary attached-TUI placement is unsupported and unclaimed. |
84
112
 
85
113
  The repo-local `entwurf-release` skill owns the `land → prepare → make → publish`
86
114
  state machine. Each mode is a separate GLG authorization. Preserve one candidate,
@@ -143,8 +171,9 @@ The goal is not merely "invoke Claude Code." We want:
143
171
  4. source stable-bin exposure — including certified `entwurf` → this checkout's `run.sh`, the managed runtime Copilot fresh resolves; helper units are attempted independently and a foreign helper is a named FAIL
144
172
  5. agy bridge + exact permission + statusline + `PreInvocation` hook — only when `agy` is on PATH; each adapter is idempotent and independently doctorable
145
173
  6. Copilot four-unit composition (birth → MCP → receiver → visible footer) — only when `copilot` is on PATH (#86 C3b); the units run independently, each keeps its package-owned install-state and inverse, and a failed unit is a named component FAIL. The explicit `install-copilot-*`/`uninstall-copilot-*` surfaces remain the per-unit repair and inverse path
146
- 7. `entwurf-bridge` install smoke (`validate_entwurf_bridge`)
147
- 8. computed summary — per-component PASS/SKIP/FAIL; any detected-integration FAIL makes the whole command exit nonzero while valid components stay installed
174
+ 7. Codex composition — only when `codex` is on PATH: setup publishes all three operator-owned atoms and stays non-green until the vendor's trust receipt for the birth declaration exists; it never invokes sudo, never writes `[hooks.state]`, and never starts the app-server or creates the `codex` tmux home. Its setup cells prove the exact `env_vars` boundary, independent component outcomes, idempotence, the non-green-until-trusted verdict with its named operator repair, and that setup never writes the receipt itself. First-admission qualification follows the separate explicit-home LIVE; unrestricted attached-TUI placement is outside the claim.
175
+ 8. `entwurf-bridge` install smoke (`validate_entwurf_bridge`)
176
+ 9. computed summary — per-component PASS/SKIP/FAIL; any detected-integration FAIL makes the whole command exit nonzero while valid components stay installed
148
177
 
149
178
  ```bash
150
179
  git clone https://github.com/junghan0611/entwurf /path/to/entwurf && cd $_
@@ -300,9 +329,10 @@ The minimum passing bar:
300
329
  5. **Honest self-recognition:** the bridged model identifies its actual harness/backend, lists `entwurf-bridge` as the single MCP server with its seven current tools, and presents a backend-native (not normalized) tool surface.
301
330
  6. **Carrier separation honored:** engraving vs pi-context-augment kept distinct (§1A.0); no bridge-identity narrative attributed to the engraving carrier.
302
331
  7. **agy shipped lane accepted:** all three agy doctors are green; automatic birth/statusline/sender identity and same-gid native-push reply are confirmed in a fresh conversation. `agentId=meta-session/antigravity` is correct; model display is not part of that contract. Same-pid concurrent conversation invocation is not claimed.
303
- 8. **Boundary preservation across backends/machines:** for every shipped or explicitly probed backend, regardless of install path or host, no cross-backend tool-surface contamination and no confabulation about pi internals.
304
- 9. **Hygiene:** no orphan ACP children; no unexpected persisted session garbage (a turn-scoped `cwd:` fallback is never a persisted reuse).
305
- 10. **New-harness admission closed:** a release that introduces a native harness admitted under the #82 contract has that harness in `FRESH_CALL_BACKENDS` on all three public surfaces, with `check-harness-admission-parity` green and its clause 7 visible-fresh LIVE step green in the MUST tier. That release also owes the cross-harness leg the same release stop names — two dispatch receipts, an existing citizen's live turn delivered into the new citizen and the new citizen's live turn delivered into an existing one — recorded in `DELIVERY.md`; its deterministic half is an owed follow-up, so until that gate lands this half of the condition is prose and is judged by the recorded receipts. Partial evidence is a branch state; an `unsupported` note in `DELIVERY.md` is a description, never a permit.
332
+ 8. **Codex first-admission home topology accepted:** its birth/MCP/status-line doctors are green and the on-demand real visible `Pi Codex Pi` cell records four coordinates: initial Pi in a session other than the exact existing `codex` home; operator-owned app-server, omitted-placement fresh Codex, and Codex-opened outbound Pi in that home; exact callbacks and addressed delivery succeed both ways. Missing home/app-server rejects without mutation. Entwurf neither creates nor supervises them, infers attached-TUI panes, nor claims unrestricted client placement. Codex remains outside ACP and has no resume claim.
333
+ 9. **Boundary preservation across backends/machines:** for every shipped or explicitly probed backend, regardless of install path or host, no cross-backend tool-surface contamination and no confabulation about pi internals.
334
+ 10. **Hygiene:** no orphan ACP children; no unexpected persisted session garbage (a turn-scoped `cwd:` fallback is never a persisted reuse).
335
+ 11. **New-harness admission closed:** a release that introduces a native harness admitted under the #82 contract has that harness in `FRESH_CALL_BACKENDS` on all three public surfaces, with `check-harness-admission-parity` green and its clause 7 visible-fresh LIVE step green in the MUST tier. That release also owes the cross-harness leg the same release stop names — two dispatch receipts, an existing citizen's live turn delivered into the new citizen and the new citizen's live turn delivered into an existing one — recorded in `DELIVERY.md`; its deterministic half is an owed follow-up, so until that gate lands this half of the condition is prose and is judged by the recorded receipts. Partial evidence is a branch state; an `unsupported` note in `DELIVERY.md` never weakens this stop.
306
336
 
307
337
  Passing establishes a **release verification floor**, not an 8-hour/day operational guarantee. The floor says: gates hold, the agent honestly recognizes its environment, no tool surface is normalized away, no identity leaks, no orphans. It does **not** say a real-day workload (50–100+ turns, tool bursts, partial MCP failures, auth/version drift) survives — that needs L3–L5 evidence (appendix).
308
338
 
@@ -401,5 +401,4 @@ Receipt, limits and the `completed`-branch gap: `scripts/raw-acp-compaction-meas
401
401
 
402
402
  - causal MCP-readiness diagnosis and, only with proof, a backend-invariant fence;
403
403
  - broader installed-host and cross-machine Cortex evidence;
404
- - any future Codex managed native-citizen lane—separate from ACP; the lane is open as #95 and no release ships one;
405
404
  - persisted ACP resume/load, which is not implemented by today's in-memory reuse.
@@ -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