@junghanacs/entwurf 0.45.0 → 0.51.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 (116) hide show
  1. package/AGENTS.md +5 -3
  2. package/BASELINE.md +110 -1
  3. package/CHANGELOG.md +249 -0
  4. package/CONTRIBUTING.md +1 -1
  5. package/ENTWURF-CLI.json +1229 -0
  6. package/ENTWURF.json +63 -15
  7. package/FAQ.md +4 -4
  8. package/README.md +132 -188
  9. package/VERIFY.md +18 -11
  10. package/demo/README.md +1 -1
  11. package/docs/acp-backend-rail.md +1 -1
  12. package/docs/external-mcp-host.md +4 -4
  13. package/docs/setup-clean-host.md +94 -22
  14. package/mcp/entwurf-bridge/dist/ENTWURF.json +63 -15
  15. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +19 -8
  16. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-contract.js +4 -4
  17. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-visible-resume.js +483 -18
  18. package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-dispatch.js +47 -10
  19. package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-fresh-call.js +613 -33
  20. package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-resume-call.js +497 -0
  21. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js +3 -1
  22. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-resume-call.js +50 -5
  23. package/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-fresh-preflight.js +7 -1
  24. package/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-location.js +347 -0
  25. package/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-owner-fence.js +1132 -0
  26. package/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-runtime.js +1 -0
  27. package/mcp/entwurf-bridge/dist/pi-extensions/lib/resume-launch-identity.js +18 -11
  28. package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-pi-durable.js +30 -15
  29. package/mcp/entwurf-bridge/dist/scripts/herdr-rails.mjs +40 -7
  30. package/mcp/entwurf-bridge/dist/scripts/meta-bridge-fresh-cut.js +1 -27
  31. package/mcp/entwurf-bridge/dist/scripts/meta-bridge-prune.js +1 -1
  32. package/mcp/entwurf-bridge/dist/scripts/meta-facts.js +1 -1
  33. package/mcp/entwurf-bridge/dist/scripts/peer-facts.js +1 -1
  34. package/mcp/entwurf-bridge/src/index.ts +28 -8
  35. package/mcp/entwurf-bridge/tsconfig.build.json +7 -1
  36. package/native/win32-x64/manifest.json +1 -1
  37. package/package.json +16 -20
  38. package/pi/pi-durable/bootstrap.mjs +179 -6
  39. package/pi/pi-durable/carrier/runtime.js +327 -57
  40. package/pi/pi-durable/carrier/session-name.js +11 -0
  41. package/pi/pi-durable/carrier/sessions.js +96 -20
  42. package/pi/pi-durable/carrier/subagent.js +5 -2
  43. package/pi/pi-durable/carrier/tui.js +60 -22
  44. package/pi/pi-durable/overlay/upstream-pin.json +26 -19
  45. package/pi-extensions/lib/entwurf-v2-contract-schema.ts +73 -69
  46. package/pi-extensions/lib/entwurf-v2-contract.ts +4 -4
  47. package/pi-extensions/lib/entwurf-v2-visible-resume.ts +710 -38
  48. package/pi-extensions/lib/fresh-call-dispatch.ts +64 -12
  49. package/pi-extensions/lib/herdr-fresh-call.ts +919 -40
  50. package/pi-extensions/lib/herdr-resume-call.ts +810 -0
  51. package/pi-extensions/lib/meta-session.ts +13 -8
  52. package/pi-extensions/lib/mux-resume-call.ts +77 -4
  53. package/pi-extensions/lib/pi-durable-fresh-preflight.ts +9 -2
  54. package/pi-extensions/lib/pi-durable-location.ts +446 -0
  55. package/pi-extensions/lib/pi-durable-owner-fence.ts +1461 -0
  56. package/pi-extensions/lib/pi-durable-runtime.ts +4 -0
  57. package/pi-extensions/lib/resume-launch-identity.ts +31 -12
  58. package/pi-extensions/meta-bridge-pi-durable.ts +30 -16
  59. package/run.sh +552 -699
  60. package/scripts/check-claude-launch.ts +341 -0
  61. package/scripts/check-entwurf-v2-contract.ts +4 -4
  62. package/scripts/check-entwurf-v2-surface.ts +2 -2
  63. package/scripts/check-fresh-call-dispatch.ts +70 -9
  64. package/scripts/check-fresh-cut-gate.sh +23 -9
  65. package/scripts/check-gate-qualification.ts +19 -14
  66. package/scripts/check-herdr-activation.ts +301 -23
  67. package/scripts/check-herdr-fresh-call.ts +161 -25
  68. package/scripts/check-herdr-plugin-build.ts +173 -1
  69. package/scripts/check-herdr-plugin.ts +28 -2
  70. package/scripts/check-herdr-runtime-bootstrap.ts +136 -0
  71. package/scripts/check-install-surface.ts +1236 -6
  72. package/scripts/check-meta-doctor-oracle.sh +1 -1
  73. package/scripts/check-meta-facts.ts +20 -1
  74. package/scripts/check-mux-resume-call.ts +28 -0
  75. package/scripts/check-peer-facts.ts +39 -2
  76. package/scripts/cli-help.mjs +222 -0
  77. package/scripts/dev-bin.sh +39 -2
  78. package/scripts/herdr-activation.mjs +28 -6
  79. package/scripts/herdr-operator-link.mjs +389 -0
  80. package/scripts/herdr-plugin-activate.mjs +5 -0
  81. package/scripts/herdr-plugin-deactivate.mjs +18 -5
  82. package/scripts/herdr-rails.mjs +43 -7
  83. package/scripts/herdr-runtime.mjs +27 -0
  84. package/scripts/meta-bridge-fresh-cut.ts +1 -27
  85. package/scripts/meta-bridge-prune.ts +1 -1
  86. package/scripts/meta-facts.ts +1 -1
  87. package/scripts/mutants/agent-guidance.json +4 -4
  88. package/scripts/mutants/claude-launch.json +142 -0
  89. package/scripts/mutants/cli-help-source.json +420 -0
  90. package/scripts/mutants/control-send-receipt.json +1 -1
  91. package/scripts/mutants/dev-gate-dispatch.json +109 -0
  92. package/scripts/mutants/fresh-call-dispatch.json +38 -2
  93. package/scripts/mutants/fresh-cut.json +15 -0
  94. package/scripts/mutants/herdr-activation.json +128 -0
  95. package/scripts/mutants/herdr-fresh-call.json +278 -5
  96. package/scripts/mutants/herdr-operator-link.json +318 -0
  97. package/scripts/mutants/herdr-plugin-build.json +55 -0
  98. package/scripts/mutants/herdr-plugin.json +17 -0
  99. package/scripts/mutants/herdr-resume-call.json +288 -0
  100. package/scripts/mutants/herdr-runtime-bootstrap.json +48 -0
  101. package/scripts/mutants/meta-facts.json +15 -0
  102. package/scripts/mutants/mux-resume-call.json +63 -2
  103. package/scripts/mutants/pack-install.json +4 -4
  104. package/scripts/mutants/peer-facts.json +15 -0
  105. package/scripts/mutants/pi-durable.json +1167 -3
  106. package/scripts/mutants/pi-floor.json +22 -5
  107. package/scripts/mutants/source-install.json +35 -0
  108. package/scripts/mutants/v2-surface.json +2 -2
  109. package/scripts/mutants/v2-visible-resume.json +255 -6
  110. package/scripts/peer-facts.ts +1 -1
  111. package/scripts/smoke-agy-install-state.sh +66 -0
  112. package/scripts/smoke-meta-prune.sh +23 -0
  113. package/scripts/smoke-omp-fresh-live.ts +12 -3
  114. package/scripts/smoke-omp-receive-live.ts +7 -1
  115. package/scripts/smoke-pi-durable-fresh-live.ts +12 -2
  116. package/team.json.example +39 -0
@@ -23,10 +23,11 @@ evidence split, and its three states must not collapse into "macOS is supported"
23
23
 
24
24
  ## Requirements
25
25
 
26
- This table is the published `@junghanacs/entwurf@0.41.2`'s, which ships the Pi 1.4.0 SDK set.
26
+ This table and the 1.4-specific install examples below describe the published `@junghanacs/entwurf@0.50.0`, which ships the Pi 1.4.0 SDK set. The #155 0.51.0 preparation at `434874ef6d6e399a0ae6813c850d01c5232eb1d9` pins SDK 1.5.0; its separate validation TGZ was installed in the approved operating window, but is not the still-unpublished final shipping artifact. npm publication requires its own authority. See the [SDK 1.5 boundary](#sdk-15-development-boundary-155) below; these 0.50 instructions do not select or prove 0.51 bytes.
27
27
  The previous package, `0.41.0`, ships the Pi 1.3.0 SDK set and requires Pi 1.3.x; the setup guide
28
28
  inside that tarball is its contract.
29
29
 
30
+ <!-- entwurf:published @junghanacs/entwurf@0.50.0 pi 1.4.0 -->
30
31
  | Component | Requirement | Needed for |
31
32
  |---|---|---|
32
33
  | Node | **`>=24.0.0`** | package and bridge runtime |
@@ -41,6 +42,7 @@ inside that tarball is its contract.
41
42
  | Antigravity `agy` | optional, operator-installed and authenticated | legacy citizen; native push paused (#152) |
42
43
  | OpenAI Codex CLI | optional-by-presence, operator-installed and authenticated; detected composes all four operator-owned units (birth/MCP/status-line/terminal-title) and stays non-green until the operator trusts the birth declaration once in a visible Codex; visible fresh also requires the operator-owned app-server, started with `entwurf codex-app-server` in a tmux session of the operator's choosing (#95 D1 retired the fixed `codex` home) | native-push and visible fresh, supported in 0.21.0 |
43
44
  | Cortex Code | optional, operator-installed and authenticated | Cortex ACP backend |
45
+ <!-- /entwurf:published -->
44
46
 
45
47
  Claude Code >=2.1.306 is required for the managed exec-hook lifecycle and same-id resume. The package
46
48
  never supplies or proxies backend credentials.
@@ -83,14 +85,16 @@ node --version
83
85
  npm --version
84
86
  ```
85
87
 
86
- The two npm forms below install the **published** package, `0.41.2` (Pi 1.4.x, the Pi 1.4.0 SDK
87
- set).
88
+ The two npm forms below install the **published** package, `0.50.0` (Pi 1.4.x, the Pi 1.4.0 SDK
89
+ set). The [product entry](../README.md#install)'s unpinned `npm install -g @junghanacs/entwurf`
90
+ takes whichever version npm's `latest` dist-tag names; pin `@<version>` as below when you mean the
91
+ exact artifact the [Requirements](#requirements) table describes.
88
92
 
89
93
  Global install is simplest when native harnesses should find stable bins from every
90
94
  working directory:
91
95
 
92
96
  ```bash
93
- npm install -g @junghanacs/entwurf@0.41.2
97
+ npm install -g @junghanacs/entwurf@0.50.0
94
98
  entwurf --help
95
99
  entwurf setup /path/to/your-project
96
100
  entwurf check-bridge
@@ -101,7 +105,7 @@ A project-local installation is also supported:
101
105
  ```bash
102
106
  mkdir -p ~/entwurf-smoke && cd ~/entwurf-smoke
103
107
  npm init -y
104
- npm install --save-dev @junghanacs/entwurf@0.41.2
108
+ npm install --save-dev @junghanacs/entwurf@0.50.0
105
109
  npx entwurf setup .
106
110
  npx entwurf check-bridge
107
111
  ```
@@ -111,7 +115,7 @@ project-local install, or `./run.sh <command>` from a source checkout. Keep that
111
115
  same scope when upgrading and checking; native process restarts are separate (§7).
112
116
 
113
117
  `check-bridge` is auth-free. It proves the installed prebuilt MCP server boots and
114
- lists the nine garden tools (0.41.2); it does not prove a backend model turn, native hook or retirement permission.
118
+ lists the nine garden tools (0.50.0); it does not prove a backend model turn, native hook or retirement permission.
115
119
 
116
120
  Neither npm form installs a harness runtime. `pi`, Claude Code, Copilot CLI, Codex CLI, agy,
117
121
  Cortex, and their authentication remain operator-owned optional prerequisites for the
@@ -226,6 +230,7 @@ Install the exact release floor, then wire the project. The previous 0.41.0 inst
226
230
  Pi 1.3.x its own setup guide names. A source checkout passes its checkout path to `-e` in place of
227
231
  the global package root:
228
232
 
233
+ <!-- entwurf:published @junghanacs/entwurf@0.50.0 pi 1.4.0 -->
229
234
  ```bash
230
235
  npm install -g @earendil-works/pi-coding-agent@1.4.0
231
236
  pi --version
@@ -239,6 +244,7 @@ The supported range is `>=1.4.0 <1.5`. It is a hard minimum: installing this
239
244
  release onto a 0.83.x pi host upgrades the runtime rather than keeping the older
240
245
  minor. A host using only the external MCP bridge can skip pi until it needs a
241
246
  control socket; no delivery rail launches a pi process.
247
+ <!-- /entwurf:published -->
242
248
 
243
249
  For daily garden-native pi sessions:
244
250
 
@@ -257,7 +263,7 @@ A plain `pi` can load the package without becoming a citizen.
257
263
  In that session ask for `entwurf_self`, then `entwurf_peers`. Pick an existing
258
264
  live garden id and ask for `entwurf_v2` with `intent: "fire-and-forget"` and your
259
265
  message. These are harness tools, not shell subcommands; pi exposes them through
260
- Pi's built-in MCP as `mcp__entwurf_bridge__<verb>`. The 0.41.2 bridge has nine tools;
266
+ Pi's built-in MCP as `mcp__entwurf_bridge__<verb>`. The 0.50.0 bridge has nine tools;
261
267
  pi hides `entwurf_inbox_read` and `entwurf_register_native`, leaving seven active.
262
268
  The added `entwurf_retire_call` is only for one target/action explicitly requested by the
263
269
  human operator, never autonomous cleanup. Where a host provides or is configured with an approval
@@ -270,6 +276,10 @@ Tool-call ingress is MCP; delivery to a pi citizen is still its record-addressed
270
276
  control address (a Unix socket; a named pipe on the experimental native-Windows Pi
271
277
  preview). A boot/listing from `check-bridge` is not a model-turn or
272
278
  native-rail acceptance.
279
+ The server key stays `entwurf-bridge`. An operator/trusted-project MCP entry with
280
+ that normalized namespace can replace the registration by Pi's own precedence;
281
+ `/mcp` shows it, and Entwurf does not scan or fight that choice.
282
+ Codemode is harness-internal, not another garden citizen.
273
283
 
274
284
  To inspect the ACP provider from a project-local package instead of a global one:
275
285
 
@@ -338,11 +348,11 @@ by `entwurf pi` or by registering ordinary Pi's adapter.
338
348
 
339
349
  What the Entwurf package supplies:
340
350
 
341
- 1. **The pi SDK set** — `@earendil-works/{pi-coding-agent, pi-ai, pi-tui, pi-agent-core,
342
- pi-durable, chord, pi-mcp, pi-codemode, pi-telemetry}` at exactly `1.4.0`, plus
343
- `proper-lockfile`, as Entwurf's own production dependencies (declared once in
344
- `pi/pi-durable/overlay/upstream-pin.json` `sdk`). Model data rides the published
345
- `pi-ai`; there is no catalog copy.
351
+ 1. **The pi SDK set** — in the 0.51.0 prepared checkout, `@earendil-works/{pi-coding-agent, pi-ai, pi-tui, pi-agent-core,
352
+ pi-durable, chord, pi-mcp, pi-codemode, pi-telemetry, pi-schema}` at exactly `1.5.0`, plus
353
+ `proper-lockfile`, as Entwurf's own production dependencies. The authoritative current set is
354
+ the [SDK pin](../pi/pi-durable/overlay/upstream-pin.json) and [package manifest](../package.json);
355
+ for this candidate, model data rides `@earendil-works/pi-ai@1.5.0`, not a catalog copy.
346
356
  2. **The native app/TUI carrier** — upstream does not publish the experimental durable
347
357
  app (`pi-coding-agent` excludes `dist/experimental`; `pi-durable` on npm is the
348
358
  library). Entwurf owns the six app files as canonical source (`pi/pi-durable/src`,
@@ -352,7 +362,7 @@ What the Entwurf package supplies:
352
362
  (`pi/pi-durable/carrier-resolver.mjs`), which maps them to the published package's
353
363
  own `dist/` as installed — never through a source checkout.
354
364
 
355
- The upstream [library guide](https://github.com/earendil-works/pi/blob/v1.4.0/packages/durable/README.md)
365
+ The upstream [library guide](https://github.com/earendil-works/pi/blob/v1.5.0/packages/durable/README.md)
356
366
  and the [experimental app guide](https://github.com/earendil-works/pi/blob/v1.1.0/packages/coding-agent/src/experimental/durable/README.md)
357
367
  (last published at v1.1.0) describe what the carrier is. Running the bare upstream app does not attach Entwurf's
358
368
  garden contact. Entwurf does not rebuild the controller/model/auth/storage system or
@@ -367,8 +377,7 @@ carrier file, the license, the resolver and its relocation grammar are present
367
377
  (`pi-durable-carrier-drift`); every declared SDK package resolves from the carrier
368
378
  (`pi-durable-sdk-absent`); and every reachable physical edge — from the carrier to each package,
369
379
  and from every package copy it reaches, at any depth, to its own `@earendil-works` dependencies —
370
- lands on a package of the declared name at exactly the pinned version (`pi-durable-sdk-mismatch`). The members declare one another with caret
371
- ranges, so semver satisfaction is not the contract; the bytes an import binds are. Sharing
380
+ lands on a package of the declared name at exactly the pinned version (`pi-durable-sdk-mismatch`). The members' declarations are exact in the prepared 1.5.0 set; even so, the physical package each import binds must be checked. Sharing
372
381
  one copy with an aligned host install of the same version is an ordinary layout. A mixed
373
382
  tree (for example an override that binds pi-durable's `pi-ai` to an older published version) still
374
383
  *imports* — which is why the edge check exists — and is refused by name.
@@ -385,15 +394,28 @@ tree (for example an override that binds pi-durable's `pi-ai` to an older publis
385
394
  entwurf pi-durable --provider <provider> --model <exact catalog id> --width task-wide
386
395
  entwurf pi-durable --continue # this directory's newest durable session
387
396
  entwurf pi-durable --resume # choose one of this directory's durable sessions
397
+ entwurf pi-durable --session <native id> # reopen exactly that session of this directory
388
398
  ```
389
399
 
400
+ `--continue`, `--resume` and `--session` reopen a session as it was saved: they take no
401
+ `--provider`, `--model`, `--width` or `--entwurf-bootstrap` and are mutually exclusive.
402
+ `--session` takes the native session id (`<13-digit ms>-<uuid>`, as `--resume` lists it), not a
403
+ garden id, with no interactive session picker or picker-specific TTY check (the normal TUI still
404
+ runs); a value of any other shape is refused as `session-id-malformed` before your module is
405
+ evaluated, the TUI loads or the app opens. To retain an operator module on a CLI reopen, pass
406
+ its original explicit `--native-module <absolute file>` or `--native-module-dir <absolute directory>`
407
+ again; garden-id `entwurf_resume_call` is bridge-only and does not restore either ingress or
408
+ the prior seat. It opens on the caller's launch rail; check for an existing owner before opening
409
+ another instance. Close the old owner normally and prove its exit before reopening; for an SDK transition, back up
410
+ the complete durable SQLite file set before the first write ([upgrade boundary](#default-radius-for-a-requested-pi-sdk-upgrade)). Ordinary Pi transcripts are separate JSONL files.
411
+
390
412
  The bootstrap imports only the compiled closure shipped beside it
391
413
  (`mcp/entwurf-bridge/dist/…`); a source checkout needs a built bridge
392
414
  (`pnpm run build-bridge`) and its development dependencies installed first.
393
415
  `plugins/pi-durable/bootstrap.mjs` is only the checkout path that re-exports it.
394
416
 
395
- - **One explicit native module (optional).** Add `--native-module <absolute file>` to either
396
- launch to load ONE module of your own. It is an ordinary ES module: its top-level code (top-level
417
+ - **One explicit native module (optional).** Add `--native-module <absolute file>` to any of these
418
+ launches to load ONE module of your own. It is an ordinary ES module: its top-level code (top-level
397
419
  await included) runs first, before the TUI and the durable runtime load, so it can prepare the
398
420
  environment the provider runtime and tool children will read; its default export is a native
399
421
  durable `Extension` (`{ name, tools?, sections?, hooks?, … }`, the upstream durable API, not
@@ -437,7 +459,7 @@ tree (for example an override that binds pi-durable's `pi-ai` to an older publis
437
459
  manifest field; it is a known constraint of supplying both from one package, accepted for 0.32,
438
460
  not a claim that every load path is harmless.
439
461
 
440
- The ordinary Pi CLI on PATH is a separate subject (§2, range `>=1.4.0 <1.5`): a host Pi is
462
+ The ordinary Pi CLI on PATH is a separate subject (§2, range <!-- entwurf:published @junghanacs/entwurf@0.50.0 pi 1.4.0 -->`>=1.4.0 <1.5`<!-- /entwurf:published -->): a host Pi is
441
463
  not needed to run pi-durable, and a host inside the range need not be the same copy as the
442
464
  carrier's. Native Pi owns credentials, model settings and durable SQLite sessions; keep them
443
465
  in place rather than copying credentials or migrating ordinary Pi transcripts. Native
@@ -448,10 +470,49 @@ to the app's own model resolution; the checkout contact/send/receive gates run t
448
470
  with scripted S plus native H; Pi ↔ pi-durable LIVE on this supply surface is a separate release
449
471
  proof ([#130](https://github.com/junghan0611/entwurf/issues/130)).
450
472
 
473
+ ### SDK 1.5 development boundary (#155)
474
+
475
+ The published 0.50.0 / SDK 1.4.0 instructions above and the #155 SDK 1.5 development
476
+ candidates are distinct subjects. Synthetic migration and downgrade-refusal fixtures on `0408a66`
477
+ did not authorize an operating first write. A separately approved 1.5 operating window and Native
478
+ LIVE were later measured as described below; neither makes the validation TGZ the final release
479
+ artifact. Installing an older package is not a data rollback. Follow the current evidence in
480
+ [#155](https://github.com/junghan0611/entwurf/issues/155), not the 1.3→1.4 receipt below.
481
+
482
+ ### Default radius for a requested Pi SDK upgrade
483
+
484
+ **[Decision, 2026-11-03 (#155)](https://github.com/junghan0611/entwurf/issues/155#issuecomment-6595580400): reuse existing native authentication; do not make a fresh login or credential copy the default upgrade prerequisite.** An upgrade request selects this method without another authentication-strategy questionnaire. It does not authorize an immediate operating cutover, autonomous retirement, or publication.
485
+
486
+ The measured precedent is [#148's pre-cutover hold](https://github.com/junghan0611/entwurf/issues/148#issuecomment-6342034678) and [its actual Pi 1.4 operating reattachment](https://github.com/junghan0611/entwurf/issues/148#issuecomment-6348113761): isolated package/SDK checks first; no authentication-copy or mixed host/extension workaround; then normal owner exit, a consistent complete durable-session file-set backup, coherent candidate installation/takeover, same-identity reattachment and actual turns/round trips. Native aggregate acceptance followed the coherent installation. These are historical receipts, not evidence for the next SDK.
487
+
488
+ - **Proceed within the requested upgrade:** inspect upstream changes; isolate the development checkout from stable operating runtimes; pin the coherent SDK set; run private, authentication-free fixtures, focused checks, review and the required floors. Prepare the exact candidate, shutdown/reopen commands and backup manifest before the operating window.
489
+ - **Reuse at the coherent operating boundary:** the native harness uses its existing account/subscription authentication in its ordinary location. Its normal same-account token refresh is not a reason to demand another login. Entwurf does not extract, print, copy, transplant or proxy credentials, and does not introduce an auth-path shim. Keep the selected subscription/provider rail; no account, endpoint, scope or billing-rail substitution is implied.
490
+ - **Still coordinate the human-owned lifecycle window:** identify the exact operating owners and obtain the requested normal shutdown; prove old-owner exit and preserve the complete session directory consistently, including WAL/SHM, before the newer SDK opens operating data. Verify candidate, host, extension, bridge and SDK coherence, then reopen the same identities and measure actual model/tool/round-trip behavior. Authentication reuse is the default method, not permission to skip these gates or to force-close a working citizen.
491
+ - **Do not invent a pre-cutover exception:** if a real-model test cannot use the approved isolated surface without touching operating auth/data, defer it to that operating window. A separate private login or operating-agent-directory experiment is optional and needs a specific request; neither is a recurring prerequisite. Record the missing model/TUI evidence rather than claiming scripted S/H proves it.
492
+ - **Stop and report a real exception:** expired/revoked authentication needing login, changed account/permissions/endpoint or an unmeasured new external transfer, ownership conflict, inconsistent backup, unexpected write, or unknown result. No blind retry or automatic data rollback; downgrading the package does not undo a newer document write.
493
+
494
+ Push, release/tag/npm publication and retirement of any other citizen retain their separate authorization boundaries. This decision fixes the repeatable method; it does not pre-authorize those actions.
495
+
496
+ **Recurring SDK rail.** At task start, agree the exact SDK and the scope of development, review and verification once; do not repeatedly ask for the existing-auth strategy. Inspect the published SDK set and upstream changes, isolate the development checkout from operating runtimes, and implement against the coherent pinned set in private HOME, PI and writable XDG roots. Use the gate's own temporary-directory constraints: the #155 FULL's deeper scratch produced 117/109-byte socket paths and EINVAL; run3 used `TMPDIR=/tmp` with each gate creating its own private scratch and passed. Preserve both `pnpm_config_verify_deps_before_run=error` and `npm_config_verify_deps_before_run=error`, managed builds and candidate seals; do not run offline gates under ambient CI.
497
+
498
+ The owned canonical app/TUI source is production responsibility; the SDK owns the execution engine. In independent upgrade review, compare old and new app-visible payload semantics against known fixtures and a separate official SDK oracle: more than 50 active entries on reopen and same-process conversation switch, head/reset and background-compaction retention, and the distinction from model memory. API names, typechecks or a QK that only fixes the new SDK's window can all be green while older display compatibility regresses. When official emission changes a generated digest, check its pin-linked mutant find literal and the existing manifest inventory together; this is not a new gate.
499
+
500
+ Freeze for sequential independent review, then affected PARTIAL groups, frozen FULL and applicable scripted/native-H carrier cells. Accept the **same reviewed validation TGZ** in the Linux container and rehearse ownership refusal/takeover in isolation. Measure SDK publication age early to plan the window, then recheck it fresh before installation; never automatically exempt the global policy.
501
+
502
+ In the named operating window, identify owners, obtain their normal exit and prove it, then take a consistent complete session backup including WAL/SHM before the first new-SDK write. Install a coherent host and validation TGZ, verify installed global root (GR), runtime and ownership independently, and perform only the approved takeover. Reopen the same identities with their original explicitly named native modules and existing auth; observe real turns and round trips before the Native LIVE floor. Public pi-durable `entwurf_resume_call` is bridge-only: it does not save or restore operator native modules. If one is needed, use the original explicit CLI entry; do not infer a profile or kill a citizen automatically. One approval can cover the named scope, sibling roles and fixture work, but not bypass normal exit, the first-write decision, or separate push/npm authority.
503
+
504
+ For each named Native fixture, record the runtime root and SDK actually selected separately from the operator installation: a private PATH can select the genuine source CLI while an ordinary Pi extension still comes from an older installed GR. Do not substitute a wrapper or rewire the operating host to make the evidence agree. A planned source-CLI Native run is not a completed receipt.
505
+
506
+ **#155 measured example (2026-11-03; before the D1 display repair):** at 20:47 KST host Pi 1.5.0 and the unpublished, addon-less 0.51.0 validation TGZ (SHA-256 `199e5f41d20e5b2665125868b7e3e27740e5985f7a25ac61902126b7ed638bf5`) were installed, not the final publication artifact. Four owners exited normally; the 171-file durable backup's source-before/backup/source-after manifests matched (`5c08bed1968fd1e26baa58112f0ce5c36874e147ccc5e9b4c69ec1a0d9070a46`); two Pi transcripts were also backed up. All ten SDK members were freshly measured at 24.70–24.84 hours old, without a policy exception. The P5 Native LIVE cut recorded MUST 24/0/0 and BEHAVIOR 1/0/0 (pass/fail/skip) on its own sealed candidate, not the prepared SHA. Native-host criterion 4 separately measured a fresh Claude Code fixture's actual installed 0.51 MCP child and strict doctor; existing pre-cutover Claude sessions retained their loaded 0.50 bridge. Neither the 20:47 installation nor P5 proves that the later D1 display repair is operating: the D1-bearing artifact must first be installed, and the human must then restart the process; restarting against the earlier artifact still loads the old app. See [0.51.0 release evidence](../CHANGELOG.md#release-evidence) for scopes and receipts.
507
+
508
+ The Windows-bearing release TGZ, its Linux acceptance, exact prepared-SHA CI, installed-host acceptance and publication/registry proof are separate later evidence. Re-measure owners, loaded bytes, version/integrity, backup and candidate evidence on every upgrade; this ordering does not guarantee success for a future SDK.
509
+
510
+ For the separate land, prepare, make and publish authorities and the one accepted artifact, follow [entwurf-release](../.claude/skills/entwurf-release/SKILL.md). Push, tag, human-owned npm publication and the subsequent Herdr re-pin remain independently coordinated decisions. The current SDK-specific decisions and receipts belong to the active issue and handoff, not this reusable ordering.
511
+
451
512
  ### Moving durable sessions from the Pi 1.3 SDK to 1.4
452
513
 
453
- 0.41.1 and 0.41.2 are published on the 1.4.0 SDK set; moving a live host onto either remains a
454
- separate operator decision for each host. When you do:
514
+ 0.41.1, 0.41.2, 0.45.0 and 0.50.0 are published on the 1.4.0 SDK set; moving a live host onto any of them
515
+ remains a separate operator decision for each host. When you do:
455
516
 
456
517
  1. **Confirm the old owner is gone.** Close the 0.41 (SDK 1.3) durable session normally and check
457
518
  that its process has exited. A stale session lock alone is not proof.
@@ -558,7 +619,7 @@ entwurf install-meta-bridge
558
619
  entwurf doctor-meta-bridge
559
620
  ```
560
621
 
561
- The supported floor `>=2.1.306` is enforced by package metadata through installer
622
+ The Claude Code supported floor `>=2.1.306` is enforced by package metadata through installer
562
623
  and doctor gates. Older Claude versions validate an exec-form hook but silently drop
563
624
  its `args` at runtime, so there is no shell-form fallback.
564
625
 
@@ -568,6 +629,17 @@ validation, or hand-inspected marker cannot replace a new real session. If the l
568
629
  form is unsupported, reinstall; if ownership is correct but the live join is absent,
569
630
  restart the affected session.
570
631
 
632
+ Start a session as plain `claude` or as `entwurf claude [args…]`. The second execs the
633
+ `claude` on PATH in this terminal — same cwd, pid and exit status, signals reaching it directly,
634
+ arguments and `--help` unchanged — after removing an inherited `PI_SESSION_ID`/`PI_AGENT_ID` and
635
+ exporting only `ENTWURF_CLAUDE_LAUNCH_ACTIVE=1`. Unlike `entwurf copilot` it checks no Entwurf setup
636
+ or citizen precondition before launch — only that `claude` resolves to an executable file that
637
+ is not this launcher — and sets no feature flag: either spelling is a citizen through the hooks above,
638
+ and plain Claude Code without them. A launch whose environment already carries that sentinel
639
+ (inherited from an earlier `entwurf claude` up its process tree), or whose `claude` resolves to
640
+ Entwurf's own `run.sh`, refuses as a recursive managed launch instead of looping; its message
641
+ currently names only the PATH cause. Run plain `claude` there.
642
+
571
643
  The Darwin install fence is open (`Linux | Darwin`); python3 is the
572
644
  refusal, not the platform name. A completed Darwin install is still not
573
645
  a rail receipt: this rail is NOT CERTIFIED — pending physical host. The
@@ -701,7 +773,7 @@ as a note rather than a fault.
701
773
 
702
774
  **entwurf sets no OMP version floor in code, and will not grow one on schedule.** Detection is
703
775
  presence-only (`command -v "${OMP_BIN:-omp}"`); there is no `entwurf.ompFloor`, no coherence
704
- gate, and no exact pin — unlike Node (`engines.node`), pi (`>=1.4.0 <1.5`) and Claude Code
776
+ gate, and no exact pin — unlike Node (`engines.node`), pi (<!-- entwurf:published @junghanacs/entwurf@0.50.0 pi 1.4.0 -->`>=1.4.0 <1.5`<!-- /entwurf:published --> for published 0.50.0) and Claude Code
705
777
  (`entwurf.claudeCodeFloor`), each of which has an enforcement point. That asymmetry is a
706
778
  decision, not an omission. A floor is the answer to a vendor that fails SILENTLY — Claude Code
707
779
  earned one because an older binary validates the exec manifest, drops `args` at runtime, and
@@ -6,8 +6,56 @@
6
6
  "editing": "Preserve decoded strings exactly unless an explicit semantic amendment is intended. Review related consumers and independent oracles atomically. Accepted payload changes advance revision; history-only edits, routine releases and unrelated code changes do not. schemaVersion tracks reader/structural compatibility. A slotted hint holds only the text around its slot: the slot value (for record-less-socket, the fresh-cut command in both invocation forms) is supplied by its code owner and never copied here. Metadata is not delivered to models."
7
7
  },
8
8
  "schemaVersion": 6,
9
- "revision": 11,
9
+ "revision": 19,
10
10
  "history": [
11
+ {
12
+ "revision": 19,
13
+ "source": "2734f524bfeca6eb1a0ae092494d45576af9ebef",
14
+ "issue": "https://github.com/junghan0611/entwurf/issues/157",
15
+ "change": "Claude Code same-id resume on the herdr rail (#157 stage 4): both entwurf_resume_call descriptions open every resumable backend inside herdr in a new herdr tab and drop the Claude Code refusal there, the MCP description's Claude Code observation names the window or tab and herdr's pane-process-unread outcome, and the tmux leaf's herdr guard hint no longer says the herdr rail reopens pi only (stale since B3)"
16
+ },
17
+ {
18
+ "revision": 18,
19
+ "source": "43594eba588a51bdb9d58a4631e3ebc7b3a235ea",
20
+ "issue": "https://github.com/junghan0611/entwurf/issues/157",
21
+ "change": "pi-durable same-id resume, joint-review alignment (#157 B3): the entwurf_resume_call MCP description is rewritten within the host cap to state pi-durable eligibility (claim closed and released, or holder proven gone), its observation classes and that a held session is refused without unconditional delivery advice, since a pi-durable reservation awaiting adoption is not reachable by entwurf_v2; the durable description names that holder; the entwurf_v2 and entwurf_peers descriptions add pi-durable to the resume domain; the target-live and target-indeterminate visible-resume hints become holder-neutral. No wire value, argument, schema or reason identifier changes."
22
+ },
23
+ {
24
+ "revision": 17,
25
+ "source": "46c7c776c7fed9001be4498ea024cfd214f4347f",
26
+ "issue": "https://github.com/junghan0611/entwurf/issues/157",
27
+ "change": "Same-id visible resume names pi-durable (#157 B3): both entwurf_resume_call descriptions and its target parameter add pi-durable beside pi and Claude Code, the MCP description's OBSERVATION receipt names the pi-durable adoption of this call's reservation and both descriptions put pi-durable with pi inside herdr, and the target-backend-unsupported hint lists pi-durable among the backends that do resume. No wire value, argument, schema shape or reason identifier changes."
28
+ },
29
+ {
30
+ "revision": 16,
31
+ "source": "d6129139ad9738a3bc0ade9b5ef3abbac4b7580f",
32
+ "issue": "https://github.com/junghan0611/entwurf/issues/157",
33
+ "change": "The entwurf_fresh_call cwd parameter's last sentence names every source an echoed directory can come from: the REQUESTED path, a Codex caller's record directory, or another caller's own process directory, which its default clause already named (checkpoint amendment A1 D3). Every description and every other parameter are unchanged. No wire value, argument, schema or reason identifier changes."
34
+ },
35
+ {
36
+ "revision": 15,
37
+ "source": "d6129139ad9738a3bc0ade9b5ef3abbac4b7580f",
38
+ "issue": "https://github.com/junghan0611/entwurf/issues/157",
39
+ "change": "The entwurf_fresh_call MCP description restores four contract phrases that revision 14's host-cap rewrite dropped (entwurf_peers only reports, Model is REQUIRED, the CALLER, NOTHING is created), at 1748 characters under the 2048-character host description cap (A-L1 host-cap recovery). The durable description, every parameter and the other tools are unchanged. No wire value, argument, schema or reason identifier changes."
40
+ },
41
+ {
42
+ "revision": 14,
43
+ "source": "d6129139ad9738a3bc0ade9b5ef3abbac4b7580f",
44
+ "issue": "https://github.com/junghan0611/entwurf/issues/157",
45
+ "change": "The entwurf_fresh_call MCP description is rewritten to fit the 2048-character host description cap (A-L1 host-cap recovery): revision 13's 2182-character text becomes 1732 characters. The durable description, every parameter and the other tools are unchanged. No wire value, argument, schema or reason identifier changes."
46
+ },
47
+ {
48
+ "revision": 13,
49
+ "source": "d6129139ad9738a3bc0ade9b5ef3abbac4b7580f",
50
+ "issue": "https://github.com/junghan0611/entwurf/issues/157",
51
+ "change": "pi-durable opens inside herdr through one layout.apply request (A-L1): the entwurf_fresh_call MCP description says the herdr tab is requested unfocused, names pi and claude-code through agent start and pi-durable through a native layout command, and keeps the by-name herdr refusal for copilot/omp/codex only; the durable description's last sentence and the backend parameter name pi-durable among the herdr backends. No wire value, argument, schema or reason identifier changes."
52
+ },
53
+ {
54
+ "revision": 12,
55
+ "source": "0e04bf6e728aa14320decc056ec91e74727a8f2b",
56
+ "issue": "https://github.com/junghan0611/entwurf/issues/157",
57
+ "change": "Pi same-id visible resume gains a herdr rail (G1a): both entwurf_resume_call descriptions name the pi-only herdr tab and the Claude Code refusal there, the launch-refused hint no longer says only tmux was not asked, and the tmux leaf's herdr guard hint is narrowed to that leaf. No wire value, schema or reason identifier changes."
58
+ },
11
59
  {
12
60
  "revision": 11,
13
61
  "source": "d3963c2d16a93453baf7a318ad9395c57990531d",
@@ -78,7 +126,7 @@
78
126
  "tools": {
79
127
  "entwurf_v2": {
80
128
  "descriptions": {
81
- "mcp": "CANONICAL DELIVERY SURFACE for garden ids: message, reply, or hand off to whoever an id names. The id alone does not say which rail that citizen answers on. Give target + intent; the decider picks transport from liveness (live socket citizen → control-socket send; deliverable self-fetch citizen → meta-bridge mailbox; probe-alive native-push citizen → direct injection into its conversation) and reports ONE outcome, named per rail: a control receiver's acceptance boundary (`sent` / `queued-steer` / `queued-follow-up` / `accepted-unknown-boundary` — those four, or `mailbox-enqueued` on a re-resolved mailbox leg), a mailbox ENQUEUE receipt, a native-push INJECTION, a rejection, or lock-dirty. This receipt is not the colleague's reply or evidence of reading. EXISTING targets only; discover with entwurf_peers. A live socket can receive fire-and-forget. unsupported means only \"no control-socket probe,\" not reachability. The decider resolves the actual rail at dispatch time: a self-fetch mailbox only while deliverable, native-push direct injection only while its adapter probe is alive, or a THIRD RESULT: rejection when neither holds. An inactive self-fetch citizen is mailbox-undeliverable; native-push has NO mailbox: dead → native-push-target-dead; indeterminate → native-push-probe-indeterminate; agy (paused, #152) → native-push-paused, no probe. A dormant socket rejects this delivery verb as dormant-fire-forget-unsupported. owned-outcome was withdrawn. Only on an explicit operator request, entwurf_resume_call may visibly reopen a proven-dormant Pi or Claude Code citizen under the same id, submitting no prompt; that lifecycle is separate from delivery. LOCK: taken for a control-socket-DOMAIN dispatch. The mailbox and native-push rails are lock-free — deliverability and the adapter probe guard them. mode applies to a CONTROL-SOCKET send only; other plans carry no mode. wants_reply rides every rail. message caps at 16000 chars; send an artifact path + digest for more.",
129
+ "mcp": "CANONICAL DELIVERY SURFACE for garden ids: message, reply, or hand off to whoever an id names. The id alone does not say which rail that citizen answers on. Give target + intent; the decider picks transport from liveness (live socket citizen → control-socket send; deliverable self-fetch citizen → meta-bridge mailbox; probe-alive native-push citizen → direct injection into its conversation) and reports ONE outcome, named per rail: a control receiver's acceptance boundary (`sent` / `queued-steer` / `queued-follow-up` / `accepted-unknown-boundary` — those four, or `mailbox-enqueued` on a re-resolved mailbox leg), a mailbox ENQUEUE receipt, a native-push INJECTION, a rejection, or lock-dirty. This receipt is not the colleague's reply or evidence of reading. EXISTING targets only; discover with entwurf_peers. A live socket can receive fire-and-forget. unsupported means only \"no control-socket probe,\" not reachability. The decider resolves the actual rail at dispatch time: a self-fetch mailbox only while deliverable, native-push direct injection only while its adapter probe is alive, or a THIRD RESULT: rejection when neither holds. An inactive self-fetch citizen is mailbox-undeliverable; native-push has NO mailbox: dead → native-push-target-dead; indeterminate → native-push-probe-indeterminate; agy (paused, #152) → native-push-paused, no probe. A dormant socket rejects this delivery verb as dormant-fire-forget-unsupported. owned-outcome was withdrawn. Only on an explicit operator request, entwurf_resume_call may visibly reopen a proven-dormant Pi, pi-durable or Claude Code citizen under the same id, submitting no prompt; that lifecycle is separate from delivery. LOCK: taken for a control-socket-DOMAIN dispatch. The mailbox and native-push rails are lock-free — deliverability and the adapter probe guard them. mode applies to a CONTROL-SOCKET send only; other plans carry no mode. wants_reply rides every rail. message caps at 16000 chars; send an artifact path + digest for more.",
82
130
  "durable": "Send a message to an existing garden citizen by garden id through the entwurf-bridge (intent fire-and-forget). Returns the bridge's delivery receipt, or fails with its named rejection. This receipt is not the colleague's reply."
83
131
  },
84
132
  "parameters": {
@@ -98,7 +146,7 @@
98
146
  },
99
147
  "entwurf_peers": {
100
148
  "descriptions": {
101
- "mcp": "List the entwurf fact surface: garden citizens (from meta-records) with their liveness, plus diagnostics. The record is the sole address axis — a control socket no record claims is a `record-less-socket` diagnostic (a stale/diagnostic state), never a peer row. This reports FACTS, never verbs: `liveness` is a fact (alive/dead/indeterminate, or `unsupported` for a backend with no control-socket probe such as claude-code); the delivery choice or rejection is resolved LATER by entwurf_v2 from the record and current rail facts, not here — this surface carries no per-row routing field, so do not read a transport off a row. A `dead` socket row is a REPORTED FACT: no entwurf_v2 delivery is available while it is dormant. Listing it grants no action and is not an invitation to dispatch. Only on an explicit operator request, the separate entwurf_resume_call may visibly reopen a proven-dormant Pi or Claude Code citizen under the same id, submitting no prompt. In particular `unsupported` does NOT mean mailbox: it means this backend has no control-socket probe. Dispatch has THREE possible answers — a self-fetch mailbox (only while that mailbox is deliverable), native-push direct injection (only while its adapter probe is alive), or a REJECT when neither holds: mailbox-undeliverable for a self-fetch citizen whose inbox nobody drains, and on the native-push probe dead → native-push-target-dead vs indeterminate → native-push-probe-indeterminate, kept apart because an unestablished probe is not a departed host; an agy target rejects as native-push-paused (#152) without being probed. Note: this is the *active* world and it is facts-only — it creates nothing. Pass an existing garden id to entwurf_v2; to open a NEW sibling use entwurf_fresh_call.",
149
+ "mcp": "List the entwurf fact surface: garden citizens (from meta-records) with their liveness, plus diagnostics. The record is the sole address axis — a control socket no record claims is a `record-less-socket` diagnostic (a stale/diagnostic state), never a peer row. This reports FACTS, never verbs: `liveness` is a fact (alive/dead/indeterminate, or `unsupported` for a backend with no control-socket probe such as claude-code); the delivery choice or rejection is resolved LATER by entwurf_v2 from the record and current rail facts, not here — this surface carries no per-row routing field, so do not read a transport off a row. A `dead` socket row is a REPORTED FACT: no entwurf_v2 delivery is available while it is dormant. Listing it grants no action and is not an invitation to dispatch. Only on an explicit operator request, the separate entwurf_resume_call may visibly reopen a proven-dormant Pi, pi-durable or Claude Code citizen under the same id, submitting no prompt. In particular `unsupported` does NOT mean mailbox: it means this backend has no control-socket probe. Dispatch has THREE possible answers — a self-fetch mailbox (only while that mailbox is deliverable), native-push direct injection (only while its adapter probe is alive), or a REJECT when neither holds: mailbox-undeliverable for a self-fetch citizen whose inbox nobody drains, and on the native-push probe dead → native-push-target-dead vs indeterminate → native-push-probe-indeterminate, kept apart because an unestablished probe is not a departed host; an agy target rejects as native-push-paused (#152) without being probed. Note: this is the *active* world and it is facts-only — it creates nothing. Pass an existing garden id to entwurf_v2; to open a NEW sibling use entwurf_fresh_call.",
102
150
  "durable": "List the garden's citizens with their liveness facts from the entwurf-bridge. Read-only; facts, not verbs."
103
151
  },
104
152
  "parameters": {}
@@ -124,12 +172,12 @@
124
172
  },
125
173
  "entwurf_fresh_call": {
126
174
  "descriptions": {
127
- "mcp": "Open ONE fresh visible sibling beside you and hand it a first task. The launch rail follows THIS agent's process context; tmux placement can be explicitly named: inside herdr (HERDR_ENV=1) it opens a NEW UNFOCUSED TAB in your own herdr workspace, pi and claude-code only; everywhere else it opens in the operator's tmux with all six backends (pi, claude-code, copilot, omp, codex, pi-durable). No fallback: an incomplete herdr context is refused by name, never by a tmux window you cannot see from herdr. The sibling is asked to call back before its task with the launch nonce; match that nonce and use the callback sender envelope for its garden id. This returns a LAUNCH receipt (the owner's coordinates plus that nonce): it does NOT mean the runtime started, the first turn ran, or the task was delivered. Those coordinates are a VIEW, never an address — a herdr tab/pane id can change under a running sibling. Nothing polls. For EXISTING citizens use entwurf_v2 — this tool only creates, and entwurf_peers only reports. Model is REQUIRED and passed to the chosen 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 opens when their birth, MCP, receive or visible-identity units are absent, pi-durable also on named refusals of its installed carrier or exact SDK set, and codex also needs the operator-owned app-server socket entwurf never starts; in herdr they are refused by name first. Optional placement.tmuxSession is a TMUX-ONLY seat naming ONE EXISTING session and ALWAYS wins; omitted, the seat follows the CALLER, never the backend opened: a CODEX CALLER opens beside its own TUI pane, every other caller in its own session; a missing one is tmux-session-missing and 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 processes.",
128
- "durable": "Open ONE fresh visible sibling beside this durable host through the entwurf-bridge and hand it a first task (backend, model and task required; optional cwd and placement). Returns the bridge's LAUNCH receipt or its named refusal — not a started runtime, a delivered task or a reply; the sibling's callback names it. Opening beside this host needs the tmux pane this host itself was launched in."
175
+ "mcp": "Open ONE fresh visible sibling beside you and hand it a first task. Rail follows THIS agent's process context: inside herdr (HERDR_ENV=1), request a NEW UNFOCUSED TAB in your workspace: pi/claude-code via agent start, pi-durable via one native layout request; otherwise use the operator's tmux with all six backends (pi, claude-code, copilot, omp, codex, pi-durable). No fallback: incomplete herdr context rejects by name, never opens unseen tmux. The sibling is asked to call back before its task with the launch nonce; match it and take its garden id from the callback sender envelope. Returns a LAUNCH receipt (owner coordinates + nonce), NOT proof of runtime start, first turn or task delivery. Coordinates are a VIEW, never an address; herdr tab/pane ids can change. Nothing polls. EXISTING citizens: entwurf_v2; entwurf_peers only reports. Model is REQUIRED, passed to runtime CLI: provider/model for pi; id/alias for Claude Code; provider/<exact catalog model id> for pi-durable; pattern for the rest. On tmux, copilot/omp/codex/pi-durable refuse BEFORE window creation if birth, MCP, receive or visible-identity units are absent; pi-durable also refuses installed-carrier/exact-SDK failures; codex needs the operator-owned app-server socket Entwurf never starts. In herdr, copilot/omp/codex reject first; pi-durable runs the same unit/carrier/SDK preflight before layout. Optional placement.tmuxSession is TMUX ONLY: ONE EXISTING session, always wins. Omitted, seat follows the CALLER, not opened backend: a CODEX CALLER opens beside its TUI pane, others in their own session. Missing session: tmux-session-missing, NOTHING is created. Herdr refuses placement by name. No secrets in task: model/task argv are visible to same-user processes.",
176
+ "durable": "Open ONE fresh visible sibling beside this durable host through the entwurf-bridge and hand it a first task (backend, model and task required; optional cwd and placement). Returns the bridge's LAUNCH receipt or its named refusal — not a started runtime, a delivered task or a reply; the sibling's callback names it. Inside herdr, it requests a new unfocused tab in this host's workspace for pi, claude-code or pi-durable; elsewhere it needs the tmux pane this host itself was launched in."
129
177
  },
130
178
  "parameters": {
131
- "backend": "Which fixed runtime to open. Only these six, and only pi/claude-code when this agent runs inside herdr; there is no arbitrary command.",
132
- "cwd": "Optional literal ABSOLUTE path of an existing directory to start the sibling in (cross-repo fresh). Omit or pass \"\" to start where the CALLER is — this agent's own cwd on BOTH rails, or for a Codex caller its own record directory, because this bridge is the app-server's child and that process's directory is not the caller's. Taken exactly as given: no trim, no realpath, no project-name resolution. '#' is refused on the tmux rail only, because tmux format-expands a start directory; inside herdr it is an ordinary path character. The receipt echoes the directory that was REQUESTED or the caller record it came from, never an observation of where the pane landed.",
179
+ "backend": "Which fixed runtime to open. Only these six, and only pi/claude-code/pi-durable when this agent runs inside herdr; there is no arbitrary command.",
180
+ "cwd": "Optional literal ABSOLUTE path of an existing directory to start the sibling in (cross-repo fresh). Omit or pass \"\" to start where the CALLER is — this agent's own cwd on BOTH rails, or for a Codex caller its own record directory, because this bridge is the app-server's child and that process's directory is not the caller's. Taken exactly as given: no trim, no realpath, no project-name resolution. '#' is refused on the tmux rail only, because tmux format-expands a start directory; inside herdr it is an ordinary path character. The receipt echoes the directory that was REQUESTED, a Codex caller's record directory, or another caller's own process directory, never an observation of where the pane landed.",
133
181
  "model": "Required runtime model: canonical provider/model for pi (a bare alias is refused), provider/<exact catalog model id> for pi-durable, a Claude Code model id/alias, or a Copilot/OMP/Codex model name. Neither pi nor pi-durable may name the openrouter provider.",
134
182
  "placement": "Optional expert seat override, TMUX ONLY: open the sibling in ONE EXISTING tmux session of this agent's own server, and it always wins. When omitted the seat follows the CALLER, never the backend being opened: a Codex CALLER opens beside its own TUI pane, matched by thread-id in that pane's terminal title (0 or 2+ matching panes refuse, never fall back); every other caller opens in its own session. A pane title is a placement input only — never an address, liveness or delivery fact. Nothing is ever created. Inside herdr this field is refused by name — placement there belongs to herdr, and a tmux session name would silently place the sibling somewhere else. Independent of cwd; neither is inferred from the other. The receipt reports the selected name (absent for the caller-pane rule, which observed a session rather than requesting a name), its source, and resolved target session id.",
135
183
  "placement.tmuxSession": "EXACT name of an EXISTING session on this agent's own tmux server. Nothing is created: an absent session is refused as tmux-session-missing, and a name outside [A-Za-z0-9][A-Za-z0-9_-]* as tmux-session-name-invalid.",
@@ -138,11 +186,11 @@
138
186
  },
139
187
  "entwurf_resume_call": {
140
188
  "descriptions": {
141
- "mcp": "Only on an explicit operator request, reopen ONE DORMANT pi or Claude Code citizen under its OWN garden id, in a visible window in the operator's own tmux session. The record supplies everything — which session, which cwd, and for pi which transcript, model and provider — so the only input is the target id: there is no model override, no task, and no prompt, and a Claude Code session comes back as `claude --resume <its session id>` with none of the model or permission flags it was once started with. This verb submits NO prompt; what the runtime itself does after it comes back (for Claude Code, its hook re-arms the doorbell and unread mail may then wake a turn) is not measured, and talking to it is still entwurf_v2 fire-and-forget. You get TWO receipts and they mean different things: a LAUNCH receipt (tmux made a window and was asked to start the runtime) and an OBSERVATION receipt (pi: the control socket answered under the same id; Claude Code: a NEW live process in the window this call opened re-armed its receiver marker for the same session with a fresh arm binding the record also carries; or resume-unobserved; or record-unreadable with its cause). Unobserved is a real outcome, not an error to retry — the window is visible, so read it. A citizen that is already LIVE is refused: address it with entwurf_v2 instead. A Claude Code citizen is reopened only when the last validated owner the Entwurf hook armed for its session is proven gone AND that arm is still the record's last one; no marker, an unreadable or mismatched one, an inconclusive owner or an unbound one is refused, and a process holding the session without a committed hook attach is invisible to that proof. Other backends have no same-id resume. Requires that this agent itself runs inside tmux (inside herdr it is refused).",
142
- "durable": "Only on an explicit operator request, reopen ONE dormant pi or Claude Code citizen under its own garden id in a visible tmux window through the entwurf-bridge (target required; the record supplies the session and cwd — no task or prompt is submitted). Returns the bridge's LAUNCH and OBSERVATION receipts or its named refusal: a live target, another backend, or a Claude Code citizen not proven dormant is refused by name, and opening the window needs the tmux session this host itself was launched in. Existing unread mail may still wake the resumed runtime; neither receipt proves what it did."
189
+ "mcp": "Only on an explicit operator request, reopen ONE DORMANT pi, pi-durable or Claude Code citizen under its OWN garden id, in a visible window in the operator's own tmux session — or, inside herdr, a new herdr tab. The record supplies session, cwd and, for pi, the transcript's model and provider; the only input is the target id (no model override, task or prompt). Claude Code returns as `claude --resume <session id>` without its earlier model or permission flags. This verb submits NO prompt; what the runtime does once back (for Claude Code, unread mail may wake a turn) is not measured; talking to it is still entwurf_v2 fire-and-forget. TWO receipts mean different things: a LAUNCH receipt (a tmux window or herdr tab was made and asked to start the runtime) and an OBSERVATION receipt (pi: the control socket answered under the same id; pi-durable: the started child adopted this call's reservation (not readiness), or not-yet-adopted / reservation-lost; Claude Code: a NEW live process in this call's window or tab re-armed the session's marker with a fresh binding in the record, or pane-process-unread if herdr reports no pane child; or resume-unobserved, or record-unreadable with its cause). Unobserved is an outcome, not an error to retry; read the visible window. A session a live process holds is refused: a running citizen is reached only via entwurf_v2's rail check; an unadopted pi-durable reservation is not reachable by entwurf_v2. pi-durable reopens only if its session claim is closed and released or its holder is proven gone. Claude Code reopens only if the last validated owner its Entwurf hook armed is proven gone AND that arm is still the record's last; a missing, unreadable, mismatched or unbound marker or an inconclusive owner refuses, and a session held without a committed hook attach is invisible to that proof. Other backends have no same-id resume. This agent itself must run inside tmux or herdr; a herdr launch never falls back to tmux.",
190
+ "durable": "Only on an explicit operator request, reopen ONE dormant pi, pi-durable or Claude Code citizen under its own garden id in a visible tmux window — or, inside herdr, a new herdr tab — through the entwurf-bridge (target required; the record supplies the session and cwd — no task or prompt is submitted). Returns the bridge's LAUNCH and OBSERVATION receipts or its named refusal: a session a live process holds (a running citizen, or a pi-durable reservation awaiting adoption), another backend, or a citizen not proven dormant is refused by name, and opening it needs the tmux session — or the herdr pane — this host itself was launched in; a herdr launch never falls back to tmux. Existing unread mail may still wake the resumed runtime; neither receipt proves what it did."
143
191
  },
144
192
  "parameters": {
145
- "target": "Garden id of the DORMANT pi or Claude Code citizen to reopen (discover with entwurf_peers)."
193
+ "target": "Garden id of the DORMANT pi, pi-durable or Claude Code citizen to reopen (discover with entwurf_peers)."
146
194
  }
147
195
  },
148
196
  "entwurf_retire_call": {
@@ -171,16 +219,16 @@
171
219
  },
172
220
  "resumeRejectHints": {
173
221
  "target-invalid": "the target is not a valid garden id; use entwurf_peers to discover valid targets",
174
- "target-backend-unsupported": "that citizen's backend has no same-id resume (only pi and Claude Code do) — reach it with entwurf_v2, or open a fresh sibling",
222
+ "target-backend-unsupported": "that citizen's backend has no same-id resume (only pi, pi-durable and Claude Code do) — reach it with entwurf_v2, or open a fresh sibling",
175
223
  "target-locked": "another resume or dispatch holds this citizen's lock right now",
176
- "target-live": "that citizen is already running — this verb only reopens dormant ones",
177
- "target-indeterminate": "the citizen could not be proven dormant (an inconclusive socket probe or receiver owner), so nothing was started",
224
+ "target-live": "a live process holds that citizen's session — this verb only reopens dormant ones",
225
+ "target-indeterminate": "the citizen could not be proven dormant (an inconclusive socket probe, receiver owner or session holder), so nothing was started",
178
226
  "target-receiver-absent": "a Claude Code citizen is proven dormant only by its receiver marker, and it has none — there is no marker with which this verb could prove dormancy, so it opens nothing. Ask the human operator whether its window is still open; reopening it is the operator's act, not this verb's",
179
227
  "target-receiver-corrupt": "the citizen's receiver marker cannot be read as a marker, so its owner cannot be judged",
180
228
  "target-receiver-mismatch": "the receiver marker under this garden id names another backend or native session than the record — look at the store before anything reopens",
181
229
  "target-receiver-unbound": "the dead receiver owner is not bound to the record's last arm (none observed, a later attach, or the record moved), so a Claude Code process may still hold the session and this verb opens nothing. Ask the human operator whether its window is still open; reopening it is the operator's act, not this verb's",
182
230
  "target-address-conflict": "that citizen's control-socket path is corrupt",
183
- "launch-refused": "tmux was never asked to open a window"
231
+ "launch-refused": "the launcher refused before creating anything — no tmux window and no herdr tab was opened"
184
232
  },
185
233
  "freshLaunchRejectHints": {
186
234
  "no-tmux-context": "this agent is not running inside tmux, so there is no session to open a sibling beside",
@@ -212,7 +260,7 @@
212
260
  "cwd-format-token": "the recorded cwd contains '#', which tmux expands as a format inside -c — it would silently rewrite the path or run a command",
213
261
  "cwd-missing": "the recorded cwd no longer exists; tmux would not report this, it would open the window in $HOME and look successful",
214
262
  "cwd-not-directory": "the recorded cwd exists but is not a directory",
215
- "herdr-resume-unsupported": "this agent runs inside herdr, and same-id resume has only a tmux rail — nothing was opened in any ambient tmux session",
263
+ "herdr-resume-unsupported": "this agent runs inside herdr, where this tmux resume leaf does not open anything — nothing was opened in any ambient tmux session (from inside herdr, entwurf_resume_call reopens through its herdr rail instead)",
216
264
  "runtime-unresolved": "the citizen's runtime (pi or claude) is not installed on PATH",
217
265
  "runtime-not-absolute": "the resolved runtime path is not absolute",
218
266
  "runtime-path-whitespace": "the resolved runtime path contains whitespace, which tmux would re-split",
@@ -31,9 +31,10 @@
31
31
  * seat, #105; nothing is ever created), optionally at ONE literal requested
32
32
  * cwd (cross-repo fresh, #73); returns a LAUNCH receipt only, and the new
33
33
  * address arrives later as the sender envelope of the sibling's nonce callback.
34
- * - entwurf_resume_call — reopen ONE DORMANT pi or Claude Code citizen under its OWN garden id
35
- * in a visible window; target-only, submits no prompt, LAUNCH and OBSERVATION
36
- * receipts stay apart, the caller is attributed but grants nothing.
34
+ * - entwurf_resume_call — reopen ONE DORMANT pi, pi-durable or Claude Code citizen under its OWN
35
+ * garden id in a visible window or herdr tab; target-only, submits no prompt,
36
+ * LAUNCH and OBSERVATION receipts stay apart, the caller is attributed but
37
+ * grants nothing.
37
38
  * - entwurf_retire_call — one operator-requested target, one retirement attempt; actor is not
38
39
  * permission, and action / append-record / lock-release receipts stay separate.
39
40
  * - entwurf_callback — ZERO-ARGUMENT fresh-sibling callback. Reads ENTWURF_CALLBACK_TARGET +
@@ -84,10 +85,10 @@ import { retireCitizen } from "../../../pi-extensions/lib/entwurf-v2-retire.js";
84
85
  import { makeProductionRetireDeps } from "../../../pi-extensions/lib/entwurf-v2-retire-production.js";
85
86
  import { runAndRenderEntwurfV2FromSurface } from "../../../pi-extensions/lib/entwurf-v2-surface.js";
86
87
  import { makeVisibleResumeDeps, renderVisibleResume, visibleResume, } from "../../../pi-extensions/lib/entwurf-v2-visible-resume.js";
87
- import { dispatchFreshCall, renderDispatchedFreshCall } from "../../../pi-extensions/lib/fresh-call-dispatch.js";
88
+ import { dispatchFreshCall, dispatchResumeLaunch, renderDispatchedFreshCall, } from "../../../pi-extensions/lib/fresh-call-dispatch.js";
88
89
  import { probeNativeSenderAlive, reconcileSenderIdentityClaims, resolveCodexRequestSenderIdentity, resolveTrustedMetaSenderIdentity, } from "../../../pi-extensions/lib/meta-sender-identity.js";
89
90
  import { applyOmpBridgeChildRootPolicy, defaultMetaMailboxDir, defaultMetaSessionsDir, makeStoreRecordReader, readActiveStoreEntries, readMetaIdentityByGardenId, readMetaInbox, readMetaReceiverMarker, readMetaSenderMarker, requireBackend, } from "../../../pi-extensions/lib/meta-session.js";
90
- import { RESUME_CALL_REJECT_HINT, resumeCall } from "../../../pi-extensions/lib/mux-resume-call.js";
91
+ import { RESUME_CALL_REJECT_HINT, resumeCall, resumePiDurableCall, } from "../../../pi-extensions/lib/mux-resume-call.js";
91
92
  import { registerNativeConversation } from "../../../pi-extensions/lib/native-push/register.js";
92
93
  import { makeLocalTraceFact, readRemotePairOptIn, remotePairOptInPath, runEntwurfV2WithRemoteRoute, } from "../../../pi-extensions/lib/remote-pair-send.js";
93
94
  import { processTransport } from "../../../pi-extensions/lib/remote-pair-transport.js";
@@ -641,12 +642,22 @@ server.tool("entwurf_fresh_call", ENTWURF_TOOL_GUIDANCE.entwurf_fresh_call.descr
641
642
  // halves, so it is where the mux launcher is handed to the v2 composition. `entwurf-v2-visible-
642
643
  // resume` cannot open a window on its own and `mux-resume-call` cannot read a record — that is
643
644
  // the import fence in docs/mux-launch-rail.md §11, expressed as a function argument.
644
- const visibleResumeLaunch = (input) => {
645
- const launched = resumeCall({ backend: input.backend, cwd: input.cwd, runtimeArgs: input.runtimeArgs });
645
+ //
646
+ // #157 G1a: this surface does NOT choose the rail. It hands the fixed tmux mapping below to the
647
+ // shared rail root (`fresh-call-dispatch`), which reads the same capability fact fresh_call reads
648
+ // and, inside herdr, launches the herdr resume instead — never calling this mapping there.
649
+ //
650
+ // #157 B3: a pi-durable resume is the same leaf's second entry, which also carries the fixed adopt
651
+ // projection the v2 side built from its reservation; no other backend can hand it one.
652
+ const tmuxResumeLaunch = (input) => {
653
+ const launched = input.backend === "pi-durable"
654
+ ? resumePiDurableCall({ cwd: input.cwd, runtimeArgs: input.runtimeArgs, adoptEnv: input.piDurableAdoptEnv })
655
+ : resumeCall({ backend: input.backend, cwd: input.cwd, runtimeArgs: input.runtimeArgs });
646
656
  return launched.ok
647
- ? { ok: true, handle: launched.receipt }
657
+ ? { ok: true, handle: { rail: "tmux", ...launched.receipt } }
648
658
  : { ok: false, reason: launched.reason, hint: RESUME_CALL_REJECT_HINT[launched.reason] };
649
659
  };
660
+ const visibleResumeLaunch = (input) => dispatchResumeLaunch(input, tmuxResumeLaunch);
650
661
  server.tool("entwurf_resume_call", ENTWURF_TOOL_GUIDANCE.entwurf_resume_call.descriptions.mcp, {
651
662
  target: z
652
663
  .string()
@@ -3,7 +3,7 @@
3
3
  * verb (0.11 Stage 0 step 4-pre / 동결결정 10). PURE pi-FREE core: the
4
4
  * intent×liveness decision table + the reject taxonomy + a pure resolver.
5
5
  * NO runtime dispatch, NO spawn/send, NO I/O — step 5 wires this to transports.
6
- * The pi-ai TypeBox REPRESENTATION of this contract lives in the separate
6
+ * The pi-schema REPRESENTATION of this contract lives in the separate
7
7
  * `entwurf-v2-contract-schema.ts` (0.12.1 B-1) so this module — which the
8
8
  * harness-neutral MCP bridge reaches at boot — carries no pi dependency.
9
9
  *
@@ -431,9 +431,9 @@ export function resolveNativePushDispatch(intent, liveness) {
431
431
  observedLiveness: liveness,
432
432
  };
433
433
  }
434
- // ── TypeBox schemas ────────────────────────────────────────────────────────
435
- // MOVED to `entwurf-v2-contract-schema.ts` (0.12.1 B-1): the pi-ai TypeBox
436
- // builders (StringEnum/Type) are a pi-lane dependency, so they cannot live in
434
+ // ── JSON schemas ───────────────────────────────────────────────────────────
435
+ // MOVED to `entwurf-v2-contract-schema.ts` (0.12.1 B-1): the pi schema
436
+ // builders (pi-ai StringEnum, pi-schema since #155) are a pi-lane dependency, so they cannot live in
437
437
  // this pi-free core — the MCP bridge reaches this module at boot and must stay
438
438
  // harness-neutral (check-entwurf-bridge-pi-free). The schemas import the
439
439
  // constants/types above; pi-side consumers import the schemas from there.