@junghanacs/entwurf 0.13.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +52 -17
- package/BASELINE.md +60 -206
- package/CHANGELOG.md +64 -0
- package/CONTRIBUTING.md +14 -10
- package/DELIVERY.md +118 -261
- package/README.md +91 -431
- package/VERIFY.md +74 -95
- package/demo/README.md +1 -1
- package/demo/demo-baseline.sh +1 -3
- package/demo/demo.sh +2 -5
- package/docs/acp-backend-rail.md +236 -0
- package/docs/external-mcp-host.md +131 -0
- package/docs/fresh-cut-policy.md +99 -0
- package/docs/setup-clean-host.md +124 -328
- package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +160 -67
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/acp-client.js +12 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/backend-adapter.js +11 -10
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/config.js +1 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/engraving.js +42 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/models.js +4 -4
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/overlay.js +11 -9
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-control-rpc.js +7 -5
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-core.js +13 -14
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-resume-args.js +45 -40
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-contract.js +117 -95
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-decider.js +23 -57
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-lock.js +16 -7
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-production.js +5 -53
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-release.js +21 -36
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-runner.js +3 -15
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-send-fallback.js +12 -11
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-send.js +2 -7
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-surface.js +30 -67
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-visible-resume.js +256 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js +91 -5
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +258 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-launch.js +202 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-placement.js +289 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-resume-call.js +170 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/resume-launch-identity.js +136 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/session-id.js +8 -5
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/socket-discovery.js +3 -3
- package/mcp/entwurf-bridge/dist/scripts/meta-facts.js +51 -0
- package/mcp/entwurf-bridge/dist/scripts/new-session-id.js +9 -4
- package/mcp/entwurf-bridge/src/index.ts +176 -67
- package/mcp/entwurf-bridge/start.sh +2 -2
- package/mcp/entwurf-bridge/test.sh +23 -9
- package/mcp/entwurf-bridge/tsconfig.build.json +11 -2
- package/package.json +25 -11
- package/pi-extensions/entwurf-control.ts +220 -71
- package/pi-extensions/lib/acp/acp-client.ts +22 -0
- package/pi-extensions/lib/acp/augment.ts +42 -3
- package/pi-extensions/lib/acp/backend-adapter.ts +12 -11
- package/pi-extensions/lib/acp/backend.ts +366 -38
- package/pi-extensions/lib/acp/config.ts +1 -1
- package/pi-extensions/lib/acp/engraving.ts +43 -1
- package/pi-extensions/lib/acp/event-mapper.ts +16 -13
- package/pi-extensions/lib/acp/models.ts +4 -4
- package/pi-extensions/lib/acp/overlay.ts +11 -9
- package/pi-extensions/lib/entwurf-control-rpc.ts +7 -5
- package/pi-extensions/lib/entwurf-core.ts +15 -15
- package/pi-extensions/lib/entwurf-resume-args.ts +41 -52
- package/pi-extensions/lib/entwurf-v2-contract-schema.ts +1 -1
- package/pi-extensions/lib/entwurf-v2-contract.ts +120 -99
- package/pi-extensions/lib/entwurf-v2-decider.ts +30 -91
- package/pi-extensions/lib/entwurf-v2-lock.ts +16 -7
- package/pi-extensions/lib/entwurf-v2-production.ts +4 -78
- package/pi-extensions/lib/entwurf-v2-release.ts +25 -49
- package/pi-extensions/lib/entwurf-v2-runner.ts +6 -21
- package/pi-extensions/lib/entwurf-v2-send-fallback.ts +12 -11
- package/pi-extensions/lib/entwurf-v2-send.ts +2 -7
- package/pi-extensions/lib/entwurf-v2-surface.ts +36 -76
- package/pi-extensions/lib/entwurf-v2-visible-resume.ts +370 -0
- package/pi-extensions/lib/meta-session.ts +93 -5
- package/pi-extensions/lib/mux-fresh-call.ts +328 -0
- package/pi-extensions/lib/mux-launch.ts +267 -0
- package/pi-extensions/lib/mux-placement.ts +387 -0
- package/pi-extensions/lib/mux-resume-call.ts +221 -0
- package/pi-extensions/lib/resume-launch-identity.ts +162 -0
- package/pi-extensions/lib/session-id.js +8 -5
- package/pi-extensions/lib/socket-discovery.ts +3 -3
- package/prompts/engraving.md +13 -5
- package/run.sh +688 -282
- package/scripts/agy-bridge-config.py +5 -1
- package/scripts/check-acp-backend-preflight.ts +1 -1
- package/scripts/check-acp-carrier-augment.ts +246 -10
- package/scripts/check-acp-cortex.ts +5 -5
- package/scripts/check-acp-overlay.ts +13 -3
- package/scripts/check-acp-prompt-lifecycle.ts +565 -0
- package/scripts/check-acp-stop-reason.ts +342 -0
- package/scripts/check-acp-stream-hooks.ts +504 -0
- package/scripts/check-elapsed.sh +25 -0
- package/scripts/check-entwurf-bridge-boot.ts +51 -4
- package/scripts/check-entwurf-bridge-pi-free.ts +6 -5
- package/scripts/check-entwurf-control-rpc.ts +4 -3
- package/scripts/check-entwurf-resume-args.ts +72 -70
- package/scripts/check-entwurf-session-identity.ts +14 -10
- package/scripts/check-entwurf-v2-contract.ts +34 -59
- package/scripts/check-entwurf-v2-decider.ts +17 -177
- package/scripts/check-entwurf-v2-lock.ts +5 -2
- package/scripts/check-entwurf-v2-matrix.ts +3 -53
- package/scripts/check-entwurf-v2-production.ts +2 -91
- package/scripts/check-entwurf-v2-release.ts +10 -105
- package/scripts/check-entwurf-v2-runner.ts +4 -85
- package/scripts/check-entwurf-v2-send-fallback.ts +5 -6
- package/scripts/check-entwurf-v2-send.ts +0 -28
- package/scripts/check-entwurf-v2-surface.ts +198 -128
- package/scripts/check-entwurf-v2-visible-resume.ts +445 -0
- package/scripts/check-fresh-cut-gate.sh +1 -1
- package/scripts/check-gate-qualification.ts +101 -7
- package/scripts/check-install-container.sh +10 -2
- package/scripts/check-install-surface.ts +1 -1
- package/scripts/check-keyset-overlap.py +1 -1
- package/scripts/check-meta-facts.ts +249 -0
- package/scripts/check-meta-identity-consumers.ts +1 -1
- package/scripts/check-meta-session.ts +169 -0
- package/scripts/check-mux-launch-tmux.ts +316 -0
- package/scripts/check-mux-launch.ts +288 -0
- package/scripts/check-mux-launcher-fence.ts +264 -0
- package/scripts/check-mux-parent-artifact.ts +195 -0
- package/scripts/check-mux-placement-tmux.ts +322 -0
- package/scripts/check-mux-placement.ts +323 -0
- package/scripts/check-mux-resume-call.ts +282 -0
- package/scripts/check-probe-cli-shim.ts +25 -22
- package/scripts/check-probe-ordering.ts +121 -81
- package/scripts/check-release-gate-outcomes.ts +452 -0
- package/scripts/check-resume-launch-identity.ts +244 -0
- package/scripts/check-socket-discovery.ts +1 -1
- package/scripts/fixtures/mux-parent-transcript.scrubbed.jsonl +3 -0
- package/scripts/inventory-verification-surface.ts +349 -0
- package/scripts/lib/claude-launcher-fence.ts +322 -0
- package/scripts/lib/live-skip.ts +33 -0
- package/scripts/lib/mutation-qualify.ts +109 -3
- package/scripts/lib/probe-acp-turn.ts +33 -12
- package/scripts/lib/step-outcome.sh +88 -0
- package/scripts/meta-bridge-doctor.sh +6 -8
- package/scripts/meta-facts.ts +60 -0
- package/scripts/mutants/acp-augment.json +106 -0
- package/scripts/mutants/acp-cortex.json +2 -2
- package/scripts/mutants/acp-overlay.json +17 -0
- package/scripts/mutants/acp-prompt-lifecycle.json +100 -0
- package/scripts/mutants/acp-stop-reason.json +80 -0
- package/scripts/mutants/acp-stream-hooks.json +158 -0
- package/scripts/mutants/bridge-boot-resume.json +45 -0
- package/scripts/mutants/meta-facts.json +50 -0
- package/scripts/mutants/meta-identity.json +36 -0
- package/scripts/mutants/mux-boundary.json +196 -0
- package/scripts/mutants/mux-fresh-call.json +185 -0
- package/scripts/mutants/mux-launcher-fence.json +123 -0
- package/scripts/mutants/mux-parent-artifact.json +39 -0
- package/scripts/mutants/mux-resume-call.json +148 -0
- package/scripts/mutants/probe-ordering.json +6 -1021
- package/scripts/mutants/release-gate.json +140 -0
- package/scripts/mutants/resume-args.json +76 -0
- package/scripts/mutants/resume-launch-identity.json +96 -0
- package/scripts/mutants/v2-surface.json +74 -23
- package/scripts/mutants/v2-visible-resume.json +215 -0
- package/scripts/new-session-id.ts +9 -4
- package/scripts/smoke-acp-bundled-mcp-live.ts +4 -3
- package/scripts/smoke-acp-carrier-augment-live.ts +2 -2
- package/scripts/smoke-acp-cortex-live.ts +8 -14
- package/scripts/smoke-acp-long-turn-live.ts +185 -0
- package/scripts/smoke-acp-mcp-live.ts +2 -2
- package/scripts/smoke-acp-memory-containment-live.ts +2 -2
- package/scripts/smoke-acp-ordering-probe-live.ts +2 -2
- package/scripts/smoke-acp-overlay-live.ts +2 -2
- package/scripts/smoke-acp-provider-live.ts +2 -2
- package/scripts/smoke-acp-raw-turn-live.ts +3 -3
- package/scripts/smoke-acp-session-reuse-live.ts +2 -2
- package/scripts/smoke-acp-skill-live.ts +2 -2
- package/scripts/smoke-acp-socket-citizen-live.ts +4 -3
- package/scripts/smoke-acp-v2-send-live.ts +4 -3
- package/scripts/smoke-agy-native-push-live.ts +10 -20
- package/scripts/smoke-claude-native-resume-live.sh +13 -3
- package/scripts/smoke-entwurf-chain-live.ts +352 -0
- package/scripts/smoke-entwurf-v2-matrix-live.ts +3 -3
- package/scripts/smoke-meta-honesty.sh +1 -1
- package/scripts/smoke-meta-install-state.sh +4 -0
- package/scripts/smoke-mux-fresh-call-live.ts +365 -0
- package/scripts/smoke-mux-lifecycle-live.ts +1136 -0
- package/scripts/smoke-pi-attach.ts +1 -1
- package/scripts/smoke-user-scope-citizen.sh +1 -1
- package/scripts/tsconfig.json +1 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-preflight.js +0 -160
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-spawn-production.js +0 -273
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-spawn.js +0 -216
- package/pi-extensions/lib/entwurf-v2-spawn-production.ts +0 -373
- package/pi-extensions/lib/entwurf-v2-spawn.ts +0 -323
- package/scripts/check-acp-sdk-surface.ts +0 -236
- package/scripts/check-entwurf-v2-spawn-production.ts +0 -551
- package/scripts/check-entwurf-v2-spawn.ts +0 -399
- package/scripts/smoke-entwurf-v2-spawn-live.ts +0 -188
- package/scripts/smoke-entwurf-v2-spawn-resume-live.ts +0 -469
package/README.md
CHANGED
|
@@ -10,25 +10,16 @@ npm package: <https://www.npmjs.com/package/@junghanacs/entwurf>
|
|
|
10
10
|
|
|
11
11
|
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.
|
|
12
12
|
|
|
13
|
-
> **Repository shape.** This repo is **entwurf-core (v2 dispatch) + native-harness bridges + a pi adapter + an ACP plugin**. Pi is one supported harness adapter — important because it supplies control sockets and hosts the ACP plugin today — but it is not the project subject. Claude Code is shipped as a mailbox-backed meta-session; Antigravity (`agy`) is shipped as a native-push citizen with automatic `PreInvocation` birth, ambient garden-id status, and a managed MCP/permission install surface. Codex has a launch-mode-specific verified delivery probe documented in [DELIVERY.md](./DELIVERY.md), but no managed native-citizen install lane yet. The ACP plugin ships two backends through one adapter rail: Claude (the reference) and Snowflake Cortex Code (landed in 0.13.0 under
|
|
14
|
-
|
|
15
|
-
<details>
|
|
16
|
-
<summary>Watch archived pre-0.12 demo (2131×1142 GIF, click to expand)</summary>
|
|
17
|
-
|
|
18
|
-
> This GIF is historical pre-0.12 evidence and still shows the retired v1 demo flow. The current 0.12 tool surface is `entwurf_v2`; a v2-native demo retake is a follow-up.
|
|
19
|
-
|
|
20
|
-

|
|
21
|
-
|
|
22
|
-
</details>
|
|
13
|
+
> **Repository shape.** This repo is **entwurf-core (v2 dispatch) + native-harness bridges + a pi adapter + an ACP plugin**. Pi is one supported harness adapter — important because it supplies control sockets and hosts the ACP plugin today — but it is not the project subject. Claude Code is shipped as a mailbox-backed meta-session; Antigravity (`agy`) is shipped as a native-push citizen with automatic `PreInvocation` birth, ambient garden-id status, and a managed MCP/permission install surface. Codex has a launch-mode-specific verified delivery probe documented in [DELIVERY.md](./DELIVERY.md), but no managed native-citizen install lane yet. The ACP plugin ships two backends through one adapter rail: Claude (the reference) and Snowflake Cortex Code (landed in 0.13.0 under the measured contract in [docs/acp-backend-rail.md](./docs/acp-backend-rail.md#cortex-code-audit-d1d10)).
|
|
23
14
|
|
|
24
15
|
```text
|
|
25
16
|
Claude Code / Codex / agy / pi
|
|
26
17
|
→ garden id
|
|
27
18
|
→ entwurf_v2
|
|
28
|
-
→ control-socket |
|
|
19
|
+
→ control-socket | meta-mailbox | native-push
|
|
29
20
|
```
|
|
30
21
|
|
|
31
|
-
[`entwurf_v2`](#entwurf_v2--canonical-dispatch-verb) is the canonical dispatch surface over *existing* garden citizens — live control-socket send,
|
|
22
|
+
[`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. It starts no process on any rail: the hidden background resume that used to answer a dormant target was withdrawn under the visible-first rule, so a dormant citizen rejects honestly here and is reopened by a separate lifecycle verb, `entwurf_resume_call`. The meta-record is the sole address authority (#50 C4): a record-less control socket is refused as a `record-less-socket` diagnostic, never dispatched. The v1 entwurf verbs are gone. Fresh siblings and resumes are separate verbs — `entwurf_fresh_call` opens a NEW sibling in the operator's own tmux session and learns its garden id from the callback it makes, while `entwurf_resume_call` reopens a DORMANT pi citizen under its own garden id in a visible window without running a turn; the non-Claude ACP lane landed earlier — Snowflake Cortex Code became the second backend in 0.13.0.
|
|
32
23
|
|
|
33
24
|
**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.
|
|
34
25
|
|
|
@@ -66,17 +57,11 @@ Claude's `install-meta-bridge` and agy's `install-agy-{bridge,statusline,hooks}`
|
|
|
66
57
|
|
|
67
58
|
> **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.
|
|
68
59
|
|
|
69
|
-
> **Project boundary.** `entwurf` is not a fork, plugin, dependency, or integration layer of `oh-my-pi`, and it is not developed in coordination with `oh-my-pi`. Issues in other Pi / ACP projects may be useful as general implementation references, but they are not `entwurf` integration issues unless this repository explicitly links them as such.
|
|
70
|
-
|
|
71
|
-
> **Anthropic subscription billing.** From 2026-06-15, Anthropic third-party agent paths (ACP, Agent SDK, `claude -p`, entwurf's Claude backend) consume a separate Agent SDK credit pool, distinct from Claude chat and the `claude` CLI used as an interactive terminal. `entwurf` respects that distinction — no bypass, no emulation — and preserves capability dignity across supported backends (see [AGENTS.md](./AGENTS.md) invariants #7, #9, #10). The recommended default runtime leans toward paths outside Anthropic's Agent SDK metering, with Claude invoked when its quality is worth the credit cost. The operator decides the mix.
|
|
72
|
-
|
|
73
|
-
> **Gemini CLI migration.** Google announced that Gemini CLI stops serving requests for Google AI Pro / Ultra and unpaid individual tiers on **2026-06-18**; those users should migrate to [Antigravity CLI](https://antigravity.google/product/antigravity-cli). See Google's migration note: [Transitioning Gemini CLI to Antigravity CLI](https://developers.googleblog.com/an-important-update-transitioning-gemini-cli-to-antigravity-cli/). The repository still carries existing Gemini adapter code for compatibility, but this README no longer presents Gemini CLI as a recommended setup path during the migration window.
|
|
74
|
-
|
|
75
60
|
## Concept primer
|
|
76
61
|
|
|
77
62
|
A few words that look unusual for a coding tool.
|
|
78
63
|
|
|
79
|
-
- **Entwurf** (기투, projection-of-self) — sibling sessions with their own runtime boundary. Not "delegate," not "worker," not "sub-agent."
|
|
64
|
+
- **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.
|
|
80
65
|
- **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`, agy `PreInvocation`). 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, so callers discover facts with `entwurf_peers` and deliver with `entwurf_v2` instead of choosing a transport by hand.
|
|
81
66
|
- **Engraving** — optional short operator text delivered through each backend's native identity carrier. Not a giant hidden prompt, not a tool catalog.
|
|
82
67
|
- **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, Codex, Antigravity, …) 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.
|
|
@@ -115,8 +100,8 @@ entwurf check-bridge
|
|
|
115
100
|
```
|
|
116
101
|
|
|
117
102
|
This writes `.pi/settings.json` in the target project with the absolute path to
|
|
118
|
-
the installed `entwurf-bridge` launcher.
|
|
119
|
-
|
|
103
|
+
the installed `entwurf-bridge` launcher. (The old `~/.pi/agent/` target-registry
|
|
104
|
+
link is gone — #50 C3; nothing reads it.) The global install is the easiest path when
|
|
120
105
|
Claude Code's USER-scope MCP registration should work from every cwd.
|
|
121
106
|
|
|
122
107
|
### From npm — project-local install
|
|
@@ -159,7 +144,7 @@ because Node refuses to strip `.ts` files under `node_modules`.
|
|
|
159
144
|
### Pi adapter / ACP plugin lane
|
|
160
145
|
|
|
161
146
|
To use the `entwurf` provider inside pi, install a compatible pi binary
|
|
162
|
-
separately (`@earendil-works/pi-coding-agent >=0.
|
|
147
|
+
separately (`@earendil-works/pi-coding-agent >=0.84.1 <0.85`). Then point pi at
|
|
163
148
|
the npm-installed package or development clone:
|
|
164
149
|
|
|
165
150
|
```bash
|
|
@@ -174,272 +159,21 @@ For daily operator sessions, launch pi with `--entwurf-control` — no id
|
|
|
174
159
|
injection; the meta-record mints the garden address (see [Garden launcher](#garden-launcher)). Older pi
|
|
175
160
|
versions may silently miss the provider/extension surface, so treat the pi floor
|
|
176
161
|
as release-critical for the ACP/plugin lane. A host that only uses
|
|
177
|
-
`entwurf-bridge` from Claude Code / Codex / Antigravity does not need pi
|
|
178
|
-
|
|
162
|
+
`entwurf-bridge` from Claude Code / Codex / Antigravity does not need pi at all for
|
|
163
|
+
delivery: no `entwurf_v2` rail launches a pi process.
|
|
179
164
|
|
|
180
|
-
###
|
|
165
|
+
### Native harness install and doctors
|
|
181
166
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
claude mcp add --scope user entwurf-bridge \
|
|
186
|
-
entwurf-bridge
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
If the host does not inherit the npm bin directory, use an absolute path to the
|
|
190
|
-
bin or `start.sh`. For a garden-native Claude Code meta-session (replyable by
|
|
191
|
-
garden id), run this on Linux. entwurf refuses new macOS meta-bridge
|
|
192
|
-
installs because its strict live-owner doctor currently depends on `/proc`; macOS
|
|
193
|
-
is **not yet verified/certified for this cut**, not permanently impossible, and
|
|
194
|
-
future native validation may reopen it. Package-level `os` is intentionally
|
|
195
|
-
unrestricted, and Darwin uninstall remains available for legacy cleanup.
|
|
196
|
-
|
|
197
|
-
```bash
|
|
198
|
-
entwurf install-meta-bridge
|
|
199
|
-
entwurf doctor-meta-bridge
|
|
200
|
-
```
|
|
201
|
-
|
|
202
|
-
> **Upgrade action:** after installing a package that moves the hook launch form, run `entwurf install-meta-bridge` and restart **every already-open Claude Code session** before trusting send/receive. A new hook reached through an old cached command fails closed: it may still mint a garden record, but the owner join it depends on is not the one the old command produces. Reinstall materializes the matching manifest; restart makes live Claude processes load it. This release moves to the exec form and requires Claude Code `>=2.1.217`; `install-meta-bridge` and `doctor-meta-bridge` refuse anything older outright, because an older Claude drops the hook's `args` silently and still reports success.
|
|
203
|
-
|
|
204
|
-
On npm/pnpm-installed packages, `doctor-meta-bridge` must use prebuilt JS for its
|
|
205
|
-
store scan and defer repo-only source-shape gates; Node refuses strip-types for
|
|
206
|
-
raw `.ts` helpers under `node_modules`. It also refuses any Claude Code below the
|
|
207
|
-
supported floor `>=2.1.217` (an older one silently drops the hook's `args` and still
|
|
208
|
-
reports success, so nothing else in the output could be trusted), checks Claude's
|
|
209
|
-
installed hooks are the exec form through the shipped `hook-launch.sh`, and on Linux
|
|
210
|
-
verifies every live Claude MCP process joins to live sender/receiver markers.
|
|
211
|
-
`launch form is UNSUPPORTED` means reinstall the meta-bridge; a live-owner-join failure after
|
|
212
|
-
that means restart the affected Claude session so it loads the new manifest. If no
|
|
213
|
-
matching MCP child exists the doctor reports `NOT CERTIFIED` and **exits nonzero** — a
|
|
214
|
-
host whose live tier could not be measured is not a certified host, and that is worded
|
|
215
|
-
differently from a broken install on purpose. If the doctor reports
|
|
216
|
-
`ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING`, reinstall a current package before
|
|
217
|
-
trusting the floor result.
|
|
218
|
-
|
|
219
|
-
**Release evidence boundary.** The required Linux `artifact-consumer` CI job
|
|
220
|
-
installs one read-only candidate tarball globally as a non-root user in a Node 24
|
|
221
|
-
container that cannot see the checkout, records the tarball digest and image
|
|
222
|
-
identity, freezes the package root, and drives the strict doctor. Its Claude cache,
|
|
223
|
-
owner process, and live bridge are deliberately synthetic fixtures; that job proves
|
|
224
|
-
the package-consumer/oracle shape, not a real Claude lifecycle. The direct B/B2
|
|
225
|
-
runtime evidence came from actual Claude 2.1.138/2.1.217 sessions on one NixOS host.
|
|
226
|
-
A production host is certified only when a **new session using the installed
|
|
227
|
-
artifact** makes `doctor-meta-bridge` exit 0 with the live join. See the explicit
|
|
228
|
-
support matrix and release order in [VERIFY.md](./VERIFY.md). For the release
|
|
229
|
-
artifact, first preserve one `npm pack` output, then run
|
|
230
|
-
`ENTWURF_CANDIDATE_TGZ=/absolute/path/to/candidate.tgz ./run.sh check-install-container`;
|
|
231
|
-
the gate prints that canonical path and sha256 and consumes it without re-packing.
|
|
232
|
-
Only that accepted file may be published under the explicitly authorized lane:
|
|
233
|
-
`--tag latest` for stable `0.12.8`, while preserving
|
|
234
|
-
`repair=0.12.8-repair.1`.
|
|
235
|
-
|
|
236
|
-
> **Generations — the fresh-cut policy.** The bridge is a call-relay, never a
|
|
237
|
-
> memory layer: a meta-record is routing state for a **current-generation**
|
|
238
|
-
> citizen, and memory lives in the native transcripts and the embedding axes
|
|
239
|
-
> outside this repo. Sessions flow. Four sentences fix the whole policy:
|
|
240
|
-
>
|
|
241
|
-
> 1. The active citizen store is **v3-only** and provides **no cross-generation
|
|
242
|
-
> address or resume continuity**.
|
|
243
|
-
> 2. If even one entry in the store fails certification, **install and citizen
|
|
244
|
-
> birth/registration refuse before writing** and demand the explicit fresh-cut
|
|
245
|
-
> verb.
|
|
246
|
-
> 3. `fresh-cut` **requires quiescence** — it verifies it and refuses while any
|
|
247
|
-
> surface is live *or* unprovable, never closing a session for you — then moves
|
|
248
|
-
> the whole previous generation to a timestamped archive
|
|
249
|
-
> (`meta-sessions.archive-<ts>`, `meta-mailbox.archive-<ts>`) and opens an
|
|
250
|
-
> empty live generation.
|
|
251
|
-
> 4. The archive is **forensic bytes only**: no runtime reads it and no restore
|
|
252
|
-
> verb exists. Native transcripts and the memory axes are never touched.
|
|
253
|
-
>
|
|
254
|
-
> **Certification** is one shared contract (`certifyActiveStore`), held identically
|
|
255
|
-
> by the install doctor and by all four identity writers — pi birth, the Claude
|
|
256
|
-
> `SessionStart` hook, the agy imprint, `entwurf_register_native`. Every
|
|
257
|
-
> `.meta.json` must be a **regular file** (a symlink is refused, never followed),
|
|
258
|
-
> **readable by the live schema**, **named by its own body**, and the **unique
|
|
259
|
-
> holder of its `nativeSessionId`**. All five defect kinds — previous generation,
|
|
260
|
-
> corruption, drift, duplicate, symlink — collapse to the same prescription, so
|
|
261
|
-
> there is nothing to diagnose or branch on.
|
|
262
|
-
>
|
|
263
|
-
> Note the deliberate scope, stated as it actually is. A **store-wide** scan runs on
|
|
264
|
-
> **identity writes**, in the doctor, and on the two read surfaces below — not on every
|
|
265
|
-
> mailbox poke; a call-relay does not re-scan the whole store per message. What every
|
|
266
|
-
> **targeted read** holds is the per-entry half of the same contract:
|
|
267
|
-
> `readMetaIdentityByGardenId` refuses a record that is not a regular file (a symlink is
|
|
268
|
-
> never followed, in *either* direction) and one whose body disagrees with its name,
|
|
269
|
-
> naming the verb. That is what the mailbox poke, the sender-marker trust and
|
|
270
|
-
> `entwurf_self` use, and it is all they need.
|
|
271
|
-
>
|
|
272
|
-
> Store-wide **uniqueness** is checked on the read snapshot at the two places where it
|
|
273
|
-
> is both affordable and load-bearing ([#52](https://github.com/junghan0611/entwurf/issues/52),
|
|
274
|
-
> 0.12.9):
|
|
275
|
-
>
|
|
276
|
-
> - **Discovery** — `listAllMetaIdentities`, and so `entwurf_peers`, already reads the
|
|
277
|
-
> whole store, so the check is free. Two records claiming one `nativeSessionId` are
|
|
278
|
-
> **not** two citizens: *neither* is listed (the store cannot say which one owns that
|
|
279
|
-
> session, and a facts surface may not mint an authority the certification refuses),
|
|
280
|
-
> both become diagnostics naming each other, and every unrelated citizen keeps listing.
|
|
281
|
-
> - **Dispatch** — `readAddressableMetaIdentity`, used by v2 `resolveTarget` and by the
|
|
282
|
-
> pi resume path. Those are the moments a record stops being data and becomes an
|
|
283
|
-
> **address**, they happen once per dispatch next to a socket connect and a spawn, and
|
|
284
|
-
> a duplicate there means direct-injecting one live conversation under two garden ids,
|
|
285
|
-
> or resuming one transcript twice under two per-garden-id locks. It fails **loud**; a
|
|
286
|
-
> soft `bad-target` is reserved for a record that is genuinely absent.
|
|
287
|
-
>
|
|
288
|
-
> **A rival is a record that could be addressed instead** — narrower than "a file whose
|
|
289
|
-
> bytes mention the same id". A **symlinked** entry is not a candidate and is *never
|
|
290
|
-
> read* (rule 1 again: following it to see whether it counts would break the rule in the
|
|
291
|
-
> act of enforcing it, and let planted foreign bytes quarantine a healthy citizen); a
|
|
292
|
-
> **drifted** or **unparseable** neighbour is not a candidate either, because no garden
|
|
293
|
-
> id can reach it. All three remain certification defects and the listing reports them as
|
|
294
|
-
> diagnostics — they just may not blind a healthy record. The opposite case is a
|
|
295
|
-
> **regular `.meta.json` this process cannot read**: that one might BE the duplicate, so
|
|
296
|
-
> it fails loud rather than being skipped, because "holds it alone" from a scan that
|
|
297
|
-
> never asked is the same vacuous pass in miniature. (`ENOENT` alone is the exception —
|
|
298
|
-
> a file that vanished mid-scan is not in the store.)
|
|
299
|
-
>
|
|
300
|
-
> Both store-wide read scans take entries **with their kind** from one shared
|
|
301
|
-
> `readActiveStoreEntries`, rather than each binding doing its own name-only `readdir`.
|
|
302
|
-
> That is what makes rule 1 structural: a scan handed bare names has no choice but to
|
|
303
|
-
> read the path, which is how both `entwurf_peers` bindings came to follow a symlinked
|
|
304
|
-
> record while the doctor refused the very same entry.
|
|
305
|
-
>
|
|
306
|
-
> A kind carried alongside a **name** is still only half of it, because a name can stop
|
|
307
|
-
> meaning what it meant. `lstat`-then-`readFileSync(path)` classifies one entry and reads
|
|
308
|
-
> another: replace the final path component with a symlink in between, and the read
|
|
309
|
-
> follows it into foreign bytes while every test on a settled store stays green. So the
|
|
310
|
-
> bytes of a record come from exactly one place — `readStoreRecordFile`, shared by the
|
|
311
|
-
> store-wide reader and by `readMetaIdentityByGardenId` — which opens with `O_NOFOLLOW`
|
|
312
|
-
> (a symlink fails the **open**, before a byte is read), decides the kind by `fstat` on
|
|
313
|
-
> **that file description** rather than on a name, and closes it in a `finally`. It also
|
|
314
|
-
> opens `O_NONBLOCK`, because classify-then-open never had to care that `open(fifo,
|
|
315
|
-
> O_RDONLY)` blocks until a writer appears, and deciding on the fd does. The reader does
|
|
316
|
-
> not flatten errno: callers still separate a record that raced away (`ENOENT`, skipped)
|
|
317
|
-
> from one that cannot be read (`EACCES`, loud) from one that was swapped (`ELOOP`,
|
|
318
|
-
> refused) — and the rival scan's raced-away skip depends on exactly that.
|
|
319
|
-
>
|
|
320
|
-
> That reader does not replace the `lstat` classification in front of the targeted read;
|
|
321
|
-
> the two hold **different** things, and collapsing them into "one enforcement point"
|
|
322
|
-
> was itself a regression (caught in review before shipping). The classification decides
|
|
323
|
-
> POLICY on a settled store **without opening anything**, which is what lets a socket, a
|
|
324
|
-
> device or a mode-000 directory earn the certification's own sentence — an `open` would
|
|
325
|
-
> answer `ENXIO` or `EACCES` there, errnos that say nothing about regularity, and the
|
|
326
|
-
> targeted read would start calling the host unreadable where the doctor calls the entry
|
|
327
|
-
> non-regular. Two contracts for one store is precisely the defect rule 1 exists to
|
|
328
|
-
> prevent. The fd layer decides the RACE: after a regular snapshot, its errno verdicts
|
|
329
|
-
> (`ELOOP`, `ENXIO`, a non-regular `fstat`) collapse back onto the settled sentences
|
|
330
|
-
> through one pure classifier, so a race never teaches the operator a second vocabulary
|
|
331
|
-
> for one state of the world. Because the classification answers first, those branches
|
|
332
|
-
> are unreachable from any settled store — which is why the classifier is pure and pinned
|
|
333
|
-
> with synthetic errnos rather than by a store on disk.
|
|
334
|
-
>
|
|
335
|
-
> This is not only a defence against external corruption. `upsertMetaSession` certifies
|
|
336
|
-
> and then writes, which is **not a transaction**, so two concurrent births — two
|
|
337
|
-
> `SessionStart` hooks, an `entwurf_register_native` racing an agy imprint — can both
|
|
338
|
-
> observe one clean store and mint different garden ids for one native session. A
|
|
339
|
-
> duplicate can therefore appear on a host where nothing was ever corrupted.
|
|
340
|
-
>
|
|
341
|
-
> There is **no migrator and no legacy reader anywhere in this repo** — carrying
|
|
342
|
-
> old records forward would serve a continuity the system deliberately does not
|
|
343
|
-
> promise. When the store cannot be read, the sender surfaces (`entwurf_self`,
|
|
344
|
-
> `entwurf_v2`, the inbox) **fail loud** naming the verb in both invocation
|
|
345
|
-
> forms; `entwurf_peers` keeps listing and folds unreadable records into a
|
|
346
|
-
> **diagnostic** line, because a facts surface that dies on corruption tells you
|
|
347
|
-
> less than one that shows what it could and could not read.
|
|
348
|
-
>
|
|
349
|
-
> **The installer entrypoints will not cross that boundary silently.** `setup`,
|
|
350
|
-
> `install` and `install-meta-bridge` each certify the store *before* they write
|
|
351
|
-
> anything: on a host that fails certification they refuse, name the verb, and
|
|
352
|
-
> leave your settings, plugin registry and `auth.json` untouched. So an upgrade
|
|
353
|
-
> through those commands is a refusal you answer, not a broken install you
|
|
354
|
-
> diagnose:
|
|
355
|
-
>
|
|
356
|
-
> ```bash
|
|
357
|
-
> entwurf meta-bridge-fresh-cut # quiesce-checked: archive the old generation, open an empty one
|
|
358
|
-
> ```
|
|
359
|
-
>
|
|
360
|
-
> **Read its exit status, don't just chain it.** The cut answers with a contract
|
|
361
|
-
> ([#54](https://github.com/junghan0611/entwurf/issues/54), `--help` prints it), because
|
|
362
|
-
> "it failed" is not one world-state:
|
|
363
|
-
>
|
|
364
|
-
> | exit | what already moved | what to do |
|
|
365
|
-
> |---|---|---|
|
|
366
|
-
> | `0` | the cut is complete | run `setup` |
|
|
367
|
-
> | `1` | **nothing** — a live/unprovable surface, an occupied archive destination, an unreadable surface | fix the named cause, re-run. **Do not** run `setup`: the store it refused is still there |
|
|
368
|
-
> | `2` | nothing — usage error | fix the command |
|
|
369
|
-
> | `3` | the cut transition is **incomplete** after at least one archive move; the fresh generation is not confirmed open | inspect, or re-run to finish under a new stamp |
|
|
370
|
-
> | `4` | the cut is **complete**; marker/socket residue could not be unlinked | `setup` may run. Prefer repairing the named residue before `setup`; if new citizens have already been born, remove it manually — another fresh-cut would archive their generation too |
|
|
371
|
-
>
|
|
372
|
-
> Only `0` is success — a failed sweep never becomes a pass. `fresh-cut && setup` is
|
|
373
|
-
> still the right chain for the common path; the codes are there so a runbook, CI or an
|
|
374
|
-
> agent can tell a refusal that changed nothing from a cut that already unblocked the
|
|
375
|
-
> install. An exit-4 re-run is safe only before `setup` or any new citizen birth.
|
|
376
|
-
>
|
|
377
|
-
> The refusal is a **preflight, not a lock**: it certifies the store as it stands
|
|
378
|
-
> at that moment. On a host whose pi/Claude settings point straight at a checkout,
|
|
379
|
-
> a `git pull` can put the new code in front of live sessions before you run
|
|
380
|
-
> anything at all, so order the upgrade explicitly — **quiesce the sessions on
|
|
381
|
-
> that host → pull → fresh-cut → `setup` → reopen**. `fresh-cut` enforces the
|
|
382
|
-
> quiesce half itself: a live control socket, a marker whose owner process is
|
|
383
|
-
> still running, a **native-push (agy) conversation its own adapter probe answers
|
|
384
|
-
> alive**, or **any surface it cannot prove is gone** — an indeterminate socket, an
|
|
385
|
-
> unreadable or symlinked marker, a conversation that probes indeterminate, a
|
|
386
|
-
> surface directory it cannot even inspect (absent is ENOENT alone, and the name
|
|
387
|
-
> must hold an actual directory — a symlinked surface is never followed) —
|
|
388
|
-
> refuses the cut before anything moves. Cutting needs proof of death, not absence
|
|
389
|
-
> of proof of life.
|
|
390
|
-
>
|
|
391
|
-
> **One marker is cleared without proving death, and it is the exception that keeps
|
|
392
|
-
> this path open.** A marker whose recorded `ownerPid` cannot own anything — `1`
|
|
393
|
-
> (init), `0`, a negative or non-integer — is *refuted by construction*: no writer in
|
|
394
|
-
> this tree can mint one any more, so on a current install it is **legacy or corrupt
|
|
395
|
-
> residue** — a pre-fix writer whose parent had been reparented to init (the retired
|
|
396
|
-
> shell-form Claude hook; the agy imprint, which asked only `> 0` until this repair),
|
|
397
|
-
> or a foreign/damaged marker, the only way a non-integer pid appears at all. The one
|
|
398
|
-
> file actually observed was a shell-form hook reparented to init. Honoring it was not
|
|
399
|
-
> merely wrong, it was a trap: init runs for the whole boot and its start-key does not
|
|
400
|
-
> change while it does, so the owner verdict is `live` and **the very action this
|
|
401
|
-
> refusal prescribes cannot change that** — you quiesce every session, exactly as
|
|
402
|
-
> told, and the cut refuses again. (Deleting the marker removes the claim rather than
|
|
403
|
-
> refuting the verdict; a reboot recomputes the key with no contract either way.)
|
|
404
|
-
> Meanwhile the one repair this page names could not run: on the affected host the cut
|
|
405
|
-
> stayed blocked until the marker file was removed by hand (#53 A, measured on a second
|
|
406
|
-
> Linux host 2026-07-25). Such a marker is now swept as residue and **reported apart
|
|
407
|
-
> from the dead ones** (`refuted:`), because a proof of invalidity is a different
|
|
408
|
-
> finding from a proof of death — and a stronger one.
|
|
409
|
-
>
|
|
410
|
-
> **Scope of that rule.** "A native session is never owned by init" is a property of the
|
|
411
|
-
> axis entwurf certifies — a Linux desktop/workstation host, where init is the service
|
|
412
|
-
> manager and every harness descends from a login session. A container that runs the
|
|
413
|
-
> harness **as pid 1** is a real shape, and there the marker would name a genuine owner.
|
|
414
|
-
> That host is **unsupported and fails closed**: the writers refuse the marker, so the
|
|
415
|
-
> session still gets its meta-record but never claims reply-addressability — a lost
|
|
416
|
-
> capability rather than a false identity. Reopening that lane needs new evidence and a
|
|
417
|
-
> new contract, not a looser predicate.
|
|
418
|
-
>
|
|
419
|
-
> That agy row is not symmetry for its own sake: `entwurf_register_native` writes a
|
|
420
|
-
> record and **no marker at all**, and `entwurf_v2` dispatches to such a citizen
|
|
421
|
-
> straight off the record, so marker absence is the *normal* state of a live,
|
|
422
|
-
> fully deliverable conversation. A socket+marker scan alone would call that host
|
|
423
|
-
> quiesced. Quiescing agy is also what makes the cut legal — with no host process
|
|
424
|
-
> the probe answers *dead* — so the rule can never trap you on a host you have
|
|
425
|
-
> already closed.
|
|
426
|
-
>
|
|
427
|
-
> **What quiescence is proven over, exactly.** The live-schema-readable identities of
|
|
428
|
-
> the current generation, plus the transport artifacts (sockets, markers). A record the
|
|
429
|
-
> live schema *cannot* read is archived without probing it, and that is not a claim
|
|
430
|
-
> that its session exited — only that those bytes front no addressable citizen here,
|
|
431
|
-
> since every targeted address/dispatch path refuses them. The alternative deadlocks the cut on the very
|
|
432
|
-
> store it exists to clear, and salvaging ids out of an unreadable shape in order to
|
|
433
|
-
> probe it would be the legacy reader this repo deleted. A native conversation that
|
|
434
|
-
> outlives a cut simply gets a **new** garden id from its next hook or registration —
|
|
435
|
-
> re-birth in the new generation, never continuity of the old address.
|
|
436
|
-
|
|
437
|
-
After upgrading a globally installed package, reinstall the native-harness surface you use before trusting it:
|
|
167
|
+
A plain MCP registration exposes the bridge tools; a **garden-native** session also
|
|
168
|
+
needs entwurf's lifecycle hook and identity marker. Use the managed installers rather
|
|
169
|
+
than editing native-harness state by hand:
|
|
438
170
|
|
|
439
171
|
```bash
|
|
172
|
+
# Claude Code (Linux-certified axis)
|
|
440
173
|
entwurf install-meta-bridge
|
|
441
174
|
entwurf doctor-meta-bridge
|
|
442
175
|
|
|
176
|
+
# Antigravity / agy
|
|
443
177
|
entwurf install-agy-bridge
|
|
444
178
|
entwurf install-agy-statusline
|
|
445
179
|
entwurf install-agy-hooks
|
|
@@ -448,17 +182,31 @@ entwurf doctor-agy-statusline
|
|
|
448
182
|
entwurf doctor-agy-hooks
|
|
449
183
|
```
|
|
450
184
|
|
|
451
|
-
|
|
185
|
+
Claude Code uses the supported floor `>=2.1.217`; older versions silently discard the
|
|
186
|
+
exec-hook `args`, so install and doctor fail loud rather than falling back. After any
|
|
187
|
+
upgrade, rerun the installer for the native harness you use and restart its existing
|
|
188
|
+
processes. A claimed Claude host is certified only when a **new** session using the
|
|
189
|
+
installed artifact makes `doctor-meta-bridge` exit 0 with the live owner join.
|
|
452
190
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
191
|
+
Linux is the only currently certified Claude meta-bridge axis. New macOS wiring is
|
|
192
|
+
refused because the strict live-owner doctor depends on `/proc`; Darwin uninstall
|
|
193
|
+
remains available for legacy cleanup, and the neutral package itself has no `os`
|
|
194
|
+
restriction. Detailed diagnosis and clean-host steps live in
|
|
195
|
+
[docs/setup-clean-host.md](./docs/setup-clean-host.md).
|
|
456
196
|
|
|
457
|
-
|
|
197
|
+
The active citizen store is V3-only. A store that fails certification is never
|
|
198
|
+
silently migrated: quiesce the native sessions, run `entwurf meta-bridge-fresh-cut`,
|
|
199
|
+
then reinstall. The cut archives routing records only—never native transcripts or
|
|
200
|
+
external memory—and no runtime reads the archive. Exit meanings and the complete
|
|
201
|
+
operator contract are in [docs/fresh-cut-policy.md](./docs/fresh-cut-policy.md).
|
|
458
202
|
|
|
459
|
-
|
|
203
|
+
`entwurf check-bridge` proves the MCP surface boots without backend auth. A real ACP
|
|
204
|
+
turn requires `LIVE=1 entwurf smoke-acp-provider-live`; the full release protocol and
|
|
205
|
+
host evidence boundaries are [VERIFY.md](./VERIFY.md) and [BASELINE.md](./BASELINE.md).
|
|
460
206
|
|
|
461
|
-
> **Extension set — do not filter.**
|
|
207
|
+
> **Extension set — do not filter.** The ACP provider, `entwurf-control`, and
|
|
208
|
+
> `model-lock` extensions ship as one set. Disable the package as a whole rather than
|
|
209
|
+
> filtering individual entries into a partially wired state.
|
|
462
210
|
|
|
463
211
|
### Backend prerequisites
|
|
464
212
|
|
|
@@ -468,20 +216,31 @@ plain MCP registrations.
|
|
|
468
216
|
2. **`require.resolve(...)` against the bundled package dependency** (`@agentclientprotocol/claude-agent-acp`). This is the default path; no extra global install needed.
|
|
469
217
|
3. **`PATH:claude-agent-acp` fallback** — used when the package resolution fails (e.g. a hand-edited `node_modules`).
|
|
470
218
|
|
|
471
|
-
The curated model registry exposes
|
|
219
|
+
The curated model registry exposes unprefixed Claude ids plus `cortex-` rows.
|
|
220
|
+
Codex is not an ACP backend or a shipped managed citizen lane: it has verified
|
|
221
|
+
native-delivery probe evidence only. Release 0.14.0 does not add Codex support;
|
|
222
|
+
any future managed lane still owes lifecycle, identity, installation, and doctors
|
|
223
|
+
before calling it supported.
|
|
472
224
|
|
|
473
|
-
**Snowflake Cortex Code is the second
|
|
225
|
+
**Snowflake Cortex Code is the second ACP backend** (contract and audit:
|
|
226
|
+
[docs/acp-backend-rail.md](./docs/acp-backend-rail.md#cortex-code-audit-d1d10)). Curated ids are
|
|
227
|
+
`cortex-auto`, `cortex-claude-opus-5`, `cortex-claude-sonnet-5`, and
|
|
228
|
+
`cortex-openai-gpt-5.4`.
|
|
474
229
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
230
|
+
The operator supplies an authenticated `cortex` CLI and selects a connection with
|
|
231
|
+
`entwurfProvider.cortexConnection` or `ENTWURF_ACP_CORTEX_CONNECTION`. `CORTEX_HOME`
|
|
232
|
+
must be absent: the adapter refuses it because it would bypass the session-scoped
|
|
233
|
+
HOME containment. Explicit MCP servers are projected into the overlay-private
|
|
234
|
+
`cortex/mcp.json`; only the bridge receives the real operator HOME needed for the
|
|
235
|
+
garden store.
|
|
481
236
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
237
|
+
`check-acp-cortex` runs in `pnpm run check:full`. Real acceptance is deliberately on demand:
|
|
238
|
+
|
|
239
|
+
```bash
|
|
240
|
+
LIVE=1 ENTWURF_ACP_CORTEX_CONNECTION=<conn> entwurf smoke-acp-cortex-live
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
The aggregate Claude floor does not run this smoke, so silence is not a Cortex PASS.
|
|
485
244
|
|
|
486
245
|
### Emacs frontends
|
|
487
246
|
|
|
@@ -538,135 +297,34 @@ Reference shape lives in [`pi/settings.reference.json`](./pi/settings.reference.
|
|
|
538
297
|
|
|
539
298
|
**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.
|
|
540
299
|
|
|
541
|
-
###
|
|
542
|
-
|
|
543
|
-
`entwurf-bridge` can also be registered in a separate MCP-aware harness (Claude Code, Codex CLI, Antigravity/`agy`, …). That host does **not** become a pi session and does **not** need to be ACP-backed. There are now two honest cases:
|
|
544
|
-
|
|
545
|
-
- **plain external MCP host**: no garden meta-record / sender marker. It can call the read surfaces (`entwurf_peers`, `entwurf_inbox_read`), but `entwurf_v2` sends are **refused by default** (#50 C4: "if we don't know who sent it, we don't send it"). The operator may wire the explicit hatch below; the send then goes out external/non-replyable.
|
|
546
|
-
- **garden-native native session**: a trusted lifecycle hook minted a garden id and sender marker — `SessionStart` for Claude Code, `PreInvocation` for agy. It is not a pi control-socket session, but it can be replyable by garden id when its own mailbox/probe rail says so.
|
|
547
|
-
|
|
548
|
-
**Which verb an external agent should reach for:** to deliver to / reply to a garden id, use **`entwurf_v2`** — it is the canonical delivery surface and the only one that reads whether the target is live pi, dormant pi, mailbox-backed Claude Code, or native-push Antigravity and routes correctly. Discover targets with `entwurf_peers`, confirm your own identity with `entwurf_self`, drain a mailbox with `entwurf_inbox_read`, and use `entwurf_register_native` only as the explicit/manual fallback for binding an already-running agy conversation (normal agy birth is automatic through the installed hook). Fresh sibling creation from nothing is a deferred lane. (The old v1 verbs `entwurf` / `entwurf_resume` / `entwurf_send` are gone.)
|
|
300
|
+
### External MCP registration
|
|
549
301
|
|
|
550
|
-
|
|
302
|
+
`entwurf-bridge` can also be registered in a separate MCP-aware harness (Claude Code,
|
|
303
|
+
Codex CLI, Antigravity). Two shapes exist and they are not interchangeable:
|
|
551
304
|
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
> **PATH boundary.** MCP servers are often launched by GUI/editor daemons and may not inherit the interactive shell's PATH. If `pi` works in your terminal but an external-host `entwurf_v2` spawn-bg resume fails with `spawn pi ENOENT`, pass a full PATH in the MCP server `env`, set `ENTWURF_BRIDGE_ENV_FILE` to a small shell file that exports PATH, or point the host at a wrapper that can find `pi`. `start.sh` sources only the explicit `ENTWURF_BRIDGE_ENV_FILE`; it never reads personal dotfiles automatically.
|
|
558
|
-
|
|
559
|
-
Example env file:
|
|
305
|
+
- **plain external MCP host** — no garden meta-record or sender marker. It can read the
|
|
306
|
+
surfaces, but `entwurf_v2` is **refused by default**: there is no authoritative sender.
|
|
307
|
+
- **garden-native session** — a trusted lifecycle hook minted a garden id, so it is
|
|
308
|
+
addressable and replyable by that id.
|
|
560
309
|
|
|
561
310
|
```bash
|
|
562
|
-
|
|
563
|
-
export PATH="$HOME/.local/share/pnpm:$HOME/.local/bin:$HOME/.nvm/versions/node/v24.15.0/bin:$PATH"
|
|
564
|
-
```
|
|
565
|
-
|
|
566
|
-
Then add it to the external MCP config:
|
|
567
|
-
|
|
568
|
-
```json
|
|
569
|
-
{
|
|
570
|
-
"env": {
|
|
571
|
-
"ENTWURF_BRIDGE_ENV_FILE": "/home/operator/.config/entwurf-bridge/env.sh",
|
|
572
|
-
"ENTWURF_BRIDGE_EXTERNAL_AGENT_ID": "external-mcp/claude-code"
|
|
573
|
-
}
|
|
574
|
-
}
|
|
311
|
+
claude mcp add --scope user entwurf-bridge entwurf-bridge
|
|
575
312
|
```
|
|
576
313
|
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
External/meta-session semantics:
|
|
582
|
-
|
|
583
|
-
- `entwurf_v2` from a plain external host is **refused by default** (no authoritative sender — #50 C4). With the explicit `ENTWURF_BRIDGE_ALLOW_ANONYMOUS_SENDER=1` hatch it delivers with `origin: "external-mcp"` / `replyable: false`; there is still no reply address.
|
|
584
|
-
- `entwurf_v2` from a trusted meta-session delivers with `origin: "meta-session"`, and `replyable` is **derived from that sender's own rail — not granted by being trusted**: a self-fetch sender (Claude Code) is replyable only while its receiver is live and armed, and a native-push sender (Antigravity) only while its adapter probe finds the live conversation. Identity survives either way; only `replyable` drops to `false`. When it is `true`, `wants_reply: true` is allowed and the receiver can reply to the sender's garden id.
|
|
585
|
-
- `entwurf_v2` with `intent: "owned-outcome"` to a dormant pi target needs `pi` on PATH (it spawns a `pi --entwurf-control` resume child); async completion followUp requires a replyable pi control-socket caller.
|
|
586
|
-
- `entwurf_self` returns the same authoritative identity for pi sessions **and** trusted meta-sessions. A plain external host with no pi env and no trusted sender marker still fails because there is no reply address to report.
|
|
587
|
-
|
|
588
|
-
#### Claude Code
|
|
589
|
-
|
|
590
|
-
Claude Code supports both CLI registration and a separated global MCP config. The separated file is recommended for dotfile / `agent-config` workflows because `~/.claude.json` also carries OAuth-bearing state.
|
|
591
|
-
|
|
592
|
-
**Option A — CLI add:**
|
|
593
|
-
|
|
594
|
-
```bash
|
|
595
|
-
claude mcp add --scope user entwurf-bridge \
|
|
596
|
-
bash /absolute/path/to/entwurf/mcp/entwurf-bridge/start.sh
|
|
597
|
-
```
|
|
598
|
-
|
|
599
|
-
This writes the entry into `~/.claude.json`'s top-level `mcpServers`. Good for one-off setup; do not version-control the resulting `~/.claude.json`.
|
|
600
|
-
|
|
601
|
-
**Option B — separated `~/.mcp.json`:**
|
|
602
|
-
|
|
603
|
-
```json
|
|
604
|
-
{
|
|
605
|
-
"mcpServers": {
|
|
606
|
-
"entwurf-bridge": {
|
|
607
|
-
"type": "stdio",
|
|
608
|
-
"command": "bash",
|
|
609
|
-
"args": [
|
|
610
|
-
"/absolute/path/to/entwurf/mcp/entwurf-bridge/start.sh"
|
|
611
|
-
],
|
|
612
|
-
"env": {
|
|
613
|
-
"ENTWURF_BRIDGE_EXTERNAL_AGENT_ID": "external-mcp/claude-code"
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
```
|
|
619
|
-
|
|
620
|
-
Claude Code reads `~/.mcp.json` in addition to `~/.claude.json`'s top-level `mcpServers`. The `env` block identifies the calling host on the receiver render — omit it and `entwurf_v2` shows `external-mcp/unknown-host`. If Claude Code permissions are locked down, allow `mcp__*` or `mcp__entwurf-bridge__*` in `~/.claude/settings.json`.
|
|
621
|
-
|
|
622
|
-
#### Codex CLI
|
|
623
|
-
|
|
624
|
-
Add the server to `~/.codex/config.toml`:
|
|
625
|
-
|
|
626
|
-
```toml
|
|
627
|
-
[mcp_servers.entwurf-bridge]
|
|
628
|
-
command = "/absolute/path/to/entwurf/mcp/entwurf-bridge/start.sh"
|
|
629
|
-
```
|
|
630
|
-
|
|
631
|
-
Codex has no managed citizen lifecycle (no sender marker), so this wiring can read `entwurf_peers`/`entwurf_inbox_read` but `entwurf_v2` sends are refused by default (#50 C4). To send anonymously anyway, add the explicit hatch to the same block: `env = { ENTWURF_BRIDGE_ALLOW_ANONYMOUS_SENDER = "1" }` — the send is then marked external/non-replyable (see the hatch paragraph above).
|
|
632
|
-
|
|
633
|
-
#### Antigravity CLI (`agy`)
|
|
634
|
-
|
|
635
|
-
Use the managed install surface rather than editing agy's files by hand:
|
|
636
|
-
|
|
637
|
-
```bash
|
|
638
|
-
entwurf install-agy-bridge
|
|
639
|
-
entwurf install-agy-statusline
|
|
640
|
-
entwurf install-agy-hooks
|
|
641
|
-
|
|
642
|
-
entwurf doctor-agy-bridge
|
|
643
|
-
entwurf doctor-agy-statusline
|
|
644
|
-
entwurf doctor-agy-hooks
|
|
645
|
-
```
|
|
646
|
-
|
|
647
|
-
The three adapters deliberately own different atoms:
|
|
648
|
-
|
|
649
|
-
- bridge: one `entwurf-bridge` server in `~/.gemini/config/mcp_config.json`, plus one narrow permission string per tool the normal agy workflow calls — `mcp(entwurf-bridge/entwurf_v2)`, `mcp(entwurf-bridge/entwurf_peers)`, `mcp(entwurf-bridge/entwurf_self)` — in `~/.gemini/antigravity-cli/settings.json`. agy defaults every `mcp` action to Ask, so a tool that ships without its own rule stops for a y/n on every call; `entwurf_inbox_read` is deliberately not granted (native-push has no inbox) and neither is the manual `entwurf_register_native` fallback;
|
|
650
|
-
- statusline: the complete `statusLine` subtree pointing at the bare stable bin `entwurf-agy-statusline`;
|
|
651
|
-
- hooks: one named `PreInvocation` hook pointing at the bare stable bin `entwurf-agy-imprint`.
|
|
652
|
-
|
|
653
|
-
Unrelated servers, permissions, settings, and hooks are preserved; every adapter has a state-backed honest inverse and refuses symlink-owned SSOTs. The installer never grants broad `command(*)`, `unsandboxed(*)`, or other YOLO policy — those remain operator decisions.
|
|
654
|
-
|
|
655
|
-
The **global** MCP config live agy reads is `~/.gemini/config/mcp_config.json`. `~/.gemini/antigravity-cli/mcp_config.json` is not the global MCP root; the bridge installer one-way cleans only a stale entwurf-owned entry there. After the first model invocation, the imprint hook binds the native `conversationId` to a garden id, the statusline shows `🪛 <garden-id> agy`, and sends from that MCP child carry `agentId=meta-session/antigravity` with `replyable:true` only when the record exists and the live native-push probe succeeds.
|
|
656
|
-
|
|
657
|
-
#### External-host skills and commands
|
|
658
|
-
|
|
659
|
-
MCP registration gives the external harness the tools; the host still needs workflow guidance. Put the Mitsein-over-MCP (cross-harness collaboration) rules in that host's instruction file or, when supported, as a host-native skill. Do not assume pi slash commands are portable across external hosts — if a workflow must work across Claude Code, Codex CLI, Antigravity, and future hosts, make it a skill or MCP tool rather than a command shortcut.
|
|
660
|
-
|
|
314
|
+
Per-harness registration (Claude Code `~/.mcp.json`, Codex `~/.codex/config.toml`, the
|
|
315
|
+
managed `install-agy-*` surfaces), the PATH/env boundary for GUI-launched MCP servers, the
|
|
316
|
+
anonymous-sender hatch, and the full external/meta-session semantics are in
|
|
317
|
+
[docs/external-mcp-host.md](./docs/external-mcp-host.md).
|
|
661
318
|
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.
|
|
662
319
|
|
|
663
320
|
## Per-backend operating surface
|
|
664
321
|
|
|
665
322
|
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.
|
|
666
323
|
|
|
667
|
-
**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. The four-tool baseline
|
|
324
|
+
**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>`.
|
|
668
325
|
|
|
669
|
-
|
|
326
|
+
Codex is not an ACP backend here. Its native delivery probe remains separate from
|
|
327
|
+
the governed ACP adapter rail and does not yet constitute a managed garden citizen.
|
|
670
328
|
|
|
671
329
|
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.
|
|
672
330
|
|
|
@@ -675,7 +333,8 @@ entwurf owns **no** memory layer at all — the ACP plugin's boundary explicitly
|
|
|
675
333
|
## Smoke commands
|
|
676
334
|
|
|
677
335
|
```bash
|
|
678
|
-
pnpm check #
|
|
336
|
+
pnpm check # everyday core (prints wall time; <=60s on the reference host)
|
|
337
|
+
pnpm run check:full # full deterministic floor (adds the hermetic + package/install tiers)
|
|
679
338
|
./run.sh check-bridge # entwurf-bridge direct MCP smoke (no backend auth)
|
|
680
339
|
./run.sh smoke-agy-install-state # agy MCP + exact permission ownership lifecycle (install/uninstall/doctor/inverse)
|
|
681
340
|
./run.sh smoke-agy-statusline-state # agy ambient garden-id install surface
|
|
@@ -698,11 +357,13 @@ LIVE=1 ./run.sh smoke-acp-provider-live # real pi provider path + progre
|
|
|
698
357
|
LIVE=1 ./run.sh smoke-acp-session-reuse-live # process-scoped reuse + codeword recall (S2d)
|
|
699
358
|
LIVE=1 ./run.sh smoke-acp-carrier-augment-live # augment delivery + empty-carrier billing clean (S2e-1)
|
|
700
359
|
|
|
701
|
-
LIVE=1 ./run.sh release-gate /tmp/scratch
|
|
360
|
+
LIVE=1 ./run.sh release-gate /tmp/scratch --cut # the single cut gate (MUST + BEHAVIOR; --cut refuses any MUST SKIP)
|
|
361
|
+
LIVE=1 ENTWURF_ACP_CORTEX_CONNECTION=<conn> ./run.sh smoke-acp-cortex-live # Cortex is on-demand: the aggregate does not re-certify it
|
|
702
362
|
```
|
|
703
363
|
|
|
704
|
-
`pnpm check`
|
|
705
|
-
|
|
364
|
+
`pnpm run check:full` includes the AGY permission contract matrix; the committed-mutant
|
|
365
|
+
gate qualification is scheduled separately (`./run.sh check-gate-qualification` — the CI
|
|
366
|
+
`check` job runs it on every push, and release-gate carries it as a MUST step). A gate a
|
|
706
367
|
release touches must kill its known defect for the claimed `[QK:<claim>]` reason —
|
|
707
368
|
the descriptions above name what each smoke covers, and no check count is quality
|
|
708
369
|
evidence on its own. Gate qualification needs the git work surface, while the matrix
|
|
@@ -744,9 +405,9 @@ For a real consumer arranging many skills, see [agent-config](https://github.com
|
|
|
744
405
|
|
|
745
406
|
**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.
|
|
746
407
|
|
|
747
|
-
A sibling has its own runtime boundary and its own provider/model identity — not a worker, delegate, or sub-agent.
|
|
408
|
+
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.
|
|
748
409
|
|
|
749
|
-
A two-pane recording covers the surface end-to-end — sibling resume, cross-process MCP dispatch across a different cwd, and a live peer greeting
|
|
410
|
+
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`).
|
|
750
411
|
|
|
751
412
|
<details>
|
|
752
413
|
<summary>Watch (2131×1142 GIF, click to expand)</summary>
|
|
@@ -757,29 +418,26 @@ A two-pane recording covers the surface end-to-end — sibling resume, cross-pro
|
|
|
757
418
|
|
|
758
419
|
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.)
|
|
759
420
|
|
|
760
|
-
In ACP-backed and external native-harness sessions, `entwurf-bridge` exposes
|
|
421
|
+
In ACP-backed and external native-harness sessions, `entwurf-bridge` exposes seven tools: `entwurf_v2`, `entwurf_peers`, `entwurf_fresh_call`, `entwurf_resume_call`, `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` 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 / Claude-Code-meta-mailbox / Antigravity-native-push, and refuses a dormant target instead of waking it. **To open a sibling that does not exist yet use `entwurf_fresh_call`** — it launches one fixed backend (`pi` or `claude-code`) as a visible window in the operator's own tmux session, requires an explicit model, passes that model on the runtime CLI, and hands it a first task; the sibling's first action is a nonce callback whose sender envelope carries its garden id. The launch receipt records the requested model plus tmux coordinates and never claims that the runtime accepted the model 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 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.
|
|
761
422
|
|
|
762
423
|
### `entwurf_v2` — canonical dispatch verb
|
|
763
424
|
|
|
764
|
-
`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`
|
|
425
|
+
`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:
|
|
765
426
|
|
|
766
427
|
| target state | intent | transport |
|
|
767
428
|
|---|---|---|
|
|
768
429
|
| live pi | fire-and-forget | control-socket send |
|
|
769
|
-
|
|
|
770
|
-
|
|
|
771
|
-
| dormant pi | fire-and-forget | **reject** (`dormant-fire-forget-unsupported`) |
|
|
430
|
+
| dormant pi | fire-and-forget | **reject** (`dormant-fire-forget-unsupported` — nothing is launched) |
|
|
431
|
+
| any pi | indeterminate probe | **reject** (`indeterminate-no-spawn` — an unestablished probe is not a measured death) |
|
|
772
432
|
| active self-fetch receiver | fire-and-forget | meta-mailbox enqueue + doorbell |
|
|
773
433
|
| inactive / terminated self-fetch receiver | fire-and-forget | **reject** (`mailbox-undeliverable` — no `.msg`, no doorbell) |
|
|
774
|
-
| self-fetch | owned-outcome | **reject** (no owned result over a mailbox) |
|
|
775
434
|
| live native-push conversation | fire-and-forget | native-push direct injection |
|
|
776
435
|
| dead / indeterminate native-push conversation | fire-and-forget | **reject** (`native-push-target-dead` / `native-push-probe-indeterminate`) |
|
|
777
|
-
| native-push | owned-outcome | **reject** (`native-push-no-resume-authority`) |
|
|
778
436
|
| record-less control socket (no meta-record) | any | **reject** (`record-less-socket` — pre-probe; diagnostic state, #50 C4) |
|
|
779
437
|
|
|
780
438
|
**`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, or native-push Antigravity and routes correctly; *when unsure which transport, use `entwurf_v2`*. This prevents callers from guessing a rail from the shape of an id.
|
|
781
439
|
|
|
782
|
-
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.
|
|
440
|
+
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. It opens one fixed Pi or Claude Code runtime visibly in the caller's tmux session with a required explicit model passed in that runtime's measured CLI dialect, returns only a synchronous launch receipt, and lets the sibling report its new address asynchronously through the sender envelope of a nonce callback. 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.
|
|
783
441
|
|
|
784
442
|
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.
|
|
785
443
|
|
|
@@ -793,7 +451,7 @@ A `--entwurf-control` session needs **no special launcher** (#50 C2): pi mints i
|
|
|
793
451
|
pi --entwurf-control
|
|
794
452
|
```
|
|
795
453
|
|
|
796
|
-
**Resuming an existing garden session.**
|
|
454
|
+
**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`.
|
|
797
455
|
|
|
798
456
|
**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).
|
|
799
457
|
|
|
@@ -803,6 +461,8 @@ System / developer carriers and rich pi context are separate.
|
|
|
803
461
|
|
|
804
462
|
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.
|
|
805
463
|
|
|
464
|
+
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.
|
|
465
|
+
|
|
806
466
|
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.
|
|
807
467
|
|
|
808
468
|
## Compaction policy
|
|
@@ -815,7 +475,7 @@ The footer uses ACP `usage_update.used / size` (backend prompt/tools/cache/sessi
|
|
|
815
475
|
|
|
816
476
|
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.
|
|
817
477
|
|
|
818
|
-
Does not: reconstruct full history, hydrate backend transcripts into pi history, emulate Claude Code or Codex, run broad multi-agent orchestration (entwurf is narrow,
|
|
478
|
+
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.
|
|
819
479
|
|
|
820
480
|
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.
|
|
821
481
|
|