@junghanacs/entwurf 0.13.0 → 0.13.1

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 (72) hide show
  1. package/AGENTS.md +5 -3
  2. package/BASELINE.md +57 -203
  3. package/CHANGELOG.md +19 -0
  4. package/CONTRIBUTING.md +1 -1
  5. package/DELIVERY.md +117 -261
  6. package/README.md +68 -408
  7. package/VERIFY.md +58 -87
  8. package/demo/README.md +1 -1
  9. package/docs/acp-backend-rail.md +231 -0
  10. package/docs/external-mcp-host.md +132 -0
  11. package/docs/fresh-cut-policy.md +99 -0
  12. package/docs/setup-clean-host.md +123 -328
  13. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +21 -48
  14. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/acp-client.js +12 -0
  15. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/backend-adapter.js +11 -10
  16. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/config.js +1 -1
  17. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/engraving.js +42 -1
  18. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/models.js +4 -4
  19. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/overlay.js +3 -3
  20. package/mcp/entwurf-bridge/src/index.ts +21 -48
  21. package/package.json +12 -9
  22. package/pi-extensions/entwurf-control.ts +20 -49
  23. package/pi-extensions/lib/acp/acp-client.ts +22 -0
  24. package/pi-extensions/lib/acp/augment.ts +42 -3
  25. package/pi-extensions/lib/acp/backend-adapter.ts +12 -11
  26. package/pi-extensions/lib/acp/backend.ts +305 -36
  27. package/pi-extensions/lib/acp/config.ts +1 -1
  28. package/pi-extensions/lib/acp/engraving.ts +43 -1
  29. package/pi-extensions/lib/acp/event-mapper.ts +16 -13
  30. package/pi-extensions/lib/acp/models.ts +4 -4
  31. package/pi-extensions/lib/acp/overlay.ts +3 -3
  32. package/prompts/engraving.md +13 -5
  33. package/run.sh +244 -108
  34. package/scripts/check-acp-carrier-augment.ts +246 -10
  35. package/scripts/check-acp-cortex.ts +5 -5
  36. package/scripts/check-acp-prompt-lifecycle.ts +565 -0
  37. package/scripts/check-acp-sdk-surface.ts +60 -21
  38. package/scripts/check-acp-stop-reason.ts +342 -0
  39. package/scripts/check-entwurf-v2-surface.ts +41 -0
  40. package/scripts/check-gate-qualification.ts +6 -3
  41. package/scripts/check-probe-ordering.ts +39 -7
  42. package/scripts/check-release-gate-outcomes.ts +332 -0
  43. package/scripts/lib/live-skip.ts +33 -0
  44. package/scripts/lib/probe-acp-turn.ts +33 -12
  45. package/scripts/lib/step-outcome.sh +88 -0
  46. package/scripts/mutants/acp-augment.json +106 -0
  47. package/scripts/mutants/acp-cortex.json +2 -2
  48. package/scripts/mutants/acp-prompt-lifecycle.json +100 -0
  49. package/scripts/mutants/acp-stop-reason.json +80 -0
  50. package/scripts/mutants/probe-ordering.json +27 -5
  51. package/scripts/mutants/release-gate.json +105 -0
  52. package/scripts/mutants/v2-surface.json +26 -15
  53. package/scripts/smoke-acp-bundled-mcp-live.ts +4 -3
  54. package/scripts/smoke-acp-carrier-augment-live.ts +2 -2
  55. package/scripts/smoke-acp-cortex-live.ts +8 -14
  56. package/scripts/smoke-acp-long-turn-live.ts +185 -0
  57. package/scripts/smoke-acp-mcp-live.ts +2 -2
  58. package/scripts/smoke-acp-memory-containment-live.ts +2 -2
  59. package/scripts/smoke-acp-ordering-probe-live.ts +2 -2
  60. package/scripts/smoke-acp-overlay-live.ts +2 -2
  61. package/scripts/smoke-acp-provider-live.ts +2 -2
  62. package/scripts/smoke-acp-raw-turn-live.ts +3 -3
  63. package/scripts/smoke-acp-session-reuse-live.ts +2 -2
  64. package/scripts/smoke-acp-skill-live.ts +2 -2
  65. package/scripts/smoke-acp-socket-citizen-live.ts +4 -3
  66. package/scripts/smoke-acp-v2-send-live.ts +4 -3
  67. package/scripts/smoke-agy-native-push-live.ts +4 -3
  68. package/scripts/smoke-claude-native-resume-live.sh +13 -3
  69. package/scripts/smoke-entwurf-chain-live.ts +352 -0
  70. package/scripts/smoke-entwurf-v2-matrix-live.ts +2 -2
  71. package/scripts/smoke-entwurf-v2-spawn-resume-live.ts +2 -4
  72. package/scripts/smoke-meta-install-state.sh +4 -0
package/README.md CHANGED
@@ -10,16 +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 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
@@ -66,12 +57,6 @@ 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.
@@ -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.83.0 <0.84`). Then point pi at
163
148
  the npm-installed package or development clone:
164
149
 
165
150
  ```bash
@@ -177,269 +162,18 @@ as release-critical for the ACP/plugin lane. A host that only uses
177
162
  `entwurf-bridge` from Claude Code / Codex / Antigravity does not need pi until it
178
163
  tries an `owned-outcome` spawn-bg resume target.
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,30 @@ 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, and the reserved 0.14.0 work must turn that evidence
222
+ into lifecycle, identity, installation, and doctors before calling it supported.
472
223
 
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:
224
+ **Snowflake Cortex Code is the second ACP backend** (contract and audit:
225
+ [docs/acp-backend-rail.md](./docs/acp-backend-rail.md#cortex-code-audit-d1d10)). Curated ids are
226
+ `cortex-auto`, `cortex-claude-opus-5`, `cortex-claude-sonnet-5`, and
227
+ `cortex-openai-gpt-5.4`.
474
228
 
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*:
229
+ The operator supplies an authenticated `cortex` CLI and selects a connection with
230
+ `entwurfProvider.cortexConnection` or `ENTWURF_ACP_CORTEX_CONNECTION`. `CORTEX_HOME`
231
+ must be absent: the adapter refuses it because it would bypass the session-scoped
232
+ HOME containment. Explicit MCP servers are projected into the overlay-private
233
+ `cortex/mcp.json`; only the bridge receives the real operator HOME needed for the
234
+ garden store.
481
235
 
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`.
236
+ `check-acp-cortex` runs in `pnpm check`. Real acceptance is deliberately on demand:
237
+
238
+ ```bash
239
+ LIVE=1 ENTWURF_ACP_CORTEX_CONNECTION=<conn> entwurf smoke-acp-cortex-live
240
+ ```
241
+
242
+ The aggregate Claude floor does not run this smoke, so silence is not a Cortex PASS.
485
243
 
486
244
  ### Emacs frontends
487
245
 
@@ -538,126 +296,24 @@ Reference shape lives in [`pi/settings.reference.json`](./pi/settings.reference.
538
296
 
539
297
  **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
298
 
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.)
299
+ ### External MCP registration
549
300
 
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.
301
+ `entwurf-bridge` can also be registered in a separate MCP-aware harness (Claude Code,
302
+ Codex CLI, Antigravity). Two shapes exist and they are not interchangeable:
551
303
 
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:
304
+ - **plain external MCP host** no garden meta-record or sender marker. It can read the
305
+ surfaces, but `entwurf_v2` is **refused by default**: there is no authoritative sender.
306
+ - **garden-native session** a trusted lifecycle hook minted a garden id, so it is
307
+ addressable and replyable by that id.
560
308
 
561
309
  ```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
- }
310
+ claude mcp add --scope user entwurf-bridge entwurf-bridge
575
311
  ```
576
312
 
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
-
313
+ Per-harness registration (Claude Code `~/.mcp.json`, Codex `~/.codex/config.toml`, the
314
+ managed `install-agy-*` surfaces), the PATH/env boundary for GUI-launched MCP servers, the
315
+ anonymous-sender hatch, and the full external/meta-session semantics are in
316
+ [docs/external-mcp-host.md](./docs/external-mcp-host.md).
661
317
  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
318
 
663
319
  ## Per-backend operating surface
@@ -666,7 +322,8 @@ The Claude ACP backend keeps its native model / API / tools; entwurf shapes only
666
322
 
667
323
  **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>`.
668
324
 
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.)
325
+ Codex is not an ACP backend here. Its native delivery probe remains separate from
326
+ the governed ACP adapter rail and does not yet constitute a managed garden citizen.
670
327
 
671
328
  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
329
 
@@ -698,7 +355,8 @@ LIVE=1 ./run.sh smoke-acp-provider-live # real pi provider path + progre
698
355
  LIVE=1 ./run.sh smoke-acp-session-reuse-live # process-scoped reuse + codeword recall (S2d)
699
356
  LIVE=1 ./run.sh smoke-acp-carrier-augment-live # augment delivery + empty-carrier billing clean (S2e-1)
700
357
 
701
- LIVE=1 ./run.sh release-gate /tmp/scratch # the single cut gate (MUST + BEHAVIOR, SKIP=0 for a real cut)
358
+ LIVE=1 ./run.sh release-gate /tmp/scratch --cut # the single cut gate (MUST + BEHAVIOR; --cut refuses any MUST SKIP)
359
+ 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
360
  ```
703
361
 
704
362
  `pnpm check` already includes the two maintainer gates: the AGY permission contract
@@ -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