@junghanacs/entwurf 0.41.0 → 0.41.2

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 (91) hide show
  1. package/AGENTS.md +1 -1
  2. package/BASELINE.md +7 -5
  3. package/CHANGELOG.md +426 -0
  4. package/DELIVERY.md +37 -20
  5. package/README.md +83 -81
  6. package/VERIFY.md +10 -10
  7. package/demo/README.md +1 -1
  8. package/docs/acp-backend-rail.md +1 -1
  9. package/docs/external-mcp-host.md +1 -1
  10. package/docs/setup-clean-host.md +95 -29
  11. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +14 -11
  12. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-self-address.js +10 -0
  13. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-contract.js +23 -2
  14. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-decider.js +7 -1
  15. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-surface.js +3 -0
  16. package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-composition.js +21 -10
  17. package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-fresh-call.js +4 -2
  18. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-sender-identity.js +5 -0
  19. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +6 -4
  20. package/mcp/entwurf-bridge/dist/pi-extensions/lib/native-push/register.js +13 -2
  21. package/mcp/entwurf-bridge/dist/pi-extensions/lib/omp-fresh-preflight.js +2 -2
  22. package/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-fresh-preflight.js +1 -1
  23. package/mcp/entwurf-bridge/dist/pi-extensions/lib/remote-pair-receive.js +25 -17
  24. package/mcp/entwurf-bridge/dist/pi-extensions/lib/remote-pair-send.js +94 -28
  25. package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-omp.js +57 -7
  26. package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-pi-durable.js +2 -2
  27. package/mcp/entwurf-bridge/dist/scripts/remote-pair-receive.js +12 -12
  28. package/mcp/entwurf-bridge/src/index.ts +14 -11
  29. package/native/win32-x64/entwurf_win.node +0 -0
  30. package/native/win32-x64/manifest.json +2 -2
  31. package/package.json +15 -14
  32. package/pi/pi-durable/carrier/tui.js +18 -3
  33. package/pi/pi-durable/overlay/upstream-pin.json +12 -12
  34. package/pi-extensions/lib/acp/backend-adapter.ts +5 -3
  35. package/pi-extensions/lib/acp/backend.ts +12 -9
  36. package/pi-extensions/lib/entwurf-self-address.ts +13 -0
  37. package/pi-extensions/lib/entwurf-v2-contract.ts +25 -2
  38. package/pi-extensions/lib/entwurf-v2-decider.ts +7 -0
  39. package/pi-extensions/lib/entwurf-v2-surface.ts +3 -0
  40. package/pi-extensions/lib/fresh-call-composition.ts +21 -10
  41. package/pi-extensions/lib/herdr-fresh-call.ts +4 -2
  42. package/pi-extensions/lib/meta-sender-identity.ts +4 -0
  43. package/pi-extensions/lib/mux-fresh-call.ts +6 -4
  44. package/pi-extensions/lib/native-push/register.ts +17 -2
  45. package/pi-extensions/lib/omp-fresh-preflight.ts +2 -2
  46. package/pi-extensions/lib/pi-durable-fresh-preflight.ts +1 -1
  47. package/pi-extensions/lib/remote-pair-receive.ts +27 -19
  48. package/pi-extensions/lib/remote-pair-send.ts +117 -30
  49. package/pi-extensions/meta-bridge-omp.ts +69 -7
  50. package/pi-extensions/meta-bridge-pi-durable.ts +3 -3
  51. package/run.sh +218 -49
  52. package/scripts/check-agy-sender-identity.ts +6 -4
  53. package/scripts/check-entwurf-self-address.ts +34 -3
  54. package/scripts/check-entwurf-v2-contract.ts +24 -2
  55. package/scripts/check-entwurf-v2-production.ts +45 -31
  56. package/scripts/check-gate-qualification.ts +11 -9
  57. package/scripts/check-native-push-adapter.ts +4 -0
  58. package/scripts/check-native-push-register.ts +32 -82
  59. package/scripts/check-remote-pair-installed.ts +2 -2
  60. package/scripts/dev-bin.sh +112 -1
  61. package/scripts/lib/chain-terminus-publication.ts +58 -0
  62. package/scripts/lib/codex-fresh-live-protocol.ts +33 -2
  63. package/scripts/lib/codex-fresh-source-receipts.ts +112 -3
  64. package/scripts/lib/pi-durable-fresh-live-receipts.ts +29 -1
  65. package/scripts/mutants/chain-terminus-publication.json +17 -0
  66. package/scripts/mutants/codex-native.json +98 -0
  67. package/scripts/mutants/control-send-receipt.json +3 -3
  68. package/scripts/mutants/mux-fresh-call.json +16 -1
  69. package/scripts/mutants/native-push-paused.json +17 -0
  70. package/scripts/mutants/omp-fresh.json +13 -2
  71. package/scripts/mutants/pack-install.json +50 -4
  72. package/scripts/mutants/pi-durable.json +60 -0
  73. package/scripts/mutants/pi-floor.json +5 -5
  74. package/scripts/mutants/remote-pair-display.json +3 -3
  75. package/scripts/mutants/remote-pair-receive.json +41 -11
  76. package/scripts/mutants/remote-pair-roundtrip.json +67 -7
  77. package/scripts/mutants/remote-pair-send.json +156 -17
  78. package/scripts/mutants/remote-pair-transport.json +4 -4
  79. package/scripts/mutants/source-install.json +66 -0
  80. package/scripts/remote-pair-receive.ts +12 -12
  81. package/scripts/smoke-agy-install-state.sh +121 -0
  82. package/scripts/smoke-agy-native-push-live.ts +13 -0
  83. package/scripts/smoke-codex-fresh-live.ts +52 -28
  84. package/scripts/smoke-entwurf-chain-live.ts +7 -9
  85. package/scripts/smoke-herdr-fresh-call-live.ts +4 -4
  86. package/scripts/smoke-herdr-plugin-build-live.ts +179 -50
  87. package/scripts/smoke-herdr-raw-install-live.sh +10 -3
  88. package/scripts/smoke-mux-lifecycle-live.ts +140 -8
  89. package/scripts/smoke-omp-fresh-live.ts +5 -4
  90. package/scripts/smoke-pi-durable-fresh-live.ts +29 -7
  91. package/scripts/smoke-setup-verdict.sh +27 -2
package/DELIVERY.md CHANGED
@@ -126,7 +126,7 @@ physical host. Do not read a D-level cell as a Darwin receipt.
126
126
  |---|---|---|---|
127
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. |
128
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. |
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. |
129
+ | **Antigravity / agy** | shipped, native push paused (#152) | D6; D7 partial | Record-backed native-push through LS gRPC `agentapi send-message` is paused: on the measured external path the agy LS refused `agentapi` calls for a missing CSRF token (#128; a first-party token path is unmeasured), so dispatch refuses an agy target pre-probe as `native-push-paused`. Birth, statusline and sender identity stay; 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. |
130
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. |
131
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. |
132
132
  | **Copilot CLI first-party extension** | raw transport probe; superseded by the owned product unit | D7 path observed; D3 control receipt incomplete; D8 unproven | CLI-spawned extension over stdio JSON-RPC; `joinSession()` + documented `fs.watch` → `session.send({mode:"enqueue"})`. Idle wake, exact-marker reply, and completion passed on 2026-08-23 (CLI 1.0.80, L4, one Linux host). Two-process isolation was observed but its decisive B log was not preserved. Kept as the transport receipt the owned receive unit was built on; the shipped unit differs deliberately — it announces the inbox instead of injecting the body. |
@@ -156,13 +156,24 @@ coalescing bounds, re-arm gaps, and crash/re-poke behavior are measured. A synth
156
156
  container doctor proves package/oracle shape, not a real Claude wake; a claimed host
157
157
  needs the installed strict doctor against a new native session.
158
158
 
159
- ### Antigravity: native push
159
+ ### Antigravity: native push (paused, #152)
160
160
 
161
161
  `PreInvocation` births or reattaches a citizen by native `conversationId` and writes a
162
- record-backed sender marker. `entwurf_v2` probes the live conversation and injects
163
- directly through the native adapter, with one bounded re-probe retry. Replyability is
164
- `record-backed identity ∧ probe-alive`; mailbox state does not exist on this rail, and no
165
- rail has resume authority since the visible-first cut.
162
+ record-backed sender marker. Native push is paused: on the external send path entwurf
163
+ measured, the agy language server refused the `agentapi` call with `Unauthenticated: missing
164
+ CSRF token` (#128 on agy 1.2.16, again on agy 1.4.6). Whether a first-party own-tool shell
165
+ holds a token entwurf could legitimately present is unmeasured, not ruled out. One fixed constant,
166
+ `NATIVE_PUSH_PAUSED_BACKENDS` in the v2 contract, drives three refusals before any probe:
167
+ `entwurf_v2` rejects an agy target as `native-push-paused` with `observedLiveness: null`, a
168
+ sender's `replyable` is false with a reason naming the pause, and `entwurf_register_native`
169
+ refuses without minting a garden id. Existing records, the backend enum and the record
170
+ reader are unchanged, so agy citizens stay readable and listed. The fresh-cut quiescence
171
+ probe is deliberately not behind the pause: it protects an operator-named archive and keeps
172
+ failing closed. When the pause lifts, the adapter path below applies again: `entwurf_v2`
173
+ probes the live conversation and injects directly through the native adapter, with one
174
+ bounded re-probe retry, and replyability is `record-backed identity ∧ probe-alive`.
175
+ Mailbox state does not exist on this rail, and no rail has resume authority since the
176
+ visible-first cut.
166
177
 
167
178
  The owned bridge, statusline, and hook installers own separate configuration atoms.
168
179
  Same-pid concurrent model invocation by multiple conversations is not claimed because
@@ -587,39 +598,45 @@ same class of capability through the CLI-owned stdio lifecycle. Absence of Claud
587
598
  `FileChanged` / `asyncRewake` / `watchPaths` therefore means only that Claude's hook
588
599
  mechanism cannot be copied — it never proved Copilot had no vendor wake surface.
589
600
 
590
- ### Remote pair (#107): one declared pair across two hosts
601
+ ### Remote pairs (#107, #149): declared pairs across hosts
591
602
 
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.
603
+ This is an opt-in route between 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
604
 
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`.
605
+ - **Declaration (#149).** 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 is exactly `{"version": 2, "routes": [...]}` with a non-empty `routes` array. Each route is one exact pair with exactly four keys:
606
+ - `localGardenId`: the citizen on this host.
607
+ - `remoteGardenId`: the citizen on the other host.
596
608
  - `ssh`: an existing SSH alias whose characters need no quoting.
597
- - `localGardenId`.
598
609
  - `remotePairFile`: the other host's canonical absolute path to its own file.
599
610
 
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.
611
+ One route serves both directions of its pair. An absent file means off. A symlink, non-regular, unreadable or invalid file is refused, and so is an empty `routes`. The versionless single-pair object of 0.41 and earlier is refused by name with the manual rewrite (`{"version":2,"routes":[<that object>]}`); there is no legacy reader. 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.
612
+
613
+ The set of routes is checked as a whole, never by position:
614
+ - Allowed: one local citizen paired with several remotes, one remote paired with several locals, and several routes over one alias.
615
+ - Refused: the same exact pair twice; a garden id that is local in one route and remote in another (a route to itself included); one remote garden id with two different aliases or files; one alias with two different `remotePairFile` values, because the receive entry behind that alias accepts only its one canonical file.
616
+
617
+ A broken file is refused at every `entwurf_v2` call of that bridge, local targets included, because the bridge reads the file before it can tell a remote target from a local one. That is not a silent fallback, and it is a cost to keep in mind when editing the file.
601
618
  - **Send.** The sending host's `entwurf_v2` reads the file and then decides:
602
- - A target other than `remoteGardenId` takes the unchanged local path.
619
+ - A target that no route names as remote takes the unchanged local path.
603
620
  - 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.
621
+ - Otherwise the route is the one whose two ends are this sender and this target. If there is none, the call is refused as `remote-sender-not-declared`, with zero SSH and no local fallback. That also covers an anonymous sender.
622
+ - That route spawns one `ssh -o BatchMode=yes -o ConnectTimeout=10 <alias> entwurf remote-pair-receive --pair <remotePairFile>`, with no shell and the claim on stdin.
623
+ - **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 looks for the one route whose remote end is the claim's `from` and whose local end is the claim's `target`, comparing both ends in that same route. A `from` that no route declares is refused as `remote-pair-from-mismatch`. A declared `from` addressing anything but its own declared local, including another route's local, is refused as `remote-pair-target-mismatch`. Both refusals come 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 pairs used the self-fetch mailbox.
607
624
  - **Outcomes.** The send ends in one of three outcomes:
608
625
  - 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
626
  - the other host's named refusal, made before any mutation: a pair mismatch (exit 3) or invalid input (exit 2);
610
627
  - `remote-indeterminate`.
611
628
 
612
629
  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.
630
+ - **Display.** The received envelope names the remote origin and the matched route's alias, 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.
631
+ - **Trust.** The model is same-operator SSH account trust. The receiving host checks the declared pairs. It does not cryptographically authenticate a citizen, and the same account could claim any declared id. With several routes, everything allowed to log in to that account is inside this boundary. That includes the keys of every other host. Separating them is the operator's SSH policy; Entwurf adds no per-route or per-key binding.
615
632
  - **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
633
  - **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
634
 
618
635
  Evidence:
619
636
 
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.
637
+ - 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. Since #149 it also runs three hosts, where one host's one file carries three routes: fan-out, a shared remote, both directions, an undeclared sender with zero SSH, and a cross claim refused on the receiving host. An installed-tarball gate runs the T2 cells against installed bytes.
621
638
  - 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.
639
+ - Unmeasured: an OFF target, negative paths over real SSH, other rails, more than one pair over real SSH (the #149 schema has no T3 yet, so the #107 exchange does not carry over to it), a sender and a receiver on different releases (the claim, argv, exit codes and receipt are unchanged, so that interoperation is inferred, not measured), and non-Linux hosts.
623
640
 
624
641
  ## Recording a new claim
625
642
 
package/README.md CHANGED
@@ -27,11 +27,12 @@ Git branches, worktrees, file isolation and internal delegation remain their con
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
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 |
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 |
30
+ | **Retire** one sibling the human operator explicitly requested | `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 |
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, Codex (Antigravity native push is paused, #152) |
32
32
 
33
- Antigravity is message-only: an existing conversation registers itself, and Entwurf never
34
- launches one. A launch receipt is neither the new sibling's address nor proof that it
33
+ Antigravity is a legacy citizen: an existing conversation registers itself, and Entwurf never
34
+ launches one. Its native push is paused (#152): on the measured external path the agy language server
35
+ refused Entwurf's calls for a missing CSRF token, so a message to it is refused as `native-push-paused`. A launch receipt is neither the new sibling's address nor proof that it
35
36
  processed its task; the sibling's own callback envelope supplies its garden id.
36
37
 
37
38
  The package holds the v2 core, native-harness bridges, a pi adapter and an ACP
@@ -54,11 +55,15 @@ ownership of the harness.
54
55
 
55
56
  Prerequisites: **Node >=24.0.0**, Python 3 for `setup`, and any harnesses you choose
56
57
  to use, already installed and authenticated. Pi is optional-by-presence, supported
57
- range `>=1.3.0 <1.4`; Claude Code supported floor `>=2.1.306` is required for its
58
+ range `>=1.4.0 <1.5`; Claude Code supported floor `>=2.1.306` is required for its
58
59
  managed exec-hook lifecycle. pnpm is needed only for source-checkout setup.
59
60
 
61
+ **Release state.** The Pi range above belongs to the published 0.41.1, which ships the Pi
62
+ 1.4.0 SDK set. The previous `0.41.0` ships the Pi 1.3.0 set and requires Pi 1.3.x; the README
63
+ inside that tarball and the Herdr snapshot `v2026.10.22-herdr.0.41.0` are its contract.
64
+
60
65
  ```bash
61
- npm install -g @junghanacs/entwurf
66
+ npm install -g @junghanacs/entwurf@0.41.1
62
67
  entwurf setup /path/to/your-project
63
68
  entwurf check-bridge
64
69
  ```
@@ -76,27 +81,18 @@ consent: [the operator steps](./docs/setup-clean-host.md#three-operator-decision
76
81
  Plain MCP registration alone does not make a garden citizen.
77
82
 
78
83
  **Platform bounds:** Linux is the certified harness-rail axis. macOS Entwurf-only
79
- installation is **CERTIFIED (CI)**, weaker than a physical-host doctor; its native
80
- harness rails, ACP and mux remain **NOT CERTIFIED — pending physical host**.
81
- Detected harness wiring on Darwin can therefore be written while setup stays
82
- non-green. WSL2: a CI runner (experimental) measured the Entwurf-only install and a
83
- zero-token Pi citizen; general, physical-machine or harness-parity WSL2 support is **not
84
- declared**, and delivery, other harnesses and physical machines are unmeasured. Native
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
- delivered one garden-id `entwurf_v2` message between them, with an offline scripted model
90
- and no vendor turn. Every other native-Windows surface (other harnesses, `setup` and the
91
- `.sh` bins, fresh/resume, mux/Herdr/ACP, physical machines) is **not declared**; the
92
- measured scope and its named gaps are in [BASELINE](./BASELINE.md#release-host-baseline).
93
- The package has no npm `os` restriction; that is not certification of every platform's
94
- harness rails.
84
+ installation is **CERTIFIED (CI)**; native harness rails, ACP and mux remain
85
+ **NOT CERTIFIED — pending physical host**, so detected wiring can still leave setup non-green.
86
+ WSL2 and native Windows have **experimental CI previews**, not general or physical-host support.
87
+ The native-Windows preview covers Pi citizens and offline scripted delivery, not vendor turns,
88
+ other harnesses, shell entrypoints, fresh/resume, mux, Herdr or ACP.
89
+ [BASELINE](./BASELINE.md#release-host-baseline) owns the version-specific receipts and gaps;
90
+ an npm package without an `os` restriction is not an all-platform certification.
95
91
 
96
92
  ### Native durable (pi-durable)
97
93
 
98
- **The package supplies Entwurf's owned app/TUI carrier and nine exact Pi 1.3.0 SDK
99
- members.** No separate runtime checkout, patch, build or ordinary Pi CLI is needed.
94
+ **The package supplies Entwurf's owned app/TUI carrier and nine exact Pi 1.4.0 SDK
95
+ members** (the previous 0.41.0 supplies the 1.3.0 set). No separate runtime checkout, patch, build or ordinary Pi CLI is needed.
100
96
  The launch verifies carrier integrity and every reachable SDK edge's name/version;
101
97
  incomplete, drifted or mixed supply refuses by name.
102
98
 
@@ -106,7 +102,7 @@ entwurf pi-durable --continue
106
102
  entwurf pi-durable --resume
107
103
  ```
108
104
 
109
- `--continue` opens this directory's newest durable session. `--resume` lists its
105
+ `--continue` opens this directory's latest-created durable session. `--resume` lists its
110
106
  sessions, most recently written first, by creation time, garden id and last write, and
111
107
  reopens the one you choose as it was saved; a cancelled choice opens nothing.
112
108
 
@@ -117,11 +113,9 @@ SQLite, transcripts and recovery remain the upstream SDK's:
117
113
 
118
114
  Optional `--native-module <absolute file>` initializes trusted ESM before runtime/TUI
119
115
  imports, then installs its default native Extension after the contact.
120
- `--native-module-dir <absolute directory>` (#134) loads the top-level `*.extension.mjs` files of that
121
- one named directory in byte-wise order instead, and the durable TUI keeps a live
122
- background-task badge on screen at any terminal size. No automatic module discovery or
116
+ `--native-module-dir <absolute directory>` loads that named directory's top-level
117
+ `*.extension.mjs` files in byte-wise order instead. No automatic module discovery or
123
118
  classic Pi API shim; guards are not a sandbox or rollback.
124
- Ordinary Pi's known `peerDependencies` warning reflects the separate native SDK supply.
125
119
  [Native setup](./docs/setup-clean-host.md#2b-optional-native-durable-pi-durable)
126
120
  owns flags, refusals and the extension contract.
127
121
 
@@ -131,9 +125,14 @@ owns flags, refusals and the extension contract.
131
125
  herdr plugin install junghan0611/entwurf/plugins/herdr --yes
132
126
  ```
133
127
 
128
+ The [Herdr guide](./plugins/herdr/README.md#from-a-raw-linux-box-in-nine-steps) says which
129
+ runtime each ref installs.
130
+
134
131
  This separate path activates Herdr-integrated **pi and Claude Code** with an
135
132
  independently locked runtime and an observation pane; it puts no Entwurf bins on PATH.
136
- An Entwurf release does not automatically re-pin that runtime.
133
+ An Entwurf release does not automatically re-pin that runtime: while 0.41.2 is an unpublished
134
+ candidate, main's lock rides the `herdr-checkout` verification carrier, and the published 0.41.1
135
+ runtime stays pinned by the Herdr tag `v2026.10.23-herdr.0.41.1`.
137
136
  [Herdr guide](./plugins/herdr/README.md) owns install, use, evidence and removal;
138
137
  the plugin does not own delivery or the Herdr launch rail.
139
138
 
@@ -169,7 +168,7 @@ In that **harness session**, ask the agent to:
169
168
  ```
170
169
 
171
170
  These are tools, not shell subcommands. Pi calls
172
- `mcp__entwurf_bridge__<verb>` through Pi's built-in MCP. The 0.41.0 bridge exposes **nine**
171
+ `mcp__entwurf_bridge__<verb>` through Pi's built-in MCP. The bridge exposes **nine**
173
172
  verbs; a pi citizen has **seven active + two hidden** (`entwurf_inbox_read` and
174
173
  `entwurf_register_native`), since it has no mailbox and its own extension births it.
175
174
  Those counts describe the source; a running host offers what its already-loaded bridge
@@ -194,58 +193,61 @@ pi / pi-durable / Claude Code / Copilot / OMP / Antigravity / Codex
194
193
  → control-socket | self-fetch mailbox | native-push
195
194
  ```
196
195
 
197
- | What you want | Tool | What its receipt means |
198
- |---|---|---|
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 |
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 |
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 |
196
+ **Delivery starts no process.** A live pi receives over its record-addressed control
197
+ address, an armed self-fetch citizen (Claude Code, Copilot, OMP, pi-durable) through its
198
+ mailbox, a probe-alive native-push citizen (Codex; Antigravity is paused, #152) through its own
199
+ conversation or thread. Anything else is refused honestly, never launched into. The receipt
200
+ is a receiver acceptance boundary, a durable mailbox file or an injection, never completion;
201
+ `wants_reply` is etiquette, not waiting. [DELIVERY](./DELIVERY.md#control-socket-acceptance-boundaries-120)
202
+ owns what each boundary means and each rail's admission.
203
+
204
+ The lifecycle verbs in the [table above](#entwurf) return their own receipts, never a delivery
205
+ result: fresh a launch receipt (the sibling's callback envelope supplies its garden id), resume
206
+ separate LAUNCH and OBSERVATION receipts, retire separate action, record and lock results.
207
+ **Retirement is never autonomous:** it needs the human operator's explicit target and action, and
208
+ no role, caller identity or tool annotation grants it ([DELIVERY](./DELIVERY.md#candidate-lifecycle-commands-are-not-delivery-boundaries-147)).
209
+
210
+ **Mux launches; it never delivers.** Fresh opens in the caller's Herdr workspace or tmux session;
211
+ `placement.tmuxSession` names another existing tmux session and is refused inside Herdr. The model
212
+ is required and `cwd` is a literal absolute directory. Missing or ambiguous context refuses with no
213
+ fallback. Exact argv, identity checks and limits: [tmux launch](./docs/mux-launch-rail.md),
214
+ [Herdr launch](./docs/herdr-launch-rail.md).
215
+
216
+ ### Remote siblings — declared pairs across hosts
217
+
218
+ Opt-in. The v2 file below is in main and the 0.41.2 candidate; the published 0.41.1 reads the
219
+ earlier single-pair file. On each host, write `~/.pi/agent/entwurf-remote-pair.json` (mode `0600`; a
220
+ moved agent dir or meta store moves it, see [setup §2c](./docs/setup-clean-host.md#2c-optional-remote-pair-between-two-hosts-107)). The ids
221
+ below are fictional; use your own records' garden ids:
222
+
223
+ ```json
224
+ {
225
+ "version": 2,
226
+ "routes": [
227
+ {
228
+ "localGardenId": "20260101T090000-a1b2c3",
229
+ "remoteGardenId": "20260101T091500-d4e5f6",
230
+ "ssh": "host-b",
231
+ "remotePairFile": "/home/me/.pi/agent/entwurf-remote-pair.json"
232
+ }
233
+ ]
234
+ }
235
+ ```
236
+
237
+ The other host carries the mirror route: the two garden ids swapped, its own SSH alias for this
238
+ host, and this file's canonical absolute path. Then send with the ordinary `entwurf_v2` to the
239
+ remote garden id: one SSH attempt over your own account, checked and dispatched by the host that
240
+ holds the record. There is no peer discovery, wake or retry. Why: [remote sibling principles](./docs/remote-sibling-principles.md);
241
+ contract: [DELIVERY](./DELIVERY.md#remote-pairs-107-149-declared-pairs-across-hosts); steps and the
242
+ hand conversion from the earlier file: [setup §2c](./docs/setup-clean-host.md#2c-optional-remote-pair-between-two-hosts-107).
203
243
 
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.
244
+ ### Team bookmarks — `team.json`
205
245
 
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
209
- its native conversation/thread. Dormant pi, undeliverable mailboxes, dead/indeterminate native
210
- push and record-less sockets reject honestly. Only control-socket dispatch takes
211
- the per-target lock; the other rails use their own deliverability/probe evidence.
212
-
213
- A control-socket `sent` means a turn was triggered, not that the model read the
214
- text. `queued-steer` and `queued-follow-up` are volatile process queues, with no
215
- FIFO between them; abort drops them. `accepted-unknown-boundary` is acceptance
216
- we cannot classify, never a reason to retry. A mailbox enqueue is a durable file
217
- receipt. `wants_reply` is etiquette, not waiting, polling or ownership; replyability
218
- is a live rail fact, not merely a consequence of trusted identity.
219
- [DELIVERY](./DELIVERY.md#control-socket-acceptance-boundaries-120) owns these distinctions.
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
-
223
- **Mux launches; it never delivers.** Inside Herdr, fresh opens a new unfocused tab
224
- in the caller's workspace, pi/Claude Code only, with no tmux fallback. Outside,
225
- it opens pi/pi-durable/Claude Code/Copilot/OMP/Codex visibly on the caller's own tmux
226
- server. pi-durable runs the installed carrier on its exact SDK set described above and
227
- refuses by name before any window when that verdict is not green; Herdr does not fresh-open it. The model is required; optional `cwd` is a literal
228
- absolute directory, omitted or empty means the caller's cwd. Optional `placement.tmuxSession` names an existing
229
- tmux session (not permitted inside Herdr), never a new session or a cwd lookup.
230
- Omitted tmux placement follows the caller: Codex beside its own TUI pane matched
231
- by thread-id, everyone else in their own session. Missing/ambiguous context refuses.
232
- A launch receipt is neither the callback address nor proof of task processing.
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.
247
- Exact boundaries: [tmux launch](https://github.com/junghan0611/entwurf/blob/main/docs/mux-launch-rail.md)
248
- and [Herdr launch](https://github.com/junghan0611/entwurf/blob/main/docs/herdr-launch-rail.md).
246
+ A person's bookmark of the siblings one project works with. Copy [`team.json.example`](./team.json.example)
247
+ to an ignored `team.json` at the project root and fill names, backend, garden id, native session id
248
+ and cwd from Entwurf's record facts; read liveness from `entwurf_peers` when you dispatch. Entwurf
249
+ does not load it as runtime configuration, and it is not configuration for identity, routes, roles, permissions or lifecycle. The
250
+ example's own notes say the rest.
249
251
 
250
252
  ## Concepts
251
253
 
package/VERIFY.md CHANGED
@@ -6,8 +6,8 @@ and the manual judgements a gate cannot make.
6
6
 
7
7
  > **Current surface.** `entwurf-bridge` exposes `entwurf_v2`, `entwurf_peers`,
8
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
- > backends are Claude and Snowflake Cortex Code. Antigravity is a separate shipped
10
- > native-push citizen lane. Codex is a native-push citizen supported in 0.21.0, whose birth hook is
9
+ > backends are Claude and Snowflake Cortex Code. Antigravity is a separate legacy citizen
10
+ > lane whose native push is paused (#152). 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
12
12
  > visible fresh; it remains outside ACP and has no resume.
13
13
  > This banner names the surface, not an acceptance. The 48-assertion / MUST 24/0/0 / 475/475 numbers
@@ -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, 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.
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. The native pi launch names `placement.tmuxSession` (#108): the harness asks tmux itself that the pane sits in a caller-distinct seat session on the same private server, that the caller's windows are unchanged, and that the close later removes only that window and leaves the seat's anchor standing. The steps are: 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
  >
@@ -125,7 +125,7 @@ that moved out of `scripts/` and into a file beside its subject would otherwise
125
125
 
126
126
  > **A visible-fresh receipt outside the MUST tier is no longer the general rule — it is Copilot's preserved exception.** #87 measured what that wording permitted: because clause 7 sat outside every aggregate, a harness could be admitted as a full D6 citizen that `entwurf_fresh_call` cannot open, carry an honest "not a supported harness" sentence in the delivery matrix, and still pass the whole floor toward a cut. From OMP onward, the FIRST release of a harness admitted under the #82 contract owes its clause 7 receipt as a release-gate MUST step (`smoke-omp-fresh-live`), and the deterministic half of the same rule (`check-harness-admission-parity`, inside `check:full`) makes a citizen backend missing from `FRESH_CALL_BACKENDS` a red floor rather than a documented one. Forward-only by decision: Copilot's row is not reopened. The contract lives in `docs/adding-a-harness.md` step 9, "The release stop"; this note exists so the cost discipline above is not read as permission.
127
127
  >
128
- > The aggregate release gate does not own a live agy conversation id, so agy's real native-push round trip is a separate acceptance axis: three fail-loud doctors plus `LIVE=1 AGY_CONVERSATION_ID=<id> ./run.sh smoke-agy-native-push-live`, followed by a fresh-conversation sender/reply check after package install. Its deterministic install/sender gates are already inside `pnpm run check:full`; do not misreport the aggregate gate as live agy evidence. **Cost fence:** the agy conversation this smoke drives runs on a free account, so open it on `gemini-3.6-flash` — never a Pro tier. The model is the operator's choice at conversation-open time; entwurf never selects it, and no assertion reads it (see the shipped-lane note: model display is not part of the agy contract).
128
+ > agy native push is paused (#152). On the external send path entwurf measured, the agy language server refused the `agentapi` call with `Unauthenticated: missing CSRF token` (#128 on agy 1.2.16, again on agy 1.4.6). Whether a first-party own-tool shell holds a token entwurf could legitimately present is unmeasured; this pause does not claim it is impossible. So `entwurf_v2` refuses an agy target before any probe as `native-push-paused` (`observedLiveness: null`), an agy sender's `replyable` is false by name, and `entwurf_register_native` refuses before probing or minting a garden id. Existing agy records stay readable. The pause itself is proven by the deterministic `[QK:AGY-NATIVE-PUSH-PAUSED]` cell, which needs no agy conversation. Separately, an agy host's retained legacy identity surfaces can be checked by three fail-loud doctors plus a fresh-conversation identity check (birth, statusline, `entwurf_self`) after package install. While the pause holds, that check is an optional diagnostic run on request, not a cut requirement; a green one is neither send evidence nor a certification that the installed agy host works or can receive. `LIVE=1 AGY_CONVERSATION_ID=<id> ./run.sh smoke-agy-native-push-live` exits 2 with `[entwurf:paused]` before it reads the conversation id, and that is not a PASS. The deterministic paused refusal (`[QK:AGY-NATIVE-PUSH-PAUSED]`) and the install/sender gates are inside `pnpm run check:full`; do not misreport the aggregate gate as live agy evidence. **Cost fence:** the pause proof (criterion 7) needs no agy conversation, model or conversation id, and none is opened for `smoke-agy-native-push-live`. The optional retained-identity diagnostic does need one fresh agy conversation after package install; when it is asked for, open it on `gemini-3.6-flash` (free account) — never a Pro tier; entwurf never selects the model, and no assertion reads it (model display is not part of the agy contract). Lifting the pause would be a separate future change with its own positive acceptance; nothing here certifies one.
129
129
  >
130
130
  > Aggregate release-gate does not own a loaded Codex thread. Codex native-push remains an
131
131
  > on-demand host axis. The first-admission acceptance passed on 2026-09-12 under the fixed `codex`
@@ -211,7 +211,7 @@ Do not collapse source, package, fixture, and native-host evidence into one “g
211
211
 
212
212
  **Remote pair (#107).** Each layer of evidence lives in its own place:
213
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.
214
+ - **Deterministic.** The cells sit beside the behavior in `pi-extensions/lib/remote-pair-*.test.ts` and are found by `check-tests-beside-behavior`. Their mutants run through the narrow execution coordinate `check-remote-pair-beside` (rule 3, #149). 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. It covers two hosts, and since #149 also three hosts whose one file carries several routes. Both are offline.
215
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
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
217
  - the SSH argv or quoting;
@@ -289,7 +289,7 @@ caller/target/cross-harness LIVE are owed by `smoke-pi-durable-fresh-live`, a re
289
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. |
290
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. |
291
291
  | Native Claude host | installed strict doctor against a new real session | Missing live join is `NOT CERTIFIED`, not a fixture PASS. |
292
- | Native agy host | three doctors plus conversation-id-gated native-push round trip | Aggregate release-gate does not own an agy conversation id. |
292
+ | Native agy host | the deterministic pause cell plus the agy record, install and sender gates inside `check:full`; native push is paused (#152), so no round trip is claimed. The three doctors and a fresh-conversation identity check are an optional diagnostic, run on request | Aggregate release-gate does not own an agy conversation id. The paused LIVE smoke exits 2 `[entwurf:paused]`, not a PASS. |
293
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. |
294
294
 
295
295
  The repo-local `entwurf-release` skill owns the `land → prepare → make → publish`
@@ -305,7 +305,7 @@ consumer image outside this standing protocol.
305
305
 
306
306
  - **Garden-id delivery:** discover a target with `entwurf_peers`, then `entwurf_v2 fire-and-forget` — the intent axis is single-valued since the visible-first cut. It reaches a live pi target over its control socket, a deliverable self-fetch citizen through the mailbox, and a probe-alive native-push conversation by direct injection. A DORMANT in-domain citizen rejects as `dormant-fire-forget-unsupported` — delivery never wakes it; reopen it with the separate lifecycle verb `entwurf_resume_call {target}`, then dispatch again. A wrong target state is rejected, never auto-fixed and never launched into.
307
307
  - **ACP continuity:** a direct `pi --provider entwurf --model claude-sonnet-5-5` turn, or the `smoke-acp-session-reuse-live` gate (process-scoped reuse + recall). Multi-turn reuse is proven by that gate, not by any v1 resume tool.
308
- - **agy citizenship:** in a fresh agy conversation, the first `PreInvocation` must yield a garden id, `entwurf_self` must report `agentId=meta-session/antigravity` and `replyable:true` only while the native probe is alive, and a reply to that same garden id must direct-inject into the same conversation. No mailbox/receiver-marker evidence counts on this rail.
308
+ - **agy citizenship (optional diagnostic while #152 holds; run on request, not a cut requirement):** in a fresh agy conversation, the first `PreInvocation` must yield a garden id and `entwurf_self` must report `agentId=meta-session/antigravity`. While native push is paused (#152), `replyable` is `false` with a reason naming the pause, and a dispatch to that garden id returns `native-push-paused` with nothing probed or sent. A same-gid reply is not claimed. No mailbox/receiver-marker evidence counts on this rail.
309
309
 
310
310
  ### What NOT to do — bypassing the operational path
311
311
 
@@ -374,7 +374,7 @@ The goal is not merely "invoke Claude Code." We want:
374
374
  **One install command to remember: `./run.sh setup <project>`.** It is idempotent — re-run the exact same command whenever anything looks wrong. There is no second install surface to juggle: from a clone `setup` runs the whole floor in order.
375
375
 
376
376
  1. `pnpm install` — installs the pinned development dependencies and builds the bridge (source-checkout bootstrap only; an installed package never runs npm/pnpm inside `node_modules`)
377
- 2. pi wiring → `<project>/.pi/settings.json` + user-scope registration — only when a `pi` inside the supported range (`>=1.3.0 <1.4`) is on PATH; absent pi is an explicit zero-state SKIP, a below-floor pi is a detected FAIL. The user-scope entry is owner-recorded (#86 C2): another root's live-or-missing ownership makes this step a zero-write refusal (setup: pi FAIL) that names `takeover-user-scope`; the install-states bind the exact managed settings path (a drifted/symlinked/corrupt target is a zero-write refusal) and the inverse removes only the recorded owner's exact entry; `doctor-pi-package` reports the verdict
377
+ 2. pi wiring → `<project>/.pi/settings.json` + user-scope registration — only when a `pi` inside the supported range (`>=1.4.0 <1.5`) is on PATH; absent pi is an explicit zero-state SKIP, a below-floor pi is a detected FAIL. The user-scope entry is owner-recorded (#86 C2): another root's live-or-missing ownership makes this step a zero-write refusal (setup: pi FAIL) that names `takeover-user-scope`; the install-states bind the exact managed settings path (a drifted/symlinked/corrupt target is a zero-write refusal) and the inverse removes only the recorded owner's exact entry; `doctor-pi-package` reports the verdict
378
378
  3. Claude meta-bridge global plugin — only when `claude` is on PATH; otherwise skipped cleanly
379
379
  4. source stable-bin exposure — including certified `entwurf` → this checkout's `run.sh`, the managed runtime Copilot fresh resolves; helper units are attempted independently and a foreign helper is a named FAIL
380
380
  5. agy bridge + exact permission + statusline + `PreInvocation` hook — only when `agy` is on PATH; each adapter is idempotent and independently doctorable
@@ -437,7 +437,7 @@ addressable sends require `--entwurf-control` (measured 2026-07-24: the same
437
437
  one-shot with that flag returns its own gid and delivers `entwurf_v2` to a peer
438
438
  mailbox with `origin=pi-session`, `replyable=true`).
439
439
 
440
- `setup` requires Node 24 and Python 3 (pnpm only on a source checkout — the dependency bootstrap is source-only, and installed mode needs no pnpm); harnesses including pi are optional-by-presence (absent → explicit zero-state SKIP, detected incomplete/below-floor → named FAIL + nonzero result). On a source checkout it runs the frozen `pnpm install` + presence-gated pi project/user wiring + detected Claude wiring + source stable-bin exposure + detected agy wiring + detected Copilot four-unit composition + the v2 install smoke. A green setup certifies that the source-owned `entwurf` symlink targets this checkout and wins PATH resolution; helper units are attempted independently and a foreign helper is a named bins FAIL (nonzero), with harness-specific doctors keeping the per-leaf verdict. A detected `copilot` composes all four native units (birth → MCP → receiver → visible footer) with independent per-unit verdicts (#86 C3b); setup does **not** replace any native-harness doctor. The full aggregate live floor is still `LIVE=1 ./run.sh release-gate <scratch> --cut` — without `--cut` it is a diagnostic pass, not acceptance — with agy's conversation-id-gated round trip verified separately.
440
+ `setup` requires Node 24 and Python 3 (pnpm only on a source checkout — the dependency bootstrap is source-only, and installed mode needs no pnpm); harnesses including pi are optional-by-presence (absent → explicit zero-state SKIP, detected incomplete/below-floor → named FAIL + nonzero result). On a source checkout it runs the frozen `pnpm install` + presence-gated pi project/user wiring + detected Claude wiring + source stable-bin exposure + detected agy wiring + detected Copilot four-unit composition + the v2 install smoke. A green setup certifies that the source-owned `entwurf` symlink targets this checkout and wins PATH resolution; helper units are attempted independently and a foreign helper is a named bins FAIL (nonzero), with harness-specific doctors keeping the per-leaf verdict. A detected `copilot` composes all four native units (birth → MCP → receiver → visible footer) with independent per-unit verdicts (#86 C3b); setup does **not** replace any native-harness doctor. The full aggregate live floor is still `LIVE=1 ./run.sh release-gate <scratch> --cut` — without `--cut` it is a diagnostic pass, not acceptance — with agy accepted separately under criterion 7: its paused native push by the deterministic `[QK:AGY-NATIVE-PUSH-PAUSED]` cell and its record, install and sender gates, all inside `check:full`; the retained legacy identity check is an optional diagnostic while #152 holds, and no agy round trip is claimed.
441
441
 
442
442
  ### 1.4 Cross-install / cross-backend parity (optional, high-value)
443
443
 
@@ -545,7 +545,7 @@ The minimum passing bar:
545
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.
546
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.
547
547
  6. **Carrier separation honored:** engraving vs pi-context-augment kept distinct (§1A.0); no bridge-identity narrative attributed to the engraving carrier.
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.
548
+ 7. **agy native push paused (#152); acceptance is deterministic while the pause holds.** Required for a cut: a dispatch to an agy target returns `native-push-paused` with zero probe, send, adapter or record activity, an agy sender's `replyable` is false by name, and `entwurf_register_native` refuses before probing — proven by the deterministic `[QK:AGY-NATIVE-PUSH-PAUSED]` cell, which needs no agy conversation and is not a live round trip — and the agy record schema and reader stay in place, with the agy install and sender gates green inside `check:full`. A same-gid reply is not claimed while the pause holds. Not required for a cut: the retained legacy identity check (all three agy doctors, plus automatic birth/statusline/sender identity in a fresh conversation, where `agentId=meta-session/antigravity` is correct and model display is not part of it). It is an optional diagnostic run when explicitly asked, it is not send evidence, and a release does not claim that the installed agy host is certified or can receive. An optional diagnostic is not a cut requirement, and neither is a native-push PASS. Lifting the pause needs a separate future change with its own positive acceptance. Same-pid concurrent conversation invocation is not claimed.
549
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.
550
550
  9. **Boundary preservation across backends/machines:** for every shipped or explicitly probed backend, regardless of install path or host, no cross-backend tool-surface contamination and no confabulation about pi internals.
551
551
  10. **Hygiene:** no orphan ACP children; no unexpected persisted session garbage (a turn-scoped `cwd:` fallback is never a persisted reuse).
package/demo/README.md CHANGED
@@ -24,7 +24,7 @@ takes/helpers/tests are maintainer tooling and are deliberately excluded from th
24
24
 
25
25
  - Node at the `package.json#engines.node` floor; the checkout's pinned dependencies and
26
26
  current compiled bridge. After a source/bootstrap change, build with `pnpm run build-bridge`.
27
- - Pi at the checkout's supported floor (**current floor 1.3.0**); `entwurf` on PATH must be
27
+ - Pi at the checkout's supported floor (**current floor 1.4.0**); `entwurf` on PATH must be
28
28
  the intended source stable-bin. Source evidence is not registry-package acceptance.
29
29
  - `herdr` on PATH at exactly `scripts/fixtures/herdr-supply.json#version`; `asciinema` v3.
30
30
  - Herdr's existing Pi integration at
@@ -135,7 +135,7 @@ Dep bump ledger. 0.38.0 (#145) changes only the pi SDK set; the ACP rows are unc
135
135
  | Surface | Declaration | Class | What a green actually says |
136
136
  |---|---|---|---|
137
137
  | Entwurf package | `package.json` `version` | checkout declaration, not a release receipt | names the package label these rows sit beside; whether that label shipped, and with which rows, is CHANGELOG's record |
138
- | pi runtime | SDK exact `1.3.0`, host `>=1.3.0 <1.4` | **exact** oracle + **closed range** | the SDK set this checkout builds, gates and (since #130) ships as exact production dependencies; an ordinary Pi host inside the range is accepted, and the ceiling moves only on measurement. The published 0.36.0 through 0.37.1 shipped the 1.2.0 set (#141); 0.38.0 (#145) is the first to carry 1.3.0. Historical: the published 0.33.0 through 0.35.1 ship SDK exact 1.1.0, 0.32.0 ships SDK exact 1.0.4 with the next-minor ceiling (ROADMAP's 2026-10-07 bump entry), and 0.31.0 declared the earlier peer floor 1.0.2 |
138
+ | pi runtime | SDK exact `1.4.0`, host `>=1.4.0 <1.5` | **exact** oracle + **closed range** | the SDK set this checkout builds, gates and (since #130) ships as exact production dependencies; an ordinary Pi host inside the range is accepted, and the ceiling moves only on measurement. The published 0.36.0 through 0.37.1 shipped the 1.2.0 set (#141); 0.38.0 (#145) is the first to carry 1.3.0. Historical: the published 0.33.0 through 0.35.1 ship SDK exact 1.1.0, 0.32.0 ships SDK exact 1.0.4 with the next-minor ceiling (ROADMAP's 2026-10-07 bump entry), and 0.31.0 declared the earlier peer floor 1.0.2 |
139
139
  | ACP wire SDK | `@agentclientprotocol/sdk 1.8.0` | **exact** | the shared wire oracle both adapters speak |
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 |
@@ -83,7 +83,7 @@ Emergency/manual workaround when the MCP server environment is wrong but an exis
83
83
  External/meta-session semantics:
84
84
 
85
85
  - `entwurf_v2` from a plain external host is **refused by default** (no authoritative sender — #50 C4). With the explicit `ENTWURF_BRIDGE_ALLOW_ANONYMOUS_SENDER=1` hatch it delivers with `origin: "external-mcp"` / `replyable: false`; there is still no reply address.
86
- - `entwurf_v2` from a trusted meta-session delivers with `origin: "meta-session"`, and `replyable` is **derived from that sender's own rail — not granted by being trusted**: a self-fetch sender (Claude Code, Copilot, OMP) is replyable only while its receiver is live and armed; Antigravity requires a live native probe; Codex requires a matching request-scoped record and loaded app-server thread. Identity survives either way; only `replyable` drops to `false`.
86
+ - `entwurf_v2` from a trusted meta-session delivers with `origin: "meta-session"`, and `replyable` is **derived from that sender's own rail — not granted by being trusted**: a self-fetch sender (Claude Code, Copilot, OMP) is replyable only while its receiver is live and armed; Antigravity requires a live native probe, and while its native push is paused (#152) it is `false` by name without a probe; Codex requires a matching request-scoped record and loaded app-server thread. Identity survives either way; only `replyable` drops to `false`.
87
87
  - `entwurf_v2` never launches a process, so no delivery path needs `pi` on PATH. A dormant pi target is refused as `dormant-fire-forget-unsupported`: the hidden background resume that used to answer there was withdrawn under the visible-first rule, and re-opening the session is the separate `entwurf_resume_call` verb — which DOES need `pi` on PATH, because it starts one.
88
88
  - `entwurf_self` returns the same authoritative identity for pi sessions **and** trusted meta-sessions. A plain external host with no pi env, native marker, or Codex request claim still fails because there is no reply address to report.
89
89