@junghanacs/entwurf 0.13.0 → 0.14.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 (193) hide show
  1. package/AGENTS.md +52 -17
  2. package/BASELINE.md +60 -206
  3. package/CHANGELOG.md +64 -0
  4. package/CONTRIBUTING.md +14 -10
  5. package/DELIVERY.md +118 -261
  6. package/README.md +91 -431
  7. package/VERIFY.md +74 -95
  8. package/demo/README.md +1 -1
  9. package/demo/demo-baseline.sh +1 -3
  10. package/demo/demo.sh +2 -5
  11. package/docs/acp-backend-rail.md +236 -0
  12. package/docs/external-mcp-host.md +131 -0
  13. package/docs/fresh-cut-policy.md +99 -0
  14. package/docs/setup-clean-host.md +124 -328
  15. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +160 -67
  16. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/acp-client.js +12 -0
  17. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/backend-adapter.js +11 -10
  18. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/config.js +1 -1
  19. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/engraving.js +42 -1
  20. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/models.js +4 -4
  21. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/overlay.js +11 -9
  22. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-control-rpc.js +7 -5
  23. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-core.js +13 -14
  24. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-resume-args.js +45 -40
  25. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-contract.js +117 -95
  26. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-decider.js +23 -57
  27. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-lock.js +16 -7
  28. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-production.js +5 -53
  29. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-release.js +21 -36
  30. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-runner.js +3 -15
  31. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-send-fallback.js +12 -11
  32. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-send.js +2 -7
  33. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-surface.js +30 -67
  34. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-visible-resume.js +256 -0
  35. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js +91 -5
  36. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +258 -0
  37. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-launch.js +202 -0
  38. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-placement.js +289 -0
  39. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-resume-call.js +170 -0
  40. package/mcp/entwurf-bridge/dist/pi-extensions/lib/resume-launch-identity.js +136 -0
  41. package/mcp/entwurf-bridge/dist/pi-extensions/lib/session-id.js +8 -5
  42. package/mcp/entwurf-bridge/dist/pi-extensions/lib/socket-discovery.js +3 -3
  43. package/mcp/entwurf-bridge/dist/scripts/meta-facts.js +51 -0
  44. package/mcp/entwurf-bridge/dist/scripts/new-session-id.js +9 -4
  45. package/mcp/entwurf-bridge/src/index.ts +176 -67
  46. package/mcp/entwurf-bridge/start.sh +2 -2
  47. package/mcp/entwurf-bridge/test.sh +23 -9
  48. package/mcp/entwurf-bridge/tsconfig.build.json +11 -2
  49. package/package.json +25 -11
  50. package/pi-extensions/entwurf-control.ts +220 -71
  51. package/pi-extensions/lib/acp/acp-client.ts +22 -0
  52. package/pi-extensions/lib/acp/augment.ts +42 -3
  53. package/pi-extensions/lib/acp/backend-adapter.ts +12 -11
  54. package/pi-extensions/lib/acp/backend.ts +366 -38
  55. package/pi-extensions/lib/acp/config.ts +1 -1
  56. package/pi-extensions/lib/acp/engraving.ts +43 -1
  57. package/pi-extensions/lib/acp/event-mapper.ts +16 -13
  58. package/pi-extensions/lib/acp/models.ts +4 -4
  59. package/pi-extensions/lib/acp/overlay.ts +11 -9
  60. package/pi-extensions/lib/entwurf-control-rpc.ts +7 -5
  61. package/pi-extensions/lib/entwurf-core.ts +15 -15
  62. package/pi-extensions/lib/entwurf-resume-args.ts +41 -52
  63. package/pi-extensions/lib/entwurf-v2-contract-schema.ts +1 -1
  64. package/pi-extensions/lib/entwurf-v2-contract.ts +120 -99
  65. package/pi-extensions/lib/entwurf-v2-decider.ts +30 -91
  66. package/pi-extensions/lib/entwurf-v2-lock.ts +16 -7
  67. package/pi-extensions/lib/entwurf-v2-production.ts +4 -78
  68. package/pi-extensions/lib/entwurf-v2-release.ts +25 -49
  69. package/pi-extensions/lib/entwurf-v2-runner.ts +6 -21
  70. package/pi-extensions/lib/entwurf-v2-send-fallback.ts +12 -11
  71. package/pi-extensions/lib/entwurf-v2-send.ts +2 -7
  72. package/pi-extensions/lib/entwurf-v2-surface.ts +36 -76
  73. package/pi-extensions/lib/entwurf-v2-visible-resume.ts +370 -0
  74. package/pi-extensions/lib/meta-session.ts +93 -5
  75. package/pi-extensions/lib/mux-fresh-call.ts +328 -0
  76. package/pi-extensions/lib/mux-launch.ts +267 -0
  77. package/pi-extensions/lib/mux-placement.ts +387 -0
  78. package/pi-extensions/lib/mux-resume-call.ts +221 -0
  79. package/pi-extensions/lib/resume-launch-identity.ts +162 -0
  80. package/pi-extensions/lib/session-id.js +8 -5
  81. package/pi-extensions/lib/socket-discovery.ts +3 -3
  82. package/prompts/engraving.md +13 -5
  83. package/run.sh +688 -282
  84. package/scripts/agy-bridge-config.py +5 -1
  85. package/scripts/check-acp-backend-preflight.ts +1 -1
  86. package/scripts/check-acp-carrier-augment.ts +246 -10
  87. package/scripts/check-acp-cortex.ts +5 -5
  88. package/scripts/check-acp-overlay.ts +13 -3
  89. package/scripts/check-acp-prompt-lifecycle.ts +565 -0
  90. package/scripts/check-acp-stop-reason.ts +342 -0
  91. package/scripts/check-acp-stream-hooks.ts +504 -0
  92. package/scripts/check-elapsed.sh +25 -0
  93. package/scripts/check-entwurf-bridge-boot.ts +51 -4
  94. package/scripts/check-entwurf-bridge-pi-free.ts +6 -5
  95. package/scripts/check-entwurf-control-rpc.ts +4 -3
  96. package/scripts/check-entwurf-resume-args.ts +72 -70
  97. package/scripts/check-entwurf-session-identity.ts +14 -10
  98. package/scripts/check-entwurf-v2-contract.ts +34 -59
  99. package/scripts/check-entwurf-v2-decider.ts +17 -177
  100. package/scripts/check-entwurf-v2-lock.ts +5 -2
  101. package/scripts/check-entwurf-v2-matrix.ts +3 -53
  102. package/scripts/check-entwurf-v2-production.ts +2 -91
  103. package/scripts/check-entwurf-v2-release.ts +10 -105
  104. package/scripts/check-entwurf-v2-runner.ts +4 -85
  105. package/scripts/check-entwurf-v2-send-fallback.ts +5 -6
  106. package/scripts/check-entwurf-v2-send.ts +0 -28
  107. package/scripts/check-entwurf-v2-surface.ts +198 -128
  108. package/scripts/check-entwurf-v2-visible-resume.ts +445 -0
  109. package/scripts/check-fresh-cut-gate.sh +1 -1
  110. package/scripts/check-gate-qualification.ts +101 -7
  111. package/scripts/check-install-container.sh +10 -2
  112. package/scripts/check-install-surface.ts +1 -1
  113. package/scripts/check-keyset-overlap.py +1 -1
  114. package/scripts/check-meta-facts.ts +249 -0
  115. package/scripts/check-meta-identity-consumers.ts +1 -1
  116. package/scripts/check-meta-session.ts +169 -0
  117. package/scripts/check-mux-launch-tmux.ts +316 -0
  118. package/scripts/check-mux-launch.ts +288 -0
  119. package/scripts/check-mux-launcher-fence.ts +264 -0
  120. package/scripts/check-mux-parent-artifact.ts +195 -0
  121. package/scripts/check-mux-placement-tmux.ts +322 -0
  122. package/scripts/check-mux-placement.ts +323 -0
  123. package/scripts/check-mux-resume-call.ts +282 -0
  124. package/scripts/check-probe-cli-shim.ts +25 -22
  125. package/scripts/check-probe-ordering.ts +121 -81
  126. package/scripts/check-release-gate-outcomes.ts +452 -0
  127. package/scripts/check-resume-launch-identity.ts +244 -0
  128. package/scripts/check-socket-discovery.ts +1 -1
  129. package/scripts/fixtures/mux-parent-transcript.scrubbed.jsonl +3 -0
  130. package/scripts/inventory-verification-surface.ts +349 -0
  131. package/scripts/lib/claude-launcher-fence.ts +322 -0
  132. package/scripts/lib/live-skip.ts +33 -0
  133. package/scripts/lib/mutation-qualify.ts +109 -3
  134. package/scripts/lib/probe-acp-turn.ts +33 -12
  135. package/scripts/lib/step-outcome.sh +88 -0
  136. package/scripts/meta-bridge-doctor.sh +6 -8
  137. package/scripts/meta-facts.ts +60 -0
  138. package/scripts/mutants/acp-augment.json +106 -0
  139. package/scripts/mutants/acp-cortex.json +2 -2
  140. package/scripts/mutants/acp-overlay.json +17 -0
  141. package/scripts/mutants/acp-prompt-lifecycle.json +100 -0
  142. package/scripts/mutants/acp-stop-reason.json +80 -0
  143. package/scripts/mutants/acp-stream-hooks.json +158 -0
  144. package/scripts/mutants/bridge-boot-resume.json +45 -0
  145. package/scripts/mutants/meta-facts.json +50 -0
  146. package/scripts/mutants/meta-identity.json +36 -0
  147. package/scripts/mutants/mux-boundary.json +196 -0
  148. package/scripts/mutants/mux-fresh-call.json +185 -0
  149. package/scripts/mutants/mux-launcher-fence.json +123 -0
  150. package/scripts/mutants/mux-parent-artifact.json +39 -0
  151. package/scripts/mutants/mux-resume-call.json +148 -0
  152. package/scripts/mutants/probe-ordering.json +6 -1021
  153. package/scripts/mutants/release-gate.json +140 -0
  154. package/scripts/mutants/resume-args.json +76 -0
  155. package/scripts/mutants/resume-launch-identity.json +96 -0
  156. package/scripts/mutants/v2-surface.json +74 -23
  157. package/scripts/mutants/v2-visible-resume.json +215 -0
  158. package/scripts/new-session-id.ts +9 -4
  159. package/scripts/smoke-acp-bundled-mcp-live.ts +4 -3
  160. package/scripts/smoke-acp-carrier-augment-live.ts +2 -2
  161. package/scripts/smoke-acp-cortex-live.ts +8 -14
  162. package/scripts/smoke-acp-long-turn-live.ts +185 -0
  163. package/scripts/smoke-acp-mcp-live.ts +2 -2
  164. package/scripts/smoke-acp-memory-containment-live.ts +2 -2
  165. package/scripts/smoke-acp-ordering-probe-live.ts +2 -2
  166. package/scripts/smoke-acp-overlay-live.ts +2 -2
  167. package/scripts/smoke-acp-provider-live.ts +2 -2
  168. package/scripts/smoke-acp-raw-turn-live.ts +3 -3
  169. package/scripts/smoke-acp-session-reuse-live.ts +2 -2
  170. package/scripts/smoke-acp-skill-live.ts +2 -2
  171. package/scripts/smoke-acp-socket-citizen-live.ts +4 -3
  172. package/scripts/smoke-acp-v2-send-live.ts +4 -3
  173. package/scripts/smoke-agy-native-push-live.ts +10 -20
  174. package/scripts/smoke-claude-native-resume-live.sh +13 -3
  175. package/scripts/smoke-entwurf-chain-live.ts +352 -0
  176. package/scripts/smoke-entwurf-v2-matrix-live.ts +3 -3
  177. package/scripts/smoke-meta-honesty.sh +1 -1
  178. package/scripts/smoke-meta-install-state.sh +4 -0
  179. package/scripts/smoke-mux-fresh-call-live.ts +365 -0
  180. package/scripts/smoke-mux-lifecycle-live.ts +1136 -0
  181. package/scripts/smoke-pi-attach.ts +1 -1
  182. package/scripts/smoke-user-scope-citizen.sh +1 -1
  183. package/scripts/tsconfig.json +1 -0
  184. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-preflight.js +0 -160
  185. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-spawn-production.js +0 -273
  186. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-spawn.js +0 -216
  187. package/pi-extensions/lib/entwurf-v2-spawn-production.ts +0 -373
  188. package/pi-extensions/lib/entwurf-v2-spawn.ts +0 -323
  189. package/scripts/check-acp-sdk-surface.ts +0 -236
  190. package/scripts/check-entwurf-v2-spawn-production.ts +0 -551
  191. package/scripts/check-entwurf-v2-spawn.ts +0 -399
  192. package/scripts/smoke-entwurf-v2-spawn-live.ts +0 -188
  193. package/scripts/smoke-entwurf-v2-spawn-resume-live.ts +0 -469
package/README.md CHANGED
@@ -10,25 +10,16 @@ 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 is shipped as a mailbox-backed meta-session; Antigravity (`agy`) is shipped as a native-push citizen with automatic `PreInvocation` birth, ambient garden-id status, and a managed MCP/permission install surface. 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 a session-scoped dual-HOME containment [docs/acp-backend-rail.md](./docs/acp-backend-rail.md) §11-8).
14
-
15
- <details>
16
- <summary>Watch archived pre-0.12 demo (2131×1142 GIF, click to expand)</summary>
17
-
18
- > This GIF is historical pre-0.12 evidence and still shows the retired v1 demo flow. The current 0.12 tool surface is `entwurf_v2`; a v2-native demo retake is a follow-up.
19
-
20
- ![entwurf demo](docs/assets/entwurf-demo.gif)
21
-
22
- </details>
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 is shipped as a mailbox-backed meta-session; Antigravity (`agy`) is shipped as a native-push citizen with automatic `PreInvocation` birth, ambient garden-id status, and a managed MCP/permission install surface. 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)).
23
14
 
24
15
  ```text
25
16
  Claude Code / Codex / agy / pi
26
17
  → garden id
27
18
  → entwurf_v2
28
- → control-socket | spawn-bg resume | meta-mailbox | native-push
19
+ → control-socket | meta-mailbox | native-push
29
20
  ```
30
21
 
31
- [`entwurf_v2`](#entwurf_v2--canonical-dispatch-verb) is the canonical dispatch surface over *existing* garden citizens — live control-socket send, spawn-bg resume, meta-mailbox enqueue, and native-push into a live Antigravity conversation. 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 sibling minting is still a deferred lane; the non-Claude ACP lane is not — Snowflake Cortex Code landed as 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. 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 own tmux session 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.
32
23
 
33
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.
34
25
 
@@ -66,17 +57,11 @@ Claude's `install-meta-bridge` and agy's `install-agy-{bridge,statusline,hooks}`
66
57
 
67
58
  > **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.
68
59
 
69
- > **Project boundary.** `entwurf` is not a fork, plugin, dependency, or integration layer of `oh-my-pi`, and it is not developed in coordination with `oh-my-pi`. Issues in other Pi / ACP projects may be useful as general implementation references, but they are not `entwurf` integration issues unless this repository explicitly links them as such.
70
-
71
- > **Anthropic subscription billing.** From 2026-06-15, Anthropic third-party agent paths (ACP, Agent SDK, `claude -p`, entwurf's Claude backend) consume a separate Agent SDK credit pool, distinct from Claude chat and the `claude` CLI used as an interactive terminal. `entwurf` respects that distinction — no bypass, no emulation — and preserves capability dignity across supported backends (see [AGENTS.md](./AGENTS.md) invariants #7, #9, #10). The recommended default runtime leans toward paths outside Anthropic's Agent SDK metering, with Claude invoked when its quality is worth the credit cost. The operator decides the mix.
72
-
73
- > **Gemini CLI migration.** Google announced that Gemini CLI stops serving requests for Google AI Pro / Ultra and unpaid individual tiers on **2026-06-18**; those users should migrate to [Antigravity CLI](https://antigravity.google/product/antigravity-cli). See Google's migration note: [Transitioning Gemini CLI to Antigravity CLI](https://developers.googleblog.com/an-important-update-transitioning-gemini-cli-to-antigravity-cli/). The repository still carries existing Gemini adapter code for compatibility, but this README no longer presents Gemini CLI as a recommended setup path during the migration window.
74
-
75
60
  ## Concept primer
76
61
 
77
62
  A few words that look unusual for a coding tool.
78
63
 
79
- - **Entwurf** (기투, projection-of-self) — sibling sessions with their own runtime boundary. Not "delegate," not "worker," not "sub-agent." Spawn, resume, and live peer messaging are first-class.
64
+ - **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.
80
65
  - **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`, agy `PreInvocation`). 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.
81
66
  - **Engraving** — optional short operator text delivered through each backend's native identity carrier. Not a giant hidden prompt, not a tool catalog.
82
67
  - **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, Codex, Antigravity, …) 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.
@@ -115,8 +100,8 @@ entwurf check-bridge
115
100
  ```
116
101
 
117
102
  This writes `.pi/settings.json` in the target project with the absolute path to
118
- the installed `entwurf-bridge` launcher. It also links the target registry under
119
- `~/.pi/agent/` for spawn-bg resume. The global install is the easiest path when
103
+ the installed `entwurf-bridge` launcher. (The old `~/.pi/agent/` target-registry
104
+ link is gone — #50 C3; nothing reads it.) The global install is the easiest path when
120
105
  Claude Code's USER-scope MCP registration should work from every cwd.
121
106
 
122
107
  ### From npm — project-local install
@@ -159,7 +144,7 @@ because Node refuses to strip `.ts` files under `node_modules`.
159
144
  ### Pi adapter / ACP plugin lane
160
145
 
161
146
  To use the `entwurf` provider inside pi, install a compatible pi binary
162
- separately (`@earendil-works/pi-coding-agent >=0.82.1 <0.83`). Then point pi at
147
+ separately (`@earendil-works/pi-coding-agent >=0.84.1 <0.85`). Then point pi at
163
148
  the npm-installed package or development clone:
164
149
 
165
150
  ```bash
@@ -174,272 +159,21 @@ For daily operator sessions, launch pi with `--entwurf-control` — no id
174
159
  injection; the meta-record mints the garden address (see [Garden launcher](#garden-launcher)). Older pi
175
160
  versions may silently miss the provider/extension surface, so treat the pi floor
176
161
  as release-critical for the ACP/plugin lane. A host that only uses
177
- `entwurf-bridge` from Claude Code / Codex / Antigravity does not need pi until it
178
- tries an `owned-outcome` spawn-bg resume target.
162
+ `entwurf-bridge` from Claude Code / Codex / Antigravity does not need pi at all for
163
+ delivery: no `entwurf_v2` rail launches a pi process.
179
164
 
180
- ### External MCP host lane
165
+ ### Native harness install and doctors
181
166
 
182
- After any npm install, register `entwurf-bridge` with the external host:
183
-
184
- ```bash
185
- claude mcp add --scope user entwurf-bridge \
186
- entwurf-bridge
187
- ```
188
-
189
- If the host does not inherit the npm bin directory, use an absolute path to the
190
- bin or `start.sh`. For a garden-native Claude Code meta-session (replyable by
191
- garden id), run this on Linux. entwurf refuses new macOS meta-bridge
192
- installs because its strict live-owner doctor currently depends on `/proc`; macOS
193
- is **not yet verified/certified for this cut**, not permanently impossible, and
194
- future native validation may reopen it. Package-level `os` is intentionally
195
- unrestricted, and Darwin uninstall remains available for legacy cleanup.
196
-
197
- ```bash
198
- entwurf install-meta-bridge
199
- entwurf doctor-meta-bridge
200
- ```
201
-
202
- > **Upgrade action:** after installing a package that moves the hook launch form, run `entwurf install-meta-bridge` and restart **every already-open Claude Code session** before trusting send/receive. A new hook reached through an old cached command fails closed: it may still mint a garden record, but the owner join it depends on is not the one the old command produces. Reinstall materializes the matching manifest; restart makes live Claude processes load it. This release moves to the exec form and requires Claude Code `>=2.1.217`; `install-meta-bridge` and `doctor-meta-bridge` refuse anything older outright, because an older Claude drops the hook's `args` silently and still reports success.
203
-
204
- On npm/pnpm-installed packages, `doctor-meta-bridge` must use prebuilt JS for its
205
- store scan and defer repo-only source-shape gates; Node refuses strip-types for
206
- raw `.ts` helpers under `node_modules`. It also refuses any Claude Code below the
207
- supported floor `>=2.1.217` (an older one silently drops the hook's `args` and still
208
- reports success, so nothing else in the output could be trusted), checks Claude's
209
- installed hooks are the exec form through the shipped `hook-launch.sh`, and on Linux
210
- verifies every live Claude MCP process joins to live sender/receiver markers.
211
- `launch form is UNSUPPORTED` means reinstall the meta-bridge; a live-owner-join failure after
212
- that means restart the affected Claude session so it loads the new manifest. If no
213
- matching MCP child exists the doctor reports `NOT CERTIFIED` and **exits nonzero** — a
214
- host whose live tier could not be measured is not a certified host, and that is worded
215
- differently from a broken install on purpose. If the doctor reports
216
- `ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING`, reinstall a current package before
217
- trusting the floor result.
218
-
219
- **Release evidence boundary.** The required Linux `artifact-consumer` CI job
220
- installs one read-only candidate tarball globally as a non-root user in a Node 24
221
- container that cannot see the checkout, records the tarball digest and image
222
- identity, freezes the package root, and drives the strict doctor. Its Claude cache,
223
- owner process, and live bridge are deliberately synthetic fixtures; that job proves
224
- the package-consumer/oracle shape, not a real Claude lifecycle. The direct B/B2
225
- runtime evidence came from actual Claude 2.1.138/2.1.217 sessions on one NixOS host.
226
- A production host is certified only when a **new session using the installed
227
- artifact** makes `doctor-meta-bridge` exit 0 with the live join. See the explicit
228
- support matrix and release order in [VERIFY.md](./VERIFY.md). For the release
229
- artifact, first preserve one `npm pack` output, then run
230
- `ENTWURF_CANDIDATE_TGZ=/absolute/path/to/candidate.tgz ./run.sh check-install-container`;
231
- the gate prints that canonical path and sha256 and consumes it without re-packing.
232
- Only that accepted file may be published under the explicitly authorized lane:
233
- `--tag latest` for stable `0.12.8`, while preserving
234
- `repair=0.12.8-repair.1`.
235
-
236
- > **Generations — the fresh-cut policy.** The bridge is a call-relay, never a
237
- > memory layer: a meta-record is routing state for a **current-generation**
238
- > citizen, and memory lives in the native transcripts and the embedding axes
239
- > outside this repo. Sessions flow. Four sentences fix the whole policy:
240
- >
241
- > 1. The active citizen store is **v3-only** and provides **no cross-generation
242
- > address or resume continuity**.
243
- > 2. If even one entry in the store fails certification, **install and citizen
244
- > birth/registration refuse before writing** and demand the explicit fresh-cut
245
- > verb.
246
- > 3. `fresh-cut` **requires quiescence** — it verifies it and refuses while any
247
- > surface is live *or* unprovable, never closing a session for you — then moves
248
- > the whole previous generation to a timestamped archive
249
- > (`meta-sessions.archive-<ts>`, `meta-mailbox.archive-<ts>`) and opens an
250
- > empty live generation.
251
- > 4. The archive is **forensic bytes only**: no runtime reads it and no restore
252
- > verb exists. Native transcripts and the memory axes are never touched.
253
- >
254
- > **Certification** is one shared contract (`certifyActiveStore`), held identically
255
- > by the install doctor and by all four identity writers — pi birth, the Claude
256
- > `SessionStart` hook, the agy imprint, `entwurf_register_native`. Every
257
- > `.meta.json` must be a **regular file** (a symlink is refused, never followed),
258
- > **readable by the live schema**, **named by its own body**, and the **unique
259
- > holder of its `nativeSessionId`**. All five defect kinds — previous generation,
260
- > corruption, drift, duplicate, symlink — collapse to the same prescription, so
261
- > there is nothing to diagnose or branch on.
262
- >
263
- > Note the deliberate scope, stated as it actually is. A **store-wide** scan runs on
264
- > **identity writes**, in the doctor, and on the two read surfaces below — not on every
265
- > mailbox poke; a call-relay does not re-scan the whole store per message. What every
266
- > **targeted read** holds is the per-entry half of the same contract:
267
- > `readMetaIdentityByGardenId` refuses a record that is not a regular file (a symlink is
268
- > never followed, in *either* direction) and one whose body disagrees with its name,
269
- > naming the verb. That is what the mailbox poke, the sender-marker trust and
270
- > `entwurf_self` use, and it is all they need.
271
- >
272
- > Store-wide **uniqueness** is checked on the read snapshot at the two places where it
273
- > is both affordable and load-bearing ([#52](https://github.com/junghan0611/entwurf/issues/52),
274
- > 0.12.9):
275
- >
276
- > - **Discovery** — `listAllMetaIdentities`, and so `entwurf_peers`, already reads the
277
- > whole store, so the check is free. Two records claiming one `nativeSessionId` are
278
- > **not** two citizens: *neither* is listed (the store cannot say which one owns that
279
- > session, and a facts surface may not mint an authority the certification refuses),
280
- > both become diagnostics naming each other, and every unrelated citizen keeps listing.
281
- > - **Dispatch** — `readAddressableMetaIdentity`, used by v2 `resolveTarget` and by the
282
- > pi resume path. Those are the moments a record stops being data and becomes an
283
- > **address**, they happen once per dispatch next to a socket connect and a spawn, and
284
- > a duplicate there means direct-injecting one live conversation under two garden ids,
285
- > or resuming one transcript twice under two per-garden-id locks. It fails **loud**; a
286
- > soft `bad-target` is reserved for a record that is genuinely absent.
287
- >
288
- > **A rival is a record that could be addressed instead** — narrower than "a file whose
289
- > bytes mention the same id". A **symlinked** entry is not a candidate and is *never
290
- > read* (rule 1 again: following it to see whether it counts would break the rule in the
291
- > act of enforcing it, and let planted foreign bytes quarantine a healthy citizen); a
292
- > **drifted** or **unparseable** neighbour is not a candidate either, because no garden
293
- > id can reach it. All three remain certification defects and the listing reports them as
294
- > diagnostics — they just may not blind a healthy record. The opposite case is a
295
- > **regular `.meta.json` this process cannot read**: that one might BE the duplicate, so
296
- > it fails loud rather than being skipped, because "holds it alone" from a scan that
297
- > never asked is the same vacuous pass in miniature. (`ENOENT` alone is the exception —
298
- > a file that vanished mid-scan is not in the store.)
299
- >
300
- > Both store-wide read scans take entries **with their kind** from one shared
301
- > `readActiveStoreEntries`, rather than each binding doing its own name-only `readdir`.
302
- > That is what makes rule 1 structural: a scan handed bare names has no choice but to
303
- > read the path, which is how both `entwurf_peers` bindings came to follow a symlinked
304
- > record while the doctor refused the very same entry.
305
- >
306
- > A kind carried alongside a **name** is still only half of it, because a name can stop
307
- > meaning what it meant. `lstat`-then-`readFileSync(path)` classifies one entry and reads
308
- > another: replace the final path component with a symlink in between, and the read
309
- > follows it into foreign bytes while every test on a settled store stays green. So the
310
- > bytes of a record come from exactly one place — `readStoreRecordFile`, shared by the
311
- > store-wide reader and by `readMetaIdentityByGardenId` — which opens with `O_NOFOLLOW`
312
- > (a symlink fails the **open**, before a byte is read), decides the kind by `fstat` on
313
- > **that file description** rather than on a name, and closes it in a `finally`. It also
314
- > opens `O_NONBLOCK`, because classify-then-open never had to care that `open(fifo,
315
- > O_RDONLY)` blocks until a writer appears, and deciding on the fd does. The reader does
316
- > not flatten errno: callers still separate a record that raced away (`ENOENT`, skipped)
317
- > from one that cannot be read (`EACCES`, loud) from one that was swapped (`ELOOP`,
318
- > refused) — and the rival scan's raced-away skip depends on exactly that.
319
- >
320
- > That reader does not replace the `lstat` classification in front of the targeted read;
321
- > the two hold **different** things, and collapsing them into "one enforcement point"
322
- > was itself a regression (caught in review before shipping). The classification decides
323
- > POLICY on a settled store **without opening anything**, which is what lets a socket, a
324
- > device or a mode-000 directory earn the certification's own sentence — an `open` would
325
- > answer `ENXIO` or `EACCES` there, errnos that say nothing about regularity, and the
326
- > targeted read would start calling the host unreadable where the doctor calls the entry
327
- > non-regular. Two contracts for one store is precisely the defect rule 1 exists to
328
- > prevent. The fd layer decides the RACE: after a regular snapshot, its errno verdicts
329
- > (`ELOOP`, `ENXIO`, a non-regular `fstat`) collapse back onto the settled sentences
330
- > through one pure classifier, so a race never teaches the operator a second vocabulary
331
- > for one state of the world. Because the classification answers first, those branches
332
- > are unreachable from any settled store — which is why the classifier is pure and pinned
333
- > with synthetic errnos rather than by a store on disk.
334
- >
335
- > This is not only a defence against external corruption. `upsertMetaSession` certifies
336
- > and then writes, which is **not a transaction**, so two concurrent births — two
337
- > `SessionStart` hooks, an `entwurf_register_native` racing an agy imprint — can both
338
- > observe one clean store and mint different garden ids for one native session. A
339
- > duplicate can therefore appear on a host where nothing was ever corrupted.
340
- >
341
- > There is **no migrator and no legacy reader anywhere in this repo** — carrying
342
- > old records forward would serve a continuity the system deliberately does not
343
- > promise. When the store cannot be read, the sender surfaces (`entwurf_self`,
344
- > `entwurf_v2`, the inbox) **fail loud** naming the verb in both invocation
345
- > forms; `entwurf_peers` keeps listing and folds unreadable records into a
346
- > **diagnostic** line, because a facts surface that dies on corruption tells you
347
- > less than one that shows what it could and could not read.
348
- >
349
- > **The installer entrypoints will not cross that boundary silently.** `setup`,
350
- > `install` and `install-meta-bridge` each certify the store *before* they write
351
- > anything: on a host that fails certification they refuse, name the verb, and
352
- > leave your settings, plugin registry and `auth.json` untouched. So an upgrade
353
- > through those commands is a refusal you answer, not a broken install you
354
- > diagnose:
355
- >
356
- > ```bash
357
- > entwurf meta-bridge-fresh-cut # quiesce-checked: archive the old generation, open an empty one
358
- > ```
359
- >
360
- > **Read its exit status, don't just chain it.** The cut answers with a contract
361
- > ([#54](https://github.com/junghan0611/entwurf/issues/54), `--help` prints it), because
362
- > "it failed" is not one world-state:
363
- >
364
- > | exit | what already moved | what to do |
365
- > |---|---|---|
366
- > | `0` | the cut is complete | run `setup` |
367
- > | `1` | **nothing** — a live/unprovable surface, an occupied archive destination, an unreadable surface | fix the named cause, re-run. **Do not** run `setup`: the store it refused is still there |
368
- > | `2` | nothing — usage error | fix the command |
369
- > | `3` | the cut transition is **incomplete** after at least one archive move; the fresh generation is not confirmed open | inspect, or re-run to finish under a new stamp |
370
- > | `4` | the cut is **complete**; marker/socket residue could not be unlinked | `setup` may run. Prefer repairing the named residue before `setup`; if new citizens have already been born, remove it manually — another fresh-cut would archive their generation too |
371
- >
372
- > Only `0` is success — a failed sweep never becomes a pass. `fresh-cut && setup` is
373
- > still the right chain for the common path; the codes are there so a runbook, CI or an
374
- > agent can tell a refusal that changed nothing from a cut that already unblocked the
375
- > install. An exit-4 re-run is safe only before `setup` or any new citizen birth.
376
- >
377
- > The refusal is a **preflight, not a lock**: it certifies the store as it stands
378
- > at that moment. On a host whose pi/Claude settings point straight at a checkout,
379
- > a `git pull` can put the new code in front of live sessions before you run
380
- > anything at all, so order the upgrade explicitly — **quiesce the sessions on
381
- > that host → pull → fresh-cut → `setup` → reopen**. `fresh-cut` enforces the
382
- > quiesce half itself: a live control socket, a marker whose owner process is
383
- > still running, a **native-push (agy) conversation its own adapter probe answers
384
- > alive**, or **any surface it cannot prove is gone** — an indeterminate socket, an
385
- > unreadable or symlinked marker, a conversation that probes indeterminate, a
386
- > surface directory it cannot even inspect (absent is ENOENT alone, and the name
387
- > must hold an actual directory — a symlinked surface is never followed) —
388
- > refuses the cut before anything moves. Cutting needs proof of death, not absence
389
- > of proof of life.
390
- >
391
- > **One marker is cleared without proving death, and it is the exception that keeps
392
- > this path open.** A marker whose recorded `ownerPid` cannot own anything — `1`
393
- > (init), `0`, a negative or non-integer — is *refuted by construction*: no writer in
394
- > this tree can mint one any more, so on a current install it is **legacy or corrupt
395
- > residue** — a pre-fix writer whose parent had been reparented to init (the retired
396
- > shell-form Claude hook; the agy imprint, which asked only `> 0` until this repair),
397
- > or a foreign/damaged marker, the only way a non-integer pid appears at all. The one
398
- > file actually observed was a shell-form hook reparented to init. Honoring it was not
399
- > merely wrong, it was a trap: init runs for the whole boot and its start-key does not
400
- > change while it does, so the owner verdict is `live` and **the very action this
401
- > refusal prescribes cannot change that** — you quiesce every session, exactly as
402
- > told, and the cut refuses again. (Deleting the marker removes the claim rather than
403
- > refuting the verdict; a reboot recomputes the key with no contract either way.)
404
- > Meanwhile the one repair this page names could not run: on the affected host the cut
405
- > stayed blocked until the marker file was removed by hand (#53 A, measured on a second
406
- > Linux host 2026-07-25). Such a marker is now swept as residue and **reported apart
407
- > from the dead ones** (`refuted:`), because a proof of invalidity is a different
408
- > finding from a proof of death — and a stronger one.
409
- >
410
- > **Scope of that rule.** "A native session is never owned by init" is a property of the
411
- > axis entwurf certifies — a Linux desktop/workstation host, where init is the service
412
- > manager and every harness descends from a login session. A container that runs the
413
- > harness **as pid 1** is a real shape, and there the marker would name a genuine owner.
414
- > That host is **unsupported and fails closed**: the writers refuse the marker, so the
415
- > session still gets its meta-record but never claims reply-addressability — a lost
416
- > capability rather than a false identity. Reopening that lane needs new evidence and a
417
- > new contract, not a looser predicate.
418
- >
419
- > That agy row is not symmetry for its own sake: `entwurf_register_native` writes a
420
- > record and **no marker at all**, and `entwurf_v2` dispatches to such a citizen
421
- > straight off the record, so marker absence is the *normal* state of a live,
422
- > fully deliverable conversation. A socket+marker scan alone would call that host
423
- > quiesced. Quiescing agy is also what makes the cut legal — with no host process
424
- > the probe answers *dead* — so the rule can never trap you on a host you have
425
- > already closed.
426
- >
427
- > **What quiescence is proven over, exactly.** The live-schema-readable identities of
428
- > the current generation, plus the transport artifacts (sockets, markers). A record the
429
- > live schema *cannot* read is archived without probing it, and that is not a claim
430
- > that its session exited — only that those bytes front no addressable citizen here,
431
- > since every targeted address/dispatch path refuses them. The alternative deadlocks the cut on the very
432
- > store it exists to clear, and salvaging ids out of an unreadable shape in order to
433
- > probe it would be the legacy reader this repo deleted. A native conversation that
434
- > outlives a cut simply gets a **new** garden id from its next hook or registration —
435
- > re-birth in the new generation, never continuity of the old address.
436
-
437
- After upgrading a globally installed package, reinstall the native-harness surface you use before trusting it:
167
+ A plain MCP registration exposes the bridge tools; a **garden-native** session also
168
+ needs entwurf's lifecycle hook and identity marker. Use the managed installers rather
169
+ than editing native-harness state by hand:
438
170
 
439
171
  ```bash
172
+ # Claude Code (Linux-certified axis)
440
173
  entwurf install-meta-bridge
441
174
  entwurf doctor-meta-bridge
442
175
 
176
+ # Antigravity / agy
443
177
  entwurf install-agy-bridge
444
178
  entwurf install-agy-statusline
445
179
  entwurf install-agy-hooks
@@ -448,17 +182,31 @@ entwurf doctor-agy-statusline
448
182
  entwurf doctor-agy-hooks
449
183
  ```
450
184
 
451
- The installed entries use stable bin shims, but Claude's plugin bundle/cache still has to be re-materialized and agy's three ownership records must be refreshed by their idempotent installers. Restart existing Claude Code and agy processes after reinstall.
185
+ Claude Code uses the supported floor `>=2.1.217`; older versions silently discard the
186
+ exec-hook `args`, so install and doctor fail loud rather than falling back. After any
187
+ upgrade, rerun the installer for the native harness you use and restart its existing
188
+ processes. A claimed Claude host is certified only when a **new** session using the
189
+ installed artifact makes `doctor-meta-bridge` exit 0 with the live owner join.
452
190
 
453
- For manual configuration, [`pi/settings.reference.json`](./pi/settings.reference.json)
454
- shows the pi adapter settings shape, and the external-host examples below show
455
- plain MCP registrations.
191
+ Linux is the only currently certified Claude meta-bridge axis. New macOS wiring is
192
+ refused because the strict live-owner doctor depends on `/proc`; Darwin uninstall
193
+ remains available for legacy cleanup, and the neutral package itself has no `os`
194
+ restriction. Detailed diagnosis and clean-host steps live in
195
+ [docs/setup-clean-host.md](./docs/setup-clean-host.md).
456
196
 
457
- > **First time on a clean Linux host (Ubuntu / Debian / NixOS)?** See the [clean-host walk-through](./docs/setup-clean-host.md) — Node/npm install, auth-free bridge boot, optional pi adapter verification, and authenticated runtime smokes. The neutral package may install elsewhere, but Linux is the only currently certified Claude meta-bridge axis: its installer refuses macOS and its doctor remains `NOT CERTIFIED`/nonzero because the live owner join is not yet instrumented. Future native validation may reopen the macOS lane.
197
+ The active citizen store is V3-only. A store that fails certification is never
198
+ silently migrated: quiesce the native sessions, run `entwurf meta-bridge-fresh-cut`,
199
+ then reinstall. The cut archives routing records only—never native transcripts or
200
+ external memory—and no runtime reads the archive. Exit meanings and the complete
201
+ operator contract are in [docs/fresh-cut-policy.md](./docs/fresh-cut-policy.md).
458
202
 
459
- > **Post-install checks.** `entwurf check-bridge` (or `./run.sh check-bridge` from a clone) proves the `entwurf-bridge` MCP surface loads with no backend auth needed. To prove the **ACP backend actually answers** — the bridge spawns Claude through the pi provider path and a real turn comes back — run `LIVE=1 entwurf smoke-acp-provider-live` from an installed package/clone with pi and Claude auth available. Package-source routing is pinned deterministically by `run.sh check-package-source-routing`, which runs inside `pnpm check` and the release gate.
203
+ `entwurf check-bridge` proves the MCP surface boots without backend auth. A real ACP
204
+ turn requires `LIVE=1 entwurf smoke-acp-provider-live`; the full release protocol and
205
+ host evidence boundaries are [VERIFY.md](./VERIFY.md) and [BASELINE.md](./BASELINE.md).
460
206
 
461
- > **Extension set — do not filter.** `entwurf` ships three `pi.extensions` entries as a single set: the ACP provider extension (`pi-extensions/acp-provider.ts`) plus `pi-extensions/entwurf-control.ts` and `pi-extensions/model-lock.ts`. Filtering some out via pi's object-form package configuration can leave the model lock or entwurf-control surface in a broken state. Disable the entire package or none of it unless you know precisely which boundary you are turning off.
207
+ > **Extension set — do not filter.** The ACP provider, `entwurf-control`, and
208
+ > `model-lock` extensions ship as one set. Disable the package as a whole rather than
209
+ > filtering individual entries into a partially wired state.
462
210
 
463
211
  ### Backend prerequisites
464
212
 
@@ -468,20 +216,31 @@ plain MCP registrations.
468
216
  2. **`require.resolve(...)` against the bundled package dependency** (`@agentclientprotocol/claude-agent-acp`). This is the default path; no extra global install needed.
469
217
  3. **`PATH:claude-agent-acp` fallback** — used when the package resolution fails (e.g. a hand-edited `node_modules`).
470
218
 
471
- The curated model registry exposes the unprefixed Claude ids plus the `cortex-` prefixed Cortex rows (below). Codex is *not* an ACP backend here — a native Codex session is already a first-class garden citizen via direct injection, so it needs no ACP plugin (see [AGENTS.md](./AGENTS.md)).
219
+ The curated model registry exposes unprefixed Claude ids plus `cortex-` rows.
220
+ Codex is not an ACP backend or a shipped managed citizen lane: it has verified
221
+ native-delivery probe evidence only. Release 0.14.0 does not add Codex support;
222
+ any future managed lane still owes lifecycle, identity, installation, and doctors
223
+ before calling it supported.
472
224
 
473
- **Snowflake Cortex Code is the second landed ACP backend** (0.13.0; audit record and contract: [docs/acp-backend-rail.md](./docs/acp-backend-rail.md) §11-8). Operator surface:
225
+ **Snowflake Cortex Code is the second ACP backend** (contract and audit:
226
+ [docs/acp-backend-rail.md](./docs/acp-backend-rail.md#cortex-code-audit-d1d10)). Curated ids are
227
+ `cortex-auto`, `cortex-claude-opus-5`, `cortex-claude-sonnet-5`, and
228
+ `cortex-openai-gpt-5.4`.
474
229
 
475
- - **Curated ids (4):** `cortex-auto`, `cortex-claude-opus-5`, `cortex-claude-sonnet-5`, `cortex-openai-gpt-5.4`. The `cortex-` prefix routes; the model is enforced per turn via ACP set-model, so an id the running cortex no longer serves fails loud before the prompt.
476
- - **CLI + auth:** `cortex` must be on PATH and already authenticated through Cortex's **own web-login flow** (there is no `cortex auth` subcommand; entwurf never supplies or proxies the Snowflake credential — the overlay reaches it by **symlinking through** exactly `connections.toml`, optional `config.toml`, and `cortex/cache/credential_cache`, and nothing else. The narrowing is *which paths are reachable* (§11-8 D5), not a read-only mount: a symlink carries no write protection, so the child holds the same access to those three paths that it would have with the operator's own HOME. What the overlay removes is the rest of the operator's **home** surface — the rest of `cortex/cache`, `~/.claude`/`~/.cortex` skills, home-anchored hooks and settings, and the operator's `cortex/mcp.json`. It does **not** reach cortex's *bundled* plugin/hook surface, which fires from the CLI's own install directory and is outside any HOME the overlay controls — that stays a host fact, not a containment claim (§11-8 D1).)
477
- - **Connection:** pin a Snowflake connection with `entwurfProvider.cortexConnection` in settings or per-shell via `ENTWURF_ACP_CORTEX_CONNECTION` (env wins). `entwurfProvider.backend: "cortex"` is an optional diagnostic guard, never the router.
478
- - **`CORTEX_HOME` must be unset.** It outranks `SNOWFLAKE_HOME` inside cortex and would bypass the dual-HOME containment, so the adapter refuses the spawn when it is present at all (empty string included).
479
- - **Containment:** each session runs under an isolated HOME (operator-global `~/.claude`/`~/.cortex` skills, hooks and settings are structurally unreachable; explicit `<cwd>/.claude` project scope is retained), with the explicit `entwurfProvider.mcpServers` projected into an overlay-private `cortex/mcp.json` cortex's ACP server ignores the wire `mcpServers` parameter, so this projection is how tools reach a cortex session. Only the `entwurf-bridge` entry gets the real operator HOME back (the garden store axis).
480
- - **Live check:** `LIVE=1 ENTWURF_ACP_CORTEX_CONNECTION=<conn> entwurf smoke-acp-cortex-live` (or `./run.sh …` from a clone) drives one real cortex turn: outbound `entwurf_v2` delivery as `entwurf/<cortex model>`, overlay disk facts, and process-group reclaim. It is deliberately **on-demand** — which is a statement about *wiring*, not about *whether cortex needs live evidence*:
230
+ The operator supplies an authenticated `cortex` CLI and selects a connection with
231
+ `entwurfProvider.cortexConnection` or `ENTWURF_ACP_CORTEX_CONNECTION`. `CORTEX_HOME`
232
+ must be absent: the adapter refuses it because it would bypass the session-scoped
233
+ HOME containment. Explicit MCP servers are projected into the overlay-private
234
+ `cortex/mcp.json`; only the bridge receives the real operator HOME needed for the
235
+ garden store.
481
236
 
482
- - The aggregate floor (`LIVE=1 ./run.sh release-gate`) stays claude-only, so a host with no cortex install or no Snowflake auth cannot redden a release for a backend it does not run.
483
- - That is **not** a claim that cortex ships on deterministic evidence alone. Accepting a cut that ships cortex means running this smoke deliberately, and reading its result — the aggregate gate will not run it for you, and its silence is not a pass.
484
- - Cortex's always-on axis is the deterministic `check-acp-cortex`, which does ride `pnpm check`.
237
+ `check-acp-cortex` runs in `pnpm run check:full`. Real acceptance is deliberately on demand:
238
+
239
+ ```bash
240
+ LIVE=1 ENTWURF_ACP_CORTEX_CONNECTION=<conn> entwurf smoke-acp-cortex-live
241
+ ```
242
+
243
+ The aggregate Claude floor does not run this smoke, so silence is not a Cortex PASS.
485
244
 
486
245
  ### Emacs frontends
487
246
 
@@ -538,135 +297,34 @@ Reference shape lives in [`pi/settings.reference.json`](./pi/settings.reference.
538
297
 
539
298
  **Which keys reach which backend.** `entwurfProvider` is one block for both backends, but its keys are not universal. `tools` / `permissionAllow` / `disallowedTools` / `settingSources` / `skillPlugins` / `appendSystemPrompt` are Claude's declaration surface: they do not shape a cortex session, which runs its own native tools and reaches MCP through the overlay-private `mcp.json` projection instead. They are not inert, though — the bridge still reads `tools` for its backend-invariant exclude-tools preflight and folds all of them into the config signature, so editing one still forces a fresh cortex session. `cortexConnection` is cortex-only. `mcpServers` is the one declaration surface that reaches both, each through its own transport. (`compaction` is a *top-level* pi key, not an `entwurfProvider` one — it is pi's own transcript policy and is not a backend knob at all; see §Compaction.) Unknown and retired keys are ignored rather than rejected, so a key aimed at the wrong backend fails silently: if a cortex session does not show the surface you configured, check that the key is one cortex actually consumes before suspecting the bridge.
540
299
 
541
- ### Wiring `entwurf-bridge` into an external MCP host
542
-
543
- `entwurf-bridge` can also be registered in a separate MCP-aware harness (Claude Code, Codex CLI, Antigravity/`agy`, …). That host does **not** become a pi session and does **not** need to be ACP-backed. There are now two honest cases:
544
-
545
- - **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.
546
- - **garden-native native session**: a trusted lifecycle hook minted a garden id and sender marker — `SessionStart` for Claude Code, `PreInvocation` for agy. It is not a pi control-socket session, but it can be replyable by garden id when its own mailbox/probe rail says so.
547
-
548
- **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, mailbox-backed Claude Code, 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). Fresh sibling creation from nothing is a deferred lane. (The old v1 verbs `entwurf` / `entwurf_resume` / `entwurf_send` are gone.)
300
+ ### External MCP registration
549
301
 
550
- Observed: Claude Code, Codex CLI, and Antigravity CLI 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 becomes symmetric/replyable through its mailbox-backed meta-session; 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.
302
+ `entwurf-bridge` can also be registered in a separate MCP-aware harness (Claude Code,
303
+ Codex CLI, Antigravity). Two shapes exist and they are not interchangeable:
551
304
 
552
- Prerequisites on the host running the external MCP client:
553
-
554
- - `pi` on PATH (for the `owned-outcome` spawn-bg resume path).
555
- - 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`.
556
-
557
- > **PATH boundary.** MCP servers are often launched by GUI/editor daemons and may not inherit the interactive shell's PATH. If `pi` works in your terminal but an external-host `entwurf_v2` spawn-bg resume fails with `spawn pi 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 `pi`. `start.sh` sources only the explicit `ENTWURF_BRIDGE_ENV_FILE`; it never reads personal dotfiles automatically.
558
-
559
- Example env file:
305
+ - **plain external MCP host** no garden meta-record or sender marker. It can read the
306
+ surfaces, but `entwurf_v2` is **refused by default**: there is no authoritative sender.
307
+ - **garden-native session** a trusted lifecycle hook minted a garden id, so it is
308
+ addressable and replyable by that id.
560
309
 
561
310
  ```bash
562
- # ~/.config/entwurf-bridge/env.sh
563
- export PATH="$HOME/.local/share/pnpm:$HOME/.local/bin:$HOME/.nvm/versions/node/v24.15.0/bin:$PATH"
564
- ```
565
-
566
- Then add it to the external MCP config:
567
-
568
- ```json
569
- {
570
- "env": {
571
- "ENTWURF_BRIDGE_ENV_FILE": "/home/operator/.config/entwurf-bridge/env.sh",
572
- "ENTWURF_BRIDGE_EXTERNAL_AGENT_ID": "external-mcp/claude-code"
573
- }
574
- }
311
+ claude mcp add --scope user entwurf-bridge entwurf-bridge
575
312
  ```
576
313
 
577
- **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.
578
-
579
- 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.
580
-
581
- External/meta-session semantics:
582
-
583
- - `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.
584
- - `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) 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.
585
- - `entwurf_v2` with `intent: "owned-outcome"` to a dormant pi target needs `pi` on PATH (it spawns a `pi --entwurf-control` resume child); async completion followUp requires a replyable pi control-socket caller.
586
- - `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.
587
-
588
- #### Claude Code
589
-
590
- Claude Code supports both CLI registration and a separated global MCP config. The separated file is recommended for dotfile / `agent-config` workflows because `~/.claude.json` also carries OAuth-bearing state.
591
-
592
- **Option A — CLI add:**
593
-
594
- ```bash
595
- claude mcp add --scope user entwurf-bridge \
596
- bash /absolute/path/to/entwurf/mcp/entwurf-bridge/start.sh
597
- ```
598
-
599
- This writes the entry into `~/.claude.json`'s top-level `mcpServers`. Good for one-off setup; do not version-control the resulting `~/.claude.json`.
600
-
601
- **Option B — separated `~/.mcp.json`:**
602
-
603
- ```json
604
- {
605
- "mcpServers": {
606
- "entwurf-bridge": {
607
- "type": "stdio",
608
- "command": "bash",
609
- "args": [
610
- "/absolute/path/to/entwurf/mcp/entwurf-bridge/start.sh"
611
- ],
612
- "env": {
613
- "ENTWURF_BRIDGE_EXTERNAL_AGENT_ID": "external-mcp/claude-code"
614
- }
615
- }
616
- }
617
- }
618
- ```
619
-
620
- Claude Code reads `~/.mcp.json` in addition to `~/.claude.json`'s top-level `mcpServers`. The `env` block identifies the calling host on the receiver render — omit it and `entwurf_v2` shows `external-mcp/unknown-host`. If Claude Code permissions are locked down, allow `mcp__*` or `mcp__entwurf-bridge__*` in `~/.claude/settings.json`.
621
-
622
- #### Codex CLI
623
-
624
- Add the server to `~/.codex/config.toml`:
625
-
626
- ```toml
627
- [mcp_servers.entwurf-bridge]
628
- command = "/absolute/path/to/entwurf/mcp/entwurf-bridge/start.sh"
629
- ```
630
-
631
- 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).
632
-
633
- #### Antigravity CLI (`agy`)
634
-
635
- Use the managed install surface rather than editing agy's files by hand:
636
-
637
- ```bash
638
- entwurf install-agy-bridge
639
- entwurf install-agy-statusline
640
- entwurf install-agy-hooks
641
-
642
- entwurf doctor-agy-bridge
643
- entwurf doctor-agy-statusline
644
- entwurf doctor-agy-hooks
645
- ```
646
-
647
- The three adapters deliberately own different atoms:
648
-
649
- - bridge: one `entwurf-bridge` server in `~/.gemini/config/mcp_config.json`, plus one narrow permission string per tool the normal agy workflow calls — `mcp(entwurf-bridge/entwurf_v2)`, `mcp(entwurf-bridge/entwurf_peers)`, `mcp(entwurf-bridge/entwurf_self)` — in `~/.gemini/antigravity-cli/settings.json`. agy defaults every `mcp` action to Ask, so a tool that ships without its own rule stops for a y/n on every call; `entwurf_inbox_read` is deliberately not granted (native-push has no inbox) and neither is the manual `entwurf_register_native` fallback;
650
- - statusline: the complete `statusLine` subtree pointing at the bare stable bin `entwurf-agy-statusline`;
651
- - hooks: one named `PreInvocation` hook pointing at the bare stable bin `entwurf-agy-imprint`.
652
-
653
- Unrelated servers, permissions, settings, and hooks are preserved; every adapter has a state-backed honest inverse and refuses symlink-owned SSOTs. The installer never grants broad `command(*)`, `unsandboxed(*)`, or other YOLO policy — those remain operator decisions.
654
-
655
- The **global** MCP config live agy reads is `~/.gemini/config/mcp_config.json`. `~/.gemini/antigravity-cli/mcp_config.json` is not the global MCP root; the bridge installer one-way cleans only a stale entwurf-owned entry there. After the first model invocation, the imprint hook binds the native `conversationId` to a garden id, the statusline shows `🪛 <garden-id> agy`, and sends from that MCP child carry `agentId=meta-session/antigravity` with `replyable:true` only when the record exists and the live native-push probe succeeds.
656
-
657
- #### External-host skills and commands
658
-
659
- MCP registration gives the external harness the tools; the host still needs workflow guidance. Put the Mitsein-over-MCP (cross-harness collaboration) rules in that host's instruction file or, when supported, as a host-native skill. Do not assume pi slash commands are portable across external hosts — if a workflow must work across Claude Code, Codex CLI, Antigravity, and future hosts, make it a skill or MCP tool rather than a command shortcut.
660
-
314
+ Per-harness registration (Claude Code `~/.mcp.json`, Codex `~/.codex/config.toml`, the
315
+ managed `install-agy-*` surfaces), the PATH/env boundary for GUI-launched MCP servers, the
316
+ anonymous-sender hatch, and the full external/meta-session semantics are in
317
+ [docs/external-mcp-host.md](./docs/external-mcp-host.md).
661
318
  For the maintained multi-harness setup and skill/command packaging details, see `agent-config`. See also the MCP entry in [Concept primer](#concept-primer), the sender envelope contract in [AGENTS.md](./AGENTS.md), and [Custom skills](#custom-skills) for the in-pi ACP skill surface.
662
319
 
663
320
  ## Per-backend operating surface
664
321
 
665
322
  The Claude ACP backend keeps its native model / API / tools; entwurf shapes only what enters from pi. Claude honors an explicit `CLAUDE_CONFIG_DIR` export when set by the operator.
666
323
 
667
- **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. The four-tool baseline (`Read`, `Bash`, `Edit`, `Write`) is enforced through `tools` + `permissionAllow`; `Skill` is added automatically when `skillPlugins` is non-empty. Operator context cap override: `ENTWURF_ACP_CLAUDE_CONTEXT=<int>`.
324
+ **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>`.
668
325
 
669
- (Codex is *not* an ACP backend here — it reaches the garden natively. The governed-CLI lane is open, not later: Snowflake Cortex Code landed on it in 0.13.0, and a third governed backend would join the same adapter rail.)
326
+ Codex is not an ACP backend here. Its native delivery probe remains separate from
327
+ the governed ACP adapter rail and does not yet constitute a managed garden citizen.
670
328
 
671
329
  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.
672
330
 
@@ -675,7 +333,8 @@ entwurf owns **no** memory layer at all — the ACP plugin's boundary explicitly
675
333
  ## Smoke commands
676
334
 
677
335
  ```bash
678
- pnpm check # full deterministic floor (all check-* gates, incl. check-acp-*)
336
+ pnpm check # everyday core (prints wall time; <=60s on the reference host)
337
+ pnpm run check:full # full deterministic floor (adds the hermetic + package/install tiers)
679
338
  ./run.sh check-bridge # entwurf-bridge direct MCP smoke (no backend auth)
680
339
  ./run.sh smoke-agy-install-state # agy MCP + exact permission ownership lifecycle (install/uninstall/doctor/inverse)
681
340
  ./run.sh smoke-agy-statusline-state # agy ambient garden-id install surface
@@ -698,11 +357,13 @@ LIVE=1 ./run.sh smoke-acp-provider-live # real pi provider path + progre
698
357
  LIVE=1 ./run.sh smoke-acp-session-reuse-live # process-scoped reuse + codeword recall (S2d)
699
358
  LIVE=1 ./run.sh smoke-acp-carrier-augment-live # augment delivery + empty-carrier billing clean (S2e-1)
700
359
 
701
- LIVE=1 ./run.sh release-gate /tmp/scratch # the single cut gate (MUST + BEHAVIOR, SKIP=0 for a real cut)
360
+ LIVE=1 ./run.sh release-gate /tmp/scratch --cut # the single cut gate (MUST + BEHAVIOR; --cut refuses any MUST SKIP)
361
+ LIVE=1 ENTWURF_ACP_CORTEX_CONNECTION=<conn> ./run.sh smoke-acp-cortex-live # Cortex is on-demand: the aggregate does not re-certify it
702
362
  ```
703
363
 
704
- `pnpm check` already includes the two maintainer gates: the AGY permission contract
705
- matrix and the full committed-mutant gate qualification run on every pass. A gate a
364
+ `pnpm run check:full` includes the AGY permission contract matrix; the committed-mutant
365
+ gate qualification is scheduled separately (`./run.sh check-gate-qualification` — the CI
366
+ `check` job runs it on every push, and release-gate carries it as a MUST step). A gate a
706
367
  release touches must kill its known defect for the claimed `[QK:<claim>]` reason —
707
368
  the descriptions above name what each smoke covers, and no check count is quality
708
369
  evidence on its own. Gate qualification needs the git work surface, while the matrix
@@ -744,9 +405,9 @@ For a real consumer arranging many skills, see [agent-config](https://github.com
744
405
 
745
406
  **Entwurf is one dispatch capability with native-pi and MCP surfaces.** Native pi exposes it directly as extension tools; ACP-backed and external native-harness sessions reach it through `entwurf-bridge`. The purpose is not to invent a different sub-agent system, but to preserve the same sibling-based model across harnesses.
746
407
 
747
- A sibling has its own runtime boundary and its own provider/model identity — not a worker, delegate, or sub-agent. Minting a brand-new sibling from nothing is a deferred v2 lane (`spawn-fresh`); today every transport targets an *existing* garden citizen. `entwurf_v2` routes from rail-specific liveness + intent: live pi fire-and-forget → control socket; dormant pi owned-outcome spawn-bg resume; active self-fetch → meta-mailbox; probe-alive agy → native-push. A **control-socket-domain** dispatch takes the per-target lock both the live send and the dormant cell's spawn-bg resume; mailbox and native-push use their own deliverability evidence and remain lock-free.
408
+ A sibling has its own runtime boundary and its own provider/model identity — not a worker, delegate, or sub-agent. Today every transport targets an *existing* garden citizen, and none of them starts a process. `entwurf_v2` routes from rail-specific liveness: live pi fire-and-forget → control socket; dormant pi → an honest reject; active self-fetch → meta-mailbox; probe-alive agy → native-push. A **control-socket-domain** dispatch takes the per-target lock, which now serializes concurrent live sends at one garden id; mailbox and native-push use their own deliverability evidence and remain lock-free.
748
409
 
749
- A two-pane recording covers the surface end-to-end — sibling resume, cross-process MCP dispatch across a different cwd, and a live peer greeting:
410
+ A two-pane recording covers the pre-0.12 v1 surface end-to-end — sibling resume, cross-process MCP dispatch across a different cwd, and a live peer greeting. It is **archived evidence**: it shows a resume verb this repo no longer has (see `demo/README.md`).
750
411
 
751
412
  <details>
752
413
  <summary>Watch (2131×1142 GIF, click to expand)</summary>
@@ -757,29 +418,26 @@ A two-pane recording covers the surface end-to-end — sibling resume, cross-pro
757
418
 
758
419
  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.)
759
420
 
760
- In ACP-backed and external native-harness sessions, `entwurf-bridge` exposes five tools: `entwurf_v2`, `entwurf_peers`, `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` 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 / dormant-resume / Claude-Code-meta-mailbox / Antigravity-native-push. Fresh sibling creation from nothing is a deferred lane. (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` resumes an already-identified record-backed citizen, never a model tuple from a file.
421
+ 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 / Claude-Code-meta-mailbox / 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` or `claude-code`) as a visible window in the operator's own tmux session, requires an explicit model, passes that model on the runtime CLI, and hands it a first task; the sibling's first action is a nonce callback whose sender envelope carries its garden id. The launch receipt records the requested model plus tmux coordinates and never claims that the runtime accepted the model 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 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.
761
422
 
762
423
  ### `entwurf_v2` — canonical dispatch verb
763
424
 
764
- `entwurf_v2` / `runEntwurfV2` is the canonical v2 dispatch verb over **existing** garden targets — record-backed citizens only (#50 C4: the record is the sole address authority; a record-less control socket rejects pre-probe as `record-less-socket`, a diagnostic state, never a delivery target). You give a target garden id plus an intent (`fire-and-forget` or `owned-outcome`); one decider reads the target's liveness as a fact and picks the transport from a frozen table keyed on **both** the target's state **and** the intent — never on state alone — then reports one outcome under the v2 lock policy. A **control-socket-domain** dispatch takes a per-target lock — the live control-socket send and the dormant cell's spawn-bg resume alike, even though spawn-bg is a separate relaunch transport; mailbox and native-push are lock-free, with deliverability guarded by their own receiver/probe evidence:
425
+ `entwurf_v2` / `runEntwurfV2` is the canonical v2 dispatch verb over **existing** garden targets — record-backed citizens only (#50 C4: the record is the sole address authority; a record-less control socket rejects pre-probe as `record-less-socket`, a diagnostic state, never a delivery target). You give a target garden id plus an intent (`fire-and-forget` the axis is single-valued since the visible-first cut); one decider reads the target's liveness as a fact and picks the transport from a frozen table keyed on **both** the target's state **and** the intent — never on state alone — then reports one outcome under the v2 lock policy. A **control-socket-domain** dispatch takes a per-target lock; mailbox and native-push are lock-free, with deliverability guarded by their own receiver/probe evidence:
765
426
 
766
427
  | target state | intent | transport |
767
428
  |---|---|---|
768
429
  | live pi | fire-and-forget | control-socket send |
769
- | live pi | owned-outcome | **reject** (a live peer is not an owned spawn target) |
770
- | dormant pi | owned-outcome | spawn-bg resume (a real `pi --entwurf-control` child) |
771
- | dormant pi | fire-and-forget | **reject** (`dormant-fire-forget-unsupported`) |
430
+ | dormant pi | fire-and-forget | **reject** (`dormant-fire-forget-unsupported` nothing is launched) |
431
+ | any pi | indeterminate probe | **reject** (`indeterminate-no-spawn` an unestablished probe is not a measured death) |
772
432
  | active self-fetch receiver | fire-and-forget | meta-mailbox enqueue + doorbell |
773
433
  | inactive / terminated self-fetch receiver | fire-and-forget | **reject** (`mailbox-undeliverable` — no `.msg`, no doorbell) |
774
- | self-fetch | owned-outcome | **reject** (no owned result over a mailbox) |
775
434
  | live native-push conversation | fire-and-forget | native-push direct injection |
776
435
  | dead / indeterminate native-push conversation | fire-and-forget | **reject** (`native-push-target-dead` / `native-push-probe-indeterminate`) |
777
- | native-push | owned-outcome | **reject** (`native-push-no-resume-authority`) |
778
436
  | record-less control socket (no meta-record) | any | **reject** (`record-less-socket` — pre-probe; diagnostic state, #50 C4) |
779
437
 
780
438
  **`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, 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.
781
439
 
782
- 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. What it does **not** do is **fresh sibling creation** minting a brand-new sibling from a provider/model/prompt is a deferred lane (the `dormant pi spawn-bg resume` row above resumes an *already-identified* citizen, it does not mint one). 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. Claude↔Claude / Claude tmux-live transport is a later lane (the contract enum names `tmux-live` but no production path executes it).
440
+ 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 or Claude Code runtime visibly in the caller's tmux session with a required explicit model passed in that runtime's measured CLI dialect, returns only a synchronous launch receipt, and lets the sibling report its new address asynchronously through the sender envelope of a nonce callback. 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.
783
441
 
784
442
  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.
785
443
 
@@ -793,7 +451,7 @@ A `--entwurf-control` session needs **no special launcher** (#50 C2): pi mints i
793
451
  pi --entwurf-control
794
452
  ```
795
453
 
796
- **Resuming an existing garden session.** Addressing is record-only: `entwurf_v2` with `intent: owned-outcome` resumes a dormant record-backed citizen via spawn-bg (`--session <transcriptPath>` under the hood, resolved from the recordnever a header scan). Operators re-opening a session by hand use pi's own `--session` picker; the reopened session re-attaches to the same record (same garden id) at `session_start`.
454
+ **Resuming an existing garden session.** Use `entwurf_resume_call {target}`. It reopens a DORMANT pi citizen under the SAME garden id in a visible window in the caller's own tmux session, resolving the transcript, model, provider and cwd from the record — so it takes only the target id, and it runs no turn: the window comes back with the conversation and waits, and talking to it is still `entwurf_v2`. Two receipts arrive and mean different things: a LAUNCH receipt (tmux made a window and was asked to start pi) and an OBSERVATION receipt (the control socket answered under the same id, or `resume-unobserved`). Unobserved is a real outcome, not an error to retry the window is visible, so read it. A citizen that is already live is refused; so is a non-pi target (`target-not-pi`), because only pi stands a control socket up. The predecessor, `entwurf_v2 intent=owned-outcome`, resumed by launching a hidden window-less background child and was withdrawn under the visible-first rule; delivery still starts no process. Identity preconditions live in `resume-launch-identity.ts`, gated by `check-resume-launch-identity`.
797
455
 
798
456
  **Starting a new session in-process — pi's own `/new`.** Since the #50 C2 cut there is nothing to replace it with: `/new`, `/fork`, `/clone` and RPC session replacement are pi's again. The replacement session fires `session_start`, which upserts its own meta-record and rebinds the control socket to that record's garden id; the old socket is dropped. pi's session id (a uuidv7) is recorded as the citizen's `nativeSessionId` and is never an address. Gate: `run.sh smoke-resident-garden-guard` REPLACEMENT section (0-token RPC E2E).
799
457
 
@@ -803,6 +461,8 @@ System / developer carriers and rich pi context are separate.
803
461
 
804
462
  The carrier holds an optional short operator engraving; empty or missing is fine. The runtime default is the bundled `pi-extensions/lib/acp/prompts/engraving.md` (the `# Engraving Here` placeholder, pinned non-empty by a gate); [`prompts/engraving.md`](./prompts/engraving.md) is a documented sample you copy and point the runtime at with `ENTWURF_ACP_ENGRAVING_PATH=/path/to/alt.md`. Template variables: `{{backend}}`, `{{mcp_servers}}`. Do not put AGENTS.md, bridge narrative, or tool catalogs here — large Claude carriers can route OAuth sessions to metered "extra usage" billing.
805
463
 
464
+ Your file's own leading and trailing whitespace is trimmed, and the loader then opens the carrier with one blank line. That boundary is not cosmetic: the Claude Agent SDK prefixes its own fixed identity sentence and concatenates the carrier onto it with nothing in between, so without it the engraving's first line reads as the tail of the SDK's sentence (measured 2026-07-31 as `You are a Claude agent, built on Anthropic's Claude Agent SDK.# Engraving Here`). Do not try to supply the boundary from inside the markdown — it is trimmed away before it reaches the wire.
465
+
806
466
  Bridge identity, pi context, `~/AGENTS.md`, `cwd/AGENTS.md`, and date/cwd ride a one-shot first-user prepend (`pi-context-augment.ts`). Entwurf prompts already carry `cwd/AGENTS.md` inside `<project-context ...>`; the augment removes that duplicate. The augment describes capabilities, but the **actual callable schema remains source of truth** — `read` vs `Read` vs `exec_command`, MCP only when schema-visible.
807
467
 
808
468
  ## Compaction policy
@@ -815,7 +475,7 @@ The footer uses ACP `usage_update.used / size` (backend prompt/tools/cache/sessi
815
475
 
816
476
  Owns: provider registration (`entwurf/...`), ACP subprocess lifecycle + `resume > load > new`, prompt forwarding + ACP event mapping, the bridge surface that exposes pi capabilities such as entwurf to ACP-backed sessions, pi-facing MCP injection via `entwurfProvider.mcpServers`, and bridge-local cleanup and diagnostics.
817
477
 
818
- Does not: reconstruct full history, hydrate backend transcripts into pi history, emulate Claude Code or Codex, run broad multi-agent orchestration (entwurf is narrow, registry-gated, identity-locked), or run a second session model competing with pi.
478
+ Does not: reconstruct full history, hydrate backend transcripts into pi history, emulate Claude Code or Codex, run broad multi-agent orchestration (entwurf is narrow, record-addressed, identity-locked), or run a second session model competing with pi.
819
479
 
820
480
  Only `pi:<sessionId>` mappings are persisted (`~/.pi/agent/cache/entwurf/sessions/`) — enough to re-attach pi to the same remote ACP session, never enough to act as a second harness. Backend stores (`~/.claude/`, `~/.codex/`) are interoperability side effects, not authority.
821
481