@junghanacs/entwurf 0.14.2 → 0.15.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 (72) hide show
  1. package/AGENTS.md +8 -5
  2. package/CHANGELOG.md +79 -0
  3. package/DELIVERY.md +72 -59
  4. package/README.md +55 -4
  5. package/VERIFY.md +2 -2
  6. package/demo/README.md +1 -1
  7. package/docs/acp-backend-rail.md +1 -1
  8. package/docs/external-mcp-host.md +26 -4
  9. package/docs/setup-clean-host.md +3 -3
  10. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +8 -6
  11. package/mcp/entwurf-bridge/dist/pi-extensions/entwurf-capabilities.json +1 -0
  12. package/mcp/entwurf-bridge/dist/pi-extensions/lib/copilot-fresh-preflight.js +253 -0
  13. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-sender-identity.js +10 -2
  14. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js +102 -28
  15. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +77 -11
  16. package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-hook-copilot.js +288 -0
  17. package/mcp/entwurf-bridge/src/index.ts +10 -6
  18. package/mcp/entwurf-bridge/tsconfig.build.json +2 -1
  19. package/package.json +22 -11
  20. package/pi/copilot-receive/entwurf-receive/extension.mjs +323 -0
  21. package/pi/entwurf-capabilities.json +1 -0
  22. package/pi/meta-bridge-copilot/.claude-plugin/marketplace.json +11 -0
  23. package/pi/meta-bridge-copilot/entwurf-meta-receive-copilot/.claude-plugin/plugin.json +5 -0
  24. package/pi/meta-bridge-copilot/entwurf-meta-receive-copilot/hooks/hooks.json +7 -0
  25. package/pi/meta-bridge-copilot/entwurf-meta-receive-copilot/scripts/copilot-hook-launch.sh +85 -0
  26. package/pi-extensions/entwurf-control.ts +17 -8
  27. package/pi-extensions/lib/copilot-fresh-preflight.ts +251 -0
  28. package/pi-extensions/lib/meta-sender-identity.ts +10 -2
  29. package/pi-extensions/lib/meta-session.ts +102 -29
  30. package/pi-extensions/lib/mux-fresh-call.ts +82 -11
  31. package/pi-extensions/meta-bridge-hook-copilot.ts +323 -0
  32. package/run.sh +334 -16
  33. package/scripts/check-copilot-birth-hook.ts +497 -0
  34. package/scripts/check-copilot-launch.ts +395 -0
  35. package/scripts/check-copilot-receive-arm.ts +734 -0
  36. package/scripts/check-copilot-statusline.ts +122 -0
  37. package/scripts/check-entwurf-capabilities.ts +25 -6
  38. package/scripts/check-fresh-cut-gate.sh +92 -23
  39. package/scripts/check-gate-qualification.ts +7 -1
  40. package/scripts/check-install-container.sh +2 -2
  41. package/scripts/check-install-surface.ts +1 -1
  42. package/scripts/check-meta-capability-source.ts +89 -5
  43. package/scripts/check-meta-doctor-oracle.sh +26 -0
  44. package/scripts/check-meta-manifest-schema.py +38 -0
  45. package/scripts/check-meta-receiver-marker.ts +25 -2
  46. package/scripts/check-meta-session.ts +32 -0
  47. package/scripts/copilot-bridge-doctor.sh +209 -0
  48. package/scripts/copilot-bridge-install.sh +224 -0
  49. package/scripts/copilot-launch.sh +236 -0
  50. package/scripts/copilot-mcp-bridge.sh +177 -0
  51. package/scripts/copilot-mcp-config.py +221 -0
  52. package/scripts/copilot-receive-bridge.sh +483 -0
  53. package/scripts/copilot-statusline-bridge.sh +149 -0
  54. package/scripts/copilot-statusline-config.py +201 -0
  55. package/scripts/copilot-statusline.sh +68 -0
  56. package/scripts/dev-bin.sh +4 -1
  57. package/scripts/meta-bridge-hook-log.sh +17 -5
  58. package/scripts/mutants/capability-cache.json +64 -0
  59. package/scripts/mutants/copilot-birth.json +155 -0
  60. package/scripts/mutants/copilot-launch.json +187 -0
  61. package/scripts/mutants/copilot-receive.json +263 -0
  62. package/scripts/mutants/fresh-cut.json +17 -0
  63. package/scripts/mutants/mux-fresh-call.json +216 -2
  64. package/scripts/mutants/pack-install.json +17 -0
  65. package/scripts/raw-async-delivery/README.md +170 -125
  66. package/scripts/raw-async-delivery/copilot-enqueue-addressed.sh +35 -0
  67. package/scripts/raw-async-delivery/copilot-extension-receive/extension.mjs +123 -0
  68. package/scripts/smoke-copilot-mcp-state.sh +153 -0
  69. package/scripts/smoke-copilot-statusline-state.sh +131 -0
  70. package/scripts/smoke-mux-fresh-call-live.ts +2 -0
  71. package/scripts/smoke-mux-lifecycle-live.ts +3 -1
  72. package/scripts/tsconfig.json +1 -0
package/AGENTS.md CHANGED
@@ -28,9 +28,9 @@ For agents that own this repo: invariant principles and reproducible verificatio
28
28
  - **entwurf-core (v2)** owns garden-id addressing, peer facts, liveness interfaces, dispatch decisions, rail choice, and delivery evidence.
29
29
  - **Record authority** owns citizen identity. Every addressable citizen uses the same V3 `MetaIdentity` schema. `backend` selects capability/rail behavior; it does not create an identity hierarchy.
30
30
  - **pi adapter** attaches a pi session to a record at `session_start`, hosts the record-keyed control socket, and exposes the native pi tool surface.
31
- - **Native bridges** register already-running native sessions without taking over their transcript or auth: Claude Code is mailbox/self-fetch; Antigravity is probe-backed native-push. Codex has archived probe evidence, but its managed native lane was declined because pi already supplies the official GPT provider path; do not duplicate it as a native citizen or ACP backend.
31
+ - **Native bridges** register already-running native sessions without taking over their transcript or auth: Claude Code and Copilot CLI are mailbox/self-fetch (a Claude hook arms the watch; a forked Copilot extension holds it); Antigravity is probe-backed native-push. Codex has archived probe evidence, but its managed native lane was declined because pi already supplies the official GPT provider path; do not duplicate it as a native citizen or ACP backend.
32
32
  - **ACP plugin** registers provider `entwurf` inside a pi host session and drives a backend under an isolated overlay. The host pi session is already a record-backed socket citizen; the plugin does not mint another citizen/socket/peer layer.
33
- - **mux is launch-only and deliberately small.** Shipped: the tmux placement leaf (`mux-placement.ts` — inspect/append/close inside the caller's own session), the visible runtime launch composition (`mux-launch.ts`), the fresh-call composition (`mux-fresh-call.ts`), and the cwd-aware resume placement composition (`mux-resume-call.ts`). Fresh call is exposed as `entwurf_fresh_call`; visible same-id resume is `entwurf_resume_call`, composed at the two public surfaces by injecting `mux-resume-call` into the v2-side `entwurf-v2-visible-resume.ts`. The exact import graph is enumerated in [docs/mux-launch-rail.md](./docs/mux-launch-rail.md) §11. `entwurf_v2` behavior is unchanged and delivery still never imports launch. Fresh call learns a new sibling's garden id from the CALLBACK it makes, never from a lookup — so the pre-injected-token → identity-lookup design (rail §6) is **closed, not paused**. Do not reopen it without new evidence and explicit GLG re-approval. Fresh-call requires one explicit model and passes it in each runtime's measured CLI dialect (Pi `--model <provider/model>`, Claude Code `--model=<id-or-alias>`); it also takes ONE optional literal absolute `cwd` (#73 — omitted/`""` means the caller's own directory; no trim, no realpath, no project-name resolver; classified by the shared `classify-tmux-cwd.ts` leaf; the receipt echoes the REQUEST only), so a cross-repo fresh sibling is a fresh-call fact and never a reason to resume a dormant record. Resume takes only an existing target id and gets transcript/model/provider/cwd from its record. Do not grow either narrow carrier into a generic driver, harness profile, arbitrary command/env selection, or a second creation API. Ownership and import prohibitions: [docs/mux-launch-rail.md](./docs/mux-launch-rail.md) §11.
33
+ - **mux is launch-only and deliberately small.** Shipped: the tmux placement leaf (`mux-placement.ts` — inspect/append/close inside the caller's own session), the visible runtime launch composition (`mux-launch.ts`), the fresh-call composition (`mux-fresh-call.ts`), and the cwd-aware resume placement composition (`mux-resume-call.ts`). Fresh call is exposed as `entwurf_fresh_call`; visible same-id resume is `entwurf_resume_call`, composed at the two public surfaces by injecting `mux-resume-call` into the v2-side `entwurf-v2-visible-resume.ts`. The exact import graph is enumerated in [docs/mux-launch-rail.md](./docs/mux-launch-rail.md) §11. `entwurf_v2` behavior is unchanged and delivery still never imports launch. Fresh call learns a new sibling's garden id from the CALLBACK it makes, never from a lookup — so the pre-injected-token → identity-lookup design (rail §6) is **closed, not paused**. Do not reopen it without new evidence and explicit GLG re-approval. Fresh-call requires one explicit model and passes it in each runtime's measured CLI dialect (Pi `--model <provider/model>`, Claude Code `--model=<id-or-alias>`, Copilot `--model <name>` beside `--interactive <prompt>` and the explicit `--yolo` policy — a GLG width decision, 2026-08-25, after a callback-only `--allow-tool` grant stopped the sibling at every task tool); the Copilot backend opens through entwurf's OWN managed invocation (`entwurf copilot`), never the bare vendor, and its four required capabilities — birth, MCP hand, receiver, visible footer — are decided by `copilot-fresh-preflight.ts` BEFORE the tmux mutation, so a missing unit is a named refusal instead of a dead window; it also takes ONE optional literal absolute `cwd` (#73 — omitted/`""` means the caller's own directory; no trim, no realpath, no project-name resolver; classified by the shared `classify-tmux-cwd.ts` leaf; the receipt echoes the REQUEST only), so a cross-repo fresh sibling is a fresh-call fact and never a reason to resume a dormant record. Resume takes only an existing target id and gets transcript/model/provider/cwd from its record. Do not grow either narrow carrier into a generic driver, harness profile, arbitrary command/env selection, or a second creation API. Ownership and import prohibitions: [docs/mux-launch-rail.md](./docs/mux-launch-rail.md) §11.
34
34
  - **One delivery verb:** `entwurf_v2` addresses an existing garden id. Current routes are live control-socket send, active self-fetch mailbox enqueue, and probe-alive native-push. NO route starts a process: the hidden background resume for a dormant citizen was withdrawn under the visible-first rule, so a dormant in-domain citizen rejects as `dormant-fire-forget-unsupported` and the intent axis is single-valued. Every other complementary state pair rejects honestly too. Fresh creation is a separate verb — `entwurf_fresh_call` — and it returns a launch receipt only; the new sibling's address arrives asynchronously as the sender envelope of its nonce callback.
35
35
 
36
36
  ## Hard Rules
@@ -41,7 +41,7 @@ For agents that own this repo: invariant principles and reproducible verificatio
41
41
  4. **Dispatch is computed from live facts.** Never store liveness. Never infer send/resume from session type before resolving the target and probing its rail.
42
42
  5. **Rejects are real.** Wrong intent, dead/drifted identity, undeliverable mailbox, ambiguous address, or indeterminate probe returns a reject and mutates nothing. No cosmetic success or silent fallback.
43
43
  6. **MCP is explicit.** Only explicit `mcpServers` wiring. No ambient `~/.mcp.json` scan or automatic retrieval.
44
- 7. **Meta-record store contract is one contract.** Record body is authority; filename must agree with the body; every active entry is a regular non-symlink file readable by the live V3 schema; `nativeSessionId` ownership is unique. Identity writers and doctor certify the whole active store before writing. Address-bearing reads use `readAddressableMetaIdentity`; targeted relay reads keep the documented per-entry contract. `readStoreRecordFile`/`O_NOFOLLOW` and the lstat policy layer are both load-bearing—do not merge or bypass them. No legacy reader/migrator: quiesce and run `entwurf meta-bridge-fresh-cut` when the generation is unreadable. Source and gates: `meta-session.ts`, `check-meta-*`, `check-fresh-cut-gate`.
44
+ 7. **Meta-record store contract is one contract.** Record body is authority; filename must agree with the body; every active entry is a regular non-symlink file readable by the live V3 schema; `nativeSessionId` ownership is unique. Identity writers and doctor certify the whole active store before writing. Address-bearing reads use `readAddressableMetaIdentity`; targeted relay reads keep the documented per-entry contract. `readStoreRecordFile`/`O_NOFOLLOW` and the lstat policy layer are both load-bearing—do not merge or bypass them. No legacy reader/migrator: quiesce and run `entwurf meta-bridge-fresh-cut` when the generation is unreadable. An unknown-backend defect is a stale deployed reader, not a rotten generation: redeploy the sibling unit; do not fresh-cut. `doctor-meta-bridge` already names that — run it after `META_BACKENDS` changes. Source and gates: `meta-session.ts`, `check-meta-*`, `check-fresh-cut-gate`.
45
45
  8. **GC reclaims process resources, never memory/data.** Records and transcripts are preserved or archived; they are not casually deleted.
46
46
  9. **This is not a second harness.** No prompt reconstruction, transcript hydration, tool-result ledger, credential mediation, or harness emulation. Each backend owns auth and transcript state.
47
47
  10. **Native-push is its own rail.** It is not a mailbox or pi socket in disguise. Antigravity replyability is record-backed plus probe-alive; no receiver marker, watch state, or resume authority is invented for it.
@@ -89,7 +89,7 @@ A `--entwurf-control` pi session is a citizen for the same reason a native bridg
89
89
 
90
90
  - **control-socket domain (currently `pi`)**: socket liveness, per-target lock, live send, and a dormant cell that rejects.
91
91
  - **No relaunch transport exists inside delivery.** `spawn-bg` — a detached, window-less resume child — was removed under the visible-first rule, not deprecated behind a reject, and `entwurf_v2` still starts no process: a dormant socket-domain citizen is refused as `dormant-fire-forget-unsupported`. Reopening one is the separate lifecycle verb `entwurf_resume_call`, which is pi-only (`target-not-pi` otherwise), returns a LAUNCH receipt and an OBSERVATION receipt that are never merged, releases its per-gid lock on every path, and on an unobserved socket leaves the visible window open rather than retrying. No watcher, no retry, no supervisor. Do not re-route it through `entwurf_v2` and do not describe a spawn domain that does not exist.
92
- - **self-fetch domain (currently Claude Code)**: active receiver + mailbox deliverability; no resume authority.
92
+ - **self-fetch domain (Claude Code, Copilot CLI)**: active receiver + mailbox deliverability; no resume authority. The two arm that receiver through different vendor surfaces — a Claude hook that emits watchPaths, a Copilot first-party extension that holds the watch in a forked child — and the marker records which, because the pid a reader verifies differs.
93
93
  - **native-push domain (currently Antigravity)**: adapter probe + direct injection; no mailbox and no resume authority.
94
94
  - `origin: "pi-session" | "meta-session" | "external-mcp"` records sender provenance. It is not the citizen identity schema and not a hierarchy.
95
95
  - `entwurf_peers` reports record citizens and liveness facts only. It never embeds routing verbs or socket addresses for peers.
@@ -111,6 +111,7 @@ A `--entwurf-control` pi session is a citizen for the same reason a native bridg
111
111
  - A new issue brings the best available observation plus an executable next measurement. Recovering a missing signature may be the first acceptance; an idea with neither evidence nor a next measurement is not an issue.
112
112
  - No collection points, umbrella trackers, or fallout buckets. Merge issues only when they share one cause and one acceptance.
113
113
  - Sweep manually after a release and before opening an issue. Solved work closes at its durable SHA; it does not wait for the next release.
114
+ - **An issue body is a snapshot; the thread is the live contract.** A body written before GLG stated what the work is *for* will disagree with the thread, and a reader who opens only the body inherits the stale half — including its fences, which is how a fence once forbade the very deliverable the lane existed to produce (#82, 2026-08-20). When they disagree the thread wins. Whoever notices owes the body an edit that strikes the withdrawn clause and says why it was there; deleting it silently makes the queue tidy and the lesson invisible.
114
115
 
115
116
  ## Verification
116
117
 
@@ -180,7 +181,7 @@ implement → affected focused gates → independent review → one amendment bu
180
181
  - Every `.ts` file belongs to one typecheck fence: root emit-capable config, MCP strip-types config, or scripts strip-types config. Do not hide files with `exclude`.
181
182
  - Root pi extensions import TypeBox through `@earendil-works/pi-ai`; do not mix direct `@sinclair/typebox` types.
182
183
  - MCP/scripts use explicit `.ts` imports where Node strip-types requires them. Installed operator surfaces route to compiled JS.
183
- - pi runtime range is `>=0.84.2 <0.85` with devDep exact `0.84.2`; re-evaluate loader aliases and `/compat` at every minor ceiling. The ceiling moves on measurement, never on assumption, and a previous bump's argument is never reused: at 0.83.0→0.84.0 `compat.ts` was still byte-identical but `loader.ts` was NOT, so the diff itself had to be read and judged reachable-or-not. Per-bump hashes, diff judgments, and reachability findings live in the ROADMAP **Dep bump(별도 트랙)** ledger — keep them there, not here.
184
+ - pi runtime range is `>=0.84.3 <0.85` with devDep exact `0.84.3`; re-evaluate loader aliases and `/compat` at every minor ceiling. The ceiling moves on measurement, never on assumption, and a previous bump's argument is never reused: at 0.83.0→0.84.0 `compat.ts` was still byte-identical but `loader.ts` was NOT, so the diff itself had to be read and judged reachable-or-not. Per-bump hashes, diff judgments, and reachability findings live in the ROADMAP **Dep bump(별도 트랙)** ledger — keep them there, not here.
184
185
  - ACP pins are recorded in `package.json` and checked by `check-dep-versions`/`check-acp-sdk-surface`; do not describe a dependency bump as a behavioral fix without evidence.
185
186
 
186
187
  ## Working Style
@@ -206,12 +207,14 @@ An overgrowth is built from locally correct steps; what fails is the absence of
206
207
  - **Unrelated meta-infra never rides a capability lane.** The source-adjacent gate/mutant that proves a capability's contract belongs in the same change as that capability — the removal/repair rule above is untouched. What stays out of a feature commit candidate is unrelated verification machinery: scheduling rework, selectors, caches, receipts, floor restructuring. That pain is recorded and handled in its own subtraction lane later.
207
208
  - **Stop signal — evidence outgrowing the product.** When verification/meta-tool changes grow larger than the capability change they serve, stop and report to GLG. This is an operator-judgment trigger, deliberately not a mechanized ratio gate.
208
209
  - **Claim only what the evidence carries.** "The full floor was green once on the declared candidate" is strong enough; a stronger sentence mints proof obligations, and those obligations mint subsystems.
210
+ - **A claim that crosses sessions carries its evidence state.** Measured here (name the receipt), read at `file:line`, read from an external artifact (name the path), inherited and unchecked (name the source) — and design proposals in a box of their own, since a proposal is adopted or decided differently, never measured. This repo is where siblings from many models each get a few turns, so a bare sentence costs the next one its turn re-deriving the ground, and leaves it only "believe or refute" when the honest move is "that carries no receipt, so I measured it." Retire claims, not people. Receipts in host-local paths do not travel: paste the decisive lines into the artifact that crosses.
209
211
  - **Risk classes are not equal.** Data loss, identity authority, false delivery success, install destruction, and secrets get fail-closed strength. Doc tense, future possibility, and total environment-byte binding get repaired when seen — they do not justify new gate machinery.
210
212
 
211
213
  ## Next and References
212
214
 
213
215
  - [NEXT.md](./NEXT.md) — current priority and exact next move; branch work uses disposable `NEXT--<branch>.md`.
214
216
  - [ROADMAP.md](./ROADMAP.md) — forward direction and deferred lanes.
217
+ - [docs/adding-a-harness.md](./docs/adding-a-harness.md) — **first entry point for putting a NEW harness in the garden.** The order the eight steps are actually walked in (lane choice, vendor measurement, backend registration, birth, statusline, MCP hand, sender identity, receive, grade), what each owes before the next may start, and which of the other docs owns each slice. It routes; it grants nothing — Hard Rule 7 remains the authority on the meta-record store contract its registration step touches, and the gates and doctors it names remain the truth.
215
218
  - [docs/acp-backend-rail.md](./docs/acp-backend-rail.md) — ACP adapter contract and current entry conditions.
216
219
  - [DELIVERY.md](./DELIVERY.md) — delivery capability/evidence coordinates.
217
220
  - [VERIFY.md](./VERIFY.md) / [BASELINE.md](./BASELINE.md) — verification protocol and recorded evidence.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,85 @@ All notable changes to this project will be documented here. Format follows [Kee
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 0.15.0 — 2026-08-25
8
+
9
+ This release admits Copilot CLI as a full self-fetch garden citizen — birth, an owned
10
+ MCP hand, record-backed sender identity, an installed first-party receive extension, and
11
+ a visible-fresh launch path — closing #82, and fixes the fresh-launch YOLO regression the
12
+ admission work exposed.
13
+
14
+ ### Added
15
+
16
+ - **Copilot CLI is a self-fetch garden citizen.** `birthCopilotCitizen` upserts
17
+ `(backend:"copilot", nativeSessionId)` into the shared V3 record store, gives the
18
+ session a garden id, an owned `entwurf` MCP hand, a footer statusline slot showing that
19
+ id, and record-backed sender identity on every dispatched message. The receive rail is
20
+ an installed first-party extension (`run.sh install-copilot-receive`) that binds to the
21
+ V3 record, arms a watcher, and rings a doorbell the model drains with
22
+ `entwurf_inbox_read`; `wakeMode` is `self-fetch`, so dispatch reaches the mailbox rail —
23
+ armed sessions get `delivered`, unarmed or stale sessions get the honest
24
+ `mailbox-undeliverable` refusal. Launch is exclusively through the owned invocation
25
+ `entwurf copilot`, which sets `COPILOT_CLI_ENABLED_FEATURE_FLAGS=EXTENSIONS` for that one
26
+ process; a session started any other way is silently inert by design, and
27
+ `doctor-copilot-receive` reads the live CLI environment to catch that class. (#82)
28
+ - **Fresh Copilot siblings are admitted through the same preflight contract as birth.**
29
+ `copilot-fresh-preflight.ts` decides birth, MCP hand, receiver, and visible footer
30
+ BEFORE the tmux mutation, so a missing capability is a named refusal instead of a dead
31
+ window. `entwurf_fresh_call` takes one explicit model in Copilot's measured CLI dialect
32
+ (`--model <name>` beside `--interactive <prompt>`) and opens through `entwurf`'s own
33
+ managed invocation, never the bare vendor binary. (#82)
34
+ - **Visible-fresh and D6 receive are LIVE-accepted on one host.** 2026-08-23 D6 receive
35
+ acceptance (garden `20260823T181316-d9f6ba`, CLI 1.0.80): `joined → armed → doorbell →
36
+ rang`, mailbox enqueue/read timestamps, and a model reply on the same
37
+ record/native/garden-id chain. 2026-08-25 visible-fresh acceptance: launch window `@89`,
38
+ nonce `mux-fresh-call-690529ae99f99faa2252aefb`, exact-callback garden
39
+ `20260825T085721-f68be0`, one `entwurf_v2` mailbox enqueue plus a same-garden reply, and
40
+ an operator-observed footer garden id on a healthy multi-turn window. Both rows are
41
+ evidence level L4 (one host) and stay unmerged; visible fresh is operator-metered and is
42
+ not a release-gate MUST.
43
+
44
+ ### Fixed
45
+
46
+ - **Fresh Copilot siblings now default to `--yolo`, matching the profile a human gets
47
+ from `entwurf copilot`.** `mux-fresh-call.ts` previously passed a callback-only
48
+ `--allow-tool=entwurf-bridge(entwurf_v2)` grant, which caused `copilot-launch.sh`'s
49
+ explicit-policy scan to suppress its own `--yolo` injection — every task-tool call then
50
+ stopped on a confirmation prompt the new sibling could not answer, making it practically
51
+ unusable. GLG operator LIVE on 2026-08-25 confirmed the missing `YOLO` footer and the
52
+ per-task-tool stalls; the fix restores the default without adding a permission
53
+ parameter to the fresh-call API or changing the manual `entwurf copilot` override
54
+ behavior. (#82 follow-up)
55
+ - **Pi runtime pin moves to `0.84.3`.** Certified development pin and closed peer range
56
+ are now `0.84.3` / `>=0.84.3 <0.85`. `compat.ts` stayed byte-identical at the previous
57
+ bump but `loader.ts` did not, so the diff was read and judged reachable before moving
58
+ the ceiling; the argument is not reused across bumps and each future ceiling move is
59
+ judged on its own diff.
60
+
61
+ ### Verification
62
+
63
+ - **`pnpm run check:full`** — PASS, exit 0, 406s standalone on the prepared HEAD.
64
+ - **`LIVE=1 ./run.sh release-gate <scratch> --cut`** — **MUST PASS=21 FAIL=0 SKIP=0,
65
+ BEHAVIOR PASS=1 FAIL=0 SKIP=0, `cut: OK`.** `check-gate-qualification` inside the gate
66
+ killed **252/252** mutants across 30 lanes. Log:
67
+ `/tmp/entwurf-release-gate-0.15.0.run2.fB2LLP/release-gate.log` (SHA-256
68
+ `9a00a22e8b4ca715ad0b955b51c99a2bacc385bf0c225180026948a4618e5ff6`).
69
+ - A first attempt on the same prepared HEAD (scratch `.../entwurf-release-gate-0.15.0.7yUIFH`)
70
+ hit one MUST FAIL in `smoke-mux-lifecycle-live`'s cleanup launcher-integrity self-fence:
71
+ all 78 lifecycle assertions passed, but the Claude Code launcher symlink changed target
72
+ and content mid-run (`2.1.243`→`2.1.245`) because auto-update was still on the default
73
+ channel. GLG pinned `autoUpdatesChannel: stable` before the rerun above; this was an
74
+ environment race during the LIVE run, not a defect in the 0.15.0 candidate.
75
+
76
+ ### Notes
77
+
78
+ - Copilot's D7 grade is PARTIAL: reply and read receipts are observed; completion
79
+ taxonomy and long-haul operation are not. D3 (second-session isolation of an owned
80
+ invocation) is PENDING — observed once, but the decisive log was lost to scratch
81
+ cleanup before it could be preserved; re-measurement is tracked outside this release.
82
+ - #76 (subscription-first refusal of `openrouter/*` sibling launches), #78
83
+ (macOS/native-Windows portability evidence), #80 (public vocabulary), #83, #84, #85, and
84
+ the remaining #72 field-cause investigation stay outside this release.
85
+
7
86
  ## 0.14.2 — 2026-08-20
8
87
 
9
88
  This patch makes bridge availability a property of the exact invocation each harness will run, preserves child-exit facts across ACP transport closure, and records Copilot delivery as probe evidence without admitting a new citizen rail.
package/DELIVERY.md CHANGED
@@ -74,14 +74,22 @@ D8 robustness: partial reason="..."
74
74
 
75
75
  ## Current matrix
76
76
 
77
+ **Vocabulary.** *Owned* in this file names entwurf ownership of a concrete invocation,
78
+ installer, or config writer. Historical receipts may say *managed* for that same ownership
79
+ axis; neither word is an admission grade or a synonym of *supported*. A pre-contract probe
80
+ can be owned without being supported; a supported harness still has to walk
81
+ [`docs/adding-a-harness.md`](./docs/adding-a-harness.md) step 9.
82
+
77
83
  | Harness / surface | Product status | Capability | Transport and boundary |
78
84
  |---|---|---|---|
79
85
  | **pi native Entwurf** | shipped | D7; D8 partial | Record-addressed Unix control socket. A record-less socket is diagnostic only and never dispatched. |
80
86
  | **Claude Code interactive `>=2.1.217`** | shipped; Linux certified | D6; D7/D8 partial | Per-session mailbox + exec-form `FileChanged`/`asyncRewake`. B2 proved idle wake and same-session continuity on one NixOS host. |
81
87
  | **Antigravity / agy** | shipped | D6; D7 partial | Record-backed native-push through LS gRPC `agentapi send-message`; no mailbox or receiver marker. |
82
- | **Codex app-server-backed TUI** | verified probe | D7; D8 unproven | WebSocket-over-UDS `turn/start` into a live `threadId`; status events expose completion. No managed citizen lane yet. |
88
+ | **Codex app-server-backed TUI** | verified probe | D7; D8 unproven | WebSocket-over-UDS `turn/start` into a live `threadId`; status events expose completion. No owned native-citizen install/invocation lane. |
83
89
  | **Codex embedded TUI** | deferred | D0 partial | No supported receive socket/hook on the measured standalone shape. |
84
- | **Copilot CLI TUI+server** | verified probe | D7; D8 unproven | **Old chronological-slice run; the named-turn probe in the tree is unrun.** Official SDK over hidden `--ui-server`: foreground native id + metadata, two-session addressed idle enqueue, same-session auto-model reply, completion events read back through the official session event-history API. Loopback RPC authentication is not established; no managed citizen lane. Evidence is L4 direct-native on ONE Linux workstation, host-local stdout, not archived. |
90
+ | **Copilot CLI first-party extension** | raw transport probe; superseded by the owned product unit | D7 path observed; D3 control receipt incomplete; D8 unproven | CLI-spawned extension over stdio JSON-RPC; `joinSession()` + documented `fs.watch` `session.send({mode:"enqueue"})`. Idle wake, exact-marker reply, and completion passed on 2026-08-23 (CLI 1.0.80, L4, one Linux host). Two-process isolation was observed but its decisive B log was not preserved. Kept as the transport receipt the owned receive unit was built on; the shipped unit differs deliberately it announces the inbox instead of injecting the body. |
91
+ | **Copilot CLI garden citizen** | branch candidate; send + receive + visible fresh accepted on one host | D6; D7 partial; D3 pending; D8 unproven | Birth, garden id, MCP hand and record-backed sender identity are accepted; the RECEIVER is an installed first-party extension that binds to the V3 record, writes a receiver marker owned by the WATCHER pid, and rings a doorbell the model drains with `entwurf_inbox_read`. `wakeMode` is `self-fetch`, so dispatch reaches the mailbox rail: armed → delivered, unarmed/stale → the honest `mailbox-undeliverable` refusal. **D6 is the owned-invocation LIVE acceptance of 2026-08-23** — garden `20260823T181316-d9f6ba`, native `20fe30c8-b2bc-4600-91a0-8a409131be51`, CLI 1.0.80: receive log `joined`→`armed`→`doorbell fresh=1`→`rang`, mailbox `lastEnqueuedAt 09:23:41.235Z` / `lastReadAt 09:23:56.480Z`, and a model reply on the same record/native/gid chain. **Visible fresh (step 9 clause 7) is a separate LIVE, 2026-08-25** — launch window `@89`/`%89` nonce `mux-fresh-call-690529ae99f99faa2252aefb`; exact-callback garden `20260825T085721-f68be0`; one `entwurf_v2` → `meta-mailbox → enqueued`; same garden `lastReadAt 2026-08-24T23:57:47.784Z` plus same-gid reply; GLG saw footer garden id and a healthy multi-turn window. Those rows stay unmerged. D7 is PARTIAL: reply and read receipt were observed, the completion taxonomy and long-haul operation were not. D3 (second-session isolation of an owned invocation) is PENDING — observed once, decisive log lost to scratch cleanup. Evidence level L4: one host. Launch through the owned invocation `entwurf copilot`, which sets `COPILOT_CLI_ENABLED_FEATURE_FLAGS=EXTENSIONS` for that one process; `doctor-copilot-receive` reads live CLI environments because a session launched without it is silently inert. Visible fresh is operator-metered and is not a release-gate MUST. |
92
+ | **Copilot CLI TUI+server** — *withdrawn lane, kept as evidence* | rejected | D7; D8 unproven | Older official-SDK probe over hidden `--ui-server`; idle enqueue worked, but loopback RPC authentication was not established. The bundled extension supersedes this candidate without reviving it. |
85
93
  | **ACP Claude / Cortex** | shipped runtime, outside this matrix | — | ACP sessions are children launched by entwurf's pi adapter, not already-running native sessions to wake. |
86
94
 
87
95
  “Verified probe” means the transport worked in a reproducible raw probe but entwurf
@@ -112,7 +120,7 @@ directly through the native adapter, with one bounded re-probe retry. Replyabili
112
120
  `record-backed identity ∧ probe-alive`; mailbox state does not exist on this rail, and no
113
121
  rail has resume authority since the visible-first cut.
114
122
 
115
- The managed bridge, statusline, and hook installers own separate configuration atoms.
123
+ The owned bridge, statusline, and hook installers own separate configuration atoms.
116
124
  Same-pid concurrent model invocation by multiple conversations is not claimed because
117
125
  the pid/start-key sender marker would be last-writer-wins. Current operator checks are
118
126
  in [BASELINE.md](./BASELINE.md); deterministic ownership and sender gates run in
@@ -123,65 +131,70 @@ in [BASELINE.md](./BASELINE.md); deterministic ownership and sender gates run in
123
131
  Do not describe “Codex” as one delivery shape. The measured app-server-backed TUI can
124
132
  accept `turn/start` for a live thread and report completion; the standalone embedded
125
133
  TUI exposed no equivalent receive route. This remains archived method evidence, not a
126
- shipping commitment: GLG closed the managed native Codex lane on 2026-08-01 because pi
134
+ shipping commitment: GLG declined to own a native Codex invocation/install lane on 2026-08-01 because pi
127
135
  already supplies the official GPT provider path. Entwurf will not duplicate it as a
128
136
  native citizen or ACP backend. `turn/steer` is active-turn steering, not idle wake.
129
137
 
130
- ### Copilot CLI: TUI+server is the positive launch mode
131
-
132
- **Two things are recorded here and they are not the same thing: what was MEASURED on
133
- 2026-08-19, and what the probe now CONTRACTS to measure.** The measurement below was
134
- taken with the earlier chronological-slice probe, which scored the events following the
135
- marker's position in the history. The current probe scores a named turn instead (next
136
- subsection). No result below has been re-taken under that contract, and none of it is
137
- retroactively a demonstration of it.
138
-
139
- **Measured 2026-08-19 (old chronological-slice probe).** A plain Copilot TUI and shell
140
- command hooks do not establish the measured route. CLI 1.0.80 launched with hidden
141
- `--ui-server --port <port>` and joined by first-party `@github/copilot-sdk` 1.0.11 did:
142
- protocol-v3 ping, foreground session id plus cwd/git metadata, exact session resume, idle
143
- `enqueue`, same-session model-`auto` reply, and `assistant.message`/`turn_end`/`session.idle`
144
- completion. A second run created a no-turn control session B, targeted A, and proved B
145
- received no user/turn/assistant event, closing D3 on one Linux workstation.
146
-
147
- The two-session shape also exposed a D8 gap: A visibly replied and persisted
148
- `assistant.message` + `turn_end`, but the joining SDK client did not receive ephemeral
149
- `session.idle`, so SDK `sendAndWait()` timed out. Bounded reads of the official session
150
- event-history API (`session.getEvents()` / `getMessages()`) then observed the completed
151
- target turn. Claim that at its real size: it is the SDK's own full event history, not a
152
- narrower or more privileged view, and equally not TUI, file, or database transcript
153
- scraping the probe never reads Copilot's own storage. It is probe evidence, not a
154
- product polling/retry design.
155
-
156
- **Evidence level for everything above: L4 direct-native, ONE Linux workstation, one run.**
157
- The receipt is host-local probe stdout; it was not archived as a durable artifact, so this
158
- row is reproducible-by-instruction, not citable to a stored file.
159
-
160
- **Current probe contract (not yet run LIVE).** Attribution is now a named chain rather
161
- than a position in the history: the probe's unique marker body must match exactly one
162
- `user.message`; that event's `interactionId` must open exactly one `assistant.turn_start`;
163
- that turn_start must expose a `turnId`; and only `assistant.message` / `assistant.turn_end`
164
- carrying that `turnId` are scored. Every link is required, and absent-or-ambiguous fails
165
- the probe closed there is no positional fallback and no "the turn after ours" rule.
166
- Note what is deliberately NOT the key: `session.send()` resolves to the SDK's own
167
- submission handle, a string that appears on no server event and is a different axis from
168
- `user.message.id`/`interactionId`, so joining on it cannot hold. It is logged as a
169
- diagnostic only. The next LIVE turn is what would demonstrate this contract; until then
170
- it is a design, not evidence.
171
-
172
- The probe also stays out of the operator's lifecycle, stated precisely: it never deletes
173
- target session A and issues no `A.disconnect()` of its own. `client.stop()` does tear down
174
- every tracked session — A included — as a wire `session.destroy`; because A's foreground
175
- ownership is re-confirmed immediately before teardown, the TUI keeps A as its foreground
176
- session, so the net effect on A is detach-equivalent, not removal.
177
-
178
- This is not yet admissible as a native-push adapter. The flag is hidden from CLI help,
179
- and the loopback JSON-RPC server did not enforce the SDK connection token in the
180
- measured launch: an unauthenticated client connected, while a token-bearing client was
181
- rejected as `AUTHENTICATION_NOT_CONFIGURED`. The TCP port is only a runtime endpoint,
182
- never identity authority. Do not add a record backend or dispatch route until
183
- permission ownership, stale/crash handling, and a supported fail-closed local
184
- boundary are demonstrated.
138
+ ### Copilot CLI: one citizen, two rails, one pending receipt
139
+
140
+ The branch product owns the native citizen's birth, garden id, statusline, MCP hand,
141
+ outbound sender identity — and, since RAIL 5, the receiver. A real Copilot CLI 1.0.80
142
+ session minted a V3 record and sent under that record-backed garden id on 2026-08-21.
143
+ That proved who SENDS. Whether a reply LANDS is a different fact on a different process,
144
+ and the paragraphs below are the two halves of it: where the transport came from, and
145
+ what the owned product unit had to add before it could be dispatched to.
146
+
147
+ The missing receive transport was found and measured on 2026-08-23. Copilot's platform
148
+ package bundles its first-party extension SDK and bootstrap. With
149
+ `COPILOT_CLI_ENABLED_FEATURE_FLAGS=EXTENSIONS`, the CLI forks an installed extension and
150
+ speaks JSON-RPC over the child's stdio; `joinSession()` binds the foreground session, and
151
+ the vendor-documented `fs.watch` `session.send({mode:"enqueue"})` pattern wakes it.
152
+ An idle, never-typed-into session received a unique marker and returned it, then emitted
153
+ `session.idle`. A second armed process was observed to remain untouched, but its decisive
154
+ B log was not preserved before scratch cleanup, so D3 isolation remains an admission
155
+ rerun rather than a durable acceptance. This is L4 direct-native evidence on one Linux
156
+ workstation. The travelling receipt and reproduction are in
157
+ [`scripts/raw-async-delivery/README.md`](./scripts/raw-async-delivery/README.md).
158
+
159
+ This extension rail has no network listener, so the rejected `--ui-server` loopback
160
+ transport's authentication blocker does not apply. The owned product unit then took that
161
+ transport and gave it the obligations a product owes (#82 RAIL 5):
162
+
163
+ - **Artifact.** `run.sh install-copilot-receive` installs the receiver into the user
164
+ extensions directory from an install-state file it owns, refuses a unit it did not
165
+ put there, and removes only what that state names.
166
+ - **Identity.** The extension arms only when the CLI pid's sender marker, the V3 record
167
+ and the SDK's `session.sessionId` all agree; a drifted id or a parent-pid carrier that
168
+ disagrees with the real parent is a refusal, logged, never a best guess.
169
+ - **Liveness.** The marker's owner is the EXTENSION child the process that actually
170
+ holds the watch so a crashed receiver stops being deliverable at the next start-key
171
+ read, and the vendor's bootstrap already exits that child when the CLI goes.
172
+ - **Dispatch.** `wakeMode: self-fetch` puts Copilot on the existing mailbox rail. Armed
173
+ and matching enqueue + doorbell; anything else `mailbox-undeliverable`.
174
+ - **The flag.** entwurf still does not own the operator's shell and writes nothing to it,
175
+ but it owns ONE invocation: `entwurf copilot` execs the vendor CLI in the caller's own
176
+ terminal with `COPILOT_CLI_ENABLED_FEATURE_FLAGS=EXTENSIONS` set for that process only,
177
+ and refuses to launch at all unless the receiver unit it is promising is really
178
+ installed. `doctor-copilot-receive` still reads the live CLI environments, because a
179
+ session started any other way without the flag is silently inert. Plain `copilot` is
180
+ untouched; running the owned invocation IS the consent to its profile (EXTENSIONS,
181
+ `--model auto` when no model was given, `--yolo` when no explicit permission or surface
182
+ policy flag was given).
183
+
184
+ The owned receive invocation has now been accepted LIVE (2026-08-23, receipts in the matrix row
185
+ above), which is what moved receive from D0 to D6. Visible fresh is a later, separate LIVE
186
+ (2026-08-25, same row) and does not reopen D3 or D8. What remains owed is still EVIDENCE,
187
+ not code: D3 isolation lost its decisive log to a scratch cleanup and is pending, and the
188
+ active-turn case, `/clear` re-arm and flag durability across vendor releases are
189
+ unmeasured. The hermetic gate drives the real installer and the real extension against a
190
+ stubbed SDK it proves everything on entwurf's side of the fork and nothing about the
191
+ vendor turn on the other side of it, so a green gate is still not a wake.
192
+
193
+ The hidden `--ui-server` probe remains retired evidence, not a fallback. It found a real
194
+ idle-enqueue capability through an unauthenticated loopback door; the extension finds the
195
+ same class of capability through the CLI-owned stdio lifecycle. Absence of Claude's
196
+ `FileChanged` / `asyncRewake` / `watchPaths` therefore means only that Claude's hook
197
+ mechanism cannot be copied — it never proved Copilot had no vendor wake surface.
185
198
 
186
199
  ## Recording a new claim
187
200
 
package/README.md CHANGED
@@ -74,13 +74,14 @@ A few words that look unusual for a coding tool.
74
74
  adapter that hosts the ACP plugin and live control-socket surface, but the base
75
75
  install is **not** `pi install npm:...` anymore.
76
76
 
77
- The package exposes five bins:
77
+ The package exposes six bins:
78
78
 
79
79
  - `entwurf` → `run.sh` (installer, checks, native-bridge doctors/installers)
80
80
  - `entwurf-bridge` → the MCP stdio launcher (`mcp/entwurf-bridge/start.sh`)
81
81
  - `entwurf-statusline` → the Claude Code statusline renderer (`scripts/meta-bridge-statusline.sh`)
82
82
  - `entwurf-agy-statusline` → the Antigravity garden-id statusline renderer (`scripts/agy-statusline.sh`)
83
83
  - `entwurf-agy-imprint` → the Antigravity `PreInvocation` birth/sender hook (`scripts/agy-imprint.sh`)
84
+ - `entwurf-copilot-statusline` → the Copilot CLI garden-id footer renderer (`scripts/copilot-statusline.sh`)
84
85
 
85
86
  The bridge/renderers/hook use stable bin names so package upgrades do not bake versioned package-store paths into native-harness settings.
86
87
 
@@ -154,7 +155,7 @@ gitignored and may be absent or stale immediately after a checkout or pull.
154
155
  ### Pi adapter / ACP plugin lane
155
156
 
156
157
  To use the `entwurf` provider inside pi, install a compatible pi binary
157
- separately (`@earendil-works/pi-coding-agent >=0.84.2 <0.85`). Then point pi at
158
+ separately (`@earendil-works/pi-coding-agent >=0.84.3 <0.85`). Then point pi at
158
159
  the npm-installed package or development clone:
159
160
 
160
161
  ```bash
@@ -190,8 +191,58 @@ entwurf install-agy-hooks
190
191
  entwurf doctor-agy-bridge
191
192
  entwurf doctor-agy-statusline
192
193
  entwurf doctor-agy-hooks
194
+
195
+ # GitHub Copilot CLI — four independent surfaces, four independent failure modes
196
+ entwurf install-copilot-bridge # birth: garden id + who-sent, on the first prompt
197
+ entwurf install-copilot-mcp # the entwurf tool hand (entwurf_inbox_read lives here)
198
+ entwurf install-copilot-receive # the receiver extension: doorbell + receiver marker
199
+ entwurf install-copilot-statusline # optional for a manual citizen; required for supported fresh
200
+ entwurf doctor-copilot-bridge
201
+ entwurf doctor-copilot-receive
193
202
  ```
194
203
 
204
+ #### Launching Copilot as a garden citizen — `entwurf copilot`
205
+
206
+ Copilot only scans for extensions when its CLI is started with
207
+ `COPILOT_CLI_ENABLED_FEATURE_FLAGS=EXTENSIONS`, and when that flag is absent it skips the
208
+ scan **silently** — no error, no log line, no receiver. entwurf does not own your shell
209
+ and writes nothing to your rc files, so it owns one invocation instead:
210
+
211
+ ```bash
212
+ entwurf copilot # managed launch, in this terminal
213
+ entwurf copilot -p "…" --model gpt-5.4
214
+ copilot # the plain vendor CLI, untouched
215
+ ```
216
+
217
+ `entwurf copilot` execs the vendor CLI in your current terminal — same cwd, same pid,
218
+ same exit status, no tmux window and no new citizen (a Copilot session is still born on
219
+ its first prompt). Before it launches it verifies that the receiver unit it is about to
220
+ promise is actually installed, and refuses with `entwurf install-copilot-receive` if it
221
+ is not, rather than starting a session that can never be delivered to.
222
+
223
+ **Running it is your consent to its profile.** For that one invocation it adds:
224
+
225
+ | Injected | When |
226
+ |---|---|
227
+ | `COPILOT_CLI_ENABLED_FEATURE_FLAGS=EXTENSIONS` | always — your other feature-flag tokens are preserved, in order, deduplicated |
228
+ | `--model auto` | only when you passed no `--model` |
229
+ | `--yolo` | only when you passed no explicit permission or surface policy flag |
230
+
231
+ The escape hatch is simply to state your own policy: any of `--yolo`, `--allow-all`,
232
+ `--allow-all-tools`, `--allow-all-paths`, `--allow-all-urls`, `--allow-tool`,
233
+ `--deny-tool`, `--allow-url`, `--deny-url`, `--available-tools` or `--excluded-tools`
234
+ suppresses the injected `--yolo`. The narrowing flags are in that list on purpose — adding
235
+ `--yolo` beside your `--allow-url=…` would silently widen exactly what you were
236
+ restricting. `--allow-all-mcp-server-instructions` (prompt content, not authorization) and
237
+ `--autopilot` (a mode) are deliberately not policy flags. Everything you pass is forwarded
238
+ byte-identically, injected defaults land before any `--` terminator, and nothing after the
239
+ terminator is read as policy. Nothing is written to disk; run plain `copilot` and none of
240
+ this applies.
241
+
242
+ Why `--yolo` is the default: the managed lane exists so a sibling can wake an idle session,
243
+ and draining the mailbox with `entwurf_inbox_read` costs two interactive approvals under
244
+ the default permission prompts — which an idle, unattended session is not there to answer.
245
+
195
246
  Claude Code uses the supported floor `>=2.1.217`; older versions silently discard the
196
247
  exec-hook `args`, so install and doctor fail loud rather than falling back. After any
197
248
  upgrade, rerun the installer for the native harness you use and restart its existing
@@ -427,7 +478,7 @@ A two-pane recording covers the pre-0.12 v1 surface end-to-end — sibling resum
427
478
 
428
479
  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.)
429
480
 
430
- 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, accepts one optional literal absolute `cwd` (omit it or pass `""` to use the caller's cwd), passes the model and selected directory through the runtime's visible launch path, 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/cwd plus tmux coordinates and never claims that the runtime accepted them or completed delivery. (The v1 verbs `entwurf` / `entwurf_resume` / `entwurf_send` are gone.) Garden-native operator commands require `--entwurf-control`. There is no spawn target allowlist — the target registry is gone (#50 C3): `entwurf_v2` and `entwurf_resume_call` address an existing record-backed citizen, while `entwurf_fresh_call` takes its explicit backend/model/task and optional cwd directly rather than resolving a model tuple from a registry file. **To reopen a DORMANT pi citizen use `entwurf_resume_call {target}`** — the record supplies transcript, model, provider and cwd, so it takes no prompt, no task and no model override; it runs no turn, returns a LAUNCH receipt and a separate OBSERVATION receipt (only the second says the citizen is back), refuses a non-pi target as `target-not-pi`, and on an unobserved socket leaves the visible window open and releases its lock rather than retrying.
481
+ 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`, `claude-code`, or `copilot`) as a visible window in the operator's own tmux session, requires an explicit model, accepts one optional literal absolute `cwd` (omit it or pass `""` to use the caller's cwd), passes the model and selected directory through the runtime's visible launch path, and hands it a first task; a `copilot` launch goes through entwurf's own managed invocation and is refused before any window opens if this host lacks the Copilot birth, MCP, receiver or visible-footer units. The sibling's first action is a nonce callback whose sender envelope carries its garden id. The launch receipt records the requested model/cwd plus tmux coordinates and never claims that the runtime accepted them or completed delivery. (The v1 verbs `entwurf` / `entwurf_resume` / `entwurf_send` are gone.) Garden-native operator commands require `--entwurf-control`. There is no spawn target allowlist — the target registry is gone (#50 C3): `entwurf_v2` and `entwurf_resume_call` address an existing record-backed citizen, while `entwurf_fresh_call` takes its explicit backend/model/task and optional cwd directly rather than resolving a model tuple from a registry file. **To reopen a DORMANT pi citizen use `entwurf_resume_call {target}`** — the record supplies transcript, model, provider and cwd, so it takes no prompt, no task and no model override; it runs no turn, returns a LAUNCH receipt and a separate OBSERVATION receipt (only the second says the citizen is back), refuses a non-pi target as `target-not-pi`, and on an unobserved socket leaves the visible window open and releases its lock rather than retrying.
431
482
 
432
483
  ### `entwurf_v2` — canonical dispatch verb
433
484
 
@@ -446,7 +497,7 @@ In ACP-backed and external native-harness sessions, `entwurf-bridge` exposes sev
446
497
 
447
498
  **`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.
448
499
 
449
- 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 and one optional literal absolute `cwd`; omitted or `""` means the caller's cwd. It returns only a synchronous launch receipt and lets the sibling report its new address asynchronously through the sender envelope of a nonce callback. Use this cwd input for a new cross-repository sibling; do not resume a dormant citizen as a cwd substitute. The meta-mailbox row requires an **active** self-fetch receiver; native-push requires a record-backed, probe-alive native conversation and never borrows mailbox state. The [mux launch lane](./docs/mux-launch-rail.md) owns placement, fixed-runtime launch, and the two narrow compositions above it (fresh-call and resume-call placement); delivery does not import launch, and mux is not a delivery transport.
500
+ What v2 provides is a **deterministic dispatch substrate** that moves the "which transport?" decision out of the fallible caller/model and into the decider, with transport-appropriate locking and an honest reject (no `✓ delivered`, no `.msg` garbage) when a target cannot receive. It still does **not** mint siblings, and it does not relaunch one either: every row above either reaches a citizen that is already running or refuses. Reopening a dormant pi citizen is `entwurf_resume_call`, a separate lifecycle verb that never routes through this decider. Fresh creation is the separate `entwurf_fresh_call` verb. It opens one fixed Pi, Claude Code, or Copilot runtime visibly in the caller's tmux session with a required explicit model passed in that runtime's measured CLI dialect and one optional literal absolute `cwd`; omitted or `""` means the caller's cwd. Copilot opens through `entwurf copilot`, never the bare vendor. It returns only a synchronous launch receipt and lets the sibling report its new address asynchronously through the sender envelope of a nonce callback. Use this cwd input for a new cross-repository sibling; do not resume a dormant citizen as a cwd substitute. The meta-mailbox row requires an **active** self-fetch receiver; native-push requires a record-backed, probe-alive native conversation and never borrows mailbox state. The [mux launch lane](./docs/mux-launch-rail.md) owns placement, fixed-runtime launch, and the two narrow compositions above it (fresh-call and resume-call placement); delivery does not import launch, and mux is not a delivery transport.
450
501
 
451
502
  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.
452
503
 
package/VERIFY.md CHANGED
@@ -58,7 +58,7 @@ Verification here is not a benchmark. In production we exchange short turns and
58
58
  >
59
59
  > Be honest about its cost, because it is a MUST: it spends real model turns on the operator's configured runtimes (two pi siblings, each resumed once, plus one Claude Code sibling), it READS the operator's real runtime config, and the siblings' native session transcripts stay in the real pi agent dir — they are not cleaned up, and they are part of the evidence a resume was real. The fence is per-axis rather than absolute: the four meta roots (records, mailbox, receivers, senders) are fixture for every cell, so no garden record is ever minted outside it; the native pi cell's control socket and v2 lock are fixture too; the ACP pi cell's are REAL and transient (see the paragraph above); and the Claude cell runs its runtime under the real HOME while its mailbox and meta stay fixture. The run ends by proving the operator's SIX real roots — the four meta roots plus `~/.pi/entwurf-control` and `~/.pi/entwurf-v2-locks` — hold the entry sets they started with, with no fixture garden id anywhere, and that proof now runs on the failure path as well as the green one. Missing prerequisites decline with the protocol SKIP (97), which `release-gate --cut` refuses rather than rounding up.
60
60
  >
61
- > **Fresh-call LIVE is on-demand, not part of `release-gate`.** `LIVE=1 ./run.sh smoke-mux-fresh-call-live` opens configured Pi and Claude Code siblings, spends two model turns, and preserves their native transcripts; run it directly whenever a cut changes mux fresh-call/launch behavior. Its private tmux servers and fixture-bound entwurf write axes make it deterministic about garden-record/socket residue, but those real native turns should not appear unexpectedly in every aggregate cut.
61
+ > **Fresh-call LIVE is on-demand, not part of `release-gate`.** `LIVE=1 ./run.sh smoke-mux-fresh-call-live` opens configured Pi and Claude Code siblings, spends two model turns, and preserves their native transcripts; run it directly whenever a cut changes mux fresh-call/launch behavior. Its private tmux servers and fixture-bound entwurf write axes make it deterministic about garden-record/socket residue, but those real native turns should not appear unexpectedly in every aggregate cut. **Both this smoke and `smoke-mux-lifecycle-live` enumerate `pi` and `claude-code` only — that exclusion is intentional.** A Copilot visible-fresh (step 9 clause 7) was accepted on 2026-08-25 as an operator-metered LIVE (receipts in `DELIVERY.md`'s Copilot matrix row and the issue #82 thread). It still spends Copilot premium quota, still needs its own grant to re-run, and is never folded into those loops or into the release-gate MUST tier.
62
62
  >
63
63
  > The aggregate release gate does not own a live agy conversation id, so agy's real native-push round trip is a separate acceptance axis: three fail-loud doctors plus `LIVE=1 AGY_CONVERSATION_ID=<id> ./run.sh smoke-agy-native-push-live`, followed by a fresh-conversation sender/reply check after package install. Its deterministic install/sender gates are already inside `pnpm run check:full`; do not misreport the aggregate gate as live agy evidence. **Cost fence:** the agy conversation this smoke drives runs on a free account, so open it on `gemini-3.6-flash` — never a Pro tier. The model is the operator's choice at conversation-open time; entwurf never selects it, and no assertion reads it (see the shipped-lane note: model display is not part of the agy contract).
64
64
  >
@@ -146,7 +146,7 @@ git clone https://github.com/junghan0611/entwurf /path/to/entwurf && cd $_
146
146
  # re-run the SAME command any time to repair a broken install
147
147
  ```
148
148
 
149
- Expected tail: `DONE: entwurf setup (pi adapter + detected native bridges + v2 install smoke) green.` On a host with `claude`, verify `./run.sh doctor-meta-bridge`. On a host with `agy`, verify all three: `doctor-agy-bridge`, `doctor-agy-statusline`, and `doctor-agy-hooks`. Setup keeps optional-harness failures non-fatal so pi/Claude hosts are not bricked; the doctors are the fail-loud acceptance surface.
149
+ Expected tail: `DONE: entwurf setup (pi adapter + detected native bridges + v2 install smoke) green.` On a host with `claude`, verify `./run.sh doctor-meta-bridge`. On a host with `agy`, verify all three: `doctor-agy-bridge`, `doctor-agy-statusline`, and `doctor-agy-hooks`. After adding a backend to `META_BACKENDS`, re-run the sibling install then the doctor — a green checkout with a stale deployed plugin is a silent write-stop on that rail. Setup keeps optional-harness failures non-fatal so pi/Claude hosts are not bricked; the doctors are the fail-loud acceptance surface.
150
150
 
151
151
  The wiring / meta-bridge / smoke steps are internal building blocks of `setup` (`install_local_package`, `scripts/meta-bridge-install.sh`, `validate_entwurf_bridge`) — call `setup`, never the parts. Consumers who `npm install @junghanacs/entwurf` get the obvious npm surface; that path is not the developer concern here.
152
152
 
package/demo/README.md CHANGED
@@ -139,7 +139,7 @@ SCENE_DELAY=30 FINAL_PAUSE=10 bash demo.sh
139
139
 
140
140
  ## Prerequisites
141
141
 
142
- - `pi` on PATH (current floor 0.84.2)
142
+ - `pi` on PATH (current floor 0.84.3)
143
143
  - `entwurf` provider configured + auth ready for the selected sender/peer models
144
144
  - `asciinema` installed
145
145
  - `agg` installed (optional — only for GIF conversion)
@@ -93,7 +93,7 @@ undifferentiated "supported" column is what let a Claude PASS read as if it also
93
93
  | Surface | Declaration | Class | What a green actually says |
94
94
  |---|---|---|---|
95
95
  | Entwurf package | `0.14.1` | shipped baseline | the package contract these rows belong to |
96
- | pi runtime | devDep exact `0.84.2`, peer `>=0.84.2 <0.85` | **exact** oracle + **closed range** | built and certified against 0.84.2; hosts inside the range are accepted, and the ceiling moves only on measurement |
96
+ | pi runtime | devDep exact `0.84.3`, peer `>=0.84.3 <0.85` | **exact** oracle + **closed range** | built and certified against 0.84.3; hosts inside the range are accepted, and the ceiling moves only on measurement |
97
97
  | ACP wire SDK | `@agentclientprotocol/sdk 1.3.0` | **exact** | the shared wire oracle both adapters speak |
98
98
  | Claude ACP adapter | `@agentclientprotocol/claude-agent-acp 0.70.0` | **exact**, bundled | the adapter we ship and certify; resolved before any PATH fallback |
99
99
  | Claude Agent SDK | `0.3.232` (transitive) | **exact** oracle | the runtime risk surface behind the adapter |
@@ -7,17 +7,39 @@ the one-line registration; everything an operator needs to actually wire a host
7
7
  `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:
8
8
 
9
9
  - **plain external MCP host**: no garden meta-record / sender marker. It can call the read surfaces (`entwurf_peers`, `entwurf_inbox_read`), but `entwurf_v2` sends are **refused by default** (#50 C4: "if we don't know who sent it, we don't send it"). The operator may wire the explicit hatch below; the send then goes out external/non-replyable.
10
- - **garden-native native session**: a trusted lifecycle hook minted a garden id and sender marker — `SessionStart` for Claude Code, `PreInvocation` for agy. It is not a pi control-socket session, but it can be replyable by garden id when its own mailbox/probe rail says so.
10
+ - **garden-native native session**: a trusted lifecycle hook minted a garden id and sender marker — `SessionStart` for Claude Code, `PreInvocation` for agy, `userPromptSubmitted`/`sessionStart` for GitHub Copilot CLI. It is not a pi control-socket session, but it can be replyable by garden id when its own mailbox/probe rail says so.
11
11
 
12
- **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). Open a NEW sibling with **`entwurf_fresh_call {backend, model, task, cwd?}`**, and reopen a DORMANT pi citizen under its own garden id with **`entwurf_resume_call {target}`**. Fresh call accepts one optional literal absolute `cwd`: omit it or pass `""` for the caller's cwd; otherwise it must name an existing directory and may not contain `#`. Use that input for a new cross-repository sibling resume preserves a dormant Pi citizen's recorded continuity and is not a cwd substitute. Fresh call needs its selected runtime (`pi` or `claude`) on the server's PATH; resume call always needs `pi`. Both start a runtime, while delivery does not. (The old v1 verbs `entwurf` / `entwurf_resume` / `entwurf_send` are gone.)
12
+ **Being garden-native is not the same as being replyable, and Copilot is the case that separates them.** Its hook writes a sender marker, so an `entwurf_v2` send carries its own garden id and the receiver learns who wrote measured 2026-08-21 on Copilot CLI 1.0.80, where a live send arrived under its own garden id with `origin: "meta-session"` and `replyable: false`. Replyability arrived later and through a different process: a first-party extension (`run.sh install-copilot-receive`) that the CLI forks, which binds to the same V3 record and writes a receiver marker owned by its own pid (#82 RAIL 5). So a Copilot citizen is `replyable: true` exactly while that extension is armed for it, and `replyable: false` honestly, with a real garden identity when it is not installed, not launched with `COPILOT_CLI_ENABLED_FEATURE_FLAGS=EXTENSIONS` (which is what `entwurf copilot` sets for one invocation), not yet born, or gone. Read the two facts off different rails: the sender marker answers *who sent this*; the receiver marker answers *can a reply land*. The onboarding obligations are in [`adding-a-harness.md`](./adding-a-harness.md) step 7, and the evidence boundary the managed LIVE acceptance that moved receive to D6, and what D7-partial / D3-pending still exclude is in [`DELIVERY.md`](../DELIVERY.md).
13
13
 
14
- 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.
14
+ **Which verb an external agent should reach for:** to deliver to / reply to a garden id, use **`entwurf_v2`** — it is the canonical delivery surface and the only one that reads whether the target is live pi, dormant pi, 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). Open a NEW sibling with **`entwurf_fresh_call {backend, model, task, cwd?}`**, and reopen a DORMANT pi citizen under its own garden id with **`entwurf_resume_call {target}`**. Fresh call accepts one optional literal absolute `cwd`: omit it or pass `""` for the caller's cwd; otherwise it must name an existing directory and may not contain `#`. Use that input for a new cross-repository sibling resume preserves a dormant Pi citizen's recorded continuity and is not a cwd substitute. Fresh call needs its selected runtime on the server's PATH (`pi`, `claude`, or `entwurf` — Copilot opens through the managed `entwurf copilot` invocation, never the bare vendor); resume call always needs `pi`. Both start a runtime, while delivery does not. (The old v1 verbs `entwurf` / `entwurf_resume` / `entwurf_send` are gone.)
15
+
16
+ Observed: Claude Code, Codex CLI, Antigravity CLI and GitHub Copilot 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 and Copilot become symmetric/replyable through a mailbox-backed meta-session — Claude's watch armed by its own hook, Copilot's by the forked extension it installs; 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.
15
17
 
16
18
  Prerequisites on the host running the external MCP client:
17
19
 
18
20
  - A live pi session launched with `--entwurf-control` populates `~/.pi/entwurf-control/<gardenId>.sock` — the key is the **record's** garden id, never a transcript/session id (`PI_SESSION_ID` only carries the id record birth already established). Required for `entwurf_v2` control-socket dispatch and `entwurf_peers`.
19
21
 
20
- > **PATH boundary.** MCP servers are often launched by GUI/editor daemons and may not inherit the interactive shell's PATH. No `entwurf_v2` rail launches a process, so this does not affect delivery — but `entwurf_fresh_call` and `entwurf_resume_call` do open a fixed runtime. If that runtime works in your terminal but an external-host call fails with `spawn pi ENOENT` or `spawn claude ENOENT`, pass a full PATH in the MCP server `env`, set `ENTWURF_BRIDGE_ENV_FILE` to a small shell file that exports PATH, or point the host at a wrapper that can find the runtime. `start.sh` sources only the explicit `ENTWURF_BRIDGE_ENV_FILE`; it never reads personal dotfiles automatically.
22
+ > **PATH boundary.** MCP servers are often launched by GUI/editor daemons and may not inherit the interactive shell's PATH. No `entwurf_v2` rail launches a process, so this does not affect delivery — but `entwurf_fresh_call` and `entwurf_resume_call` do open a fixed runtime. If that runtime works in your terminal but an external-host call fails with `spawn pi ENOENT`, `spawn claude ENOENT`, or `spawn entwurf ENOENT`, pass a full PATH in the MCP server `env`, set `ENTWURF_BRIDGE_ENV_FILE` to a small shell file that exports PATH, or point the host at a wrapper that can find the runtime. `start.sh` sources only the explicit `ENTWURF_BRIDGE_ENV_FILE`; it never reads personal dotfiles automatically.
23
+
24
+ > **Identity-carrier boundary.** A complete `PI_SESSION_ID` + `PI_AGENT_ID` pair wins before a
25
+ > native sender marker in the bridge's authoritative-self resolution. Those variables are correct
26
+ > inside the pi process that planted them from record birth; they are foreign identity in another
27
+ > native harness. Starting that harness from a pi citizen's bash can otherwise make the new host —
28
+ > and any internal agents borrowing its MCP manager — speak under the parent pi garden id. Every
29
+ > managed native launcher therefore removes both variables before exec and lets the launched
30
+ > harness establish its own identity through its trusted birth marker. Clearing only one is not a
31
+ > repair: an incomplete pair merely changes the failure wording, while retaining either carrier
32
+ > invites future partial-reader drift. An unmanaged launch that inherits them is unsupported. A
33
+ > native-harness admission made under this contract must add a doctor cell that names this
34
+ > contamination rather than silently preferring the marker.
35
+ >
36
+ > **Certified so far: Copilot only** (#82 RAIL 9). `scripts/copilot-launch.sh` unsets both before
37
+ > `exec`, `check-copilot-launch` asserts their absence from the vendor's OWN report of its
38
+ > environment (and that clearing one alone does not pass), and `doctor-copilot-receive` reports any
39
+ > live Copilot CLI still carrying either as RED on its own axis — a session can be perfectly armed
40
+ > and still speak under the parent's garden id, so the flag verdict must not absorb it. The other
41
+ > native launchers have NOT been certified against this failure mode; do not read Copilot's cells
42
+ > as coverage for them.
21
43
 
22
44
  Example env file:
23
45