@junghanacs/entwurf 0.30.1 → 0.31.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 (58) hide show
  1. package/AGENTS.md +1 -0
  2. package/CHANGELOG.md +112 -0
  3. package/DELIVERY.md +1 -0
  4. package/README.md +47 -7
  5. package/VERIFY.md +17 -1
  6. package/demo/README.md +1 -1
  7. package/docs/acp-backend-rail.md +1 -1
  8. package/docs/setup-clean-host.md +104 -4
  9. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +7 -7
  10. package/mcp/entwurf-bridge/dist/pi-extensions/entwurf-capabilities.json +1 -0
  11. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-deliverability.js +7 -1
  12. package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-composition.js +34 -3
  13. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-sender-identity.js +7 -1
  14. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js +28 -2
  15. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +30 -2
  16. package/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-fresh-preflight.js +92 -0
  17. package/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-runtime.js +288 -0
  18. package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-pi-durable.js +744 -0
  19. package/mcp/entwurf-bridge/src/index.ts +7 -7
  20. package/mcp/entwurf-bridge/tsconfig.build.json +8 -1
  21. package/package.json +13 -10
  22. package/pi/entwurf-capabilities.json +1 -0
  23. package/pi/pi-durable/bootstrap.mjs +281 -0
  24. package/pi/pi-durable/overlay/runtime-contacts.patch +91 -0
  25. package/pi/pi-durable/overlay/upstream-pin.json +16 -0
  26. package/pi-extensions/lib/entwurf-deliverability.ts +7 -1
  27. package/pi-extensions/lib/fresh-call-composition.ts +42 -7
  28. package/pi-extensions/lib/meta-sender-identity.ts +7 -1
  29. package/pi-extensions/lib/meta-session.ts +28 -2
  30. package/pi-extensions/lib/mux-fresh-call.ts +34 -2
  31. package/pi-extensions/lib/pi-durable-fresh-preflight.ts +113 -0
  32. package/pi-extensions/lib/pi-durable-runtime.ts +384 -0
  33. package/pi-extensions/meta-bridge-pi-durable.ts +1020 -0
  34. package/run.sh +200 -33
  35. package/scripts/check-fresh-call-dispatch.ts +18 -0
  36. package/scripts/check-gate-qualification.ts +2 -1
  37. package/scripts/check-pi-durable-contact.ts +834 -0
  38. package/scripts/check-pi-durable-receive.ts +1116 -0
  39. package/scripts/check-pi-durable-send.ts +1661 -0
  40. package/scripts/lib/pi-durable-fresh-live-receipts.ts +139 -0
  41. package/scripts/lib/pi-durable-r1.ts +100 -0
  42. package/scripts/lib/pi-durable-reap.ts +27 -0
  43. package/scripts/lib/pi-durable-receive.ts +137 -0
  44. package/scripts/mutants/control-send-receipt.json +1 -1
  45. package/scripts/mutants/copilot-birth.json +2 -2
  46. package/scripts/mutants/meta-hook-session-switch.json +4 -2
  47. package/scripts/mutants/mux-fresh-call.json +69 -3
  48. package/scripts/mutants/omp-birth.json +2 -2
  49. package/scripts/mutants/omp-fresh.json +6 -4
  50. package/scripts/mutants/pack-install.json +3 -3
  51. package/scripts/mutants/pi-durable.json +390 -0
  52. package/scripts/mutants/pi-floor.json +2 -2
  53. package/scripts/pi-durable-contact-host.mjs +105 -0
  54. package/scripts/pi-durable-receive-host.mjs +127 -0
  55. package/scripts/pi-durable-send-host.mjs +213 -0
  56. package/scripts/smoke-pi-durable-fresh-live.ts +624 -0
  57. package/scripts/smoke-setup-verdict.sh +21 -2
  58. package/scripts/tsconfig.json +1 -0
package/AGENTS.md CHANGED
@@ -100,6 +100,7 @@ implement → affected focused gates → independent review → one amendment bu
100
100
  ## Type and Working Boundaries
101
101
 
102
102
  - Each `.ts` file belongs to one declared typecheck fence; `exclude` may move a file between fences but never out of all of them, so an exclude states the receiving `include` in the same change. Root pi extensions use TypeBox through `@earendil-works/pi-ai`; MCP/scripts use explicit `.ts` imports required by Node strip-types. Installed surfaces route to compiled JS. Behavior-adjacent `.test.ts` takes the fence `test/` takes — `scripts/tsconfig.json`, never the emit-capable root program — and is kept out of the tarball by a files-array negation whose tripwire is `check-pack`.
103
+ - Develop only on branches in this repository checkout; never use worktrees or tmp development clones. Isolated test fixtures and temporary evidence are not development checkouts.
103
104
  - Make surgical, one-contract changes. Removal repairs source and its proof together; a green gate never proves retired behavior.
104
105
  - Before commit, sweep repository-wide for retired authority vocabulary and landed-plan future tense. Historical tombstones may remain; live docs, source, gates, and usage must agree.
105
106
  - Prefer capability-domain language over identity rank. Use tabs unless the project formatter requires otherwise. GLG decides commit, push, and release gates.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,118 @@ All notable changes to this project will be documented here. Format follows [Kee
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 0.31.0 - 2026-10-07
8
+
9
+ ### Added
10
+
11
+ - **Native durable contact (`pi-durable`, #129).**
12
+ Treat the experimental durable coding app as a separate native harness. One
13
+ native host attaches to one V3 garden id; the contact adds visible garden
14
+ identity, self/peers, canonical outbound dispatch and announce-only root-directed
15
+ mailbox receive with explicit inbox read. Runtime, TUI, model/auth, SQLite
16
+ storage, transcript and recovery remain upstream-owned. Internal subagents do
17
+ not mint another citizen. Native recovery is not exactly-once Entwurf delivery.
18
+
19
+ ### Installation
20
+
21
+ - **Entwurf's npm/source installation route stays unchanged.** Optional native
22
+ durable use additionally requires an operator-provided pinned upstream source
23
+ runtime and the narrow runtime-contact overlay (Pi 1.0.2/source `cd32f77` in this
24
+ lane). The `@earendil-works/pi-durable` **library** is npm-installable; the
25
+ experimental coding app/TUI is not supplied by the ordinary Pi npm package.
26
+ Entwurf does not install a harness or copy credentials. Existing non-durable
27
+ users do not need a source checkout. [Native durable setup boundary](docs/setup-clean-host.md#2b-optional-native-durable-pi-durable).
28
+
29
+ ### Changed
30
+
31
+ - **Pi dependency/runtime floor is 1.0.2**, with pi peer range `>=1.0.2 <1.1`.
32
+ Native durable pins upstream source `cd32f77` and its narrow contact overlay;
33
+ no upgrade to a newer experimental app or replacement runtime is included.
34
+ - **Source workspace discovery is root-only.** Explicit `packages: ['.']` keeps
35
+ pnpm's workspace scan coherent with the root install, without disabling its
36
+ dependency verification. Prepack still runs the foreground build lifecycle,
37
+ with build output on stderr so pack JSON stays readable and failures propagate.
38
+
39
+ ### Verification
40
+
41
+ - **Reviewed pre-version main:** product `165338e` and native-admission `535c2e1`
42
+ landed separately with normal hooks/shared safety scans. The final landing's
43
+ [exact-SHA CI](https://github.com/junghan0611/entwurf/actions/runs/37478003145)
44
+ at `535c2e1b63e6a66c45227e197de80bf261202aec` passed all four required jobs
45
+ and the qualification BODY **872/872 KILLED**, including the new foreign-sender
46
+ guard4.5s KILLED against subject `7bdea2c4c664…`. Implementation issue #129 is closed; #130 owns
47
+ npm-only runtime supply, Pi upgrades, ACP and UI follow-ups. This main receipt
48
+ is not the later prepared-commit or preserved-artifact acceptance.
49
+ - **Native first admission:** corrected real Pi → pi-durable → Pi LIVE36
50
+ passed in41s on `openai-codex/gpt-6.1-sol`, task-wide native5 + contact6 offering.
51
+ A consistent official SQLite backup establishes first callback, own-id inbox
52
+ read, joined successful native dispatch and the exact root doorbell reaching done.
53
+ Pi transcript row19 carries the exact expected payload and authoritative durable
54
+ sender. Native V evidence is distinct from checkout contact95/send19/receive21
55
+ scripted S + native H and installed-bootstrap import-only proof.
56
+ - **Receipt-oracle repair is portable and independently reviewed.** Exact payload,
57
+ terminal sender envelope, root scope, effective offering, call/result and doorbell
58
+ joins replace substring/union predicates. Eight inline tests and the existing mux
59
+ coordinate carry a foreign-sender guard mutant. The original35-GREEN predicate
60
+ selected fixture row15; real old-row19 delivery was independently present. Old raw,
61
+ wrong receipt and RED857/870 remain preserved, never relabelled new acceptance.
62
+ Native admission raw: `.tmp-verify/b9ffcb-pack-cause/live-pd-20261006T223708/`.
63
+ - **Versioned working-candidate P4:** FULL666s/exit0, HEAD/index/tracked work-surface
64
+ and binary diff frozen MATCH; pack dry-run580files. The pre-verdict release-metadata
65
+ patch/bytes are preserved in `.tmp-verify/031-release/prepare/p4/`, snapshot SHA
66
+ `2529949384af40c6ca15f65575638b97980817cb85d7a68bb81e9ff1b34c8fa7`, full-log SHA
67
+ `57da80e9e32159f8bc966381a2756d6a34cca647dd5ee5791ee681ed3509b0df`.
68
+ This floor used the exact private Herdr0.9.3 aarch64 supply pin; operator0.9.1 stayed
69
+ unchanged. No qualification BODY or native LIVE was executed in this P4 run.
70
+ - **Explicit agy criterion7 exception for this0.31.0 cut, confirmed by GLG
71
+ (2026-10-07):** “agy 예외. 0.30.1부터 그렇게 헀어 더 진행가자” permits continuing
72
+ prepare/make while agy1.2 remains **NOT CERTIFIED**. The historical1.1 pin and
73
+ DELIVERY certification row stay unchanged; #128 remains first-party own-context
74
+ prerequisite research. No nonce/credential extraction, old-CID replay, transport
75
+ implementation, permanent floor change or npm publication is authorized by this
76
+ exception. Other release axes remain mandatory.
77
+ - **Fresh native Claude host:** owned writer/registry refresh corrected the stale
78
+ deployed v3 bytes before P5. Source=assembled=installed writer `c8f05d394d0e`,
79
+ capability registry `2f95e94f1eb9`; strict doctor exits0 with four live owner joins.
80
+ A new Claude2.1.291 session after install, garden `20261007T024544-c71062`,
81
+ ownerPID2784041/startKey `linux:304383485`, bridge child2784080, joins sender,
82
+ receiver and V3 record with no model turn. `prepare/claude-native-cert/accepted.json`
83
+ retains the exact metadata. This is checkout-provenance certification, not an
84
+ installed emitted-JS/bin-shim, registry, model-turn or fresh delivery/reply receipt.
85
+ - **First P5 and scoped repair remain distinct.** The frozen candidate's first
86
+ `--cut` run ended RED: MUST23/2/0, BEHAVIOR1/0/0, qualification872/872 KILLED,
87
+ HEAD/index/tracked bytes MATCH. Log `/tmp/entwurf-release-gate-0.31.0.JdQ4q6/release-gate.log`.
88
+ The owned OMP receiver's stale writer was refreshed through its installer; strict
89
+ runtime/ownership doctor and fresh receive LIVE11 passed. Initial Pi's Codex relay
90
+ added one trailing newline; the exact source oracle rejected it. A fresh chain on
91
+ the unchanged candidate passed65 assertions, source audits Pi3/3 + Codex3/3, with
92
+ no oracle relaxation or old-CID replay. The owed on-demand seat×cwd fresh-call
93
+ matrix passed39. `prepare/p5-focused-1/accepted.json` retains those focused receipts;
94
+ they do not turn the original aggregate RED into GREEN. Immediate P9 was report-only,
95
+ five eligible roots312K after P5 and six328K after the focused bundle; protected
96
+ Emacs was the only reparented `/tmp` process, with no deletion or signal.
97
+ - **Final fresh P5 cut verdict:** `LIVE=1 ./run.sh release-gate
98
+ /tmp/entwurf-release-gate-0.31.0.Xzbk2b --cut` exited0 after110m15s:
99
+ **MUST PASS=25 FAIL=0 SKIP=0**, **BEHAVIOR PASS=1 FAIL=0 SKIP=0**, its own
100
+ FULL and qualification **872/872 KILLED**, frozen HEAD/index/tracked bytes MATCH.
101
+ Log SHA256 `609612baff850ec06fc74321002b33a6b71d3dabc76874dc1546b8722c317bf2`;
102
+ snapshot SHA256 `38c49ca5123ffc9883762d637084dba9ed77adbdd5f3e86287a1ace565f763de`.
103
+ `prepare/p5-final/accepted.json` retains the boundary; native durable raw is
104
+ `/tmp/smoke-pi-durable-fresh-live-2026-10-06T23-27-34-983Z`. Immediate P9 reported
105
+ unused198/eligible6/328K, protected Emacs only; no deletion/signal.
106
+ - **Fresh-eyes documentation alignment:** independent Grok review found one
107
+ underclaim bundle, not a false green: operator docs still called accepted native
108
+ admission pending and a direction note pointed to a retired branch handoff.
109
+ After the final P5 verdict, documentation-only commit `87de9f7` aligns those states
110
+ with DELIVERY/#129 and distinguishes seven citizen backends from six fresh-openable
111
+ ones. Bounded independent re-review closed the Defect (Blocker0/Defect0); normal
112
+ hooks/shared scanner passed, and main fast-forwarded locally before release-prep. Executable/gate/model/pin/manifest bytes are unchanged; no LIVE Claude ↔
113
+ durable or registry/runtime provision is inferred. Review artifacts live in
114
+ `.tmp-verify/031-release/grok-fresh-review/` and `docs-amendment/`.
115
+ - **Release acceptance is pending:** prep commit/prepared-SHA CI and preserved
116
+ artifact remain outstanding. No final candidate, tag, GitHub release or npm
117
+ publication is asserted here.
118
+
7
119
  ## 0.30.1 - 2026-10-04
8
120
 
9
121
  ### Fixed
package/DELIVERY.md CHANGED
@@ -116,6 +116,7 @@ physical host. Do not read a D-level cell as a Darwin receipt.
116
116
  | **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. |
117
117
  | **Copilot CLI garden citizen** | shipped in 0.15.0; send + receive + visible fresh accepted on one host | D6; D7 partial; D3 pending; D8 unproven | Birth, garden id, MCP hand and record-backed sender identity are accepted; the RECEIVER is an installed first-party extension that binds to the V3 record, writes a receiver marker owned by the WATCHER pid, and rings a doorbell the model drains with `entwurf_inbox_read`. `wakeMode` is `self-fetch`, so dispatch reaches the mailbox rail: armed → delivered, unarmed/stale → the honest `mailbox-undeliverable` refusal. **D6 is the owned-invocation LIVE acceptance of 2026-08-23** — garden `20260823T181316-d9f6ba`, native `20fe30c8-b2bc-4600-91a0-8a409131be51`, CLI 1.0.80: receive log `joined`→`armed`→`doorbell fresh=1`→`rang`, mailbox `lastEnqueuedAt 09:23:41.235Z` / `lastReadAt 09:23:56.480Z`, and a model reply on the same record/native/gid chain. **Visible fresh (step 9 clause 7) is a separate LIVE, 2026-08-25** — launch window `@89`/`%89` nonce `mux-fresh-call-690529ae99f99faa2252aefb`; exact-callback garden `20260825T085721-f68be0`; one `entwurf_v2` → `meta-mailbox → enqueued`; same garden `lastReadAt 2026-08-24T23:57:47.784Z` plus same-gid reply; GLG saw footer garden id and a healthy multi-turn window. Those rows stay unmerged. D7 is PARTIAL: reply and read receipt were observed, the completion taxonomy and long-haul operation were not. D3 (second-session isolation of an owned invocation) is PENDING — observed once, decisive log lost to scratch cleanup. Evidence level L4: one host. Launch through the owned invocation `entwurf copilot`, which sets `COPILOT_CLI_ENABLED_FEATURE_FLAGS=EXTENSIONS` for that one process; `doctor-copilot-receive` reads live CLI environments because a session launched without it is silently inert. Visible fresh is operator-metered and is not a release-gate MUST. |
118
118
  | **OMP (`omp`) garden citizen** | shipped in 0.16.0; send + receive + visible fresh accepted on one host | D6; D3 proven; D7 partial; D8 partial | Two in-process extensions in the operator's own TUI: birth mints the `mode === "tui"` host (subagents mint nothing) and names it as sender; the RECEIVER unit joins that citizen in the SAME process, holds an `fs.watch` on its mailbox signal, and rings an announce-only doorbell through the vendor's `pi.sendUserMessage` — measured to be on the FACTORY object, not the event ctx, and measured to start a turn on an idle host with zero typing (`agent_start` +31ms). `wakeMode` is `self-fetch`: the model drains with `entwurf_inbox_read` and THAT read is the receipt. **D6 is the LIVE acceptance of 2026-08-30** (oracle, omp 18.0.0) — garden `20260830T140819-116f6a`, `lastEnqueuedAt 05:08:20.555Z` / `lastReadAt 05:08:23.958Z`, and the citizen's own transcript carrying `mcp__entwurf_bridge_entwurf_inbox_read` for its own garden id. **D3 is PROVEN, not pending**: with two live omp citizens armed, one addressed enqueue rang exactly one doorbell and the sibling persisted no transcript and kept an empty mailbox. D7 is PARTIAL (`lastReadAt` needs no scraping; the reply does). D8 is PARTIAL — dedupe and every stale-handling path are implemented and hermetically pinned, ordering/loop-guard/crash-recovery are not. The `/new` unarm is the rail-specific one: the watch lives in the operator's TUI, so pid + start-key cannot see a citizen change underneath a living process, and without an explicit unarm the previous garden id would keep reading deliverable. Requires `tools: xdev: false` in `~/.omp/agent/config.yml` — the vendor default hides MCP tool schemas from the prompt, so the doorbell would name a tool the model cannot call. The decisive receipt lines — the ordering probe, the D6 chain, the D3 isolation and the `/new` unarm — are pasted into `scripts/raw-omp-measure/README.md` §M7 rather than left in a host-local `/tmp` path. Evidence level L4: one Linux host, ARM. **Visible fresh (step 9) is ACCEPTED — the clause 7 LIVE went green on 2026-08-30:** `entwurf_fresh_call` opens omp on all three public surfaces through the bare `omp` runtime with an explicit `--approval-mode yolo` width and the `mcp__entwurf_bridge_entwurf_v` callback name, behind a five-axis pre-mutation preflight whose fifth axis is omp-specific (`tools.xdev !== true`, without which the model cannot call the callback tool at all). **The first turn is a TWO-STAGE BOOTSTRAP rather than a positional prompt, and that is a measured correction, not a preference.** `[LIVE 2026-08-30]` the positional-prompt candidate opened its window and minted its citizen (garden `20260830T181342-452167`, native `01a051f2-3107-7147-8806-fa2a6f527610`), delivered the byte-identical framing as a user message at `09:13:42.413Z`, and the model answered the literal text `ACK` at `09:13:47.105Z` with ZERO tool calls; the caller timed out at 240s. `[source]` the interactive UI defers MCP discovery (`sdk.ts:1847-1855`, `:1881-1905`) while the positional `initialMessage` prompts straight after `mode.init()` (`main.ts:540-565`), and `[측정]` an observer on the same runtime saw `turn_start` at +654ms with the entwurf tools absent and the callback tool present only at +1484ms. So the launcher now carries `{v,target,nonce,task}` on the fixed registered flag `--entwurf-bootstrap`, and the in-process birth extension polls `getAllTools`(`source:"mcp"`) AND `getActiveTools` for the exact callback name, sends a callback-ONLY prompt, arms the task only on a `tool_result` whose stored `toolCallId`, tool name, target, nonce and `isError === false` all match, and DELIVERS it at the next `turn_end` of that same session. `[LIVE 2026-08-30]` that last boundary is itself a measured correction: a first attempt sent the task from inside the `tool_result` handler with an explicit `deliverAs: "followUp"`, the hook log showed the full chain (`bootstrap-armed` → `bootstrap-ready` +440ms → `bootstrap-callback-observed` → `bootstrap-released`), and the task still never appeared in the session — `[source]` an explicit `deliverAs` queues without starting a turn in either state, while the omitted form starts one when idle (`agent-session.ts:6511-6513`), which the same transcript confirmed three seconds later when the Bundle B doorbell's omitted-option send landed and started a turn. `[측정 2026-08-30]` the callback-only half is what was proven to work: model `openai-codex/gpt-5.6-sol`, tool live at +1105ms, prompt injected at +1107ms, and the sibling calling `mcp__entwurf_bridge_entwurf_v` with the exact nonce (`omp-cb-btkvva4r87` → `20260830T184054-1aa1f2`, `meta-mailbox → enqueued`). **The clause 7 acceptance, `smoke-omp-fresh-live` (release-gate MUST), 2026-08-30, 21 assertions, omp 18.0.0 / `openai-codex/gpt-5.6-sol`:** launch through `tools/call entwurf_fresh_call` with nonce `mux-fresh-call-34faea228f3c3638684b40ff`; sibling garden `20260830T192913-df52b9` taken from the CALLBACK's sender envelope and never from the launch receipt; exactly one new citizen (§3.5); addressed `entwurf_v2` → `meta-mailbox → enqueued`; `lastEnqueuedAt 10:29:19.179Z` / `lastReadAt 10:29:24.673Z`; the drain visible in that session's own transcript for its own garden id. **Both bootstrap stages are in that transcript in order** — the callback-only prompt at offset 970, the caller's task at 4732, no four-backend positional framing anywhere, and an assistant message after the task proving it started a turn rather than sitting in a queue. The unit's own log carries the state machine: `bootstrap-armed 10:29:13.399Z` → `bootstrap-ready 10:29:13.819Z` (+420ms) → `bootstrap-callback-observed 10:29:18.663Z` (`toolCallId=call_KZJtkxfzy1IEE4ejKutCPWcm|fc_0858…`) → `bootstrap-released 10:29:18.731Z` → `bootstrap-task-sent 10:29:18.759Z`. Evidence level L4: one Linux host, ARM, one run. `check-harness-admission-parity` keeps the deterministic half of the admission from being a matter of memory. What this does NOT establish: multi-host, multi-model, or repeated fresh calls in one process. **Cross-harness leg, 2026-08-31 (first bidirectional live-turn roundtrip on the committed bytes + operator redeploy):** a claude-code citizen (`20260831T090152-f998ac`) opened a fresh omp sibling through the public surface (nonce `mux-fresh-call-807cb693b1913ce98ffdbfd1`, callback sender garden `20260831T124226-eac41a`), dispatched an addressed `entwurf_v2` → `meta-mailbox → enqueued`, and the omp citizen's own live turn drained and replied into the claude mailbox — its self-report (doorbell → `entwurf_inbox_read`; callback-only first user message, task second) agrees with every receipt on the chain. |
119
+ | **pi-durable native host** | 0.31.0 candidate; visible fresh + addressed receive + both cross-harness legs accepted on one Linux host (2026-10-06, `smoke-pi-durable-fresh-live`, 36 assertions, exit 0) | D6 (D3/D4 shown); D7 partial; D8 unproven | The operator-provided pinned source runtime (`cd32f77` + overlay) launched by the managed `entwurf pi-durable --provider … --model … --width task-wide`; the contact registers six tools and spawns the compiled bridge as a direct child; receive is announce-only self-fetch (doorbell → root submission → `entwurf_inbox_read`). Receipt (V on both sides, `openai-codex/gpt-6.1-sol`): real Pi `20261006T223710-e51e21` opened pd `20261006T223720-ba0c08` through `entwurf_fresh_call`; pd's first action called back into Pi with the exact nonce; Pi's live turn → pd mailbox ENQUEUE `2026-10-06T13-37-35-446Z-b57c69.msg`, that message's doorbell submission `done` on pd's root conversation, drained by pd's live turn (`entwurf_inbox_read` of its own id, settled); pd's live turn → Pi `entwurf_v2` settled `control-socket → sent` (native tool-result, joined by call id) and landed as exactly one Pi row with the expected payload and pd as the anchored sender. Native offering (root `pi.system`, toolsAdded/toolsRemoved applied): the six contact tools + bash/read/edit/write/subagent; pd's first root tool call is the callback. Actual model from root `pi.assistant` rows; the generation checkpoint does not retain `request.model` after completion. |
119
120
  | **Copilot CLI TUI+server** — *withdrawn lane, kept as evidence* | rejected | D7; D8 unproven | Older official-SDK probe over hidden `--ui-server`; idle enqueue worked, but loopback RPC authentication was not established. The bundled extension supersedes this candidate without reviving it. |
120
121
  | **ACP Claude / Cortex** | shipped runtime, outside this matrix | — | ACP sessions are children launched by entwurf's pi adapter, not already-running native sessions to wake. |
121
122
 
package/README.md CHANGED
@@ -27,6 +27,15 @@ Claude and Cortex Code are ACP backends. Codex and Antigravity stay native so th
27
27
  retain their own tools and work context. Capabilities differ by rail: a shipped
28
28
  citizen is not a promise of every lifecycle operation.
29
29
 
30
+ **Native durable contact (`pi-durable`) is accepted in the 0.31.0 candidate (#129).**
31
+ It is a separate native harness contact, not ordinary Pi's control-socket adapter.
32
+ The contact attaches one durable host to a garden id, adds visible identity and
33
+ two-way mailbox contact, while the upstream harness keeps its TUI, models/auth,
34
+ SQLite storage and recovery. First native admission passed on one Linux host;
35
+ release/artifact acceptance is separate. The
36
+ [installation requirements](#optional-native-durable--0310) differ from installing
37
+ the ordinary Pi CLI.
38
+
30
39
  ## Install
31
40
 
32
41
  **Choose by where you work. Neither route installs a harness, subscription or login.**
@@ -35,7 +44,7 @@ citizen is not a promise of every lifecycle operation.
35
44
 
36
45
  Prerequisites: **Node >=24.0.0**, Python 3 for `setup`, and any harnesses you choose
37
46
  to use, already installed and authenticated. Pi is optional-by-presence, supported
38
- range `>=1.0.0 <1.1`; Claude Code supported floor `>=2.1.217` is required for its
47
+ range `>=1.0.2 <1.1`; Claude Code supported floor `>=2.1.217` is required for its
39
48
  managed exec-hook lifecycle. pnpm is needed only for source-checkout setup.
40
49
 
41
50
  ```bash
@@ -63,6 +72,34 @@ Detected harness wiring on Darwin can therefore be written while setup stays
63
72
  non-green. Native Windows is **UNSUPPORTED**. The package has no npm `os`
64
73
  restriction; that is not certification of every platform's harness rails.
65
74
 
75
+ ### Optional native durable — 0.31.0
76
+
77
+ **Entwurf's npm installation does not change. Enabling durable additionally needs
78
+ an operator-provided native runtime; installing ordinary Pi from npm is not enough.**
79
+
80
+ | Component | How it is supplied | What it does not supply |
81
+ |---|---|---|
82
+ | Entwurf | Existing npm or source-checkout route above | A harness binary, source runtime, subscription or login |
83
+ | `@earendil-works/pi-durable` | npm **library** | The experimental app/TUI or Entwurf citizen attachment |
84
+ | Native experimental durable app | Pinned upstream **source checkout**, with Entwurf's narrow runtime-contact overlay | A replacement ordinary Pi CLI or an Entwurf-owned harness |
85
+
86
+ This lane retains Pi 1.0.2/source `cd32f77`; a newer global Pi installation does
87
+ not provision that experimental runtime. Existing non-durable users do not need
88
+ a Pi source checkout. Native auth and sessions stay upstream-owned: Entwurf does
89
+ not copy credentials or convert ordinary Pi sessions into durable SQLite sessions.
90
+
91
+ The package ships the compiled adapter and bootstrap: `entwurf setup` reports the
92
+ operator runtime as PASS/SKIP/FAIL without writing durable wiring, and
93
+ `entwurf pi-durable` is the managed launch. First native admission passed on
94
+ 2026-10-06: visible fresh/callback, addressed receive and real Pi ↔ pi-durable
95
+ LIVE36 on one Linux host ([receipts](./DELIVERY.md)). Installed-bootstrap import
96
+ proof does not supply or certify the native runtime; npm installation alone is
97
+ not a durable readiness verdict. #129 closed at reviewed main; release acceptance
98
+ remains its own floor.
99
+ See [setup: native durable](./docs/setup-clean-host.md#2b-optional-native-durable-pi-durable)
100
+ and [#130](https://github.com/junghan0611/entwurf/issues/130) for runtime-supply,
101
+ Pi-upgrade, ACP and UI follow-ups.
102
+
66
103
  ### Herdr workbench
67
104
 
68
105
  ```bash
@@ -182,14 +219,17 @@ and [Herdr launch](https://github.com/junghan0611/entwurf/blob/main/docs/herdr-l
182
219
  Instead of keeping native pi copies of the verbs, the adapter registers the same
183
220
  compiled bridge after record birth, under the citizen's identity, and Pi supervises
184
221
  that MCP child. **MCP is ingress; pi receive remains the record-addressed Unix
185
- control socket.** Garden identity and dispatch stay Entwurf's. What `pi-durable`
186
- or Pi codemode should mean here remains undecided: this cut neither adopts nor
187
- certifies them. The 0.30.0 follow-up separately measured Herdr supply 0.9.3 and
222
+ control socket.** Garden identity and dispatch stay Entwurf's. That 0.30.0 cut
223
+ did not adopt or certify `pi-durable` or codemode. Native durable support is now
224
+ accepted as a separate 0.31.0 candidate contact, as described above; codemode remains a
225
+ harness-internal capability, not another garden citizen. The 0.30.0 follow-up
226
+ separately measured Herdr supply 0.9.3 and
188
227
  re-pinned the plugin runtime to published npm 0.30.0 with manifest 0.30.0; its
189
228
  public consumer and CI receipts live in the #126 thread. That dated alignment is
190
- not a permanent version-equality rule. Unpublished 0.30.1 uses the closed
191
- `herdr-checkout` verification carrier; the [plugin source contract](./plugins/herdr/README.md)
192
- owns the measured npm re-pin and source-specific proof owed after publication.
229
+ not a permanent version-equality rule. 0.30.1 is published (npm, 2026-10-04); at its tag the
230
+ plugin's runtime lock is still the closed `herdr-checkout` verification carrier. Publication did
231
+ not re-pin it: the measured npm re-pin and its source-specific proof are separate, still-open
232
+ work that the [plugin source contract](./plugins/herdr/README.md) owns.
193
233
 
194
234
  Entwurf is the 0.12+ successor to
195
235
  [`@junghanacs/pi-shell-acp`](https://www.npmjs.com/package/@junghanacs/pi-shell-acp):
package/VERIFY.md CHANGED
@@ -200,6 +200,22 @@ that moved out of `scripts/` and into a file beside its subject would otherwise
200
200
 
201
201
  Do not collapse source, package, fixture, and native-host evidence into one “green.”
202
202
 
203
+ **Native durable contact (#129).** `check-pi-durable-beside` is the deterministic
204
+ beside-test/mutant coordinate in `check:contracts`. The separate
205
+ `check-pi-durable-contact`, `check-pi-durable-send` and `check-pi-durable-receive`
206
+ are CHECKOUT-ONLY SDK/contact gates: they require the provisioned pinned overlay
207
+ runtime (`ENTWURF_PI_DURABLE_RUNTIME`) and a private compiled bridge emitted from
208
+ this candidate (`ENTWURF_PI_DURABLE_BRIDGE_ENTRY`). Missing inputs report SKIP 97,
209
+ never PASS; present invalid inputs fail. Installed packages refuse these developer
210
+ gates. Contact is adapter-direct/harness-bypassed; send/receive exercise scripted
211
+ loopback S plus actual native harness H, not vendor V. Their scoped raw records,
212
+ wire, native view and SQLite oracles do not certify every queue/crash window.
213
+ The installed-bootstrap import in `check-pack-install` proves package closure,
214
+ not runtime supply or native admission. First-release visible fresh/callback and
215
+ native caller/target/cross-harness LIVE are owed by `smoke-pi-durable-fresh-live`, a
216
+ release-gate MUST (LIVE=1; vendor V on a real Pi and a real pi-durable host, operator
217
+ runtime required); SDK or package green does not waive it.
218
+
203
219
  | Axis | Required proof | Limit |
204
220
  |---|---|---|
205
221
  | Source | `pnpm run check:full` + `./run.sh check-gate-qualification` | Does not prove an installed consumer. |
@@ -290,7 +306,7 @@ The goal is not merely "invoke Claude Code." We want:
290
306
  **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.
291
307
 
292
308
  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`)
293
- 2. pi wiring → `<project>/.pi/settings.json` + user-scope registration — only when a `pi` inside the supported range (`>=1.0.0 <1.1`) 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
309
+ 2. pi wiring → `<project>/.pi/settings.json` + user-scope registration — only when a `pi` inside the supported range (`>=1.0.2 <1.1`) 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
294
310
  3. Claude meta-bridge global plugin — only when `claude` is on PATH; otherwise skipped cleanly
295
311
  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
296
312
  5. agy bridge + exact permission + statusline + `PreInvocation` hook — only when `agy` is on PATH; each adapter is idempotent and independently doctorable
package/demo/README.md CHANGED
@@ -142,7 +142,7 @@ SCENE_DELAY=30 FINAL_PAUSE=10 bash demo.sh
142
142
 
143
143
  ## Prerequisites
144
144
 
145
- - `pi` on PATH (current floor 1.0.0)
145
+ - `pi` on PATH (current floor 1.0.2)
146
146
  - `entwurf` provider configured + auth ready for the selected sender/peer models
147
147
  - `asciinema` installed
148
148
  - `agg` installed (optional — only for GIF conversion)
@@ -134,7 +134,7 @@ ledger, 2026-10-03).
134
134
  | Surface | Declaration | Class | What a green actually says |
135
135
  |---|---|---|---|
136
136
  | 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 |
137
- | pi runtime | devDep exact `1.0.0`, peer `>=1.0.0 <1.1` | **exact** oracle + **closed range** | the source pin this checkout builds and gates against; hosts inside the range are accepted, and the ceiling moves only on measurement. Shipped in 0.30.0 (`[측정 2026-10-04]` npm `latest` `0.30.0` declares this same peer range); its acceptance record is CHANGELOG 0.30.0, with #125 as the branch evidence |
137
+ | pi runtime | devDep exact `1.0.2`, peer `>=1.0.2 <1.1` | **exact** oracle + **closed range** | the source pin this checkout builds and gates against; hosts inside the range are accepted, and the ceiling moves only on measurement. Unshipped candidate floor: the published 0.30.0 and 0.30.1 declare floor 1.0.0 with the same next-minor ceiling (`[측정 2026-10-05]` `npm view @junghanacs/entwurf@0.30.1 peerDependencies`; acceptance record CHANGELOG 0.30.0, #125 branch evidence). The 1.0.2 move and its receipts are ROADMAP's 2026-10-05 bump entry |
138
138
  | ACP wire SDK | `@agentclientprotocol/sdk 1.6.0` | **exact** | the shared wire oracle both adapters speak |
139
139
  | Claude ACP adapter | `@agentclientprotocol/claude-agent-acp 0.85.1` | **exact**, bundled | checkout candidate; resolved before any PATH fallback; declaration is not compatibility evidence |
140
140
  | Claude Agent SDK | `0.3.286` (transitive) | **exact** oracle | the runtime risk surface behind the adapter |
@@ -28,7 +28,8 @@ evidence split, and its three states must not collapse into "macOS is supported"
28
28
  | npm/pnpm | npm is bundled with Node; pnpm is required for source setup | package or source installation |
29
29
  | Python 3 | required by `setup`/`install` (project-path normalization + settings writers); `--help`/`check-bridge` stay Python-free | pi/Claude/agy/Copilot/Codex wiring writers |
30
30
  | entwurf | global/project-local `@junghanacs/entwurf`, or a source checkout | operator command and garden capability |
31
- | pi | optional-by-presence, `>=1.0.0 <1.1` — absent is an explicit setup SKIP, below-floor is a named FAIL | ACP provider, control sockets |
31
+ | pi | optional-by-presence, `>=1.0.2 <1.1` — absent is an explicit setup SKIP, below-floor is a named FAIL | ACP provider, control sockets |
32
+ | Native durable (`pi-durable`) | optional-by-presence, operator-provided experimental source runtime at the fixed `…/entwurf/pi-durable/runtime`, pinned to `cd32f77` plus the narrow runtime-contact overlay; not supplied by ordinary Pi npm installation — absent is an explicit setup SKIP, present-but-not-the-pin a named FAIL | optional 0.31.0 candidate contact via `entwurf pi-durable`; first native Pi ↔ pi-durable admission/LIVE accepted on one Linux host, runtime provision and release acceptance separate (§2b) |
32
33
  | Claude Code | optional, **`>=2.1.217`** — the exec-form hook floor | Claude ACP auth/runtime and mailbox-backed native citizen |
33
34
  | GitHub Copilot CLI | optional-by-presence, operator-installed and authenticated — absent is an explicit setup SKIP; detected composes all four units (birth/MCP/receiver/footer) | self-fetch citizen and visible fresh |
34
35
  | OMP (`omp`) | optional-by-presence, operator-installed — absent is an explicit setup SKIP; detected composes all four units (birth/MCP/`tools.xdev` setting/receiver) | self-fetch citizen and visible fresh (accepted on one host — see §4b) |
@@ -190,7 +191,7 @@ on Darwin; that is wiring, not a rail receipt. If `pi` is on PATH,
190
191
  Install the exact release floor, then wire the project:
191
192
 
192
193
  ```bash
193
- npm install -g @earendil-works/pi-coding-agent@1.0.0
194
+ npm install -g @earendil-works/pi-coding-agent@1.0.2
194
195
  pi --version
195
196
 
196
197
  cd ~/entwurf-smoke
@@ -198,7 +199,7 @@ entwurf install .
198
199
  pi -e "$(npm root -g)/@junghanacs/entwurf" --list-models entwurf
199
200
  ```
200
201
 
201
- The supported range is `>=1.0.0 <1.1`. It is a hard minimum: installing this
202
+ The supported range is `>=1.0.2 <1.1`. It is a hard minimum: installing this
202
203
  release onto a 0.83.x pi host upgrades the runtime rather than keeping the older
203
204
  minor. A host using only the external MCP bridge can skip pi until it needs a
204
205
  control socket; no delivery rail launches a pi process.
@@ -282,6 +283,105 @@ emacsclient -s "${PI_EMACS_AGENT_SOCKET:-server}" --eval '(...)'
282
283
 
283
284
  </details>
284
285
 
286
+ ## 2b. Optional native durable (`pi-durable`)
287
+
288
+ **0.31.0 installation contract; first native admission/LIVE accepted on one Linux host.**
289
+ The [DELIVERY matrix](../DELIVERY.md) records the 2026-10-06 LIVE36 candidate
290
+ acceptance; release/artifact and runtime-supply evidence remain separate.
291
+ Durable is a separate native harness contact, not an option automatically enabled
292
+ by `entwurf pi` or by registering ordinary Pi's adapter.
293
+
294
+ There are three different things to install or supply:
295
+
296
+ 1. **Entwurf:** the existing global/project-local npm or source-checkout route in
297
+ §1 stays unchanged. It supplies Entwurf bytes, not the native harness.
298
+ 2. **Durable library:** `@earendil-works/pi-durable` is npm-installable. A library
299
+ dependency does not install the experimental coding app/TUI or birth a citizen.
300
+ 3. **Native app/runtime:** upstream's experimental durable app runs from a Pi
301
+ source checkout through its source resolver. The pinned 1.0.2 ordinary
302
+ `pi-coding-agent` package excludes experimental app output; upgrading the global
303
+ `pi` CLI does not supply it. Entwurf's maintained contact requires the exact
304
+ upstream commit `cd32f7725fdbddbaecdff5b1e68491563394e0ca` plus its narrow
305
+ runtime-contact overlay, operator-provided dependencies and pinned runtime data.
306
+
307
+ The upstream [library installation guide](https://github.com/earendil-works/pi/blob/cd32f7725fdbddbaecdff5b1e68491563394e0ca/packages/durable/README.md#installation)
308
+ and [experimental app guide](https://github.com/earendil-works/pi/blob/cd32f7725fdbddbaecdff5b1e68491563394e0ca/packages/coding-agent/src/experimental/durable/README.md)
309
+ describe different consumers. Running the bare upstream app does not attach
310
+ Entwurf's garden contact. Entwurf does not install a second harness distribution,
311
+ rebuild its controller/model/auth/storage system or proxy a subscription.
312
+
313
+ ### What the operator supplies
314
+
315
+ The runtime lives at ONE fixed place, with no path override:
316
+ `$XDG_DATA_HOME/entwurf/pi-durable/runtime` (an absolute `XDG_DATA_HOME`; otherwise
317
+ `~/.local/share/entwurf/pi-durable/runtime`). It counts as the pin only when:
318
+
319
+ - HEAD is `cd32f7725fdbddbaecdff5b1e68491563394e0ca`, nothing is staged, `git diff`
320
+ equals the packaged overlay `pi/pi-durable/overlay/runtime-contacts.patch` byte for
321
+ byte, and no non-ignored file is untracked;
322
+ - the upstream source resolver
323
+ `packages/coding-agent/src/experimental/source-resolver.ts` is present;
324
+ - the git-ignored model data in `packages/ai/src/providers/data` matches the pinned
325
+ digest: the bytes `@earendil-works/pi-ai@1.0.2` ships in `dist/providers/data`,
326
+ **including its `.manifest.json` dotfile** (recorded in
327
+ `pi/pi-durable/overlay/upstream-pin.json`).
328
+
329
+ The operator supplies it by hand; Entwurf never downloads, installs, repairs,
330
+ moves or deletes the runtime. The sequence below was run on a Linux host
331
+ (2026-10-06); afterwards the runtime verifier `entwurf setup` consults returned
332
+ the pin (exit 0) and the checkout's native contact cells passed against it.
333
+ `ENTWURF_ROOT` is your Entwurf package root or checkout, which ships the patch:
334
+
335
+ ```bash
336
+ case "${XDG_DATA_HOME:-}" in
337
+ /*) R="$XDG_DATA_HOME/entwurf/pi-durable/runtime" ;;
338
+ *) R="$HOME/.local/share/entwurf/pi-durable/runtime" ;;
339
+ esac
340
+ git clone --no-checkout https://github.com/earendil-works/pi.git "$R"
341
+ git -C "$R" checkout --detach cd32f7725fdbddbaecdff5b1e68491563394e0ca
342
+ git -C "$R" apply "$ENTWURF_ROOT/pi/pi-durable/overlay/runtime-contacts.patch" # worktree only; do not stage
343
+ (cd "$R" && npm ci --ignore-scripts) # upstream lockfile; dependency install scripts skipped
344
+ T=$(mktemp -d)
345
+ (cd "$T" && npm pack @earendil-works/pi-ai@1.0.2)
346
+ tar -xzf "$T/earendil-works-pi-ai-1.0.2.tgz" -C "$T" package/dist/providers/data
347
+ cp -a "$T/package/dist/providers/data/." "$R/packages/ai/src/providers/data/" # dotfiles too
348
+ entwurf setup # expect: pi-durable PASS
349
+ ```
350
+
351
+ Why it is a source checkout and not `npm install`: `@earendil-works/pi-coding-agent`
352
+ (1.0.2 and 1.0.4 alike) excludes `dist/experimental` from its `files`, and its only
353
+ experimental export resolves to `src/`, which the tarball does not ship, so the
354
+ durable app's `experimental/durable/runtime.ts` and `tui.ts` exist only in a source
355
+ checkout. `@earendil-works/pi-durable` on npm is the library (no app, TUI or bin).
356
+
357
+ ### What Entwurf does with it
358
+
359
+ - `entwurf setup` reports a `pi-durable` row from that check and writes no durable
360
+ wiring in any case: **PASS** when the runtime is the pin, **SKIP** when nothing is
361
+ at the fixed place, **FAIL** with the named refusal when something is there but is
362
+ not the pin.
363
+ - `entwurf pi-durable [args…]` is the managed launch. It verifies the same runtime
364
+ first (refusal exit 4 when absent, 3 otherwise), then runs the packaged bootstrap
365
+ `pi/pi-durable/bootstrap.mjs` under the runtime's source resolver, owning this
366
+ terminal. The bootstrap imports only the compiled closure shipped beside it
367
+ (`mcp/entwurf-bridge/dist/…`), so an installed package never imports checkout
368
+ TypeScript; a source checkout needs a built bridge (`pnpm run build-bridge`) first.
369
+ `plugins/pi-durable/bootstrap.mjs` is only the checkout path that re-exports it.
370
+
371
+ Existing users who do not enable durable keep the same installation steps. For
372
+ durable, native Pi owns credentials, model settings, SQLite sessions and recovery;
373
+ keep them in place rather than copying credentials or migrating ordinary Pi
374
+ transcripts. Native recovery does not prove exactly-once Entwurf dispatch or inbox
375
+ read recovery. The contact provides garden attachment, visible identity and
376
+ announce-only root-directed self-fetch. First native admission closed
377
+ [#129](https://github.com/junghan0611/entwurf/issues/129) at reviewed main:
378
+ visible fresh/callback, addressed receive and both real Pi ↔ pi-durable LIVE legs
379
+ passed on one Linux host. Installed-bootstrap import-only proof is not a native
380
+ runtime/TUI launch or a live Claude ↔ pi-durable receipt. This section states the
381
+ installation contract; exact release/artifact acceptance is a separate floor.
382
+ [#130](https://github.com/junghan0611/entwurf/issues/130) owns npm-only runtime
383
+ supply, later Pi versions, ACP and UI follow-ups.
384
+
285
385
  ## 3. Optional Claude Code native citizen
286
386
 
287
387
  This rail is CERTIFIED on Linux; on macOS it is NOT CERTIFIED — pending physical
@@ -445,7 +545,7 @@ as a note rather than a fault.
445
545
 
446
546
  **entwurf sets no OMP version floor in code, and will not grow one on schedule.** Detection is
447
547
  presence-only (`command -v "${OMP_BIN:-omp}"`); there is no `entwurf.ompFloor`, no coherence
448
- gate, and no exact pin — unlike Node (`engines.node`), pi (`>=1.0.0 <1.1`) and Claude Code
548
+ gate, and no exact pin — unlike Node (`engines.node`), pi (`>=1.0.2 <1.1`) and Claude Code
449
549
  (`entwurf.claudeCodeFloor`), each of which has an enforcement point. That asymmetry is a
450
550
  decision, not an omission. A floor is the answer to a vendor that fails SILENTLY — Claude Code
451
551
  earned one because an older binary validates the exec manifest, drops `args` at runtime, and
@@ -630,8 +630,8 @@ server.tool("entwurf_register_native", "Register an ALREADY-RUNNING native conve
630
630
  server.tool("entwurf_fresh_call", "Open ONE fresh visible sibling beside you and hand it a first task. WHERE it opens is decided by where THIS " +
631
631
  "agent runs, never by a parameter: inside herdr (HERDR_ENV=1) it opens a NEW UNFOCUSED TAB in your own " +
632
632
  "herdr workspace, pi and claude-code " +
633
- "only; everywhere else it opens in the operator's tmux with all five backends (pi, " +
634
- "claude-code, copilot, omp, codex). No fallback: an incomplete herdr context is refused by name, " +
633
+ "only; everywhere else it opens in the operator's tmux with all six backends (pi, " +
634
+ "claude-code, copilot, omp, codex, pi-durable). No fallback: an incomplete herdr context is refused by name, " +
635
635
  "never by a tmux window you cannot see from herdr. The sibling's FIRST action " +
636
636
  "is a callback to you carrying a nonce, whose sender envelope is its garden id — that is how you learn the " +
637
637
  "address of a thing that did not exist a moment ago. This returns a LAUNCH receipt (the owner's " +
@@ -639,8 +639,8 @@ server.tool("entwurf_fresh_call", "Open ONE fresh visible sibling beside you and
639
639
  "the task was delivered. Those coordinates are a VIEW, never an address — a herdr tab/pane id can change under " +
640
640
  "a running sibling. Nothing polls. " +
641
641
  "For EXISTING citizens use entwurf_v2 — this tool only creates, and entwurf_peers only reports. Model is REQUIRED and passed to the chosen " +
642
- "runtime CLI (`provider/model` for pi, an id/alias for Claude Code, a pattern for the rest). On tmux, copilot/omp/codex are refused BEFORE any window " +
643
- "opens when their birth, MCP, receive or visible-identity units are absent, and codex also needs the " +
642
+ "runtime CLI (`provider/model` for pi, an id/alias for Claude Code, `provider/model id` exactly as the catalog names it for pi-durable, a pattern for the rest). On tmux, copilot/omp/codex/pi-durable are refused BEFORE any window " +
643
+ "opens when their birth, MCP, receive or visible-identity units are absent, pi-durable also when its operator-provided pinned runtime is absent or drifted, and codex also needs the " +
644
644
  "operator-owned app-server socket entwurf never starts; in herdr they are refused by name first. " +
645
645
  "Optional placement.tmuxSession is a TMUX-ONLY seat naming ONE EXISTING session and " +
646
646
  "ALWAYS wins; omitted, the seat follows the CALLER, never the backend opened: a CODEX CALLER opens beside " +
@@ -648,8 +648,8 @@ server.tool("entwurf_fresh_call", "Open ONE fresh visible sibling beside you and
648
648
  "NOTHING is created. In herdr the field is refused by name. Do not put secrets in the task — model and task argv are visible to same-user " +
649
649
  "processes.", {
650
650
  backend: z
651
- .enum(["pi", "claude-code", "copilot", "omp", "codex"])
652
- .describe("Which fixed runtime to open. Only these five, and only pi/claude-code when this agent runs inside herdr; there is no arbitrary command."),
651
+ .enum(["pi", "claude-code", "copilot", "omp", "codex", "pi-durable"])
652
+ .describe("Which fixed runtime to open. Only these six, and only pi/claude-code when this agent runs inside herdr; there is no arbitrary command."),
653
653
  model: z
654
654
  .string()
655
655
  .min(1)
@@ -663,7 +663,7 @@ server.tool("entwurf_fresh_call", "Open ONE fresh visible sibling beside you and
663
663
  // as an unused suppression while the escape goes back to being fixable.
664
664
  // biome-ignore lint/complexity/noUselessEscapeInRegex: emitted to a Rust regex validator, see above
665
665
  .regex(/^[A-Za-z0-9][A-Za-z0-9._/:\[\]-]*$/)
666
- .describe("Required runtime model: canonical provider/model for pi, a Claude Code model id/alias, or a Copilot/OMP/Codex model name."),
666
+ .describe("Required runtime model: canonical provider/model for pi, provider/<exact catalog model id> for pi-durable, a Claude Code model id/alias, or a Copilot/OMP/Codex model name."),
667
667
  task: z
668
668
  .string()
669
669
  .min(1)
@@ -6,6 +6,7 @@
6
6
  "codex": { "wakeMode": "direct-inject", "deliveryLevel": "D6", "nativeIdLabel": "threadId" },
7
7
  "copilot": { "wakeMode": "self-fetch", "deliveryLevel": "D6", "nativeIdLabel": "sessionId" },
8
8
  "omp": { "wakeMode": "self-fetch", "deliveryLevel": "D6", "nativeIdLabel": "sessionId" },
9
+ "pi-durable": { "wakeMode": "self-fetch", "deliveryLevel": "D2", "nativeIdLabel": "sessionId" },
9
10
  "pi": { "wakeMode": "direct-inject", "deliveryLevel": "D6", "nativeIdLabel": "sessionId" }
10
11
  }
11
12
  }
@@ -105,7 +105,13 @@ export function receiverMarkerMatchesIdentity(marker, identity) {
105
105
  // previous garden in-process on its `/new` edge (the unarm the claude hook lacked),
106
106
  // so the cell this join closes has a different owner there. Admitting it needs its
107
107
  // own measurement, not this file's optimism.
108
- export const SENDER_JOINED_RECEIVER_OWNER_KINDS = ["claude-code-cli"];
108
+ //
109
+ // `pi-durable-host` is admitted because the join holds by construction: the durable contact
110
+ // writes its sender marker under the host's own pid and arms its watch in that same process
111
+ // (`meta-bridge-pi-durable.ts`), so the receiver marker's `ownerPid` IS the sender-marker key.
112
+ // Inside the scope a host whose sender marker is gone or names another garden is a retired
113
+ // watch, which is the refusal this admission exists to make reachable.
114
+ export const SENDER_JOINED_RECEIVER_OWNER_KINDS = ["claude-code-cli", "pi-durable-host"];
109
115
  /** Does this watch owner share its pid with the backend's sender marker? */
110
116
  export function receiverOwnerKindJoinsSender(ownerKind) {
111
117
  return SENDER_JOINED_RECEIVER_OWNER_KINDS.includes(ownerKind);
@@ -23,11 +23,12 @@
23
23
  */
24
24
  import { randomBytes } from "node:crypto";
25
25
  /** The backends this rail can open. Fixed set, not a profile — a further one is a decision,
26
- * not a config entry. `copilot` was added by #82 RAIL 9, `omp` by #87 Bundle C, and `codex`
27
- * by #95 after its system birth and app-server rails were measured. The set is joined to the
26
+ * not a config entry. `copilot` was added by #82 RAIL 9, `omp` by #87 Bundle C, `codex`
27
+ * by #95 after its system birth and app-server rails were measured, and `pi-durable` by #129
28
+ * (the operator-provided durable app through the managed `entwurf pi-durable`). The set is joined to the
28
29
  * citizen backends by `check-harness-admission-parity`: a harness that mints records but is
29
30
  * missing HERE is not an unwired convenience, it is a release blocker. */
30
- export const FRESH_CALL_BACKENDS = ["pi", "claude-code", "copilot", "omp", "codex"];
31
+ export const FRESH_CALL_BACKENDS = ["pi", "claude-code", "copilot", "omp", "codex", "pi-durable"];
31
32
  /**
32
33
  * The callback tool NAME differs per backend and that is not cosmetic: every session reaches the
33
34
  * capability under whatever name its harness composes for the `entwurf-bridge` MCP server, and
@@ -64,6 +65,11 @@ export const FRESH_CALL_CALLBACK_TOOL = {
64
65
  // Codex's tool process is the operator app-server, not the pane: the no-arg
65
66
  // verb refuses there by name. First action stays the delivery verb with args.
66
67
  codex: "mcp__entwurf_bridge__entwurf_v2",
68
+ // The durable contact registers the bridge verbs as its OWN tools under their bare names, and the
69
+ // app offers a tool to the model by its registered name (`durable/harness/registry.ts:99`
70
+ // @cd32f77). Measured for `entwurf_v2` on the S+native-H wire (receipt 4012c066); the callback
71
+ // spelling follows the same rule and is owed its first LIVE observation.
72
+ "pi-durable": "entwurf_callback",
67
73
  };
68
74
  /** Where a sibling SENDS the task result. Always the delivery verb, never the birth callback. */
69
75
  export const FRESH_CALL_DELIVERY_TOOL = {
@@ -72,6 +78,7 @@ export const FRESH_CALL_DELIVERY_TOOL = {
72
78
  copilot: "entwurf-bridge-entwurf_v2",
73
79
  omp: "mcp__entwurf_bridge_entwurf_v",
74
80
  codex: "mcp__entwurf_bridge__entwurf_v2",
81
+ "pi-durable": "entwurf_v2",
75
82
  };
76
83
  /**
77
84
  * The same dialects, for the tool the framing OFFERS rather than requires.
@@ -93,6 +100,7 @@ export const FRESH_CALL_PEERS_TOOL = {
93
100
  copilot: "entwurf-bridge-entwurf_peers",
94
101
  omp: "mcp__entwurf_bridge_entwurf_peers",
95
102
  codex: "mcp__entwurf_bridge__entwurf_peers",
103
+ "pi-durable": "entwurf_peers",
96
104
  };
97
105
  /**
98
106
  * ONE BACKEND NEEDS TO BE TOLD HOW TO REACH ITS OWN CALLBACK TOOL `[측정 2026-09-18, n=5 + n=3]`.
@@ -132,6 +140,7 @@ export const FRESH_CALL_TOOL_LOAD_HINT = {
132
140
  copilot: [],
133
141
  omp: [],
134
142
  codex: [],
143
+ "pi-durable": [],
135
144
  };
136
145
  /**
137
146
  * The omp bootstrap flag, spelled WITHOUT dashes — the vendor's flag map is keyed by bare
@@ -311,6 +320,28 @@ export function composeBackendArgs(backend, composition, model, resolveCodexSock
311
320
  "--dangerously-bypass-approvals-and-sandbox",
312
321
  composition.prompt,
313
322
  ];
323
+ case "pi-durable": {
324
+ // The managed verb (`entwurf pi-durable`) carries the explicit model as pi's own
325
+ // `--provider`/`--model` pair, split at the FIRST `/` (the preflight already refused a model
326
+ // with no provider), the explicit width, and the WHOLE framing as the closed `{v,task}`
327
+ // payload — the durable bootstrap submits it to the root once, after the birth. The
328
+ // caller's address rides the env pair, never this payload.
329
+ const at = model.indexOf("/");
330
+ if (at <= 0 || at === model.length - 1) {
331
+ throw new Error(`fresh-call composition: pi-durable model ${JSON.stringify(model)} is not <provider>/<model id>`);
332
+ }
333
+ return [
334
+ "pi-durable",
335
+ "--provider",
336
+ model.slice(0, at),
337
+ "--model",
338
+ model.slice(at + 1),
339
+ "--width",
340
+ "task-wide",
341
+ `--${OMP_BOOTSTRAP_FLAG}`,
342
+ JSON.stringify({ v: OMP_BOOTSTRAP_VERSION, task: composition.prompt }),
343
+ ];
344
+ }
314
345
  }
315
346
  }
316
347
  /**
@@ -57,7 +57,13 @@ import { resolveNativePushAdapter } from "./native-push/adapter.js";
57
57
  * exactly the #46 defect on agy, and copilot joined the list only once its own hook wrote one
58
58
  * (#82 RAIL 5b). Membership says a marker may EXIST, never that a reply can land: the reply
59
59
  * rail is chosen from `nativePushSupported` at the bridge, not from this list. */
60
- export const META_SENDER_BACKENDS = ["claude-code", "antigravity", "copilot", "omp"];
60
+ export const META_SENDER_BACKENDS = [
61
+ "claude-code",
62
+ "antigravity",
63
+ "copilot",
64
+ "omp",
65
+ "pi-durable",
66
+ ];
61
67
  /**
62
68
  * Two live native sessions claim this MCP process as their own. We can SEE both identities but
63
69
  * cannot say which one is calling, so we send under neither. Choosing by lookup order, backend