@junghanacs/entwurf 0.25.0 → 0.30.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 (88) hide show
  1. package/AGENTS.md +1 -1
  2. package/BASELINE.md +1 -1
  3. package/CHANGELOG.md +182 -0
  4. package/CONTRIBUTING.md +1 -1
  5. package/DELIVERY.md +1 -1
  6. package/README.md +178 -674
  7. package/VERIFY.md +17 -16
  8. package/demo/README.md +1 -1
  9. package/docs/acp-backend-rail.md +6 -2
  10. package/docs/external-mcp-host.md +1 -1
  11. package/docs/setup-clean-host.md +346 -10
  12. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +27 -4
  13. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/backend-adapter.js +12 -1
  14. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-peers-render.js +1 -1
  15. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-decider.js +2 -2
  16. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-mailbox.js +2 -2
  17. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-production.js +2 -2
  18. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-surface.js +3 -5
  19. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-visible-resume.js +3 -3
  20. package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-composition.js +13 -6
  21. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-mailbox-body.js +1 -1
  22. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +3 -3
  23. package/mcp/entwurf-bridge/src/index.ts +30 -3
  24. package/package.json +11 -11
  25. package/pi-extensions/entwurf-control.ts +50 -637
  26. package/pi-extensions/lib/acp/backend-adapter.ts +13 -1
  27. package/pi-extensions/lib/entwurf-fact-provider.ts +2 -2
  28. package/pi-extensions/lib/entwurf-mcp-server.ts +74 -0
  29. package/pi-extensions/lib/entwurf-peers-render.ts +1 -1
  30. package/pi-extensions/lib/entwurf-v2-decider.ts +2 -2
  31. package/pi-extensions/lib/entwurf-v2-mailbox.ts +2 -2
  32. package/pi-extensions/lib/entwurf-v2-production.ts +3 -3
  33. package/pi-extensions/lib/entwurf-v2-surface.ts +3 -5
  34. package/pi-extensions/lib/entwurf-v2-visible-resume.ts +3 -3
  35. package/pi-extensions/lib/fresh-call-composition.ts +13 -6
  36. package/pi-extensions/lib/meta-mailbox-body.ts +1 -1
  37. package/pi-extensions/lib/mux-fresh-call.ts +3 -3
  38. package/run.sh +168 -54
  39. package/scripts/check-entwurf-peers-surface.ts +8 -15
  40. package/scripts/check-entwurf-self-address.ts +7 -11
  41. package/scripts/check-entwurf-v2-surface.ts +20 -112
  42. package/scripts/check-fresh-call-dispatch.ts +12 -13
  43. package/scripts/check-gate-qualification.ts +2741 -157
  44. package/scripts/check-install-surface.ts +3 -2
  45. package/scripts/check-mux-resume-call.ts +24 -35
  46. package/scripts/check-pi-bridge-sender.ts +130 -0
  47. package/scripts/check-pi-mcp-bridge.ts +251 -0
  48. package/scripts/lib/codex-fresh-live-protocol.ts +17 -5
  49. package/scripts/lib/codex-fresh-source-receipts.ts +21 -2
  50. package/scripts/lib/mutation-qualify.ts +269 -44
  51. package/scripts/lib/pi-mcp-bridge-probe.mjs +170 -0
  52. package/scripts/lib/qualification-acceptance.ts +146 -0
  53. package/scripts/lib/qualification-artifact.ts +359 -0
  54. package/scripts/lib/qualification-collection.ts +384 -0
  55. package/scripts/lib/qualification-gh.ts +231 -0
  56. package/scripts/lib/qualification-ledger.ts +669 -0
  57. package/scripts/lib/qualification-oracle-core.ts +279 -0
  58. package/scripts/lib/qualification-receipt.ts +561 -0
  59. package/scripts/lib/qualification-release.ts +271 -0
  60. package/scripts/meta-bridge-doctor.sh +16 -5
  61. package/scripts/mutants/acp-foreground-only.json +17 -0
  62. package/scripts/mutants/codex-native.json +11 -11
  63. package/scripts/mutants/control-send-receipt.json +6 -4
  64. package/scripts/mutants/copilot-birth.json +2 -1
  65. package/scripts/mutants/fresh-call-dispatch.json +3 -14
  66. package/scripts/mutants/gate-qualification.json +59 -0
  67. package/scripts/mutants/mux-fresh-call.json +43 -45
  68. package/scripts/mutants/mux-resume-call.json +11 -35
  69. package/scripts/mutants/pack-install.json +15 -4
  70. package/scripts/mutants/pi-bridge-sender.json +54 -0
  71. package/scripts/mutants/pi-floor.json +6 -2
  72. package/scripts/mutants/pi-mcp-bridge.json +36 -0
  73. package/scripts/mutants/pi-mcp-register.json +51 -0
  74. package/scripts/mutants/qualification-ledger.json +489 -0
  75. package/scripts/mutants/v2-surface.json +6 -6
  76. package/scripts/qualification-oracle.ts +124 -0
  77. package/scripts/resident-rpc-drive.ts +5 -1
  78. package/scripts/smoke-acp-bundled-mcp-live.ts +3 -1
  79. package/scripts/smoke-acp-cortex-live.ts +3 -1
  80. package/scripts/smoke-acp-session-reuse-live.ts +29 -12
  81. package/scripts/smoke-acp-socket-citizen-live.ts +3 -1
  82. package/scripts/smoke-acp-v2-send-live.ts +3 -1
  83. package/scripts/smoke-codex-fresh-live.ts +20 -1
  84. package/scripts/smoke-entwurf-chain-live.ts +6 -2
  85. package/scripts/smoke-entwurf-v2-matrix-live.ts +3 -1
  86. package/scripts/smoke-herdr-raw-install-live.sh +8 -3
  87. package/scripts/smoke-mux-lifecycle-live.ts +1 -1
  88. package/scripts/smoke-resident-garden-guard.sh +3 -1
@@ -1,6 +1,12 @@
1
1
  # Clean-host setup
2
2
 
3
- Operator recipe for a POSIX host. **Linux is the certified axis** — §2–§6 are
3
+ Operator recipe for the **direct installation route** on a POSIX host. Start at
4
+ [the product entry](../README.md#install); for the separate Herdr plugin route,
5
+ use [Herdr Entwurf](https://github.com/junghan0611/entwurf/blob/main/plugins/herdr/README.md).
6
+ Neither route installs a harness, subscription, credential, or login. Entwurf installs
7
+ its own bytes; `setup` composes the operator-installed harnesses it finds.
8
+
9
+ **Linux is the certified axis** — §2–§6 are
4
10
  written for it and their receipts are Linux receipts. What follows is the macOS
5
11
  evidence split, and its three states must not collapse into "macOS is supported":
6
12
 
@@ -22,7 +28,7 @@ evidence split, and its three states must not collapse into "macOS is supported"
22
28
  | npm/pnpm | npm is bundled with Node; pnpm is required for source setup | package or source installation |
23
29
  | Python 3 | required by `setup`/`install` (project-path normalization + settings writers); `--help`/`check-bridge` stay Python-free | pi/Claude/agy/Copilot/Codex wiring writers |
24
30
  | entwurf | global/project-local `@junghanacs/entwurf`, or a source checkout | operator command and garden capability |
25
- | pi | optional-by-presence, `>=0.87.1 <0.88` — absent is an explicit setup SKIP, below-floor is a named FAIL | ACP provider, control sockets |
31
+ | pi | optional-by-presence, `>=1.0.0 <1.1` — absent is an explicit setup SKIP, below-floor is a named FAIL | ACP provider, control sockets |
26
32
  | Claude Code | optional, **`>=2.1.217`** — the exec-form hook floor | Claude ACP auth/runtime and mailbox-backed native citizen |
27
33
  | 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 |
28
34
  | 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) |
@@ -77,6 +83,7 @@ working directory:
77
83
  ```bash
78
84
  npm install -g @junghanacs/entwurf
79
85
  entwurf --help
86
+ entwurf setup /path/to/your-project
80
87
  entwurf check-bridge
81
88
  ```
82
89
 
@@ -86,11 +93,16 @@ A project-local installation is also supported:
86
93
  mkdir -p ~/entwurf-smoke && cd ~/entwurf-smoke
87
94
  npm init -y
88
95
  npm install --save-dev @junghanacs/entwurf
96
+ npx entwurf setup .
89
97
  npx entwurf check-bridge
90
98
  ```
91
99
 
100
+ Use `entwurf <command>` for a global install, `npx entwurf <command>` from the
101
+ project-local install, or `./run.sh <command>` from a source checkout. Keep that
102
+ same scope when upgrading and checking; native process restarts are separate (§7).
103
+
92
104
  `check-bridge` is auth-free. It proves the installed prebuilt MCP server boots and
93
- lists the seven garden tools; it does not prove a backend model turn or native hook.
105
+ lists the eight garden tools; it does not prove a backend model turn or native hook.
94
106
 
95
107
  Neither npm form installs a harness runtime. `pi`, Claude Code, Copilot CLI, Codex CLI, agy,
96
108
  Cortex, and their authentication remain operator-owned optional prerequisites for the
@@ -119,6 +131,28 @@ component outcomes, preserves exact env forwarding and idempotence, and returns
119
131
  `codex-birth FAIL` until the vendor trust receipt exists.
120
132
  §4, §4b, and §4c keep the explicit per-unit install/doctor/inverse surfaces.
121
133
 
134
+ ### Stable bins and external MCP hosts
135
+
136
+ The package exposes six bins: `entwurf` (the operator command), `entwurf-bridge`
137
+ (MCP stdio), `entwurf-statusline` (Claude Code), `entwurf-agy-statusline`,
138
+ `entwurf-agy-imprint` (the agy birth hook), and `entwurf-copilot-statusline`.
139
+ Stable bin names keep native settings independent of versioned package-store paths.
140
+
141
+ A plain MCP registration exposes tools, but **does not make a garden citizen**:
142
+ a trusted lifecycle/identity unit must also birth the record. A plain host has no
143
+ authoritative sender and `entwurf_v2` is refused by default. `setup` composes those
144
+ units for detected harnesses; the per-harness commands below are repair surfaces.
145
+ For explicit manual registration, for example:
146
+
147
+ ```bash
148
+ claude mcp add --scope user entwurf-bridge entwurf-bridge
149
+ ```
150
+
151
+ A project-local MCP host can point at `node_modules/.bin/entwurf-bridge`.
152
+ [External MCP hosts](./external-mcp-host.md) owns each host's config paths,
153
+ PATH/env boundary (including GUI-launched servers), anonymous-sender hatch, and
154
+ external-versus-garden-native semantics. There is no ambient MCP config scan.
155
+
122
156
  ### 1.1 User-scope ownership (one shared registration, one recorded owner)
123
157
 
124
158
  The GLOBAL pi user-scope registration (`~/.pi/agent/settings.json` `packages[]` plus the
@@ -156,7 +190,7 @@ on Darwin; that is wiring, not a rail receipt. If `pi` is on PATH,
156
190
  Install the exact release floor, then wire the project:
157
191
 
158
192
  ```bash
159
- npm install -g @earendil-works/pi-coding-agent@0.87.1
193
+ npm install -g @earendil-works/pi-coding-agent@1.0.0
160
194
  pi --version
161
195
 
162
196
  cd ~/entwurf-smoke
@@ -164,7 +198,7 @@ entwurf install .
164
198
  pi -e "$(npm root -g)/@junghanacs/entwurf" --list-models entwurf
165
199
  ```
166
200
 
167
- The supported range is `>=0.87.1 <0.88`. It is a hard minimum: installing this
201
+ The supported range is `>=1.0.0 <1.1`. It is a hard minimum: installing this
168
202
  release onto a 0.83.x pi host upgrades the runtime rather than keeping the older
169
203
  minor. A host using only the external MCP bridge can skip pi until it needs a
170
204
  control socket; no delivery rail launches a pi process.
@@ -173,10 +207,80 @@ For daily garden-native pi sessions:
173
207
 
174
208
  ```bash
175
209
  cd ~/entwurf-smoke
176
- pi -e "$(npm root -g)/@junghanacs/entwurf" --entwurf-control
210
+ entwurf pi
211
+ # equivalent when the package is already registered:
212
+ pi --entwurf-control
177
213
  ```
178
214
 
215
+ A project-local install uses `npx entwurf pi`. `entwurf pi` execs the operator's
216
+ pi with the control flag; it does not install pi or inject model/Emacs preferences.
179
217
  The V3 record births the garden id; do not inject a pi session id manually.
218
+ A plain `pi` can load the package without becoming a citizen.
219
+
220
+ In that session ask for `entwurf_self`, then `entwurf_peers`. Pick an existing
221
+ live garden id and ask for `entwurf_v2` with `intent: "fire-and-forget"` and your
222
+ message. These are harness tools, not shell subcommands; pi exposes them through
223
+ Pi's built-in MCP as `mcp__entwurf_bridge__<verb>`. The bridge has eight tools;
224
+ pi hides `entwurf_inbox_read` and `entwurf_register_native`, leaving six active.
225
+ Tool-call ingress is MCP; delivery to a pi citizen is still its record-addressed
226
+ Unix control socket. A boot/listing from `check-bridge` is not a model-turn or
227
+ native-rail acceptance.
228
+
229
+ To inspect the ACP provider from a project-local package instead of a global one:
230
+
231
+ ```bash
232
+ pi -e ./node_modules/@junghanacs/entwurf --list-models entwurf
233
+ ```
234
+
235
+ The ACP provider, `entwurf-control`, and `model-lock` extensions ship as one set.
236
+ Disable the package as a whole rather than filtering individual entries into a
237
+ partially wired state. A pi-less external-only host can skip this section: no
238
+ delivery rail launches pi, and OMP remains its own binary with its own agent dir.
239
+
240
+ ### Starting in Herdr by hand
241
+
242
+ Herdr's ordinary pi start has no control flag. Forward it through Herdr's own argv
243
+ passthrough when starting a citizen manually:
244
+
245
+ ```bash
246
+ herdr agent start <name> --kind pi --pane <pane-id> -- --entwurf-control
247
+ ```
248
+
249
+ An Entwurf-opened pi sibling already carries the flag. Native Claude Code needs
250
+ no extra argv: its installed `SessionStart` hook births the record. Where the
251
+ citizen is drawn is placement evidence, never its address. The
252
+ [Herdr plugin guide](https://github.com/junghan0611/entwurf/blob/main/plugins/herdr/README.md#quick-start)
253
+ owns plugin installation, activation, first-use status pane and removal; direct
254
+ installation does not prevent context-selected fresh calls inside Herdr.
255
+
256
+ Pi's own `/new`, `/fork`, `/clone` and RPC replacement retain their native meaning:
257
+ `session_start` attaches the replacement to its own record and drops the old
258
+ control socket. Pi's native session id is not a garden address.
259
+
260
+ ### Emacs frontends
261
+
262
+ Terminal and Emacs frontends that launch
263
+ [pi-coding-agent](https://github.com/dnouri/pi-coding-agent) use the same control
264
+ flag. For a dedicated agent socket:
265
+
266
+ ```elisp
267
+ (setq pi-coding-agent-extra-args
268
+ '("--entwurf-control" "--emacs-agent-socket" "pi"))
269
+ ```
270
+
271
+ The bridge exports that name to ACP children as `PI_EMACS_AGENT_SOCKET`, so skills
272
+ can use the operator's socket without hardcoding it:
273
+
274
+ ```bash
275
+ emacsclient -s "${PI_EMACS_AGENT_SOCKET:-server}" --eval '(...)'
276
+ ```
277
+
278
+ <details>
279
+ <summary>Watch entwurf in Doom Emacs (click to expand)</summary>
280
+
281
+ ![entwurf in Doom Emacs](./assets/entwurf-doomemacs.gif)
282
+
283
+ </details>
180
284
 
181
285
  ## 3. Optional Claude Code native citizen
182
286
 
@@ -245,6 +349,40 @@ scanning for that process, checks the receiver, removes inherited pi identity ca
245
349
  the model/permission defaults. Birth occurs on the first prompt. `entwurf_fresh_call` uses this
246
350
  same managed invocation and requires the birth, MCP, receiver, and visible-identity preflight.
247
351
 
352
+ ### Managed launch and permissions
353
+
354
+ Copilot silently skips extension scanning without
355
+ `COPILOT_CLI_ENABLED_FEATURE_FLAGS=EXTENSIONS`. Entwurf owns one invocation, not
356
+ your shell profile:
357
+
358
+ ```bash
359
+ entwurf copilot
360
+ entwurf copilot -p "…" --model gpt-5.4
361
+ copilot # the plain vendor CLI, untouched
362
+ ```
363
+
364
+ The managed launch execs the vendor CLI in this terminal: same cwd/pid/exit status,
365
+ no tmux window and no citizen before the first prompt. It checks the receiver
366
+ before launch, refusing with `entwurf install-copilot-receive` if absent.
367
+ **Running it is consent to this process's profile:**
368
+
369
+ | Injected | When |
370
+ |---|---|
371
+ | `COPILOT_CLI_ENABLED_FEATURE_FLAGS=EXTENSIONS` | always; existing tokens preserved, in order, deduplicated |
372
+ | `--model auto` | only when no `--model` was supplied |
373
+ | `--yolo` | only when no explicit permission or surface policy flag was supplied |
374
+
375
+ Any of `--yolo`, `--allow-all`, `--allow-all-tools`, `--allow-all-paths`,
376
+ `--allow-all-urls`, `--allow-tool`, `--deny-tool`, `--allow-url`, `--deny-url`,
377
+ `--available-tools` or `--excluded-tools` suppresses injected `--yolo`. Narrowing
378
+ flags count so a default cannot silently widen your restriction.
379
+ `--allow-all-mcp-server-instructions` (prompt content) and `--autopilot` (mode)
380
+ are not policy flags. Arguments are forwarded byte-identically; defaults land
381
+ before `--`, and arguments after it are not read as policy. Nothing is written to
382
+ disk; plain `copilot` gets none of this profile. The unattended managed lane uses
383
+ `--yolo` because an idle session cannot answer the default interactive approvals
384
+ needed to drain `entwurf_inbox_read`.
385
+
248
386
  ## 4b. Optional OMP (`omp`) native citizen — accepted on one Linux host (thinkpad)
249
387
 
250
388
  This rail is CERTIFIED on Linux (one host, named in the version rule
@@ -252,7 +390,7 @@ below); on macOS it is NOT CERTIFIED — pending physical host. The Darwin
252
390
  install fences for `install-omp-bridge` and `install-omp-receive` are
253
391
  open (`Linux | Darwin`); a completed install is not a rail receipt.
254
392
 
255
- Three independently owned surfaces, and a boundary that is part of the instructions rather
393
+ Four independently owned surfaces, and a boundary that is part of the instructions rather
256
394
  than a footnote. Birth, visible identity, who-sent, the MCP hand and RECEIVE are landed, and
257
395
  `entwurf_fresh_call` opens an omp sibling on all three public surfaces. The clause 7 LIVE
258
396
  receipt has been taken: `smoke-omp-fresh-live` went green on 2026-08-30 (omp 18.0.0, one
@@ -307,7 +445,7 @@ as a note rather than a fault.
307
445
 
308
446
  **entwurf sets no OMP version floor in code, and will not grow one on schedule.** Detection is
309
447
  presence-only (`command -v "${OMP_BIN:-omp}"`); there is no `entwurf.ompFloor`, no coherence
310
- gate, and no exact pin — unlike Node (`engines.node`), pi (`>=0.87.1 <0.88`) and Claude Code
448
+ gate, and no exact pin — unlike Node (`engines.node`), pi (`>=1.0.0 <1.1`) and Claude Code
311
449
  (`entwurf.claudeCodeFloor`), each of which has an enforcement point. That asymmetry is a
312
450
  decision, not an omission. A floor is the answer to a vendor that fails SILENTLY — Claude Code
313
451
  earned one because an older binary validates the exec manifest, drops `args` at runtime, and
@@ -402,6 +540,26 @@ why `disabledServers` is never the way to hide an import.
402
540
 
403
541
  ## 4c. Optional OpenAI Codex CLI native citizen
404
542
 
543
+ ### Three operator decisions before fresh
544
+
545
+ `setup` writes every byte Entwurf owns, but three decisions remain yours:
546
+
547
+ 1. Trust the birth hook once in a visible plain Codex: answer `Trust all and continue`
548
+ and send a first turn. `setup`/`doctor-codex-birth` remain non-green until the
549
+ vendor's receipt exists; Entwurf never authors your security decision.
550
+ 2. Start `entwurf codex-app-server` in your own detached tmux session. For LIVE
551
+ acceptance it must differ from the Pi/Codex work session. The command execs
552
+ the vendor's default Unix-socket server here; Ctrl-C is yours, with no Entwurf
553
+ supervision or restart.
554
+ 3. Answer folder consent once per exact sibling launch directory, for example
555
+ `codex -C <the directory you will launch siblings in>`, then quit. An ordinary
556
+ fresh call notes `codex-launch-cwd-undecided` and opens the window anyway so
557
+ you can answer it; the unattended smoke requires that answer beforehand.
558
+
559
+ Your own `codex --remote` attachment is optional and independent: an Entwurf-opened
560
+ sibling brings its own TUI. That operator attachment has no managed Entwurf spelling.
561
+ The detailed neighbouring consent cases and ownership boundaries follow.
562
+
405
563
  Supported in 0.21.0 with Linux-focused evidence; on macOS it is NOT CERTIFIED —
406
564
  pending physical host. It is native, not ACP: the purpose is to preserve the operator's
407
565
  Codex tools, delegation, and work context rather than provide another GPT access path.
@@ -589,10 +747,174 @@ LIVE=1 ENTWURF_ACP_CORTEX_CONNECTION=<conn> \
589
747
  The aggregate release gate is Claude-backed and does not run Cortex automatically.
590
748
  Its silence is not a Cortex PASS.
591
749
 
750
+ ### Backend prerequisites and resolver
751
+
752
+ Claude is the reference ACP backend. `@agentclientprotocol/claude-agent-acp` and
753
+ its SDK are pinned dependencies of Entwurf; authentication belongs to the local
754
+ operator-installed Claude runtime. Resolution order:
755
+
756
+ 1. `CLAUDE_AGENT_ACP_COMMAND` — explicit alternative binary/wrapper override.
757
+ 2. `require.resolve(...)` against the bundled `@agentclientprotocol/claude-agent-acp`.
758
+ 3. `PATH:claude-agent-acp` fallback if package resolution fails.
759
+
760
+ The curated Claude ids are `claude-opus-5`, `claude-sonnet-5`, and
761
+ `claude-fable-5-1`. Cortex exposes `cortex-auto`, `cortex-claude-opus-5`,
762
+ `cortex-claude-sonnet-5`, and `cortex-openai-gpt-5.4`.
763
+ `entwurfProvider.cortexConnection` or `ENTWURF_ACP_CORTEX_CONNECTION` selects
764
+ an authenticated connection; the env override wins. Codex and Antigravity are
765
+ native-push citizens (§4c/§5), not ACP backends.
766
+
767
+ ### Settings reference and backend-specific keys
768
+
769
+ The reference shape is [pi/settings.reference.json](../pi/settings.reference.json).
770
+ A minimum declaration:
771
+
772
+ ```json
773
+ {
774
+ "compaction": { "enabled": false },
775
+ "entwurfProvider": {
776
+ "appendSystemPrompt": false,
777
+ "settingSources": [],
778
+ "strictMcpConfig": true,
779
+ "showToolNotifications": true,
780
+ "tools": ["Read", "Bash", "Edit", "Write"],
781
+ "skillPlugins": [],
782
+ "permissionAllow": ["Read(*)", "Bash(*)", "Edit(*)", "Write(*)", "mcp__*"],
783
+ "mcpServers": {
784
+ "entwurf-bridge": {
785
+ "command": "/path/to/entwurf/mcp/entwurf-bridge/start.sh",
786
+ "args": []
787
+ }
788
+ }
789
+ }
790
+ }
791
+ ```
792
+
793
+ `mcpServers` is the only ACP MCP injection path; invalid entries throw
794
+ `McpServerConfigError`. `install` writes the bundled bridge entry and prunes the
795
+ retired bundled `session-bridge` entry. It is not a general MCP catalog.
796
+ `appendSystemPrompt: false` is intentional: pi / AGENTS context rides the first
797
+ user augment. Large Claude `_meta.systemPrompt` carriers can route OAuth sessions
798
+ to metered “extra usage” billing.
799
+
800
+ `entwurfProvider` is one block, not a promise that every key shapes both backends.
801
+ `tools`, `permissionAllow`, `disallowedTools`, `settingSources`, `skillPlugins`
802
+ and `appendSystemPrompt` are Claude's declaration surface, not Cortex's native
803
+ tool surface. They still enter the config signature (and `tools` the common
804
+ exclude-tools preflight), so editing them forces a fresh Cortex session.
805
+ `cortexConnection` is Cortex-only; `mcpServers` reaches both through their own
806
+ transports. `compaction` is a **top-level pi key**, not a backend knob. Unknown
807
+ and retired keys are ignored; a wrong-backend key can fail silently, so check
808
+ which backend consumes it before diagnosing bridge failure.
809
+
810
+ ### Backend operation and containment
811
+
812
+ Claude honors operator `CLAUDE_CONFIG_DIR`. The ACP overlay whitelists required
813
+ auth/runtime state while hiding operator memory, hooks, agents, history, local
814
+ settings and project memory. It writes explicit empty `hooks: {}` for the SDK's
815
+ organic compaction shape and `permissions.defaultMode: "bypassPermissions"`
816
+ so unattended turns do not suspend on permission prompts. Explicit `tools` /
817
+ `disallowedTools` still constrain callable tools. The four-tool baseline is
818
+ Read/Bash/Edit/Write; `Skill` is added for non-empty `skillPlugins`.
819
+ `ENTWURF_ACP_CLAUDE_CONTEXT=<int>` overrides the operator context cap.
820
+ The launch env sets `CLAUDE_CODE_DISABLE_BACKGROUND_TASKS=1`: vendor-managed
821
+ background work is off, not arbitrary processes a command detaches itself.
822
+
823
+ Cortex uses its native tools, session-private HOME / `SNOWFLAKE_HOME`, and private
824
+ `cortex/mcp.json` projection because it ignores wire `mcpServers`. Only the bridge
825
+ receives the real operator HOME needed for the garden store; `CORTEX_HOME` is
826
+ refused, including an empty value, because it bypasses containment.
827
+ [ACP backend rail — Shipped adapters](./acp-backend-rail.md#shipped-adapters)
828
+ owns the exact overlay/carrier/backend mechanics and audit limits.
829
+
830
+ Entwurf owns no memory layer or memory DB, and no backend is another's memory
831
+ authority. Operator semantic-memory / Denote tools remain host-native skills,
832
+ not a bridge or pi privilege.
833
+
834
+ ### Custom skills
835
+
836
+ Claude ACP accepts `skillPlugins`: absolute directories following the Claude
837
+ Agent SDK plugin layout, outside pi's internal `~/.pi/agent/` cache:
838
+
839
+ ```text
840
+ <your-plugin-root>/
841
+ ├── .claude-plugin/
842
+ │ └── plugin.json
843
+ └── skills/
844
+ └── <skill-name>/
845
+ └── SKILL.md
846
+ ```
847
+
848
+ Example: [pi/skill-plugin-example](../pi/skill-plugin-example/).
849
+
850
+ ```json
851
+ {
852
+ "entwurfProvider": {
853
+ "skillPlugins": ["/absolute/path/to/your-plugin-root"]
854
+ }
855
+ }
856
+ ```
857
+
858
+ Non-empty `skillPlugins` auto-adds `Skill` to `tools` and `Skill(*)` to
859
+ `permissionAllow`. Settings parsing refuses a missing, relative, non-directory
860
+ path or missing `.claude-plugin/plugin.json` before starting the session; the
861
+ SDK, not the bridge, validates plugin.json contents and SKILL.md bodies.
862
+ Start a fresh Claude session and ask it to list callable skills. The operator
863
+ judgement is `Q-SKILL-CALLABLE` in [VERIFY](../VERIFY.md).
864
+ This is Claude-only; Codex uses native `~/.codex/skills/` passthrough.
865
+ [agent-config](https://github.com/junghan0611/agent-config) is a real multi-harness
866
+ consumer, not a requirement for this setup.
867
+
868
+ ### Context carriers
869
+
870
+ An optional short engraving and rich pi context are separate. Runtime default:
871
+ `pi-extensions/lib/acp/prompts/engraving.md` (`# Engraving Here`, pinned non-empty
872
+ by a gate). [prompts/engraving.md](../prompts/engraving.md) is a sample:
873
+ copy it and set `ENTWURF_ACP_ENGRAVING_PATH=/path/to/alt.md`.
874
+ Template variables are `{{backend}}` and `{{mcp_servers}}`; empty/missing operator
875
+ text is fine. Do not put AGENTS, tool catalogs or bridge narrative in the carrier:
876
+ large Claude system carriers can change billing semantics.
877
+
878
+ The loader trims leading/trailing whitespace and then opens the rendered carrier
879
+ with one blank line. Claude's SDK prefixes its identity sentence with no separator;
880
+ the boundary prevents the engraving from becoming its tail. The measured
881
+ 2026-07-31 unseparated string was
882
+ `You are a Claude agent, built on Anthropic's Claude Agent SDK.# Engraving Here`.
883
+ Template whitespace cannot supply the boundary because it is trimmed away.
884
+
885
+ Bridge identity, pi context, `~/AGENTS.md`, cwd AGENTS and date/cwd ride a one-shot
886
+ first-user prepend (`pi-context-augment.ts`), not the system carrier. It removes
887
+ duplicate cwd AGENTS already in `<project-context ...>`. Its descriptions do not
888
+ grant tools: the actual callable schema is truth (`read` / `Read` / `exec_command`,
889
+ MCP only when schema-visible). Cortex engraving rides this augment because it
890
+ has no system-prompt carrier; Claude uses `_meta.systemPrompt`.
891
+
892
+ ### Session reuse and compaction
893
+
894
+ Current process-scoped turns reuse a live ACP session; persisted records are
895
+ written/validated for a future resume/load path, not consumed as today's live
896
+ continuation mechanism. The `pi:<sessionId>` mapping lives under
897
+ `~/.pi/agent/cache/entwurf/sessions/`; it never hydrates backend transcripts into
898
+ pi history. Backend stores such as `~/.claude/` and `~/.codex/` are interoperability
899
+ side effects, never Entwurf identity authority.
900
+ [VERIFY — bridge continuity vs semantic continuity](../VERIFY.md#bridge-continuity-vs-semantic-continuity)
901
+ owns the reuse policy and distinguishes live mapping from semantic recall.
902
+
903
+ **Entwurf does not implement compaction.** A backend's native compaction preserves
904
+ the pi session/mapping; configure its policy through that backend's own interface,
905
+ not a pi-side JSONL summary. The bridge exposes no backend-specific compaction
906
+ knobs. The ACP footer uses `usage_update.used / size` (backend prompt/tools/cache/
907
+ session included) with `[entwurf:usage]` diagnostics. Near limit choose a visible
908
+ action: clear, open a new session with a different model, or let the backend
909
+ compact natively.
910
+
592
911
  ## 7. Upgrade and repair
593
912
 
594
- After upgrading the package, rerun the managed installers for every native harness
595
- in use and restart their existing processes. Native plugin caches are not live-reload
913
+ After an npm upgrade, rerun `entwurf setup <project>` in the same installation
914
+ scope (`npx entwurf setup .` for project-local), then `check-bridge` in that scope.
915
+ Use `@junghanacs/entwurf@latest` when explicitly choosing the registry stable line.
916
+ Setup re-composes every detected harness; use a single `install-*` only to repair
917
+ one unit, and restart existing native processes. Native plugin caches are not live-reload
596
918
  safe across launch-contract changes.
597
919
 
598
920
  On Darwin the four previously closed install fences
@@ -625,6 +947,20 @@ source-checkout-only), and composes the detected harnesses with the same per-com
625
947
  PASS/SKIP/FAIL summary. The complete quiescence, archive, and exit-code contract is
626
948
  [fresh-cut-policy.md](./fresh-cut-policy.md).
627
949
 
950
+ ### Source checkout and pi-only repair
951
+
952
+ `entwurf install <project>` is the narrower pi wiring leaf: it writes the target
953
+ `.pi/settings.json` with the installed bridge's absolute launcher path and composes
954
+ no native harness. The retired `~/.pi/agent/` target-registry link is not read.
955
+ User-scope ownership/refusal/takeover remains §1.1, not an automatic repair.
956
+
957
+ The development bridge uses Node strip-types directly; the npm artifact runs
958
+ prebuilt `mcp/entwurf-bridge/dist/` JS because Node refuses to strip TypeScript
959
+ under `node_modules`. Source `./run.sh check-bridge` needs no build. After a pull,
960
+ run `pnpm install` when the lockfile changed and `pnpm run build-bridge` before
961
+ artifact-consuming checks such as `./run.sh check-bridge-delivery`; ignored dist
962
+ may be missing or stale. Installed mode never bootstraps pnpm in `node_modules`.
963
+
628
964
  ## 8. Release acceptance versus host acceptance
629
965
 
630
966
  - `entwurf check-bridge`: installed MCP bytes boot; no backend auth.
@@ -81,7 +81,7 @@ import { runAndRenderEntwurfV2FromSurface } from "../../../pi-extensions/lib/ent
81
81
  import { makeVisibleResumeDeps, renderVisibleResume, visibleResume, } from "../../../pi-extensions/lib/entwurf-v2-visible-resume.js";
82
82
  import { dispatchFreshCall, renderDispatchedFreshCall } from "../../../pi-extensions/lib/fresh-call-dispatch.js";
83
83
  import { probeNativeSenderAlive, reconcileSenderIdentityClaims, resolveCodexRequestSenderIdentity, resolveTrustedMetaSenderIdentity, } from "../../../pi-extensions/lib/meta-sender-identity.js";
84
- import { applyOmpBridgeChildRootPolicy, defaultMetaMailboxDir, defaultMetaSessionsDir, makeStoreRecordReader, readActiveStoreEntries, readMetaInbox, readMetaReceiverMarker, readMetaSenderMarker, requireBackend, } from "../../../pi-extensions/lib/meta-session.js";
84
+ import { applyOmpBridgeChildRootPolicy, defaultMetaMailboxDir, defaultMetaSessionsDir, makeStoreRecordReader, readActiveStoreEntries, readMetaIdentityByGardenId, readMetaInbox, readMetaReceiverMarker, readMetaSenderMarker, requireBackend, } from "../../../pi-extensions/lib/meta-session.js";
85
85
  import { RESUME_CALL_REJECT_HINT, resumeCall } from "../../../pi-extensions/lib/mux-resume-call.js";
86
86
  import { registerNativeConversation } from "../../../pi-extensions/lib/native-push/register.js";
87
87
  const HOME = os.homedir();
@@ -281,9 +281,32 @@ async function resolveAuthoritativeSender(context, cwd = process.cwd()) {
281
281
  return buildMetaSenderEnvelope(marker.identity, marker.marker.cwd || cwd, marker);
282
282
  }
283
283
  if (pi && pi.sessionId === selected.id)
284
- return { envelope: pi };
284
+ return { envelope: requirePiSenderRecord(pi) };
285
285
  throw new Error(`entwurf-bridge: reconciled sender ${selected.id} has no matching identity source`);
286
286
  }
287
+ /**
288
+ * PI_SESSION_ID CARRIES a record-established identity; it does not establish one (Hard Rule 2).
289
+ * Since #125 the pi session reaches every Entwurf verb through this bridge, so the pi rail is the
290
+ * main road rather than a side door, and the carrier is checked where the sender is SELECTED:
291
+ * after the claims reconcile (so a complete conflicting Codex claim still earns its own
292
+ * diagnostic) and before any caller builds on the answer.
293
+ *
294
+ * The per-entry targeted read the sender/self policy already uses — no store scan, no second
295
+ * nativeSessionId carrier. An absent, unreadable or irregular record throws from the reader with
296
+ * its own cause; a record of another backend throws here. None of it falls back to another rail
297
+ * or to anonymous. Socket presence stays what it was: replyability, never admission.
298
+ *
299
+ * What it does NOT prove: that a trusted host which deliberately exports ANOTHER real pi
300
+ * citizen's id is that citizen. Carrier integrity below the record is the host's.
301
+ */
302
+ function requirePiSenderRecord(envelope) {
303
+ const identity = readMetaIdentityByGardenId(envelope.sessionId);
304
+ if (identity.backend !== "pi") {
305
+ throw new Error(`entwurf-bridge: PI_SESSION_ID ${envelope.sessionId} names a ${identity.backend} record, not a pi citizen — ` +
306
+ "the pi-session carrier must name the record this pi session was born with");
307
+ }
308
+ return envelope;
309
+ }
287
310
  // Async only when the selected sender rides native-push: replyability is the adapter's live fact.
288
311
  async function buildAuthoritativeSelfEnvelope(context = {}) {
289
312
  const resolved = await resolveAuthoritativeSender(context);
@@ -343,7 +366,7 @@ function abbreviateHomeMcp(cwd) {
343
366
  // reports one outcome. The per-target lock is NOT taken by every rail: the decider
344
367
  // locks only a control-socket-domain dispatch; the mailbox and native-push branches
345
368
  // carry `lock: null` (entwurf-v2-decider.ts). It runs
346
- // IN-PROCESS here (the same production runner pi-native uses) — NOT a delegating
369
+ // IN-PROCESS here (the one production runner; pi reaches it through this bridge since #125) — NOT a delegating
347
370
  // RPC — so control, mailbox and native-push all flow through
348
371
  // `runEntwurfV2`. The sender envelope is
349
372
  // `buildSendSenderEnvelope()` verbatim (origin/replyable as resolved) — v2 does
@@ -402,7 +425,7 @@ server.tool("entwurf_v2", "CANONICAL DELIVERY SURFACE for garden ids: message, r
402
425
  const rendered = await runAndRenderEntwurfV2FromSurface({ target, intent, message, mode, wants_reply },
403
426
  // No trust-preflight inputs are passed, and none exist to pass: the preflight on this
404
427
  // path guarded the resume verdict and left with `owned-outcome`. `senderProvider` is
405
- // the whole options surface now (see the pi-native surface for the same note).
428
+ // the whole options surface now.
406
429
  { senderProvider: () => sender });
407
430
  return rendered.isError ? textErr(rendered.text) : textOk(rendered.text);
408
431
  }
@@ -62,6 +62,17 @@ function resolveClaudeLaunch() {
62
62
  const launcher = fileURLToPath(new URL("./claude-acp-launch.js", import.meta.url));
63
63
  return { command: process.execPath, args: [launcher] };
64
64
  }
65
+ /**
66
+ * The claude child runs with Claude Code's own background-task switch on (#125): the vendor drops
67
+ * `run_in_background` from the Bash/Agent schemas it declares to the model and turns off timeout and
68
+ * turn-abort backgrounding. Without it a vendor-backgrounded command runs in its own session (setsid),
69
+ * outlives an ACP cancel, and is orphaned to init when the child exits — the process-group teardown
70
+ * cannot reach it (`[측정 2026-10-02]` claude-agent-sdk 0.3.274 / claude-agent-acp 0.79.0). A process a
71
+ * command detaches on its own is outside the switch. It rides
72
+ * the claude adapter's launch env, which backend.ts spreads over `process.env` at spawn, so an
73
+ * ambient value cannot turn it off. Cortex declares nothing of the kind.
74
+ */
75
+ export const CLAUDE_FOREGROUND_ONLY_ENV = { CLAUDE_CODE_DISABLE_BACKGROUND_TASKS: "1" };
65
76
  export const claudeAdapter = {
66
77
  backend: "claude",
67
78
  // Claude owns its UNPREFIXED curated ids only (rail “Adapter contract”). The native id
@@ -82,7 +93,7 @@ export const claudeAdapter = {
82
93
  return resolveClaudeLaunch();
83
94
  },
84
95
  launchEnvDefaults() {
85
- return claudeLaunchEnvDefaults();
96
+ return { ...claudeLaunchEnvDefaults(), ...CLAUDE_FOREGROUND_ONLY_ENV };
86
97
  },
87
98
  ensureOverlay() {
88
99
  // Claude's overlay is constant (no settings dependence), so it ignores params.
@@ -3,7 +3,7 @@
3
3
  * surface (0.11 Stage 0 step 4, slice 4c). The MCP handler stays thin: it does IO
4
4
  * (readdir the meta-store + probe sockets via `listEntwurfFacts`) and then calls
5
5
  * THIS to shape the text + JSON. No IO here, so the gate drives it without a
6
- * filesystem and the SAME facts can feed pi-native / doctor / v2 dispatch later
6
+ * filesystem and the SAME facts can feed the bridge / doctor / v2 dispatch
7
7
  * (a handler that did its own brain work would deny them that reuse).
8
8
  *
9
9
  * Two hard rules carried from the frozen contract (동결결정 10 + #50 C4):
@@ -22,7 +22,7 @@
22
22
  *
23
23
  * The frozen 7-step order (NEXT.md "통합 decider 순서"):
24
24
  * 1. requireGardenId — runtime guard BEFORE any path is built (F2-P1; closes the
25
- * MCP-schema bypass for pi-native/internal callers).
25
+ * MCP-schema bypass for internal callers).
26
26
  * 2. resolveTarget — no citizen → bad-target; a record-less control socket →
27
27
  * record-less-socket (#50 C4: the record is the sole address authority);
28
28
  * quarantined (out-of-socket-domain record sharing a socket/symlink) → target-address-conflict.
@@ -54,7 +54,7 @@ export { isOutOfSocketDomainGardenIdConflict };
54
54
  export const ENTWURF_V2_MODE_DEFAULT = "follow_up";
55
55
  /**
56
56
  * F2-P1 defense in depth: never build a lock/socket path from an unvalidated gid.
57
- * The MCP TypeBox pattern guards that one surface; a pi-native / internal caller
57
+ * The MCP schema pattern guards that one surface; an internal caller
58
58
  * bypasses the schema, so the decider re-validates as its very first step.
59
59
  */
60
60
  function requireGardenId(target) {
@@ -58,8 +58,8 @@ export function executeMetaMailboxSend(plan, sender, deps) {
58
58
  * Build the production `sendViaMailbox(plan, lock)` adapter the 5c-2a send hand consumes.
59
59
  * It IGNORES `lock` entirely (release is the hand's job; a mailbox plan is lock-free) and
60
60
  * wraps the sync body in the async dep signature. `senderProvider` is supplied by the
61
- * wiring site — it calls the private `buildLocalSenderEnvelope(ctx)` and decorates
62
- * origin/replyable — so this module never imports ExtensionContext. `enqueue` defaults to
61
+ * wiring site — the bridge's authoritative sender resolution (the one caller-side
62
+ * surface since #125) — so this module never imports ExtensionContext. `enqueue` defaults to
63
63
  * the real `enqueueMetaMessage`; the gate injects a fake.
64
64
  */
65
65
  export function makeProductionSendViaMailbox(opts) {
@@ -2,8 +2,8 @@
2
2
  * entwurf-v2-production — 5d-2b: the ctx-free PRODUCTION assembly of `runEntwurfV2`'s deps.
3
3
  * `makeProductionEntwurfV2Deps(opts)` wires the real 5b decider IO seams + the three 5c
4
4
  * transport hands into one `EntwurfV2RunDeps` (`{decide, executor}`). It touches NO
5
- * `ExtensionContext`/`ExtensionAPI` — the wiring SITE (5d-3, entwurf-control.ts) builds the
6
- * `senderProvider` from `buildLocalSenderEnvelope(ctx)` and passes it in, so this module
5
+ * `ExtensionContext`/`ExtensionAPI` — the wiring SITE (the entwurf-bridge `entwurf_v2`
6
+ * handler; the pi-native one left with #125) builds the `senderProvider` and passes it in, so this module
7
7
  * stays in `lib/` with the rest of the gate-/smoke-testable v2 core.
8
8
  *
9
9
  * Three invariants this factory exists to guarantee (each gate-proven):
@@ -1,11 +1,9 @@
1
1
  /**
2
2
  * entwurf-v2-surface — 5d-3a: the ctx-free SURFACE adapter that joins a surface's raw params
3
3
  * to `runEntwurfV2` and renders the outcome-rich result back to a human/tool string. It is the
4
- * ONE place a surface (pi-native `entwurf_v2` tool now; the MCP `entwurf_v2` verb in 5d-3b)
5
- * crosses into the v2 fence — so `entwurf-control.ts` (a root-tsc, emit-capable surface that
6
- * CANNOT statically import the `.ts`-extension fence without TS5097) reaches this via a
7
- * NON-LITERAL dynamic import, and the MCP bridge (already a `.ts`-import consumer) imports it
8
- * directly. Either way the ctx binding stays OUT of the fence: the caller builds
4
+ * ONE place a surface (the MCP `entwurf_v2` verb — the only one since #125 retired the
5
+ * pi-native copy) crosses into the v2 fence; the MCP bridge (a `.ts`-import consumer) imports
6
+ * it directly. Either way the ctx binding stays OUT of the fence: the caller builds
9
7
  * `senderProvider` from its own envelope source and passes it in `opts`.
10
8
  *
11
9
  * Three exports:
@@ -217,9 +217,9 @@ const REJECT_HINT = {
217
217
  "launch-refused": "tmux was never asked to open a window",
218
218
  };
219
219
  /**
220
- * ONE renderer for both surfaces — the same reason `mux-fresh-call` has one: the two
221
- * registrations are separate literals, and a shared renderer is what keeps the operator-visible
222
- * answer from drifting apart between native pi and the MCP bridge.
220
+ * ONE renderer — the same reason `mux-fresh-call` has one: it keeps the operator-visible answer
221
+ * out of the registration literal. (Since #125 the MCP bridge is the only registration; the
222
+ * native pi copy it once had to agree with is retired.)
223
223
  *
224
224
  * The success text keeps the two receipts visibly apart, including in the good case. A reader
225
225
  * who sees "window opened" and "socket alive" as one sentence has been told the resume worked;