@junghanacs/entwurf 0.25.1 → 0.30.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 (86) hide show
  1. package/AGENTS.md +1 -1
  2. package/BASELINE.md +1 -1
  3. package/CHANGELOG.md +190 -0
  4. package/DELIVERY.md +1 -1
  5. package/README.md +183 -674
  6. package/VERIFY.md +21 -17
  7. package/demo/README.md +1 -1
  8. package/docs/acp-backend-rail.md +115 -18
  9. package/docs/external-mcp-host.md +1 -1
  10. package/docs/setup-clean-host.md +346 -10
  11. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +27 -4
  12. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/backend-adapter.js +55 -1
  13. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-peers-render.js +1 -1
  14. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-decider.js +2 -2
  15. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-mailbox.js +2 -2
  16. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-production.js +2 -2
  17. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-surface.js +3 -5
  18. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-visible-resume.js +3 -3
  19. package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-composition.js +13 -6
  20. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-mailbox-body.js +1 -1
  21. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +3 -3
  22. package/mcp/entwurf-bridge/src/index.ts +30 -3
  23. package/package.json +13 -13
  24. package/pi-extensions/entwurf-control.ts +50 -637
  25. package/pi-extensions/lib/acp/backend-adapter.ts +81 -1
  26. package/pi-extensions/lib/acp/backend.ts +41 -5
  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 +177 -53
  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 +250 -5
  44. package/scripts/check-herdr-plugin-build.ts +159 -57
  45. package/scripts/check-install-surface.ts +3 -2
  46. package/scripts/check-mux-resume-call.ts +24 -35
  47. package/scripts/check-pi-bridge-sender.ts +130 -0
  48. package/scripts/check-pi-mcp-bridge.ts +251 -0
  49. package/scripts/fixtures/herdr-supply.json +8 -8
  50. package/scripts/lib/codex-fresh-live-protocol.ts +17 -5
  51. package/scripts/lib/codex-fresh-source-receipts.ts +21 -2
  52. package/scripts/lib/mutation-qualify.ts +269 -44
  53. package/scripts/lib/pi-mcp-bridge-probe.mjs +170 -0
  54. package/scripts/meta-bridge-doctor.sh +16 -5
  55. package/scripts/mutants/acp-foreground-only.json +17 -0
  56. package/scripts/mutants/acp-prompt-lifecycle.json +2 -2
  57. package/scripts/mutants/acp-vendor-rejection.json +62 -0
  58. package/scripts/mutants/codex-native.json +11 -11
  59. package/scripts/mutants/control-send-receipt.json +6 -4
  60. package/scripts/mutants/copilot-birth.json +2 -1
  61. package/scripts/mutants/fresh-call-dispatch.json +3 -14
  62. package/scripts/mutants/gate-qualification.json +59 -0
  63. package/scripts/mutants/herdr-plugin-build.json +11 -0
  64. package/scripts/mutants/herdr-supply.json +3 -3
  65. package/scripts/mutants/mux-fresh-call.json +43 -45
  66. package/scripts/mutants/mux-resume-call.json +11 -35
  67. package/scripts/mutants/pack-install.json +15 -4
  68. package/scripts/mutants/pi-bridge-sender.json +54 -0
  69. package/scripts/mutants/pi-floor.json +6 -2
  70. package/scripts/mutants/pi-mcp-bridge.json +36 -0
  71. package/scripts/mutants/pi-mcp-register.json +51 -0
  72. package/scripts/mutants/v2-surface.json +6 -6
  73. package/scripts/resident-rpc-drive.ts +5 -1
  74. package/scripts/smoke-acp-bundled-mcp-live.ts +3 -1
  75. package/scripts/smoke-acp-cortex-live.ts +3 -1
  76. package/scripts/smoke-acp-raw-turn-live.ts +1 -1
  77. package/scripts/smoke-acp-session-reuse-live.ts +29 -12
  78. package/scripts/smoke-acp-socket-citizen-live.ts +3 -1
  79. package/scripts/smoke-acp-v2-send-live.ts +3 -1
  80. package/scripts/smoke-codex-fresh-live.ts +20 -1
  81. package/scripts/smoke-entwurf-chain-live.ts +6 -2
  82. package/scripts/smoke-entwurf-v2-matrix-live.ts +3 -1
  83. package/scripts/smoke-herdr-fresh-call-live.ts +21 -7
  84. package/scripts/smoke-herdr-raw-install-live.sh +1 -1
  85. package/scripts/smoke-mux-lifecycle-live.ts +1 -1
  86. package/scripts/smoke-resident-garden-guard.sh +3 -1
package/README.md CHANGED
@@ -1,748 +1,257 @@
1
1
  # entwurf
2
2
 
3
- `entwurf` is a garden-citizen dispatch substrate: a thin bridge that lets already-existing agent harnesses address one another by **garden id** without pretending to own each other's transcript, auth, or runtime.
3
+ `entwurf` is a garden-citizen dispatch substrate: a thin bridge that lets independent
4
+ agent harnesses address one another by **garden id**, without owning each other's
5
+ transcript, authentication, tools, or runtime.
4
6
 
5
7
  ![entwurf — a forged screwdriver for garden-citizen dispatch](docs/assets/entwurf-hero.jpg)
6
8
 
7
9
  [![npm](https://img.shields.io/npm/v/@junghanacs/entwurf?logo=npm)](https://www.npmjs.com/package/@junghanacs/entwurf) · maintained by [junghanacs.com](https://junghanacs.com/)
8
10
 
9
- npm package: <https://www.npmjs.com/package/@junghanacs/entwurf>
10
-
11
- **Short answers for common questions:** [FAQ.md](./FAQ.md).
12
-
13
- Legacy package: [`@junghanacs/pi-shell-acp`](https://www.npmjs.com/package/@junghanacs/pi-shell-acp). `entwurf` is its 0.12+ successor line: the same work renamed around the garden-citizen dispatch substrate rather than the pi adapter.
14
-
15
- > **Repository shape.** This repo is **entwurf-core (v2 dispatch) + native-harness bridges + a pi adapter + an ACP plugin**. Pi is one adapter, not the project subject. Claude Code, GitHub Copilot CLI, and OMP (`omp`) are shipped self-fetch citizens; Antigravity (`agy`) is shipped native-push. Codex CLI is a **native-push citizen, supported in 0.21.0 on Linux** over the operator-owned app-server: vendor-trusted user-scope birth, strict request-scoped identity, loaded-thread probe, one-shot queue delivery, and visible fresh beside the CALLER's own pane. Unrestricted attached-TUI seat inference is not supported or claimed; #95 D1 retired the fixed `codex` tmux home on 2026-09-16, so the operator seats the app-server wherever they like and a Codex caller opens its sibling beside its own TUI pane. Codex remains native so it keeps its vendor tools, delegation, and work context; this is not another way to buy or expose GPT through ACP.
11
+ A sibling is a visible session with its own runtime boundary, not a disposable
12
+ worker or a subagent of a new central harness. Entwurf supplies identity, delivery,
13
+ receipts and named lifecycle verbs; the operator and native harnesses keep steering.
14
+ Git branches, worktrees, file isolation and internal delegation remain their concern.
15
+ [Short answers: FAQ](./FAQ.md).
16
16
 
17
17
  ```text
18
- Claude Code / Copilot / Codex / agy / omp / pi
19
- → garden id
18
+ pi / Claude Code / Copilot / OMP / Antigravity / Codex
19
+ → record-backed garden id
20
20
  → entwurf_v2
21
- → control-socket | meta-mailbox | native-push
22
- ```
23
-
24
- [`entwurf_v2`](#entwurf_v2--canonical-dispatch-verb) is the canonical dispatch surface over *existing* garden citizens — live control-socket send, meta-mailbox enqueue, and native-push into a live Antigravity conversation or loaded Codex thread. It starts no process on any rail. `entwurf_fresh_call` is separate lifecycle. With placement omitted the seat follows the CALLER: a Codex citizen opens beside its own TUI pane, found by the `thread-id` in that pane's terminal title, and 0 or 2+ matching panes reject with no fallback; every other caller opens in its own session. A missing app-server rejects before launch, and Entwurf never creates or supervises it. An explicit `placement.tmuxSession` remains an expert override. Exact 0.153.4 source confirms that arbitrary attached-TUI request→seat inference is unavailable, so Entwurf makes no such claim. Codex has no resume surface.
25
-
26
- **Garden id is deliberate vocabulary.** It is not a decorative synonym for session id, worker, delegate, or subagent. The unfamiliar word is a guard: each harness keeps its own identity and transcript, while `entwurf` supplies a narrow addressable surface between siblings.
27
-
28
- **A narrow harness tool surface is discipline, not a missing feature.** When entwurf drives a backend the way pi taught — the ACP Claude session, a pi-native sibling — it runs without a sub-agent tool or a todo tool, on a narrow tool surface in auto-approve (`yolo`) mode (the ACP backend yolo-runs inside its isolated overlay). That restraint is the point: it keeps the one forged screwdriver from drifting into a second orchestrator, and keeps the operator's own driver — not a hidden agent swarm — the thing actually steering. See [AGENTS.md](./AGENTS.md) North Star.
29
-
30
- **The ACP plugin is one ingress, not the boundary.** It re-enters as a pi provider/model on a host `--entwurf-control` session that is *already* a v2 socket-citizen; it does not mint its own socket / peers / citizen layer (see [AGENTS.md](./AGENTS.md) §ACP Plugin Boundary). No OAuth proxy, no subscription bypass, no CLI transcript scraping, no Claude Code emulation.
31
-
32
- ```text
33
- pi --entwurf-control
34
- → entwurf ACP plugin
35
- → claude-agent-acp
36
- → Claude backend under the operator's local auth
37
- ```
38
-
39
- **Native bridges reach beyond ACP transport.** A global `SessionStart` hook registers native Claude Code sessions as **garden-native meta-sessions** with a garden id, a mailbox, and a trusted sender marker. That makes an already-running Claude Code terminal addressable through `entwurf_v2` (the mailbox path), self-identifying through `entwurf_self`, and replyable by garden id — without turning pi into a second harness or importing Claude's transcript.
40
-
41
- ```text
42
- native Claude Code
43
- → SessionStart hook
44
- → mailbox-backed meta-session <garden-id>
45
- → entwurf-bridge MCP
46
- → entwurf_self | entwurf_v2 | entwurf_inbox_read
21
+ → control-socket | self-fetch mailbox | native-push
47
22
  ```
48
23
 
49
- Copilot uses another self-fetch rail. Its birth hook mints the V3 record on the first prompt,
50
- the MCP hand supplies `entwurf_inbox_read`, and a first-party extension arms the receiver marker
51
- and rings a doorbell for queued bodies. Supported launch is `entwurf copilot`; visible fresh uses
52
- that same managed invocation so extension scan, receiver preconditions, model, and permission
53
- policy are present before the sibling calls back with its garden id.
54
-
55
- Antigravity uses a separate shipped rail. Its `PreInvocation` hook births or re-attaches the conversation by native `conversationId`, writes a record-backed sender marker, and leaves delivery to the live native LS gRPC route. There is no mailbox or receiver marker on this rail: `entwurf_v2` probes the conversation and direct-injects with native-push.
24
+ The package holds the v2 core, native-harness bridges, a pi adapter and an ACP
25
+ plugin. ACP is one ingress, not the boundary; pi is one adapter, not the center.
26
+ Claude and Cortex Code are ACP backends. Codex and Antigravity stay native so they
27
+ retain their own tools and work context. Capabilities differ by rail: a shipped
28
+ citizen is not a promise of every lifecycle operation.
56
29
 
57
- ```text
58
- native Antigravity / agy
59
- → PreInvocation imprint → meta-session <garden-id>
60
- → entwurf-bridge MCP sender identity
61
- ↔ entwurf_v2 native-push
62
- ```
63
-
64
- Claude's `install-meta-bridge`, Copilot's four `install-copilot-*` surfaces, agy's
65
- `install-agy-{bridge,statusline,hooks}`, OMP's four `install-omp-{bridge,mcp,config,receive}`,
66
- and Codex's birth, MCP and statusline units are distinct because their
67
- lifecycle and delivery transports are genuinely different. See [DELIVERY.md](./DELIVERY.md).
68
-
69
- > **Direction.** Inverse of [`pi-acp`](https://github.com/svkozak/pi-acp). `pi-acp` lets external ACP clients talk *to* pi; `entwurf` lets garden citizens talk across harness boundaries — with pi as one adapter, not the center.
70
-
71
- ## Concept primer
30
+ ## Install
72
31
 
73
- A few words that look unusual for a coding tool.
32
+ **Choose by where you work. Neither route installs a harness, subscription or login.**
74
33
 
75
- - **Entwurf** (기투, projection-of-self) — sibling sessions with their own runtime boundary. Not "delegate," not "worker," not "sub-agent." Opening a visible sibling (`entwurf_fresh_call`), live peer messaging (`entwurf_v2`) and reopening a dormant one (`entwurf_resume_call`) are first-class; the hidden background resume that preceded the last of those was withdrawn under the visible-first rule.
76
- - **Garden / garden id** — the garden is the shared address space where independent harness sessions become citizens without losing their own runtime or transcript. A garden id is the stable address of one such citizen (for pi, a garden-native session id like `YYYYMMDDTHHMMSS-<6hex>`; for native harnesses, a meta-session id minted from an authoritative lifecycle hook — Claude `SessionStart`, Codex `SessionStart`, Copilot's first-prompt birth hook, agy `PreInvocation`, and for OMP an in-process extension bound to both session edges that mints only the visible `mode === "tui"` host). It is not a worker name and not proof that pi owns the session. The same-looking id may name a live control socket, a dormant pi record, a mailbox-backed native session, or a native-push conversation/thread, so routing always reads the record and live rail facts first.
77
- - **Engraving** — optional short operator text delivered through each backend's native identity carrier. Not a giant hidden prompt, not a tool catalog.
78
- - **MCP** — in this repo, MCP is just the transport by which ACP-backed sessions receive pi capabilities that native pi exposes directly as extensions. It is not a general MCP platform. Explicit `entwurfProvider.mcpServers` only; no ambient `~/.mcp.json` scanning, no automatic retrieval. The same `entwurf-bridge` entry can also be wired into another host's MCP catalog (Claude Code, Copilot, Codex, Antigravity, OMP, …) when the operator chooses. `entwurf_self` returns an authoritative pi-session or trusted meta-session identity envelope; `entwurf_v2` requires an authoritative sender by default (#50 C4) — a plain external MCP host with no identity lane is refused unless the operator explicitly wires the documented anonymous hatch, and even then it is never replyable.
79
- - **Session persistence** — re-attaches pi to the same remote ACP session. Does not hydrate backend transcripts into pi history.
34
+ ### Direct — ordinary shell or tmux
80
35
 
81
- ## Install
82
-
83
- **Two installation routes, and the one you want depends on where you work.**
84
-
85
- - **Direct** — `npm install -g @junghanacs/entwurf`, then `entwurf setup <project>`. This is the
86
- general route: it composes every harness it FINDS on the host — pi, Claude Code, Codex, Copilot,
87
- Antigravity, the ACP rail — and reports each one PASS / SKIP / FAIL. Use it in an ordinary shell,
88
- in tmux, anywhere.
89
- - **Herdr workbench** — `herdr plugin install` from inside a Herdr session. It activates Entwurf for
90
- the pi and Claude Code sessions Herdr has integrated, and only those. Contract, evidence grades
91
- and its refusals: [plugins/herdr/README.md](plugins/herdr/README.md).
92
-
93
- Neither route installs a harness, a subscription, or a login (Hard Rule 17). Everything below this
94
- paragraph describes the direct route.
95
-
96
- **Platform evidence, in one breath.** The Entwurf-only install surface on
97
- macOS is CERTIFIED (CI) (`macos-install-surface`). On macOS every
98
- garden-native harness rail, marker/request join, ACP turn, and mux is NOT CERTIFIED —
99
- pending physical host. On Linux the released harness rails are the certified axis;
100
- Codex joined them in 0.21.0. Its explicit-home first admission was measured on 2026-09-12 — a real Pi
101
- outside `codex` opened Codex and outbound Pi inside it, with exact callbacks and delivery both ways —
102
- and the release acceptance that followed closed `check:full`, the LIVE release gate at MUST 24/0/0,
103
- and a 475/475 qualification.
104
- Unrestricted attached-TUI placement
105
- is unsupported rather than a release blocker.
106
- Native Windows is UNSUPPORTED.
107
- CERTIFIED (CI) is weaker than a physical-host doctor green.
108
- The npm package has no `os` restriction; that is installability, not a
109
- support claim.
110
-
111
- **macOS landed in 0.20.0.** That release added macOS support for Entwurf's
112
- install surface: the npm package installs, `entwurf --help` and `entwurf
113
- check-bridge` boot, and `entwurf setup` composes the integrations it finds.
114
- The four installers that previously rejected Darwin by platform name no longer
115
- do. That surface was validated only in macOS CI because the developers did not
116
- have a physical Mac. So macOS native-harness rails—marker join, an ACP turn,
117
- and mux included—are **NOT CERTIFIED — pending physical host**; setup writes
118
- their wiring but reports them non-green. Linux's certified rail status is
119
- unchanged.
120
-
121
- On Darwin, `entwurf setup` that detects a harness is intentionally
122
- non-green: the wiring WAS written and nothing failed to install, but the
123
- harness rail is NOT CERTIFIED — pending physical host. That FAIL is not
124
- an install defect. An Entwurf-only host (no harness on PATH) stays green.
125
-
126
- A physical Mac can send host facts without installing or logging in:
127
- `scripts/raw-macos-measure/probe.sh` (`/bin/sh`, ~3.5s). Measurement,
128
- not certification.
129
-
130
- `entwurf` is a neutral npm package first. Get the package, then run **`entwurf setup
131
- <project>`** — one command, the same front door from an npm global install, an npm
132
- project-local install, or a source checkout. It composes every harness it finds on the
133
- host and reports each one PASS / SKIP / FAIL. You are not meant to assemble the parts by
134
- hand; the per-harness installers further down are the repair surface for when one unit
135
- needs to be redone alone. Pi is still the adapter that hosts the ACP plugin and live
136
- control-socket surface, but the base install is **not** `pi install npm:...` anymore.
137
-
138
- The package exposes six bins:
139
-
140
- - `entwurf` → `run.sh` (installer, checks, native-bridge doctors/installers)
141
- - `entwurf-bridge` → the MCP stdio launcher (`mcp/entwurf-bridge/start.sh`)
142
- - `entwurf-statusline` → the Claude Code statusline renderer (`scripts/meta-bridge-statusline.sh`)
143
- - `entwurf-agy-statusline` → the Antigravity garden-id statusline renderer (`scripts/agy-statusline.sh`)
144
- - `entwurf-agy-imprint` → the Antigravity `PreInvocation` birth/sender hook (`scripts/agy-imprint.sh`)
145
- - `entwurf-copilot-statusline` → the Copilot CLI garden-id footer renderer (`scripts/copilot-statusline.sh`)
146
-
147
- The bridge/renderers/hook use stable bin names so package upgrades do not bake versioned package-store paths into native-harness settings.
148
-
149
- Installing Entwurf installs **Entwurf only**: its package bytes, six bins, bridge, and
150
- integration artifacts. It does not install `pi`, Claude Code, Copilot CLI, Codex, agy, omp, Cortex,
151
- or any other harness runtime. Those are operator choices and may all be absent. The bridge also
152
- does not provide credentials, tokens, subscription access, or an auth bypass; whatever an
153
- operator-installed harness already trusts is what Entwurf can use. `setup` is composition, not
154
- recruitment: it may wire a harness that is present, but never downloads one to make a matrix cell
155
- look complete.
156
-
157
- ### From npm — user/global install
36
+ Prerequisites: **Node >=24.0.0**, Python 3 for `setup`, and any harnesses you choose
37
+ to use, already installed and authenticated. Pi is optional-by-presence, supported
38
+ range `>=1.0.0 <1.1`; Claude Code supported floor `>=2.1.217` is required for its
39
+ managed exec-hook lifecycle. pnpm is needed only for source-checkout setup.
158
40
 
159
41
  ```bash
160
42
  npm install -g @junghanacs/entwurf
161
-
162
43
  entwurf setup /path/to/your-project
163
44
  entwurf check-bridge
164
45
  ```
165
46
 
166
- `setup` wires the target project for the pi adapter / ACP plugin lane and composes
167
- whatever native harnesses are present. The global install is the easiest path when
168
- Claude Code's USER-scope MCP registration should work from every cwd.
169
-
170
- #### Codex: what you still type by hand
171
-
172
- `setup` writes every byte Entwurf owns, and three things are deliberately left to you. They
173
- are the only manual steps between a fresh install and calling a Codex sibling, and all three are
174
- answers only you can give:
175
-
176
- 1. **Trust the birth hook once, in a visible plain Codex.** Answer `Trust all and continue`
177
- and send one first turn. Nobody can do this for you: the receipt is the vendor's record of
178
- *your* security decision, and writing it ourselves would turn a prompt into a silent
179
- install. Until it exists, `setup` and `doctor-codex-birth` stay honestly non-green and say
180
- exactly this.
181
- 2. **Start the app-server, in a detached tmux session that is NOT where you work.**
182
-
183
- ```bash
184
- entwurf codex-app-server
185
- ```
186
-
187
- One command for the vendor string nobody wants to type. It `exec`s
188
- `codex app-server --listen unix://<default socket>` right there — so Ctrl-C is yours, and
189
- Entwurf neither supervises nor restarts it. The session matters because the MCP bridge is
190
- this server's child and inherits its `TMUX`: that tmux server is the one caller-seat
191
- lookups read. The launcher prints which seat it got rather than guessing one for you.
192
- 3. **Answer the folder-consent prompt once for each directory you open siblings in.** Codex
193
- records a direct decision per EXACT directory — not the parent, not the git root — and a
194
- directory with no answer anywhere opens a consent screen instead of running a first turn.
195
- `entwurf_fresh_call` prints `codex-launch-cwd-undecided` and opens the window anyway — the
196
- screen is self-repairing, and answering it once is the whole fix. (Two neighbours differ: a
197
- project `.codex` layer can consent with no entry at all, and a directory inside an explicitly
198
- untrusted project is noted as `codex-launch-cwd-untrusted-ancestor` because the vendor answers
199
- that one with an error naming the repository root instead of a prompt.) You can also do it up
200
- front:
201
-
202
- ```bash
203
- codex -C <the directory you will launch siblings in> # answer the prompt, then quit
204
- ```
205
-
206
- Same reason as step 1: it is your security decision, and writing it ourselves would turn a
207
- prompt into a silent install.
208
-
209
- Then `entwurf_fresh_call` with `backend: "codex"` works. Attaching your own visible Codex TUI
210
- (`codex --remote "unix://$CODEX_HOME/app-server-control/app-server-control.sock"`) is optional
211
- and independent — a sibling Entwurf opens brings its own.
212
-
213
- > **Observation, not a verb (#95).** That `--remote` string is exactly as unfriendly as the
214
- > `--listen` one was, and it has no managed spelling. It is deliberately left alone: it is how
215
- > an operator attaches their OWN terminal, not how a sibling is opened, and `entwurf_fresh_call`
216
- > already spells it internally for the siblings it launches. Whether the operator-facing half
217
- > deserves its own verb is open.
218
-
219
- ### From npm — project-local install
220
-
221
- ```bash
222
- cd /path/to/your-project
223
- npm install --save-dev @junghanacs/entwurf
224
-
225
- npx entwurf setup .
226
- npx entwurf check-bridge
227
- ```
47
+ `setup` composes each harness it finds and reports PASS / SKIP / FAIL. An absent
48
+ harness is an explicit SKIP; a detected incomplete/below-floor integration is a
49
+ named FAIL with nonzero exit, not a false green. `check-bridge` proves the installed
50
+ MCP bytes boot and list tools without backend auth; it does **not** certify a real
51
+ model turn or native receive rail.
228
52
 
229
- For an npm upgrade, rerun `setup` in the same scope (use
230
- `@junghanacs/entwurf@latest` when you want the registry's stable line explicitly),
231
- then make the first check from that same scope: `entwurf check-bridge` for a global
232
- install or `npx entwurf check-bridge` for a project-local install. Native-harness
233
- repair and process restarts remain a separate post-upgrade step below.
53
+ [Clean-host setup](./docs/setup-clean-host.md) owns project-local/source installs,
54
+ config paths, optional backends, doctors, upgrade/repair and uninstall.
55
+ Codex also needs your own hook-trust decision, app-server and exact-directory
56
+ consent: [the operator steps](./docs/setup-clean-host.md#three-operator-decisions-before-fresh).
57
+ Plain MCP registration alone does not make a garden citizen.
234
58
 
235
- `entwurf install <project>` is the narrower repair leaf: it writes only
236
- `.pi/settings.json` in the target project, with the absolute path to the installed
237
- `entwurf-bridge` launcher, and composes no harness. Reach for it when the pi wiring
238
- alone needs redoing. (The old `~/.pi/agent/` target-registry link is gone — #50 C3;
239
- nothing reads it.)
59
+ **Platform bounds:** Linux is the certified harness-rail axis. macOS Entwurf-only
60
+ installation is **CERTIFIED (CI)**, weaker than a physical-host doctor; its native
61
+ harness rails, ACP and mux remain **NOT CERTIFIED — pending physical host**.
62
+ Detected harness wiring on Darwin can therefore be written while setup stays
63
+ non-green. Native Windows is **UNSUPPORTED**. The package has no npm `os`
64
+ restriction; that is not certification of every platform's harness rails.
240
65
 
241
- To register the bridge in an MCP host by hand from a project-local install, point it at
242
- `node_modules/.bin/entwurf-bridge` — see
243
- [External MCP registration](#external-mcp-registration).
244
-
245
- ### From source — development clone
246
-
247
- ```bash
248
- git clone https://github.com/junghan0611/entwurf ~/repos/gh/entwurf
249
- cd ~/repos/gh/entwurf
250
-
251
- ./run.sh setup /path/to/your-project
252
- ./run.sh check-bridge
253
- ```
254
-
255
- The full source setup requires Node 24, pnpm, and Python 3 on PATH. Harnesses are
256
- optional-by-presence: `setup` runs the frozen dependency install, then composes what the operator
257
- already installed — a compatible `pi` (`>=0.87.1 <0.88`), Claude Code, agy, and the Copilot CLI
258
- each get their wiring completed when detected, an absent harness is an explicit zero-state SKIP,
259
- and a detected harness that cannot be completed (including a below-floor `pi`) is a named FAIL
260
- that makes setup exit nonzero. `setup` never installs a harness binary or touches a credential
261
- store. It also exposes stable commands under `~/.local/bin`, including `entwurf` → this
262
- checkout's `run.sh`, so managed Copilot fresh does not depend on an unrelated global npm/pnpm
263
- installation. A detected `copilot` composes all four of its native units (birth → MCP →
264
- receiver → visible footer) in one go. Package consumers
265
- run the same `entwurf setup <project>` through their npm-provided bin: installed mode is decided
266
- by name first, skips the source-only pnpm bootstrap entirely, and reports the stable commands as
267
- already provided by npm bin linking.
268
-
269
- The pi user-scope registration is ONE shared entry with a recorded owner (#86 C2): installing
270
- from a second checkout or npm root does not silently steal it — normal `install`/`setup` refuse
271
- with zero settings bytes written, `entwurf takeover-user-scope` is the operator-explicit move,
272
- `entwurf doctor-pi-package` names the ownership verdict, and `entwurf remove-user-scope` is
273
- same-owner-only. The full contract — atomicity across the package and provider halves, the
274
- split verdict over an operator's own override, legacy adoption, and the `managedSettingsPath`
275
- binding — is [docs/setup-clean-host.md §1.1](./docs/setup-clean-host.md#11-user-scope-ownership-one-shared-registration-one-recorded-owner).
276
-
277
- A development clone runs the bridge source through Node's strip-types path;
278
- an npm-installed package runs the prebuilt JS under `mcp/entwurf-bridge/dist/`
279
- because Node refuses to strip `.ts` files under `node_modules`. The dev launcher's
280
- source path means `./run.sh check-bridge` needs no build. After `git pull`, however,
281
- run `pnpm install` when the lockfile changed and run `pnpm run build-bridge` before
282
- artifact-consuming checks such as `./run.sh check-bridge-delivery`: `dist/` is
283
- gitignored and may be absent or stale immediately after a checkout or pull.
284
-
285
- ### Pi adapter / ACP plugin lane
286
-
287
- This rail is CERTIFIED on Linux; on macOS it is NOT CERTIFIED — pending
288
- physical host (control sockets, mux, ACP plugin hosting).
289
-
290
- To use the `entwurf` provider inside pi, install a compatible pi binary
291
- separately (`@earendil-works/pi-coding-agent >=0.87.1 <0.88`). Then point pi at
292
- the npm-installed package or development clone:
293
-
294
- ```bash
295
- # global npm install path
296
- pi -e "$(npm root -g)/@junghanacs/entwurf" --list-models entwurf
297
-
298
- # project-local install path
299
- pi -e ./node_modules/@junghanacs/entwurf --list-models entwurf
300
- ```
301
-
302
- For daily operator sessions, launch pi with `--entwurf-control` — no id
303
- injection; the meta-record mints the garden address (see [Garden launcher](#garden-launcher)). Older pi
304
- versions may silently miss the provider/extension surface, so treat the pi floor
305
- as release-critical for the ACP/plugin lane. A host that only uses
306
- `entwurf-bridge` from Claude Code / Copilot / Codex / Antigravity / OMP does not need pi at all for
307
- delivery: no `entwurf_v2` rail launches a pi process. OMP is a pi fork, but it is its own binary and
308
- resolves its own agent directory, so that lineage does not reintroduce a `pi` requirement either. That external-only shape works with the same
309
- `setup` command: pi is optional-by-presence there, so a pi-less host simply gets an explicit pi
310
- SKIP while the detected harnesses are composed.
311
-
312
- ### Native harness repair and doctors
313
-
314
- A plain MCP registration exposes the bridge tools; a **garden-native** session also
315
- needs entwurf's lifecycle and identity unit. `setup` composes every user-scope unit
316
- for each detected harness, including Codex's birth hook — its paths are the operator's
317
- own, so nothing here asks for root. What setup cannot do is answer the vendor's one-time
318
- hook-trust prompt: only the operator can, in their own visible Codex, so a first setup on
319
- a Codex host writes every byte and stays honestly non-green until they have. This is the
320
- repair surface: each unit has its own installer, doctor, and inverse.
321
-
322
- - **Claude Code** (Linux CERTIFIED; macOS NOT CERTIFIED — pending physical host) — `install-meta-bridge`, `doctor-meta-bridge`.
323
- - **Antigravity / agy** (Linux CERTIFIED; macOS NOT CERTIFIED — pending physical host) — `install-agy-bridge`, `install-agy-statusline`, `install-agy-hooks`, each with a matching `doctor-agy-*`.
324
- - **GitHub Copilot CLI** (Linux CERTIFIED; macOS NOT CERTIFIED — pending physical host) — four independent units, four independent failure modes: `install-copilot-bridge` (birth: garden id + who-sent, on the first prompt), `install-copilot-mcp` (the entwurf tool hand, where `entwurf_inbox_read` lives), `install-copilot-receive` (the receiver extension: doorbell + receiver marker), `install-copilot-statusline` (optional for a manual citizen, required for supported fresh) — each with a matching `doctor-copilot-*` and `uninstall-copilot-*`.
325
- - **OMP (`omp`)** (Linux CERTIFIED; macOS NOT CERTIFIED — pending physical host) — four units, in-process extensions rather than launchers: `install-omp-bridge` (birth: the `mode === "tui"` visible host, its garden id on the status line, and who-sent), `install-omp-mcp` (the omp-native `entwurf-bridge` entry), `install-omp-config` (the one operator setting `tools: xdev: false`, without which the vendor mounts MCP tools as `xd://` devices the model cannot call), `install-omp-receive` (the receiver extension: mailbox watch + announce-only doorbell) — each with a matching `uninstall-omp-*`, and a `doctor-omp-*` for all but the setting, whose runtime axis `doctor-omp-mcp` owns. The setting writer owns exactly the lines it adds and refuses an explicit operator `tools: xdev: true` by name rather than overwriting it.
326
- - **OpenAI Codex CLI** (supported in 0.21.0 on Linux; macOS NOT CERTIFIED — pending physical host) — `install-codex-birth` publishes the `SessionStart` declaration into `$CODEX_HOME/hooks.json` with its launcher closure under `$XDG_DATA_HOME/entwurf/codex-birth`, all operator-owned; it owns **that declaration, not the file** — another integration's `SessionStart` group coexists (the vendor keys trust per `<path>:<event>:<group>:<handler>`), so install appends, uninstall splices only ours out, foreign bytes survive both, and `doctor-codex-birth` reports neighbours in a FOREIGN section it certifies nothing in; the vendor trust receipt is read at the index our declaration was measured at, is the operator's single answer, and `doctor-codex-birth` reports it as its own axis (present, or red with the exact instruction — never computed, never written); `install-codex-mcp` owns `[mcp_servers.entwurf-bridge]`, including the `env_vars` boundary for `CODEX_HOME`, Entwurf garden/control roots, and the app-server's `TMUX`/`TMUX_PANE`; `install-codex-statusline` owns `thread-title` in `tui.status_line`; `install-codex-terminal-title` owns `thread-id` in `tui.terminal_title`. Each has a matching doctor and inverse. **One Codex precondition is the vendor's own and no installer supplies it: the directory a sibling starts in must already be answered in this Codex.** A direct consent decision is recorded per exact directory on this rail — not the parent, not the git root — and a directory the vendor has no answer for at all opens a consent screen instead of running a first turn, so `entwurf_fresh_call` prints `codex-launch-cwd-undecided` and opens the window anyway; that screen is self-repairing, and answering it once teaches the vendor the directory for good. **Nothing here refuses a launch** — the note is a diagnostic, and the one caller that treats it as a precondition is `smoke-codex-fresh-live`, where nobody is at the keyboard. Two neighbouring cases read differently: a directory answered `untrusted` is not noted at all (the vendor skips its screen on this rail, so that turn starts), and a directory INSIDE an explicitly untrusted project is noted as `codex-launch-cwd-untrusted-ancestor` because the vendor answers that one with an error naming the repository root rather than a prompt. The check is a narrow local read of the operator's own config, never the vendor's verdict: the vendor also consents through project layers it reads from its app-server, and everything the check cannot see stays silent. The operator owns the app-server and seats it wherever they like; Entwurf never creates or supervises it. Omitted fresh placement follows the CALLER, never the backend being opened: an explicit `placement` always wins, then a Codex CALLER opens beside its own TUI pane — located by the `thread-id` that pane's terminal title carries, with 0 or 2+ matching panes refused and no fallback — then the caller's own session. (#95 first shipped a fixed `codex` home for omitted-placement Codex TARGETS; #95 D1 retired it on 2026-09-16 once the anchor made the room unnecessary.) A pane title is a placement input only, never an address, liveness or delivery fact. Exact 0.153.4 source's missing per-client carrier still bounds placing a sibling beside a TUI whose thread nobody named as unsupported.
327
-
328
- Run them as `entwurf <command>`. Which unit a doctor's refusal names, and the clean-host
329
- walk-through for each harness, live in [docs/setup-clean-host.md](./docs/setup-clean-host.md).
330
-
331
- #### Launching Copilot as a garden citizen — `entwurf copilot`
332
-
333
- Copilot only scans for extensions when its CLI is started with
334
- `COPILOT_CLI_ENABLED_FEATURE_FLAGS=EXTENSIONS`, and when that flag is absent it skips the
335
- scan **silently** — no error, no log line, no receiver. entwurf does not own your shell
336
- and writes nothing to your rc files, so it owns one invocation instead:
337
-
338
- ```bash
339
- entwurf copilot # managed launch, in this terminal
340
- entwurf copilot -p "…" --model gpt-5.4
341
- copilot # the plain vendor CLI, untouched
342
- ```
343
-
344
- `entwurf copilot` execs the vendor CLI in your current terminal — same cwd, same pid,
345
- same exit status, no tmux window and no new citizen (a Copilot session is still born on
346
- its first prompt). Before it launches it verifies that the receiver unit it is about to
347
- promise is actually installed, and refuses with `entwurf install-copilot-receive` if it
348
- is not, rather than starting a session that can never be delivered to.
349
-
350
- **Running it is your consent to its profile.** For that one invocation it adds:
351
-
352
- | Injected | When |
353
- |---|---|
354
- | `COPILOT_CLI_ENABLED_FEATURE_FLAGS=EXTENSIONS` | always — your other feature-flag tokens are preserved, in order, deduplicated |
355
- | `--model auto` | only when you passed no `--model` |
356
- | `--yolo` | only when you passed no explicit permission or surface policy flag |
357
-
358
- The escape hatch is simply to state your own policy: any of `--yolo`, `--allow-all`,
359
- `--allow-all-tools`, `--allow-all-paths`, `--allow-all-urls`, `--allow-tool`,
360
- `--deny-tool`, `--allow-url`, `--deny-url`, `--available-tools` or `--excluded-tools`
361
- suppresses the injected `--yolo`. The narrowing flags are in that list on purpose — adding
362
- `--yolo` beside your `--allow-url=…` would silently widen exactly what you were
363
- restricting. `--allow-all-mcp-server-instructions` (prompt content, not authorization) and
364
- `--autopilot` (a mode) are deliberately not policy flags. Everything you pass is forwarded
365
- byte-identically, injected defaults land before any `--` terminator, and nothing after the
366
- terminator is read as policy. Nothing is written to disk; run plain `copilot` and none of
367
- this applies.
368
-
369
- Why `--yolo` is the default: the managed lane exists so a sibling can wake an idle session,
370
- and draining the mailbox with `entwurf_inbox_read` costs two interactive approvals under
371
- the default permission prompts — which an idle, unattended session is not there to answer.
372
-
373
- Claude Code uses the supported floor `>=2.1.217`; older versions silently discard the
374
- exec-hook `args`, so install and doctor fail loud rather than falling back. After any
375
- upgrade, rerun `entwurf setup <project>` — it re-composes every detected harness, all four
376
- Copilot units included — and restart its existing processes; reach for a single
377
- `install-*` only when one unit needs repair on its own. A claimed Claude host is certified
378
- only when a **new** session using the
379
- installed artifact makes `doctor-meta-bridge` exit 0 with the live owner join.
380
-
381
- The Claude meta-bridge rail is CERTIFIED on Linux. On macOS it is
382
- NOT CERTIFIED — pending physical host. The Darwin install fence is
383
- open; uninstall was already open. Detailed diagnosis and clean-host
384
- steps live in
385
- [docs/setup-clean-host.md](./docs/setup-clean-host.md).
386
-
387
- The active citizen store is V3-only. A store that fails certification is never
388
- silently migrated: quiesce the native sessions, run `entwurf meta-bridge-fresh-cut`,
389
- then reinstall. The cut archives routing records only—never native transcripts or
390
- external memory—and no runtime reads the archive. Exit meanings and the complete
391
- operator contract are in [docs/fresh-cut-policy.md](./docs/fresh-cut-policy.md).
392
-
393
- `entwurf check-bridge` proves the MCP surface boots without backend auth. A real ACP
394
- turn requires `LIVE=1 entwurf smoke-acp-provider-live`; the full release protocol and
395
- host evidence boundaries are [VERIFY.md](./VERIFY.md) and [BASELINE.md](./BASELINE.md).
396
-
397
- > **Extension set — do not filter.** The ACP provider, `entwurf-control`, and
398
- > `model-lock` extensions ship as one set. Disable the package as a whole rather than
399
- > filtering individual entries into a partially wired state.
400
-
401
- ### Backend prerequisites
402
-
403
- **Claude is the reference ACP backend.** The Claude ACP server package (`@agentclientprotocol/claude-agent-acp`, pinned with `@agentclientprotocol/sdk`) ships as a pinned `dependency` of `entwurf`; backend authentication still belongs to the operator's local `claude` CLI / runtime. Once the bridge is installed, the resolver picks the ACP server in this order:
404
-
405
- 1. **`CLAUDE_AGENT_ACP_COMMAND` env override** — explicit override for an alternative binary or a wrapper command.
406
- 2. **`require.resolve(...)` against the bundled package dependency** (`@agentclientprotocol/claude-agent-acp`). This is the default path; no extra global install needed.
407
- 3. **`PATH:claude-agent-acp` fallback** — used when the package resolution fails (e.g. a hand-edited `node_modules`).
408
-
409
- The curated model registry exposes unprefixed Claude ids — `claude-opus-5`, `claude-sonnet-5`,
410
- `claude-fable-5-1` — plus the `cortex-` rows below.
411
- Codex is not an ACP backend. Its native-citizen lane, supported in 0.21.0, is independent so the
412
- session retains Codex's native tools, delegation, and work context: trust-gated hook birth, strict
413
- request `_meta` identity, app-server probe, one-shot native-push, and visible fresh.
414
-
415
- **Snowflake Cortex Code is the second ACP backend** (contract and audit:
416
- [docs/acp-backend-rail.md](./docs/acp-backend-rail.md#cortex-code-audit-d1d10)). Curated ids are
417
- `cortex-auto`, `cortex-claude-opus-5`, `cortex-claude-sonnet-5`, and
418
- `cortex-openai-gpt-5.4`.
419
-
420
- The operator supplies an authenticated `cortex` CLI and selects a connection with
421
- `entwurfProvider.cortexConnection` or `ENTWURF_ACP_CORTEX_CONNECTION`. `CORTEX_HOME`
422
- must be absent: the adapter refuses it because it would bypass the session-scoped
423
- HOME containment. Explicit MCP servers are projected into the overlay-private
424
- `cortex/mcp.json`; only the bridge receives the real operator HOME needed for the
425
- garden store.
426
-
427
- `check-acp-cortex` runs in `pnpm run check:full`. Real acceptance is deliberately on demand:
66
+ ### Herdr workbench
428
67
 
429
68
  ```bash
430
- LIVE=1 ENTWURF_ACP_CORTEX_CONNECTION=<conn> entwurf smoke-acp-cortex-live
69
+ herdr plugin install junghan0611/entwurf/plugins/herdr --yes
431
70
  ```
432
71
 
433
- The aggregate Claude floor does not run this smoke, so silence is not a Cortex PASS.
72
+ This is a separate consumer path: it activates only Herdr-integrated **pi and
73
+ Claude Code**, acquires the locked Entwurf runtime, and adds an observation pane.
74
+ It puts no Entwurf bins on PATH. The
75
+ [Herdr integration guide](https://github.com/junghan0611/entwurf/blob/main/plugins/herdr/README.md)
76
+ owns its prerequisites, first install → first use, evidence and explicit removal.
77
+ The plugin does not own delivery or the Herdr launch rail; a direct-installed
78
+ citizen inside Herdr selects that rail from its own process context too.
434
79
 
435
- ### Emacs frontends
436
-
437
- Works from terminals and from Emacs frontends that launch [pi-coding-agent](https://github.com/dnouri/pi-coding-agent).
438
-
439
- <details>
440
- <summary>Watch entwurf in Doom Emacs (1104×627 GIF, click to expand)</summary>
441
-
442
- ![entwurf in Doom Emacs](docs/assets/entwurf-doomemacs.gif)
443
-
444
- </details>
445
-
446
- For a dedicated agent socket, pass the socket name:
447
-
448
- ```elisp
449
- (setq pi-coding-agent-extra-args
450
- '("--entwurf-control" "--emacs-agent-socket" "pi"))
451
- ```
452
-
453
- The bridge exports the socket name to ACP children as `PI_EMACS_AGENT_SOCKET`, so skills call Emacs without hardcoding:
454
-
455
- ```bash
456
- emacsclient -s "${PI_EMACS_AGENT_SOCKET:-server}" --eval '(...)'
457
- ```
458
-
459
- ## Settings
460
-
461
- Reference shape lives in [`pi/settings.reference.json`](./pi/settings.reference.json). Minimum:
462
-
463
- ```json
464
- {
465
- "compaction": { "enabled": false },
466
- "entwurfProvider": {
467
- "appendSystemPrompt": false,
468
- "settingSources": [],
469
- "strictMcpConfig": true,
470
- "showToolNotifications": true,
471
- "tools": ["Read", "Bash", "Edit", "Write"],
472
- "skillPlugins": [],
473
- "permissionAllow": ["Read(*)", "Bash(*)", "Edit(*)", "Write(*)", "mcp__*"],
474
- "mcpServers": {
475
- "entwurf-bridge": {
476
- "command": "/path/to/entwurf/mcp/entwurf-bridge/start.sh",
477
- "args": []
478
- }
479
- }
480
- }
481
- }
482
- ```
483
-
484
- `mcpServers` is the only ACP MCP injection path. In practice this repo is about the bundled `entwurf-bridge`, which carries pi capabilities into ACP-backed sessions — not about being a general MCP catalog. Invalid entries throw `McpServerConfigError` — broken tool state surfaces as broken tool state. `./run.sh install` writes the bundled `entwurf-bridge` entry and prunes the legacy bundled `session-bridge` entry from older installs.
485
-
486
- `appendSystemPrompt: false` is intentional. Pi / AGENTS context rides the first-user augment; putting it into the Claude `_meta.systemPrompt` carrier can route OAuth sessions to metered "extra usage" billing.
487
-
488
- **Which keys reach which backend.** `entwurfProvider` is one block for both backends, but its keys are not universal. `tools` / `permissionAllow` / `disallowedTools` / `settingSources` / `skillPlugins` / `appendSystemPrompt` are Claude's declaration surface: they do not shape a cortex session, which runs its own native tools and reaches MCP through the overlay-private `mcp.json` projection instead. They are not inert, though — the bridge still reads `tools` for its backend-invariant exclude-tools preflight and folds all of them into the config signature, so editing one still forces a fresh cortex session. `cortexConnection` is cortex-only. `mcpServers` is the one declaration surface that reaches both, each through its own transport. (`compaction` is a *top-level* pi key, not an `entwurfProvider` one — it is pi's own transcript policy and is not a backend knob at all; see §Compaction.) Unknown and retired keys are ignored rather than rejected, so a key aimed at the wrong backend fails silently: if a cortex session does not show the surface you configured, check that the key is one cortex actually consumes before suspecting the bridge.
489
-
490
- ### External MCP registration
491
-
492
- `entwurf-bridge` can also be registered in a separate MCP-aware harness (Claude Code,
493
- Copilot CLI, Codex CLI, Antigravity, OMP). Two shapes exist and they are not interchangeable:
494
-
495
- - **plain external MCP host** — no garden meta-record or sender marker. It can read the
496
- surfaces, but `entwurf_v2` is **refused by default**: there is no authoritative sender.
497
- - **garden-native session** — a trusted lifecycle hook minted a garden id, so it is
498
- addressable and replyable by that id.
499
-
500
- ```bash
501
- claude mcp add --scope user entwurf-bridge entwurf-bridge
502
- ```
503
-
504
- Per-harness registration (Claude Code `~/.mcp.json`, Codex `~/.codex/config.toml`, the
505
- managed `install-agy-*` surfaces, and OMP's managed `install-omp-mcp` into `<omp agent dir>/mcp.json`
506
- — whose pinned server key is what shadows a borrowed Claude import, see
507
- [docs/external-mcp-host.md](./docs/external-mcp-host.md) §OMP), the PATH/env boundary for
508
- GUI-launched MCP servers, the
509
- anonymous-sender hatch, and the full external/meta-session semantics are in
510
- [docs/external-mcp-host.md](./docs/external-mcp-host.md).
511
- For the maintained multi-harness setup and skill/command packaging details, see `agent-config`. See also the MCP entry in [Concept primer](#concept-primer), the sender envelope contract in [AGENTS.md](./AGENTS.md), and [Custom skills](#custom-skills) for the in-pi ACP skill surface.
512
-
513
- ## Per-backend operating surface
514
-
515
- The Claude ACP backend keeps its native model / API / tools; entwurf shapes only what enters from pi. Claude honors an explicit `CLAUDE_CONFIG_DIR` export when set by the operator.
516
-
517
- **Claude** uses `_meta.systemPrompt` for the engraving carrier (kept short and pure — billing-safe; rich operator context rides the first user message instead, see [Context carriers](#context-carriers)) and `CLAUDE_CONFIG_DIR` for a whitelist overlay so auth/runtime entries stay available while operator memory, hooks, agents, history, local settings, and project memory remain hidden. The overlay writes an explicit empty `hooks: {}` because Claude SDK organic compaction needs the configured-empty shape; no operator hook definitions are inherited. It also pins `permissions.defaultMode: "bypassPermissions"` so an unattended ACP turn cannot suspend on an interactive permission prompt; explicit `tools` / `disallowedTools` still constrain the callable surface and backend authentication remains the operator's. The four-tool baseline is `Read`, `Bash`, `Edit`, and `Write`; `permissionAllow` carries their allow declarations, and `Skill` is added automatically when `skillPlugins` is non-empty. Operator context cap override: `ENTWURF_ACP_CLAUDE_CONTEXT=<int>`.
518
-
519
- Codex is not an ACP backend here. Its native-push lane, supported in 0.21.0, uses `SessionStart`
520
- for birth, strict request `_meta` for who-sent, and a loaded app-server thread as the live route.
521
- A Codex CALLER with placement omitted opens its sibling beside its own TUI pane, matched by the
522
- `thread-id` its terminal title carries; a pane whose thread nobody named is never inferred.
523
-
524
- Antigravity is also not an ACP backend. It is a native-push citizen: `PreInvocation` supplies birth/sender identity, `entwurf_v2` probes and direct-injects replies into the live conversation, and no mailbox/receiver marker is involved.
525
-
526
- entwurf owns **no** memory layer at all — the ACP plugin's boundary explicitly excludes a memory DB (see `AGENTS.md` §ACP Plugin Boundary), and no backend is a memory authority for another. What this overlay does is narrower: Claude's native memory layer is pinned off so operator memory, project state, and history never leak into an ACP session. Whatever semantic-memory / Denote tooling an operator runs is their own skill surface on whichever harness hosts it — deliberately kept out of the MCP bridge, and not a pi privilege.
80
+ ### Garden launcher
527
81
 
528
- ## Smoke commands
82
+ After direct setup, start a pi citizen in your project:
529
83
 
530
84
  ```bash
531
- pnpm check # everyday core (prints wall time; <=60s on the reference host)
532
- pnpm run check:full # full deterministic floor (adds the hermetic + package/install tiers)
533
- ./run.sh check-bridge # entwurf-bridge direct MCP smoke (no backend auth)
534
- ./run.sh smoke-agy-install-state # agy MCP + exact permission ownership lifecycle (install/uninstall/doctor/inverse)
535
- ./run.sh smoke-agy-statusline-state # agy ambient garden-id install surface
536
- ./run.sh smoke-agy-hooks-state # agy PreInvocation birth hook
537
- ./run.sh check-agy-sender-identity # record-backed pid/start-key sender identity
538
- ./run.sh check-codex-native-push # deterministic Codex UDS probe + no-replay queue contract
539
- ./run.sh smoke-codex-config-state # Codex MCP/status-line install, doctor, and inverse
540
- ./run.sh check-codex-birth-hook # sandboxed payload + declaration: one V3 per top-level thread, the envelope's own axes, no markers, and the declaration leaf (normalized digest, selection by launcher command, byte-preserving splice)
541
-
542
- # source-maintainer only — qualification snapshots the git work surface, and both
543
- # commands are source-contract gates rather than installed operator checks:
544
- ./run.sh check-agy-permission-matrix # AGY permission contract space as a literal table (declared cells + stated exclusions)
545
- ./run.sh check-gate-manifests # the qualification HEAD alone: self-test + manifest-set validation + declared lane inventory, zero mutants run (in check:hermetic)
546
- ./run.sh check-gate-qualification # kill-proof: committed defect mutants must turn their gates red for the claimed reason
547
-
548
- # agy LIVE acceptance — requires an already-running conversation:
549
- LIVE=1 AGY_CONVERSATION_ID=<id> ./run.sh smoke-agy-native-push-live
550
-
551
- # Codex loaded-thread probe (pre-amendment evidence; not first-release acceptance):
552
- LIVE=1 CODEX_LIVE_THREAD_ID=<threadId> ./run.sh smoke-codex-native-push-live
553
-
554
- # Codex caller-seat acceptance. The operator-owned app-server must sit in a session OTHER than
555
- # the one the Pi/Codex pair runs in — that separation is what tells the caller-pane anchor apart
556
- # from the app-server's inherited environment. This smoke ALSO needs its one stable launch
557
- # directory answered once (manual step 3 above); VERIFY.md's Codex section owns that procedure,
558
- # including how to derive the exact path instead of retyping it. Record all four coordinates:
559
- LIVE=1 ENTWURF_CODEX_APP_SERVER_PID=<existing-app-server-pid> \
560
- ENTWURF_CODEX_FRESH_MODEL=<codex-model> \
561
- ENTWURF_CODEX_FRESH_PI_MODEL=<pi-model> \
562
- ./run.sh smoke-codex-fresh-live
563
- # This accepts only a real visible Pi → visible Codex → visible Pi run: initial Pi in a session
564
- # other than the app-server's, then Codex and its outbound Pi beside it. A fixture first leg may
565
- # collect receipts but does not satisfy the topology. No arbitrary attached-TUI parity is claimed.
566
-
567
- # ACP plugin LIVE acceptance — need the operator's local Claude auth/credit:
568
- LIVE=1 ./run.sh smoke-acp-socket-citizen-live # turn-free socket citizenship (S1)
569
- LIVE=1 ./run.sh smoke-acp-raw-turn-live # pinned ACP pipe + raw 1 turn (S2a)
570
- LIVE=1 ./run.sh smoke-acp-overlay-live # config overlay + hooks:{} + tool meta (S2b)
571
- LIVE=1 ./run.sh smoke-acp-provider-live # real pi provider path + progress/L3 (S2c/S2f)
572
- LIVE=1 ./run.sh smoke-acp-session-reuse-live # process-scoped reuse + codeword recall (S2d)
573
- LIVE=1 ./run.sh smoke-acp-carrier-augment-live # augment delivery + empty-carrier billing clean (S2e-1)
574
-
575
- LIVE=1 ./run.sh release-gate /tmp/scratch --cut # the single cut gate (MUST + BEHAVIOR; --cut refuses any MUST SKIP)
576
- LIVE=1 ENTWURF_ACP_CORTEX_CONNECTION=<conn> ./run.sh smoke-acp-cortex-live # Cortex is on-demand: the aggregate does not re-certify it
85
+ cd /path/to/your-project
86
+ entwurf pi
87
+ # equivalent when the package is registered (also the Herdr plugin's spelling):
88
+ pi --entwurf-control
577
89
  ```
578
90
 
579
- `pnpm run check:full` includes the AGY permission contract matrix and the qualification
580
- HEAD (`check-gate-manifests` — runner self-test, manifest-set validation, declared lane
581
- inventory, zero mutants executed); the committed-mutant EXECUTION is scheduled separately
582
- (`./run.sh check-gate-qualification` — the CI `check` job runs it on a branch push that touched the qualification surface,
583
- unconditionally on `workflow_dispatch -f qualify=true` and a weekly schedule, and release-gate
584
- carries it as a MUST step; a tag push runs no CI at all). A branch run whose body was filtered out
585
- is not release evidence: the exact-SHA oracle requires the run it reads to carry that step
586
- concluding success. A gate a
587
- release touches must kill its known defect for the claimed `[QK:<claim>]` reason —
588
- the descriptions above name what each smoke covers, and no check count is quality
589
- evidence on its own. Gate qualification needs the git work surface, while the matrix
590
- is the source permission-contract gate; both run from a clone, never as a post-install
591
- operator step.
91
+ `entwurf pi` execs your own pi with the control flag. A plain pi may load the
92
+ package without becoming a citizen. The record mints the garden id; do not inject
93
+ a session id. Native harness launch and manual Herdr argv instructions live in
94
+ [setup §2–§5](./docs/setup-clean-host.md#2-optional-pi-adapter--acp-plugin).
592
95
 
593
- ## Custom skills
96
+ In that **harness session**, ask the agent to:
594
97
 
595
- Claude sessions accept custom skills through `skillPlugins` — an array of absolute paths to directories matching the Claude Agent SDK plugin layout:
596
-
597
- ```
598
- <your-plugin-root>/
599
- ├── .claude-plugin/
600
- │ └── plugin.json
601
- └── skills/
602
- └── <skill-name>/
603
- └── SKILL.md
604
- ```
605
-
606
- A self-contained example lives at [`pi/skill-plugin-example/`](./pi/skill-plugin-example/). Put plugin roots anywhere on disk except under `~/.pi/agent/` (pi's internal cache).
98
+ 1. Call `entwurf_self` to confirm its authoritative identity.
99
+ 2. Call `entwurf_peers` to discover existing citizens and their liveness facts.
100
+ 3. Pick a live garden id and call `entwurf_v2` with a message, for example:
607
101
 
608
102
  ```json
609
103
  {
610
- "entwurfProvider": {
611
- "skillPlugins": ["/absolute/path/to/your-plugin-root"]
612
- }
104
+ "target": "<garden-id from entwurf_peers>",
105
+ "intent": "fire-and-forget",
106
+ "message": "Hello from a sibling.",
107
+ "wants_reply": true
613
108
  }
614
109
  ```
615
110
 
616
- `Skill` is auto-added to `tools` and `Skill(*)` to `permissionAllow` whenever `skillPlugins` is non-empty. Each entry is validated at settings parse time and throws when the path is missing, not absolute, not a directory, or missing `.claude-plugin/plugin.json`. The Claude session does not start until the violation is fixed. The bridge does not validate `plugin.json` contents or `SKILL.md` bodies — that is the Claude Agent SDK's contract.
617
-
618
- To verify, start a fresh Claude session and ask the model to list its skills; the names declared in your `SKILL.md` frontmatter should appear among the visible skills. The operator-driven version of this check is `Q-SKILL-CALLABLE` in [VERIFY.md](./VERIFY.md).
619
-
620
- `skillPlugins` is a Claude-backend-only install surface. Codex exposes skills through native `~/.codex/skills/` passthrough.
621
-
622
- For a real consumer arranging many skills, see [agent-config](https://github.com/junghan0611/agent-config).
111
+ These are tools, not shell subcommands. Pi calls
112
+ `mcp__entwurf_bridge__<verb>` through Pi's built-in MCP. The server exposes **eight**
113
+ verbs; a pi citizen has **six active + two hidden** (`entwurf_inbox_read` and
114
+ `entwurf_register_native`), since it has no mailbox and its own extension births it.
115
+ The server key stays `entwurf-bridge`. An operator/trusted-project MCP entry with
116
+ that normalized namespace can replace the registration by Pi's own precedence;
117
+ `/mcp` shows it, and Entwurf does not scan or fight that choice.
623
118
 
624
119
  ## Entwurf orchestration
625
120
 
626
- **Entwurf is one dispatch capability with native-pi and MCP surfaces.** Native pi exposes it directly as extension tools; ACP-backed and external native-harness sessions reach it through `entwurf-bridge`. The purpose is not to invent a different sub-agent system, but to preserve the same sibling-based model across harnesses.
627
-
628
- A sibling has its own runtime boundary and its own provider/model identity — not a worker, delegate, or sub-agent. Today every transport targets an *existing* garden citizen, and none of them starts a process. `entwurf_v2` routes from rail-specific liveness: live pi fire-and-forget → control socket; dormant pi → an honest reject; active self-fetch → meta-mailbox; probe-alive agy → native-push. A **control-socket-domain** dispatch takes the per-target lock, which now serializes concurrent live sends at one garden id; mailbox and native-push use their own deliverability evidence and remain lock-free.
629
-
630
- A two-pane recording covers the pre-0.12 v1 surface end-to-end — sibling resume, cross-process MCP dispatch across a different cwd, and a live peer greeting. It is **archived evidence**: it shows a resume verb this repo no longer has (see `demo/README.md`).
631
-
632
- <details>
633
- <summary>Watch (2131×1142 GIF, click to expand)</summary>
634
-
635
- ![entwurf demo](./docs/assets/entwurf-entwurf.gif)
636
-
637
- </details>
638
-
639
- Live peer messaging carries a sender envelope `{ sessionId, agentId, cwd, timestamp, origin?, replyable? }`; `entwurf_self` returns that authoritative envelope for the current pi session or trusted meta-session. Plain external MCP hosts are non-replyable. A garden-native meta-session carries a trusted `meta-session` envelope, but **`replyable` is a fact its own rail decides, not a consequence of being trusted** — a self-fetch citizen needs a live armed receiver, a native-push citizen needs an alive adapter probe, and a pi session needs its control socket. `entwurf_self` also reports which rail a meta-session reply would ride, because a native-push citizen has no mailbox to name. `wants_reply` is an etiquette marker rendered as a `(wants reply)` badge — not a transport contract, no wait, no polling. **v2 never gates on it:** a `wants_reply` from an external/non-replyable caller is passed through and surfaced honestly beside that sender's `replyable: false`, not rejected — the decider routes on target + intent, never on sender replyability. (The retired v1 `entwurf_send` did reject it; that behaviour went with the verb.)
640
-
641
- In ACP-backed and external native-harness sessions, `entwurf-bridge` exposes eight tools: `entwurf_v2`, `entwurf_peers`, `entwurf_fresh_call`, `entwurf_resume_call`, `entwurf_callback`, `entwurf_self`, `entwurf_inbox_read`, and the explicit/manual `entwurf_register_native` fallback. Native pi exposes the shared capability directly through the extension surface (`entwurf_v2`, `entwurf_peers`, `entwurf_fresh_call`, `entwurf_resume_call`, `entwurf_callback` tools; the socket-scan `/entwurf-sessions` command is gone — #50 C4). **For garden-id delivery/reply use `entwurf_v2`** — the canonical surface that classifies the target and routes to live-pi / self-fetch meta-mailbox (Claude Code, Copilot, OMP) / native-push (Antigravity, Codex), and refuses a dormant target instead of waking it. **To open a sibling that does not exist yet use `entwurf_fresh_call`** — WHERE it opens is a fact about the calling process, not a parameter: inside herdr (`HERDR_ENV=1`) it opens a NEW TAB in the caller's own workspace, without taking focus, and admits only `pi` and `claude-code`, refusing `placement` by name; everywhere else it launches one of five fixed backends (`pi`, `claude-code`, `copilot`, `omp`, `codex`) as a visible window on the operator's own tmux server. There is no fallback between the two — an incomplete herdr context is refused by its herdr reason rather than quietly opening a tmux window nobody inside herdr can see. Everything that follows in this paragraph describes the tmux rail; the herdr rail's coordinates, refusals and reclaim are owned by [docs/herdr-launch-rail.md](./docs/herdr-launch-rail.md). On tmux it requires an explicit model, accepts one optional literal absolute `cwd` (omit it or pass `""` to use the caller's cwd) and one optional `placement.tmuxSession` naming an EXISTING session on that server to open the sibling in instead of the caller's own (the operator's project seat — nothing is ever created: an absent session is `tmux-session-missing` and a name outside the addressed grammar `[A-Za-z0-9][A-Za-z0-9_-]*` is `tmux-session-name-invalid`, with no window and no session left behind. The two inputs never infer each other, and the positive form of that matters more than the prohibition: **with `cwd` omitted, a seated sibling starts in THIS agent's own working directory**, measured — it does not inherit the target session's path or its active pane, so naming the `org` seat does not put the sibling in the `org` project. Pass `cwd` when you want a different directory. entwurf never resolves that path: it hands the literal string to tmux, so with a SYMLINKED directory the receipt echoes what you asked for while the record, the transcript path, the callback envelope and `pane_current_path` all show the path the OS resolved it to — two spellings of one place, and the resolved one is what `entwurf_resume_call` later reopens), passes the model and selected directory through the runtime's visible launch path, and hands it a first task; a `copilot` launch goes through entwurf's own managed invocation and is refused before any window opens if this host lacks the Copilot birth, MCP, receiver or visible-footer units, and an `omp` launch carries its task in the `--entwurf-bootstrap` payload instead of an argv prompt because that vendor connects its MCP tools after the session has already started. Omitted placement follows the CALLER rather than the backend being opened: a Codex CALLER opens its sibling beside its own TUI pane, located by the `thread-id` that pane's terminal title carries (0 or 2+ matching panes reject with no fallback); every other caller opens in its own seat. (0.21.0 shipped a backend-selected default instead — an omitted-placement `codex` TARGET resolved a fixed operator-owned session named `codex` — and #95 D1 retired it on 2026-09-16.) Any of the five can still be sent to an operator-owned existing seat with an explicit `placement.tmuxSession`, and named `claude`/`pi` seats remain available that way, but they are not omitted-placement defaults. Read tmux here as the VISIBLE LAUNCH SEAT, never as an address: the garden id is the address, and it arrives in the callback envelope. Sibling symmetry means each backend keeps its own transcript, auth, native tools and visibility — not that the transports are made identical. The sibling's first action is a nonce callback whose sender envelope carries its garden id. The launch receipt records the requested model/cwd/seat plus tmux coordinates — including the RESOLVED target session id, which is where the window actually is — and never claims that the runtime accepted them or completed delivery. (The v1 verbs `entwurf` / `entwurf_resume` / `entwurf_send` are gone.) Garden-native operator commands require `--entwurf-control`. There is no spawn target allowlist — the target registry is gone (#50 C3): `entwurf_v2` and `entwurf_resume_call` address an existing record-backed citizen, while `entwurf_fresh_call` takes its explicit backend/model/task and optional cwd directly rather than resolving a model tuple from a registry file. **To reopen a DORMANT pi citizen use `entwurf_resume_call {target}`** — the record supplies transcript, model, provider and cwd, so it takes no prompt, no task and no model override; it runs no turn, returns a LAUNCH receipt and a separate OBSERVATION receipt (only the second says the citizen is back), refuses a non-pi target as `target-not-pi`, and on an unobserved socket leaves the visible window open and releases its lock rather than retrying.
121
+ One narrow dispatch capability connects independent siblings, not an agent factory.
122
+ The record is the **sole address authority**. A pane, socket, native transcript id,
123
+ model or process marker is a carrier or observation, never a second address axis.
124
+ Liveness is read at dispatch time; an id's shape does not tell you its transport.
642
125
 
643
126
  ### `entwurf_v2` — canonical dispatch verb
644
127
 
645
- `entwurf_v2` / `runEntwurfV2` is the canonical v2 dispatch verb over **existing** garden targets — record-backed citizens only (#50 C4: the record is the sole address authority; a record-less control socket rejects pre-probe as `record-less-socket`, a diagnostic state, never a delivery target). You give a target garden id plus an intent (`fire-and-forget` — the axis is single-valued since the visible-first cut); one decider reads the target's liveness as a fact and picks the transport from a frozen table keyed on **both** the target's state **and** the intent — never on state alone — then reports one outcome under the v2 lock policy. A **control-socket-domain** dispatch takes a per-target lock; mailbox and native-push are lock-free, with deliverability guarded by their own receiver/probe evidence:
646
-
647
- | target state | intent | transport |
128
+ | What you want | Tool | What its receipt means |
648
129
  |---|---|---|
649
- | live pi | fire-and-forget | control-socket send |
650
- | dormant pi | fire-and-forget | **reject** (`dormant-fire-forget-unsupported` — nothing is launched) |
651
- | any pi | indeterminate probe | **reject** (`indeterminate-no-spawn` — an unestablished probe is not a measured death) |
652
- | active self-fetch receiver | fire-and-forget | meta-mailbox enqueue + doorbell |
653
- | inactive / terminated self-fetch receiver | fire-and-forget | **reject** (`mailbox-undeliverable` — no `.msg`, no doorbell) |
654
- | live native-push conversation | fire-and-forget | native-push direct injection |
655
- | dead / indeterminate native-push conversation | fire-and-forget | **reject** (`native-push-target-dead` / `native-push-probe-indeterminate`) |
656
- | record-less control socket (no meta-record) | any | **reject** (`record-less-socket` — pre-probe; diagnostic state, #50 C4) |
657
-
658
- **`entwurf_v2` is the canonical surface for garden-id delivery.** When you have a garden id and want to reach whoever it names — message, reply, or hand-off — `entwurf_v2` is the one surface that reads whether the target is live pi, dormant pi, mailbox-backed Claude Code, Copilot or OMP, or native-push Antigravity or Codex, and routes correctly; *when unsure which transport, use `entwurf_v2`*. This prevents callers from guessing a rail from the shape of an id.
659
-
660
- What v2 provides is a **deterministic dispatch substrate** that moves the "which transport?" decision out of the fallible caller/model and into the decider, with transport-appropriate locking and an honest reject (no `✓ delivered`, no `.msg` garbage) when a target cannot receive. It still does **not** mint siblings, and it does not relaunch one either: every row above either reaches a citizen that is already running or refuses. Reopening a dormant pi citizen is `entwurf_resume_call`, a separate lifecycle verb that never routes through this decider. Fresh creation is the separate `entwurf_fresh_call` verb. Inside herdr it opens a Pi or Claude Code sibling in a new tab in the caller's own workspace; everywhere else it opens one of five fixed runtimes — Pi, Claude Code, Copilot, OMP or Codex — visibly on the caller's tmux server. The rest of this paragraph is the tmux rail. Omitting placement follows the CALLER rather than the backend being opened: an explicit seat always wins; then a Codex CALLER opens beside its own TUI pane, found by the `thread-id` in that pane's terminal title (0 or 2+ matching panes reject, with no fallback); then the caller's own session. An explicit `placement.tmuxSession` sends ANY of the five to an existing named session instead (see the `entwurf_fresh_call` paragraph above for the owning detail). Each launch carries a required explicit model passed in that runtime's measured CLI dialect and one optional literal absolute `cwd`; omitted or `""` means the caller's cwd. Copilot opens through `entwurf copilot`, never the bare vendor; OMP is the opposite — the bare `omp` runtime with no positional prompt at all, because that vendor connects its MCP tools after the session has started, so the task rides a two-stage `--entwurf-bootstrap` payload the installed birth extension releases once the callback tool has actually answered. It returns only a synchronous launch receipt and lets the sibling report its new address asynchronously through the sender envelope of a nonce callback. Use this cwd input for a new cross-repository sibling; do not resume a dormant citizen as a cwd substitute. The meta-mailbox row requires an **active** self-fetch receiver; native-push requires a record-backed, probe-alive native conversation and never borrows mailbox state. The [mux launch lane](./docs/mux-launch-rail.md) owns placement, fixed-runtime launch, and the two narrow compositions above it (fresh-call and resume-call placement); delivery does not import launch, and mux is not a delivery transport.
661
-
662
- A live pi target is *reached* over its control socket, but the socket is dispatch-internal transport, never identity (#50 C4). A control socket that no meta-record claims — a pre-record-era resident, an unreadable store, or a stale/planted file — is refused for **every** intent as `record-less-socket`, and the reject names the fix (restart the resident so `session_start` births its record, or quiesce and run the fresh-cut). `entwurf_peers` reports the same state as an aggregated `record-less-socket` diagnostic rather than a peer row.
663
-
664
- > **Direction.** An Entwurf core (peer identity / garden id / inbox / liveness / dispatch / replyability / evidence) could later extract into its own repo with per-backend plugins; today this repo holds the v2 core + meta-bridge + ACP plugin together. ACP is one plugin, not the boundary — rationale: [#38](https://github.com/junghan0611/entwurf/issues/38).
665
-
666
- ### Garden launcher
667
-
668
- A `--entwurf-control` session needs **no special launcher** (#50 C2): pi mints its own session id (a `uuidv7` is normal), and `session_start` attaches the session to its meta-record — the record mints the garden id, keys the control socket on it, and `PI_SESSION_ID` carries that garden address to every MCP child. The old `--session-id "$(run.sh new-session-id)"` injection contract (and the header-id guard it fed) is gone; `run.sh new-session-id` survives only as the garden-id generator the record layer itself uses. Launch is simply:
130
+ | Message, reply or hand off to an existing garden id | `entwurf_v2 {target, intent:"fire-and-forget", message}` | Receiver acceptance boundary, mailbox enqueue or native injection; not completion |
131
+ | Open a new visible sibling | `entwurf_fresh_call {backend, model, task, cwd?}` | Launch receipt only; the sibling's nonce callback envelope supplies its new garden id |
132
+ | Reopen a dormant pi citizen under its own id | `entwurf_resume_call {target}` | Separate LAUNCH and socket OBSERVATION receipts; no turn is run |
133
+
134
+ **Delivery starts no process.** A live pi receives over its record-addressed Unix
135
+ control socket; an armed self-fetch citizen (Claude Code, Copilot, OMP) through its
136
+ mailbox; a probe-alive native-push citizen (Antigravity, Codex) through its native
137
+ conversation/thread. Dormant pi, undeliverable mailboxes, dead/indeterminate native
138
+ push and record-less sockets reject honestly. Only control-socket dispatch takes
139
+ the per-target lock; the other rails use their own deliverability/probe evidence.
140
+
141
+ A control-socket `sent` means a turn was triggered, not that the model read the
142
+ text. `queued-steer` and `queued-follow-up` are volatile process queues, with no
143
+ FIFO between them; abort drops them. `accepted-unknown-boundary` is acceptance
144
+ we cannot classify, never a reason to retry. A mailbox enqueue is a durable file
145
+ receipt. `wants_reply` is etiquette, not waiting, polling or ownership; replyability
146
+ is a live rail fact, not merely a consequence of trusted identity.
147
+ [DELIVERY](./DELIVERY.md#control-socket-acceptance-boundaries-120) owns these distinctions.
148
+
149
+ **Mux launches; it never delivers.** Inside Herdr, fresh opens a new unfocused tab
150
+ in the caller's workspace, pi/Claude Code only, with no tmux fallback. Outside,
151
+ it opens pi/Claude Code/Copilot/OMP/Codex visibly on the caller's own tmux server.
152
+ The model is required; optional `cwd` is a literal absolute directory, omitted or
153
+ empty means the caller's cwd. Optional `placement.tmuxSession` names an existing
154
+ tmux session (not permitted inside Herdr), never a new session or a cwd lookup.
155
+ Omitted tmux placement follows the caller: Codex beside its own TUI pane matched
156
+ by thread-id, everyone else in their own session. Missing/ambiguous context refuses.
157
+ A launch receipt is neither the callback address nor proof of task processing.
158
+ Resume is pi-only, tmux-only, same id, record-supplied transcript/model/provider/cwd;
159
+ a live or non-pi target refuses, and an unobserved window stays visible without retry.
160
+ Exact boundaries: [tmux launch](https://github.com/junghan0611/entwurf/blob/main/docs/mux-launch-rail.md)
161
+ and [Herdr launch](https://github.com/junghan0611/entwurf/blob/main/docs/herdr-launch-rail.md).
669
162
 
670
- ```bash
671
- pi --entwurf-control
672
- ```
673
-
674
- **Opening pi inside herdr.** herdr starts a supported agent with its canonical executable and no
675
- extra argv — measured on herdr 0.9.0, `herdr agent start <name> --kind pi --pane <id>` runs plain
676
- `pi`. Citizenship is argv-gated (`pi-extensions/entwurf-control.ts`), so a pi opened that way is a
677
- running pi and *not* a garden citizen: no record, no control socket, no garden id. Pass the flag
678
- through herdr's own argv passthrough:
679
-
680
- ```bash
681
- herdr agent start <name> --kind pi --pane <pane-id> -- --entwurf-control
682
- ```
683
-
684
- A sibling that entwurf itself opens already carries the flag; this is only for a pi you start from
685
- herdr by hand. The claude axis needs nothing extra — its record is minted by the meta-bridge plugin's
686
- own `SessionStart` hook, which entwurf installs and herdr does not touch. Where such a citizen is
687
- visible is reported as evidence in `entwurf_peers` (`placement=herdr <pane>`), never as an address:
688
- [`docs/mux-launch-rail.md`](./docs/mux-launch-rail.md) §7-a.
689
-
690
- The wiring those two axes need does not have to be installed by hand on a Herdr host: the Entwurf
691
- Herdr plugin's install-time build reads Herdr's own `integration status`, and for every harness in
692
- `{pi, claude-code}` that Herdr reports as integrated it acquires an Entwurf runtime at a stable
693
- Entwurf-owned address and registers the user-scope wiring against it — no Entwurf clone, no manual
694
- `npm install`. `herdr plugin install junghan0611/entwurf/plugins/herdr` is the whole command, every
695
- reinstall is the refresh trigger, and `entwurf herdr-plugin-deactivate` is the explicit inverse
696
- (Herdr's own uninstall removes only its checkout and calls no cleanup hook).
697
- [`plugins/herdr/README.md`](./plugins/herdr/README.md) owns that lane, including which acquisition
698
- source the committed lock names.
699
-
700
- **Resuming an existing garden session.** Use `entwurf_resume_call {target}`. It reopens a DORMANT pi citizen under the SAME garden id in a visible window in the caller's own tmux session, resolving the transcript, model, provider and cwd from the record — so it takes only the target id, and it runs no turn: the window comes back with the conversation and waits, and talking to it is still `entwurf_v2`. Two receipts arrive and mean different things: a LAUNCH receipt (tmux made a window and was asked to start pi) and an OBSERVATION receipt (the control socket answered under the same id, or `resume-unobserved`). Unobserved is a real outcome, not an error to retry — the window is visible, so read it. A citizen that is already live is refused; so is a non-pi target (`target-not-pi`), because only pi stands a control socket up. The predecessor, `entwurf_v2 intent=owned-outcome`, resumed by launching a hidden window-less background child and was withdrawn under the visible-first rule; delivery still starts no process. Identity preconditions live in `resume-launch-identity.ts`, gated by `check-resume-launch-identity`.
163
+ ## Concept primer
701
164
 
702
- **Starting a new session in-process — pi's own `/new`.** Since the #50 C2 cut there is nothing to replace it with: `/new`, `/fork`, `/clone` and RPC session replacement are pi's again. The replacement session fires `session_start`, which upserts its own meta-record and rebinds the control socket to that record's garden id; the old socket is dropped. pi's session id (a uuidv7) is recorded as the citizen's `nativeSessionId` and is never an address. Gate: `run.sh smoke-resident-garden-guard` REPLACEMENT section (0-token RPC E2E).
165
+ - **Entwurf** (기투, projection-of-self): opening and addressing independent visible
166
+ siblings, not turning them into workers of a second orchestrator.
167
+ - **Garden / garden id:** the shared address space and stable id of one V3
168
+ record-backed citizen. Native harnesses keep their own identity and transcript.
169
+ - **ACP:** a plugin that exposes backend turns as pi provider/models under the
170
+ operator's own auth. The host pi is already a citizen; ACP mints no second one.
171
+ - **MCP:** tool-call ingress to the same compiled `entwurf-bridge`, not a general
172
+ MCP platform or the pi receive rail. ACP uses explicit `mcpServers` declarations;
173
+ no ambient config scan or automatic retrieval. Plain anonymous hosts have no
174
+ authoritative reply address and sends are refused by default.
175
+ - **Engraving:** optional short operator text in the backend's identity carrier,
176
+ not rich project context or a hidden tool catalog.
177
+
178
+ ## Pi 1.0 and a short history
179
+
180
+ **We welcome Pi's built-in MCP:** [You Said No MCP!](https://earendil.com/posts/you-said-no-mcp/)
181
+ (Earendil, 2026-09-29). The 0.30.0 cut targets Pi 1.0 admission and ACP basics.
182
+ Instead of keeping native pi copies of the verbs, the adapter registers the same
183
+ compiled bridge after record birth, under the citizen's identity, and Pi supervises
184
+ that MCP child. **MCP is ingress; pi receive remains the record-addressed Unix
185
+ control socket.** Garden identity and dispatch stay Entwurf's. What `pi-durable`
186
+ or Pi codemode should mean here remains undecided: this cut neither adopts nor
187
+ certifies them. The 0.30.0 follow-up separately measured Herdr supply 0.9.3 and
188
+ re-pinned the plugin runtime to published npm 0.30.0 with manifest 0.30.0; its
189
+ public consumer and CI receipts live in the #126 thread. That dated alignment is
190
+ not a permanent version-equality rule. Unpublished 0.30.1 uses the closed
191
+ `herdr-checkout` verification carrier; the [plugin source contract](./plugins/herdr/README.md)
192
+ owns the measured npm re-pin and source-specific proof owed after publication.
193
+
194
+ Entwurf is the 0.12+ successor to
195
+ [`@junghanacs/pi-shell-acp`](https://www.npmjs.com/package/@junghanacs/pi-shell-acp):
196
+ the work was renamed around the dispatch substrate rather than the pi adapter.
197
+ The pre-0.12 [two-pane demo](./demo/README.md) is **archived evidence**, including
198
+ retired resume behaviour, not current instructions. [CHANGELOG](./CHANGELOG.md)
199
+ keeps the release history; dated Codex topology and qualification receipts remain
200
+ in [DELIVERY](./DELIVERY.md) and [BASELINE](./BASELINE.md), not rewritten as today's acceptance.
201
+
202
+ ## Detailed operation
203
+
204
+ ### Settings
205
+
206
+ [Setup — settings and backend-specific keys](./docs/setup-clean-host.md#settings-reference-and-backend-specific-keys)
207
+ owns configuration, including billing-safe carrier choices and pi-versus-backend keys.
703
208
 
704
- ## Context carriers
209
+ ### External MCP registration
705
210
 
706
- System / developer carriers and rich pi context are separate.
211
+ [Setup — external hosts](./docs/setup-clean-host.md#stable-bins-and-external-mcp-hosts)
212
+ and [manual wiring](./docs/external-mcp-host.md) own paths, env and identity prerequisites.
707
213
 
708
- The carrier holds an optional short operator engraving; empty or missing is fine. The runtime default is the bundled `pi-extensions/lib/acp/prompts/engraving.md` (the `# Engraving Here` placeholder, pinned non-empty by a gate); [`prompts/engraving.md`](./prompts/engraving.md) is a documented sample you copy and point the runtime at with `ENTWURF_ACP_ENGRAVING_PATH=/path/to/alt.md`. Template variables: `{{backend}}`, `{{mcp_servers}}`. Do not put AGENTS.md, bridge narrative, or tool catalogs here — large Claude carriers can route OAuth sessions to metered "extra usage" billing.
214
+ ### Custom skills
709
215
 
710
- Your file's own leading and trailing whitespace is trimmed, and the loader then opens the carrier with one blank line. That boundary is not cosmetic: the Claude Agent SDK prefixes its own fixed identity sentence and concatenates the carrier onto it with nothing in between, so without it the engraving's first line reads as the tail of the SDK's sentence (measured 2026-07-31 as `You are a Claude agent, built on Anthropic's Claude Agent SDK.# Engraving Here`). Do not try to supply the boundary from inside the markdown — it is trimmed away before it reaches the wire.
216
+ [Setup — custom skills](./docs/setup-clean-host.md#custom-skills) owns the Claude ACP
217
+ plugin layout and validation; other harnesses keep their native skill mechanisms.
711
218
 
712
- Bridge identity, pi context, `~/AGENTS.md`, `cwd/AGENTS.md`, and date/cwd ride a one-shot first-user prepend (`pi-context-augment.ts`). Entwurf prompts already carry `cwd/AGENTS.md` inside `<project-context ...>`; the augment removes that duplicate. The augment describes capabilities, but the **actual callable schema remains source of truth** — `read` vs `Read` vs `exec_command`, MCP only when schema-visible.
219
+ ### Context carriers
713
220
 
714
- ## Compaction policy
221
+ [Setup — carriers](./docs/setup-clean-host.md#context-carriers) separates short
222
+ engraving from the rich first-user augment and the actual callable schema.
715
223
 
716
- **entwurf does not implement compaction.** When a backend compacts natively, the pi session and mapping survive that. The bridge exposes no backend-specific compaction knobs; operators who need to alter a backend's auto-compaction configure that backend through its own native interface. Do not rely on a pi-side JSONL summary to reduce a backend transcript — it does not.
224
+ ### Compaction policy
717
225
 
718
- The footer uses ACP `usage_update.used / size` (backend prompt/tools/cache/session included) with `[entwurf:usage]` diagnostics. Near limit, choose a visible action: clear, open a new session with a different model, or let the backend compact on its own.
226
+ [Setup — reuse and compaction](./docs/setup-clean-host.md#session-reuse-and-compaction):
227
+ Entwurf does not implement backend compaction or hydrate another harness's transcript.
719
228
 
720
229
  ## What this repo owns, and does not
721
230
 
722
- Owns: provider registration (`entwurf/...`), ACP subprocess lifecycle + `resume > load > new`, prompt forwarding + ACP event mapping, the bridge surface that exposes pi capabilities such as entwurf to ACP-backed sessions, pi-facing MCP injection via `entwurfProvider.mcpServers`, and bridge-local cleanup and diagnostics.
723
-
724
- Does not: reconstruct full history, hydrate backend transcripts into pi history, emulate Claude Code or Codex, run broad multi-agent orchestration (entwurf is narrow, record-addressed, identity-locked), or run a second session model competing with pi.
725
-
726
- Only `pi:<sessionId>` mappings are persisted (`~/.pi/agent/cache/entwurf/sessions/`) — enough to re-attach pi to the same remote ACP session, never enough to act as a second harness. Backend stores (`~/.claude/`, `~/.codex/`) are interoperability side effects, not authority.
727
-
728
- This repo also doubles as the maintainer's working laboratory for agent-harness boundaries — new workflow patterns (e.g. Mitsein over MCP) land here first as low-level instruments, before crystallizing into invariants or graduating into more polished surfaces elsewhere.
231
+ Owns: garden identity, live-fact dispatch and receipts, native bridge integrations,
232
+ visible lifecycle launch seams, the pi adapter and isolated ACP plugin lifecycle.
233
+ Does not: a second harness, memory DB, prompt reconstruction, transcript hydration,
234
+ credential mediation, subscription bypass, pane-typing delivery or hidden orchestration.
235
+ [AGENTS](./AGENTS.md) owns these invariants and exact capability boundaries.
729
236
 
730
237
  ## Verification surfaces
731
238
 
732
- - **[VERIFY.md](./VERIFY.md)** — agent-driven. One ACP-bridged identity runs the script against another and records what it sees. Carries the Evidence Levels L0–L5 rung ladder and the Claims Ledger so each claim is parked at the rung it has actually reached, and owns the deterministic-floor kill-proof protocol (gate qualification and its `[QK:<claim>]` coordinates).
733
- - **[BASELINE.md](./BASELINE.md)** — operator-driven. The maintainer runs the interview directly (no agent in the verifier seat) and the result is recorded.
734
- - **[DELIVERY.md](./DELIVERY.md)** — capability-coordinate. The cross-harness yardstick for one question: can an already-running native session receive an async message without pretending pi owns the backend transcript? Records the per-backend async-delivery level (`D0–D8`) each harness actually reaches instead of collapsing into works/doesn't.
239
+ - [VERIFY](./VERIFY.md): evidence strength, deterministic/package gates and LIVE release protocol.
240
+ - [BASELINE](./BASELINE.md): operator-driven observations and dated host receipts.
241
+ - [DELIVERY](./DELIVERY.md): per-harness async receive capabilities, boundaries and limits.
735
242
 
736
- VERIFY + BASELINE are the verification pair — use both; either one alone leaves a blind spot the other closes. DELIVERY sits on the orthogonal delivery-capability axis.
243
+ Installation, a doctor, a fixture and an exact release candidate prove different things;
244
+ none is interchangeable with the others or an all-host/long-haul guarantee.
737
245
 
738
246
  ## References
739
247
 
740
- - File map + code-level invariants: [AGENTS.md](./AGENTS.md)
741
- - Current priority + open decisions: [NEXT.md](https://github.com/junghan0611/entwurf/blob/main/NEXT.md)
742
- - Release record: [CHANGELOG.md](./CHANGELOG.md)
743
- - [xenodium/agent-shell](https://github.com/xenodium/agent-shell) — Emacs ACP client, `resume > load > new` idea origin
744
- - [agentclientprotocol/claude-agent-acp](https://github.com/agentclientprotocol/claude-agent-acp) — canonical ACP server for Claude Code
745
- - [agent-config](https://github.com/junghan0611/agent-config) — real consumer repo
248
+ - [FAQ](./FAQ.md) · [Clean-host setup](./docs/setup-clean-host.md) · [Release record](./CHANGELOG.md)
249
+ - [Current ordering](https://github.com/junghan0611/entwurf/blob/main/NEXT.md)
250
+ - [Adding a harness](https://github.com/junghan0611/entwurf/blob/main/docs/adding-a-harness.md)
251
+ - [ACP backend contract](./docs/acp-backend-rail.md)
252
+ - [xenodium/agent-shell](https://github.com/xenodium/agent-shell) — Emacs ACP client, `resume > load > new` origin
253
+ - [claude-agent-acp](https://github.com/agentclientprotocol/claude-agent-acp) — canonical Claude ACP server
254
+ - [agent-config](https://github.com/junghan0611/agent-config) — real consumer
746
255
 
747
256
  ## License
748
257