@junghanacs/entwurf 0.39.0 → 0.41.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 (137) hide show
  1. package/AGENTS.md +4 -2
  2. package/BASELINE.md +60 -5
  3. package/CHANGELOG.md +557 -0
  4. package/CONTRIBUTING.md +1 -1
  5. package/DELIVERY.md +52 -2
  6. package/FAQ.md +8 -4
  7. package/README.md +37 -17
  8. package/VERIFY.md +23 -10
  9. package/docs/acp-backend-rail.md +5 -1
  10. package/docs/external-mcp-host.md +8 -6
  11. package/docs/fresh-cut-policy.md +9 -1
  12. package/docs/setup-clean-host.md +64 -9
  13. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +100 -24
  14. package/mcp/entwurf-bridge/dist/pi-extensions/lib/cc-retire-pidfd.py +236 -0
  15. package/mcp/entwurf-bridge/dist/pi-extensions/lib/cc-retire-request.js +473 -0
  16. package/mcp/entwurf-bridge/dist/pi-extensions/lib/control-retire.js +125 -0
  17. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-retire-production.js +149 -0
  18. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-retire.js +158 -0
  19. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-surface.js +2 -1
  20. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-visible-resume.js +283 -20
  21. package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-composition.js +7 -0
  22. package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-dispatch.js +47 -3
  23. package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-fresh-call.js +3 -1
  24. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-mailbox-body.js +10 -4
  25. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js +96 -16
  26. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +5 -29
  27. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-launch.js +28 -0
  28. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-resume-call.js +26 -11
  29. package/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-retire-request.js +76 -0
  30. package/mcp/entwurf-bridge/dist/pi-extensions/lib/remote-pair-receive.js +123 -0
  31. package/mcp/entwurf-bridge/dist/pi-extensions/lib/remote-pair-send.js +259 -0
  32. package/mcp/entwurf-bridge/dist/pi-extensions/lib/remote-pair-transport.js +80 -0
  33. package/mcp/entwurf-bridge/dist/pi-extensions/lib/resume-launch-identity.js +67 -17
  34. package/mcp/entwurf-bridge/dist/pi-extensions/lib/retirement-event.js +211 -0
  35. package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-hook.js +124 -10
  36. package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-pi-durable.js +53 -2
  37. package/mcp/entwurf-bridge/dist/scripts/meta-facts.js +1 -1
  38. package/mcp/entwurf-bridge/dist/scripts/probe-bridge-command.js +1 -0
  39. package/mcp/entwurf-bridge/dist/scripts/remote-pair-receive.js +65 -0
  40. package/mcp/entwurf-bridge/src/index.ts +118 -24
  41. package/mcp/entwurf-bridge/test.sh +2 -2
  42. package/mcp/entwurf-bridge/tsconfig.build.json +17 -1
  43. package/package.json +5 -5
  44. package/pi/meta-bridge/entwurf-meta-receive/scripts/hook-launch.sh +3 -3
  45. package/pi-extensions/entwurf-control.ts +55 -20
  46. package/pi-extensions/lib/cc-retire-pidfd.py +236 -0
  47. package/pi-extensions/lib/cc-retire-request.ts +553 -0
  48. package/pi-extensions/lib/control-retire.ts +195 -0
  49. package/pi-extensions/lib/entwurf-control-rpc.ts +17 -1
  50. package/pi-extensions/lib/entwurf-v2-retire-production.ts +227 -0
  51. package/pi-extensions/lib/entwurf-v2-retire.ts +269 -0
  52. package/pi-extensions/lib/entwurf-v2-surface.ts +2 -1
  53. package/pi-extensions/lib/entwurf-v2-visible-resume.ts +390 -36
  54. package/pi-extensions/lib/fresh-call-composition.ts +15 -0
  55. package/pi-extensions/lib/fresh-call-dispatch.ts +45 -4
  56. package/pi-extensions/lib/herdr-fresh-call.ts +5 -0
  57. package/pi-extensions/lib/meta-mailbox-body.ts +13 -4
  58. package/pi-extensions/lib/meta-session.ts +140 -16
  59. package/pi-extensions/lib/mux-fresh-call.ts +7 -27
  60. package/pi-extensions/lib/mux-launch.ts +29 -0
  61. package/pi-extensions/lib/mux-resume-call.ts +33 -12
  62. package/pi-extensions/lib/pi-retire-request.ts +111 -0
  63. package/pi-extensions/lib/remote-pair-receive.ts +184 -0
  64. package/pi-extensions/lib/remote-pair-send.ts +325 -0
  65. package/pi-extensions/lib/remote-pair-transport.ts +81 -0
  66. package/pi-extensions/lib/resume-launch-identity.ts +104 -18
  67. package/pi-extensions/lib/retirement-event.ts +253 -0
  68. package/pi-extensions/meta-bridge-hook.ts +142 -13
  69. package/pi-extensions/meta-bridge-pi-durable.ts +56 -2
  70. package/run.sh +122 -7
  71. package/scripts/agy-bridge-config.py +4 -1
  72. package/scripts/build-bridge.sh +14 -0
  73. package/scripts/check-capability-bundle-reach.ts +135 -2
  74. package/scripts/check-entwurf-bridge-boot.ts +2 -1
  75. package/scripts/check-entwurf-fact-provider.ts +1 -0
  76. package/scripts/check-entwurf-v2-production.ts +2 -0
  77. package/scripts/check-entwurf-v2-surface.ts +7 -0
  78. package/scripts/check-gate-qualification.ts +20 -8
  79. package/scripts/check-herdr-activation.ts +2 -2
  80. package/scripts/check-herdr-placement.ts +1 -0
  81. package/scripts/check-hook-launch-topology.ts +6 -15
  82. package/scripts/check-install-container.sh +2 -2
  83. package/scripts/check-meta-doctor-oracle.sh +27 -1
  84. package/scripts/check-meta-facts.ts +8 -3
  85. package/scripts/check-meta-hook-session-switch.ts +230 -13
  86. package/scripts/check-meta-identity-consumers.ts +1 -0
  87. package/scripts/check-meta-listing.ts +1 -0
  88. package/scripts/check-meta-manifest-schema.py +1 -1
  89. package/scripts/check-meta-session.ts +31 -0
  90. package/scripts/check-meta-v3-record.ts +55 -1
  91. package/scripts/check-mux-resume-call.ts +35 -9
  92. package/scripts/check-peer-facts.ts +1 -0
  93. package/scripts/check-pi-durable-contact.ts +3 -1
  94. package/scripts/check-pi-durable-send.ts +4 -2
  95. package/scripts/check-pi-mcp-bridge.ts +1 -0
  96. package/scripts/check-remote-pair-installed.ts +242 -0
  97. package/scripts/check-resume-launch-identity.ts +62 -23
  98. package/scripts/lib/meta-hook-bundle.ts +72 -0
  99. package/scripts/lib/remote-pair-installed-audit.ts +25 -0
  100. package/scripts/meta-bridge-claude-floor.sh +13 -9
  101. package/scripts/meta-bridge-doctor.sh +17 -0
  102. package/scripts/meta-bridge-install.sh +6 -0
  103. package/scripts/meta-facts.ts +1 -1
  104. package/scripts/mutants/bridge-boot-resume.json +5 -2
  105. package/scripts/mutants/cc-retire-pidfd.json +129 -0
  106. package/scripts/mutants/cc-retire-request.json +171 -0
  107. package/scripts/mutants/control-retire.json +105 -0
  108. package/scripts/mutants/control-send-receipt.json +2 -2
  109. package/scripts/mutants/copilot-receive.json +2 -6
  110. package/scripts/mutants/entwurf-v2-retire.json +476 -0
  111. package/scripts/mutants/fresh-call-dispatch.json +37 -0
  112. package/scripts/mutants/meta-hook-session-switch.json +88 -0
  113. package/scripts/mutants/meta-identity.json +57 -0
  114. package/scripts/mutants/mux-fresh-call.json +1 -1
  115. package/scripts/mutants/mux-resume-call.json +40 -4
  116. package/scripts/mutants/pi-durable.json +79 -0
  117. package/scripts/mutants/pi-retire-request.json +72 -0
  118. package/scripts/mutants/remote-pair-display.json +53 -0
  119. package/scripts/mutants/remote-pair-installed-audit.json +28 -0
  120. package/scripts/mutants/remote-pair-receive.json +126 -0
  121. package/scripts/mutants/remote-pair-roundtrip.json +83 -0
  122. package/scripts/mutants/remote-pair-send.json +194 -0
  123. package/scripts/mutants/remote-pair-transport.json +66 -0
  124. package/scripts/mutants/resume-launch-identity.json +35 -8
  125. package/scripts/mutants/retirement-event.json +195 -0
  126. package/scripts/mutants/v2-surface.json +16 -0
  127. package/scripts/mutants/v2-visible-resume.json +283 -13
  128. package/scripts/probe-bridge-command.ts +1 -0
  129. package/scripts/remote-pair-receive.ts +73 -0
  130. package/scripts/smoke-agy-install-state.sh +2 -2
  131. package/scripts/smoke-copilot-mcp-state.sh +1 -1
  132. package/scripts/smoke-herdr-raw-install-live.sh +5 -4
  133. package/scripts/smoke-meta-install-state.sh +4 -4
  134. package/scripts/smoke-mux-lifecycle-live.ts +10 -10
  135. package/scripts/smoke-omp-mcp-state.sh +1 -1
  136. package/scripts/smoke-pi-durable-fresh-live.ts +5 -3
  137. package/scripts/smoke-pi-provider-state.sh +3 -3
package/DELIVERY.md CHANGED
@@ -59,6 +59,22 @@ ringing any doorbell, its filenames are ISO stamps so a drain is a global FIFO,
59
59
  is an honest `mailbox-undeliverable` refusal, and its receipt is that FILE. A mailbox enqueue is a
60
60
  durable file receipt; a control-socket queue is process memory.
61
61
 
62
+ ### Candidate lifecycle commands are not delivery boundaries (#147)
63
+
64
+ The current checkout's Pi control server also accepts the internal `retire` RPC, so a Pi
65
+ started from that checkout can expose it before a release is cut. It requires matching
66
+ record/native identity, a TUI, idle state and no pending work. Its `shutdown-requested` ACK
67
+ is not socket closure, process exit, flush or model silence. This command is not itself a public
68
+ MCP/contact verb and is not part of the four send boundaries above; the candidate public
69
+ `entwurf_retire_call` reaches a Pi target through it. A Pi whose process started before its
70
+ checkout carried this command does not know it, and its generic error answer is classified as
71
+ `unknown`/`malformed-response`, never as a safe refusal or a reason to replay. The candidate
72
+ CC pidfd helper observes one fd-pinned process, not a conversation, descendants or flush;
73
+ unknown never grants safe replay. Package/source presence, compiled consumer checks and a
74
+ real native lifecycle remain separate evidence axes; the one dated native lifecycle measurement
75
+ is [BASELINE's 2026-10-22 n=1 round trip](./BASELINE.md#147-native-round-trip-2026-10-22-n1). Historical delivery receipts below do
76
+ not certify these candidate components.
77
+
62
78
  ## Levels (D0–D8)
63
79
 
64
80
  These are independent of VERIFY's `L0–L5` evidence levels and BASELINE's question
@@ -109,7 +125,7 @@ physical host. Do not read a D-level cell as a Darwin receipt.
109
125
  | Harness / surface | Product status | Capability | Transport and boundary |
110
126
  |---|---|---|---|
111
127
  | **pi Entwurf** | shipped | D7; D8 partial | Record-addressed Unix control socket. A record-less socket is diagnostic only and never dispatched. A pi session calls the verbs through Pi's built-in MCP running the compiled `entwurf-bridge` (#125); the receive rail is unchanged. |
112
- | **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. |
128
+ | **Claude Code interactive `>=2.1.306`** | 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. `>=2.1.306` is the current declared floor; that historical B2 receipt does not certify #147 resume/retire lifecycle on this floor. |
113
129
  | **Antigravity / agy** | shipped | D6; D7 partial | Record-backed native-push through LS gRPC `agentapi send-message`; no mailbox or receiver marker. Admitted before the #82 step 9 contract and not re-evaluated under it, so it is legacy citizen evidence, not a step-9 supported harness: `entwurf_fresh_call` cannot open an agy sibling, and nothing here should be read as claiming visible lifecycle parity. |
114
130
  | **Codex CLI app-server citizen** | supported in 0.21.0; caller-seat topology ACCEPTED on Linux at 0.153.4, 2026-09-16 (56 assertions, exit 0), and the caller-DIRECTORY axis ACCEPTED the same day on the same host (#95 lane C, 65 assertions, exit 0; the 2026-09-12 explicit-home acceptance is history — #95 D1 retired that room) | D6 accepted; D7 partial; D8 unproven | Vendor-trusted user-scope `SessionStart` birth plus strict per-request metadata join; no shared-pid sender marker. `thread/loaded/list` probes the operator-owned app-server UDS and one-shot `codex queue` delivers with no retry. The operator owns the app-server and seats it wherever they like; Entwurf creates, moves and supervises none of it. Since #95 lane B a Codex CALLER with no explicit placement opens its sibling beside its OWN pane, located by the `thread-id` its `[tui].terminal_title` writes into the pane title; 0 or 2+ matching panes refuse with no fallback, and the title is a placement input only — never an address, liveness or delivery fact. #95 D1 (2026-09-16) retired the fixed `codex` home that omitted-placement Codex TARGETS used to select, so an omitted seat is the caller's own session for every backend. Exact 0.153.4 source still bounds placing a sibling beside a TUI whose thread nobody named as unsupported; the anchor resolves only the pane showing the caller's own thread. |
115
131
  | **Codex embedded TUI** | deferred | D0 partial | At the 2026-09-08 Codex 0.153.4 measurement, standalone mode had no `watchPaths`/`FileChanged`/`asyncRewake` analogue or supported idle receive route. This is dated vendor evidence, not a claim that the current candidate lacks records or fresh. |
@@ -128,7 +144,7 @@ Re-audit backend versions before turning probe evidence into a shipped adapter.
128
144
 
129
145
  ### Claude Code: durable body, edge-triggered wake
130
146
 
131
- Claude's hook contract is exec-only at `>=2.1.217`. `SessionStart` arms a per-session
147
+ Claude's hook contract is exec-only at `>=2.1.306`. `SessionStart` arms a per-session
132
148
  watch path; the sender writes durable `*.msg` bodies before poking the signal;
133
149
  `FileChanged` emits a doorbell and `asyncRewake` wakes the idle session. The receiver
134
150
  then calls `entwurf_inbox_read`, which drains all unread bodies and archives them as
@@ -571,6 +587,40 @@ same class of capability through the CLI-owned stdio lifecycle. Absence of Claud
571
587
  `FileChanged` / `asyncRewake` / `watchPaths` therefore means only that Claude's hook
572
588
  mechanism cannot be copied — it never proved Copilot had no vendor wake surface.
573
589
 
590
+ ### Remote pair (#107): one declared pair across two hosts
591
+
592
+ This is an opt-in route between two existing citizens on two hosts. Why it keeps these boundaries, and which questions it leaves open, is [remote-sibling-principles](./docs/remote-sibling-principles.md); this section owns the mechanism. It is off unless the operator places a file on each host. The operator owns the connection between the hosts (a private network such as Tailscale is recommended, not required) and owns SSH authentication and authorization. Entwurf installs, configures, discovers and retries none of it. It adds no relay, listener, queue or registry. One SSH process runs per send and then exits.
593
+
594
+ - **Declaration.** Each host has at most one `entwurf-remote-pair.json`, beside its meta-record store (`remotePairOptInPath()`: the pi agent dir, unless `ENTWURF_META_SESSIONS_DIR` moves the store and the file with it). It has exactly four keys:
595
+ - `remoteGardenId`.
596
+ - `ssh`: an existing SSH alias whose characters need no quoting.
597
+ - `localGardenId`.
598
+ - `remotePairFile`: the other host's canonical absolute path to its own file.
599
+
600
+ One file serves both directions. An absent file means off. A symlink, non-regular, unreadable or invalid file is refused. The file is read at every send and receive and never cached. The sending bridge and the SSH-spawned receive entry must resolve the same root; an override set in only one of those environments moves the file.
601
+ - **Send.** The sending host's `entwurf_v2` reads the file and then decides:
602
+ - A target other than `remoteGardenId` takes the unchanged local path.
603
+ - A declared target that also has a local trace on this host (a record in any state, or a control socket) is refused as `remote-route-local-conflict`, with zero SSH.
604
+ - A sender other than `localGardenId` is refused as `remote-sender-not-declared`, with zero SSH.
605
+ - Otherwise it spawns one `ssh -o BatchMode=yes -o ConnectTimeout=10 <alias> entwurf remote-pair-receive --pair <remotePairFile>`, with no shell and the claim on stdin.
606
+ - **Receive.** The entry accepts only when `--pair` equals its own canonical file path. Canonical means the normalized expected path, not realpath equivalence: a spelling through a symlinked home alias is refused. That file is the entry's whole scope. It refuses a `from` or `target` outside the declared pair before any mailbox mutation. A match goes to that host's existing local `entwurf_v2` dispatch, so every deliverability rule of the target's rail applies there; the measured pair used the self-fetch mailbox.
607
+ - **Outcomes.** The send ends in one of three outcomes:
608
+ - the other host's receipt, quoted verbatim (its paths are on the other host). This is either an acceptance or an error the other host reported while processing (`remote-reported-error`, exit 4). That error can follow a write, for example an enqueue whose later step threw and is reported `retry-safe: false`. The sender's header therefore claims neither non-delivery nor safety to resend; the quoted text is the fact;
609
+ - the other host's named refusal, made before any mutation: a pair mismatch (exit 3) or invalid input (exit 2);
610
+ - `remote-indeterminate`.
611
+
612
+ A result is decided only when the exit code and the JSON kind agree. There is no retry. An unanswered message stays unconfirmed. Acceptance is not a read or a reply. Neither side learns whether its last message was read unless the other side answers, and there is no third ACK.
613
+ - **Display.** The received envelope names the remote origin and route, marks the sender's cwd as a sender-host path, and carries `replyable=false`, with "reply route declared, not probed" stated separately. Display is proven only for the self-fetch mailbox body; control-socket and native-push rendering are not proven. `entwurf_callback` stays local.
614
+ - **Trust.** The model is same-operator SSH account trust. The receiving host checks the declared pair. It does not cryptographically authenticate a citizen, and the same account could claim either declared id.
615
+ - **Visibility.** For the measured self-fetch pair, delivery requires backing records, live owners and armed watches, so an OFF citizen of that rail is undeliverable. Any other target keeps its own rail-specific local admission. The remote route has no launch, fresh or resume path. The code does not check that the target's screen is open: an owner pid, a TTY or a garden-id label is not visibility. Live use addresses only siblings the user has open, and delivery to an active pi-durable can start a turn at once.
616
+ - **SSH side effects.** The transport adds exactly two options, `BatchMode=yes` and `ConnectTimeout=10`. Host-key checking, known-hosts writes and every other SSH policy remain the operator's, as configured. Their side effects on a real exchange were not measured.
617
+
618
+ Evidence:
619
+
620
+ - T1 (executor-in) and T2 (surface-in) are deterministic. T2 drives the real bridge over MCP stdio, and a stand-in `ssh` on PATH runs the other host's real entry under a separate HOME. An installed-tarball gate runs the T2 cells against installed bytes.
621
+ - One real exchange is recorded in [#107](https://github.com/junghan0611/entwurf/issues/107#issuecomment-6278665895), at branch commit `e632294` on 2026-10-20. A nonce went B→A→B between two already-open pi-durable siblings on two Linux hosts. That is L4: one pair, one run.
622
+ - Unmeasured: an OFF target, negative paths over real SSH, other rails, more than one pair, and non-Linux hosts.
623
+
574
624
  ## Recording a new claim
575
625
 
576
626
  For every matrix change, record:
package/FAQ.md CHANGED
@@ -8,7 +8,7 @@ No. Entwurf is closer to a workshop than a factory: it connects independently ow
8
8
 
9
9
  ## What does Entwurf do?
10
10
 
11
- It opens, reopens and messages visible sibling sessions across independent harnesses, each under a stable **garden id**. `entwurf_fresh_call` opens a new sibling in the caller's Herdr workspace or tmux session by default, `entwurf_resume_call` reopens a dormant pi sibling under the same id, and `entwurf_v2` delivers a message, reply or hand-off to an existing one through the rail it actually supports. It does not reconstruct prompts, hydrate transcripts, manage credentials, or emulate another harness.
11
+ It opens, reopens and messages visible sibling sessions across independent harnesses, each under a stable **garden id**. `entwurf_fresh_call` opens a new sibling in the caller's Herdr workspace or tmux session by default, `entwurf_resume_call` reopens a dormant pi or Claude Code sibling under the same id, and `entwurf_v2` delivers a message, reply or hand-off to an existing one through the rail it actually supports. It does not reconstruct prompts, hydrate transcripts, manage credentials, or emulate another harness.
12
12
 
13
13
  ## What is a garden id?
14
14
 
@@ -24,15 +24,19 @@ The visible top-level session is the garden principal. Internal children remain
24
24
 
25
25
  ## Does sending a message start a process?
26
26
 
27
- No. [`entwurf_v2`](./README.md#entwurf_v2--canonical-dispatch-verb) only addresses an existing garden citizen and selects its supported delivery rail. `entwurf_fresh_call` opens a new visible sibling; `entwurf_resume_call` reopens a dormant pi citizen under the same id.
27
+ No. [`entwurf_v2`](./README.md#entwurf_v2--canonical-dispatch-verb) only addresses an existing garden citizen and selects its supported delivery rail. `entwurf_fresh_call` opens a new visible sibling; `entwurf_resume_call` reopens a dormant pi or Claude Code citizen under the same id.
28
+
29
+ ## Can an agent retire a sibling on its own?
30
+
31
+ No. `entwurf_retire_call` (new in 0.41.0) attempts retirement of one Pi or Claude Code sibling, and only one the human operator explicitly requested. Seeing the tool, a coordinator role or the caller's identity is not permission; where a host provides an approval step it applies too, and Pi and pi-durable 1.3.0 have none by default. The configured Claude ACP child is also [set to run without per-call permission prompts](./docs/acp-backend-rail.md#configured-claude-acp-permissions); that is source configuration, not measured ACP retirement. The record stays, so `entwurf_resume_call` can reopen the same id, and an unknown result is never a reason to retry.
28
32
 
29
33
  ## Where do visible siblings open?
30
34
 
31
- Inside Herdr, Entwurf opens a new unfocused tab in the caller's workspace and currently admits pi and Claude Code. Outside Herdr, it opens one of the fixed pi, pi-durable, Claude Code, Copilot, OMP, or Codex runtimes in a new window of the caller's tmux session, or of an existing session the caller names; without an explicit tmux placement, a Codex caller opens beside its own TUI pane. A dormant pi sibling reopens under the same id in a visible tmux window. The runtime keeps its own model, tools, authentication, and transcript.
35
+ Inside Herdr, Entwurf opens a new unfocused tab in the caller's workspace and currently admits pi and Claude Code. Outside Herdr, it opens one of the fixed pi, pi-durable, Claude Code, Copilot, OMP, or Codex runtimes in a new window of the caller's tmux session, or of an existing session the caller names; without an explicit tmux placement, a Codex caller opens beside its own TUI pane. A dormant pi or Claude Code sibling reopens under the same id in a visible tmux window. The runtime keeps its own model, tools, authentication, and transcript.
32
36
 
33
37
  ## Does Entwurf replace Herdr or tmux?
34
38
 
35
- No. Herdr and tmux provide visible seats. Entwurf opens siblings into them, reopens dormant pi siblings, and carries identity, delivery, and receipts across independent harness sessions. A pane or tmux session is a location for a sibling, never its address or liveness proof.
39
+ No. Herdr and tmux provide visible seats. Entwurf opens siblings into them, reopens dormant pi or Claude Code siblings, and carries identity, delivery, and receipts across independent harness sessions. A pane or tmux session is a location for a sibling, never its address or liveness proof.
36
40
 
37
41
  ## How do I install it?
38
42
 
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # entwurf
2
2
 
3
3
  `entwurf` connects independent agent harnesses as visible siblings: open a new session,
4
- reopen a dormant pi session in tmux, or reach an existing sibling by **garden id**. Fresh
4
+ reopen a supported dormant sibling in tmux, or reach an existing sibling by **garden id**. Fresh
5
5
  sessions open in the caller's Herdr workspace or tmux session by default; explicit tmux
6
6
  placement can name another existing session. Each harness keeps its own runtime,
7
7
  authentication, tools and transcript.
@@ -26,7 +26,8 @@ Git branches, worktrees, file isolation and internal delegation remain their con
26
26
  | Verb | Tool | Where it lands | Harnesses |
27
27
  |---|---|---|---|
28
28
  | **Open** a new sibling | `entwurf_fresh_call` | Herdr: a new unfocused tab in the caller's workspace. tmux: a new window in the caller's session, or an existing session named by `placement.tmuxSession`; without an explicit tmux placement, a Codex caller opens beside its own TUI pane | Herdr: pi, Claude Code. tmux: pi, pi-durable, Claude Code, Copilot, OMP, Codex |
29
- | **Reopen** a dormant sibling | `entwurf_resume_call` | A visible tmux window under the same garden id, with the transcript, model, provider and cwd its record names; no turn is run | pi |
29
+ | **Reopen** a dormant sibling | `entwurf_resume_call` | A visible tmux window under the same garden id, with its record selecting the native session (Pi: identity-checked recorded transcript supplies launch metadata; Claude Code: `claude --resume <session id>` at the record cwd); the verb submits no prompt | pi, Claude Code |
30
+ | **Retire** one sibling the human operator explicitly requested (new in 0.41.0) | `entwurf_retire_call` | One guarded attempt on that target: a Pi shutdown request or Claude Code fd-pinned process termination. Refused/unknown outcomes remain possible; the record stays for guarded same-id return. Not a grant, a roster cleanup or an automatic retry | pi, Claude Code |
30
31
  | **Message** an existing sibling | `entwurf_v2` | Its own rail: control socket, self-fetch mailbox or native push; delivery starts no process | pi, pi-durable, Claude Code, Copilot, OMP, Antigravity, Codex |
31
32
 
32
33
  Antigravity is message-only: an existing conversation registers itself, and Entwurf never
@@ -53,7 +54,7 @@ ownership of the harness.
53
54
 
54
55
  Prerequisites: **Node >=24.0.0**, Python 3 for `setup`, and any harnesses you choose
55
56
  to use, already installed and authenticated. Pi is optional-by-presence, supported
56
- range `>=1.3.0 <1.4`; Claude Code supported floor `>=2.1.217` is required for its
57
+ range `>=1.3.0 <1.4`; Claude Code supported floor `>=2.1.306` is required for its
57
58
  managed exec-hook lifecycle. pnpm is needed only for source-checkout setup.
58
59
 
59
60
  ```bash
@@ -81,11 +82,10 @@ Detected harness wiring on Darwin can therefore be written while setup stays
81
82
  non-green. WSL2: a CI runner (experimental) measured the Entwurf-only install and a
82
83
  zero-token Pi citizen; general, physical-machine or harness-parity WSL2 support is **not
83
84
  declared**, and delivery, other harnesses and physical machines are unmeasured. Native
84
- Windows has an **experimental first preview**, a CI-built x64 addon on `main` for the
85
- 0.39.0 candidate (not yet released; release and npm publication pending); the registry's
86
- 0.38.1 has no Windows addon. On a GitHub Actions `windows-2025` x64 runner with Node
87
- 24.18.1 and Pi 1.3.0, a CI-built artifact was installed, wired with `pi install`, birthed
88
- two Pi citizens and
85
+ Windows has an **experimental first preview**, a CI-built x64 addon that ships in core
86
+ 0.39.0 (published to npm); 0.38.1 and earlier carry no Windows addon. On a GitHub Actions
87
+ `windows-2025` x64 runner with Node 24.18.1 and Pi 1.3.0, a CI-built artifact was installed,
88
+ wired with `pi install`, birthed two Pi citizens and
89
89
  delivered one garden-id `entwurf_v2` message between them, with an offline scripted model
90
90
  and no vendor turn. Every other native-Windows surface (other harnesses, `setup` and the
91
91
  `.sh` bins, fresh/resume, mux/Herdr/ACP, physical machines) is **not declared**; the
@@ -169,9 +169,11 @@ In that **harness session**, ask the agent to:
169
169
  ```
170
170
 
171
171
  These are tools, not shell subcommands. Pi calls
172
- `mcp__entwurf_bridge__<verb>` through Pi's built-in MCP. The server exposes **eight**
173
- verbs; a pi citizen has **six active + two hidden** (`entwurf_inbox_read` and
172
+ `mcp__entwurf_bridge__<verb>` through Pi's built-in MCP. The 0.41.0 bridge exposes **nine**
173
+ verbs; a pi citizen has **seven active + two hidden** (`entwurf_inbox_read` and
174
174
  `entwurf_register_native`), since it has no mailbox and its own extension births it.
175
+ Those counts describe the source; a running host offers what its already-loaded bridge
176
+ listed, so see [applying new source to a running host](./docs/setup-clean-host.md#applying-new-source-to-a-running-host).
175
177
  The server key stays `entwurf-bridge`. An operator/trusted-project MCP entry with
176
178
  that normalized namespace can replace the registration by Pi's own precedence;
177
179
  `/mcp` shows it, and Entwurf does not scan or fight that choice.
@@ -196,11 +198,14 @@ pi / pi-durable / Claude Code / Copilot / OMP / Antigravity / Codex
196
198
  |---|---|---|
197
199
  | Message, reply or hand off to an existing garden id | `entwurf_v2 {target, intent:"fire-and-forget", message}` | Receiver acceptance boundary, mailbox enqueue or native injection; not completion |
198
200
  | Open a new visible sibling | `entwurf_fresh_call {backend, model, task, cwd?}` | Launch receipt only; the sibling's nonce callback envelope supplies its new garden id |
199
- | Reopen a dormant pi citizen under its own id | `entwurf_resume_call {target}` | Separate LAUNCH and socket OBSERVATION receipts; no turn is run |
201
+ | Reopen a dormant pi or Claude Code citizen under its own id | `entwurf_resume_call {target}` | Separate LAUNCH and OBSERVATION receipts (pi: same-id socket; Claude Code: a new live receiver marker in the opened pane, bound to the record); the verb submits no prompt |
202
+ | Attempt retirement of one explicitly requested Pi or Claude Code target (new in 0.41.0) | `entwurf_retire_call {target}` | Separate action, append-only record and lock-release results. Pi ACK is request-only; CC exit is that fd-pinned process only |
200
203
 
201
- **Delivery starts no process.** A live pi receives over its record-addressed Unix
202
- control socket; an armed self-fetch citizen (Claude Code, Copilot, OMP, pi-durable)
203
- through its mailbox; a probe-alive native-push citizen (Antigravity, Codex) through
204
+ **Retirement is never autonomous.** Invoke it only for a target/action the human operator explicitly requested; development, coordinator status and caller identity are not standing permission. Where a host provides or is configured with an approval step, that step applies as well; Pi and pi-durable 1.3.0 have no built-in approval prompt by default. The configured Claude ACP child is likewise [set to run without per-call permission prompts](./docs/acp-backend-rail.md#configured-claude-acp-permissions); ACP retirement remains unmeasured. Tool annotations and the caller's identity are hints and attribution, not permission. No roster expansion, timer or automatic retry of an unknown/cancelled/lost result. The retirement path records requested/outcome events under a target-gid sidecar; absence is unknown, not proof of no retirement. Source, compiled fixture, Native/LIVE, rollout and release acceptance remain separate: one dated test-owned [Native round trip per backend](./BASELINE.md#147-native-round-trip-2026-10-22-n1) is recorded, which is neither a release gate nor a rollout claim.
205
+
206
+ **Delivery starts no process.** A live pi receives over its record-addressed control
207
+ address (a Unix socket; on the experimental native-Windows Pi preview, a named pipe);
208
+ an armed self-fetch citizen (Claude Code, Copilot, OMP, pi-durable) through its mailbox; a probe-alive native-push citizen (Antigravity, Codex) through
204
209
  its native conversation/thread. Dormant pi, undeliverable mailboxes, dead/indeterminate native
205
210
  push and record-less sockets reject honestly. Only control-socket dispatch takes
206
211
  the per-target lock; the other rails use their own deliverability/probe evidence.
@@ -213,6 +218,8 @@ receipt. `wants_reply` is etiquette, not waiting, polling or ownership; replyabi
213
218
  is a live rail fact, not merely a consequence of trusted identity.
214
219
  [DELIVERY](./DELIVERY.md#control-socket-acceptance-boundaries-120) owns these distinctions.
215
220
 
221
+ **One declared remote pair (opt-in, #107).** The operator places an `entwurf-remote-pair.json` on each of two hosts. Then `entwurf_v2` to the declared remote garden id runs the other host's `entwurf remote-pair-receive` once over the operator's own SSH, and that host checks the pair and dispatches locally. The trust is the same operator's SSH account. It makes one attempt with no retry, and it uses no relay or listener. [DELIVERY](./DELIVERY.md#remote-pair-107-one-declared-pair-across-two-hosts) owns the contract; [setup](./docs/setup-clean-host.md#2c-optional-remote-pair-between-two-hosts-107) covers the operator steps.
222
+
216
223
  **Mux launches; it never delivers.** Inside Herdr, fresh opens a new unfocused tab
217
224
  in the caller's workspace, pi/Claude Code only, with no tmux fallback. Outside,
218
225
  it opens pi/pi-durable/Claude Code/Copilot/OMP/Codex visibly on the caller's own tmux
@@ -223,8 +230,20 @@ tmux session (not permitted inside Herdr), never a new session or a cwd lookup.
223
230
  Omitted tmux placement follows the caller: Codex beside its own TUI pane matched
224
231
  by thread-id, everyone else in their own session. Missing/ambiguous context refuses.
225
232
  A launch receipt is neither the callback address nor proof of task processing.
226
- Resume is pi-only, tmux-only, same id, record-supplied transcript/model/provider/cwd;
227
- a live or non-pi target refuses, and an unobserved window stays visible without retry.
233
+ Resume is pi or Claude Code, tmux-only, same id, with the record as the sole address authority.
234
+ For Pi, the record selects the absolute transcript and its native id must match; the transcript's
235
+ session-header cwd and first `model_change` provider/model feed the launch resolver, without a
236
+ record/host cwd fallback. Claude Code uses `claude --resume <record session id>` at the record cwd,
237
+ without restoring model or permission flags;
238
+ a live, unprovably dormant, unbound or other-backend target refuses, and an unobserved window
239
+ stays visible without retry. Claude Code's [V3 arm binding](./docs/adding-a-harness.md#arm-binding-within-v3)
240
+ is a record–receiver witness, not a new identity or an occupancy/permission token.
241
+ Claude Code dormancy is read only from attaches the Entwurf hook committed: a process that
242
+ holds the session without one (hooks disabled, a lock timeout or store failure at its start)
243
+ is invisible to that check.
244
+ Claude Code same-id resume is new in 0.41.0. One dated test-owned
245
+ [Native round trip](./BASELINE.md#147-native-round-trip-2026-10-22-n1) was measured; its
246
+ release acceptance remains separate from the shipped Pi resume history.
228
247
  Exact boundaries: [tmux launch](https://github.com/junghan0611/entwurf/blob/main/docs/mux-launch-rail.md)
229
248
  and [Herdr launch](https://github.com/junghan0611/entwurf/blob/main/docs/herdr-launch-rail.md).
230
249
 
@@ -238,7 +257,8 @@ and [Herdr launch](https://github.com/junghan0611/entwurf/blob/main/docs/herdr-l
238
257
 
239
258
  The pi adapter registers the compiled bridge with Pi's built-in MCP after record
240
259
  birth; Pi supervises the MCP child. **MCP is ingress; pi receive remains the
241
- record-addressed Unix control socket.** Garden identity and dispatch stay Entwurf's.
260
+ record-addressed control address (a Unix socket, or the native-Windows preview's named
261
+ pipe).** Garden identity and dispatch stay Entwurf's.
242
262
  Codemode is harness-internal, not another garden citizen.
243
263
 
244
264
  ## Operation guides
package/VERIFY.md CHANGED
@@ -5,7 +5,7 @@ invariants live in gates; this file defines evidence strength, release acceptanc
5
5
  and the manual judgements a gate cannot make.
6
6
 
7
7
  > **Current surface.** `entwurf-bridge` exposes `entwurf_v2`, `entwurf_peers`,
8
- > `entwurf_fresh_call`, `entwurf_resume_call`, `entwurf_callback`, `entwurf_self`, `entwurf_inbox_read`, and `entwurf_register_native`. The ACP
8
+ > `entwurf_fresh_call`, `entwurf_resume_call`, `entwurf_retire_call` (0.41.0; operator-requested target only), `entwurf_callback`, `entwurf_self`, `entwurf_inbox_read`, and `entwurf_register_native` — nine; a pi citizen offers seven with two hidden, the pi-durable contact eight. The ACP
9
9
  > backends are Claude and Snowflake Cortex Code. Antigravity is a separate shipped
10
10
  > native-push citizen lane. Codex is a native-push citizen supported in 0.21.0, whose birth hook is
11
11
  > operator-owned and gated on one vendor trust receipt, with strict request-scoped identity and
@@ -105,7 +105,7 @@ that moved out of `scripts/` and into a file beside its subject would otherwise
105
105
 
106
106
  > **The herdr rail is an on-demand axis until it has run.** `smoke-herdr-fresh-call-live` opens a real caller inside a real herdr pane, has it invoke the PUBLIC `entwurf_fresh_call`, and reads the child's nonce callback — the one axis no deterministic gate reaches, since the rail is selected from the caller's own process environment. **The release aggregate does not yet require the herdr rail**: herdr is optional (a cut on a host without it must not block), and first evidence precedes promotion — a rail whose first acceptance run is days old has not earned a place in the gate that blocks every cut. Run it directly with `LIVE=1 ./run.sh smoke-herdr-fresh-call-live` whenever a cut changes herdr rail code, and promote it to a MUST only after the axis has a run history to point at. Without `LIVE=1`, or without herdr/pi/claude on PATH, it reports protocol SKIP — never a pass.
107
107
 
108
- > **The plugin's FIRST USER PATH is its own on-demand axis, and an npm acquisition receipt holds only for the ref it ran on (#118 홉 1).** Every other herdr gate proves the plugin against a source this repository controls: `check-herdr-plugin-build` drives a stub, and `smoke-herdr-plugin-build-live` redirects the product remote to a local bare clone with git `insteadOf` and asserts `identity.kind === "herdr-checkout"`. npm has been the production authority class since `dd84ac0`; the 0.30.0 follow-up re-pinned `@junghanacs/entwurf@0.30.0` with its published sha512 after the candidate windows rode `herdr-checkout` (`8b92e09`), and the 0.30.1 preparation rode that closed verification carrier again without inventing a registry integrity. The plugin snapshot `v2026.10.9-herdr.0.34.0` pinned the published `@junghanacs/entwurf@0.34.0` with its measured sha512 (#132); 0.31–0.33 were never pinned. The 0.34.1 preparation rode the closed `herdr-checkout` verification carrier again without inventing a registry integrity, and the snapshot `v2026.10.9-herdr.0.34.1` re-pins the published `@junghanacs/entwurf@0.34.1` with its measured sha512 (#135). The 0.34.2 preparation rode the same carrier, and the snapshot `v2026.10.11-herdr.0.34.2` re-pins the published `@junghanacs/entwurf@0.34.2` with its measured sha512 (#136). The source re-proof has separate dated npm rows: 2026-09-23 public `main=113ca53`, plugin 0.5.0, runtime 0.25.0 (CHANGELOG 0.25.1), and the 0.30.0 re-pin accepted at public `506c46f` in the [#126 thread](https://github.com/junghan0611/entwurf/issues/126). Neither certifies the 0.34.0 registry acquisition. That pin's real-npm swap / torn-swap recovery was measured locally on 2026-10-09 by driving the registry-shipped `scripts/herdr-runtime.mjs` (byte-equal to this checkout's) with its own npm acquisition: a 0.33.0 → 0.34.0 swap, and the same upgrade torn between its two renames then resumed, where the last-good 0.33.0 tree was restored before 0.34.0 was acquired again. Both ended `runtime-ready` at observed digest `sha256-e9d995ff…`, the accepted 0.34.0 artifact. That local receipt is recorded in the [#132 thread](https://github.com/junghan0611/entwurf/issues/132); it covers runtime recovery, not this smoke's cells. This smoke's public-ref acceptance of the 0.34.0 pin was measured locally at `--ref main`: PASS at resolved `b5d15d8` and again at the docs follow-up `8ed4d1a`, each acquiring `kind=npm @junghanacs/entwurf@0.34.0` — a main-ref receipt, not a tag-ref, native or full-Herdr claim. The 0.34.1 re-pin's npm-source rows — swap / torn-swap recovery, this smoke at a public ref, and the package consumer — are its own, recorded with that pin and never inherited from 0.34.0; checkout candidate evidence does not transfer. The 0.36.0 re-pin's public-ref row was measured on Herdr 0.9.4 at `--ref main`: PASS at resolved `9f33dd1`, acquiring `kind=npm @junghanacs/entwurf@0.36.0` at the accepted digest — a main-ref receipt, not a tag-ref claim. Its version-changing cross-ref reinstall (`herdr plugin install … --ref main` over the `v2026.10.13-herdr.0.35.1` installation) across a pinned Pi upgrade was a driver-level journey recorded in that snapshot's release notes, not a row of this smoke; `herdr plugin update` there was measured only as a same-ref already-up-to-date no-op. `LIVE=1 ./run.sh smoke-herdr-raw-install-live [--ref <REF>]` fills that row in a clean `node:24` container with no socket, config, cache or repo mounted, no `insteadOf`, the public remote spelled as a user would type it, and pi / herdr / Claude Code installed in the IMAGE BUILD as scaffolding (Hard Rule 17 — the product face begins at `herdr integration install pi`). herdr itself comes from `scripts/install-herdr-ci.sh` against `scripts/fixtures/herdr-supply.json`, so no version or digest lives here. `--ref` defaults to `main` rather than a pinned SHA because receipt (a) is defined as what a user gets today; the resolved commit is printed, so the receipt stays exact. It takes a REMOTE REF and not an arbitrary commit — measured on herdr 0.9.1, `--ref <sha>` fails as `couldn't find remote ref`, because the checkout fetches by name — so a candidate is addressed by its branch and pinned by the resolved commit the gate prints. **Its first question is a measurement, not an assumption:** herdr registers a plugin through a running server and falls back to `persist_plugin_offline` only on a connection error (`src/cli/plugin.rs:914-981,1016-1021` @ `c77af189`, 0.9.0 source), and a container has no server. The 2026-09-23 PASS installed with no server on the 0.9.1 binary `113ca53`'s `herdr-supply.json` pins, so that install path worked on 0.9.1 — inferred from the PASS, not a direct observation of the fallback branch; a later "no" on another herdr is #118 investigation output rather than a completion. `--yes` is always passed so a non-interactive exit 2 (`:189`) can never be misread as a missing server. **At a ref whose lock names `herdr-checkout` the acquisition axis reports a NAMED SKIP rather than a red, and the rest of the journey still runs.** That lock is the candidate carrier a cut legitimately sits on — 0.23.0 was tagged that way and `dd84ac0` pinned npm afterwards — so a red there would report a broken install when what is true is that the ref does not lock npm. What is asserted at BOTH lock kinds is that the runtime came from the source the ref locks, and the package name is read from the checkout manifest rather than the lock, because a `herdr-checkout` lock carries no `name` (`herdr-runtime.mjs:317-328`) and joining `undefined` throws instead of failing an assertion. **Cells [6]–[9] carry it past the install into the USE path (#118 H1-4/H1-5).** An install is not a use, so the same container then runs what those bytes became: the compiled bridge entry, the three `REQUIRED_BINS` as executables, and a real `entwurf check-bridge` printing its exact eight-verb set — the installed-runtime row below, measured rather than inferred from the bootstrap's refusal to install without them. Then the pi that this plugin WIRED is started with no `-e` and no `--no-extensions`, because the subject is the wiring and not this checkout: one `get_state` in `--mode rpc` costs zero tokens and needs no provider argument (a bogus `--provider` exits 1 before session_start; omitting the pair lets pi resolve the default the extension itself registers), and while that session is alive the cell reads its V3 record, its gardenId-keyed control socket, and the installed `entwurf peer-facts` reporting it `alive`. The plugin's own status fan is then driven with NEITHER side stubbed, in both states a user can be in: with no herdr server it must name `herdr-agent-list-failed` and go red rather than draw an empty table, and under a headless `herdr server` — scaffolding for that second read only, stopped again before the teardown — it exits 0 and COUNTS the citizen as `unobserved` instead of dropping it. Last, the shipped `entwurf herdr-plugin-deactivate` runs from the installed bin, deletes the runtime it is executing from, and the plugin reinstalls onto the host it fully left — the deactivation row, on the npm source. **What these cells still do not close:** swap / torn-swap recovery on the npm source, the package-consumer proof, and the fan drawing a citizen as a ROW — that needs `placement.kind === "herdr-pane"`, so the pi session must live in a herdr pane a session reference joins, and a headless container has no panes. **This smoke carries no test beside behavior and no mutant**, and that is a property of its class rather than an omission: it is a real-live container journey whose oracle is the container's own end state, so there is no pure seam to assert against and nothing a source mutation could kill deterministically. Like the other two herdr LIVE lanes it is **on-demand and not an aggregate MUST** — herdr is optional, a cut on a host without it must not block, and first evidence precedes promotion. Without `LIVE=1`, or without a usable Docker daemon, it reports a named SKIP; `ENTWURF_REQUIRE_DOCKER=1` turns the Docker absence red. Neither skip is a pass.
108
+ > **The plugin's FIRST USER PATH is its own on-demand axis, and an npm acquisition receipt holds only for the ref it ran on (#118 홉 1).** Every other herdr gate proves the plugin against a source this repository controls: `check-herdr-plugin-build` drives a stub, and `smoke-herdr-plugin-build-live` redirects the product remote to a local bare clone with git `insteadOf` and asserts `identity.kind === "herdr-checkout"`. npm has been the production authority class since `dd84ac0`; the 0.30.0 follow-up re-pinned `@junghanacs/entwurf@0.30.0` with its published sha512 after the candidate windows rode `herdr-checkout` (`8b92e09`), and the 0.30.1 preparation rode that closed verification carrier again without inventing a registry integrity. The plugin snapshot `v2026.10.9-herdr.0.34.0` pinned the published `@junghanacs/entwurf@0.34.0` with its measured sha512 (#132); 0.31–0.33 were never pinned. The 0.34.1 preparation rode the closed `herdr-checkout` verification carrier again without inventing a registry integrity, and the snapshot `v2026.10.9-herdr.0.34.1` re-pins the published `@junghanacs/entwurf@0.34.1` with its measured sha512 (#135). The 0.34.2 preparation rode the same carrier, and the snapshot `v2026.10.11-herdr.0.34.2` re-pins the published `@junghanacs/entwurf@0.34.2` with its measured sha512 (#136). The source re-proof has separate dated npm rows: 2026-09-23 public `main=113ca53`, plugin 0.5.0, runtime 0.25.0 (CHANGELOG 0.25.1), and the 0.30.0 re-pin accepted at public `506c46f` in the [#126 thread](https://github.com/junghan0611/entwurf/issues/126). Neither certifies the 0.34.0 registry acquisition. That pin's real-npm swap / torn-swap recovery was measured locally on 2026-10-09 by driving the registry-shipped `scripts/herdr-runtime.mjs` (byte-equal to this checkout's) with its own npm acquisition: a 0.33.0 → 0.34.0 swap, and the same upgrade torn between its two renames then resumed, where the last-good 0.33.0 tree was restored before 0.34.0 was acquired again. Both ended `runtime-ready` at observed digest `sha256-e9d995ff…`, the accepted 0.34.0 artifact. That local receipt is recorded in the [#132 thread](https://github.com/junghan0611/entwurf/issues/132); it covers runtime recovery, not this smoke's cells. This smoke's public-ref acceptance of the 0.34.0 pin was measured locally at `--ref main`: PASS at resolved `b5d15d8` and again at the docs follow-up `8ed4d1a`, each acquiring `kind=npm @junghanacs/entwurf@0.34.0` — a main-ref receipt, not a tag-ref, native or full-Herdr claim. The 0.34.1 re-pin's npm-source rows — swap / torn-swap recovery, this smoke at a public ref, and the package consumer — are its own, recorded with that pin and never inherited from 0.34.0; checkout candidate evidence does not transfer. The 0.36.0 re-pin's public-ref row was measured on Herdr 0.9.4 at `--ref main`: PASS at resolved `9f33dd1`, acquiring `kind=npm @junghanacs/entwurf@0.36.0` at the accepted digest — a main-ref receipt, not a tag-ref claim. Its version-changing cross-ref reinstall (`herdr plugin install … --ref main` over the `v2026.10.13-herdr.0.35.1` installation) across a pinned Pi upgrade was a driver-level journey recorded in that snapshot's release notes, not a row of this smoke; `herdr plugin update` there was measured only as a same-ref already-up-to-date no-op. `LIVE=1 ./run.sh smoke-herdr-raw-install-live [--ref <REF>]` fills that row in a clean `node:24` container with no socket, config, cache or repo mounted, no `insteadOf`, the public remote spelled as a user would type it, and pi / herdr / Claude Code installed in the IMAGE BUILD as scaffolding (Hard Rule 17 — the product face begins at `herdr integration install pi`). herdr itself comes from `scripts/install-herdr-ci.sh` against `scripts/fixtures/herdr-supply.json`, so no version or digest lives here. `--ref` defaults to `main` rather than a pinned SHA because receipt (a) is defined as what a user gets today; the resolved commit is printed, so the receipt stays exact. It takes a REMOTE REF and not an arbitrary commit — measured on herdr 0.9.1, `--ref <sha>` fails as `couldn't find remote ref`, because the checkout fetches by name — so a candidate is addressed by its branch and pinned by the resolved commit the gate prints. **Its first question is a measurement, not an assumption:** herdr registers a plugin through a running server and falls back to `persist_plugin_offline` only on a connection error (`src/cli/plugin.rs:914-981,1016-1021` @ `c77af189`, 0.9.0 source), and a container has no server. The 2026-09-23 PASS installed with no server on the 0.9.1 binary `113ca53`'s `herdr-supply.json` pins, so that install path worked on 0.9.1 — inferred from the PASS, not a direct observation of the fallback branch; a later "no" on another herdr is #118 investigation output rather than a completion. `--yes` is always passed so a non-interactive exit 2 (`:189`) can never be misread as a missing server. **At a ref whose lock names `herdr-checkout` the acquisition axis reports a NAMED SKIP rather than a red, and the rest of the journey still runs.** That lock is the candidate carrier a cut legitimately sits on — 0.23.0 was tagged that way and `dd84ac0` pinned npm afterwards — so a red there would report a broken install when what is true is that the ref does not lock npm. What is asserted at BOTH lock kinds is that the runtime came from the source the ref locks, and the package name is read from the checkout manifest rather than the lock, because a `herdr-checkout` lock carries no `name` (`herdr-runtime.mjs:317-328`) and joining `undefined` throws instead of failing an assertion. **Cells [6]–[9] carry it past the install into the USE path (#118 H1-4/H1-5).** An install is not a use, so the same container then runs what those bytes became: the compiled bridge entry, the three `REQUIRED_BINS` as executables, and a real `entwurf check-bridge` printing the exact verb set the installed bridge declares (nine since 0.41.0; each earlier recorded PASS printed its own date's set, and none of them is a nine-verb LIVE receipt) — the installed-runtime row below, measured rather than inferred from the bootstrap's refusal to install without them. Then the pi that this plugin WIRED is started with no `-e` and no `--no-extensions`, because the subject is the wiring and not this checkout: one `get_state` in `--mode rpc` costs zero tokens and needs no provider argument (a bogus `--provider` exits 1 before session_start; omitting the pair lets pi resolve the default the extension itself registers), and while that session is alive the cell reads its V3 record, its gardenId-keyed control socket, and the installed `entwurf peer-facts` reporting it `alive`. The plugin's own status fan is then driven with NEITHER side stubbed, in both states a user can be in: with no herdr server it must name `herdr-agent-list-failed` and go red rather than draw an empty table, and under a headless `herdr server` — scaffolding for that second read only, stopped again before the teardown — it exits 0 and COUNTS the citizen as `unobserved` instead of dropping it. Last, the shipped `entwurf herdr-plugin-deactivate` runs from the installed bin, deletes the runtime it is executing from, and the plugin reinstalls onto the host it fully left — the deactivation row, on the npm source. **What these cells still do not close:** swap / torn-swap recovery on the npm source, the package-consumer proof, and the fan drawing a citizen as a ROW — that needs `placement.kind === "herdr-pane"`, so the pi session must live in a herdr pane a session reference joins, and a headless container has no panes. **This smoke carries no test beside behavior and no mutant**, and that is a property of its class rather than an omission: it is a real-live container journey whose oracle is the container's own end state, so there is no pure seam to assert against and nothing a source mutation could kill deterministically. Like the other two herdr LIVE lanes it is **on-demand and not an aggregate MUST** — herdr is optional, a cut on a host without it must not block, and first evidence precedes promotion. Without `LIVE=1`, or without a usable Docker daemon, it reports a named SKIP; `ENTWURF_REQUIRE_DOCKER=1` turns the Docker absence red. Neither skip is a pass.
109
109
 
110
110
  > **The Herdr plugin's install-time build has two axes, and one of them needs the network (#116 M3-b3).** `check-herdr-plugin-build` is deterministic and rides `check:hermetic`: the listing reaches the runner through its own spawn seam (there is no fake `herdr` binary in this repo), and the one cell that drives the REAL binary reports a named SKIP on a host without it — `ENTWURF_REQUIRE_HERDR=1` makes that absence red. What it cannot have is the real acquisition: `npm pack git+https://github.com/junghan0611/entwurf.git#<full sha>` builds the bridge through `prepare`, which installs devDependencies from the registry. So `LIVE=1 ./run.sh smoke-herdr-plugin-build-live` owns that axis, and its cells are **two kinds of evidence, labelled as such**: three are the real Herdr journey — a real `herdr plugin install` driving the real `[[build]]`, with the UNCHANGED product remote redirected to a local bare clone by git `insteadOf` (the one substitution, which is what makes an unpushed candidate testable) — covering remote-commit available, the `{pi}` → `{pi, claude-code}` reinstall with OpenCode as a zero-write negative control and exactly ONE counted vendor MCP owner entry, and the post-build Herdr-commit gap (where the ledger has already rebound to the gap commit, so the honest assertion is that transition and not unchanged bytes). The fourth cell — remote-commit **unavailable** — calls the shipped runtime leaf directly with the fixed product argv and is therefore **acquisition-leaf evidence, not a journey**; routing it through Herdr would add a second explanation for one red. Its oracle is the honest one too: active runtime, ledger and harness bytes preserved, while the journal is left as the certified `installing` retry authority the transaction deliberately writes before acquiring, and a normal install afterwards is green. Its Claude cell is where the vendor question the deterministic gates refuse to claim gets answered — whether Claude Code accepts an ABSOLUTE executable as an MCP command — and it reports a named skip on a host without `claude`, never a pass. Like the herdr fresh-call rail, this smoke is **on-demand and not an aggregate MUST**. The release aggregate does not require the plugin build smoke: herdr is optional, a cut on a host without it must not block, and first evidence precedes promotion.
111
111
 
@@ -115,7 +115,7 @@ that moved out of `scripts/` and into a file beside its subject would otherwise
115
115
  >
116
116
  > A cut that touches the prompt-lifecycle contract (no wall clock on a running turn) owes one long-turn acceptance the aggregate floor is too short to carry: `LIVE=1 ./run.sh smoke-acp-long-turn-live` drives a real turn whose tool work outlasts the retired 600s cutoff and requires exactly one cold ACP bootstrap in the transcript. It takes >12 minutes by construction and is on-demand, not part of `release-gate`.
117
117
  >
118
- > **The integrated mux lifecycle IS a release-gate MUST.** `LIVE=1 ./run.sh smoke-mux-lifecycle-live` enters through a real MCP `tools/call` on the shipped bridge launcher and follows one citizen the whole way: fresh-call launch receipt, the sibling's own nonce callback whose SENDER ENVELOPE is the address, an `entwurf_v2` control-socket send that lands in the sibling's own transcript, `entwurf_resume_call` REFUSED while that citizen is live, a stable-handle close proving pane and socket gone with the record preserved, delivery to the dormant citizen refused honestly, then the public `entwurf_resume_call` — whose LAUNCH and OBSERVATION receipts stay apart — and finally an `entwurf_v2` send the resumed session answers with a fact it was told BEFORE the window closed. That last step is the point: a socket answering under the same garden id proves a process stood up at that address, and only the recall proves the same conversation came back. Both pi provider shapes are driven to it (native `openai-codex/gpt-6.1-sol:low` and recorded-ACP `entwurf/claude-sonnet-5-5`, the only shape that re-injects a bridge extension), and the Claude Code cell ends where its capability ends: its resume is refused as `target-not-pi` with no window opened and no lock residue (the per-gid coordination lock is released).
118
+ > **The integrated mux lifecycle IS a release-gate MUST.** `LIVE=1 ./run.sh smoke-mux-lifecycle-live` enters through a real MCP `tools/call` on the shipped bridge launcher and follows one citizen the whole way: fresh-call launch receipt, the sibling's own nonce callback whose SENDER ENVELOPE is the address, an `entwurf_v2` control-socket send that lands in the sibling's own transcript, `entwurf_resume_call` REFUSED while that citizen is live, a stable-handle close proving pane and socket gone with the record preserved, delivery to the dormant citizen refused honestly, then the public `entwurf_resume_call` — whose LAUNCH and OBSERVATION receipts stay apart — and finally an `entwurf_v2` send the resumed session answers with a fact it was told BEFORE the window closed. That last step is the point: a socket answering under the same garden id proves a process stood up at that address, and only the recall proves the same conversation came back. Both pi provider shapes are driven to it (native `openai-codex/gpt-6.1-sol:low` and recorded-ACP `entwurf/claude-sonnet-5-5`, the only shape that re-injects a bridge extension), and the Claude Code cell, whose sibling is still live at that point, has its resume refused as `target-live` with no window opened and no lock residue (the per-gid coordination lock is released). Claude Code's dormant same-id resume (#147 L2) is certified deterministically; its LIVE dormant→resume leg is not in this MUST yet. One test-owned retire→resume round trip outside this gate is recorded as dated n=1 evidence in [BASELINE](./BASELINE.md#147-native-round-trip-2026-10-22-n1); it is not this MUST.
119
119
  >
120
120
  > One cell cannot be fully fenced, and that is recorded rather than glossed: the recorded-ACP pi cell runs under the operator's REAL home, because its provider spawns `claude` as a child that authenticates from there — measured, a fenced HOME kills that turn with `Authentication required` before the model is ever reached, and `CLAUDE_CONFIG_DIR` alone does not fix it. That is the same trade `smoke-acp-bundled-mcp-live` already makes. While that cell runs, its citizen's control socket and per-gid lock exist under the operator's own `~/.pi/entwurf-control` and `~/.pi/entwurf-v2-locks`; its meta store stays fixture, so no garden record is minted outside the fixture, and the run ends by proving all six real roots hold the entry sets they started with, with no fixture garden id and no leftover lock.
121
121
  >
@@ -209,6 +209,16 @@ that moved out of `scripts/` and into a file beside its subject would otherwise
209
209
 
210
210
  Do not collapse source, package, fixture, and native-host evidence into one “green.”
211
211
 
212
+ **Remote pair (#107).** Each layer of evidence lives in its own place:
213
+
214
+ - **Deterministic.** The cells sit beside the behavior in `pi-extensions/lib/remote-pair-*.test.ts` and are found by `check-tests-beside-behavior`. T1 is executor-in. T2 is surface-in: the real bridge over MCP stdio, with a stand-in `ssh` on PATH that runs the other host's real `run.sh` under a separate HOME. Both are offline.
215
+ - **Installed bytes.** `./run.sh check-remote-pair-installed --tgz <abs>` is checkout-only and takes a caller-supplied candidate tarball. It installs a sandboxed copy, checks the compiled surface, and runs the T2 cells against the installed subject. It also audits that every module those processes load resolves inside the consumer prefix, with a checkout negative control. Its audit primitive is mutation-qualified as `check-remote-pair-installed-audit` inside `check:vitest`.
216
+ - **Real two-host exchange (T3).** This is opt-in operator LIVE evidence, recorded in [BASELINE](./BASELINE.md) and #107. It is not a release-gate MUST and not a fixture that needs a second machine online. A T1, T2 or installed green does not stand for it, and its record belongs to the commit it ran on; a later SHA does not inherit it. Re-run T3 when any of these change:
217
+ - the SSH argv or quoting;
218
+ - the receive entry's layout, stdout or exit codes;
219
+ - the declaration schema or root resolution;
220
+ - either host's Node or Pi.
221
+
212
222
  **Native durable contact (#129, #130 carrier).** Three deterministic beside-test/mutant
213
223
  coordinates in `check:contracts` split the durable contact by the file that carries each claim's QK
214
224
  (#144): a carrier or native-module mutant runs its one file, the rest run four. `check-pi-durable-beside` holds the adapter, the installed
@@ -275,7 +285,7 @@ caller/target/cross-harness LIVE are owed by `smoke-pi-durable-fresh-live`, a re
275
285
  | Packed install | `check-pack-install` | Real tarball, but checkout-visible. |
276
286
  | Linux artifact consumer | required `check-install-container` CI job against one preserved candidate | Fixtures prove package/oracle shape, not a real Claude lifecycle. |
277
287
  | Exact release commit | the exact SHA's push run: all four required CI jobs green (the qualification body is not read, #144) | A different green SHA is not transferable evidence. |
278
- | macOS Entwurf-only install | required `macos-install-surface` CI job at the exact release SHA | Shape-only, CERTIFIED (CI): certifies the Entwurf-only install path on macOS only — never Claude/Copilot/OMP/agy wiring, the pi rail, ACP or mux. Promoted from non-voting (`continue-on-error`) to required in 0.20.0 after its first CERTIFIED (CI) green on a GitHub Actions `macos-latest` runner (not a physical host): run 34303884286 @ `70eda03` (2026-09-09), 65s, all 10 steps success — the packed candidate installed clean, `entwurf --help` and `entwurf check-bridge` ran (the latter listing the seven `entwurf-bridge` MCP tools of that date; eight today), an all-harness-absent `entwurf setup` reached `result: green` across the five documented SKIP probes (`pi`/`claude`/`agy`/`copilot`/`omp`), and five checked paths (`.pi`, `settings.json`, `.gemini`, `.copilot`, `auth.json.bak`) plus a byte-identical `auth.json` proved zero harness/credential writes. A macOS runner failure now blocks every release cut, the same as any other required job. |
288
+ | macOS Entwurf-only install | required `macos-install-surface` CI job at the exact release SHA | Shape-only, CERTIFIED (CI): certifies the Entwurf-only install path on macOS only — never Claude/Copilot/OMP/agy wiring, the pi rail, ACP or mux. Promoted from non-voting (`continue-on-error`) to required in 0.20.0 after its first CERTIFIED (CI) green on a GitHub Actions `macos-latest` runner (not a physical host): run 34303884286 @ `70eda03` (2026-09-09), 65s, all 10 steps success — the packed candidate installed clean, `entwurf --help` and `entwurf check-bridge` ran (the latter listing the seven `entwurf-bridge` MCP tools of that date; 0.41.0 declares nine, unmeasured on that runner), an all-harness-absent `entwurf setup` reached `result: green` across the five documented SKIP probes (`pi`/`claude`/`agy`/`copilot`/`omp`), and five checked paths (`.pi`, `settings.json`, `.gemini`, `.copilot`, `auth.json.bak`) plus a byte-identical `auth.json` proved zero harness/credential writes. A macOS runner failure now blocks every release cut, the same as any other required job. |
279
289
  | Native-Windows carrier (carrier-bearing release, from 0.39.0) | one `windows-native-preview.yml` dispatch at the exact prepared SHA after its four `ci.yml` jobs are green: accepted build receipt (two byte-identical MSVC builds, exact import set), `pack` printing `present`, eight Windows addon/citizen cells on the installed tarball; that run's `win32-preview-tgz` is the release candidate, retrieved (never packed locally) with its `SHA256SUMS`, the Windows-printed install hash and the downloaded hash equal, then accepted by the Linux `check-install-container` unchanged (release skill M3w) | Experimental, shape-only, non-voting for `ci.yml`: no vendor turn, x64 runner only, named gaps in BASELINE. The four `ci.yml` jobs (incl. macOS) saw an addon-less pack, so the carrier-bearing tarball's macOS install is unmeasured. Artifacts expire after 14 days; the preserved copy is the durable one. |
280
290
  | LIVE runtime | `LIVE=1 ./run.sh release-gate <scratch> --cut` plus any shipped on-demand backend axis | `--cut` enforces `SKIP=0`; a red wired gate blocks the cut. |
281
291
  | Native Claude host | installed strict doctor against a new real session | Missing live join is `NOT CERTIFIED`, not a fixture PASS. |
@@ -283,7 +293,10 @@ caller/target/cross-harness LIVE are owed by `smoke-pi-durable-fresh-live`, a re
283
293
  | Native Codex host | caller-seat topology accepted 2026-09-16 (56 assertions, exit 0); caller-DIRECTORY axis accepted the same day (65 assertions, exit 0) | Operator-owned birth/MCP/statusline/terminal-title atoms and the on-demand four-coordinate chain are measured on Linux at Codex 0.153.4: the app-server alone in session A (`$30/@41/%45`, pid 737636), and initial Pi / omitted-placement fresh Codex / Codex-opened outbound Pi all in a different session S (`$2`, `@46`/`@47`/`@48`; `@58`/`@59`/`@61` in the lane C run); exact callbacks and addressed delivery both ways. A ≠ S is the load-bearing half — the app-server's inherited `TMUX` names A, so the outbound Pi landing in S can only have come from the caller's own pane title, and its receipt records `seat-source=codex-title-anchor`. **#95 lane C added the second axis to the same card: WHERE the sibling starts.** Hop 1 joins four authorities that cannot borrow from each other — the pane's `#{pane_current_path}`, the vendor rollout's `session_meta.cwd`, the Entwurf record and the requested scratch — and hop 1's leg requests a cwd while hop 2 requests NONE, so hop 2's directory can only come from the Codex caller's own record; both are asserted different from the app-server's live `/proc/<pid>/cwd`. 65 and 56 are different contracts, not a regression. The 2026-09-12 fixed-home chain is PAST CONTRACT (#95 D1); `DELIVERY.md` owns every receipt and digest. Placement beside a TUI whose thread nobody named is unsupported and unclaimed. |
284
294
 
285
295
  The repo-local `entwurf-release` skill owns the `land → prepare → make → publish`
286
- state machine. Each mode is a separate GLG authorization. Preserve one candidate
296
+ state machine. Each mode is a separate GLG authorization. A narrower host restriction may
297
+ block a mode; it never waives a release-acceptance axis below. Feature-branch CI is not a
298
+ substitute for a forbidden local/LIVE floor: keep make blocked until the named cut has the
299
+ required execution authority and evidence. Preserve one candidate
287
300
  (for a carrier-bearing release, the one retrieved from the preview run, never a local
288
301
  pack), accept and publish those exact bytes without repacking, and record its digest and
289
302
  consumer image outside this standing protocol.
@@ -334,7 +347,7 @@ every focused gate green at the time:
334
347
 
335
348
  ### Wording — avoid safety-interpretation contamination
336
349
 
337
- When injecting a fact for a continuity check, use **plaintext that does not trigger model safety interpretation**. Avoid `secret token`, `password`, `API key`, `credential`, and meta-directives like "do not leak" — such wording makes the model treat the prompt as an exfiltration attempt and refuse, which makes **continuity look broken even when it is alive** (this happened once with `test-token-123`, misdiagnosed as a delegation failure). Instead: `The password is owl → reply in one word → owl`; code names / colors / animal names. Do not mix continuity and safety-behavior verification in one prompt.
350
+ When injecting a fact for a continuity check, use **plaintext that does not trigger model safety interpretation**. Avoid `secret token`, `password`, `API key`, `credential`, and meta-directives like "do not leak" — such wording makes the model treat the prompt as an exfiltration attempt and refuse, which makes **continuity look broken even when it is alive** (this happened once with `test-token-123`, misdiagnosed as a delegation failure). Instead: `The code word is owl → reply in one word → owl`; code names / colors / animal names. Do not mix continuity and safety-behavior verification in one prompt.
338
351
 
339
352
  ### bridge continuity vs semantic continuity
340
353
 
@@ -428,7 +441,7 @@ mailbox with `origin=pi-session`, `replyable=true`).
428
441
 
429
442
  ### 1.4 Cross-install / cross-backend parity (optional, high-value)
430
443
 
431
- Compare a fresh self-awareness report across axes: (1) same backend, different install path — answer must be path-invariant; (2) same backend, different machine — identical native tool list + MCP server/tool set; (3) different backend, same bridge — same garden capability but **different** native tool surface (a Claude session reporting another backend's native tools is a fail); (4) native pi routing vs ACP-bridged, same model — the native pi citizen reaches the compiled `entwurf-bridge` through Pi's built-in MCP (`mcp__entwurf_bridge__<verb>`, six active tools and two hidden), while the ACP backend reaches that bridge through the explicit `entwurfProvider.mcpServers` projection in its own callable dialect (see §2.1). Compare the actual ingress provenance and backend-native tools, not an assumed absence of MCP on pi; receive to the pi citizen remains its record-addressed Unix control socket. Honest "I cannot tell" hedging is preferable to inventing provenance. This is the current #125 comparison criterion, not a newly measured parity receipt. agy is graded by its native-citizen checklist, not by pretending it has an ACP overlay.
444
+ Compare a fresh self-awareness report across axes: (1) same backend, different install path — answer must be path-invariant; (2) same backend, different machine — identical native tool list + MCP server/tool set; (3) different backend, same bridge — same garden capability but **different** native tool surface (a Claude session reporting another backend's native tools is a fail); (4) native pi routing vs ACP-bridged, same model — the native pi citizen reaches the compiled `entwurf-bridge` through Pi's built-in MCP (`mcp__entwurf_bridge__<verb>`, seven active tools and two hidden in the 0.41.0 source; installed/live offering requires its own receipt), while the ACP backend reaches that bridge through the explicit `entwurfProvider.mcpServers` projection in its own callable dialect (see §2.1). Compare the actual ingress provenance and backend-native tools, not an assumed absence of MCP on pi; receive to the pi citizen remains its record-addressed control address (a Unix socket; a named pipe on the experimental native-Windows Pi preview). Honest "I cannot tell" hedging is preferable to inventing provenance. This is the current #125 comparison criterion, not a newly measured parity receipt. agy is graded by its native-citizen checklist, not by pretending it has an ACP overlay.
432
445
 
433
446
  **Cortex on axis 3.** A cortex session must expose the same garden capability
434
447
  while keeping cortex's own native tool surface. Its callable MCP identifier shape is
@@ -458,7 +471,7 @@ Pass (carrier honesty): the subject distinguishes engraving from pi-context-augm
458
471
 
459
472
  - **Layer 0 — self-awareness:** ask environment self-awareness / MCP visibility / upstream-instruction awareness, guessing prohibited. Pass: recognizes native tool family, says "I don't know" honestly, answers MCP visibility only as configured, describes upstream instruction type without reproducing internal prompts. Fail: claims a nonexistent tool, conflates pi-custom and native tools, hallucinates MCP visibility, or conflates the two carriers (§1A.0).
460
473
  - **Layer 1 — native tool use:** throw file-reading / structure-analysis / regression-hunting tasks. Pass: Read/Edit/Bash/Grep/Glob selection is natural; no detour through MCP or recursive `pi`. Fail: strange detours for simple reads; speaks from memory without reading.
461
- - **Layer 2 — MCP boundary:** by default the eight entwurf MCP tools are not visible (they appear only when `entwurf-bridge` is registered). Pass: says invisible tools are not visible; explains the native-vs-MCP boundary; treats `entwurf_register_native` as binding an already-running conversation, never as fresh spawn. Fail: pretends to use an unseen tool; mimics entwurf via recursive `pi`.
474
+ - **Layer 2 — MCP boundary:** by default the entwurf MCP tools are not visible (they appear only when `entwurf-bridge` is registered). Pass: says invisible tools are not visible; explains the native-vs-MCP boundary; treats `entwurf_register_native` as binding an already-running conversation, never as fresh spawn. Fail: pretends to use an unseen tool; mimics entwurf via recursive `pi`.
462
475
  - **Layer 3 — focus across turns:** inject a fact, then accumulate turns mixing retrieval/exploration. Pass (post-0.4.1): after **8 turns** holds **3+ early facts** incl. **one verbatim string injected before turn 5**; no repeated exploration, no self-contradiction, no tool-strategy drift. Fail: forgets early reads; paraphrases instead of returning the verbatim string. Note: entwurf exposes no user-facing compaction; use the backend's `usage_update` footer as an overflow-risk signal (it follows the ACP backend's `used/size`, not pi's visible-transcript estimate).
463
476
  - **Layer 4 — vs direct Claude Code:** requires a verifier holding **both** the `entwurf` and a direct path (human-in-loop, or both transport handles). Compare latency / native tool accuracy / detours / boundary confusion / quality around turns 10–15. Repeated tool confusion, long-turn forgetting, or boundary workarounds are a fail.
464
477
 
@@ -530,7 +543,7 @@ The minimum passing bar:
530
543
  2. **All four required CI jobs green on the exact release commit:** `check`, `install-surface`, the required Linux `artifact-consumer`, and `macos-install-surface` — promoted from non-voting to required in 0.20.0 (see the release acceptance axes table above for what its green run does and does not certify); preserve the Linux artifact-consumer's tarball digest and image identity. For a carrier-bearing release those CI tarballs are addon-less and are NOT the release candidate: the exact release bytes are the M3w-retrieved preview tarball, accepted separately by the eight Windows cells and the Linux container (axes row "Native-Windows carrier"). The four-job floor, macOS included, is unchanged. A macOS runner failure now blocks the cut like any other required job.
531
544
  3. **Live floor MUST green:** `LIVE=1 ./run.sh release-gate <dir> --cut` exits 0 reporting `MUST PASS=N FAIL=0 SKIP=0`; with `--cut` a single SKIP is red, so the exit code itself now carries this condition. A BEHAVIOR FAIL is advisory, not blocking.
532
545
  4. **Native-host doctor green where the Claude meta-bridge is claimed:** a new post-install Claude session exists, live evidence is present, and the installed `doctor-meta-bridge` exits 0. `NOT CERTIFIED` is a release failure for that host, not a skip.
533
- 5. **Honest self-recognition:** the bridged model identifies its actual harness/backend, lists `entwurf-bridge` as the single MCP server with its eight current tools, and presents a backend-native (not normalized) tool surface.
546
+ 5. **Honest self-recognition:** the bridged model identifies its actual harness/backend, lists `entwurf-bridge` as the single MCP server with the tools its loaded bridge actually lists (the current source lists nine; a pi citizen offers seven with two hidden, the pi-durable contact eight), and presents a backend-native (not normalized) tool surface.
534
547
  6. **Carrier separation honored:** engraving vs pi-context-augment kept distinct (§1A.0); no bridge-identity narrative attributed to the engraving carrier.
535
548
  7. **agy shipped lane accepted:** all three agy doctors are green; automatic birth/statusline/sender identity and same-gid native-push reply are confirmed in a fresh conversation. `agentId=meta-session/antigravity` is correct; model display is not part of that contract. Same-pid concurrent conversation invocation is not claimed.
536
549
  8. **Codex caller-seat topology accepted:** its birth/MCP/status-line/terminal-title doctors are green and the on-demand real visible `Pi → Codex → Pi` cell records four coordinates: the operator-owned app-server in its own session A; initial Pi, omitted-placement fresh Codex, and Codex-opened outbound Pi all in a DIFFERENT session S; exact callbacks and addressed delivery succeed both ways. A ≠ S is a precondition, not a preference — the app-server's inherited environment names A, so an outbound Pi in S can only have come from the caller's own pane title, and the receipt must name `codex-title-anchor` as the rule that chose it. 0 or 2+ matching panes, and a caller whose `[tui].terminal_title` lacks `thread-id`, reject without mutation. Entwurf neither creates nor supervises the app-server, infers attached-TUI panes, nor claims placement beside a TUI whose thread nobody named. (The 2026-09-12 fixed-`codex`-home acceptance this condition replaced stays recorded in `DELIVERY.md` as history; #95 D1 retired that room.) Codex remains outside ACP and has no resume claim.
@@ -548,7 +561,7 @@ Passing establishes a **release verification floor**, not an 8-hour/day operatio
548
561
 
549
562
  - **ACP turn evidence.** The ACP path writes no `[entwurf:*]` stderr markers, and `ENTWURF_CHILD_STDERR_LOG` is not an ACP observation surface (its mirror in `pi-extensions/lib/entwurf-core.ts` belongs to the retired child-pi spawn and has no caller). Read three things instead: the lifecycle notices the backend writes into the assistant text (`[acp: preparing …]` / `[acp: reusing live session]`), the turn's typed `stopReason` (an ACP `cancelled` ending maps to `aborted`, `pi-extensions/lib/acp/backend.ts:865-866`), and the identity (pid + starttime) of the launcher and of any process the turn started.
550
563
  - **Retired dedicated smokes, live code invariants** (manual/troubleshooting only — *not* part of the release floor):
551
- - *Model-switch lock* — entwurf sessions are locked to their starting model. Gate: `check-model-lock` (in `pnpm check`, core tier). The dedicated live `smoke-model-switch` was retired in v2; the invariant lives in `pi-extensions/model-lock.ts` (extension guard) + `session-store.ts` `SessionModelLockedError` (the `decideBootstrap` fail-loud model lock).
564
+ - *ACP-boundary model guard* — a session using the `entwurf` provider keeps its starting model: `pi-extensions/model-lock.ts` reverts set/cycle transitions touching that provider (native-to-native switching is free). This is an after-event revert, not a transcript-clean refusal. Separately, `session-store.ts` `SessionModelLockedError` refuses a persisted-record model mismatch at `decideBootstrap`. Gate: `check-model-lock` (core tier); the dedicated live `smoke-model-switch` was retired in v2. This is not a lifetime model pin on every garden citizen.
552
565
  - *Cancel / abort cleanup* — an abort sends ACP `session/cancel` and lets the agent end its own turn (`awaitAcpPromptTurn`, `pi-extensions/lib/acp/backend.ts:762-801`); process-group teardown follows only after a bounded grace. A cancelled reuse turn keeps its live session; a cancelled first turn is not retained (`backend.ts:1861`), so the next turn bootstraps a new one. Dedicated `smoke-cancel` retired; invariant in code.
553
566
 
554
567
  ### Evidence preservation when a problem occurs
@@ -140,7 +140,7 @@ Dep bump ledger. 0.38.0 (#145) changes only the pi SDK set; the ACP rows are unc
140
140
  | Claude ACP adapter | `@agentclientprotocol/claude-agent-acp 0.90.0` | **exact**, bundled | shipped since 0.36.1; resolved before any PATH fallback; declaration is not compatibility evidence |
141
141
  | Claude Agent SDK | `0.3.296` (transitive) | **exact** oracle | the runtime risk surface behind the adapter |
142
142
  | Anthropic SDK | `0.100.1` | **exact**, peer-resolution only | satisfies the Agent SDK peer floor (0.93.0+); never an API client here (gate L4) |
143
- | Claude Code runtime | `>=2.1.217` (`entwurf.claudeCodeFloor`) | **floor** | below it, hook args are silently dropped; entwurf enforces this itself |
143
+ | Claude Code runtime | `>=2.1.306` (`entwurf.claudeCodeFloor`) | **floor** | below it, hook args are silently dropped; entwurf enforces this itself |
144
144
  | Node | `>=24` (`engines.node`) | **floor** | single axis, derived everywhere else |
145
145
  | Cortex Code | operator-installed CLI | **on-demand**, external | NOT a pinned dependency. Each LIVE record must name the exact CLI version it measured |
146
146
 
@@ -419,6 +419,10 @@ Otherwise record and defer. Every certification separates declared pins, determi
419
419
  evidence, Claude LIVE aggregate evidence, and Cortex direct on-demand evidence (with its CLI
420
420
  version). Per-bump measurements live in the ROADMAP **Dep bump(별도 트랙)** ledger.
421
421
 
422
+ ### Configured Claude ACP permissions
423
+
424
+ Source configuration, not a retirement LIVE receipt: `pi-extensions/lib/acp/overlay.ts` sets `permissions.defaultMode: "bypassPermissions"`, and `config.ts` includes `mcp__*` in the default `permissionAllow`. The configured Claude ACP child therefore does not supply a per-call permission prompt; these settings do not widen the explicit callable tool surface. `enrichMcpServersWithEnvelope` in `config.ts` injects the host Pi session's `PI_SESSION_ID` and the ACP agent label into the explicit entwurf-bridge stdio entry. If that bridge exposes `entwurf_retire_call`, a promptless call attributed to the host Pi citizen is consequently possible by source inference — ACP retirement through this path is unmeasured, not part of the ordinary Native Pi/Claude Code n=1 round trips. Neither those settings, annotations nor caller attribution is an operator grant: an explicit human request for that target/action and agent operating policy are still required. This describes existing configuration, not a new ACP retirement ban or permission mechanism.
425
+
422
426
  ### Bridge reachability is part of the contract
423
427
 
424
428
  Explicit entwurf-bridge connectivity inside an ACP turn is core value, so it is measured rather than
@@ -11,7 +11,7 @@ the one-line registration; everything an operator needs to actually wire a host
11
11
 
12
12
  **Being garden-native is not the same as being replyable, and Copilot is the case that separates them.** Its hook writes a sender marker, so an `entwurf_v2` send carries its own garden id and the receiver learns who wrote — measured 2026-08-21 on Copilot CLI 1.0.80, where a live send arrived under its own garden id with `origin: "meta-session"` and `replyable: false`. Replyability arrived later and through a different process: a first-party extension (`run.sh install-copilot-receive`) that the CLI forks, which binds to the same V3 record and writes a receiver marker owned by its own pid (#82 RAIL 5). So a Copilot citizen is `replyable: true` exactly while that extension is armed for it, and `replyable: false` — honestly, with a real garden identity — when it is not installed, not launched with `COPILOT_CLI_ENABLED_FEATURE_FLAGS=EXTENSIONS` (which is what `entwurf copilot` sets for one invocation), not yet born, or gone. Read the two facts off different rails: the sender marker answers *who sent this*; the receiver marker answers *can a reply land*. The onboarding obligations are in [`adding-a-harness.md`](./adding-a-harness.md) step 7, and the evidence boundary — the managed LIVE acceptance that moved receive to D6, and what D7-partial / D3-pending still exclude — is in [`DELIVERY.md`](../DELIVERY.md).
13
13
 
14
- **Which verb an external agent should reach for:** to deliver to / reply to a garden id, use **`entwurf_v2`** — it is the canonical delivery surface and the only one that reads whether the target is live pi, dormant pi, a mailbox-backed self-fetch citizen (Claude Code, Copilot, OMP), or native-push Antigravity/Codex and routes correctly. Discover targets with `entwurf_peers`, confirm your own identity with `entwurf_self`, drain a mailbox with `entwurf_inbox_read`, and use `entwurf_register_native` only as the explicit/manual fallback for binding an already-running agy conversation. Open a NEW sibling with **`entwurf_fresh_call {backend, model, task, cwd?, placement?}`**, and reopen a DORMANT pi citizen under the same id with **`entwurf_resume_call {target}`**.
14
+ **Which verb an external agent should reach for:** to deliver to / reply to a garden id, use **`entwurf_v2`** — it is the canonical delivery surface and the only one that reads whether the target is live pi, dormant pi, a mailbox-backed self-fetch citizen (Claude Code, Copilot, OMP), or native-push Antigravity/Codex and routes correctly. Discover targets with `entwurf_peers`, confirm your own identity with `entwurf_self`, drain a mailbox with `entwurf_inbox_read`, and use `entwurf_register_native` only as the explicit/manual fallback for binding an already-running agy conversation. Open a NEW sibling with **`entwurf_fresh_call {backend, model, task, cwd?, placement?}`**, and reopen a DORMANT pi or Claude Code citizen under the same id with **`entwurf_resume_call {target}`**. **`entwurf_retire_call {target}`** (new in 0.41.0) attempts retirement of exactly one target, and only one the human operator explicitly requested; mounting or seeing the tool, a coordinator role or caller identity is not permission, and an unknown result is not a reason to replay.
15
15
 
16
16
  Observed: Claude Code, Codex CLI, Antigravity CLI, GitHub Copilot CLI and OMP all reach this MCP bridge. Claude, Copilot and OMP become symmetric/replyable through mailbox-backed meta-sessions; agy and Codex use native-push. Codex's identity is request-scoped `_meta.threadId`, and its replyability requires that exact record plus a loaded app-server thread. Plain external hosts still need an identity lane to send.
17
17
 
@@ -90,7 +90,7 @@ External/meta-session semantics:
90
90
  #### Claude Code
91
91
 
92
92
  Plain MCP registration works independently of the native lifecycle. A garden-native
93
- mailbox citizen requires Claude Code >=2.1.217 plus the managed meta-bridge installer;
93
+ mailbox citizen requires Claude Code >=2.1.306 plus the managed meta-bridge installer;
94
94
  older versions silently drop exec-hook arguments and are refused.
95
95
 
96
96
  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.
@@ -185,7 +185,7 @@ entwurf doctor-agy-hooks
185
185
 
186
186
  The three adapters deliberately own different atoms:
187
187
 
188
- - 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), neither is the manual `entwurf_register_native` fallback, and neither are `entwurf_fresh_call` / `entwurf_resume_call` (both launch into the caller's own tmux server, which an agy conversation has none of);
188
+ - 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), neither is the manual `entwurf_register_native` fallback, neither are `entwurf_fresh_call` / `entwurf_resume_call` (both launch into the caller's own tmux server, which an agy conversation has none of), and `entwurf_retire_call` stays at agy's default Ask (a retirement is one operator-requested action, never a standing grant);
189
189
  - statusline: the complete `statusLine` subtree pointing at the bare stable bin `entwurf-agy-statusline`;
190
190
  - hooks: one named `PreInvocation` hook pointing at the bare stable bin `entwurf-agy-imprint`.
191
191
 
@@ -253,8 +253,10 @@ spawned. The installer refuses to write into a config that denylists its own key
253
253
  **The tool names are omp's dialect, not Claude's.** omp mints `mcp__<server>_<tool>` after
254
254
  lowercasing and replacing every `[^a-z_]+` run with `_`, collapsing runs and trimming edges,
255
255
  so `entwurf_v2` surfaces as `mcp__entwurf_bridge_entwurf_v` — the trailing digit is eaten by
256
- the charset, not by the length cap. The live tool list is the acceptance oracle; a live
257
- session mounts all eight.
256
+ the charset, not by the length cap. The live tool list is the acceptance oracle. The recorded
257
+ observation mounted eight tools; the 0.41.0 bridge has nine, and that expanded OMP
258
+ live offering needs its own receipt. Retirement remains explicitly operator-requested, not
259
+ auto-granted by mounting the tool.
258
260
 
259
261
  **And the NAME is not the invocation. Under omp's default settings an MCP tool is not a
260
262
  function the model calls — it is a virtual file it writes to.** `tools.xdev` (boolean,
@@ -273,7 +275,7 @@ top-level (`Tool web_search not found`, upstream #5973), fixed by pinning it via
273
275
 
274
276
  `read xd://` reports exactly what the default hides. `[측정]` on a host with only this
275
277
  bridge registered, **11 devices**: omp's own `ast_edit`, `debug`, `lsp`, `browser`, plus all
276
- seven `entwurf_*` (as reported in that earlier measurement; the current bridge declares eight). So the default does not merely wrap entwurf — it wraps omp's own LSP and
278
+ seven `entwurf_*` (as reported in that earlier measurement; the current source bridge declares nine). So the default does not merely wrap entwurf — it wraps omp's own LSP and
277
279
  debugger too.
278
280
 
279
281
  **`entwurf setup` writes this for a detected omp host, and `entwurf install-omp-config` is
@@ -84,7 +84,15 @@ entwurf meta-bridge-fresh-cut && entwurf setup /path/to/project
84
84
  ## Upgrade order
85
85
 
86
86
  A checkout-backed host can begin running new code immediately after `git pull`, so
87
- make the boundary explicit:
87
+ make the boundary explicit. First distinguish an unreadable generation from a stale reader:
88
+ a known V3 schema extension (such as `attachIncarnation`) or newly admitted backend rejected
89
+ by an older strict reader is **reader-version skew, not a generation change**. Preserve the
90
+ records and native identity, roll every participating source/compiled/installed reader and
91
+ external mirror forward to the owning contract, then verify each active rail. Do not delete
92
+ fields, invent bindings, rewrite ids or execute an old reader's fresh-cut prescription merely
93
+ to silence that skew. Unmeasured readers remain an open rollout axis, not a pass.
94
+
95
+ For an actual generation transition, the order remains:
88
96
 
89
97
  ```text
90
98
  quiesce sessions → update package/checkout → fresh-cut if prescribed → reinstall/setup → reopen