@junghanacs/entwurf 0.23.1 → 0.24.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/BASELINE.md +1 -0
- package/CHANGELOG.md +173 -0
- package/FAQ.md +47 -0
- package/README.md +5 -3
- package/VERIFY.md +2 -2
- package/demo/README.md +1 -1
- package/docs/acp-backend-rail.md +2 -2
- package/docs/setup-clean-host.md +8 -7
- package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +33 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/context.js +19 -5
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/callback-env.js +68 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-composition.js +54 -22
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-fresh-call.js +7 -3
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-placement.js +13 -4
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +19 -10
- package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-omp.js +35 -42
- package/mcp/entwurf-bridge/dist/scripts/probe-bridge-command.js +1 -0
- package/mcp/entwurf-bridge/src/index.ts +41 -1
- package/mcp/entwurf-bridge/test.sh +2 -2
- package/package.json +8 -7
- package/pi-extensions/entwurf-control.ts +56 -0
- package/pi-extensions/lib/acp/backend.ts +16 -6
- package/pi-extensions/lib/acp/context.ts +30 -10
- package/pi-extensions/lib/acp/session-store.ts +2 -2
- package/pi-extensions/lib/callback-env.ts +78 -0
- package/pi-extensions/lib/compaction-send-guard.ts +4 -1
- package/pi-extensions/lib/fresh-call-composition.ts +56 -22
- package/pi-extensions/lib/herdr-fresh-call.ts +12 -4
- package/pi-extensions/lib/herdr-placement.ts +13 -4
- package/pi-extensions/lib/mux-fresh-call.ts +23 -8
- package/pi-extensions/meta-bridge-omp.ts +35 -48
- package/run.sh +135 -50
- package/scripts/check-acp-backend-preflight.ts +117 -9
- package/scripts/check-acp-carrier-augment.ts +6 -4
- package/scripts/check-acp-event-mapper.ts +17 -6
- package/scripts/check-acp-prompt-builder.ts +17 -9
- package/scripts/check-acp-prompt-lifecycle.ts +23 -6
- package/scripts/check-acp-session-reuse.ts +64 -51
- package/scripts/check-acp-session-store.ts +31 -8
- package/scripts/check-acp-stop-reason.ts +20 -2
- package/scripts/check-acp-stream-hooks.ts +22 -5
- package/scripts/check-acp-usage-accounting.ts +33 -9
- package/scripts/check-entwurf-bridge-boot.ts +16 -1
- package/scripts/check-fresh-call-dispatch.ts +4 -5
- package/scripts/check-gate-qualification.ts +44 -5
- package/scripts/check-herdr-activation.ts +73 -0
- package/scripts/check-herdr-fresh-call.ts +22 -6
- package/scripts/check-herdr-sandbox.ts +14 -2
- package/scripts/check-install-container.sh +1 -1
- package/scripts/check-pi-launch.ts +5 -2
- package/scripts/lib/mutation-qualify.ts +1 -1
- package/scripts/mutants/acp-backend-preflight.json +20 -0
- package/scripts/mutants/bridge-boot-resume.json +26 -0
- package/scripts/mutants/herdr-activation.json +16 -0
- package/scripts/mutants/herdr-fresh-call.json +11 -0
- package/scripts/mutants/mux-fresh-call.json +21 -8
- package/scripts/mutants/omp-fresh.json +5 -9
- package/scripts/mutants/pack-install.json +3 -3
- package/scripts/probe-bridge-command.ts +1 -0
- package/scripts/smoke-acp-session-reuse-live.ts +23 -5
- package/scripts/smoke-agy-install-state.sh +2 -2
- package/scripts/smoke-copilot-mcp-state.sh +1 -1
- package/scripts/smoke-herdr-fresh-call-live.ts +56 -28
- package/scripts/smoke-herdr-raw-install-live.sh +31 -3
- package/scripts/smoke-omp-fresh-live.ts +4 -2
- package/scripts/smoke-omp-mcp-state.sh +1 -1
- package/scripts/smoke-pi-provider-state.sh +2 -2
package/BASELINE.md
CHANGED
|
@@ -35,6 +35,7 @@ not **UNSUPPORTED** — the deliberate never, which today is native Windows only
|
|
|
35
35
|
| Secondary Ubuntu installed package | **certified** for `0.12.8-repair.1` | 2026-07-25 same artifact, isolated agent dir → doctor exit 0 (HISTORY) |
|
|
36
36
|
| macOS Claude meta-bridge | NOT CERTIFIED — pending physical host | No physical-Mac doctor yet; a CI runner has no Claude login. Physical-host rental remains unscheduled; no cut has carried it. |
|
|
37
37
|
| 0.23.1 release acceptance (observer-amended) | accepted by GLG | `pnpm run check:full` P4 exit 0 (577 s); second aggregate `release-gate --cut` at `/tmp/entwurf-release-gate-0.23.1-retry.UIFctR` reached MUST 23/1/0 and BEHAVIOR 1/0, with only `smoke-codex-fresh-live` red because luna dropped `$`/`@` sigils in its relay (`release-gate.log:6156-6157`). The observer-amended standalone cell reran that exact leg with Codex `gpt-5.6-terra`, Pi `openai-codex/gpt-5.6-luna`: 66 assertions, exit 0; A app-server PID `3969303` `$227` ≠ fixture S `$231`; artifact `.probe-artifacts/codex-fresh-live-eZmf6p/`; pipe-pane stdout sha256 `56ee73e046e16fa95ef093fccf037670bf28b53a054bb5eec2a3b3b3ff08d93a`. Luna sigil handling is a post-cut issue, not a production-byte change. |
|
|
38
|
+
| 0.24.0 release acceptance (observer-amended) | accepted by GLG | `pnpm run build-bridge` then `pnpm run check:full` P4 exit 0 (594 s) on the prepared tree; aggregate `release-gate --cut` at `/tmp/entwurf-release-gate-0.24.0.anVBvk` (1h34m) reached MUST 22/2/0 and BEHAVIOR 1/0 with `check-gate-qualification` 739/739 killed at the candidate (`release-gate.log:5507`). The two MUST reds: `smoke-codex-fresh-live` (the Pi→Codex `<placeholder>` template relay audited for byte equality, ROADMAP 0.24.0 follow-up #3, same class as the 0.23.1 amendment) and `smoke-entwurf-chain-live` — delivery A→B (`entwurf_v2 control-socket → sent`) and B→C (B's sender envelope in C's stream) both landed, then hop 3 (ACP `claude-sonnet-5`) declined the relay on principle, naming the scratch `AGENTS.md` authority as self-vouching content (`release-gate.log:5841`); the same class hop 1 refused on the 0.22.0 gate (`f5d6e10`), not rerun (a second run is a coin flip on a model's judgement), ROADMAP follow-up #5. Standalone LIVE before the aggregate, each once with ambient identity stripped: mux-fresh-call 39/39, mux-lifecycle 81/81, omp-receive 11/11, herdr-fresh-call 31/32 after `149bef7` (the red is a claude child that called back and never attempted delivery, follow-up #1), omp-fresh 21/21 after `install-omp-bridge` (stale v1 birth unit on the host, doctor named it). P9 after the aggregate: 548 → 546 dirs, 2 roots / 756K reclaimed, operator store and control-socket dir unchanged. GLG: "릴리즈 하는데 문제 없는것 같은데? 기록하고 진행하자." |
|
|
38
39
|
| Native Codex Linux caller-DIRECTORY axis (current; #95 lane C) | **certified** | 2026-09-16, Codex 0.153.4: 65-assertion LIVE, exit 0, at `b3e07dc` with a clean worktree. Same host and topology as the lane B row below (app-server `737636` in A `$30/@41/%45`; initial Pi `20260916T154902-556eb5` `$2/@58`, Codex `20260916T155037-10ddaa` `$2/@59`, Codex-opened Pi `20260916T155102-9add8a` `$2/@61`, `seat-source=codex-title-anchor`). **hop 1 (cwd requested):** pane `#{pane_current_path}` = rollout `session_meta.cwd` = record cwd = requested scratch = `/tmp/entwurf-codex-fresh-live-db65N2`, against app-server cwd `/home/junghan/repos/gh/entwurf` read from `/proc/737636/cwd`. **hop 2 (cwd NOT requested):** the outbound Pi's pane, its own birth-written record and the Codex caller's record all read that same scratch, and its receipt names the caller-record rule. Artifact `.probe-artifacts/codex-fresh-live-w4yJBw/`; stdout `.probe-artifacts/lane-c-live-20260916T154900.log` sha256 `712050e7a8cf03ece98e7f34029ae98a92e9b2a7fd96aff87d0c1e6ba2ee9af2`. Cleanup reclaimed `@58`/`@59`/`@61` with no failure; records and transcripts preserved; app-server and `$30` untouched. 65 and 56 are different contracts — lane C added the cwd axis on top of lane B. Long floors (qualification body, frozen `check:full`) NOT run at this SHA; GLG decides host vs CI. |
|
|
39
40
|
| Native Codex Linux caller-seat topology (current; #95 lane B) | **certified** | 2026-09-16, Codex 0.153.4: 56-assertion LIVE, exit 0. App-server `737636` alone in session A `$30/@41/%45`; initial Pi `20260916T144205-e876ff` `$2/@46`, omitted-placement Codex `20260916T144230-d050d7` `$2/@47`, Codex-opened Pi `$2/@48`. A ≠ S is the decisive half: the app-server env names A, so S can only have come from the caller's pane title — receipt `seat-source=codex-title-anchor`. Artifact `.probe-artifacts/codex-fresh-live-nYcGC1/run-stdout.log` sha256 `ed60c2bd…`; the 43-assertion hop-1 run that measured the D1 retirement is kept at `codex-fresh-live-4aFCDD/run-stdout.log` sha256 `8392a603…`. Cleanup reclaimed `@46`/`@47`/`@48` with no failure; app-server and `$30` untouched. |
|
|
40
41
|
| Native Codex Linux home topology (PAST CONTRACT — #95 D1 retired the fixed home 2026-09-16) | **certified** for 0.21.0 as shipped | 2026-09-12, Codex 0.153.4: 57-assertion explicit-home LIVE. Initial Pi `$150/@397`; operator app-server `$158/@390/%390`; omitted-placement Codex `$158/@398`; Codex-opened Pi `$158/@399`; exact callbacks and addressed delivery both ways. Receipt and digest: `DELIVERY.md`. Deterministic closure of that axis: qualification 460/460 across 43 lanes with origin/snapshot purity green, then frozen full floor exit 0 in 506s — both pre-amendment. **Observer-amended acceptance, 2026-09-12:** standalone LIVE 48 assertions exit 0, source audit `initial-pi=3/3`/`codex=3/3` completed exact, initial Pi `$150/@431`, Codex `$158/@432`, Codex-opened Pi `$158/@433`, all three reclaimed by their own source receipts; artifact `.probe-artifacts/codex-fresh-live-fZccoK/`. The 48 and 57 counts are different contracts, not a regression. **Release acceptance at that same fingerprint:** `check:full` exit 0 502s, LIVE release gate `--cut` MUST 24/0/0 · BEHAVIOR 1/0/0 · `cut: OK`, `check-gate-qualification` 475/475 across 43 lanes with origin purity green, and the aggregate's Codex leg repeating the 48-assertion acceptance (artifact `codex-fresh-live-2oId4C`). Unrestricted attached-TUI placement remains unsupported and unclaimed. |
|
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,179 @@ All notable changes to this project will be documented here. Format follows [Kee
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## 0.24.0 - 2026-09-20
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **`entwurf_callback` — the eighth garden verb, and it takes no arguments.** The fresh-call
|
|
12
|
+
callback was the one place in the garden where the transcript was the address axis: the caller
|
|
13
|
+
garden id and the launch nonce reached a new sibling only as prose in its first prompt, and the
|
|
14
|
+
sibling had to retype both into `entwurf_v2`. Three releases of symptoms were one cause — 0.23.1
|
|
15
|
+
dropped `$`/`@` sigils, this lane's first release gate filled in `<placeholders>` early, its second lost the
|
|
16
|
+
trailing character of a garden id (`…-26428c` → `…-26428`, refused correctly by the decider) —
|
|
17
|
+
and the protocol had no error-detecting property: a mangled id is indistinguishable from a
|
|
18
|
+
different one, so the decider could only refuse, never repair. GLG's ruling: "luna만 못하는게
|
|
19
|
+
아니라 구조적 문제". Now both launch rails inject `ENTWURF_CALLBACK_TARGET` and
|
|
20
|
+
`ENTWURF_CALLBACK_NONCE` beside the identity scrub (tmux `-e` in `mux-fresh-call`,
|
|
21
|
+
`tab create --env` in `herdr-fresh-call`), computed by the rail and never caller-chosen;
|
|
22
|
+
`callback-env.ts` is the single leaf that formats and parses the pair, so a drifted regex cannot
|
|
23
|
+
inject what the verb rejects; and `entwurf_callback {}` reads the pair from its own process,
|
|
24
|
+
validates garden-id and nonce grammar (exactly what `mintNonce` emits), and dispatches through
|
|
25
|
+
the existing v2 runner so the target is still re-resolved by record and decider (Hard Rule 2).
|
|
26
|
+
Absent or malformed env is a named refusal with no fallback to a model-supplied target (Hard
|
|
27
|
+
Rule 5). Framing for pi, claude-code, copilot and omp now says only "call the callback with no
|
|
28
|
+
arguments"; a separate `FRESH_CALL_DELIVERY_TOOL` names the v2 dialect for the task result, and
|
|
29
|
+
Claude's `--allowedTools` and ToolSearch hint carry callback, delivery and peers together (the
|
|
30
|
+
review found the first cut loaded only two, which would have left a claude-code sibling unable
|
|
31
|
+
to send its result). OMP's bootstrap payload moves to v2 with the closed shape `{v, task}`; a
|
|
32
|
+
leftover `target`/`nonce` key is refused. Measured before the seam was written: the
|
|
33
|
+
entwurf-bridge MCP child inherits the pane env for claude-code, copilot and omp (canary in
|
|
34
|
+
`/proc/<child>/environ`, private tmux, fixture roots); pi is in-process. **Codex is the named
|
|
35
|
+
exception:** the bridge serving a Codex tool call is the operator app-server's child (ppid
|
|
36
|
+
measured) and its identity is request-scoped `_meta.threadId`, so a window env never reaches
|
|
37
|
+
it; `entwurf_callback` refuses there as `codex-callback-env-unsupported`, the Codex framing
|
|
38
|
+
keeps the argument form and says why, and a per-thread carrier is a follow-up after a
|
|
39
|
+
measurement that does not exist yet. The separate Pi→Codex template relay in
|
|
40
|
+
`codex-fresh-live-protocol.ts` (a semantic `<placeholder>` template pushed through a model and
|
|
41
|
+
audited for byte equality) is recorded, not fixed. Surface gates moved with the verb
|
|
42
|
+
(`check-entwurf-bridge-boot` exact eight-verb set + `QK:BRIDGEBOOT-CALLBACK-ZERO-ARG`,
|
|
43
|
+
`check-probe-bridge-command`, `check-bridge`, install/state smokes, README/VERIFY); manifests
|
|
44
|
+
735 → 739 mutants, 59 lanes. Commits `cb86d44`, `ff09522`, `ca04c1b`; designs by
|
|
45
|
+
`claude-opus-5` and `openai-codex/gpt-5.6-terra`, Codex verdict and implementation by
|
|
46
|
+
`xai/grok-4.6`, closing review by a fresh `claude-opus-5`.
|
|
47
|
+
- **`plugins/herdr` 0.4.0.** The plugin's install-time build now reaches a pi range check
|
|
48
|
+
(`install-user-scope`, above), its README installs pi at the published peer range, and its
|
|
49
|
+
runtime lock rides the 0.24.0 candidate window. A minor rather than a patch because a host that
|
|
50
|
+
activated 0.3.1 against an out-of-range pi was silently accepted and is now refused by name.
|
|
51
|
+
|
|
52
|
+
### Changed
|
|
53
|
+
|
|
54
|
+
- **pi floor moves to 0.86.0 (`>=0.86.0 <0.87`), and the ACP provider reads the 0.86 shape
|
|
55
|
+
honestly.** pi 0.86 hands a custom provider a branded `TranscriptContext`: `systemPrompt`
|
|
56
|
+
and `tools` are folded into a leading `role:"system"` message (`normalizeContext`,
|
|
57
|
+
pi-mono `model-runtime.ts:639-642`, `v0.86.0` = `ecac0a9c`). The ACP preflight read
|
|
58
|
+
`context.tools`, which is always `undefined` there, and fell back to the full builtin set —
|
|
59
|
+
so `assertExcludeToolsHonored` could never fire, while `tsc` (the old parameter type is
|
|
60
|
+
assignable) and `check-acp-backend-preflight` (a 0.85-shaped literal fed straight to
|
|
61
|
+
`streamAcpTurn`) both stayed green. Measured on real `pi-ai@0.86.0`: raw Context with `bash`
|
|
62
|
+
excluded → THROW; the same input through `normalizeContext` → PASS with no throw. The adapter
|
|
63
|
+
now replays the transcript (`getCurrentTools(context.messages)`, no fallback — an empty
|
|
64
|
+
replayed list is "the operator excluded everything" and is rejected as such),
|
|
65
|
+
`renderMessage` drops system messages by an explicit `case` so the never-forward-systemPrompt
|
|
66
|
+
invariant is a decision rather than a `default`, and every provider-path gate builds its
|
|
67
|
+
fixture through `normalizeContext()`. New lane `acp-backend-preflight`
|
|
68
|
+
(`[QK:ACP-PREFLIGHT-REPLAYS-TRANSCRIPT-TOOLS]`, exact-once mutant = the old fallback).
|
|
69
|
+
Session-reuse signatures now hash the folded leading system message, so a record persisted
|
|
70
|
+
under 0.85.1 is not a prefix of a 0.86 turn and falls back to `new` (the designed safe path).
|
|
71
|
+
- **Pin move, with every mirror.** devDeps 0.86.0; peers `>=0.86.0 <0.87`; the eight explicit
|
|
72
|
+
`check-pack-install` pins and its leak regex (`@0\.86\.0(_|$)`, boundary lookalike now the
|
|
73
|
+
prefix-extended `0.86.0-beta.1`); lockfile (typebox resolves to a single 1.3.27); baseline
|
|
74
|
+
docs; ROADMAP bump ledger. `check-dep-versions` now excludes dated ledger entries from its
|
|
75
|
+
baseline scan (`stripBumpLedger`, guarded both ways), because a ledger line is a receipt of
|
|
76
|
+
the range that landed THAT day and the first bump after an entry is written is what exposed
|
|
77
|
+
it. Vendor floors were re-measured rather than carried: a real 0.86.0 session file
|
|
78
|
+
(`herdr-placement.ts`), the installed `types.d.ts` (`compaction-send-guard.ts`), the
|
|
79
|
+
unregistered `--entwurf-control` refusal (`check-pi-launch.ts:18`); the registered-host
|
|
80
|
+
duplicate-flag receipt at `check-pi-launch.ts:29` stays dated 0.85.1 with the reason.
|
|
81
|
+
Constellation shrank for the first time (`pi-client`/`pi-protocol` left pi-coding-agent's
|
|
82
|
+
direct deps at 0.86.0); all eight pins are kept on purpose — a retired pin is how a caret
|
|
83
|
+
re-enters unwatched.
|
|
84
|
+
- **The two doors onto one pi registration tell the same truth (#119).** `entwurf setup`
|
|
85
|
+
refused an out-of-range pi by name, but `run.sh install-user-scope` — the verb herdr plugin
|
|
86
|
+
activation actually reaches (`build.mjs` → `herdr-plugin-activate.mjs`) — had no range check
|
|
87
|
+
and wrote the citizen in silence. It now enforces the same closed range through the same
|
|
88
|
+
helpers (`pi_supported_range`/`pi_version_in_range`, one parser), refuses with
|
|
89
|
+
`[install-user-scope] pi: FAIL … outside the supported range` and zero bytes written; absent
|
|
90
|
+
pi is deliberately unchanged (this verb invents no presence verdict). `check-herdr-activation`
|
|
91
|
+
gains `[QK:HAC-PI-FLOOR-BOTH-DOORS]` (24 → 25, the out-of-range/in-range PAIR, fixtures
|
|
92
|
+
derived from the package.json pin) and pins `PI_BIN` in its sandbox `world()` — without that
|
|
93
|
+
the gate's verdict depended on the operator's installed pi. `plugins/herdr/README.md` now
|
|
94
|
+
installs pi at the published peer range and joined `check-dep-versions` BASELINE_DOCS, so
|
|
95
|
+
the number cannot drift. Since 0.86.0 published (2026-09-19T23:15Z) the old unpinned line
|
|
96
|
+
installed a pi the shipped range rejects; the plugin had no known installs, so the repair is
|
|
97
|
+
a correction, not a migration.
|
|
98
|
+
- **Provider-path fixtures are built through `normalizeContext`, and a cast past the brand is
|
|
99
|
+
now a red gate.** The full floor on the bump commit went red in four ACP gates
|
|
100
|
+
(`check-acp-stop-reason`, `-prompt-lifecycle`, `-stream-hooks`, `-usage-accounting`) as
|
|
101
|
+
`'error' !== 'done'` — the new preflight correctly rejecting seven fixture helpers that cast a
|
|
102
|
+
tool-less literal `as Context`, which is exactly the operation that silences the
|
|
103
|
+
`TranscriptContext` brand at typecheck time. Every helper now declares the four pi builtins
|
|
104
|
+
through `normalizeContext`; no production source or assertion text changed.
|
|
105
|
+
`check-acp-backend-preflight` gains a static sweep (`[QK:ACP-FIXTURE-NO-CONTEXT-CAST]`) over
|
|
106
|
+
`scripts/check-acp-*.ts` and the lib tests so that class cannot come back silently; no mutant
|
|
107
|
+
by decision (the claim is the absence of a token and has no production subject — its
|
|
108
|
+
kill-proof is an injected offender named by `file:line`). Full floor on `5d3140c`:
|
|
109
|
+
`check:full` exit 0 in 598s. Manifests: 733 → 735 mutants, 58 → 59 lanes.
|
|
110
|
+
- **The first release gate of this lane (then numbered 0.23.2) was red on three MUST steps, and
|
|
111
|
+
two of them were this lane's own holes** (`/tmp/entwurf-release-gate-0.23.2.3s5A0o`, `MUST: PASS=21 FAIL=3 SKIP=0`).
|
|
112
|
+
`check-gate-qualification` 733/735: both `pack-install` mutants still quoted the old run.sh
|
|
113
|
+
`@0\.85\.1(_|$)` line after the pin mirror moved it (MUTANT-STALE), and nothing in
|
|
114
|
+
`check:full` looks at whether a `find` still matches — `check-gate-manifests` proved the
|
|
115
|
+
subject was tracked but never opened it. The finds now mirror run.sh byte-exact and the head
|
|
116
|
+
gains `[QK:MUTANT-FIND-MATCHES-SUBJECT]` (every `find` matches its subject exactly once, using
|
|
117
|
+
the body's own `countOccurrences`), which would have caught this at commit time instead of
|
|
118
|
+
fifty minutes into the gate. `smoke-acp-session-reuse-live`: `turn1`/`turn2` were annotated
|
|
119
|
+
`: Context` with no tools and the preflight rejected them — the third fixture of this class,
|
|
120
|
+
and the sweep written to stop it had globbed only `check-acp-*` and only the cast spelling.
|
|
121
|
+
Both turns build through `normalizeContext`; the sweep now covers `scripts/smoke-*.ts` and the
|
|
122
|
+
annotation form (indexed-access types such as `Context["messages"]` excluded), and retyped
|
|
123
|
+
`startTurn` in `check-acp-prompt-lifecycle` on the way (`5aeb8d4`). The third red,
|
|
124
|
+
`smoke-codex-fresh-live`, was a host prerequisite: the gate shell did not export
|
|
125
|
+
`ENTWURF_CODEX_APP_SERVER_PID` / `ENTWURF_CODEX_FRESH_MODEL` / `ENTWURF_CODEX_FRESH_PI_MODEL`
|
|
126
|
+
(the app-server was alive, PID 3969303); the rerun carries them. An independent audit
|
|
127
|
+
(`openai-codex/gpt-5.6-terra`, read-only) re-measured every mutant `find` against its subject
|
|
128
|
+
(0 stale), every direct provider-path fixture (0 tool-less), and named four stale declarations
|
|
129
|
+
fixed before the rerun: `docs/acp-backend-rail.md` support table, `pnpm-workspace.yaml`
|
|
130
|
+
release-age exclusions for pi-client/pi-protocol, `check-pack-install` diagnostic wording, and
|
|
131
|
+
the filename-floor sentence in `docs/mux-launch-rail.md`.
|
|
132
|
+
The qualification body on `ca04c1b` then reported 737/739 with two WRONG-REASON verdicts —
|
|
133
|
+
mutants dying at a neighbouring claim: `check-herdr-fresh-call` asserted the umbrella
|
|
134
|
+
whole-argv cell before the callback-pair cell, and the `pack-install` COVERS-CLOSURE `replace`
|
|
135
|
+
still carried the 0.85.1 pin. Both fixed in `3a9ae38`; a lane-scoped replay through the real
|
|
136
|
+
`qualifyMutants` runner (herdr-fresh-call + pack-install, 40/40 killed, 504s) is the fix
|
|
137
|
+
receipt, and the exact-SHA full body is read from `release-gate --cut`. Bridge source changes
|
|
138
|
+
need `pnpm run build-bridge` before any gate: `check-bridge-delivery` compares the gitignored
|
|
139
|
+
`dist` against source and reports staleness as red, which it did once in this lane.
|
|
140
|
+
The standalone LIVE pass that followed (each smoke once, ambient identity stripped) found the
|
|
141
|
+
one surface the verb move had not reached: `smoke-herdr-fresh-call-live` still joined both of
|
|
142
|
+
its cells on `entwurf_v2` as the callback — the pi cell on an argument predicate the
|
|
143
|
+
zero-argument verb can never satisfy, the claude cell on `indexOf("entwurf_v2")`, which landed
|
|
144
|
+
on the result delivery and reported the real callback as forbidden work before the callback.
|
|
145
|
+
The same red run is the receipt that production was right: the claude child's MCP activity
|
|
146
|
+
log reads `entwurf_peers → entwurf_callback (completed) → entwurf_v2 (completed)`, the pi
|
|
147
|
+
child's own tool result says the callback was delivered, and every delivery/fence cell passed
|
|
148
|
+
(2 FAILED / 30 ok, preserved fixture `/tmp/entwurf-opus2/herdr-live-fixture-red`). The oracle
|
|
149
|
+
now joins on the bare `entwurf_callback` the observing surfaces spell and on the ABSENCE of an
|
|
150
|
+
address in the pi call (`149bef7`); no deterministic gate covers this on-demand axis, which is
|
|
151
|
+
why it surfaced only here.
|
|
152
|
+
The same pass found the host behind the tree: `smoke-omp-fresh-live` timed out waiting for the
|
|
153
|
+
callback because the INSTALLED omp birth unit still spoke bootstrap v1 (no
|
|
154
|
+
`entwurf_bridge_entwurf_callback`, `{v:1}`), so the v2 payload the checkout launcher sent was
|
|
155
|
+
refused as `version-unsupported` — the exact condition `doctor-omp-bridge` exists to name, and
|
|
156
|
+
it did (runtime FAIL / ownership PASS). `install-omp-bridge` with no omp session open, doctor
|
|
157
|
+
green on both axes, re-run 21/21. The herdr re-run on `149bef7` is 31/32: the two repaired
|
|
158
|
+
cells pass; the remaining red is a claude-code child that called back and then never attempted
|
|
159
|
+
the delivery verb, recorded in ROADMAP as a follow-up rather than patched on n=2.
|
|
160
|
+
- **The plugin runtime lock names `herdr-checkout` again, for the length of the 0.24.0
|
|
161
|
+
candidate window** — the same two steps 0.23.0 (`dd84ac0`) and 0.23.1 (`3c3a67d`/`194e800`)
|
|
162
|
+
took: an `npm` lock must name a version that is already published and coherent with this
|
|
163
|
+
checkout's `package.json` (`certifyLockCoherence`, `check-herdr-runtime-bootstrap` 8a), and the
|
|
164
|
+
version bump lands before the publish. Lock and `check-herdr-plugin-build` moved together
|
|
165
|
+
(`47f6d36`, the measured inverse of `194e800`); the npm pin returns with its published sha512
|
|
166
|
+
once 0.24.0 is on the registry.
|
|
167
|
+
|
|
168
|
+
- **`plugins/herdr` 0.3.1 leads with the workbench a visitor is looking at.** Its manifest and
|
|
169
|
+
README now say that Entwurf brings sibling AI sessions into Herdr, then name its actual narrow
|
|
170
|
+
integration scope: the install-time build activates only Herdr-integrated pi and Claude Code,
|
|
171
|
+
while the pane is read-only. This is an affordance correction, not a new runtime claim.
|
|
172
|
+
- **The plugin runtime lock is pinned to npm `@junghanacs/entwurf@0.23.1`** and its published
|
|
173
|
+
sha512, closing the candidate window 0.23.1 shipped in. The pair moves together, as that entry
|
|
174
|
+
says it must: the lock and `check-herdr-plugin-build`'s committed-lock expectations, with the
|
|
175
|
+
`IDENTITY` fixture's sha512 travelling with its version rather than being left on the previous
|
|
176
|
+
release's bytes. The integrity was read from the registry in this session
|
|
177
|
+
(`npm view @junghanacs/entwurf@0.23.1 dist.integrity`) rather than copied from another
|
|
178
|
+
session's report, which is the whole point of a pin.
|
|
179
|
+
|
|
7
180
|
## 0.23.1 - 2026-09-19
|
|
8
181
|
|
|
9
182
|
### Changed
|
package/FAQ.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Entwurf FAQ
|
|
2
|
+
|
|
3
|
+
Short answers for people meeting Entwurf from a different agent-harness tradition.
|
|
4
|
+
|
|
5
|
+
## Is Entwurf an agent factory or a subagent framework?
|
|
6
|
+
|
|
7
|
+
No. Entwurf is closer to a workshop than a factory: it connects independently owned, visible sibling sessions without turning them into workers of a new central runtime. A harness may run its own subagents or teams, but that remains the harness's responsibility.
|
|
8
|
+
|
|
9
|
+
## What does Entwurf do?
|
|
10
|
+
|
|
11
|
+
It gives an existing session a stable **garden id** and routes a message, reply, or visible sibling launch through the rail that session actually supports. It does not reconstruct prompts, hydrate transcripts, manage credentials, or emulate another harness.
|
|
12
|
+
|
|
13
|
+
## What is a garden id?
|
|
14
|
+
|
|
15
|
+
A garden id is the stable address of one visible, record-backed session. Think of it as an invitation to a shared workshop table, not a worker name, a pane id, a transcript id, or a worktree lease.
|
|
16
|
+
|
|
17
|
+
## How are session state and project state separated?
|
|
18
|
+
|
|
19
|
+
Entwurf does not merge or isolate either one. Each harness keeps its own authentication, tools, transcript, and workspace policy. Git branches, worktrees, file locks, task allocation, and any coordination around a shared checkout belong to the operator and the harnesses involved; a garden id is never a file-isolation claim.
|
|
20
|
+
|
|
21
|
+
## What happens to a harness's internal subagents?
|
|
22
|
+
|
|
23
|
+
The visible top-level session is the garden principal. Internal children remain inside that harness and do not receive a second garden id, separate delivery authority, or an Entwurf-managed access-control layer.
|
|
24
|
+
|
|
25
|
+
## Does sending a message start a process?
|
|
26
|
+
|
|
27
|
+
No. [`entwurf_v2`](./README.md#entwurf_v2--canonical-dispatch-verb) only addresses an existing garden citizen and selects its supported delivery rail. `entwurf_fresh_call` opens a new visible sibling; `entwurf_resume_call` reopens a dormant pi citizen under the same id.
|
|
28
|
+
|
|
29
|
+
## Where do visible siblings open?
|
|
30
|
+
|
|
31
|
+
Inside Herdr, Entwurf opens a new unfocused tab in the caller's workspace and currently admits pi and Claude Code. Outside Herdr, it opens one of the fixed pi, Claude Code, Copilot, OMP, or Codex runtimes in a visible tmux window. The runtime keeps its own model, tools, authentication, and transcript.
|
|
32
|
+
|
|
33
|
+
## Does Entwurf replace Herdr or tmux?
|
|
34
|
+
|
|
35
|
+
No. Herdr and tmux provide visible seats. Entwurf provides identity, delivery, and receipts across independent harness sessions. A pane or tmux session is a location for a sibling, never its address or liveness proof.
|
|
36
|
+
|
|
37
|
+
## How do I install it?
|
|
38
|
+
|
|
39
|
+
Use the [direct installation route](./README.md#install) for the broader harness surface. In an existing Herdr workbench, use the [Herdr integration guide](./plugins/herdr/README.md); it activates only Herdr-integrated pi and Claude Code and leaves the rest to the direct route.
|
|
40
|
+
|
|
41
|
+
## How do I start pi as a citizen?
|
|
42
|
+
|
|
43
|
+
After the direct install, run `entwurf pi`; it starts pi with Entwurf's control surface. The Herdr integration does not put `entwurf` on `PATH`, so its documented command remains `pi --entwurf-control`.
|
|
44
|
+
|
|
45
|
+
## Where is the precise contract?
|
|
46
|
+
|
|
47
|
+
[AGENTS.md](./AGENTS.md) owns the project invariants. [DELIVERY.md](./DELIVERY.md), [VERIFY.md](./VERIFY.md), and [docs/adding-a-harness.md](./docs/adding-a-harness.md) own capability, evidence, and admission details.
|
package/README.md
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
|
|
9
9
|
npm package: <https://www.npmjs.com/package/@junghanacs/entwurf>
|
|
10
10
|
|
|
11
|
+
**Short answers for common questions:** [FAQ.md](./FAQ.md).
|
|
12
|
+
|
|
11
13
|
Legacy package: [`@junghanacs/pi-shell-acp`](https://www.npmjs.com/package/@junghanacs/pi-shell-acp). `entwurf` is its 0.12+ successor line: the same work renamed around the garden-citizen dispatch substrate rather than the pi adapter.
|
|
12
14
|
|
|
13
15
|
> **Repository shape.** This repo is **entwurf-core (v2 dispatch) + native-harness bridges + a pi adapter + an ACP plugin**. Pi is one adapter, not the project subject. Claude Code, GitHub Copilot CLI, and OMP (`omp`) are shipped self-fetch citizens; Antigravity (`agy`) is shipped native-push. Codex CLI is a **native-push citizen, supported in 0.21.0 on Linux** over the operator-owned app-server: vendor-trusted user-scope birth, strict request-scoped identity, loaded-thread probe, one-shot queue delivery, and visible fresh beside the CALLER's own pane. Unrestricted attached-TUI seat inference is not supported or claimed; #95 D1 retired the fixed `codex` tmux home on 2026-09-16, so the operator seats the app-server wherever they like and a Codex caller opens its sibling beside its own TUI pane. Codex remains native so it keeps its vendor tools, delegation, and work context; this is not another way to buy or expose GPT through ACP.
|
|
@@ -252,7 +254,7 @@ cd ~/repos/gh/entwurf
|
|
|
252
254
|
|
|
253
255
|
The full source setup requires Node 24, pnpm, and Python 3 on PATH. Harnesses are
|
|
254
256
|
optional-by-presence: `setup` runs the frozen dependency install, then composes what the operator
|
|
255
|
-
already installed — a compatible `pi` (`>=0.
|
|
257
|
+
already installed — a compatible `pi` (`>=0.86.0 <0.87`), Claude Code, agy, and the Copilot CLI
|
|
256
258
|
each get their wiring completed when detected, an absent harness is an explicit zero-state SKIP,
|
|
257
259
|
and a detected harness that cannot be completed (including a below-floor `pi`) is a named FAIL
|
|
258
260
|
that makes setup exit nonzero. `setup` never installs a harness binary or touches a credential
|
|
@@ -286,7 +288,7 @@ This rail is CERTIFIED on Linux; on macOS it is NOT CERTIFIED — pending
|
|
|
286
288
|
physical host (control sockets, mux, ACP plugin hosting).
|
|
287
289
|
|
|
288
290
|
To use the `entwurf` provider inside pi, install a compatible pi binary
|
|
289
|
-
separately (`@earendil-works/pi-coding-agent >=0.
|
|
291
|
+
separately (`@earendil-works/pi-coding-agent >=0.86.0 <0.87`). Then point pi at
|
|
290
292
|
the npm-installed package or development clone:
|
|
291
293
|
|
|
292
294
|
```bash
|
|
@@ -636,7 +638,7 @@ A two-pane recording covers the pre-0.12 v1 surface end-to-end — sibling resum
|
|
|
636
638
|
|
|
637
639
|
Live peer messaging carries a sender envelope `{ sessionId, agentId, cwd, timestamp, origin?, replyable? }`; `entwurf_self` returns that authoritative envelope for the current pi session or trusted meta-session. Plain external MCP hosts are non-replyable. A garden-native meta-session carries a trusted `meta-session` envelope, but **`replyable` is a fact its own rail decides, not a consequence of being trusted** — a self-fetch citizen needs a live armed receiver, a native-push citizen needs an alive adapter probe, and a pi session needs its control socket. `entwurf_self` also reports which rail a meta-session reply would ride, because a native-push citizen has no mailbox to name. `wants_reply` is an etiquette marker rendered as a `(wants reply)` badge — not a transport contract, no wait, no polling. **v2 never gates on it:** a `wants_reply` from an external/non-replyable caller is passed through and surfaced honestly beside that sender's `replyable: false`, not rejected — the decider routes on target + intent, never on sender replyability. (The retired v1 `entwurf_send` did reject it; that behaviour went with the verb.)
|
|
638
640
|
|
|
639
|
-
In ACP-backed and external native-harness sessions, `entwurf-bridge` exposes
|
|
641
|
+
In ACP-backed and external native-harness sessions, `entwurf-bridge` exposes eight tools: `entwurf_v2`, `entwurf_peers`, `entwurf_fresh_call`, `entwurf_resume_call`, `entwurf_callback`, `entwurf_self`, `entwurf_inbox_read`, and the explicit/manual `entwurf_register_native` fallback. Native pi exposes the shared capability directly through the extension surface (`entwurf_v2`, `entwurf_peers`, `entwurf_fresh_call`, `entwurf_resume_call`, `entwurf_callback` tools; the socket-scan `/entwurf-sessions` command is gone — #50 C4). **For garden-id delivery/reply use `entwurf_v2`** — the canonical surface that classifies the target and routes to live-pi / self-fetch meta-mailbox (Claude Code, Copilot, OMP) / native-push (Antigravity, Codex), and refuses a dormant target instead of waking it. **To open a sibling that does not exist yet use `entwurf_fresh_call`** — WHERE it opens is a fact about the calling process, not a parameter: inside herdr (`HERDR_ENV=1`) it opens a NEW TAB in the caller's own workspace, without taking focus, and admits only `pi` and `claude-code`, refusing `placement` by name; everywhere else it launches one of five fixed backends (`pi`, `claude-code`, `copilot`, `omp`, `codex`) as a visible window on the operator's own tmux server. There is no fallback between the two — an incomplete herdr context is refused by its herdr reason rather than quietly opening a tmux window nobody inside herdr can see. Everything that follows in this paragraph describes the tmux rail; the herdr rail's coordinates, refusals and reclaim are owned by [docs/herdr-launch-rail.md](./docs/herdr-launch-rail.md). On tmux it requires an explicit model, accepts one optional literal absolute `cwd` (omit it or pass `""` to use the caller's cwd) and one optional `placement.tmuxSession` naming an EXISTING session on that server to open the sibling in instead of the caller's own (the operator's project seat — nothing is ever created: an absent session is `tmux-session-missing` and a name outside the addressed grammar `[A-Za-z0-9][A-Za-z0-9_-]*` is `tmux-session-name-invalid`, with no window and no session left behind. The two inputs never infer each other, and the positive form of that matters more than the prohibition: **with `cwd` omitted, a seated sibling starts in THIS agent's own working directory**, measured — it does not inherit the target session's path or its active pane, so naming the `org` seat does not put the sibling in the `org` project. Pass `cwd` when you want a different directory. entwurf never resolves that path: it hands the literal string to tmux, so with a SYMLINKED directory the receipt echoes what you asked for while the record, the transcript path, the callback envelope and `pane_current_path` all show the path the OS resolved it to — two spellings of one place, and the resolved one is what `entwurf_resume_call` later reopens), passes the model and selected directory through the runtime's visible launch path, and hands it a first task; a `copilot` launch goes through entwurf's own managed invocation and is refused before any window opens if this host lacks the Copilot birth, MCP, receiver or visible-footer units, and an `omp` launch carries its task in the `--entwurf-bootstrap` payload instead of an argv prompt because that vendor connects its MCP tools after the session has already started. Omitted placement follows the CALLER rather than the backend being opened: a Codex CALLER opens its sibling beside its own TUI pane, located by the `thread-id` that pane's terminal title carries (0 or 2+ matching panes reject with no fallback); every other caller opens in its own seat. (0.21.0 shipped a backend-selected default instead — an omitted-placement `codex` TARGET resolved a fixed operator-owned session named `codex` — and #95 D1 retired it on 2026-09-16.) Any of the five can still be sent to an operator-owned existing seat with an explicit `placement.tmuxSession`, and named `claude`/`pi` seats remain available that way, but they are not omitted-placement defaults. Read tmux here as the VISIBLE LAUNCH SEAT, never as an address: the garden id is the address, and it arrives in the callback envelope. Sibling symmetry means each backend keeps its own transcript, auth, native tools and visibility — not that the transports are made identical. The sibling's first action is a nonce callback whose sender envelope carries its garden id. The launch receipt records the requested model/cwd/seat plus tmux coordinates — including the RESOLVED target session id, which is where the window actually is — and never claims that the runtime accepted them or completed delivery. (The v1 verbs `entwurf` / `entwurf_resume` / `entwurf_send` are gone.) Garden-native operator commands require `--entwurf-control`. There is no spawn target allowlist — the target registry is gone (#50 C3): `entwurf_v2` and `entwurf_resume_call` address an existing record-backed citizen, while `entwurf_fresh_call` takes its explicit backend/model/task and optional cwd directly rather than resolving a model tuple from a registry file. **To reopen a DORMANT pi citizen use `entwurf_resume_call {target}`** — the record supplies transcript, model, provider and cwd, so it takes no prompt, no task and no model override; it runs no turn, returns a LAUNCH receipt and a separate OBSERVATION receipt (only the second says the citizen is back), refuses a non-pi target as `target-not-pi`, and on an unobserved socket leaves the visible window open and releases its lock rather than retrying.
|
|
640
642
|
|
|
641
643
|
### `entwurf_v2` — canonical dispatch verb
|
|
642
644
|
|
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_self`, `entwurf_inbox_read`, and `entwurf_register_native`. The ACP
|
|
8
|
+
> `entwurf_fresh_call`, `entwurf_resume_call`, `entwurf_callback`, `entwurf_self`, `entwurf_inbox_read`, and `entwurf_register_native`. 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
|
|
@@ -269,7 +269,7 @@ The goal is not merely "invoke Claude Code." We want:
|
|
|
269
269
|
**One install command to remember: `./run.sh setup <project>`.** It is idempotent — re-run the exact same command whenever anything looks wrong. There is no second install surface to juggle: from a clone `setup` runs the whole floor in order.
|
|
270
270
|
|
|
271
271
|
1. `pnpm install` — installs the pinned development dependencies and builds the bridge (source-checkout bootstrap only; an installed package never runs npm/pnpm inside `node_modules`)
|
|
272
|
-
2. pi wiring → `<project>/.pi/settings.json` + user-scope registration — only when a `pi` inside the supported range (`>=0.
|
|
272
|
+
2. pi wiring → `<project>/.pi/settings.json` + user-scope registration — only when a `pi` inside the supported range (`>=0.86.0 <0.87`) is on PATH; absent pi is an explicit zero-state SKIP, a below-floor pi is a detected FAIL. The user-scope entry is owner-recorded (#86 C2): another root's live-or-missing ownership makes this step a zero-write refusal (setup: pi FAIL) that names `takeover-user-scope`; the install-states bind the exact managed settings path (a drifted/symlinked/corrupt target is a zero-write refusal) and the inverse removes only the recorded owner's exact entry; `doctor-pi-package` reports the verdict
|
|
273
273
|
3. Claude meta-bridge global plugin — only when `claude` is on PATH; otherwise skipped cleanly
|
|
274
274
|
4. source stable-bin exposure — including certified `entwurf` → this checkout's `run.sh`, the managed runtime Copilot fresh resolves; helper units are attempted independently and a foreign helper is a named FAIL
|
|
275
275
|
5. agy bridge + exact permission + statusline + `PreInvocation` hook — only when `agy` is on PATH; each adapter is idempotent and independently doctorable
|
package/demo/README.md
CHANGED
|
@@ -139,7 +139,7 @@ SCENE_DELAY=30 FINAL_PAUSE=10 bash demo.sh
|
|
|
139
139
|
|
|
140
140
|
## Prerequisites
|
|
141
141
|
|
|
142
|
-
- `pi` on PATH (current floor 0.
|
|
142
|
+
- `pi` on PATH (current floor 0.86.0)
|
|
143
143
|
- `entwurf` provider configured + auth ready for the selected sender/peer models
|
|
144
144
|
- `asciinema` installed
|
|
145
145
|
- `agg` installed (optional — only for GIF conversion)
|
package/docs/acp-backend-rail.md
CHANGED
|
@@ -92,8 +92,8 @@ undifferentiated "supported" column is what let a Claude PASS read as if it also
|
|
|
92
92
|
|
|
93
93
|
| Surface | Declaration | Class | What a green actually says |
|
|
94
94
|
|---|---|---|---|
|
|
95
|
-
| Entwurf package | `0.
|
|
96
|
-
| pi runtime | devDep exact `0.
|
|
95
|
+
| Entwurf package | `0.24.0` | shipped baseline | the package contract these rows belong to |
|
|
96
|
+
| pi runtime | devDep exact `0.86.0`, peer `>=0.86.0 <0.87` | **exact** oracle + **closed range** | built and certified against 0.86.0; hosts inside the range are accepted, and the ceiling moves only on measurement |
|
|
97
97
|
| ACP wire SDK | `@agentclientprotocol/sdk 1.4.0` | **exact** | the shared wire oracle both adapters speak |
|
|
98
98
|
| Claude ACP adapter | `@agentclientprotocol/claude-agent-acp 0.79.0` | **exact**, bundled | the adapter we ship and certify; resolved before any PATH fallback |
|
|
99
99
|
| Claude Agent SDK | `0.3.274` (transitive) | **exact** oracle | the runtime risk surface behind the adapter |
|
package/docs/setup-clean-host.md
CHANGED
|
@@ -22,7 +22,7 @@ evidence split, and its three states must not collapse into "macOS is supported"
|
|
|
22
22
|
| npm/pnpm | npm is bundled with Node; pnpm is required for source setup | package or source installation |
|
|
23
23
|
| Python 3 | required by `setup`/`install` (project-path normalization + settings writers); `--help`/`check-bridge` stay Python-free | pi/Claude/agy/Copilot/Codex wiring writers |
|
|
24
24
|
| entwurf | global/project-local `@junghanacs/entwurf`, or a source checkout | operator command and garden capability |
|
|
25
|
-
| pi | optional-by-presence, `>=0.
|
|
25
|
+
| pi | optional-by-presence, `>=0.86.0 <0.87` — absent is an explicit setup SKIP, below-floor is a named FAIL | ACP provider, control sockets |
|
|
26
26
|
| Claude Code | optional, **`>=2.1.217`** — the exec-form hook floor | Claude ACP auth/runtime and mailbox-backed native citizen |
|
|
27
27
|
| GitHub Copilot CLI | optional-by-presence, operator-installed and authenticated — absent is an explicit setup SKIP; detected composes all four units (birth/MCP/receiver/footer) | self-fetch citizen and visible fresh |
|
|
28
28
|
| 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) |
|
|
@@ -156,7 +156,7 @@ on Darwin; that is wiring, not a rail receipt. If `pi` is on PATH,
|
|
|
156
156
|
Install the exact release floor, then wire the project:
|
|
157
157
|
|
|
158
158
|
```bash
|
|
159
|
-
npm install -g @earendil-works/pi-coding-agent@0.
|
|
159
|
+
npm install -g @earendil-works/pi-coding-agent@0.86.0
|
|
160
160
|
pi --version
|
|
161
161
|
|
|
162
162
|
cd ~/entwurf-smoke
|
|
@@ -164,7 +164,7 @@ entwurf install .
|
|
|
164
164
|
pi -e "$(npm root -g)/@junghanacs/entwurf" --list-models entwurf
|
|
165
165
|
```
|
|
166
166
|
|
|
167
|
-
The supported range is `>=0.
|
|
167
|
+
The supported range is `>=0.86.0 <0.87`. It is a hard minimum: installing this
|
|
168
168
|
release onto a 0.83.x pi host upgrades the runtime rather than keeping the older
|
|
169
169
|
minor. A host using only the external MCP bridge can skip pi until it needs a
|
|
170
170
|
control socket; no delivery rail launches a pi process.
|
|
@@ -264,9 +264,10 @@ process. Open omp yourself and it is a two-way citizen either way — it sends u
|
|
|
264
264
|
garden id and a reply lands on it.
|
|
265
265
|
|
|
266
266
|
The first turn of a fresh omp sibling is a TWO-STAGE BOOTSTRAP, not a positional prompt: the
|
|
267
|
-
launcher carries `{v,
|
|
268
|
-
|
|
269
|
-
|
|
267
|
+
launcher carries `{v,task}` on the fixed registered flag `--entwurf-bootstrap` while the
|
|
268
|
+
caller's address rides the sibling's own process env, and the installed birth extension waits
|
|
269
|
+
until the zero-argument callback tool is actually callable, sends a callback-only prompt, and
|
|
270
|
+
delivers the operator's task only after that exact call succeeds.
|
|
270
271
|
That is a measured correction — the interactive host defers MCP discovery, so a positional
|
|
271
272
|
first turn began before the tool it named existed.
|
|
272
273
|
|
|
@@ -306,7 +307,7 @@ as a note rather than a fault.
|
|
|
306
307
|
|
|
307
308
|
**entwurf sets no OMP version floor in code, and will not grow one on schedule.** Detection is
|
|
308
309
|
presence-only (`command -v "${OMP_BIN:-omp}"`); there is no `entwurf.ompFloor`, no coherence
|
|
309
|
-
gate, and no exact pin — unlike Node (`engines.node`), pi (`>=0.
|
|
310
|
+
gate, and no exact pin — unlike Node (`engines.node`), pi (`>=0.86.0 <0.87`) and Claude Code
|
|
310
311
|
(`entwurf.claudeCodeFloor`), each of which has an enforcement point. That asymmetry is a
|
|
311
312
|
decision, not an omission. A floor is the answer to a vendor that fails SILENTLY — Claude Code
|
|
312
313
|
earned one because an older binary validates the exec manifest, drops `args` at runtime, and
|
|
@@ -34,8 +34,13 @@
|
|
|
34
34
|
* - entwurf_resume_call — reopen ONE DORMANT pi citizen under its OWN garden id in a visible
|
|
35
35
|
* window; target-only, runs no turn, LAUNCH and OBSERVATION receipts stay
|
|
36
36
|
* apart.
|
|
37
|
+
* - entwurf_callback — ZERO-ARGUMENT fresh-sibling callback. Reads ENTWURF_CALLBACK_TARGET +
|
|
38
|
+
* ENTWURF_CALLBACK_NONCE from this process env (launcher-injected), validates
|
|
39
|
+
* grammar, and dispatches through the existing v2 runner. Refuses by name
|
|
40
|
+
* when env is absent/malformed or this process is a Codex-provenance bridge.
|
|
41
|
+
* No model-supplied target fallback.
|
|
37
42
|
*
|
|
38
|
-
* That list is the WHOLE public surface —
|
|
43
|
+
* That list is the WHOLE public surface — eight verbs — and `check-entwurf-bridge-boot`
|
|
39
44
|
* (G1f) holds it as an exact set on the runtime tools/list, so a verb added or dropped
|
|
40
45
|
* here without a decision is red rather than merely undocumented.
|
|
41
46
|
*
|
|
@@ -64,6 +69,7 @@ import * as process from "node:process";
|
|
|
64
69
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
65
70
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
66
71
|
import { z } from "zod";
|
|
72
|
+
import { readCallbackEnv } from "../../../pi-extensions/lib/callback-env.js";
|
|
67
73
|
import { controlSocketPathIn, defaultControlSocketDir } from "../../../pi-extensions/lib/control-socket-path.js";
|
|
68
74
|
import { resolveMailboxReceiverFacts } from "../../../pi-extensions/lib/entwurf-deliverability.js";
|
|
69
75
|
import { listEntwurfFacts } from "../../../pi-extensions/lib/entwurf-fact-provider.js";
|
|
@@ -740,6 +746,32 @@ server.tool("entwurf_resume_call", "Reopen ONE DORMANT pi citizen under its OWN
|
|
|
740
746
|
return textErr(`entwurf_resume_call error: ${err instanceof Error ? err.message : String(err)}`);
|
|
741
747
|
}
|
|
742
748
|
});
|
|
749
|
+
server.tool("entwurf_callback", "ZERO-ARGUMENT callback for a fresh sibling this process was launched as. Reads " +
|
|
750
|
+
"ENTWURF_CALLBACK_TARGET and ENTWURF_CALLBACK_NONCE from this process environment " +
|
|
751
|
+
"(injected by the launcher next to the identity scrub), validates garden-id and nonce " +
|
|
752
|
+
"grammar, and dispatches through the existing v2 runner with intent fire-and-forget, " +
|
|
753
|
+
"message=nonce, wants_reply=false. The target is re-resolved by record/decider — env " +
|
|
754
|
+
"names the id, it does not skip dispatch. REFUSES BY NAME when the pair is absent, " +
|
|
755
|
+
"malformed, or this process is a Codex-provenance bridge (window env never reaches that " +
|
|
756
|
+
"MCP child). No arguments, no fallback to a model-supplied target.", {}, async (_args, extra) => {
|
|
757
|
+
const read = readCallbackEnv(process.env);
|
|
758
|
+
if (!read.ok) {
|
|
759
|
+
return textErr(`entwurf_callback: ${read.reason}`);
|
|
760
|
+
}
|
|
761
|
+
try {
|
|
762
|
+
const sender = await buildSendSenderEnvelope({ requestMeta: extra._meta });
|
|
763
|
+
const rendered = await runAndRenderEntwurfV2FromSurface({
|
|
764
|
+
target: read.target,
|
|
765
|
+
intent: "fire-and-forget",
|
|
766
|
+
message: read.nonce,
|
|
767
|
+
wants_reply: false,
|
|
768
|
+
}, { senderProvider: () => sender });
|
|
769
|
+
return rendered.isError ? textErr(rendered.text) : textOk(rendered.text);
|
|
770
|
+
}
|
|
771
|
+
catch (err) {
|
|
772
|
+
return textErr(`entwurf_callback error: ${err instanceof Error ? err.message : String(err)}`);
|
|
773
|
+
}
|
|
774
|
+
});
|
|
743
775
|
// ============================================================================
|
|
744
776
|
// Main
|
|
745
777
|
// ============================================================================
|
|
@@ -8,12 +8,16 @@
|
|
|
8
8
|
//
|
|
9
9
|
// S2c/S2d boundary (GPT S2c Q2): this is CONVERSATION TRANSCRIPT PASSTHROUGH, not
|
|
10
10
|
// rich-carrier identity injection. Deliberately EXCLUDED here (all S2d):
|
|
11
|
-
// -
|
|
11
|
+
// - the system prompt — never read into the prompt or `_meta.systemPrompt`
|
|
12
12
|
// (the billing carrier stays absent — NEXT §S2-scout 핀1);
|
|
13
13
|
// - `~/AGENTS.md` / cwd AGENTS / bridge identity narrative;
|
|
14
14
|
// - first-user-message augment + project-context de-dup;
|
|
15
|
-
// -
|
|
15
|
+
// - the declared tool surface — the ACP child tool surface is the S2b
|
|
16
16
|
// `_meta.claudeCode.options` SSOT, never re-sent here.
|
|
17
|
+
// Since pi 0.86 both of those ride a leading `role:"system"` message inside the
|
|
18
|
+
// `TranscriptContext` (pi-ai `normalizeContext`) rather than their own `Context`
|
|
19
|
+
// fields, so the exclusion is enforced by `renderMessage`'s explicit
|
|
20
|
+
// `case "system"` below, not by simply not reading two fields.
|
|
17
21
|
// Structured tool replay is also excluded: tool calls/results render as plain
|
|
18
22
|
// transcript text, never as ACP tool invocations (the child runs its own tools).
|
|
19
23
|
// MUST equal event-mapper.ts `LIFECYCLE_NOTICE_SIGNATURE` (the SSOT/producer).
|
|
@@ -69,13 +73,23 @@ function renderMessage(message) {
|
|
|
69
73
|
const tag = message.isError ? "Tool error" : "Tool result";
|
|
70
74
|
return text ? `${tag} (${message.toolName}): ${text}` : undefined;
|
|
71
75
|
}
|
|
76
|
+
case "system":
|
|
77
|
+
// The never-forward-systemPrompt invariant, stated rather than inherited.
|
|
78
|
+
// Since pi 0.86 the system prompt and the declared tool surface arrive AS
|
|
79
|
+
// transcript messages (`normalizeContext`), so what used to be "we simply
|
|
80
|
+
// do not read `context.systemPrompt`" is now an active decision on every
|
|
81
|
+
// turn. `default` would already skip it; the explicit case exists so
|
|
82
|
+
// deleting it is a visible change and a new pi role cannot quietly start
|
|
83
|
+
// leaking the prompt into the ACP child's transcript.
|
|
84
|
+
return undefined;
|
|
72
85
|
default:
|
|
73
86
|
return undefined;
|
|
74
87
|
}
|
|
75
88
|
}
|
|
76
89
|
/**
|
|
77
|
-
* Flatten a pi
|
|
78
|
-
*
|
|
90
|
+
* Flatten a pi transcript into a single transcript string. Excludes the system
|
|
91
|
+
* prompt and the declared tool surface by construction — since 0.86 both ride
|
|
92
|
+
* the leading `role:"system"` message, which `renderMessage` drops explicitly.
|
|
79
93
|
*/
|
|
80
94
|
export function contextTranscript(context) {
|
|
81
95
|
const lines = [];
|
|
@@ -87,7 +101,7 @@ export function contextTranscript(context) {
|
|
|
87
101
|
return lines.join("\n\n");
|
|
88
102
|
}
|
|
89
103
|
/**
|
|
90
|
-
* Convert a pi
|
|
104
|
+
* Convert a pi transcript into the ACP `prompt` array (a single text block holding
|
|
91
105
|
* the flattened transcript). Empty history yields an empty array — the caller
|
|
92
106
|
* decides whether that is a hard error.
|
|
93
107
|
*/
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Callback-env — the launch-computed pair that lets a fresh sibling call home
|
|
3
|
+
* without copying a garden id or nonce out of prose.
|
|
4
|
+
*
|
|
5
|
+
* WHY THIS FILE EXISTS. Until this seam, `composeFreshCallFraming` put
|
|
6
|
+
* `target=<caller gid>` and `message=<nonce>` into the first-turn prompt, and
|
|
7
|
+
* the sibling had to retype them into `entwurf_v2`. That is the one place in
|
|
8
|
+
* the garden Hard Rule 2 (record is the sole address axis) was violated: the
|
|
9
|
+
* transcript became the address carrier, and a model that dropped a `$` or a
|
|
10
|
+
* hex nibble produced a refused dispatch that looked like a flaky backend.
|
|
11
|
+
*
|
|
12
|
+
* WHAT TRAVELS. Two process-env names, injected by the launcher next to the
|
|
13
|
+
* identity scrub (`PI_SESSION_ID=` / `PI_AGENT_ID=`), never as a general env
|
|
14
|
+
* carrier. The no-arg `entwurf_callback` verb reads BOTH from its own process
|
|
15
|
+
* and refuses by name when they are absent, malformed, or this process is a
|
|
16
|
+
* Codex-provenance bridge (window env never reaches that MCP child — measured).
|
|
17
|
+
*
|
|
18
|
+
* This module does not dispatch. It parses, validates, and formats. Launchers
|
|
19
|
+
* and the verb share it so a drifted regex cannot inject what the verb rejects.
|
|
20
|
+
*/
|
|
21
|
+
import { SESSION_ID_RE } from "./session-id.js";
|
|
22
|
+
export const CALLBACK_TARGET_ENV = "ENTWURF_CALLBACK_TARGET";
|
|
23
|
+
export const CALLBACK_NONCE_ENV = "ENTWURF_CALLBACK_NONCE";
|
|
24
|
+
/** Exactly what `mintNonce` emits (`fresh-call-composition.ts`): `mux-fresh-call-` + 12 random
|
|
25
|
+
* bytes as hex. ONE minter, ONE grammar — both rails call the same `mintNonce`, so a second
|
|
26
|
+
* accepted prefix would be a spelling no launcher produces and this leaf exists to refuse
|
|
27
|
+
* exactly that. A gate fixture is an oracle and never a reason to widen the subject. */
|
|
28
|
+
export const CALLBACK_NONCE_RE = /^mux-fresh-call-[0-9a-f]{24}$/;
|
|
29
|
+
function parseCallbackPair(target, nonce) {
|
|
30
|
+
if (typeof target !== "string" || typeof nonce !== "string") {
|
|
31
|
+
return { ok: false, reason: "callback-env-malformed" };
|
|
32
|
+
}
|
|
33
|
+
if (!SESSION_ID_RE.test(target) || !CALLBACK_NONCE_RE.test(nonce)) {
|
|
34
|
+
return { ok: false, reason: "callback-env-malformed" };
|
|
35
|
+
}
|
|
36
|
+
return { ok: true, target, nonce };
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Format the two `KEY=value` assignments a launcher injects. Throws rather than
|
|
40
|
+
* emitting a pair the verb would refuse — a launch that cannot name its caller
|
|
41
|
+
* must not open a window whose first action is a guaranteed reject.
|
|
42
|
+
*/
|
|
43
|
+
export function callbackEnvAssignments(params) {
|
|
44
|
+
const parsed = parseCallbackPair(params.target, params.nonce);
|
|
45
|
+
if (!parsed.ok) {
|
|
46
|
+
throw new Error(`callback-env: refusing to inject ${parsed.reason}`);
|
|
47
|
+
}
|
|
48
|
+
return [`${CALLBACK_TARGET_ENV}=${parsed.target}`, `${CALLBACK_NONCE_ENV}=${parsed.nonce}`];
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Read the pair from a process environment. Codex provenance is a named refuse
|
|
52
|
+
* even when the pair is well-formed: that bridge is an app-server child, and a
|
|
53
|
+
* pane-injected value would be the wrong citizen's (or last-launch-wins).
|
|
54
|
+
*
|
|
55
|
+
* Both names absent → `callback-env-absent`. One present, or either failing
|
|
56
|
+
* grammar → `callback-env-malformed`. No fallback to a model-supplied target.
|
|
57
|
+
*/
|
|
58
|
+
export function readCallbackEnv(env = process.env) {
|
|
59
|
+
if (env.ENTWURF_BRIDGE_NATIVE_HOST?.trim() === "codex") {
|
|
60
|
+
return { ok: false, reason: "codex-callback-env-unsupported" };
|
|
61
|
+
}
|
|
62
|
+
const target = env[CALLBACK_TARGET_ENV];
|
|
63
|
+
const nonce = env[CALLBACK_NONCE_ENV];
|
|
64
|
+
if (target === undefined && nonce === undefined) {
|
|
65
|
+
return { ok: false, reason: "callback-env-absent" };
|
|
66
|
+
}
|
|
67
|
+
return parseCallbackPair(target, nonce);
|
|
68
|
+
}
|