@junghanacs/entwurf 0.40.0 → 0.41.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 +4 -2
- package/BASELINE.md +55 -2
- package/CHANGELOG.md +321 -0
- package/CONTRIBUTING.md +1 -1
- package/DELIVERY.md +18 -2
- package/FAQ.md +8 -4
- package/README.md +26 -8
- package/VERIFY.md +13 -10
- package/docs/acp-backend-rail.md +5 -1
- package/docs/external-mcp-host.md +8 -6
- package/docs/fresh-cut-policy.md +9 -1
- package/docs/setup-clean-host.md +24 -8
- package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +81 -19
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/cc-retire-pidfd.py +236 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/cc-retire-request.js +473 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/control-retire.js +125 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-retire-production.js +149 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-retire.js +158 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-surface.js +2 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-visible-resume.js +283 -20
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-composition.js +7 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-dispatch.js +47 -3
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-fresh-call.js +3 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js +96 -16
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +5 -29
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-launch.js +28 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-resume-call.js +26 -11
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-retire-request.js +76 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/resume-launch-identity.js +67 -17
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/retirement-event.js +211 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-hook.js +124 -10
- package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-pi-durable.js +53 -2
- package/mcp/entwurf-bridge/dist/scripts/meta-facts.js +1 -1
- package/mcp/entwurf-bridge/dist/scripts/probe-bridge-command.js +1 -0
- package/mcp/entwurf-bridge/src/index.ts +93 -19
- package/mcp/entwurf-bridge/test.sh +2 -2
- package/mcp/entwurf-bridge/tsconfig.build.json +10 -1
- package/package.json +4 -4
- package/pi/meta-bridge/entwurf-meta-receive/scripts/hook-launch.sh +3 -3
- package/pi-extensions/entwurf-control.ts +55 -20
- package/pi-extensions/lib/cc-retire-pidfd.py +236 -0
- package/pi-extensions/lib/cc-retire-request.ts +553 -0
- package/pi-extensions/lib/control-retire.ts +195 -0
- package/pi-extensions/lib/entwurf-control-rpc.ts +17 -1
- package/pi-extensions/lib/entwurf-v2-retire-production.ts +227 -0
- package/pi-extensions/lib/entwurf-v2-retire.ts +269 -0
- package/pi-extensions/lib/entwurf-v2-surface.ts +2 -1
- package/pi-extensions/lib/entwurf-v2-visible-resume.ts +390 -36
- package/pi-extensions/lib/fresh-call-composition.ts +15 -0
- package/pi-extensions/lib/fresh-call-dispatch.ts +45 -4
- package/pi-extensions/lib/herdr-fresh-call.ts +5 -0
- package/pi-extensions/lib/meta-session.ts +140 -16
- package/pi-extensions/lib/mux-fresh-call.ts +7 -27
- package/pi-extensions/lib/mux-launch.ts +29 -0
- package/pi-extensions/lib/mux-resume-call.ts +33 -12
- package/pi-extensions/lib/pi-retire-request.ts +111 -0
- package/pi-extensions/lib/resume-launch-identity.ts +104 -18
- package/pi-extensions/lib/retirement-event.ts +253 -0
- package/pi-extensions/meta-bridge-hook.ts +142 -13
- package/pi-extensions/meta-bridge-pi-durable.ts +56 -2
- package/run.sh +78 -7
- package/scripts/agy-bridge-config.py +4 -1
- package/scripts/build-bridge.sh +14 -0
- package/scripts/check-capability-bundle-reach.ts +135 -2
- package/scripts/check-entwurf-bridge-boot.ts +2 -1
- package/scripts/check-entwurf-fact-provider.ts +1 -0
- package/scripts/check-entwurf-v2-production.ts +2 -0
- package/scripts/check-entwurf-v2-surface.ts +7 -0
- package/scripts/check-gate-qualification.ts +14 -8
- package/scripts/check-herdr-activation.ts +2 -2
- package/scripts/check-herdr-placement.ts +1 -0
- package/scripts/check-hook-launch-topology.ts +6 -15
- package/scripts/check-install-container.sh +2 -2
- package/scripts/check-meta-doctor-oracle.sh +27 -1
- package/scripts/check-meta-facts.ts +8 -3
- package/scripts/check-meta-hook-session-switch.ts +230 -13
- package/scripts/check-meta-identity-consumers.ts +1 -0
- package/scripts/check-meta-listing.ts +1 -0
- package/scripts/check-meta-manifest-schema.py +1 -1
- package/scripts/check-meta-session.ts +31 -0
- package/scripts/check-meta-v3-record.ts +55 -1
- package/scripts/check-mux-resume-call.ts +35 -9
- package/scripts/check-peer-facts.ts +1 -0
- package/scripts/check-pi-durable-contact.ts +3 -1
- package/scripts/check-pi-durable-send.ts +4 -2
- package/scripts/check-pi-mcp-bridge.ts +1 -0
- package/scripts/check-resume-launch-identity.ts +62 -23
- package/scripts/lib/meta-hook-bundle.ts +72 -0
- package/scripts/meta-bridge-claude-floor.sh +13 -9
- package/scripts/meta-bridge-doctor.sh +17 -0
- package/scripts/meta-bridge-install.sh +6 -0
- package/scripts/meta-facts.ts +1 -1
- package/scripts/mutants/bridge-boot-resume.json +5 -2
- package/scripts/mutants/cc-retire-pidfd.json +129 -0
- package/scripts/mutants/cc-retire-request.json +171 -0
- package/scripts/mutants/control-retire.json +105 -0
- package/scripts/mutants/copilot-receive.json +2 -6
- package/scripts/mutants/entwurf-v2-retire.json +476 -0
- package/scripts/mutants/fresh-call-dispatch.json +37 -0
- package/scripts/mutants/meta-hook-session-switch.json +88 -0
- package/scripts/mutants/meta-identity.json +57 -0
- package/scripts/mutants/mux-fresh-call.json +1 -1
- package/scripts/mutants/mux-resume-call.json +40 -4
- package/scripts/mutants/pi-durable.json +79 -0
- package/scripts/mutants/pi-retire-request.json +72 -0
- package/scripts/mutants/resume-launch-identity.json +35 -8
- package/scripts/mutants/retirement-event.json +195 -0
- package/scripts/mutants/v2-surface.json +16 -0
- package/scripts/mutants/v2-visible-resume.json +283 -13
- package/scripts/probe-bridge-command.ts +1 -0
- package/scripts/smoke-agy-install-state.sh +2 -2
- package/scripts/smoke-copilot-mcp-state.sh +1 -1
- package/scripts/smoke-herdr-raw-install-live.sh +5 -4
- package/scripts/smoke-meta-install-state.sh +4 -4
- package/scripts/smoke-mux-lifecycle-live.ts +10 -10
- package/scripts/smoke-omp-mcp-state.sh +1 -1
- package/scripts/smoke-pi-durable-fresh-live.ts +5 -3
- package/scripts/smoke-pi-provider-state.sh +3 -3
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# entwurf
|
|
2
2
|
|
|
3
3
|
`entwurf` connects independent agent harnesses as visible siblings: open a new session,
|
|
4
|
-
reopen a dormant
|
|
4
|
+
reopen a supported dormant sibling in tmux, or reach an existing sibling by **garden id**. Fresh
|
|
5
5
|
sessions open in the caller's Herdr workspace or tmux session by default; explicit tmux
|
|
6
6
|
placement can name another existing session. Each harness keeps its own runtime,
|
|
7
7
|
authentication, tools and transcript.
|
|
@@ -26,7 +26,8 @@ Git branches, worktrees, file isolation and internal delegation remain their con
|
|
|
26
26
|
| Verb | Tool | Where it lands | Harnesses |
|
|
27
27
|
|---|---|---|---|
|
|
28
28
|
| **Open** a new sibling | `entwurf_fresh_call` | Herdr: a new unfocused tab in the caller's workspace. tmux: a new window in the caller's session, or an existing session named by `placement.tmuxSession`; without an explicit tmux placement, a Codex caller opens beside its own TUI pane | Herdr: pi, Claude Code. tmux: pi, pi-durable, Claude Code, Copilot, OMP, Codex |
|
|
29
|
-
| **Reopen** a dormant sibling | `entwurf_resume_call` | A visible tmux window under the same garden id, with the transcript
|
|
29
|
+
| **Reopen** a dormant sibling | `entwurf_resume_call` | A visible tmux window under the same garden id, with its record selecting the native session (Pi: identity-checked recorded transcript supplies launch metadata; Claude Code: `claude --resume <session id>` at the record cwd); the verb submits no prompt | pi, Claude Code |
|
|
30
|
+
| **Retire** one sibling the human operator explicitly requested (new in 0.41.0) | `entwurf_retire_call` | One guarded attempt on that target: a Pi shutdown request or Claude Code fd-pinned process termination. Refused/unknown outcomes remain possible; the record stays for guarded same-id return. Not a grant, a roster cleanup or an automatic retry | pi, Claude Code |
|
|
30
31
|
| **Message** an existing sibling | `entwurf_v2` | Its own rail: control socket, self-fetch mailbox or native push; delivery starts no process | pi, pi-durable, Claude Code, Copilot, OMP, Antigravity, Codex |
|
|
31
32
|
|
|
32
33
|
Antigravity is message-only: an existing conversation registers itself, and Entwurf never
|
|
@@ -53,7 +54,7 @@ ownership of the harness.
|
|
|
53
54
|
|
|
54
55
|
Prerequisites: **Node >=24.0.0**, Python 3 for `setup`, and any harnesses you choose
|
|
55
56
|
to use, already installed and authenticated. Pi is optional-by-presence, supported
|
|
56
|
-
range `>=1.3.0 <1.4`; Claude Code supported floor `>=2.1.
|
|
57
|
+
range `>=1.3.0 <1.4`; Claude Code supported floor `>=2.1.306` is required for its
|
|
57
58
|
managed exec-hook lifecycle. pnpm is needed only for source-checkout setup.
|
|
58
59
|
|
|
59
60
|
```bash
|
|
@@ -168,9 +169,11 @@ In that **harness session**, ask the agent to:
|
|
|
168
169
|
```
|
|
169
170
|
|
|
170
171
|
These are tools, not shell subcommands. Pi calls
|
|
171
|
-
`mcp__entwurf_bridge__<verb>` through Pi's built-in MCP. The
|
|
172
|
-
verbs; a pi citizen has **
|
|
172
|
+
`mcp__entwurf_bridge__<verb>` through Pi's built-in MCP. The 0.41.0 bridge exposes **nine**
|
|
173
|
+
verbs; a pi citizen has **seven active + two hidden** (`entwurf_inbox_read` and
|
|
173
174
|
`entwurf_register_native`), since it has no mailbox and its own extension births it.
|
|
175
|
+
Those counts describe the source; a running host offers what its already-loaded bridge
|
|
176
|
+
listed, so see [applying new source to a running host](./docs/setup-clean-host.md#applying-new-source-to-a-running-host).
|
|
174
177
|
The server key stays `entwurf-bridge`. An operator/trusted-project MCP entry with
|
|
175
178
|
that normalized namespace can replace the registration by Pi's own precedence;
|
|
176
179
|
`/mcp` shows it, and Entwurf does not scan or fight that choice.
|
|
@@ -195,7 +198,10 @@ pi / pi-durable / Claude Code / Copilot / OMP / Antigravity / Codex
|
|
|
195
198
|
|---|---|---|
|
|
196
199
|
| 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 |
|
|
197
200
|
| 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 |
|
|
198
|
-
| Reopen a dormant pi citizen under its own id | `entwurf_resume_call {target}` | Separate LAUNCH and
|
|
201
|
+
| Reopen a dormant pi or Claude Code citizen under its own id | `entwurf_resume_call {target}` | Separate LAUNCH and OBSERVATION receipts (pi: same-id socket; Claude Code: a new live receiver marker in the opened pane, bound to the record); the verb submits no prompt |
|
|
202
|
+
| Attempt retirement of one explicitly requested Pi or Claude Code target (new in 0.41.0) | `entwurf_retire_call {target}` | Separate action, append-only record and lock-release results. Pi ACK is request-only; CC exit is that fd-pinned process only |
|
|
203
|
+
|
|
204
|
+
**Retirement is never autonomous.** Invoke it only for a target/action the human operator explicitly requested; development, coordinator status and caller identity are not standing permission. Where a host provides or is configured with an approval step, that step applies as well; Pi and pi-durable 1.3.0 have no built-in approval prompt by default. The configured Claude ACP child is likewise [set to run without per-call permission prompts](./docs/acp-backend-rail.md#configured-claude-acp-permissions); ACP retirement remains unmeasured. Tool annotations and the caller's identity are hints and attribution, not permission. No roster expansion, timer or automatic retry of an unknown/cancelled/lost result. The retirement path records requested/outcome events under a target-gid sidecar; absence is unknown, not proof of no retirement. Source, compiled fixture, Native/LIVE, rollout and release acceptance remain separate: one dated test-owned [Native round trip per backend](./BASELINE.md#147-native-round-trip-2026-10-22-n1) is recorded, which is neither a release gate nor a rollout claim.
|
|
199
205
|
|
|
200
206
|
**Delivery starts no process.** A live pi receives over its record-addressed control
|
|
201
207
|
address (a Unix socket; on the experimental native-Windows Pi preview, a named pipe);
|
|
@@ -224,8 +230,20 @@ tmux session (not permitted inside Herdr), never a new session or a cwd lookup.
|
|
|
224
230
|
Omitted tmux placement follows the caller: Codex beside its own TUI pane matched
|
|
225
231
|
by thread-id, everyone else in their own session. Missing/ambiguous context refuses.
|
|
226
232
|
A launch receipt is neither the callback address nor proof of task processing.
|
|
227
|
-
Resume is pi
|
|
228
|
-
|
|
233
|
+
Resume is pi or Claude Code, tmux-only, same id, with the record as the sole address authority.
|
|
234
|
+
For Pi, the record selects the absolute transcript and its native id must match; the transcript's
|
|
235
|
+
session-header cwd and first `model_change` provider/model feed the launch resolver, without a
|
|
236
|
+
record/host cwd fallback. Claude Code uses `claude --resume <record session id>` at the record cwd,
|
|
237
|
+
without restoring model or permission flags;
|
|
238
|
+
a live, unprovably dormant, unbound or other-backend target refuses, and an unobserved window
|
|
239
|
+
stays visible without retry. Claude Code's [V3 arm binding](./docs/adding-a-harness.md#arm-binding-within-v3)
|
|
240
|
+
is a record–receiver witness, not a new identity or an occupancy/permission token.
|
|
241
|
+
Claude Code dormancy is read only from attaches the Entwurf hook committed: a process that
|
|
242
|
+
holds the session without one (hooks disabled, a lock timeout or store failure at its start)
|
|
243
|
+
is invisible to that check.
|
|
244
|
+
Claude Code same-id resume is new in 0.41.0. One dated test-owned
|
|
245
|
+
[Native round trip](./BASELINE.md#147-native-round-trip-2026-10-22-n1) was measured; its
|
|
246
|
+
release acceptance remains separate from the shipped Pi resume history.
|
|
229
247
|
Exact boundaries: [tmux launch](https://github.com/junghan0611/entwurf/blob/main/docs/mux-launch-rail.md)
|
|
230
248
|
and [Herdr launch](https://github.com/junghan0611/entwurf/blob/main/docs/herdr-launch-rail.md).
|
|
231
249
|
|
package/VERIFY.md
CHANGED
|
@@ -5,7 +5,7 @@ invariants live in gates; this file defines evidence strength, release acceptanc
|
|
|
5
5
|
and the manual judgements a gate cannot make.
|
|
6
6
|
|
|
7
7
|
> **Current surface.** `entwurf-bridge` exposes `entwurf_v2`, `entwurf_peers`,
|
|
8
|
-
> `entwurf_fresh_call`, `entwurf_resume_call`, `entwurf_callback`, `entwurf_self`, `entwurf_inbox_read`, and `entwurf_register_native
|
|
8
|
+
> `entwurf_fresh_call`, `entwurf_resume_call`, `entwurf_retire_call` (0.41.0; operator-requested target only), `entwurf_callback`, `entwurf_self`, `entwurf_inbox_read`, and `entwurf_register_native` — nine; a pi citizen offers seven with two hidden, the pi-durable contact eight. The ACP
|
|
9
9
|
> backends are Claude and Snowflake Cortex Code. Antigravity is a separate shipped
|
|
10
10
|
> native-push citizen lane. Codex is a native-push citizen supported in 0.21.0, whose birth hook is
|
|
11
11
|
> operator-owned and gated on one vendor trust receipt, with strict request-scoped identity and
|
|
@@ -105,7 +105,7 @@ that moved out of `scripts/` and into a file beside its subject would otherwise
|
|
|
105
105
|
|
|
106
106
|
> **The herdr rail is an on-demand axis until it has run.** `smoke-herdr-fresh-call-live` opens a real caller inside a real herdr pane, has it invoke the PUBLIC `entwurf_fresh_call`, and reads the child's nonce callback — the one axis no deterministic gate reaches, since the rail is selected from the caller's own process environment. **The release aggregate does not yet require the herdr rail**: herdr is optional (a cut on a host without it must not block), and first evidence precedes promotion — a rail whose first acceptance run is days old has not earned a place in the gate that blocks every cut. Run it directly with `LIVE=1 ./run.sh smoke-herdr-fresh-call-live` whenever a cut changes herdr rail code, and promote it to a MUST only after the axis has a run history to point at. Without `LIVE=1`, or without herdr/pi/claude on PATH, it reports protocol SKIP — never a pass.
|
|
107
107
|
|
|
108
|
-
> **The plugin's FIRST USER PATH is its own on-demand axis, and an npm acquisition receipt holds only for the ref it ran on (#118 홉 1).** Every other herdr gate proves the plugin against a source this repository controls: `check-herdr-plugin-build` drives a stub, and `smoke-herdr-plugin-build-live` redirects the product remote to a local bare clone with git `insteadOf` and asserts `identity.kind === "herdr-checkout"`. npm has been the production authority class since `dd84ac0`; the 0.30.0 follow-up re-pinned `@junghanacs/entwurf@0.30.0` with its published sha512 after the candidate windows rode `herdr-checkout` (`8b92e09`), and the 0.30.1 preparation rode that closed verification carrier again without inventing a registry integrity. The plugin snapshot `v2026.10.9-herdr.0.34.0` pinned the published `@junghanacs/entwurf@0.34.0` with its measured sha512 (#132); 0.31–0.33 were never pinned. The 0.34.1 preparation rode the closed `herdr-checkout` verification carrier again without inventing a registry integrity, and the snapshot `v2026.10.9-herdr.0.34.1` re-pins the published `@junghanacs/entwurf@0.34.1` with its measured sha512 (#135). The 0.34.2 preparation rode the same carrier, and the snapshot `v2026.10.11-herdr.0.34.2` re-pins the published `@junghanacs/entwurf@0.34.2` with its measured sha512 (#136). The source re-proof has separate dated npm rows: 2026-09-23 public `main=113ca53`, plugin 0.5.0, runtime 0.25.0 (CHANGELOG 0.25.1), and the 0.30.0 re-pin accepted at public `506c46f` in the [#126 thread](https://github.com/junghan0611/entwurf/issues/126). Neither certifies the 0.34.0 registry acquisition. That pin's real-npm swap / torn-swap recovery was measured locally on 2026-10-09 by driving the registry-shipped `scripts/herdr-runtime.mjs` (byte-equal to this checkout's) with its own npm acquisition: a 0.33.0 → 0.34.0 swap, and the same upgrade torn between its two renames then resumed, where the last-good 0.33.0 tree was restored before 0.34.0 was acquired again. Both ended `runtime-ready` at observed digest `sha256-e9d995ff…`, the accepted 0.34.0 artifact. That local receipt is recorded in the [#132 thread](https://github.com/junghan0611/entwurf/issues/132); it covers runtime recovery, not this smoke's cells. This smoke's public-ref acceptance of the 0.34.0 pin was measured locally at `--ref main`: PASS at resolved `b5d15d8` and again at the docs follow-up `8ed4d1a`, each acquiring `kind=npm @junghanacs/entwurf@0.34.0` — a main-ref receipt, not a tag-ref, native or full-Herdr claim. The 0.34.1 re-pin's npm-source rows — swap / torn-swap recovery, this smoke at a public ref, and the package consumer — are its own, recorded with that pin and never inherited from 0.34.0; checkout candidate evidence does not transfer. The 0.36.0 re-pin's public-ref row was measured on Herdr 0.9.4 at `--ref main`: PASS at resolved `9f33dd1`, acquiring `kind=npm @junghanacs/entwurf@0.36.0` at the accepted digest — a main-ref receipt, not a tag-ref claim. Its version-changing cross-ref reinstall (`herdr plugin install … --ref main` over the `v2026.10.13-herdr.0.35.1` installation) across a pinned Pi upgrade was a driver-level journey recorded in that snapshot's release notes, not a row of this smoke; `herdr plugin update` there was measured only as a same-ref already-up-to-date no-op. `LIVE=1 ./run.sh smoke-herdr-raw-install-live [--ref <REF>]` fills that row in a clean `node:24` container with no socket, config, cache or repo mounted, no `insteadOf`, the public remote spelled as a user would type it, and pi / herdr / Claude Code installed in the IMAGE BUILD as scaffolding (Hard Rule 17 — the product face begins at `herdr integration install pi`). herdr itself comes from `scripts/install-herdr-ci.sh` against `scripts/fixtures/herdr-supply.json`, so no version or digest lives here. `--ref` defaults to `main` rather than a pinned SHA because receipt (a) is defined as what a user gets today; the resolved commit is printed, so the receipt stays exact. It takes a REMOTE REF and not an arbitrary commit — measured on herdr 0.9.1, `--ref <sha>` fails as `couldn't find remote ref`, because the checkout fetches by name — so a candidate is addressed by its branch and pinned by the resolved commit the gate prints. **Its first question is a measurement, not an assumption:** herdr registers a plugin through a running server and falls back to `persist_plugin_offline` only on a connection error (`src/cli/plugin.rs:914-981,1016-1021` @ `c77af189`, 0.9.0 source), and a container has no server. The 2026-09-23 PASS installed with no server on the 0.9.1 binary `113ca53`'s `herdr-supply.json` pins, so that install path worked on 0.9.1 — inferred from the PASS, not a direct observation of the fallback branch; a later "no" on another herdr is #118 investigation output rather than a completion. `--yes` is always passed so a non-interactive exit 2 (`:189`) can never be misread as a missing server. **At a ref whose lock names `herdr-checkout` the acquisition axis reports a NAMED SKIP rather than a red, and the rest of the journey still runs.** That lock is the candidate carrier a cut legitimately sits on — 0.23.0 was tagged that way and `dd84ac0` pinned npm afterwards — so a red there would report a broken install when what is true is that the ref does not lock npm. What is asserted at BOTH lock kinds is that the runtime came from the source the ref locks, and the package name is read from the checkout manifest rather than the lock, because a `herdr-checkout` lock carries no `name` (`herdr-runtime.mjs:317-328`) and joining `undefined` throws instead of failing an assertion. **Cells [6]–[9] carry it past the install into the USE path (#118 H1-4/H1-5).** An install is not a use, so the same container then runs what those bytes became: the compiled bridge entry, the three `REQUIRED_BINS` as executables, and a real `entwurf check-bridge` printing
|
|
108
|
+
> **The plugin's FIRST USER PATH is its own on-demand axis, and an npm acquisition receipt holds only for the ref it ran on (#118 홉 1).** Every other herdr gate proves the plugin against a source this repository controls: `check-herdr-plugin-build` drives a stub, and `smoke-herdr-plugin-build-live` redirects the product remote to a local bare clone with git `insteadOf` and asserts `identity.kind === "herdr-checkout"`. npm has been the production authority class since `dd84ac0`; the 0.30.0 follow-up re-pinned `@junghanacs/entwurf@0.30.0` with its published sha512 after the candidate windows rode `herdr-checkout` (`8b92e09`), and the 0.30.1 preparation rode that closed verification carrier again without inventing a registry integrity. The plugin snapshot `v2026.10.9-herdr.0.34.0` pinned the published `@junghanacs/entwurf@0.34.0` with its measured sha512 (#132); 0.31–0.33 were never pinned. The 0.34.1 preparation rode the closed `herdr-checkout` verification carrier again without inventing a registry integrity, and the snapshot `v2026.10.9-herdr.0.34.1` re-pins the published `@junghanacs/entwurf@0.34.1` with its measured sha512 (#135). The 0.34.2 preparation rode the same carrier, and the snapshot `v2026.10.11-herdr.0.34.2` re-pins the published `@junghanacs/entwurf@0.34.2` with its measured sha512 (#136). The source re-proof has separate dated npm rows: 2026-09-23 public `main=113ca53`, plugin 0.5.0, runtime 0.25.0 (CHANGELOG 0.25.1), and the 0.30.0 re-pin accepted at public `506c46f` in the [#126 thread](https://github.com/junghan0611/entwurf/issues/126). Neither certifies the 0.34.0 registry acquisition. That pin's real-npm swap / torn-swap recovery was measured locally on 2026-10-09 by driving the registry-shipped `scripts/herdr-runtime.mjs` (byte-equal to this checkout's) with its own npm acquisition: a 0.33.0 → 0.34.0 swap, and the same upgrade torn between its two renames then resumed, where the last-good 0.33.0 tree was restored before 0.34.0 was acquired again. Both ended `runtime-ready` at observed digest `sha256-e9d995ff…`, the accepted 0.34.0 artifact. That local receipt is recorded in the [#132 thread](https://github.com/junghan0611/entwurf/issues/132); it covers runtime recovery, not this smoke's cells. This smoke's public-ref acceptance of the 0.34.0 pin was measured locally at `--ref main`: PASS at resolved `b5d15d8` and again at the docs follow-up `8ed4d1a`, each acquiring `kind=npm @junghanacs/entwurf@0.34.0` — a main-ref receipt, not a tag-ref, native or full-Herdr claim. The 0.34.1 re-pin's npm-source rows — swap / torn-swap recovery, this smoke at a public ref, and the package consumer — are its own, recorded with that pin and never inherited from 0.34.0; checkout candidate evidence does not transfer. The 0.36.0 re-pin's public-ref row was measured on Herdr 0.9.4 at `--ref main`: PASS at resolved `9f33dd1`, acquiring `kind=npm @junghanacs/entwurf@0.36.0` at the accepted digest — a main-ref receipt, not a tag-ref claim. Its version-changing cross-ref reinstall (`herdr plugin install … --ref main` over the `v2026.10.13-herdr.0.35.1` installation) across a pinned Pi upgrade was a driver-level journey recorded in that snapshot's release notes, not a row of this smoke; `herdr plugin update` there was measured only as a same-ref already-up-to-date no-op. `LIVE=1 ./run.sh smoke-herdr-raw-install-live [--ref <REF>]` fills that row in a clean `node:24` container with no socket, config, cache or repo mounted, no `insteadOf`, the public remote spelled as a user would type it, and pi / herdr / Claude Code installed in the IMAGE BUILD as scaffolding (Hard Rule 17 — the product face begins at `herdr integration install pi`). herdr itself comes from `scripts/install-herdr-ci.sh` against `scripts/fixtures/herdr-supply.json`, so no version or digest lives here. `--ref` defaults to `main` rather than a pinned SHA because receipt (a) is defined as what a user gets today; the resolved commit is printed, so the receipt stays exact. It takes a REMOTE REF and not an arbitrary commit — measured on herdr 0.9.1, `--ref <sha>` fails as `couldn't find remote ref`, because the checkout fetches by name — so a candidate is addressed by its branch and pinned by the resolved commit the gate prints. **Its first question is a measurement, not an assumption:** herdr registers a plugin through a running server and falls back to `persist_plugin_offline` only on a connection error (`src/cli/plugin.rs:914-981,1016-1021` @ `c77af189`, 0.9.0 source), and a container has no server. The 2026-09-23 PASS installed with no server on the 0.9.1 binary `113ca53`'s `herdr-supply.json` pins, so that install path worked on 0.9.1 — inferred from the PASS, not a direct observation of the fallback branch; a later "no" on another herdr is #118 investigation output rather than a completion. `--yes` is always passed so a non-interactive exit 2 (`:189`) can never be misread as a missing server. **At a ref whose lock names `herdr-checkout` the acquisition axis reports a NAMED SKIP rather than a red, and the rest of the journey still runs.** That lock is the candidate carrier a cut legitimately sits on — 0.23.0 was tagged that way and `dd84ac0` pinned npm afterwards — so a red there would report a broken install when what is true is that the ref does not lock npm. What is asserted at BOTH lock kinds is that the runtime came from the source the ref locks, and the package name is read from the checkout manifest rather than the lock, because a `herdr-checkout` lock carries no `name` (`herdr-runtime.mjs:317-328`) and joining `undefined` throws instead of failing an assertion. **Cells [6]–[9] carry it past the install into the USE path (#118 H1-4/H1-5).** An install is not a use, so the same container then runs what those bytes became: the compiled bridge entry, the three `REQUIRED_BINS` as executables, and a real `entwurf check-bridge` printing the exact verb set the installed bridge declares (nine since 0.41.0; each earlier recorded PASS printed its own date's set, and none of them is a nine-verb LIVE receipt) — the installed-runtime row below, measured rather than inferred from the bootstrap's refusal to install without them. Then the pi that this plugin WIRED is started with no `-e` and no `--no-extensions`, because the subject is the wiring and not this checkout: one `get_state` in `--mode rpc` costs zero tokens and needs no provider argument (a bogus `--provider` exits 1 before session_start; omitting the pair lets pi resolve the default the extension itself registers), and while that session is alive the cell reads its V3 record, its gardenId-keyed control socket, and the installed `entwurf peer-facts` reporting it `alive`. The plugin's own status fan is then driven with NEITHER side stubbed, in both states a user can be in: with no herdr server it must name `herdr-agent-list-failed` and go red rather than draw an empty table, and under a headless `herdr server` — scaffolding for that second read only, stopped again before the teardown — it exits 0 and COUNTS the citizen as `unobserved` instead of dropping it. Last, the shipped `entwurf herdr-plugin-deactivate` runs from the installed bin, deletes the runtime it is executing from, and the plugin reinstalls onto the host it fully left — the deactivation row, on the npm source. **What these cells still do not close:** swap / torn-swap recovery on the npm source, the package-consumer proof, and the fan drawing a citizen as a ROW — that needs `placement.kind === "herdr-pane"`, so the pi session must live in a herdr pane a session reference joins, and a headless container has no panes. **This smoke carries no test beside behavior and no mutant**, and that is a property of its class rather than an omission: it is a real-live container journey whose oracle is the container's own end state, so there is no pure seam to assert against and nothing a source mutation could kill deterministically. Like the other two herdr LIVE lanes it is **on-demand and not an aggregate MUST** — herdr is optional, a cut on a host without it must not block, and first evidence precedes promotion. Without `LIVE=1`, or without a usable Docker daemon, it reports a named SKIP; `ENTWURF_REQUIRE_DOCKER=1` turns the Docker absence red. Neither skip is a pass.
|
|
109
109
|
|
|
110
110
|
> **The Herdr plugin's install-time build has two axes, and one of them needs the network (#116 M3-b3).** `check-herdr-plugin-build` is deterministic and rides `check:hermetic`: the listing reaches the runner through its own spawn seam (there is no fake `herdr` binary in this repo), and the one cell that drives the REAL binary reports a named SKIP on a host without it — `ENTWURF_REQUIRE_HERDR=1` makes that absence red. What it cannot have is the real acquisition: `npm pack git+https://github.com/junghan0611/entwurf.git#<full sha>` builds the bridge through `prepare`, which installs devDependencies from the registry. So `LIVE=1 ./run.sh smoke-herdr-plugin-build-live` owns that axis, and its cells are **two kinds of evidence, labelled as such**: three are the real Herdr journey — a real `herdr plugin install` driving the real `[[build]]`, with the UNCHANGED product remote redirected to a local bare clone by git `insteadOf` (the one substitution, which is what makes an unpushed candidate testable) — covering remote-commit available, the `{pi}` → `{pi, claude-code}` reinstall with OpenCode as a zero-write negative control and exactly ONE counted vendor MCP owner entry, and the post-build Herdr-commit gap (where the ledger has already rebound to the gap commit, so the honest assertion is that transition and not unchanged bytes). The fourth cell — remote-commit **unavailable** — calls the shipped runtime leaf directly with the fixed product argv and is therefore **acquisition-leaf evidence, not a journey**; routing it through Herdr would add a second explanation for one red. Its oracle is the honest one too: active runtime, ledger and harness bytes preserved, while the journal is left as the certified `installing` retry authority the transaction deliberately writes before acquiring, and a normal install afterwards is green. Its Claude cell is where the vendor question the deterministic gates refuse to claim gets answered — whether Claude Code accepts an ABSOLUTE executable as an MCP command — and it reports a named skip on a host without `claude`, never a pass. Like the herdr fresh-call rail, this smoke is **on-demand and not an aggregate MUST**. The release aggregate does not require the plugin build smoke: herdr is optional, a cut on a host without it must not block, and first evidence precedes promotion.
|
|
111
111
|
|
|
@@ -115,7 +115,7 @@ that moved out of `scripts/` and into a file beside its subject would otherwise
|
|
|
115
115
|
>
|
|
116
116
|
> A cut that touches the prompt-lifecycle contract (no wall clock on a running turn) owes one long-turn acceptance the aggregate floor is too short to carry: `LIVE=1 ./run.sh smoke-acp-long-turn-live` drives a real turn whose tool work outlasts the retired 600s cutoff and requires exactly one cold ACP bootstrap in the transcript. It takes >12 minutes by construction and is on-demand, not part of `release-gate`.
|
|
117
117
|
>
|
|
118
|
-
> **The integrated mux lifecycle IS a release-gate MUST.** `LIVE=1 ./run.sh smoke-mux-lifecycle-live` enters through a real MCP `tools/call` on the shipped bridge launcher and follows one citizen the whole way: fresh-call launch receipt, the sibling's own nonce callback whose SENDER ENVELOPE is the address, an `entwurf_v2` control-socket send that lands in the sibling's own transcript, `entwurf_resume_call` REFUSED while that citizen is live, a stable-handle close proving pane and socket gone with the record preserved, delivery to the dormant citizen refused honestly, then the public `entwurf_resume_call` — whose LAUNCH and OBSERVATION receipts stay apart — and finally an `entwurf_v2` send the resumed session answers with a fact it was told BEFORE the window closed. That last step is the point: a socket answering under the same garden id proves a process stood up at that address, and only the recall proves the same conversation came back. Both pi provider shapes are driven to it (native `openai-codex/gpt-6.1-sol:low` and recorded-ACP `entwurf/claude-sonnet-5-5`, the only shape that re-injects a bridge extension), and the Claude Code cell
|
|
118
|
+
> **The integrated mux lifecycle IS a release-gate MUST.** `LIVE=1 ./run.sh smoke-mux-lifecycle-live` enters through a real MCP `tools/call` on the shipped bridge launcher and follows one citizen the whole way: fresh-call launch receipt, the sibling's own nonce callback whose SENDER ENVELOPE is the address, an `entwurf_v2` control-socket send that lands in the sibling's own transcript, `entwurf_resume_call` REFUSED while that citizen is live, a stable-handle close proving pane and socket gone with the record preserved, delivery to the dormant citizen refused honestly, then the public `entwurf_resume_call` — whose LAUNCH and OBSERVATION receipts stay apart — and finally an `entwurf_v2` send the resumed session answers with a fact it was told BEFORE the window closed. That last step is the point: a socket answering under the same garden id proves a process stood up at that address, and only the recall proves the same conversation came back. Both pi provider shapes are driven to it (native `openai-codex/gpt-6.1-sol:low` and recorded-ACP `entwurf/claude-sonnet-5-5`, the only shape that re-injects a bridge extension), and the Claude Code cell, whose sibling is still live at that point, has its resume refused as `target-live` with no window opened and no lock residue (the per-gid coordination lock is released). Claude Code's dormant same-id resume (#147 L2) is certified deterministically; its LIVE dormant→resume leg is not in this MUST yet. One test-owned retire→resume round trip outside this gate is recorded as dated n=1 evidence in [BASELINE](./BASELINE.md#147-native-round-trip-2026-10-22-n1); it is not this MUST.
|
|
119
119
|
>
|
|
120
120
|
> One cell cannot be fully fenced, and that is recorded rather than glossed: the recorded-ACP pi cell runs under the operator's REAL home, because its provider spawns `claude` as a child that authenticates from there — measured, a fenced HOME kills that turn with `Authentication required` before the model is ever reached, and `CLAUDE_CONFIG_DIR` alone does not fix it. That is the same trade `smoke-acp-bundled-mcp-live` already makes. While that cell runs, its citizen's control socket and per-gid lock exist under the operator's own `~/.pi/entwurf-control` and `~/.pi/entwurf-v2-locks`; its meta store stays fixture, so no garden record is minted outside the fixture, and the run ends by proving all six real roots hold the entry sets they started with, with no fixture garden id and no leftover lock.
|
|
121
121
|
>
|
|
@@ -285,7 +285,7 @@ caller/target/cross-harness LIVE are owed by `smoke-pi-durable-fresh-live`, a re
|
|
|
285
285
|
| Packed install | `check-pack-install` | Real tarball, but checkout-visible. |
|
|
286
286
|
| Linux artifact consumer | required `check-install-container` CI job against one preserved candidate | Fixtures prove package/oracle shape, not a real Claude lifecycle. |
|
|
287
287
|
| Exact release commit | the exact SHA's push run: all four required CI jobs green (the qualification body is not read, #144) | A different green SHA is not transferable evidence. |
|
|
288
|
-
| macOS Entwurf-only install | required `macos-install-surface` CI job at the exact release SHA | Shape-only, CERTIFIED (CI): certifies the Entwurf-only install path on macOS only — never Claude/Copilot/OMP/agy wiring, the pi rail, ACP or mux. Promoted from non-voting (`continue-on-error`) to required in 0.20.0 after its first CERTIFIED (CI) green on a GitHub Actions `macos-latest` runner (not a physical host): run 34303884286 @ `70eda03` (2026-09-09), 65s, all 10 steps success — the packed candidate installed clean, `entwurf --help` and `entwurf check-bridge` ran (the latter listing the seven `entwurf-bridge` MCP tools of that date;
|
|
288
|
+
| macOS Entwurf-only install | required `macos-install-surface` CI job at the exact release SHA | Shape-only, CERTIFIED (CI): certifies the Entwurf-only install path on macOS only — never Claude/Copilot/OMP/agy wiring, the pi rail, ACP or mux. Promoted from non-voting (`continue-on-error`) to required in 0.20.0 after its first CERTIFIED (CI) green on a GitHub Actions `macos-latest` runner (not a physical host): run 34303884286 @ `70eda03` (2026-09-09), 65s, all 10 steps success — the packed candidate installed clean, `entwurf --help` and `entwurf check-bridge` ran (the latter listing the seven `entwurf-bridge` MCP tools of that date; 0.41.0 declares nine, unmeasured on that runner), an all-harness-absent `entwurf setup` reached `result: green` across the five documented SKIP probes (`pi`/`claude`/`agy`/`copilot`/`omp`), and five checked paths (`.pi`, `settings.json`, `.gemini`, `.copilot`, `auth.json.bak`) plus a byte-identical `auth.json` proved zero harness/credential writes. A macOS runner failure now blocks every release cut, the same as any other required job. |
|
|
289
289
|
| Native-Windows carrier (carrier-bearing release, from 0.39.0) | one `windows-native-preview.yml` dispatch at the exact prepared SHA after its four `ci.yml` jobs are green: accepted build receipt (two byte-identical MSVC builds, exact import set), `pack` printing `present`, eight Windows addon/citizen cells on the installed tarball; that run's `win32-preview-tgz` is the release candidate, retrieved (never packed locally) with its `SHA256SUMS`, the Windows-printed install hash and the downloaded hash equal, then accepted by the Linux `check-install-container` unchanged (release skill M3w) | Experimental, shape-only, non-voting for `ci.yml`: no vendor turn, x64 runner only, named gaps in BASELINE. The four `ci.yml` jobs (incl. macOS) saw an addon-less pack, so the carrier-bearing tarball's macOS install is unmeasured. Artifacts expire after 14 days; the preserved copy is the durable one. |
|
|
290
290
|
| LIVE runtime | `LIVE=1 ./run.sh release-gate <scratch> --cut` plus any shipped on-demand backend axis | `--cut` enforces `SKIP=0`; a red wired gate blocks the cut. |
|
|
291
291
|
| Native Claude host | installed strict doctor against a new real session | Missing live join is `NOT CERTIFIED`, not a fixture PASS. |
|
|
@@ -293,7 +293,10 @@ caller/target/cross-harness LIVE are owed by `smoke-pi-durable-fresh-live`, a re
|
|
|
293
293
|
| Native Codex host | caller-seat topology accepted 2026-09-16 (56 assertions, exit 0); caller-DIRECTORY axis accepted the same day (65 assertions, exit 0) | Operator-owned birth/MCP/statusline/terminal-title atoms and the on-demand four-coordinate chain are measured on Linux at Codex 0.153.4: the app-server alone in session A (`$30/@41/%45`, pid 737636), and initial Pi / omitted-placement fresh Codex / Codex-opened outbound Pi all in a different session S (`$2`, `@46`/`@47`/`@48`; `@58`/`@59`/`@61` in the lane C run); exact callbacks and addressed delivery both ways. A ≠ S is the load-bearing half — the app-server's inherited `TMUX` names A, so the outbound Pi landing in S can only have come from the caller's own pane title, and its receipt records `seat-source=codex-title-anchor`. **#95 lane C added the second axis to the same card: WHERE the sibling starts.** Hop 1 joins four authorities that cannot borrow from each other — the pane's `#{pane_current_path}`, the vendor rollout's `session_meta.cwd`, the Entwurf record and the requested scratch — and hop 1's leg requests a cwd while hop 2 requests NONE, so hop 2's directory can only come from the Codex caller's own record; both are asserted different from the app-server's live `/proc/<pid>/cwd`. 65 and 56 are different contracts, not a regression. The 2026-09-12 fixed-home chain is PAST CONTRACT (#95 D1); `DELIVERY.md` owns every receipt and digest. Placement beside a TUI whose thread nobody named is unsupported and unclaimed. |
|
|
294
294
|
|
|
295
295
|
The repo-local `entwurf-release` skill owns the `land → prepare → make → publish`
|
|
296
|
-
state machine. Each mode is a separate GLG authorization.
|
|
296
|
+
state machine. Each mode is a separate GLG authorization. A narrower host restriction may
|
|
297
|
+
block a mode; it never waives a release-acceptance axis below. Feature-branch CI is not a
|
|
298
|
+
substitute for a forbidden local/LIVE floor: keep make blocked until the named cut has the
|
|
299
|
+
required execution authority and evidence. Preserve one candidate
|
|
297
300
|
(for a carrier-bearing release, the one retrieved from the preview run, never a local
|
|
298
301
|
pack), accept and publish those exact bytes without repacking, and record its digest and
|
|
299
302
|
consumer image outside this standing protocol.
|
|
@@ -344,7 +347,7 @@ every focused gate green at the time:
|
|
|
344
347
|
|
|
345
348
|
### Wording — avoid safety-interpretation contamination
|
|
346
349
|
|
|
347
|
-
When injecting a fact for a continuity check, use **plaintext that does not trigger model safety interpretation**. Avoid `secret token`, `password`, `API key`, `credential`, and meta-directives like "do not leak" — such wording makes the model treat the prompt as an exfiltration attempt and refuse, which makes **continuity look broken even when it is alive** (this happened once with `test-token-123`, misdiagnosed as a delegation failure). Instead: `The
|
|
350
|
+
When injecting a fact for a continuity check, use **plaintext that does not trigger model safety interpretation**. Avoid `secret token`, `password`, `API key`, `credential`, and meta-directives like "do not leak" — such wording makes the model treat the prompt as an exfiltration attempt and refuse, which makes **continuity look broken even when it is alive** (this happened once with `test-token-123`, misdiagnosed as a delegation failure). Instead: `The code word is owl → reply in one word → owl`; code names / colors / animal names. Do not mix continuity and safety-behavior verification in one prompt.
|
|
348
351
|
|
|
349
352
|
### bridge continuity vs semantic continuity
|
|
350
353
|
|
|
@@ -438,7 +441,7 @@ mailbox with `origin=pi-session`, `replyable=true`).
|
|
|
438
441
|
|
|
439
442
|
### 1.4 Cross-install / cross-backend parity (optional, high-value)
|
|
440
443
|
|
|
441
|
-
Compare a fresh self-awareness report across axes: (1) same backend, different install path — answer must be path-invariant; (2) same backend, different machine — identical native tool list + MCP server/tool set; (3) different backend, same bridge — same garden capability but **different** native tool surface (a Claude session reporting another backend's native tools is a fail); (4) native pi routing vs ACP-bridged, same model — the native pi citizen reaches the compiled `entwurf-bridge` through Pi's built-in MCP (`mcp__entwurf_bridge__<verb>`,
|
|
444
|
+
Compare a fresh self-awareness report across axes: (1) same backend, different install path — answer must be path-invariant; (2) same backend, different machine — identical native tool list + MCP server/tool set; (3) different backend, same bridge — same garden capability but **different** native tool surface (a Claude session reporting another backend's native tools is a fail); (4) native pi routing vs ACP-bridged, same model — the native pi citizen reaches the compiled `entwurf-bridge` through Pi's built-in MCP (`mcp__entwurf_bridge__<verb>`, seven active tools and two hidden in the 0.41.0 source; installed/live offering requires its own receipt), while the ACP backend reaches that bridge through the explicit `entwurfProvider.mcpServers` projection in its own callable dialect (see §2.1). Compare the actual ingress provenance and backend-native tools, not an assumed absence of MCP on pi; receive to the pi citizen remains its record-addressed control address (a Unix socket; a named pipe on the experimental native-Windows Pi preview). Honest "I cannot tell" hedging is preferable to inventing provenance. This is the current #125 comparison criterion, not a newly measured parity receipt. agy is graded by its native-citizen checklist, not by pretending it has an ACP overlay.
|
|
442
445
|
|
|
443
446
|
**Cortex on axis 3.** A cortex session must expose the same garden capability
|
|
444
447
|
while keeping cortex's own native tool surface. Its callable MCP identifier shape is
|
|
@@ -468,7 +471,7 @@ Pass (carrier honesty): the subject distinguishes engraving from pi-context-augm
|
|
|
468
471
|
|
|
469
472
|
- **Layer 0 — self-awareness:** ask environment self-awareness / MCP visibility / upstream-instruction awareness, guessing prohibited. Pass: recognizes native tool family, says "I don't know" honestly, answers MCP visibility only as configured, describes upstream instruction type without reproducing internal prompts. Fail: claims a nonexistent tool, conflates pi-custom and native tools, hallucinates MCP visibility, or conflates the two carriers (§1A.0).
|
|
470
473
|
- **Layer 1 — native tool use:** throw file-reading / structure-analysis / regression-hunting tasks. Pass: Read/Edit/Bash/Grep/Glob selection is natural; no detour through MCP or recursive `pi`. Fail: strange detours for simple reads; speaks from memory without reading.
|
|
471
|
-
- **Layer 2 — MCP boundary:** by default the
|
|
474
|
+
- **Layer 2 — MCP boundary:** by default the entwurf MCP tools are not visible (they appear only when `entwurf-bridge` is registered). Pass: says invisible tools are not visible; explains the native-vs-MCP boundary; treats `entwurf_register_native` as binding an already-running conversation, never as fresh spawn. Fail: pretends to use an unseen tool; mimics entwurf via recursive `pi`.
|
|
472
475
|
- **Layer 3 — focus across turns:** inject a fact, then accumulate turns mixing retrieval/exploration. Pass (post-0.4.1): after **8 turns** holds **3+ early facts** incl. **one verbatim string injected before turn 5**; no repeated exploration, no self-contradiction, no tool-strategy drift. Fail: forgets early reads; paraphrases instead of returning the verbatim string. Note: entwurf exposes no user-facing compaction; use the backend's `usage_update` footer as an overflow-risk signal (it follows the ACP backend's `used/size`, not pi's visible-transcript estimate).
|
|
473
476
|
- **Layer 4 — vs direct Claude Code:** requires a verifier holding **both** the `entwurf` and a direct path (human-in-loop, or both transport handles). Compare latency / native tool accuracy / detours / boundary confusion / quality around turns 10–15. Repeated tool confusion, long-turn forgetting, or boundary workarounds are a fail.
|
|
474
477
|
|
|
@@ -540,7 +543,7 @@ The minimum passing bar:
|
|
|
540
543
|
2. **All four required CI jobs green on the exact release commit:** `check`, `install-surface`, the required Linux `artifact-consumer`, and `macos-install-surface` — promoted from non-voting to required in 0.20.0 (see the release acceptance axes table above for what its green run does and does not certify); preserve the Linux artifact-consumer's tarball digest and image identity. For a carrier-bearing release those CI tarballs are addon-less and are NOT the release candidate: the exact release bytes are the M3w-retrieved preview tarball, accepted separately by the eight Windows cells and the Linux container (axes row "Native-Windows carrier"). The four-job floor, macOS included, is unchanged. A macOS runner failure now blocks the cut like any other required job.
|
|
541
544
|
3. **Live floor MUST green:** `LIVE=1 ./run.sh release-gate <dir> --cut` exits 0 reporting `MUST PASS=N FAIL=0 SKIP=0`; with `--cut` a single SKIP is red, so the exit code itself now carries this condition. A BEHAVIOR FAIL is advisory, not blocking.
|
|
542
545
|
4. **Native-host doctor green where the Claude meta-bridge is claimed:** a new post-install Claude session exists, live evidence is present, and the installed `doctor-meta-bridge` exits 0. `NOT CERTIFIED` is a release failure for that host, not a skip.
|
|
543
|
-
5. **Honest self-recognition:** the bridged model identifies its actual harness/backend, lists `entwurf-bridge` as the single MCP server with its
|
|
546
|
+
5. **Honest self-recognition:** the bridged model identifies its actual harness/backend, lists `entwurf-bridge` as the single MCP server with the tools its loaded bridge actually lists (the current source lists nine; a pi citizen offers seven with two hidden, the pi-durable contact eight), and presents a backend-native (not normalized) tool surface.
|
|
544
547
|
6. **Carrier separation honored:** engraving vs pi-context-augment kept distinct (§1A.0); no bridge-identity narrative attributed to the engraving carrier.
|
|
545
548
|
7. **agy shipped lane accepted:** all three agy doctors are green; automatic birth/statusline/sender identity and same-gid native-push reply are confirmed in a fresh conversation. `agentId=meta-session/antigravity` is correct; model display is not part of that contract. Same-pid concurrent conversation invocation is not claimed.
|
|
546
549
|
8. **Codex caller-seat topology accepted:** its birth/MCP/status-line/terminal-title doctors are green and the on-demand real visible `Pi → Codex → Pi` cell records four coordinates: the operator-owned app-server in its own session A; initial Pi, omitted-placement fresh Codex, and Codex-opened outbound Pi all in a DIFFERENT session S; exact callbacks and addressed delivery succeed both ways. A ≠ S is a precondition, not a preference — the app-server's inherited environment names A, so an outbound Pi in S can only have come from the caller's own pane title, and the receipt must name `codex-title-anchor` as the rule that chose it. 0 or 2+ matching panes, and a caller whose `[tui].terminal_title` lacks `thread-id`, reject without mutation. Entwurf neither creates nor supervises the app-server, infers attached-TUI panes, nor claims placement beside a TUI whose thread nobody named. (The 2026-09-12 fixed-`codex`-home acceptance this condition replaced stays recorded in `DELIVERY.md` as history; #95 D1 retired that room.) Codex remains outside ACP and has no resume claim.
|
|
@@ -558,7 +561,7 @@ Passing establishes a **release verification floor**, not an 8-hour/day operatio
|
|
|
558
561
|
|
|
559
562
|
- **ACP turn evidence.** The ACP path writes no `[entwurf:*]` stderr markers, and `ENTWURF_CHILD_STDERR_LOG` is not an ACP observation surface (its mirror in `pi-extensions/lib/entwurf-core.ts` belongs to the retired child-pi spawn and has no caller). Read three things instead: the lifecycle notices the backend writes into the assistant text (`[acp: preparing …]` / `[acp: reusing live session]`), the turn's typed `stopReason` (an ACP `cancelled` ending maps to `aborted`, `pi-extensions/lib/acp/backend.ts:865-866`), and the identity (pid + starttime) of the launcher and of any process the turn started.
|
|
560
563
|
- **Retired dedicated smokes, live code invariants** (manual/troubleshooting only — *not* part of the release floor):
|
|
561
|
-
- *
|
|
564
|
+
- *ACP-boundary model guard* — a session using the `entwurf` provider keeps its starting model: `pi-extensions/model-lock.ts` reverts set/cycle transitions touching that provider (native-to-native switching is free). This is an after-event revert, not a transcript-clean refusal. Separately, `session-store.ts` `SessionModelLockedError` refuses a persisted-record model mismatch at `decideBootstrap`. Gate: `check-model-lock` (core tier); the dedicated live `smoke-model-switch` was retired in v2. This is not a lifetime model pin on every garden citizen.
|
|
562
565
|
- *Cancel / abort cleanup* — an abort sends ACP `session/cancel` and lets the agent end its own turn (`awaitAcpPromptTurn`, `pi-extensions/lib/acp/backend.ts:762-801`); process-group teardown follows only after a bounded grace. A cancelled reuse turn keeps its live session; a cancelled first turn is not retained (`backend.ts:1861`), so the next turn bootstraps a new one. Dedicated `smoke-cancel` retired; invariant in code.
|
|
563
566
|
|
|
564
567
|
### Evidence preservation when a problem occurs
|
package/docs/acp-backend-rail.md
CHANGED
|
@@ -140,7 +140,7 @@ Dep bump ledger. 0.38.0 (#145) changes only the pi SDK set; the ACP rows are unc
|
|
|
140
140
|
| Claude ACP adapter | `@agentclientprotocol/claude-agent-acp 0.90.0` | **exact**, bundled | shipped since 0.36.1; resolved before any PATH fallback; declaration is not compatibility evidence |
|
|
141
141
|
| Claude Agent SDK | `0.3.296` (transitive) | **exact** oracle | the runtime risk surface behind the adapter |
|
|
142
142
|
| Anthropic SDK | `0.100.1` | **exact**, peer-resolution only | satisfies the Agent SDK peer floor (0.93.0+); never an API client here (gate L4) |
|
|
143
|
-
| Claude Code runtime | `>=2.1.
|
|
143
|
+
| Claude Code runtime | `>=2.1.306` (`entwurf.claudeCodeFloor`) | **floor** | below it, hook args are silently dropped; entwurf enforces this itself |
|
|
144
144
|
| Node | `>=24` (`engines.node`) | **floor** | single axis, derived everywhere else |
|
|
145
145
|
| Cortex Code | operator-installed CLI | **on-demand**, external | NOT a pinned dependency. Each LIVE record must name the exact CLI version it measured |
|
|
146
146
|
|
|
@@ -419,6 +419,10 @@ Otherwise record and defer. Every certification separates declared pins, determi
|
|
|
419
419
|
evidence, Claude LIVE aggregate evidence, and Cortex direct on-demand evidence (with its CLI
|
|
420
420
|
version). Per-bump measurements live in the ROADMAP **Dep bump(별도 트랙)** ledger.
|
|
421
421
|
|
|
422
|
+
### Configured Claude ACP permissions
|
|
423
|
+
|
|
424
|
+
Source configuration, not a retirement LIVE receipt: `pi-extensions/lib/acp/overlay.ts` sets `permissions.defaultMode: "bypassPermissions"`, and `config.ts` includes `mcp__*` in the default `permissionAllow`. The configured Claude ACP child therefore does not supply a per-call permission prompt; these settings do not widen the explicit callable tool surface. `enrichMcpServersWithEnvelope` in `config.ts` injects the host Pi session's `PI_SESSION_ID` and the ACP agent label into the explicit entwurf-bridge stdio entry. If that bridge exposes `entwurf_retire_call`, a promptless call attributed to the host Pi citizen is consequently possible by source inference — ACP retirement through this path is unmeasured, not part of the ordinary Native Pi/Claude Code n=1 round trips. Neither those settings, annotations nor caller attribution is an operator grant: an explicit human request for that target/action and agent operating policy are still required. This describes existing configuration, not a new ACP retirement ban or permission mechanism.
|
|
425
|
+
|
|
422
426
|
### Bridge reachability is part of the contract
|
|
423
427
|
|
|
424
428
|
Explicit entwurf-bridge connectivity inside an ACP turn is core value, so it is measured rather than
|
|
@@ -11,7 +11,7 @@ the one-line registration; everything an operator needs to actually wire a host
|
|
|
11
11
|
|
|
12
12
|
**Being garden-native is not the same as being replyable, and Copilot is the case that separates them.** Its hook writes a sender marker, so an `entwurf_v2` send carries its own garden id and the receiver learns who wrote — measured 2026-08-21 on Copilot CLI 1.0.80, where a live send arrived under its own garden id with `origin: "meta-session"` and `replyable: false`. Replyability arrived later and through a different process: a first-party extension (`run.sh install-copilot-receive`) that the CLI forks, which binds to the same V3 record and writes a receiver marker owned by its own pid (#82 RAIL 5). So a Copilot citizen is `replyable: true` exactly while that extension is armed for it, and `replyable: false` — honestly, with a real garden identity — when it is not installed, not launched with `COPILOT_CLI_ENABLED_FEATURE_FLAGS=EXTENSIONS` (which is what `entwurf copilot` sets for one invocation), not yet born, or gone. Read the two facts off different rails: the sender marker answers *who sent this*; the receiver marker answers *can a reply land*. The onboarding obligations are in [`adding-a-harness.md`](./adding-a-harness.md) step 7, and the evidence boundary — the managed LIVE acceptance that moved receive to D6, and what D7-partial / D3-pending still exclude — is in [`DELIVERY.md`](../DELIVERY.md).
|
|
13
13
|
|
|
14
|
-
**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, a mailbox-backed self-fetch citizen (Claude Code, Copilot, OMP), or native-push Antigravity/Codex 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. Open a NEW sibling with **`entwurf_fresh_call {backend, model, task, cwd?, placement?}`**, and reopen a DORMANT pi citizen under the same id with **`entwurf_resume_call {target}`**.
|
|
14
|
+
**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, a mailbox-backed self-fetch citizen (Claude Code, Copilot, OMP), or native-push Antigravity/Codex 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. Open a NEW sibling with **`entwurf_fresh_call {backend, model, task, cwd?, placement?}`**, and reopen a DORMANT pi or Claude Code citizen under the same id with **`entwurf_resume_call {target}`**. **`entwurf_retire_call {target}`** (new in 0.41.0) attempts retirement of exactly one target, and only one the human operator explicitly requested; mounting or seeing the tool, a coordinator role or caller identity is not permission, and an unknown result is not a reason to replay.
|
|
15
15
|
|
|
16
16
|
Observed: Claude Code, Codex CLI, Antigravity CLI, GitHub Copilot CLI and OMP all reach this MCP bridge. Claude, Copilot and OMP become symmetric/replyable through mailbox-backed meta-sessions; agy and Codex use native-push. Codex's identity is request-scoped `_meta.threadId`, and its replyability requires that exact record plus a loaded app-server thread. Plain external hosts still need an identity lane to send.
|
|
17
17
|
|
|
@@ -90,7 +90,7 @@ External/meta-session semantics:
|
|
|
90
90
|
#### Claude Code
|
|
91
91
|
|
|
92
92
|
Plain MCP registration works independently of the native lifecycle. A garden-native
|
|
93
|
-
mailbox citizen requires Claude Code >=2.1.
|
|
93
|
+
mailbox citizen requires Claude Code >=2.1.306 plus the managed meta-bridge installer;
|
|
94
94
|
older versions silently drop exec-hook arguments and are refused.
|
|
95
95
|
|
|
96
96
|
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.
|
|
@@ -185,7 +185,7 @@ entwurf doctor-agy-hooks
|
|
|
185
185
|
|
|
186
186
|
The three adapters deliberately own different atoms:
|
|
187
187
|
|
|
188
|
-
- 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), neither is the manual `entwurf_register_native` fallback,
|
|
188
|
+
- 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), neither is the manual `entwurf_register_native` fallback, neither are `entwurf_fresh_call` / `entwurf_resume_call` (both launch into the caller's own tmux server, which an agy conversation has none of), and `entwurf_retire_call` stays at agy's default Ask (a retirement is one operator-requested action, never a standing grant);
|
|
189
189
|
- statusline: the complete `statusLine` subtree pointing at the bare stable bin `entwurf-agy-statusline`;
|
|
190
190
|
- hooks: one named `PreInvocation` hook pointing at the bare stable bin `entwurf-agy-imprint`.
|
|
191
191
|
|
|
@@ -253,8 +253,10 @@ spawned. The installer refuses to write into a config that denylists its own key
|
|
|
253
253
|
**The tool names are omp's dialect, not Claude's.** omp mints `mcp__<server>_<tool>` after
|
|
254
254
|
lowercasing and replacing every `[^a-z_]+` run with `_`, collapsing runs and trimming edges,
|
|
255
255
|
so `entwurf_v2` surfaces as `mcp__entwurf_bridge_entwurf_v` — the trailing digit is eaten by
|
|
256
|
-
the charset, not by the length cap. The live tool list is the acceptance oracle
|
|
257
|
-
|
|
256
|
+
the charset, not by the length cap. The live tool list is the acceptance oracle. The recorded
|
|
257
|
+
observation mounted eight tools; the 0.41.0 bridge has nine, and that expanded OMP
|
|
258
|
+
live offering needs its own receipt. Retirement remains explicitly operator-requested, not
|
|
259
|
+
auto-granted by mounting the tool.
|
|
258
260
|
|
|
259
261
|
**And the NAME is not the invocation. Under omp's default settings an MCP tool is not a
|
|
260
262
|
function the model calls — it is a virtual file it writes to.** `tools.xdev` (boolean,
|
|
@@ -273,7 +275,7 @@ top-level (`Tool web_search not found`, upstream #5973), fixed by pinning it via
|
|
|
273
275
|
|
|
274
276
|
`read xd://` reports exactly what the default hides. `[측정]` on a host with only this
|
|
275
277
|
bridge registered, **11 devices**: omp's own `ast_edit`, `debug`, `lsp`, `browser`, plus all
|
|
276
|
-
seven `entwurf_*` (as reported in that earlier measurement; the current bridge declares
|
|
278
|
+
seven `entwurf_*` (as reported in that earlier measurement; the current source bridge declares nine). So the default does not merely wrap entwurf — it wraps omp's own LSP and
|
|
277
279
|
debugger too.
|
|
278
280
|
|
|
279
281
|
**`entwurf setup` writes this for a detected omp host, and `entwurf install-omp-config` is
|
package/docs/fresh-cut-policy.md
CHANGED
|
@@ -84,7 +84,15 @@ entwurf meta-bridge-fresh-cut && entwurf setup /path/to/project
|
|
|
84
84
|
## Upgrade order
|
|
85
85
|
|
|
86
86
|
A checkout-backed host can begin running new code immediately after `git pull`, so
|
|
87
|
-
make the boundary explicit:
|
|
87
|
+
make the boundary explicit. First distinguish an unreadable generation from a stale reader:
|
|
88
|
+
a known V3 schema extension (such as `attachIncarnation`) or newly admitted backend rejected
|
|
89
|
+
by an older strict reader is **reader-version skew, not a generation change**. Preserve the
|
|
90
|
+
records and native identity, roll every participating source/compiled/installed reader and
|
|
91
|
+
external mirror forward to the owning contract, then verify each active rail. Do not delete
|
|
92
|
+
fields, invent bindings, rewrite ids or execute an old reader's fresh-cut prescription merely
|
|
93
|
+
to silence that skew. Unmeasured readers remain an open rollout axis, not a pass.
|
|
94
|
+
|
|
95
|
+
For an actual generation transition, the order remains:
|
|
88
96
|
|
|
89
97
|
```text
|
|
90
98
|
quiesce sessions → update package/checkout → fresh-cut if prescribed → reinstall/setup → reopen
|
package/docs/setup-clean-host.md
CHANGED
|
@@ -31,14 +31,14 @@ evidence split, and its three states must not collapse into "macOS is supported"
|
|
|
31
31
|
| entwurf | global/project-local `@junghanacs/entwurf`, or a source checkout | operator command and garden capability |
|
|
32
32
|
| pi | optional-by-presence, `>=1.3.0 <1.4` — absent is an explicit setup SKIP, below-floor is a named FAIL | ACP provider, control sockets |
|
|
33
33
|
| Native durable (`pi-durable`) | supplied by the Entwurf package (#130): Entwurf's owned app carrier plus the exact 1.3.0 pi SDK set as production dependencies — setup PASS when they verify, a named FAIL otherwise (never SKIP) | the `entwurf pi-durable` contact; native birth/LIVE on this supply surface is a separate release proof (§2b) |
|
|
34
|
-
| Claude Code | optional, **`>=2.1.
|
|
34
|
+
| Claude Code | optional, **`>=2.1.306`** — the exec-form hook floor | Claude ACP auth/runtime and mailbox-backed native citizen |
|
|
35
35
|
| GitHub Copilot CLI | optional-by-presence, operator-installed and authenticated — absent is an explicit setup SKIP; detected composes all four units (birth/MCP/receiver/footer) | self-fetch citizen and visible fresh |
|
|
36
36
|
| OMP (`omp`) | optional-by-presence, operator-installed — absent is an explicit setup SKIP; detected composes all four units (birth/MCP/`tools.xdev` setting/receiver) | self-fetch citizen and visible fresh (accepted on one host — see §4b) |
|
|
37
37
|
| Antigravity `agy` | optional, operator-installed and authenticated | native-push citizen |
|
|
38
38
|
| OpenAI Codex CLI | optional-by-presence, operator-installed and authenticated; detected composes all four operator-owned units (birth/MCP/status-line/terminal-title) and stays non-green until the operator trusts the birth declaration once in a visible Codex; visible fresh also requires the operator-owned app-server, started with `entwurf codex-app-server` in a tmux session of the operator's choosing (#95 D1 retired the fixed `codex` home) | native-push and visible fresh, supported in 0.21.0 |
|
|
39
39
|
| Cortex Code | optional, operator-installed and authenticated | Cortex ACP backend |
|
|
40
40
|
|
|
41
|
-
Claude Code >=2.1.
|
|
41
|
+
Claude Code >=2.1.306 is required for the managed exec-hook lifecycle and same-id resume. The package
|
|
42
42
|
never supplies or proxies backend credentials.
|
|
43
43
|
|
|
44
44
|
On a physical Darwin host, `python3` may be the CommandLineTools stub (a
|
|
@@ -104,7 +104,7 @@ project-local install, or `./run.sh <command>` from a source checkout. Keep that
|
|
|
104
104
|
same scope when upgrading and checking; native process restarts are separate (§7).
|
|
105
105
|
|
|
106
106
|
`check-bridge` is auth-free. It proves the installed prebuilt MCP server boots and
|
|
107
|
-
lists the
|
|
107
|
+
lists the nine garden tools (0.41.0); it does not prove a backend model turn, native hook or retirement permission.
|
|
108
108
|
|
|
109
109
|
Neither npm form installs a harness runtime. `pi`, Claude Code, Copilot CLI, Codex CLI, agy,
|
|
110
110
|
Cortex, and their authentication remain operator-owned optional prerequisites for the
|
|
@@ -240,8 +240,15 @@ A plain `pi` can load the package without becoming a citizen.
|
|
|
240
240
|
In that session ask for `entwurf_self`, then `entwurf_peers`. Pick an existing
|
|
241
241
|
live garden id and ask for `entwurf_v2` with `intent: "fire-and-forget"` and your
|
|
242
242
|
message. These are harness tools, not shell subcommands; pi exposes them through
|
|
243
|
-
Pi's built-in MCP as `mcp__entwurf_bridge__<verb>`. The bridge has
|
|
244
|
-
pi hides `entwurf_inbox_read` and `entwurf_register_native`, leaving
|
|
243
|
+
Pi's built-in MCP as `mcp__entwurf_bridge__<verb>`. The 0.41.0 bridge has nine tools;
|
|
244
|
+
pi hides `entwurf_inbox_read` and `entwurf_register_native`, leaving seven active.
|
|
245
|
+
The added `entwurf_retire_call` is only for one target/action explicitly requested by the
|
|
246
|
+
human operator, never autonomous cleanup. Where a host provides or is configured with an approval
|
|
247
|
+
step, that step applies as well; Pi and pi-durable 1.3.0 have no built-in approval prompt by
|
|
248
|
+
default. The configured Claude ACP child is also [set to run without per-call permission prompts](./acp-backend-rail.md#configured-claude-acp-permissions); ACP retirement remains unmeasured.
|
|
249
|
+
Tool availability, annotations and caller identity are not a grant.
|
|
250
|
+
These candidate counts do not claim that an older installed package or running host has rolled
|
|
251
|
+
forward; see [applying new source to a running host](#applying-new-source-to-a-running-host).
|
|
245
252
|
Tool-call ingress is MCP; delivery to a pi citizen is still its record-addressed
|
|
246
253
|
control address (a Unix socket; a named pipe on the experimental native-Windows Pi
|
|
247
254
|
preview). A boot/listing from `check-bridge` is not a model-turn or
|
|
@@ -497,7 +504,7 @@ entwurf install-meta-bridge
|
|
|
497
504
|
entwurf doctor-meta-bridge
|
|
498
505
|
```
|
|
499
506
|
|
|
500
|
-
The supported floor `>=2.1.
|
|
507
|
+
The supported floor `>=2.1.306` is enforced by package metadata through installer
|
|
501
508
|
and doctor gates. Older Claude versions validate an exec-form hook but silently drop
|
|
502
509
|
its `args` at runtime, so there is no shell-form fallback.
|
|
503
510
|
|
|
@@ -1158,11 +1165,20 @@ may be missing or stale. Installed mode never bootstraps pnpm in `node_modules`.
|
|
|
1158
1165
|
|
|
1159
1166
|
### Applying new source to a running host
|
|
1160
1167
|
|
|
1161
|
-
New source on disk is not a fresh runtime.
|
|
1168
|
+
New source on disk is not a fresh runtime. From a checkout, each surface loads different bytes, and each refreshes differently:
|
|
1169
|
+
|
|
1170
|
+
| Surface | Loads | Refreshed by |
|
|
1171
|
+
|---|---|---|
|
|
1172
|
+
| pi-durable host (contact and bridge) | compiled `mcp/entwurf-bridge/dist/…`, even from a checkout (the bootstrap's `PACKAGED_BRIDGE_ENTRY`) | a build, then a new host process |
|
|
1173
|
+
| ordinary Pi's built-in MCP bridge | the same compiled `dist` entry (`entwurfBridgeCompiledEntry`); the Pi extension itself loads from the package source | a build, then a new Pi process |
|
|
1174
|
+
| an MCP host registered through `start.sh` (Claude Code, Codex, Copilot, OMP, agy) | the TypeScript source in a checkout (compiled only under `node_modules`) | a new bridge child; no build |
|
|
1175
|
+
| installed hook/extension copies (Claude Code meta-bridge, Codex birth, Copilot birth and receive, OMP birth and receive) | their own installed copy, neither `dist` nor the checkout source | that unit's owning installer, then a new vendor session |
|
|
1176
|
+
|
|
1177
|
+
An already-running process keeps the modules it loaded, whatever is on disk. So, for a host that has to run new bridge code:
|
|
1162
1178
|
|
|
1163
1179
|
1. Build once from the checkout (`pnpm run build-bridge`, or its body `bash scripts/with-dist-lock.sh bash scripts/build-bridge.sh` when no package lifecycle should run).
|
|
1164
1180
|
2. Compare the whole compiled output with the accepted candidate's, as a file-set and hash manifest. A hash of the selected entry alone is not enough: the entry can stay identical while a helper it loads differs. Check the pinned SDK set separately, and that the new process started after the build.
|
|
1165
|
-
3. Close the visible session and reopen it with its own native same-id resume.
|
|
1181
|
+
3. Close the visible session and reopen it with its own native same-id resume. Closing a working sibling is an operator decision for that session, not something an agent does because new bytes landed.
|
|
1166
1182
|
4. Confirm `entwurf_self` names the same garden id, and that the receiver/owner join points at the new process, whose bridge child started after the build.
|
|
1167
1183
|
|
|
1168
1184
|
A per-component setup PASS or a `git pull` does not show which bytes a running bridge loaded. On 2026-10-20 one host of the #107 pair still selected a pre-feature dist after a pull and a green setup. The cause was not traced. A hash check caught it before the first message, and a manual build and visible resume fixed it. Entwurf adds no auto-build, reload or watcher for this.
|
|
@@ -31,16 +31,18 @@
|
|
|
31
31
|
* seat, #105; nothing is ever created), optionally at ONE literal requested
|
|
32
32
|
* cwd (cross-repo fresh, #73); returns a LAUNCH receipt only, and the new
|
|
33
33
|
* address arrives later as the sender envelope of the sibling's nonce callback.
|
|
34
|
-
* - entwurf_resume_call — reopen ONE DORMANT pi citizen under its OWN garden id
|
|
35
|
-
* window; target-only,
|
|
36
|
-
* apart.
|
|
34
|
+
* - entwurf_resume_call — reopen ONE DORMANT pi or Claude Code citizen under its OWN garden id
|
|
35
|
+
* in a visible window; target-only, submits no prompt, LAUNCH and OBSERVATION
|
|
36
|
+
* receipts stay apart, the caller is attributed but grants nothing.
|
|
37
|
+
* - entwurf_retire_call — one operator-requested target, one retirement attempt; actor is not
|
|
38
|
+
* permission, and action / append-record / lock-release receipts stay separate.
|
|
37
39
|
* - entwurf_callback — ZERO-ARGUMENT fresh-sibling callback. Reads ENTWURF_CALLBACK_TARGET +
|
|
38
40
|
* ENTWURF_CALLBACK_NONCE from this process env (launcher-injected), validates
|
|
39
41
|
* grammar, and dispatches through the existing v2 runner. Refuses by name
|
|
40
42
|
* when env is absent/malformed or this process is a Codex-provenance bridge.
|
|
41
43
|
* No model-supplied target fallback.
|
|
42
44
|
*
|
|
43
|
-
* That list is the WHOLE public surface —
|
|
45
|
+
* That list is the WHOLE public surface — nine verbs — and `check-entwurf-bridge-boot`
|
|
44
46
|
* (G1f) holds it as an exact set on the runtime tools/list, so a verb added or dropped
|
|
45
47
|
* here without a decision is red rather than merely undocumented.
|
|
46
48
|
*
|
|
@@ -77,6 +79,8 @@ import { ENTWURF_PEERS_RENDER_LIMIT, renderEntwurfPeers } from "../../../pi-exte
|
|
|
77
79
|
import { computeSelfAddressability } from "../../../pi-extensions/lib/entwurf-self-address.js";
|
|
78
80
|
import { nativePushSupported } from "../../../pi-extensions/lib/entwurf-v2-contract.js";
|
|
79
81
|
import { resolveMailboxWakeModeCapability } from "../../../pi-extensions/lib/entwurf-v2-decider.js";
|
|
82
|
+
import { retireCitizen } from "../../../pi-extensions/lib/entwurf-v2-retire.js";
|
|
83
|
+
import { makeProductionRetireDeps } from "../../../pi-extensions/lib/entwurf-v2-retire-production.js";
|
|
80
84
|
import { runAndRenderEntwurfV2FromSurface } from "../../../pi-extensions/lib/entwurf-v2-surface.js";
|
|
81
85
|
import { makeVisibleResumeDeps, renderVisibleResume, visibleResume, } from "../../../pi-extensions/lib/entwurf-v2-visible-resume.js";
|
|
82
86
|
import { dispatchFreshCall, renderDispatchedFreshCall } from "../../../pi-extensions/lib/fresh-call-dispatch.js";
|
|
@@ -686,7 +690,7 @@ server.tool("entwurf_fresh_call", "Open ONE fresh visible sibling beside you and
|
|
|
686
690
|
// as an unused suppression while the escape goes back to being fixable.
|
|
687
691
|
// biome-ignore lint/complexity/noUselessEscapeInRegex: emitted to a Rust regex validator, see above
|
|
688
692
|
.regex(/^[A-Za-z0-9][A-Za-z0-9._/:\[\]-]*$/)
|
|
689
|
-
.describe("Required runtime model: canonical provider/model for pi, provider/<exact catalog model id> for pi-durable, a Claude Code model id/alias, or a Copilot/OMP/Codex model name."),
|
|
693
|
+
.describe("Required runtime model: canonical provider/model for pi (a bare alias is refused), provider/<exact catalog model id> for pi-durable, a Claude Code model id/alias, or a Copilot/OMP/Codex model name. Neither pi nor pi-durable may name the openrouter provider."),
|
|
690
694
|
task: z
|
|
691
695
|
.string()
|
|
692
696
|
.min(1)
|
|
@@ -763,36 +767,94 @@ server.tool("entwurf_fresh_call", "Open ONE fresh visible sibling beside you and
|
|
|
763
767
|
// resume` cannot open a window on its own and `mux-resume-call` cannot read a record — that is
|
|
764
768
|
// the import fence in docs/mux-launch-rail.md §11, expressed as a function argument.
|
|
765
769
|
const visibleResumeLaunch = (input) => {
|
|
766
|
-
const launched = resumeCall({ cwd: input.cwd, runtimeArgs: input.runtimeArgs });
|
|
770
|
+
const launched = resumeCall({ backend: input.backend, cwd: input.cwd, runtimeArgs: input.runtimeArgs });
|
|
767
771
|
return launched.ok
|
|
768
772
|
? { ok: true, handle: launched.receipt }
|
|
769
773
|
: { ok: false, reason: launched.reason, hint: RESUME_CALL_REJECT_HINT[launched.reason] };
|
|
770
774
|
};
|
|
771
|
-
server.tool("entwurf_resume_call", "Reopen ONE DORMANT pi citizen under its OWN garden id, in a visible window in the operator's
|
|
772
|
-
"session. The record supplies everything — which
|
|
773
|
-
"the only input is the target id: there is no model override, no task, and no
|
|
774
|
-
"
|
|
775
|
-
"
|
|
776
|
-
"
|
|
777
|
-
"
|
|
778
|
-
"
|
|
779
|
-
"
|
|
780
|
-
"
|
|
775
|
+
server.tool("entwurf_resume_call", "Reopen ONE DORMANT pi or Claude Code citizen under its OWN garden id, in a visible window in the operator's " +
|
|
776
|
+
"own tmux session. The record supplies everything — which session, which cwd, and for pi which transcript, " +
|
|
777
|
+
"model and provider — so the only input is the target id: there is no model override, no task, and no " +
|
|
778
|
+
"prompt, and a Claude Code session comes back as `claude --resume <its session id>` with none of the model " +
|
|
779
|
+
"or permission flags it was once started with. This verb submits NO prompt; what the runtime itself does " +
|
|
780
|
+
"after it comes back (for Claude Code, its hook re-arms the doorbell and unread mail may then wake a turn) " +
|
|
781
|
+
"is not measured, and talking to it is still entwurf_v2 fire-and-forget. You get TWO receipts and they mean " +
|
|
782
|
+
"different things: a LAUNCH receipt (tmux made a window and was asked to start the runtime) and an " +
|
|
783
|
+
"OBSERVATION receipt (pi: the control socket answered under the same id; Claude Code: a NEW live process in " +
|
|
784
|
+
"the window this call opened re-armed its receiver marker for the same session with a fresh arm binding the " +
|
|
785
|
+
"record also carries; or resume-unobserved; or record-unreadable with its cause). Unobserved is a real " +
|
|
786
|
+
"outcome, not an error to retry — the window is visible, so read it. A citizen that is already LIVE is " +
|
|
787
|
+
"refused: address it with entwurf_v2 instead. A Claude Code citizen is reopened only when the last validated " +
|
|
788
|
+
"owner the Entwurf hook armed for its session is proven gone AND that arm is still the record's last one; " +
|
|
789
|
+
"no marker, an unreadable or mismatched one, an inconclusive owner or an unbound one is refused, and a " +
|
|
790
|
+
"process holding the session without a committed hook attach is invisible to that proof. Other backends " +
|
|
791
|
+
"have no same-id resume. Requires that this agent itself runs inside tmux (inside herdr it is refused).", {
|
|
781
792
|
target: z
|
|
782
793
|
.string()
|
|
783
794
|
.min(1)
|
|
784
795
|
.regex(/^\d{8}T\d{6}-[0-9a-f]{6}$/)
|
|
785
|
-
.describe("Garden id of the DORMANT pi citizen to reopen (discover with entwurf_peers)."),
|
|
786
|
-
}, async ({ target }) => {
|
|
796
|
+
.describe("Garden id of the DORMANT pi or Claude Code citizen to reopen (discover with entwurf_peers)."),
|
|
797
|
+
}, async ({ target }, extra) => {
|
|
798
|
+
// Who asked, from the same authoritative caller identity fresh_call reads. Attribution only:
|
|
799
|
+
// an anonymous caller is reported as unattributed, never refused, and a record-backed one is
|
|
800
|
+
// granted nothing the target's record and live facts do not already decide.
|
|
801
|
+
let actor = null;
|
|
802
|
+
try {
|
|
803
|
+
actor = (await buildAuthoritativeSelfEnvelope({ requestMeta: extra._meta })).envelope.sessionId;
|
|
804
|
+
}
|
|
805
|
+
catch (err) {
|
|
806
|
+
if (!(err instanceof EntwurfEnvelopeWiringError)) {
|
|
807
|
+
return textErr(`entwurf_resume_call error: ${err instanceof Error ? err.message : String(err)}`);
|
|
808
|
+
}
|
|
809
|
+
}
|
|
787
810
|
try {
|
|
788
811
|
const result = await visibleResume(target, makeVisibleResumeDeps(visibleResumeLaunch));
|
|
789
|
-
const rendered = renderVisibleResume(result);
|
|
812
|
+
const rendered = renderVisibleResume(result, { actor });
|
|
790
813
|
return rendered.isError ? textErr(rendered.text) : textOk(rendered.text);
|
|
791
814
|
}
|
|
792
815
|
catch (err) {
|
|
793
816
|
return textErr(`entwurf_resume_call error: ${err instanceof Error ? err.message : String(err)}`);
|
|
794
817
|
}
|
|
795
818
|
});
|
|
819
|
+
server.tool("entwurf_retire_call", "Only when the human operator explicitly requested this particular target/work: make ONE retirement attempt for that request. " +
|
|
820
|
+
"Development work, a role or a record-backed actor is NOT a grant. Use host approval where provided/configured; " +
|
|
821
|
+
"Pi and pi-durable have no built-in per-call approval prompt by default. An explicit operator request and " +
|
|
822
|
+
"agent operating policy are required; attribution and MCP annotations are hints, not grants or proof of approval. " +
|
|
823
|
+
"Never use for autonomous cleanup or timers. Never automatically retry an unknown, cancelled or lost result. " +
|
|
824
|
+
"Target must be an existing Pi or Claude Code garden id; self retirement refuses. Pi shutdown-requested is " +
|
|
825
|
+
"only an ACK, not process exit, flush or model silence. Claude Code exited means that fd-pinned process only, " +
|
|
826
|
+
"not conversation end, children or flush. Do not manually /resume or /clear the target during the request. " +
|
|
827
|
+
"The JSON receipt preserves outcome, record and release separately; returning a receipt does not mean the " +
|
|
828
|
+
"target exited. Cancellation can suppress the wire response without undoing an action. For plural explicit " +
|
|
829
|
+
"requests, invoke once for each named target; never infer a roster or expand the request.", {
|
|
830
|
+
target: z
|
|
831
|
+
.string()
|
|
832
|
+
.min(1)
|
|
833
|
+
.regex(/^\d{8}T\d{6}-[0-9a-f]{6}$/)
|
|
834
|
+
.describe("Exact garden id of the Pi or Claude Code target the human operator explicitly requested to retire."),
|
|
835
|
+
}, { readOnlyHint: false, destructiveHint: true, idempotentHint: false }, async ({ target }, extra) => {
|
|
836
|
+
try {
|
|
837
|
+
const receipt = await retireCitizen(target, makeProductionRetireDeps({
|
|
838
|
+
resolveCaller: async () => (await resolveAuthoritativeSender({ requestMeta: extra._meta }))?.envelope ?? null,
|
|
839
|
+
signal: extra.signal,
|
|
840
|
+
}));
|
|
841
|
+
// isError reports uncertainty/faults, NOT a common Pi-ACK/CC-exit success boundary.
|
|
842
|
+
// Named refusals are completed decisions. Always preserve the full three-axis JSON.
|
|
843
|
+
const outcome = receipt.outcome;
|
|
844
|
+
const uncertain = outcome.kind === "unknown" ||
|
|
845
|
+
outcome.kind === "preflight-error" ||
|
|
846
|
+
(outcome.kind === "pi" && outcome.result.boundary === "unknown") ||
|
|
847
|
+
(outcome.kind === "claude-code" && outcome.result.outcome === "unknown");
|
|
848
|
+
const recordFault = receipt.record.status === "requested-append-error" || receipt.record.status === "outcome-append-error";
|
|
849
|
+
const releaseFault = receipt.release.status !== "released" && receipt.release.status !== "not-acquired";
|
|
850
|
+
const json = JSON.stringify(receipt);
|
|
851
|
+
return uncertain || recordFault || releaseFault ? textErr(json) : textOk(json);
|
|
852
|
+
}
|
|
853
|
+
catch (err) {
|
|
854
|
+
return textErr(`entwurf_retire_call error: ${err instanceof Error ? err.message : String(err)}. ` +
|
|
855
|
+
"The outcome may be unknown; this does not prove no action. Do not automatically retry.");
|
|
856
|
+
}
|
|
857
|
+
});
|
|
796
858
|
server.tool("entwurf_callback", "ZERO-ARGUMENT callback for a fresh sibling this process was launched as. Reads " +
|
|
797
859
|
"ENTWURF_CALLBACK_TARGET and ENTWURF_CALLBACK_NONCE from this process environment " +
|
|
798
860
|
"(injected by the launcher next to the identity scrub), validates garden-id and nonce " +
|