@junghanacs/entwurf 0.51.0 → 0.53.1

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 (88) hide show
  1. package/AGENTS.md +1 -1
  2. package/BASELINE.md +116 -397
  3. package/CHANGELOG.md +191 -2
  4. package/CONTRIBUTING.md +4 -4
  5. package/DELIVERY.md +2 -2
  6. package/ENTWURF-CLI.json +1 -1
  7. package/ENTWURF.json +10 -4
  8. package/README.md +11 -8
  9. package/VERIFY.md +333 -143
  10. package/demo/README.md +1 -1
  11. package/demo/demo-baseline.sh +1 -1
  12. package/demo/demo.sh +1 -1
  13. package/docs/acp-backend-rail.md +248 -34
  14. package/docs/setup-clean-host.md +75 -80
  15. package/mcp/entwurf-bridge/dist/ENTWURF.json +10 -4
  16. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/acp-client.js +1 -1
  17. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/backend-adapter.js +90 -14
  18. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/config.js +4 -9
  19. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/context.js +15 -0
  20. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/model-ids.js +9 -12
  21. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/models.js +41 -56
  22. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/tool-surface.js +33 -6
  23. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-control-rpc.js +4 -1
  24. package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-composition.js +34 -3
  25. package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-fresh-call.js +3 -1
  26. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-mailbox-body.js +1 -1
  27. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +2 -1
  28. package/package.json +14 -14
  29. package/pi/pi-durable/carrier/harness-setup.js +1 -1
  30. package/pi/pi-durable/carrier/subagent.js +1 -1
  31. package/pi/pi-durable/overlay/upstream-pin.json +15 -15
  32. package/pi/settings.reference.json +0 -1
  33. package/pi-extensions/acp-provider.ts +12 -6
  34. package/pi-extensions/lib/acp/acp-client.ts +6 -7
  35. package/pi-extensions/lib/acp/backend-adapter.ts +139 -15
  36. package/pi-extensions/lib/acp/backend.ts +261 -32
  37. package/pi-extensions/lib/acp/config.ts +4 -9
  38. package/pi-extensions/lib/acp/context.ts +17 -0
  39. package/pi-extensions/lib/acp/event-mapper.ts +236 -17
  40. package/pi-extensions/lib/acp/model-ids.js +9 -12
  41. package/pi-extensions/lib/acp/models.ts +46 -57
  42. package/pi-extensions/lib/acp/tool-surface.ts +34 -6
  43. package/pi-extensions/lib/entwurf-control-rpc.ts +5 -1
  44. package/pi-extensions/lib/fresh-call-composition.ts +37 -3
  45. package/pi-extensions/lib/herdr-fresh-call.ts +3 -1
  46. package/pi-extensions/lib/meta-mailbox-body.ts +1 -1
  47. package/pi-extensions/lib/mux-fresh-call.ts +3 -1
  48. package/pi-extensions/meta-bridge-pi-durable.ts +1 -1
  49. package/run.sh +62 -120
  50. package/scripts/check-acp-carrier-augment.ts +3 -21
  51. package/scripts/check-acp-prompt-lifecycle.ts +24 -3
  52. package/scripts/check-acp-provider-surface.ts +17 -26
  53. package/scripts/check-acp-session-reuse.ts +23 -4
  54. package/scripts/check-acp-stop-reason.ts +22 -3
  55. package/scripts/check-acp-stream-hooks.ts +24 -3
  56. package/scripts/check-acp-usage-accounting.ts +53 -24
  57. package/scripts/check-copilot-receive-arm.ts +19 -2
  58. package/scripts/check-entwurf-v2-surface.ts +7 -21
  59. package/scripts/check-fresh-call-dispatch.ts +9 -3
  60. package/scripts/check-gate-qualification.ts +6 -6
  61. package/scripts/check-herdr-fresh-call.ts +1 -0
  62. package/scripts/check-install-surface.ts +6 -0
  63. package/scripts/check-pi-durable-send.ts +4 -1
  64. package/scripts/check-probe-ordering.ts +34 -24
  65. package/scripts/check-release-gate-outcomes.ts +9 -23
  66. package/scripts/lib/codex-fresh-source-receipts.ts +12 -6
  67. package/scripts/lib/probe-cli-target.ts +3 -2
  68. package/scripts/mutants/acp-augment.json +0 -26
  69. package/scripts/mutants/acp-cortex.json +10 -183
  70. package/scripts/mutants/acp-foreground-only.json +11 -0
  71. package/scripts/mutants/acp-launch-namespace.json +3 -1
  72. package/scripts/mutants/acp-prompt-lifecycle.json +2 -2
  73. package/scripts/mutants/acp-set-model-readback.json +488 -0
  74. package/scripts/mutants/agent-guidance.json +2 -2
  75. package/scripts/mutants/codex-native.json +2 -2
  76. package/scripts/mutants/control-send-receipt.json +47 -7
  77. package/scripts/mutants/mux-fresh-call.json +12 -1
  78. package/scripts/mutants/pack-install.json +4 -4
  79. package/scripts/mutants/pi-floor.json +8 -10
  80. package/scripts/mutants/release-gate.json +3 -3
  81. package/scripts/mutants/v2-surface.json +4 -6
  82. package/scripts/raw-acp-compaction-measure/probe.ts +8 -2
  83. package/scripts/smoke-acp-cortex-live.ts +4 -408
  84. package/scripts/smoke-acp-ordering-probe-live.ts +7 -1
  85. package/scripts/smoke-acp-session-reuse-live.ts +11 -2
  86. package/scripts/smoke-acp-v2-send-live.ts +1 -1
  87. package/scripts/smoke-herdr-raw-install-live.sh +4 -4
  88. package/scripts/check-acp-cortex.ts +0 -710
@@ -23,26 +23,25 @@ evidence split, and its three states must not collapse into "macOS is supported"
23
23
 
24
24
  ## Requirements
25
25
 
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
- The previous package, `0.41.0`, ships the Pi 1.3.0 SDK set and requires Pi 1.3.x; the setup guide
28
- inside that tarball is its contract.
26
+ The current published Entwurf 0.52.0 uses the exact Pi 1.5.2 SDK set and host Pi `>=1.5.2 <1.6`. The marked 0.51.0 / SDK 1.5.0 examples below are historical and remain bound to their own published manifest. Source validation, publication and a running host's loaded bytes are separate evidence; the earlier addon-less validation tarball installed on one operating host is not the published artifact (see the [#158 boundary](#sdk-152-candidate-boundary-158)).
27
+
28
+ The validation TGZ installed in #155's operating window before the D1 display repair is an earlier, separate artifact, not the published one; see the [SDK 1.5 boundary](#sdk-15-development-boundary-155) below.
29
+
30
+ The older package, `0.50.0`, ships the Pi 1.4.0 SDK set and requires a Pi 1.4.x host; the setup guide inside that tarball is its contract.
29
31
 
30
- <!-- entwurf:published @junghanacs/entwurf@0.50.0 pi 1.4.0 -->
31
32
  | Component | Requirement | Needed for |
32
33
  |---|---|---|
33
34
  | Node | **`>=24.0.0`** | package and bridge runtime |
34
35
  | npm/pnpm | npm is bundled with Node; pnpm is required for source setup | package or source installation |
35
36
  | Python 3 | required by `setup`/`install` (project-path normalization + settings writers); `--help`/`check-bridge` stay Python-free | pi/Claude/agy/Copilot/Codex wiring writers |
36
37
  | entwurf | global/project-local `@junghanacs/entwurf`, or a source checkout | operator command and garden capability |
37
- | pi | optional-by-presence, `>=1.4.0 <1.5` — absent is an explicit setup SKIP, below-floor is a named FAIL | ACP provider, control sockets |
38
- | Native durable (`pi-durable`) | supplied by the Entwurf package (#130): Entwurf's owned app carrier plus the exact 1.4.0 pi SDK set as production dependencies — setup PASS when they verify, a named FAIL otherwise (never SKIP) | the `entwurf pi-durable` contact; native birth/LIVE on this supply surface is a separate release proof (§2b) |
38
+ | pi | optional-by-presence — current 0.52.0: `>=1.5.2 <1.6`; historical 0.51.0: <!-- entwurf:published @junghanacs/entwurf@0.51.0 pi 1.5.0 -->`>=1.5.0 <1.6`<!-- /entwurf:published -->. Absent is an explicit setup SKIP, below-floor is a named FAIL | ACP provider, control sockets |
39
+ | Native durable (`pi-durable`) | supplied by the Entwurf package (#130): Entwurf's owned app carrier plus its exact pi SDK set (1.5.2 in published 0.52.0; 1.5.0 in historical 0.51.0) as production dependencies — setup PASS when they verify, a named FAIL otherwise (never SKIP) | the `entwurf pi-durable` contact; native birth/LIVE on this supply surface is a separate release proof (§2b) |
39
40
  | Claude Code | optional, **`>=2.1.306`** — the exec-form hook floor | Claude ACP auth/runtime and mailbox-backed native citizen |
40
41
  | 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 |
41
42
  | 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) |
42
43
  | Antigravity `agy` | optional, operator-installed and authenticated | legacy citizen; native push paused (#152) |
43
44
  | 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 |
44
- | Cortex Code | optional, operator-installed and authenticated | Cortex ACP backend |
45
- <!-- /entwurf:published -->
46
45
 
47
46
  Claude Code >=2.1.306 is required for the managed exec-hook lifecycle and same-id resume. The package
48
47
  never supplies or proxies backend credentials.
@@ -85,7 +84,7 @@ node --version
85
84
  npm --version
86
85
  ```
87
86
 
88
- The two npm forms below install the **published** package, `0.50.0` (Pi 1.4.x, the Pi 1.4.0 SDK
87
+ The two npm forms below install the **published** package, `0.52.0` (Pi 1.5.x, the Pi 1.5.2 SDK
89
88
  set). The [product entry](../README.md#install)'s unpinned `npm install -g @junghanacs/entwurf`
90
89
  takes whichever version npm's `latest` dist-tag names; pin `@<version>` as below when you mean the
91
90
  exact artifact the [Requirements](#requirements) table describes.
@@ -94,7 +93,7 @@ Global install is simplest when native harnesses should find stable bins from ev
94
93
  working directory:
95
94
 
96
95
  ```bash
97
- npm install -g @junghanacs/entwurf@0.50.0
96
+ npm install -g @junghanacs/entwurf@0.52.0
98
97
  entwurf --help
99
98
  entwurf setup /path/to/your-project
100
99
  entwurf check-bridge
@@ -105,7 +104,7 @@ A project-local installation is also supported:
105
104
  ```bash
106
105
  mkdir -p ~/entwurf-smoke && cd ~/entwurf-smoke
107
106
  npm init -y
108
- npm install --save-dev @junghanacs/entwurf@0.50.0
107
+ npm install --save-dev @junghanacs/entwurf@0.52.0
109
108
  npx entwurf setup .
110
109
  npx entwurf check-bridge
111
110
  ```
@@ -115,10 +114,10 @@ project-local install, or `./run.sh <command>` from a source checkout. Keep that
115
114
  same scope when upgrading and checking; native process restarts are separate (§7).
116
115
 
117
116
  `check-bridge` is auth-free. It proves the installed prebuilt MCP server boots and
118
- lists the nine garden tools (0.50.0); it does not prove a backend model turn, native hook or retirement permission.
117
+ lists the nine garden tools (0.52.0); it does not prove a backend model turn, native hook or retirement permission.
119
118
 
120
119
  Neither npm form installs a harness runtime. `pi`, Claude Code, Copilot CLI, Codex CLI, agy,
121
- Cortex, and their authentication remain operator-owned optional prerequisites for the
120
+ and their authentication remain operator-owned optional prerequisites for the
122
121
  integrations that use them; all may be absent on an Entwurf-only host. A source checkout's
123
122
  pinned Pi development packages are for building and testing this repo, not a transitive product installation promise.
124
123
 
@@ -226,13 +225,11 @@ physical-Darwin receipt. `entwurf install` may write `.pi/settings.json`
226
225
  on Darwin; that is wiring, not a rail receipt. If `pi` is on PATH,
227
226
  `setup` names the pi row FAIL with the §1 Darwin wording.
228
227
 
229
- Install the exact release floor, then wire the project. The previous 0.41.0 instead takes the
230
- Pi 1.3.x its own setup guide names. A source checkout passes its checkout path to `-e` in place of
231
- the global package root:
228
+ For published Entwurf 0.52.0, install its exact host Pi floor and wire the project as shown below.
229
+ The previous 0.51.0 uses the Pi 1.5.0 floor named in its own setup guide:
232
230
 
233
- <!-- entwurf:published @junghanacs/entwurf@0.50.0 pi 1.4.0 -->
234
231
  ```bash
235
- npm install -g @earendil-works/pi-coding-agent@1.4.0
232
+ npm install -g @earendil-works/pi-coding-agent@1.5.2
236
233
  pi --version
237
234
 
238
235
  cd ~/entwurf-smoke
@@ -240,11 +237,12 @@ entwurf install .
240
237
  pi -e "$(npm root -g)/@junghanacs/entwurf" --list-models entwurf
241
238
  ```
242
239
 
243
- The supported range is `>=1.4.0 <1.5`. It is a hard minimum: installing this
244
- release onto a 0.83.x pi host upgrades the runtime rather than keeping the older
245
- minor. A host using only the external MCP bridge can skip pi until it needs a
240
+ The supported host Pi range for published Entwurf 0.52.0 is `>=1.5.2 <1.6`.
241
+ A detected host outside that range is refused for Pi wiring; Entwurf does not upgrade it.
242
+ Align the host explicitly, for example with the exact npm command above, before wiring Pi. A host using only the external MCP bridge can skip pi until it needs a
246
243
  control socket; no delivery rail launches a pi process.
247
- <!-- /entwurf:published -->
244
+
245
+ Published 0.52.0 requires host Pi `>=1.5.2 <1.6`; `setup` reports a detected older host as FAIL. Prepare host Pi 1.5.2 before wiring, and use the checkout path rather than the global npm package when running a checkout directly. Range eligibility does not replace installation, validation or operating approval. Ordinary Pi 1.5.2 defaults its global `cacheWarming` setting to `idle`, which may send additional provider requests; pi-durable does not map that setting. See the [#158 durable boundary](./durable-native-support.md#sdk-152-candidate-boundary-158).
248
246
 
249
247
  For daily garden-native pi sessions:
250
248
 
@@ -263,7 +261,7 @@ A plain `pi` can load the package without becoming a citizen.
263
261
  In that session ask for `entwurf_self`, then `entwurf_peers`. Pick an existing
264
262
  live garden id and ask for `entwurf_v2` with `intent: "fire-and-forget"` and your
265
263
  message. These are harness tools, not shell subcommands; pi exposes them through
266
- Pi's built-in MCP as `mcp__entwurf_bridge__<verb>`. The 0.50.0 bridge has nine tools;
264
+ Pi's built-in MCP as `mcp__entwurf_bridge__<verb>`. The bridge has nine tools;
267
265
  pi hides `entwurf_inbox_read` and `entwurf_register_native`, leaving seven active.
268
266
  The added `entwurf_retire_call` is only for one target/action explicitly requested by the
269
267
  human operator, never autonomous cleanup. Where a host provides or is configured with an approval
@@ -348,11 +346,12 @@ by `entwurf pi` or by registering ordinary Pi's adapter.
348
346
 
349
347
  What the Entwurf package supplies:
350
348
 
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.
349
+ 1. **The pi SDK set** — published 0.52.0 supplies `@earendil-works/{pi-coding-agent, pi-ai, pi-tui, pi-agent-core,
350
+ pi-durable, chord, pi-mcp, pi-codemode, pi-telemetry, pi-schema}` at exactly `1.5.2`, plus
351
+ `proper-lockfile`, as production dependencies. Model data rides `pi-ai@1.5.2`, not a catalog copy.
352
+ Historical 0.51.0 used <!-- entwurf:published @junghanacs/entwurf@0.51.0 pi 1.5.0 -->`@earendil-works/pi-ai@1.5.0`<!-- /entwurf:published -->.
353
+ The [SDK pin](../pi/pi-durable/overlay/upstream-pin.json) and [package manifest](../package.json)
354
+ describe the checkout you hold, not immutable published bytes.
356
355
  2. **The native app/TUI carrier** — upstream does not publish the experimental durable
357
356
  app (`pi-coding-agent` excludes `dist/experimental`; `pi-durable` on npm is the
358
357
  library). Entwurf owns the six app files as canonical source (`pi/pi-durable/src`,
@@ -362,9 +361,9 @@ What the Entwurf package supplies:
362
361
  (`pi/pi-durable/carrier-resolver.mjs`), which maps them to the published package's
363
362
  own `dist/` as installed — never through a source checkout.
364
363
 
365
- The upstream [library guide](https://github.com/earendil-works/pi/blob/v1.5.0/packages/durable/README.md)
366
- and the [experimental app guide](https://github.com/earendil-works/pi/blob/v1.1.0/packages/coding-agent/src/experimental/durable/README.md)
367
- (last published at v1.1.0) describe what the carrier is. Running the bare upstream app does not attach Entwurf's
364
+ The upstream library guide for the published 0.51.0 pairing is [v1.5.0](https://github.com/earendil-works/pi/blob/v1.5.0/packages/durable/README.md); for the published 0.52.0 SDK APIs use [v1.5.2](https://github.com/earendil-works/pi/blob/v1.5.2/packages/durable/README.md).
365
+ The [experimental app guide](https://github.com/earendil-works/pi/blob/v1.1.0/packages/coding-agent/src/experimental/durable/README.md)
366
+ (last published at v1.1.0) records the app's origin: Entwurf's maintained app source, not a newer upstream app, is the current composition authority. Both guides describe what the carrier is. Running the bare upstream app does not attach Entwurf's
368
367
  garden contact. Entwurf does not rebuild the controller/model/auth/storage system or
369
368
  proxy a subscription; native execution, models, auth, queues, SQLite and recovery stay
370
369
  upstream-owned.
@@ -377,7 +376,7 @@ carrier file, the license, the resolver and its relocation grammar are present
377
376
  (`pi-durable-carrier-drift`); every declared SDK package resolves from the carrier
378
377
  (`pi-durable-sdk-absent`); and every reachable physical edge — from the carrier to each package,
379
378
  and from every package copy it reaches, at any depth, to its own `@earendil-works` dependencies —
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
379
+ lands on a package of the declared name at exactly the pinned version (`pi-durable-sdk-mismatch`). The members' declarations are exact in the published 1.5.0 set and, measured on the registry tarballs (#158), in the 1.5.2 set; even so, the physical package each import binds must be checked. Sharing
381
380
  one copy with an aligned host install of the same version is an ordinary layout. A mixed
382
381
  tree (for example an override that binds pi-durable's `pi-ai` to an older published version) still
383
382
  *imports* — which is why the edge check exists — and is refused by name.
@@ -459,9 +458,9 @@ tree (for example an override that binds pi-durable's `pi-ai` to an older publis
459
458
  manifest field; it is a known constraint of supplying both from one package, accepted for 0.32,
460
459
  not a claim that every load path is harmless.
461
460
 
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
461
+ The ordinary Pi CLI on PATH is a separate subject (§2; historical 0.51.0 range <!-- entwurf:published @junghanacs/entwurf@0.51.0 pi 1.5.0 -->`>=1.5.0 <1.6`<!-- /entwurf:published -->, current 0.52.0 range `>=1.5.2 <1.6`): a host Pi is
463
462
  not needed to run pi-durable, and a host inside the range need not be the same copy as the
464
- carrier's. Native Pi owns credentials, model settings and durable SQLite sessions; keep them
463
+ carrier's. The native harnesses own credentials, model settings and their respective session stores — ordinary Pi JSONL and pi-durable SQLite; keep them
465
464
  in place rather than copying credentials or migrating ordinary Pi transcripts. Native
466
465
  recovery does not prove exactly-once Entwurf dispatch or inbox read recovery. The contact
467
466
  provides garden attachment, visible identity and announce-only root-directed self-fetch.
@@ -472,12 +471,18 @@ proof ([#130](https://github.com/junghan0611/entwurf/issues/130)).
472
471
 
473
472
  ### SDK 1.5 development boundary (#155)
474
473
 
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.
474
+ The historical 0.51.0 instructions describe the SDK 1.5.0 set that #155 shipped; the #155 SDK
475
+ 1.5 development candidates that preceded it were distinct subjects. Synthetic migration and
476
+ downgrade-refusal fixtures on `0408a66` did not authorize an operating first write. A separately
477
+ approved 1.5 operating window and Native LIVE were later measured as described below; neither made
478
+ the validation TGZ the published artifact. Installing an older package is not a data rollback. Follow the current evidence in
479
+ [#155](https://github.com/junghan0611/entwurf/issues/155), not the 1.3→1.4 receipt below. The #158 source candidate is a separate boundary, next.
480
+
481
+ ### SDK 1.5.2 candidate boundary (#158)
482
+
483
+ The published 0.52.0 package (#158) pins the Pi SDK at exactly 1.5.2 (ten members) and declares host Pi `>=1.5.2 <1.6`. Publication does not install it on an operating host; an operating move requires its own installation and same-identity return receipts. The #155 receipts above prove their own subject only; none is inherited by 1.5.2.
484
+
485
+ What #158 measured so far and what it did not is recorded in [the durable-native boundary](./durable-native-support.md#sdk-152-candidate-boundary-158) and in the #158 thread: registry tarball integrity and member edges, an isolated six-member scheduler comparison and a native SQLite cross-version read/append check. The pre-version checkpoint's frozen FULL belongs to its own recorded worktree. The earlier validation candidate has its own floor and installation receipts; later source edits and the final carrier-bearing artifact require their own acceptance. Operating-scale and interrupted sessions are not certified by the bounded fixtures. An operating move follows the default radius below, each step with its own receipt.
481
486
 
482
487
  ### Default radius for a requested Pi SDK upgrade
483
488
 
@@ -493,17 +498,19 @@ The measured precedent is [#148's pre-cutover hold](https://github.com/junghan06
493
498
 
494
499
  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
500
 
501
+ **Installation handoff: prepared is not installed.** State prominently whether the installer has actually run or is still waiting for the operator's approval. A prepared terminal, a pulled checkout or a printed resume command is not success. Keep the normal old-owner exit and consistent complete backup gates; before presenting the reopen step as ready, require the recorded installation exit 0 and coherent carrier/SDK verification. Then reopen the intended same session with its original explicit native module. Do not automatically send Enter, install or switch ownership to bridge an approval wait. The [0.51.0 ThinkPad incident](../CHANGELOG.md#install-handoff-incident-and-recovery--thinkpad-2026-11-04) records a new-source/old-dependency launch refusal and the separately authorized recovery; it is not evidence of data loss or an SDK regression.
502
+
496
503
  **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
504
 
498
505
  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
506
 
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.
507
+ 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. Any operator-approved exception must name the exact packages and versions and be recorded with that upgrade's decision; it is not a standing patch-release exemption and not permission for an operating cutover. For #158 the operator explicitly approved the exact SDK 1.5.2 release-age exception on 2026-11-11: only those ten member/version terms are added to `minimumReleaseAgeExclude`, and the global policy and every other dependency remain subject to their own checks. That differs from #155's dated no-exception installation and authorises no operating write or publication.
501
508
 
502
509
  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
510
 
504
511
  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
512
 
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.
513
+ **#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#0510--2026-11-04) for scopes and receipts.
507
514
 
508
515
  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
516
 
@@ -773,7 +780,7 @@ as a note rather than a fault.
773
780
 
774
781
  **entwurf sets no OMP version floor in code, and will not grow one on schedule.** Detection is
775
782
  presence-only (`command -v "${OMP_BIN:-omp}"`); there is no `entwurf.ompFloor`, no coherence
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
783
+ gate, and no exact pin — unlike Node (`engines.node`), pi (the #158 checkout's predicate, `>=1.5.2 <1.6`) and Claude Code
777
784
  (`entwurf.claudeCodeFloor`), each of which has an enforcement point. That asymmetry is a
778
785
  decision, not an omission. A floor is the answer to a vendor that fails SILENTLY — Claude Code
779
786
  earned one because an older binary validates the exec manifest, drops `args` at runtime, and
@@ -988,7 +995,7 @@ real initial Pi outside `codex`, then omitted-placement Codex and outbound Pi in
988
995
  callbacks and addressed delivery both ways. `DELIVERY.md` owns that 57-assertion receipt, its four
989
996
  coordinates, and its digest as history. The caller-seat re-run is **accepted** — 2026-09-16, 56
990
997
  assertions for the seat topology and 65 for the caller-DIRECTORY axis, both exit 0 — and it, not the
991
- fixed home, is the current contract (`DELIVERY.md`, `BASELINE.md:37-38`, `VERIFY.md` release
998
+ fixed home, is the current contract (`DELIVERY.md`, BASELINE.md's release-host table (Codex caller-seat rows), `VERIFY.md` release
992
999
  acceptance).
993
1000
 
994
1001
  `entwurf_fresh_call` accepts backend `codex` — supported in 0.21.0 — and requires an explicit model. Its
@@ -1068,7 +1075,7 @@ LIVE=1 AGY_CONVERSATION_ID=<id> entwurf smoke-agy-native-push-live
1068
1075
 
1069
1076
  ACP turns are CERTIFIED on Linux; on macOS they are NOT CERTIFIED —
1070
1077
  pending physical host. Mux (visible fresh / resume) is the same state.
1071
- A CI runner has no Claude or Cortex login.
1078
+ A CI runner has no Claude login.
1072
1079
 
1073
1080
  Claude uses the operator's existing local Claude authentication:
1074
1081
 
@@ -1076,16 +1083,10 @@ Claude uses the operator's existing local Claude authentication:
1076
1083
  LIVE=1 entwurf smoke-acp-provider-live
1077
1084
  ```
1078
1085
 
1079
- Cortex requires an authenticated `cortex` CLI and an explicit connection. Keep
1080
- `CORTEX_HOME` unset; the adapter refuses its presence because it bypasses containment.
1081
-
1082
- ```bash
1083
- LIVE=1 ENTWURF_ACP_CORTEX_CONNECTION=<conn> \
1084
- entwurf smoke-acp-cortex-live
1085
- ```
1086
-
1087
- The aggregate release gate is Claude-backed and does not run Cortex automatically.
1088
- Its silence is not a Cortex PASS.
1086
+ Cortex ACP is disabled. Existing Cortex configuration and credentials are not modified.
1087
+ Entwurf fresh/resume exact admission refuses retired Cortex ids; the ACP adapter also
1088
+ refuses a Cortex id that reaches it, without a Claude fallback. Pi's own direct `--model`
1089
+ fuzzy selection precedes that adapter; its result for a typed retired id is unmeasured here.
1089
1090
 
1090
1091
  ### Backend prerequisites and resolver
1091
1092
 
@@ -1097,12 +1098,10 @@ operator-installed Claude runtime. Resolution order:
1097
1098
  2. `require.resolve(...)` against the bundled `@agentclientprotocol/claude-agent-acp`.
1098
1099
  3. `PATH:claude-agent-acp` fallback if package resolution fails.
1099
1100
 
1100
- The curated Claude ids are `claude-opus-5-5`, `claude-sonnet-5-5`, and
1101
- `claude-fable-5-1`. Cortex exposes `cortex-auto`, `cortex-claude-opus-5`,
1102
- `cortex-claude-sonnet-5`, and `cortex-openai-gpt-5.4`.
1103
- `entwurfProvider.cortexConnection` or `ENTWURF_ACP_CORTEX_CONNECTION` selects
1104
- an authenticated connection; the env override wins. Codex and Antigravity are
1105
- native-push citizens (§4c/§5), not ACP backends.
1101
+ The curated Claude ids are `claude-opus-5-5`, `claude-sonnet-5-5`,
1102
+ `claude-fable-5-1`, and `claude-fable-5-2`. Cortex ACP is disabled; a legacy
1103
+ `entwurfProvider.cortexConnection` does not select a backend or block Claude.
1104
+ Codex and Antigravity are native-push citizens (§4c/§5), not ACP backends.
1106
1105
 
1107
1106
  ### Settings reference and backend-specific keys
1108
1107
 
@@ -1137,15 +1136,11 @@ retired bundled `session-bridge` entry. It is not a general MCP catalog.
1137
1136
  user augment. Large Claude `_meta.systemPrompt` carriers can route OAuth sessions
1138
1137
  to metered “extra usage” billing.
1139
1138
 
1140
- `entwurfProvider` is one block, not a promise that every key shapes both backends.
1141
1139
  `tools`, `permissionAllow`, `disallowedTools`, `settingSources`, `skillPlugins`
1142
- and `appendSystemPrompt` are Claude's declaration surface, not Cortex's native
1143
- tool surface. They still enter the config signature (and `tools` the common
1144
- exclude-tools preflight), so editing them forces a fresh Cortex session.
1145
- `cortexConnection` is Cortex-only; `mcpServers` reaches both through their own
1146
- transports. `compaction` is a **top-level pi key**, not a backend knob. Unknown
1147
- and retired keys are ignored; a wrong-backend key can fail silently, so check
1148
- which backend consumes it before diagnosing bridge failure.
1140
+ and `appendSystemPrompt` are Claude's declaration surface. `mcpServers` carries
1141
+ explicit servers. `compaction` is a **top-level pi key**, not a backend knob.
1142
+ The retired `cortexConnection` key is not consumed by Claude and does not enable
1143
+ Cortex ACP; unknown and retired keys are ignored.
1149
1144
 
1150
1145
  ### Backend operation and containment
1151
1146
 
@@ -1160,12 +1155,8 @@ Read/Bash/Edit/Write; `Skill` is added for non-empty `skillPlugins`.
1160
1155
  The launch env sets `CLAUDE_CODE_DISABLE_BACKGROUND_TASKS=1`: vendor-managed
1161
1156
  background work is off, not arbitrary processes a command detaches itself.
1162
1157
 
1163
- Cortex uses its native tools, session-private HOME / `SNOWFLAKE_HOME`, and private
1164
- `cortex/mcp.json` projection because it ignores wire `mcpServers`. Only the bridge
1165
- receives the real operator HOME needed for the garden store; `CORTEX_HOME` is
1166
- refused, including an empty value, because it bypasses containment.
1167
1158
  [ACP backend rail — Shipped adapters](./acp-backend-rail.md#shipped-adapters)
1168
- owns the exact overlay/carrier/backend mechanics and audit limits.
1159
+ owns the exact Claude overlay/carrier mechanics and audit limits.
1169
1160
 
1170
1161
  Entwurf owns no memory layer or memory DB, and no backend is another's memory
1171
1162
  authority. Operator semantic-memory / Denote tools remain host-native skills,
@@ -1200,7 +1191,7 @@ Non-empty `skillPlugins` auto-adds `Skill` to `tools` and `Skill(*)` to
1200
1191
  path or missing `.claude-plugin/plugin.json` before starting the session; the
1201
1192
  SDK, not the bridge, validates plugin.json contents and SKILL.md bodies.
1202
1193
  Start a fresh Claude session and ask it to list callable skills. The operator
1203
- judgement is `Q-SKILL-CALLABLE` in [VERIFY](../VERIFY.md).
1194
+ judgement is asking the session which skills it can call; it is not a named row in [VERIFY](../VERIFY.md).
1204
1195
  This is Claude-only; Codex uses native `~/.codex/skills/` passthrough.
1205
1196
  [agent-config](https://github.com/junghan0611/agent-config) is a real multi-harness
1206
1197
  consumer, not a requirement for this setup.
@@ -1226,8 +1217,7 @@ Bridge identity, pi context, `~/AGENTS.md`, cwd AGENTS and date/cwd ride a one-s
1226
1217
  first-user prepend (`pi-context-augment.ts`), not the system carrier. It removes
1227
1218
  duplicate cwd AGENTS already in `<project-context ...>`. Its descriptions do not
1228
1219
  grant tools: the actual callable schema is truth (`read` / `Read` / `exec_command`,
1229
- MCP only when schema-visible). Cortex engraving rides this augment because it
1230
- has no system-prompt carrier; Claude uses `_meta.systemPrompt`.
1220
+ MCP only when schema-visible). Claude uses `_meta.systemPrompt` for its engraving.
1231
1221
 
1232
1222
  ### Session reuse and compaction
1233
1223
 
@@ -1243,7 +1233,12 @@ owns the reuse policy and distinguishes live mapping from semantic recall.
1243
1233
  **Entwurf does not implement compaction.** A backend's native compaction preserves
1244
1234
  the pi session/mapping; configure its policy through that backend's own interface,
1245
1235
  not a pi-side JSONL summary. The bridge exposes no backend-specific compaction
1246
- knobs. The ACP footer uses `usage_update.used / size` (backend prompt/tools/cache/
1236
+ knobs. One fixed exception on the Claude ACP rail: its adapter's idle `/compact` (an autonomous
1237
+ turn on an idle session) is turned off inline (`settings.idleCompaction: false`), because pi would
1238
+ not see that turn; in-prompt auto-compaction stays the backend's, and a managed settings tier would
1239
+ still win over the inline value ([rail §Capability posture](./acp-backend-rail.md#capability-posture)).
1240
+ Pi's own summaries on a Claude ACP model (`/compact`, the threshold, overflow recovery, `/tree`) are not that backend compaction: each runs as a one-shot tool-less turn on a new child, and the session's retained child is closed after a compaction ([rail §11-10](./acp-backend-rail.md)).
1241
+ The ACP footer uses `usage_update.used / size` (backend prompt/tools/cache/
1247
1242
  session included) with `[entwurf:usage]` diagnostics. Near limit choose a visible
1248
1243
  action: clear, open a new session with a different model, or let the backend
1249
1244
  compact natively.
@@ -1266,7 +1261,7 @@ harness unit FAIL with the §1 Darwin wording — upgrade does not mint a
1266
1261
  rail receipt. Doctors refuse on Darwin until a physical-host receipt
1267
1262
  exists.
1268
1263
 
1269
- If install or doctor reports an unreadable/old active citizen generation, do not edit
1264
+ If install or doctor reports an active citizen generation it cannot read, first distinguish reader-version skew from a genuinely unreadable generation. Roll a stale reader forward and preserve identities and records; only a genuinely unreadable generation, as the [fresh-cut policy](./fresh-cut-policy.md) prescribes, takes the procedure below. Do not fresh-cut merely because an SDK or checkout changed. For a genuinely unreadable generation, do not edit
1270
1265
  records by hand. Close pi, Claude, Copilot, OMP, agy, and Codex sessions first, run
1271
1266
  `entwurf meta-bridge-fresh-cut`, and read its exit status before any install. Then choose the
1272
1267
  installation mode you actually own:
@@ -1316,7 +1311,7 @@ An already-running process keeps the modules it loaded, whatever is on disk. So,
1316
1311
 
1317
1312
  1. Build once from the checkout (`pnpm run build-bridge`, or its body `bash scripts/with-dist-lock.sh bash scripts/build-bridge.sh` when no package lifecycle should run).
1318
1313
  2. Compare the whole compiled output with the accepted candidate's, as a file-set and hash manifest. A hash of the selected entry alone is not enough: the entry can stay identical while a helper it loads differs. Check the pinned SDK set separately, and that the new process started after the build.
1319
- 3. Close the visible session and reopen it with its own native same-id resume. Closing a working sibling is an operator decision for that session, not something an agent does because new bytes landed.
1314
+ 3. Close the visible session and reopen it with its own native same-id resume. Closing a working sibling is an operator decision for that session, not something an agent does because new bytes landed. For a pi-durable session launched with an operator module, reopen the intended native session with its original explicit CLI and module after verifying the installation; garden-id resume does not restore modules.
1320
1315
  4. Confirm `entwurf_self` names the same garden id, and that the receiver/owner join points at the new process, whose bridge child started after the build.
1321
1316
 
1322
1317
  A per-component setup PASS or a `git pull` does not show which bytes a running bridge loaded. On 2026-10-20 one host of the #107 pair still selected a pre-feature dist after a pull and a green setup. The cause was not traced. A hash check caught it before the first message, and a manual build and visible resume fixed it. Entwurf adds no auto-build, reload or watcher for this.
@@ -6,8 +6,14 @@
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": 19,
9
+ "revision": 20,
10
10
  "history": [
11
+ {
12
+ "revision": 20,
13
+ "source": "b9fa0d2aa43ebefc1c3bd5bc864c84965b52289a",
14
+ "issue": "https://github.com/junghan0611/entwurf/issues/159",
15
+ "change": "Align delivery descriptions with completion and reply routing while preserving receipt, rail, and lifecycle boundaries."
16
+ },
11
17
  {
12
18
  "revision": 19,
13
19
  "source": "2734f524bfeca6eb1a0ae092494d45576af9ebef",
@@ -126,11 +132,11 @@
126
132
  "tools": {
127
133
  "entwurf_v2": {
128
134
  "descriptions": {
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.",
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."
135
+ "mcp": "Send, reply or hand off to an existing garden id; the decider selects the live rail (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. A receipt is not a reply or proof of reading. A local final is not delivery: when asked to reply, call entwurf_v2 for the requested recipient (normally the replyable sender); honor routing and no-send instructions. Discover existing targets with entwurf_peers. A live socket can receive fire-and-forget; unsupported means only \"no control-socket probe,\" not reachability. For a target without a live socket, the THIRD RESULT is rejection if neither mailbox nor native-push is available. 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) → native-push-paused, no probe. A dormant socket rejects this delivery verb as dormant-fire-forget-unsupported. This delivery verb never launches or resumes a process. 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. A control-socket-DOMAIN dispatch takes a target lock; mailbox and native-push rails are lock-free. mode applies only to a CONTROL-SOCKET send. wants_reply requests a response without authorizing an action or requiring an automatic acknowledgment; an absent or false marker does not cancel a request in the message. message caps at 16000 chars; send an artifact path + digest for more.",
136
+ "durable": "Send, reply or hand off to an existing garden id through entwurf-bridge (intent fire-and-forget), returning a delivery receipt or named rejection. The receipt is not a reply or proof of reading. For a requested reply, send to the authorized recipient, normally the replyable sender; a local final is not delivery. Honor routing and no-send instructions; an absent or false wants_reply does not cancel a request, and an address alone needs no ACK."
131
137
  },
132
138
  "parameters": {
133
- "intent": "fire-and-forget = send/reply/hand-off to an existing target after a current rail check. unsupported is not a reachability verdict; the decider picks a rail or REJECTS (self-fetch → mailbox when deliverable, else mailbox-undeliverable; native-push → alive: direct injection, dead: native-push-target-dead, indeterminate: native-push-probe-indeterminate; a paused backend — agy, #152 — rejects as native-push-paused before any probe); set wants_reply to request an answer, not to wait for one. This is the ONLY intent: owned-outcome, which resumed a dormant citizen by launching a hidden background child, was withdrawn under the visible-first rule. It is not selectable, not deprecated-but-tolerated; a dormant socket citizen is currently unreachable by this verb and rejects as dormant-fire-forget-unsupported.",
139
+ "intent": "fire-and-forget sends, replies or hands off to an existing target after a current rail check. unsupported is not a reachability verdict: self-fetch uses a deliverable mailbox or rejects as mailbox-undeliverable; native-push injects when alive, rejects dead as native-push-target-dead and indeterminate as native-push-probe-indeterminate; paused agy rejects as native-push-paused before probing. wants_reply requests a response, not a wait. This delivery verb never launches or resumes a process; a dormant socket rejects as dormant-fire-forget-unsupported.",
134
140
  "message": "Message / prompt to the colleague. In familiar language and natural sentences, convey what matters now and the context they need. Keep identifiers and evidence exact; trim repetition, not meaning. Hard cap 16000 chars; for larger payloads send a file/artifact path plus digest.",
135
141
  "mode": "Injection style for a CONTROL-SOCKET send only: steer (ask the receiver's steering queue) or follow_up (ask its follow-up queue). NEITHER is an interrupt: a busy receiver drains steering after each turn and follow-ups only when its inner loop ends, so a later steer overtakes every earlier follow_up and there is no order between the two. Both queues are volatile process memory — an abort drops what is in them. The receipt names which queue accepted the message (`queued-steer` / `queued-follow-up`); an idle receiver answers `sent`, meaning the turn was TRIGGERED, not that the model has seen the text. The mailbox and native-push plans carry no mode, so it has no effect on those rails.",
136
142
  "target": "Target garden id (use entwurf_peers to discover)",
@@ -16,7 +16,7 @@
16
16
  // This module owns the `AcpConnectionLike` seam so the backend and the live
17
17
  // smokes both drive ONE adapter — the SDK method-name mapping lives here only,
18
18
  // and the backend's orchestration + the gate fakes stay untouched.
19
- import { AGENT_METHODS, CLIENT_METHODS, client, } from "@agentclientprotocol/sdk";
19
+ import { AGENT_METHODS, CLIENT_METHODS, client } from "@agentclientprotocol/sdk";
20
20
  /**
21
21
  * Production factory — wrap the SDK 1.1 fluent `client()` into the
22
22
  * `AcpConnectionLike` seam the backend (and the live smokes) drive.
@@ -23,7 +23,7 @@ import { fileURLToPath } from "node:url";
23
23
  import { RequestError } from "@agentclientprotocol/sdk";
24
24
  import { enrichMcpServersWithEnvelope } from "./config.js";
25
25
  import { loadEngraving } from "./engraving.js";
26
- import { CORTEX_MODEL_PREFIX, curatedClaudeModels, curatedCortexModels, SUPPORTED_ANTHROPIC_MODEL_IDS, SUPPORTED_CORTEX_MODEL_IDS, } from "./models.js";
26
+ import { CLAUDE_ACP_EFFORT_VALUES, CORTEX_MODEL_PREFIX, curatedClaudeModels, curatedCortexModels, LEGACY_CORTEX_MODEL_IDS, SUPPORTED_ANTHROPIC_MODEL_IDS, } from "./models.js";
27
27
  import { claudeLaunchEnvDefaults, ensureClaudeConfigOverlay, ensureCortexDualHomeOverlay } from "./overlay.js";
28
28
  import { buildClaudeSessionMeta } from "./tool-surface.js";
29
29
  // POSIX-safe single-quote wrapper for shell arg interpolation. Byte-for-byte
@@ -114,7 +114,7 @@ export const CLAUDE_FOREGROUND_ONLY_ENV = { CLAUDE_CODE_DISABLE_BACKGROUND_TASKS
114
114
  * The set-model answer is read back before the prompt (#128). claude-agent-acp resolves the
115
115
  * requested id onto one of the rows it offers and answers with that row's `value` as the model
116
116
  * option's `currentValue` (0.85.1 `dist/acp-agent.js:5459-5499`). `[측정 2026-10-12]` 0.85.1, no
117
- * prompt: `claude-fable-5-1` answers its own row, while `claude-sonnet-5-5` and `claude-opus-5-5`
117
+ * prompt: `claude-fable-5-1` answers its own row (and, at 0.106.0 #142, so does `claude-fable-5-2`), while `claude-sonnet-5-5` and `claude-opus-5-5`
118
118
  * answer the alias rows `sonnet` and `opus`, named `Sonnet 5.5` and `Opus 5.5`. The id an alias row
119
119
  * resolves to (`ModelInfo.resolvedModel`) is not on the ACP wire, so an alias answer is accepted
120
120
  * only as the exact measured row, value AND name, declared here for the requested id. No other
@@ -148,6 +148,46 @@ export function confirmClaudeSetModelAnswer(response, nativeModelId) {
148
148
  `name the requested model. Refusing before the prompt so a different model cannot answer under this id; ` +
149
149
  `the installed driver and the curated id disagree, and repeating the turn cannot change that.`);
150
150
  }
151
+ const CLAUDE_EFFORT_VALUE_SET = new Set(CLAUDE_ACP_EFFORT_VALUES);
152
+ /** The named refusal for a thinking level ACP Claude cannot carry (#142). The text is fixed and
153
+ * must read as NON-transient to pi's retry and overflow classifiers: replaying the turn cannot
154
+ * change the level that was asked for. */
155
+ export const ACP_THINKING_LEVEL_UNSUPPORTED = "acp-thinking-level-unsupported";
156
+ /**
157
+ * Map pi's requested level to the claude-agent-acp effort value, or refuse by name (#142).
158
+ * The value is the registered row's `thinkingLevelMap` entry for that level — the documented pi
159
+ * meaning of the map — and must be one of the measured effort values. `undefined` (pi off, or no
160
+ * level) is refused rather than left at the vendor `default`: that would let the turn run at a
161
+ * depth nobody chose while the operator believes a level was applied.
162
+ */
163
+ export function resolveClaudeEffort(modelId, reasoning, thinkingLevelMap) {
164
+ const value = reasoning === undefined ? undefined : thinkingLevelMap?.[reasoning];
165
+ if (typeof value === "string" && CLAUDE_EFFORT_VALUE_SET.has(value))
166
+ return value;
167
+ const asked = reasoning === undefined
168
+ ? "no thinking level (pi sends none for `off`, and none when no level is set; the two are indistinguishable here)"
169
+ : `thinking level ${JSON.stringify(reasoning)}`;
170
+ throw new Error(`entwurf: ${ACP_THINKING_LEVEL_UNSUPPORTED} — ${modelId} over Claude ACP carries effort ` +
171
+ `${CLAUDE_ACP_EFFORT_VALUES.join("/")} only, and was asked for ${asked}. Choose one of those levels; ` +
172
+ `the turn was not sent, and repeating it at the same level cannot change that.`);
173
+ }
174
+ function claudeEffortRefusal(modelId, value, what, cause) {
175
+ const error = new Error(`entwurf: claude-agent-acp ${what} for effort ${JSON.stringify(value)} on ${modelId}. Refusing before the prompt ` +
176
+ `so the turn cannot run at an effort nobody chose; repeating the turn cannot change the driver's answer.`);
177
+ if (cause !== undefined)
178
+ error.cause = cause;
179
+ return error;
180
+ }
181
+ /** Refuse unless the set-effort answer reads back the requested value. */
182
+ export function confirmClaudeEffortAnswer(response, modelId, value) {
183
+ const configOptions = response?.configOptions;
184
+ const effort = Array.isArray(configOptions)
185
+ ? configOptions.find((option) => option?.id === "effort")
186
+ : undefined;
187
+ if (effort?.currentValue === value)
188
+ return;
189
+ throw claudeEffortRefusal(modelId, value, effort == null ? "answered with no effort option" : `answered effort ${jsonText(effort.currentValue)}`);
190
+ }
151
191
  export const claudeAdapter = {
152
192
  backend: "claude",
153
193
  // Claude owns its UNPREFIXED curated ids only (rail “Adapter contract”). The native id
@@ -206,6 +246,23 @@ export const claudeAdapter = {
206
246
  });
207
247
  confirmClaudeSetModelAnswer(answer, nativeModelId);
208
248
  },
249
+ resolveThinking({ modelId, reasoning, thinkingLevelMap }) {
250
+ return resolveClaudeEffort(modelId, reasoning, thinkingLevelMap);
251
+ },
252
+ async enforceThinking({ connection, acpSessionId, modelId, value }) {
253
+ const setConfig = connection.setSessionConfigOption;
254
+ if (typeof setConfig !== "function") {
255
+ throw new Error(`setSessionConfigOption unsupported — cannot set effort for ${modelId}`);
256
+ }
257
+ let answer;
258
+ try {
259
+ answer = await setConfig.call(connection, { sessionId: acpSessionId, configId: "effort", value });
260
+ }
261
+ catch (err) {
262
+ throw claudeEffortRefusal(modelId, value, "refused the set", err);
263
+ }
264
+ confirmClaudeEffortAnswer(answer, modelId, value);
265
+ },
209
266
  configSignatureFields(_adapterSettings) {
210
267
  // Claude folds no extra backend-specific fields beyond backend + nativeModelId
211
268
  // (which backend.ts adds). A future backend reads its own stable id off
@@ -226,21 +283,15 @@ export const claudeAdapter = {
226
283
  // ---------------------------------------------------------------------------
227
284
  // Registry — modelId → adapter
228
285
  // ---------------------------------------------------------------------------
229
- /** Registered adapters. Order carries NO routing authority — routeModel decides.
230
- * claude (unprefixed ids) + cortex (the `cortex-` prefix). A further backend
231
- * appends here with its own reserved prefix; the fail-fast below proves no two
232
- * adapters claim one id. */
233
- // ---------------------------------------------------------------------------
234
- // cortex adapter — Snowflake Cortex Code, the first non-claude backend on the
235
- // rail (docs/acp-backend-rail.md, “Shipped adapters”). It adds ZERO to the common layer:
236
- // everything cortex-specific lives here + models.ts + overlay.ts + the gates.
237
- // ---------------------------------------------------------------------------
238
- const SUPPORTED_CORTEX_IDS = new Set(SUPPORTED_CORTEX_MODEL_IDS);
286
+ // The former Cortex implementation is retained privately for historical source
287
+ // continuity only. The exported adapter below denies every side-effecting entry,
288
+ // and the production registry does not contain this implementation.
289
+ const SUPPORTED_CORTEX_IDS = new Set(LEGACY_CORTEX_MODEL_IDS);
239
290
  /** The env var an operator sets to pin a Snowflake connection per-shell without
240
291
  * editing settings.json. Wins over `entwurfProvider.cortexConnection`. The
241
292
  * ENTWURF_ACP_* convention (PR #40's legacy PI_SHELL_ACP* cortex-connection var renamed). */
242
293
  export const CORTEX_CONNECTION_ENV = "ENTWURF_ACP_CORTEX_CONNECTION";
243
- export const cortexAdapter = {
294
+ const retiredCortexAdapter = {
244
295
  backend: "cortex",
245
296
  // Cortex owns the reserved `cortex-` prefix (rail “Adapter contract”). routeModel strips it to the
246
297
  // native id: `cortex-auto` → "auto", `cortex-claude-sonnet-5` → "claude-sonnet-5".
@@ -373,7 +424,29 @@ export const cortexAdapter = {
373
424
  // server rejects a prompt with, so every cortex failure keeps the verbatim-first
374
425
  // errorMessage. Borrowing claude's `incomplete_tool_call` reader would be a guess.
375
426
  };
376
- const ADAPTERS = [claudeAdapter, cortexAdapter];
427
+ const cortexDisabled = () => {
428
+ throw new Error("entwurf: cortex-acp-disabled: Cortex ACP is not supported");
429
+ };
430
+ /** Retained exported shape refuses the retired adapter before a launch or overlay can be made. */
431
+ export const cortexAdapter = {
432
+ ...retiredCortexAdapter,
433
+ routeModel: () => undefined,
434
+ curatedModels: () => [],
435
+ resolveAdapterSettings: cortexDisabled,
436
+ resolveLaunch: cortexDisabled,
437
+ launchEnvDefaults: cortexDisabled,
438
+ ensureOverlay: cortexDisabled,
439
+ loadCarrier: cortexDisabled,
440
+ buildSessionMeta: cortexDisabled,
441
+ enforceModel: cortexDisabled,
442
+ configSignatureFields: cortexDisabled,
443
+ };
444
+ const ADAPTERS = [claudeAdapter];
445
+ /** A declared backend is only a guard, never a replacement routing authority. */
446
+ export function assertSupportedDeclaredBackend(backend) {
447
+ if (backend?.toLowerCase() === "cortex")
448
+ cortexDisabled();
449
+ }
377
450
  /**
378
451
  * Resolve the backend adapter that owns `modelId`.
379
452
  *
@@ -385,6 +458,9 @@ const ADAPTERS = [claudeAdapter, cortexAdapter];
385
458
  * so callers get `{ adapter, nativeModelId }` and never re-parse the id string.
386
459
  */
387
460
  export function resolveAcpBackendAdapter(modelId) {
461
+ if (modelId.toLowerCase().startsWith(CORTEX_MODEL_PREFIX)) {
462
+ throw new Error(`entwurf: cortex-acp-disabled: ${JSON.stringify(modelId)} is not supported`);
463
+ }
388
464
  const matches = [];
389
465
  for (const adapter of ADAPTERS) {
390
466
  const routed = adapter.routeModel(modelId);