@junghanacs/entwurf 0.13.1 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. package/AGENTS.md +48 -15
  2. package/BASELINE.md +3 -3
  3. package/CHANGELOG.md +45 -0
  4. package/CONTRIBUTING.md +13 -9
  5. package/DELIVERY.md +7 -6
  6. package/README.md +27 -27
  7. package/VERIFY.md +22 -14
  8. package/demo/README.md +1 -1
  9. package/demo/demo-baseline.sh +1 -3
  10. package/demo/demo.sh +2 -5
  11. package/docs/acp-backend-rail.md +9 -4
  12. package/docs/external-mcp-host.md +4 -5
  13. package/docs/setup-clean-host.md +8 -7
  14. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +148 -28
  15. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/overlay.js +8 -6
  16. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-control-rpc.js +7 -5
  17. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-core.js +13 -14
  18. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-resume-args.js +45 -40
  19. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-contract.js +117 -95
  20. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-decider.js +23 -57
  21. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-lock.js +16 -7
  22. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-production.js +5 -53
  23. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-release.js +21 -36
  24. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-runner.js +3 -15
  25. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-send-fallback.js +12 -11
  26. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-send.js +2 -7
  27. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-surface.js +30 -67
  28. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-visible-resume.js +256 -0
  29. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js +91 -5
  30. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +258 -0
  31. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-launch.js +202 -0
  32. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-placement.js +289 -0
  33. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-resume-call.js +170 -0
  34. package/mcp/entwurf-bridge/dist/pi-extensions/lib/resume-launch-identity.js +136 -0
  35. package/mcp/entwurf-bridge/dist/pi-extensions/lib/session-id.js +8 -5
  36. package/mcp/entwurf-bridge/dist/pi-extensions/lib/socket-discovery.js +3 -3
  37. package/mcp/entwurf-bridge/dist/scripts/meta-facts.js +51 -0
  38. package/mcp/entwurf-bridge/dist/scripts/new-session-id.js +9 -4
  39. package/mcp/entwurf-bridge/src/index.ts +164 -28
  40. package/mcp/entwurf-bridge/start.sh +2 -2
  41. package/mcp/entwurf-bridge/test.sh +23 -9
  42. package/mcp/entwurf-bridge/tsconfig.build.json +11 -2
  43. package/package.json +22 -11
  44. package/pi-extensions/entwurf-control.ts +218 -40
  45. package/pi-extensions/lib/acp/backend.ts +71 -12
  46. package/pi-extensions/lib/acp/overlay.ts +8 -6
  47. package/pi-extensions/lib/entwurf-control-rpc.ts +7 -5
  48. package/pi-extensions/lib/entwurf-core.ts +15 -15
  49. package/pi-extensions/lib/entwurf-resume-args.ts +41 -52
  50. package/pi-extensions/lib/entwurf-v2-contract-schema.ts +1 -1
  51. package/pi-extensions/lib/entwurf-v2-contract.ts +120 -99
  52. package/pi-extensions/lib/entwurf-v2-decider.ts +30 -91
  53. package/pi-extensions/lib/entwurf-v2-lock.ts +16 -7
  54. package/pi-extensions/lib/entwurf-v2-production.ts +4 -78
  55. package/pi-extensions/lib/entwurf-v2-release.ts +25 -49
  56. package/pi-extensions/lib/entwurf-v2-runner.ts +6 -21
  57. package/pi-extensions/lib/entwurf-v2-send-fallback.ts +12 -11
  58. package/pi-extensions/lib/entwurf-v2-send.ts +2 -7
  59. package/pi-extensions/lib/entwurf-v2-surface.ts +36 -76
  60. package/pi-extensions/lib/entwurf-v2-visible-resume.ts +370 -0
  61. package/pi-extensions/lib/meta-session.ts +93 -5
  62. package/pi-extensions/lib/mux-fresh-call.ts +328 -0
  63. package/pi-extensions/lib/mux-launch.ts +267 -0
  64. package/pi-extensions/lib/mux-placement.ts +387 -0
  65. package/pi-extensions/lib/mux-resume-call.ts +221 -0
  66. package/pi-extensions/lib/resume-launch-identity.ts +162 -0
  67. package/pi-extensions/lib/session-id.js +8 -5
  68. package/pi-extensions/lib/socket-discovery.ts +3 -3
  69. package/run.sh +471 -201
  70. package/scripts/agy-bridge-config.py +5 -1
  71. package/scripts/check-acp-backend-preflight.ts +1 -1
  72. package/scripts/check-acp-overlay.ts +13 -3
  73. package/scripts/check-acp-stream-hooks.ts +504 -0
  74. package/scripts/check-elapsed.sh +25 -0
  75. package/scripts/check-entwurf-bridge-boot.ts +51 -4
  76. package/scripts/check-entwurf-bridge-pi-free.ts +6 -5
  77. package/scripts/check-entwurf-control-rpc.ts +4 -3
  78. package/scripts/check-entwurf-resume-args.ts +72 -70
  79. package/scripts/check-entwurf-session-identity.ts +14 -10
  80. package/scripts/check-entwurf-v2-contract.ts +34 -59
  81. package/scripts/check-entwurf-v2-decider.ts +17 -177
  82. package/scripts/check-entwurf-v2-lock.ts +5 -2
  83. package/scripts/check-entwurf-v2-matrix.ts +3 -53
  84. package/scripts/check-entwurf-v2-production.ts +2 -91
  85. package/scripts/check-entwurf-v2-release.ts +10 -105
  86. package/scripts/check-entwurf-v2-runner.ts +4 -85
  87. package/scripts/check-entwurf-v2-send-fallback.ts +5 -6
  88. package/scripts/check-entwurf-v2-send.ts +0 -28
  89. package/scripts/check-entwurf-v2-surface.ts +157 -128
  90. package/scripts/check-entwurf-v2-visible-resume.ts +445 -0
  91. package/scripts/check-fresh-cut-gate.sh +1 -1
  92. package/scripts/check-gate-qualification.ts +98 -7
  93. package/scripts/check-install-container.sh +10 -2
  94. package/scripts/check-install-surface.ts +1 -1
  95. package/scripts/check-keyset-overlap.py +1 -1
  96. package/scripts/check-meta-facts.ts +249 -0
  97. package/scripts/check-meta-identity-consumers.ts +1 -1
  98. package/scripts/check-meta-session.ts +169 -0
  99. package/scripts/check-mux-launch-tmux.ts +316 -0
  100. package/scripts/check-mux-launch.ts +288 -0
  101. package/scripts/check-mux-launcher-fence.ts +264 -0
  102. package/scripts/check-mux-parent-artifact.ts +195 -0
  103. package/scripts/check-mux-placement-tmux.ts +322 -0
  104. package/scripts/check-mux-placement.ts +323 -0
  105. package/scripts/check-mux-resume-call.ts +282 -0
  106. package/scripts/check-probe-cli-shim.ts +25 -22
  107. package/scripts/check-probe-ordering.ts +84 -76
  108. package/scripts/check-release-gate-outcomes.ts +127 -7
  109. package/scripts/check-resume-launch-identity.ts +244 -0
  110. package/scripts/check-socket-discovery.ts +1 -1
  111. package/scripts/fixtures/mux-parent-transcript.scrubbed.jsonl +3 -0
  112. package/scripts/inventory-verification-surface.ts +349 -0
  113. package/scripts/lib/claude-launcher-fence.ts +322 -0
  114. package/scripts/lib/mutation-qualify.ts +109 -3
  115. package/scripts/meta-bridge-doctor.sh +6 -8
  116. package/scripts/meta-facts.ts +60 -0
  117. package/scripts/mutants/acp-overlay.json +17 -0
  118. package/scripts/mutants/acp-stream-hooks.json +158 -0
  119. package/scripts/mutants/bridge-boot-resume.json +45 -0
  120. package/scripts/mutants/meta-facts.json +50 -0
  121. package/scripts/mutants/meta-identity.json +36 -0
  122. package/scripts/mutants/mux-boundary.json +196 -0
  123. package/scripts/mutants/mux-fresh-call.json +185 -0
  124. package/scripts/mutants/mux-launcher-fence.json +123 -0
  125. package/scripts/mutants/mux-parent-artifact.json +39 -0
  126. package/scripts/mutants/mux-resume-call.json +148 -0
  127. package/scripts/mutants/probe-ordering.json +0 -1037
  128. package/scripts/mutants/release-gate.json +35 -0
  129. package/scripts/mutants/resume-args.json +76 -0
  130. package/scripts/mutants/resume-launch-identity.json +96 -0
  131. package/scripts/mutants/v2-surface.json +58 -18
  132. package/scripts/mutants/v2-visible-resume.json +215 -0
  133. package/scripts/new-session-id.ts +9 -4
  134. package/scripts/smoke-acp-raw-turn-live.ts +1 -1
  135. package/scripts/smoke-agy-native-push-live.ts +6 -17
  136. package/scripts/smoke-entwurf-v2-matrix-live.ts +1 -1
  137. package/scripts/smoke-meta-honesty.sh +1 -1
  138. package/scripts/smoke-mux-fresh-call-live.ts +365 -0
  139. package/scripts/smoke-mux-lifecycle-live.ts +1136 -0
  140. package/scripts/smoke-pi-attach.ts +1 -1
  141. package/scripts/smoke-user-scope-citizen.sh +1 -1
  142. package/scripts/tsconfig.json +1 -0
  143. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-preflight.js +0 -160
  144. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-spawn-production.js +0 -273
  145. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-spawn.js +0 -216
  146. package/pi-extensions/lib/entwurf-v2-spawn-production.ts +0 -373
  147. package/pi-extensions/lib/entwurf-v2-spawn.ts +0 -323
  148. package/scripts/check-acp-sdk-surface.ts +0 -275
  149. package/scripts/check-entwurf-v2-spawn-production.ts +0 -551
  150. package/scripts/check-entwurf-v2-spawn.ts +0 -399
  151. package/scripts/smoke-entwurf-v2-spawn-live.ts +0 -188
  152. package/scripts/smoke-entwurf-v2-spawn-resume-live.ts +0 -467
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_self`, `entwurf_inbox_read`, and `entwurf_register_native`. The ACP
8
+ > `entwurf_fresh_call`, `entwurf_resume_call`, `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 has delivery-probe evidence but no managed citizen
11
11
  > lane. Retired v1 verbs and bridge implementations belong only in CHANGELOG/git.
@@ -40,19 +40,27 @@ Verification here is not a benchmark. In production we exchange short turns and
40
40
 
41
41
  ### The canonical floor — two entry points
42
42
 
43
- - **Deterministic floor:** `pnpm check` — the full `check-*` gate set (the `check` script in `package.json` is the SSOT for what runs). Run first; it is the offline, machine-checkable layer.
44
- - **Discriminating power of that floor:** `./run.sh check-gate-qualification` (inside `pnpm check`) re-plants committed defect mutants (`scripts/mutants/*.json`, one per closed defect class) in an isolated snapshot repo and requires each to turn its gate red **bounded and at its claimed `[QK:<claim>]` signature** — a wrong-reason red fails, a baseline-red control voids the whole group, and the runner is negative-controlled on every run (zero-match/multi-match/survived/wrong-reason/hang/control-red/impurity). This measures whether the deterministic gates still *block* what they claim to block; it is **not a new evidence level** (L0–L5 are untouched) and never substitutes for LIVE evidence. Per-cut records cite claim IDs + killed mutant IDs — "N checks passed" alone is not evidence. `check-agy-permission-matrix` complements it with the enumerated permission contract space (literal cells + stated exclusion rules, oracle independent of the SUT).
45
- - **Live floor:** `LIVE=1 ./run.sh release-gate <scratch-project-dir> --cut` — `pnpm check` + the v2-native live gates + the ACP plugin acceptance floor. It reports a **two-tier summary**:
46
- - **MUST tier** (release-blocking — owns the exit code; "green" applies only here): `pnpm check`, `smoke-entwurf-v2-spawn-resume-live`, `smoke-entwurf-v2-matrix-live`, `check-bridge`, the resident-garden-guard zero-token half (record birth / record-keyed socket / attach-on-reopen), the `smoke-acp-*-live` ACP plugin smokes (socket-citizen / raw-turn / overlay / provider / session-reuse / carrier-augment / memory-containment / rgg / mcp / skill / bundled-mcp / v2-send), the two axes wired in on 2026-07-31 that the aggregate had simply never listed (`smoke-entwurf-v2-spawn-live`, `smoke-claude-native-resume-live`), and `smoke-entwurf-chain-live` — the cross-harness delivery chain (native Claude Code → pi GPT → pi ACP Sonnet → mailbox terminus) proving sender identity and replyability at every hop plus a real read receipt at the end. (`smoke-session-id-name` is gone — #50 C3: its `--session-id`/`--name` substrate has no entwurf consumer anymore.)
43
+ - **Deterministic floor — two tiers (#70):** `pnpm check` is the everyday CORE toolchain (lint + typecheck), the vitest lanes, and the pure-unit / behavioral-contract / source-topology gates plus the cheap static coherence checks. It prints its own total wall time; acceptance is ≤60s on the reference host `oracle` (an operator measurement, never a hard wall-clock gate on arbitrary hosts). `pnpm run check:full` is the FULL deterministic floor — the core plus the hermetic-integration and package/install tiers — and is what the frozen-candidate protocol, push CI, release-gate, and `prepublishOnly` run. Exact membership is the named `check:*` group scripts in `package.json` (the executable SSOT this document records meaning and principles, not the command list); a gate changes tier by semantic-class decision, never by getting faster or slower. Both tiers exclude the separately scheduled `check-gate-qualification`.
44
+ - **Discriminating power of that floor:** `./run.sh check-gate-qualification` (scheduled, not ambient: standalone when a lane changes a gate/mutant/matrix, in the CI `check` job on every push, and as a release-gate MUST step — no longer inside the default check chains) re-plants committed defect mutants (`scripts/mutants/*.json`, one per closed defect class) in an isolated snapshot repo and requires each to turn its gate red **bounded and at its claimed `[QK:<claim>]` signature** — a wrong-reason red fails, a baseline-red control voids the whole group, and the runner is negative-controlled on every run (zero-match/multi-match/survived/wrong-reason/hang/control-red/impurity). This measures whether the deterministic gates still *block* what they claim to block; it is **not a new evidence level** (L0–L5 are untouched) and never substitutes for LIVE evidence. Per-cut records cite claim IDs + killed mutant IDs — "N checks passed" alone is not evidence. `check-agy-permission-matrix` complements it with the enumerated permission contract space (literal cells + stated exclusion rules, oracle independent of the SUT).
45
+ - **Live floor:** `LIVE=1 ./run.sh release-gate <scratch-project-dir> --cut` — `pnpm run check:full` + the v2-native live gates + the ACP plugin acceptance floor. It reports a **two-tier summary**:
46
+ - **MUST tier** (release-blocking — owns the exit code; "green" applies only here): `pnpm run check:full`, `check-gate-qualification`, `smoke-entwurf-v2-matrix-live`, `check-bridge`, the resident-garden-guard zero-token half (record birth / record-keyed socket / attach-on-reopen), the `smoke-acp-*-live` ACP plugin smokes (socket-citizen / raw-turn / overlay / provider / session-reuse / carrier-augment / memory-containment / rgg / mcp / skill / bundled-mcp / v2-send), the axis wired in on 2026-07-31 that the aggregate had simply never listed (`smoke-claude-native-resume-live`), and `smoke-entwurf-chain-live` — the cross-harness delivery chain (native Claude Code → pi GPT → pi ACP Sonnet → mailbox terminus) proving sender identity and replyability at every hop plus a real read receipt at the end. (`smoke-session-id-name` is gone — #50 C3: its `--session-id`/`--name` substrate has no entwurf consumer anymore.)
47
47
  - **BEHAVIOR tier** (advisory, non-blocking): the resident-garden-guard positive (a model-in-loop `entwurf_self` turn). A BEHAVIOR FAIL is surfaced with its artifact path but **never blocks the cut**. The lane holds what the model *chooses*, never what our wiring fails to deliver — a gate that TELLS the model which tool to call stays MUST, because its failure is ours — measured 2026-07-24, when the tool turned out to be absent from the session schema in both observed failures (the bundled-MCP readiness gap recorded in `scripts/smoke-acp-v2-send-live.ts`).
48
- - **Every MUST step is invoked and reports its own outcome** (P1 STEP OUTCOME protocol, `scripts/lib/step-outcome.sh`): exit 0 = PASS, exit 97 = SKIP (a prerequisite the step does not have, printed as an `[entwurf:skip]` line), anything else = FAIL. A skip is never counted as a pass — that hole is what let a cortex-less host read as cortex acceptance. Without `--cut` this is the unattended diagnostic: SKIPs are reported and the run still exits 0. **`--cut` makes it acceptance and any MUST SKIP is red**, which is how "a real cut needs `LIVE=1` with `SKIP=0`" stopped being prose. A green MUST gate is **necessary, not sufficient** — GLG authorizes the cut.
48
+ - **Every MUST step is invoked and reports its own outcome.** Prerequisite-bearing LIVE MUST steps ride the P1 STEP OUTCOME protocol (`scripts/lib/step-outcome.sh`): exit 0 = PASS, exit 97 = SKIP (a prerequisite the step does not have, printed as an `[entwurf:skip]` line), anything else = FAIL. The two non-skippable static steps (`pnpm run check:full`, `check-gate-qualification`) are inline: 0 = PASS, nonzero = FAIL, no SKIP arm — they have no prerequisite to decline. A skip is never counted as a pass — that hole is what let a cortex-less host read as cortex acceptance. Without `--cut` this is the unattended diagnostic: SKIPs are reported and the run still exits 0. **`--cut` makes it acceptance and any MUST SKIP is red**, which is how "a real cut needs `LIVE=1` with `SKIP=0`" stopped being prose. A green MUST gate is **necessary, not sufficient** — GLG authorizes the cut.
49
49
  - **When cost-bearing MUST gates run (fixed 2026-07-23, the F6/F7 lesson):** a commit that touches a rail a MUST-tier live gate covers runs that gate **before cross-review is requested** — never parked behind "run it at approval time". Deferring a wired gate to a human decision is what let F6/F7 ship reviewed-and-approved; the wiring exists so the verdict never depends on who pressed enter. "배선이 없어 못 한 것은 OK, 배선이 있는데 안 돌린 것은 우리가 남긴 구멍이다." Model-in-loop cost is spent via the subscription-backed `entwurf` provider where the gate allows it, a free-tier native model otherwise; cost is a reason to pick the cheap target, not to skip the gate.
50
50
 
51
- > **Cortex is an on-demand axis, not an aggregate one.** Its rail needs an external Snowflake connection and login that the host owns, not the repo — so wiring it into the aggregate would block every cut taken on a host without that account. **The 0.13.1 aggregate does not re-certify Cortex**; `LIVE=1 ENTWURF_ACP_CORTEX_CONNECTION=<conn> ./run.sh smoke-acp-cortex-live` stays a required direct call whenever a cut changes Cortex rail code or an operator elects to certify that host. Its honest-skip behaviour is unchanged: run it without the connection and it reports protocol SKIP, never a pass.
51
+ > **Cortex is an on-demand axis, not an aggregate one.** Its rail needs an external Snowflake connection and login that the host owns, not the repo — so wiring it into the aggregate would block every cut taken on a host without that account. **The release aggregate does not re-certify Cortex**; `LIVE=1 ENTWURF_ACP_CORTEX_CONNECTION=<conn> ./run.sh smoke-acp-cortex-live` stays a required direct call whenever a cut changes Cortex rail code or an operator elects to certify that host. Its honest-skip behaviour is unchanged: run it without the connection and it reports protocol SKIP, never a pass.
52
52
  >
53
53
  > 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`.
54
54
  >
55
- > The aggregate release gate does not own a live agy conversation id, so agy's real native-push round trip is a separate acceptance axis: three fail-loud doctors plus `LIVE=1 AGY_CONVERSATION_ID=<id> ./run.sh smoke-agy-native-push-live`, followed by a fresh-conversation sender/reply check after package install. Its deterministic install/sender gates are already inside `pnpm check`; do not misreport the aggregate gate as live agy evidence.
55
+ > **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-5.6-terra` and recorded-ACP `entwurf/claude-sonnet-5`, the only shape that re-injects a bridge extension), and the Claude Code cell ends where its capability ends: its resume is refused as `target-not-pi` with no window opened and no lock residue (the per-gid coordination lock is released).
56
+ >
57
+ > 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.
58
+ >
59
+ > Be honest about its cost, because it is a MUST: it spends real model turns on the operator's configured runtimes (two pi siblings, each resumed once, plus one Claude Code sibling), it READS the operator's real runtime config, and the siblings' native session transcripts stay in the real pi agent dir — they are not cleaned up, and they are part of the evidence a resume was real. The fence is per-axis rather than absolute: the four meta roots (records, mailbox, receivers, senders) are fixture for every cell, so no garden record is ever minted outside it; the native pi cell's control socket and v2 lock are fixture too; the ACP pi cell's are REAL and transient (see the paragraph above); and the Claude cell runs its runtime under the real HOME while its mailbox and meta stay fixture. The run ends by proving the operator's SIX real roots — the four meta roots plus `~/.pi/entwurf-control` and `~/.pi/entwurf-v2-locks` — hold the entry sets they started with, with no fixture garden id anywhere, and that proof now runs on the failure path as well as the green one. Missing prerequisites decline with the protocol SKIP (97), which `release-gate --cut` refuses rather than rounding up.
60
+ >
61
+ > **Fresh-call LIVE is on-demand, not part of `release-gate`.** `LIVE=1 ./run.sh smoke-mux-fresh-call-live` opens configured Pi and Claude Code siblings, spends two model turns, and preserves their native transcripts; run it directly whenever a cut changes mux fresh-call/launch behavior. Its private tmux servers and fixture-bound entwurf write axes make it deterministic about garden-record/socket residue, but those real native turns should not appear unexpectedly in every aggregate cut.
62
+ >
63
+ > The aggregate release gate does not own a live agy conversation id, so agy's real native-push round trip is a separate acceptance axis: three fail-loud doctors plus `LIVE=1 AGY_CONVERSATION_ID=<id> ./run.sh smoke-agy-native-push-live`, followed by a fresh-conversation sender/reply check after package install. Its deterministic install/sender gates are already inside `pnpm run check:full`; do not misreport the aggregate gate as live agy evidence.
56
64
  >
57
65
  > Authoritative per-cut counts and digests live in BASELINE/CHANGELOG, not inline
58
66
  > here; embedding them in the protocol makes a correct guide stale after every cut.
@@ -63,7 +71,7 @@ Do not collapse source, package, fixture, and native-host evidence into one “g
63
71
 
64
72
  | Axis | Required proof | Limit |
65
73
  |---|---|---|
66
- | Source | `pnpm check` | Does not prove an installed consumer. |
74
+ | Source | `pnpm run check:full` + `./run.sh check-gate-qualification` | Does not prove an installed consumer. |
67
75
  | Packed install | `check-pack-install` | Real tarball, but checkout-visible. |
68
76
  | Linux artifact consumer | required `check-install-container` CI job against one preserved candidate | Fixtures prove package/oracle shape, not a real Claude lifecycle. |
69
77
  | Exact release commit | all required CI jobs green at the exact SHA | A different green SHA is not transferable evidence. |
@@ -78,7 +86,7 @@ consumer image outside this standing protocol.
78
86
 
79
87
  ### Verifying the two capabilities a gate cannot fully judge
80
88
 
81
- - **Garden-id delivery:** discover a target with `entwurf_peers`, then `entwurf_v2` with the correct intent — `fire-and-forget` for live pi, mailbox-backed meta, or native-push targets; `owned-outcome` only on a dormant target in the control-socket liveness domain (currently backend `pi`), where it selects **spawn-bg a relaunch transport, not the control-socket rail that carries live sends**; the launch leaf checks backend authority. Picking the wrong intent is rejected, never auto-fixed.
89
+ - **Garden-id delivery:** discover a target with `entwurf_peers`, then `entwurf_v2 fire-and-forget` the intent axis is single-valued since the visible-first cut. It reaches a live pi target over its control socket, a deliverable self-fetch citizen through the mailbox, and a probe-alive native-push conversation by direct injection. A DORMANT in-domain citizen rejects as `dormant-fire-forget-unsupported` delivery never wakes it; reopen it with the separate lifecycle verb `entwurf_resume_call {target}`, then dispatch again. A wrong target state is rejected, never auto-fixed and never launched into.
82
90
  - **ACP continuity:** a direct `pi --provider entwurf --model claude-sonnet-5` turn, or the `smoke-acp-session-reuse-live` gate (process-scoped reuse + recall). Multi-turn reuse is proven by that gate, not by any v1 resume tool.
83
91
  - **agy citizenship:** in a fresh agy conversation, the first `PreInvocation` must yield a garden id, `entwurf_self` must report `agentId=meta-session/antigravity` and `replyable:true` only while the native probe is alive, and a reply to that same garden id must direct-inject into the same conversation. No mailbox/receiver-marker evidence counts on this rail.
84
92
 
@@ -199,7 +207,7 @@ Pass (carrier honesty): the subject distinguishes engraving from pi-context-augm
199
207
 
200
208
  - **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).
201
209
  - **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.
202
- - **Layer 2 — MCP boundary:** by default the five 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`.
210
+ - **Layer 2 — MCP boundary:** by default the seven 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`.
203
211
  - **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).
204
212
  - **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.
205
213
 
@@ -265,11 +273,11 @@ Pass: user/assistant turns accumulate normally; the transcript is not broken/emp
265
273
 
266
274
  The minimum passing bar:
267
275
 
268
- 1. **Deterministic floor green:** `pnpm check` passes (lint + typecheck + the `check-*` gate set + `check-pack`).
276
+ 1. **Deterministic floor green:** `pnpm run check:full` passes (the core tier plus the hermetic-integration and package/install tiers, incl. `check-pack`), plus a `./run.sh check-gate-qualification` green on the same candidate.
269
277
  2. **All three CI jobs green on the exact release commit:** `check`, `install-surface`, and the required Linux `artifact-consumer`; preserve the latter's tarball digest and image identity.
270
278
  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.
271
279
  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.
272
- 5. **Honest self-recognition:** the bridged model identifies its actual harness/backend, lists `entwurf-bridge` as the single MCP server with its five current tools, and presents a backend-native (not normalized) tool surface.
280
+ 5. **Honest self-recognition:** the bridged model identifies its actual harness/backend, lists `entwurf-bridge` as the single MCP server with its seven current tools, and presents a backend-native (not normalized) tool surface.
273
281
  6. **Carrier separation honored:** engraving vs pi-context-augment kept distinct (§1A.0); no bridge-identity narrative attributed to the engraving carrier.
274
282
  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.
275
283
  8. **Boundary preservation across backends/machines:** for every shipped or explicitly probed backend, regardless of install path or host, no cross-backend tool-surface contamination and no confabulation about pi internals.
@@ -285,7 +293,7 @@ Passing establishes a **release verification floor**, not an 8-hour/day operatio
285
293
 
286
294
  - **`ENTWURF_CHILD_STDERR_LOG`** mirrors child stderr to a file for bootstrap-path visibility — but it must be present at **bridge-process spawn time**; `export` from a shell already bound to a running bridge does not propagate. Restart the parent session with it exported, then `grep -E '\[entwurf:(bootstrap|model-switch|cancel|shutdown)\]' "$ENTWURF_CHILD_STDERR_LOG"`.
287
295
  - **Retired dedicated smokes, live code invariants** (manual/troubleshooting only — *not* part of the release floor):
288
- - *Model-switch lock* — entwurf sessions are locked to their starting model. Gate: `check-model-lock` (in `pnpm check`). The dedicated live `smoke-model-switch` was retired in v2; the invariant lives in `pi-extensions/model-lock.ts` (extension guard) + `session-store.ts` `SessionModelLockedError` (the `decideBootstrap` fail-loud model lock).
296
+ - *Model-switch lock* — entwurf sessions are locked to their starting model. Gate: `check-model-lock` (in `pnpm check`, core tier). The dedicated live `smoke-model-switch` was retired in v2; the invariant lives in `pi-extensions/model-lock.ts` (extension guard) + `session-store.ts` `SessionModelLockedError` (the `decideBootstrap` fail-loud model lock).
289
297
  - *Cancel / abort cleanup* — `onAbort` → `cancelActivePrompt()` (session stays reusable); the stream catch closes the bridge only on `stopReason === "error"`. Dedicated `smoke-cancel` retired; invariant in code.
290
298
 
291
299
  ### Evidence preservation when a problem occurs
package/demo/README.md CHANGED
@@ -137,7 +137,7 @@ SCENE_DELAY=30 FINAL_PAUSE=10 bash demo.sh
137
137
 
138
138
  ## Prerequisites
139
139
 
140
- - `pi` on PATH (current floor 0.83.0)
140
+ - `pi` on PATH (current floor 0.84.1)
141
141
  - `entwurf` provider configured + auth ready for the selected sender/peer models
142
142
  - `asciinema` installed
143
143
  - `agg` installed (optional — only for GIF conversion)
@@ -76,12 +76,10 @@ tmux kill-session -t "$SESSION" 2>/dev/null || true
76
76
  # entwurf siblings can address it if a follow-up demo needs it.
77
77
  COMMON_ENV="ENTWURF_DEBUG=1 PI_EMACS_AGENT_SOCKET=$EMACS_SOCKET"
78
78
  COMMON_ARGS="--entwurf-control --emacs-agent-socket $EMACS_SOCKET"
79
- new_session_id() { bash "$REPO_ROOT/run.sh" new-session-id; }
80
79
 
81
80
  # ---------- start driver (single pane) ----------
82
- DRIVER_LAUNCH_ID=$(new_session_id)
83
81
  tmux new-session -d -s "$SESSION" -n demo -x 220 -y 50 \
84
- "$COMMON_ENV pi --session-id $DRIVER_LAUNCH_ID --model $DRIVER_MODEL $COMMON_ARGS 2>>$DRIVER_LOG"
82
+ "$COMMON_ENV pi --model $DRIVER_MODEL $COMMON_ARGS 2>>$DRIVER_LOG"
85
83
  DRIVER_PANE=$(tmux list-panes -s -t "$SESSION" -F '#{pane_id}' | head -1)
86
84
 
87
85
  # Give pi time to print its banner and reach the prompt.
package/demo/demo.sh CHANGED
@@ -77,7 +77,6 @@ tmux kill-session -t "$SESSION" 2>/dev/null || true
77
77
  # Plain `2>>` is POSIX sh — works under tmux's default /bin/sh.
78
78
  COMMON_ENV="ENTWURF_DEBUG=1 PI_EMACS_AGENT_SOCKET=$EMACS_SOCKET"
79
79
  COMMON_ARGS="--entwurf-control --emacs-agent-socket $EMACS_SOCKET"
80
- new_session_id() { bash "$REPO_ROOT/run.sh" new-session-id; }
81
80
 
82
81
  # Snapshot pre-existing control sockets so we can detect which one this demo's
83
82
  # peer pane creates. Without this, Scene 3 could greet an unrelated live pi
@@ -107,9 +106,8 @@ wait_for_new_socket() {
107
106
  # the operator's tmux `base-index` / `pane-base-index` settings.
108
107
 
109
108
  # ---------- start peer (top pane) — equivalent to: piat / piag / piat5 ----------
110
- PEER_LAUNCH_ID=$(new_session_id)
111
109
  tmux new-session -d -s "$SESSION" -n demo -x 220 -y 50 \
112
- "$COMMON_ENV pi --session-id $PEER_LAUNCH_ID --model $PEER_MODEL $COMMON_ARGS 2>>$PEER_LOG"
110
+ "$COMMON_ENV pi --model $PEER_MODEL $COMMON_ARGS 2>>$PEER_LOG"
113
111
  PEER_PANE=$(tmux list-panes -s -t "$SESSION" -F '#{pane_id}' | head -1)
114
112
 
115
113
  PEER_ID=$(wait_for_new_socket "$PRE_SOCKETS") || {
@@ -122,9 +120,8 @@ echo "Peer sessionId: $PEER_ID pane=$PEER_PANE"
122
120
  POST_PEER_SOCKETS=$(ls "$SOCK_DIR"/*.sock 2>/dev/null | sort || true)
123
121
 
124
122
  # ---------- start sender (bottom pane, split below) — equivalent to: pias / piao ----------
125
- SENDER_LAUNCH_ID=$(new_session_id)
126
123
  SENDER_PANE=$(tmux split-window -t "$PEER_PANE" -v -P -F '#{pane_id}' \
127
- "$COMMON_ENV pi --session-id $SENDER_LAUNCH_ID --model $SENDER_MODEL $COMMON_ARGS 2>>$SENDER_LOG")
124
+ "$COMMON_ENV pi --model $SENDER_MODEL $COMMON_ARGS 2>>$SENDER_LOG")
128
125
 
129
126
  SENDER_ID=$(wait_for_new_socket "$POST_PEER_SOCKETS") || {
130
127
  echo "ERROR: sender session never registered a control socket. Check $SENDER_LOG." >&2
@@ -159,8 +159,10 @@ caller-session `_meta`, and cross-machine certification.
159
159
 
160
160
  A backend can return `newSession` before its declared MCP server is callable. This was
161
161
  observed intermittently on the Claude rail and directly on Cortex's private `mcp.json`
162
- path. Neither `claude-agent-acp` 0.64.0 nor the Cortex landing adds a client-side
162
+ path. Neither `claude-agent-acp` 0.66.0 nor the Cortex landing adds a client-side
163
163
  readiness fence, and `mcpServerStatus()` is not called by the common loop.
164
+ (Re-measured at the 0.65.0 → 0.66.0 bump: the goal extension is not a fence,
165
+ and `mcpServerStatus` appears nowhere in the 0.66.0 adapter source.)
164
166
 
165
167
  ### 11-7-a/b. Instrument and first measurement
166
168
 
@@ -183,7 +185,10 @@ exit/signal behavior, bounds NDJSON framing, scrubs only the exact probe env all
183
185
  and timestamps snapshot/prompt hand-offs inside the downstream write callback. One
184
186
  post-wire init snapshot may support the controlled-absence row; malformed, duplicate,
185
187
  pre-wire, unarmed, or target-mismatched snapshots invalidate the run rather than proving
186
- absence. The deterministic mutants in `probe-ordering.json` are the detailed oracle.
188
+ absence. `check-probe-cli-shim` is the detailed producer oracle: its 20 direct
189
+ `[CHECK:*]` assertions remain, while their verification-infra replants were deliberately
190
+ removed by #70. `probe-ordering.json` retains only the product-subject no-production-prompt-cutoff
191
+ replant consumed by `check-probe-ordering`.
187
192
 
188
193
  Current probe contract and gates:
189
194
 
@@ -200,7 +205,7 @@ fail when the callable surface is absent. Do not add sleeps or infer readiness f
200
205
  Deterministic floor:
201
206
 
202
207
  ```bash
203
- pnpm check
208
+ pnpm run check:full
204
209
  ./run.sh check-acp-provider-surface
205
210
  ./run.sh check-acp-sdk-surface
206
211
  ./run.sh check-acp-session-reuse
@@ -227,5 +232,5 @@ not this standing contract.
227
232
 
228
233
  - causal MCP-readiness diagnosis and, only with proof, a backend-invariant fence;
229
234
  - broader installed-host and cross-machine Cortex evidence;
230
- - Codex managed native-citizen work on the reserved 0.14.0 lane—separate from ACP;
235
+ - any future Codex managed native-citizen lane—separate from ACP; 0.14.0 does not ship one;
231
236
  - persisted ACP resume/load, which is not implemented by today's in-memory reuse.
@@ -9,16 +9,15 @@ the one-line registration; everything an operator needs to actually wire a host
9
9
  - **plain external MCP host**: no garden meta-record / sender marker. It can call the read surfaces (`entwurf_peers`, `entwurf_inbox_read`), but `entwurf_v2` sends are **refused by default** (#50 C4: "if we don't know who sent it, we don't send it"). The operator may wire the explicit hatch below; the send then goes out external/non-replyable.
10
10
  - **garden-native native session**: a trusted lifecycle hook minted a garden id and sender marker — `SessionStart` for Claude Code, `PreInvocation` for agy. It is not a pi control-socket session, but it can be replyable by garden id when its own mailbox/probe rail says so.
11
11
 
12
- **Which verb an external agent should reach for:** to deliver to / reply to a garden id, use **`entwurf_v2`** — it is the canonical delivery surface and the only one that reads whether the target is live pi, dormant pi, mailbox-backed Claude Code, or native-push Antigravity and routes correctly. Discover targets with `entwurf_peers`, confirm your own identity with `entwurf_self`, drain a mailbox with `entwurf_inbox_read`, and use `entwurf_register_native` only as the explicit/manual fallback for binding an already-running agy conversation (normal agy birth is automatic through the installed hook). Fresh sibling creation from nothing is a deferred lane. (The old v1 verbs `entwurf` / `entwurf_resume` / `entwurf_send` are gone.)
12
+ **Which verb an external agent should reach for:** to deliver to / reply to a garden id, use **`entwurf_v2`** — it is the canonical delivery surface and the only one that reads whether the target is live pi, dormant pi, mailbox-backed Claude Code, or native-push Antigravity and routes correctly. Discover targets with `entwurf_peers`, confirm your own identity with `entwurf_self`, drain a mailbox with `entwurf_inbox_read`, and use `entwurf_register_native` only as the explicit/manual fallback for binding an already-running agy conversation (normal agy birth is automatic through the installed hook). Open a NEW sibling with **`entwurf_fresh_call`**, and reopen a DORMANT pi citizen under its own garden id with **`entwurf_resume_call {target}`**. Fresh call needs its selected runtime (`pi` or `claude`) on the server's PATH; resume call always needs `pi`. Both start a runtime, while delivery does not. (The old v1 verbs `entwurf` / `entwurf_resume` / `entwurf_send` are gone.)
13
13
 
14
14
  Observed: Claude Code, Codex CLI, and Antigravity CLI all reach the read surfaces through this MCP bridge from a plain external host — `entwurf_peers` is a pure fact projection, while `entwurf_inbox_read` is a **mutating drain** (it archives the messages and stamps the read-receipt), so "read" here does not mean side-effect-free; **sending** needs an identity lane. Claude becomes symmetric/replyable through its mailbox-backed meta-session; agy becomes symmetric/replyable through its record-backed sender marker plus live native-push probe. Codex has no managed citizen lifecycle yet, so a Codex host cannot send without the explicit anonymous hatch below.
15
15
 
16
16
  Prerequisites on the host running the external MCP client:
17
17
 
18
- - `pi` on PATH (for the `owned-outcome` spawn-bg resume path).
19
18
  - A live pi session launched with `--entwurf-control` populates `~/.pi/entwurf-control/<gardenId>.sock` — the key is the **record's** garden id, never a transcript/session id (`PI_SESSION_ID` only carries the id record birth already established). Required for `entwurf_v2` control-socket dispatch and `entwurf_peers`.
20
19
 
21
- > **PATH boundary.** MCP servers are often launched by GUI/editor daemons and may not inherit the interactive shell's PATH. If `pi` works in your terminal but an external-host `entwurf_v2` spawn-bg resume fails with `spawn pi ENOENT`, pass a full PATH in the MCP server `env`, set `ENTWURF_BRIDGE_ENV_FILE` to a small shell file that exports PATH, or point the host at a wrapper that can find `pi`. `start.sh` sources only the explicit `ENTWURF_BRIDGE_ENV_FILE`; it never reads personal dotfiles automatically.
20
+ > **PATH boundary.** MCP servers are often launched by GUI/editor daemons and may not inherit the interactive shell's PATH. No `entwurf_v2` rail launches a process, so this does not affect delivery — but `entwurf_fresh_call` and `entwurf_resume_call` do open a fixed runtime. If that runtime works in your terminal but an external-host call fails with `spawn pi ENOENT` or `spawn claude ENOENT`, pass a full PATH in the MCP server `env`, set `ENTWURF_BRIDGE_ENV_FILE` to a small shell file that exports PATH, or point the host at a wrapper that can find the runtime. `start.sh` sources only the explicit `ENTWURF_BRIDGE_ENV_FILE`; it never reads personal dotfiles automatically.
22
21
 
23
22
  Example env file:
24
23
 
@@ -46,7 +45,7 @@ External/meta-session semantics:
46
45
 
47
46
  - `entwurf_v2` from a plain external host is **refused by default** (no authoritative sender — #50 C4). With the explicit `ENTWURF_BRIDGE_ALLOW_ANONYMOUS_SENDER=1` hatch it delivers with `origin: "external-mcp"` / `replyable: false`; there is still no reply address.
48
47
  - `entwurf_v2` from a trusted meta-session delivers with `origin: "meta-session"`, and `replyable` is **derived from that sender's own rail — not granted by being trusted**: a self-fetch sender (Claude Code) is replyable only while its receiver is live and armed, and a native-push sender (Antigravity) only while its adapter probe finds the live conversation. Identity survives either way; only `replyable` drops to `false`. When it is `true`, `wants_reply: true` is allowed and the receiver can reply to the sender's garden id.
49
- - `entwurf_v2` with `intent: "owned-outcome"` to a dormant pi target needs `pi` on PATH (it spawns a `pi --entwurf-control` resume child); async completion followUp requires a replyable pi control-socket caller.
48
+ - `entwurf_v2` never launches a process, so no delivery path needs `pi` on PATH. A dormant pi target is refused as `dormant-fire-forget-unsupported`: the hidden background resume that used to answer there was withdrawn under the visible-first rule, and re-opening the session is the separate `entwurf_resume_call` verb — which DOES need `pi` on PATH, because it starts one.
50
49
  - `entwurf_self` returns the same authoritative identity for pi sessions **and** trusted meta-sessions. A plain external host with no pi env and no trusted sender marker still fails because there is no reply address to report.
51
50
 
52
51
  #### Claude Code
@@ -114,7 +113,7 @@ entwurf doctor-agy-hooks
114
113
 
115
114
  The three adapters deliberately own different atoms:
116
115
 
117
- - bridge: one `entwurf-bridge` server in `~/.gemini/config/mcp_config.json`, plus one narrow permission string per tool the normal agy workflow calls — `mcp(entwurf-bridge/entwurf_v2)`, `mcp(entwurf-bridge/entwurf_peers)`, `mcp(entwurf-bridge/entwurf_self)` — in `~/.gemini/antigravity-cli/settings.json`. agy defaults every `mcp` action to Ask, so a tool that ships without its own rule stops for a y/n on every call; `entwurf_inbox_read` is deliberately not granted (native-push has no inbox) and neither is the manual `entwurf_register_native` fallback;
116
+ - 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, and neither are `entwurf_fresh_call` / `entwurf_resume_call` (both launch into the caller's own tmux session, which an agy conversation is not);
118
117
  - statusline: the complete `statusLine` subtree pointing at the bare stable bin `entwurf-agy-statusline`;
119
118
  - hooks: one named `PreInvocation` hook pointing at the bare stable bin `entwurf-agy-imprint`.
120
119
 
@@ -11,7 +11,7 @@ only on Linux because its strict live-owner join uses `/proc`.
11
11
  | Node | **`>=24.0.0`** | package and bridge runtime |
12
12
  | npm | bundled with Node | package installation |
13
13
  | entwurf | `@junghanacs/entwurf` | all lanes |
14
- | pi | optional, `@earendil-works/pi-coding-agent >=0.83.0 <0.84` | ACP provider, control sockets, spawn-bg resume |
14
+ | pi | optional, `@earendil-works/pi-coding-agent >=0.84.1 <0.85` | ACP provider, control sockets |
15
15
  | Claude Code | optional, **`>=2.1.217`** — the exec-form hook floor | Claude ACP auth/runtime and mailbox-backed native citizen |
16
16
  | Antigravity `agy` | optional, operator-installed and authenticated | native-push citizen |
17
17
  | Cortex Code | optional, operator-installed and authenticated | Cortex ACP backend |
@@ -49,14 +49,14 @@ npx entwurf check-bridge
49
49
  ```
50
50
 
51
51
  `check-bridge` is auth-free. It proves the installed prebuilt MCP server boots and
52
- lists the five garden tools; it does not prove a backend model turn or native hook.
52
+ lists the seven garden tools; it does not prove a backend model turn or native hook.
53
53
 
54
54
  ## 2. Optional pi adapter / ACP plugin
55
55
 
56
56
  Install the exact release floor, then wire the project:
57
57
 
58
58
  ```bash
59
- npm install -g @earendil-works/pi-coding-agent@0.83.0
59
+ npm install -g @earendil-works/pi-coding-agent@0.84.1
60
60
  pi --version
61
61
 
62
62
  cd ~/entwurf-smoke
@@ -64,10 +64,10 @@ entwurf install .
64
64
  pi -e "$(npm root -g)/@junghanacs/entwurf" --list-models entwurf
65
65
  ```
66
66
 
67
- The supported range is `>=0.83.0 <0.84`. It is a hard minimum: installing this
68
- release onto a 0.82.x pi host upgrades the runtime rather than keeping the older
67
+ The supported range is `>=0.84.1 <0.85`. It is a hard minimum: installing this
68
+ release onto a 0.83.x pi host upgrades the runtime rather than keeping the older
69
69
  minor. A host using only the external MCP bridge can skip pi until it needs a
70
- control socket or dormant `owned-outcome` resume.
70
+ control socket; no delivery rail launches a pi process.
71
71
 
72
72
  For daily garden-native pi sessions:
73
73
 
@@ -174,7 +174,8 @@ and exit-code contract is [fresh-cut-policy.md](./fresh-cut-policy.md).
174
174
  ## 7. Release acceptance versus host acceptance
175
175
 
176
176
  - `entwurf check-bridge`: installed MCP bytes boot; no backend auth.
177
- - `pnpm check`: source deterministic floor; maintainer checkout only.
177
+ - `pnpm check` / `pnpm run check:full`: tiered source deterministic floors (everyday
178
+ core / full candidate floor); maintainer checkout only.
178
179
  - `check-install-container`: checkout-invisible Linux package-consumer shape using
179
180
  fixtures; not a native lifecycle proof.
180
181
  - `doctor-meta-bridge`: one installed real Claude host, only with a new live session.
@@ -11,7 +11,7 @@
11
11
  * Currently exposed tools (scope is deliberately narrow — anything that can live
12
12
  * as a local skill should live as a skill, not here):
13
13
  * - entwurf_v2 — canonical delivery surface for existing garden citizens; the decider
14
- * chooses live control-socket send / dormant spawn-bg resume / meta-mailbox.
14
+ * chooses live control-socket send / meta-mailbox enqueue / native-push.
15
15
  * - entwurf_peers — entwurf fact surface: garden citizens (meta-records) with liveness +
16
16
  * diagnostics (#50 C4: record-less sockets surface THERE, never as identity).
17
17
  * Brain = pi-extensions/lib/entwurf-fact-provider (listEntwurfFacts) +
@@ -24,6 +24,18 @@
24
24
  * garden record may call this — so the surface is "drain the inbox you were
25
25
  * pointed at", not "drain your own". A rung doorbell is a wake attempt;
26
26
  * this read is the receipt.
27
+ * - entwurf_register_native — explicit/manual fallback binding an ALREADY-RUNNING native
28
+ * conversation (antigravity) to a garden id. Never a spawn.
29
+ * - entwurf_fresh_call — open ONE fresh visible sibling in the operator's own tmux session;
30
+ * returns a LAUNCH receipt only, and the new address arrives later as the
31
+ * sender envelope of the sibling's nonce callback.
32
+ * - entwurf_resume_call — reopen ONE DORMANT pi citizen under its OWN garden id in a visible
33
+ * window; target-only, runs no turn, LAUNCH and OBSERVATION receipts stay
34
+ * apart.
35
+ *
36
+ * That list is the WHOLE public surface — seven verbs — and `check-entwurf-bridge-boot`
37
+ * (G1f) holds it as an exact set on the runtime tools/list, so a verb added or dropped
38
+ * here without a decision is red rather than merely undocumented.
27
39
  *
28
40
  * Removed from this v2-only surface: legacy MCP `entwurf`, `entwurf_resume`, and
29
41
  * `entwurf_send`. Use `entwurf_v2` for delivery to existing garden citizens.
@@ -57,8 +69,11 @@ import { renderEntwurfPeers } from "../../../pi-extensions/lib/entwurf-peers-ren
57
69
  import { computeSelfAddressability } from "../../../pi-extensions/lib/entwurf-self-address.js";
58
70
  import { nativePushSupported } from "../../../pi-extensions/lib/entwurf-v2-contract.js";
59
71
  import { runAndRenderEntwurfV2FromSurface } from "../../../pi-extensions/lib/entwurf-v2-surface.js";
72
+ import { makeVisibleResumeDeps, renderVisibleResume, visibleResume, } from "../../../pi-extensions/lib/entwurf-v2-visible-resume.js";
60
73
  import { probeNativeSenderAlive, resolveTrustedMetaSenderIdentity, } from "../../../pi-extensions/lib/meta-sender-identity.js";
61
74
  import { defaultMetaMailboxDir, defaultMetaSessionsDir, makeStoreRecordReader, readActiveStoreEntries, readMetaInbox, readMetaReceiverMarker, } from "../../../pi-extensions/lib/meta-session.js";
75
+ import { freshCall, renderFreshCall } from "../../../pi-extensions/lib/mux-fresh-call.js";
76
+ import { RESUME_CALL_REJECT_HINT, resumeCall } from "../../../pi-extensions/lib/mux-resume-call.js";
62
77
  import { registerNativeConversation } from "../../../pi-extensions/lib/native-push/register.js";
63
78
  const HOME = os.homedir();
64
79
  // Directory SOURCE is this adapter's own policy — the bridge honours an explicit
@@ -286,13 +301,12 @@ function abbreviateHomeMcp(cwd) {
286
301
  }
287
302
  // entwurf_v2 — the unified v2 dispatch verb (0.11 step 5d-3b). It hands the
288
303
  // target + intent to the 5b decider, which chooses the transport (live
289
- // control-socket send / spawn-bg resume / meta-mailbox enqueue / native-push
290
- // direct injection) and reports one outcome. The per-target lock is NOT taken by
291
- // every rail: the decider locks only a control-socket-domain dispatch, which
292
- // covers the live send AND the dormant cell's spawn-bg resume; the mailbox and
293
- // native-push branches carry `lock: null` (entwurf-v2-decider.ts). It runs
304
+ // control-socket send / meta-mailbox enqueue / native-push direct injection) and
305
+ // reports one outcome. The per-target lock is NOT taken by every rail: the decider
306
+ // locks only a control-socket-domain dispatch; the mailbox and native-push branches
307
+ // carry `lock: null` (entwurf-v2-decider.ts). It runs
294
308
  // IN-PROCESS here (the same production runner pi-native uses) — NOT a delegating
295
- // RPC — so control, mailbox, native-push, AND spawn-bg all flow through
309
+ // RPC — so control, mailbox and native-push all flow through
296
310
  // `runEntwurfV2`. The sender envelope is
297
311
  // `buildSendSenderEnvelope()` verbatim (origin/replyable as resolved) — v2 does
298
312
  // NOT gate on replyability (a `wants_reply` from an external/non-replyable caller
@@ -300,11 +314,10 @@ function abbreviateHomeMcp(cwd) {
300
314
  // sender replyability).
301
315
  server.tool("entwurf_v2", "CANONICAL DELIVERY SURFACE for garden ids: message, reply, or hand off to whoever an id names. The id " +
302
316
  "alone does not say which rail that citizen answers on. Give target + intent; the decider picks transport " +
303
- "from liveness (live socket citizen → control-socket send; dormant socket citizen → spawn-bg resume; " +
304
- "deliverable self-fetch citizen → meta-bridge mailbox; probe-alive native-push citizen → direct injection " +
305
- "into its conversation) and reports ONE outcome (delivered / rejected / lock-retained / " +
306
- "delivered-but-lock-dirty). EXISTING targets only; discover with entwurf_peers. INTENT picking wrong is " +
307
- "rejected, never auto-converted. A peer entwurf_peers shows as liveness=alive → fire-and-forget. A " +
317
+ "from liveness (live socket citizen → control-socket send; deliverable self-fetch citizen → meta-bridge " +
318
+ "mailbox; probe-alive native-push citizen → direct injection into its conversation) and reports ONE " +
319
+ "outcome (delivered / rejected / delivered-but-lock-dirty). EXISTING targets only; discover with " +
320
+ "entwurf_peers. A peer entwurf_peers shows as liveness=alive fire-and-forget. A " +
308
321
  "citizen with NO socket liveness (liveness=unsupported) is ALSO fire-and-forget — unsupported means only " +
309
322
  '"no control-socket probe" — and the decider picks its own rail: a self-fetch backend (e.g. Claude Code) ' +
310
323
  "gets the mailbox, a native-push backend (e.g. Antigravity) gets direct injection and has NO mailbox at " +
@@ -312,24 +325,25 @@ server.tool("entwurf_v2", "CANONICAL DELIVERY SURFACE for garden ids: message, r
312
325
  "session, or a backend with no adapter here (e.g. codex), is mailbox-undeliverable, not queued for an " +
313
326
  "inbox nobody drains. The native-push probe is 3-valued: alive → injected; dead → " +
314
327
  "native-push-target-dead; indeterminate → native-push-probe-indeterminate (unestablished ≠ gone). " +
315
- "owned-outcome wakes a DORMANT socket-domain citizen by spawn-bg resume ONLY live target → " +
316
- "owned-live-no-autosend, self-fetch backend-liveness-unsupported, native-push " +
317
- "native-push-no-resume-authority. LOCK: taken for a control-socket-DOMAIN dispatch the live send AND " +
318
- "the dormant cell's spawn-bg resume, a separate transport that still runs under that domain's lock. The " +
328
+ "DORMANT IS UNREACHABLE: a socket-domain citizen that is not running gets dormant-fire-forget-unsupported " +
329
+ " same receiver rule as the mailbox, no active drainer means no delivery. " +
330
+ "The intent that used to answer there, owned-outcome, resumed it by launching a hidden background child " +
331
+ "and was withdrawn under the visible-first rule re-open the session yourself, then dispatch again. " +
332
+ "LOCK: taken for a control-socket-DOMAIN dispatch. The " +
319
333
  "mailbox and native-push rails are lock-free — deliverability and the adapter probe guard them. mode " +
320
334
  "applies to a CONTROL-SOCKET send only; other plans carry no mode. wants_reply rides every rail. message " +
321
335
  "caps at 16000 chars; send an artifact path + digest for more.", {
322
336
  target: z.string().min(1).describe("Target garden id (use entwurf_peers to discover)"),
323
337
  intent: z
324
- .enum(["fire-and-forget", "owned-outcome"])
338
+ .enum(["fire-and-forget"])
325
339
  .describe("fire-and-forget = send/reply/hand-off to a LIVE socket target or to any citizen with no " +
326
340
  "socket liveness — the decider picks its rail, and a rail can also REJECT (self-fetch → " +
327
341
  "mailbox when deliverable, else mailbox-undeliverable; native-push → alive: direct injection, " +
328
342
  "dead: native-push-target-dead, indeterminate: native-push-probe-indeterminate); set " +
329
- "wants_reply for an answer. owned-outcome = " +
330
- "wake a DORMANT socket-domain citizen via spawn-bg resume ONLY on a live target rejected " +
331
- "as owned-live-no-autosend, on self-fetch as backend-liveness-unsupported, on native-push as " +
332
- "native-push-no-resume-authority, and never auto-converted"),
343
+ "wants_reply for an answer. This is the ONLY intent: owned-outcome, which resumed a dormant " +
344
+ "citizen by launching a hidden background child, was withdrawn under the visible-first rule. " +
345
+ "It is not selectable, not deprecated-but-tolerated; a dormant citizen is currently " +
346
+ "unreachable by this verb and rejects as dormant-fire-forget-unsupported."),
333
347
  message: z
334
348
  .string()
335
349
  .min(1)
@@ -339,8 +353,8 @@ server.tool("entwurf_v2", "CANONICAL DELIVERY SURFACE for garden ids: message, r
339
353
  .enum(["steer", "follow_up"])
340
354
  .optional()
341
355
  .describe("Injection style for a CONTROL-SOCKET send only: steer (interrupt the current turn) or " +
342
- "follow_up (queue after it). The mailbox, native-push, and spawn-bg plans carry no mode, so it " +
343
- "has no effect on those rails."),
356
+ "follow_up (queue after it). The mailbox and native-push plans carry no mode, so it has no " +
357
+ "effect on those rails."),
344
358
  wants_reply: z.boolean().optional().describe("Human-conversation reply hint (default false)"),
345
359
  }, async ({ target, intent, message, mode, wants_reply }) => {
346
360
  try {
@@ -348,8 +362,9 @@ server.tool("entwurf_v2", "CANONICAL DELIVERY SURFACE for garden ids: message, r
348
362
  // + the mailbox body sender share one envelope. No replyability gate (see above).
349
363
  const sender = await buildSendSenderEnvelope();
350
364
  const rendered = await runAndRenderEntwurfV2FromSurface({ target, intent, message, mode, wants_reply },
351
- // agentDir / prefixRoots intentionally omitted: runAndRenderEntwurfV2FromSurface falls
352
- // back to the ENTWURF_PREFIX_ROOTS env SSOT for prefixRoots (5d-4); agentDir stays undefined.
365
+ // No trust-preflight inputs are passed, and none exist to pass: the preflight on this
366
+ // path guarded the resume verdict and left with `owned-outcome`. `senderProvider` is
367
+ // the whole options surface now (see the pi-native surface for the same note).
353
368
  { senderProvider: () => sender });
354
369
  return rendered.isError ? textErr(rendered.text) : textOk(rendered.text);
355
370
  }
@@ -432,7 +447,9 @@ server.tool("entwurf_peers", "List the entwurf fact surface: garden citizens (fr
432
447
  "`unsupported` for a backend with no control-socket probe such as claude-code); the dispatch " +
433
448
  "decision (send vs resume) is computed LATER by the entwurf_v2 contract from that liveness, " +
434
449
  "not here — this surface carries no per-row routing field, so do not read a transport off a " +
435
- "row. In particular `unsupported` does NOT mean mailbox: it means this backend has no " +
450
+ "row. A `dead` row is a REPORTED FACT and nothing more: that citizen is dormant and is " +
451
+ "currently unreachable by any verb, so listing it grants no action — appearing here is not " +
452
+ "an invitation to dispatch. In particular `unsupported` does NOT mean mailbox: it means this backend has no " +
436
453
  "control-socket probe. Which rail it answers on is a capability the decider resolves at dispatch " +
437
454
  "time, and there are THREE possible answers, not two — a self-fetch mailbox (only while that " +
438
455
  "mailbox is deliverable), native-push direct injection (only while its adapter probe is alive), " +
@@ -442,8 +459,8 @@ server.tool("entwurf_peers", "List the entwurf fact surface: garden citizens (fr
442
459
  "is not a departed host. So " +
443
460
  "`unsupported` does not promise reachability either: a record whose backend has no adapter on " +
444
461
  "this lane resolves to that reject. " +
445
- "Note: this is the *active* world. It is NOT a fresh-sibling creation surface; pass an " +
446
- "existing garden id to entwurf_v2.", {}, async () => {
462
+ "Note: this is the *active* world and it is facts-only it creates nothing. Pass an " +
463
+ "existing garden id to entwurf_v2; to open a NEW sibling use entwurf_fresh_call.", {}, async () => {
447
464
  try {
448
465
  // Meta-store axis: entries WITH their kind (ENOENT = fresh install = empty; any
449
466
  // other readdir failure is a real error, not a silent empty). The name-only
@@ -535,6 +552,109 @@ server.tool("entwurf_register_native", "Register an ALREADY-RUNNING native conve
535
552
  return textErr(`entwurf_register_native error: ${err instanceof Error ? err.message : String(err)}`);
536
553
  }
537
554
  });
555
+ // The caller identity for THIS surface comes from the same authoritative resolution
556
+ // `entwurf_self` uses — BOTH of its certified paths: the pi carrier this child inherited
557
+ // (PI_SESSION_ID + PI_AGENT_ID, which a record minted at birth), or a trusted meta-sender marker
558
+ // the native host's own hook wrote. Neither is a guess and neither is deprecated here.
559
+ //
560
+ // What is refused is a caller-supplied id. The measured failure mode is exactly that: asked for
561
+ // its own garden id, a fresh cell answered with its uuidv7 `PI_SESSION_ID` value read out of the
562
+ // environment by an MCP server it had spawned itself — confidently, and wrong. A sibling launched
563
+ // against that answer would call home to a garden id nobody holds.
564
+ server.tool("entwurf_fresh_call", "Open ONE fresh visible sibling in the operator's own tmux session and hand it a first task. Two fixed " +
565
+ "backends only: pi, claude-code. The sibling's FIRST action is a callback to you carrying a nonce, and the " +
566
+ "sender envelope of that callback is its garden id — that is how you learn the address of something that " +
567
+ "did not exist a moment ago. This returns a LAUNCH receipt (tmux window/pane plus that nonce) and nothing " +
568
+ "else: it does NOT mean the runtime started, the first turn ran, or the task was delivered. Nothing polls " +
569
+ "for the callback; if it never arrives the window is visible and can be read directly. For EXISTING " +
570
+ "citizens use entwurf_v2 — this tool only creates, and entwurf_peers only reports. Model is REQUIRED and " +
571
+ "is passed to the chosen runtime CLI (`provider/model` for pi; model id/alias for Claude Code); there are no " +
572
+ "arbitrary command/cwd/env knobs. Do not put secrets in the task — model and task argv are visible to " +
573
+ "same-user processes on this host. Requires that this agent itself runs " +
574
+ "inside tmux: without a pane anchor there is no session to open a sibling beside.", {
575
+ backend: z
576
+ .enum(["pi", "claude-code"])
577
+ .describe("Which fixed runtime to open. Only these two; there is no arbitrary command."),
578
+ model: z
579
+ .string()
580
+ .min(1)
581
+ .max(200)
582
+ // The `\[` below is USELESS TO JS AND LOAD-BEARING TO THE HOST. zod emits this source
583
+ // text verbatim as the JSON Schema `pattern`, and the host's tool-schema validator is a
584
+ // Rust regex engine that rejects an unescaped `[` inside a character class as "unclosed
585
+ // character class" — a 400 on tools/list that stops EVERY Claude session from opening
586
+ // (7-M-fix). Biome's safe fix removes it; do not accept that fix. The suppression must
587
+ // stay on the line DIRECTLY above the expression, or it attaches to nothing and reads
588
+ // as an unused suppression while the escape goes back to being fixable.
589
+ // biome-ignore lint/complexity/noUselessEscapeInRegex: emitted to a Rust regex validator, see above
590
+ .regex(/^[A-Za-z0-9][A-Za-z0-9._/:\[\]-]*$/)
591
+ .describe("Required runtime model: canonical provider/model for pi, or a Claude Code model id/alias."),
592
+ task: z
593
+ .string()
594
+ .min(1)
595
+ .max(16000)
596
+ .describe("What the sibling should do after it calls you back. Plain instructions; no secrets (see the tool description)."),
597
+ }, async ({ backend, model, task }) => {
598
+ let callerGardenId = null;
599
+ try {
600
+ const self = await buildAuthoritativeSelfEnvelope();
601
+ callerGardenId = self.envelope.sessionId;
602
+ }
603
+ catch (err) {
604
+ // ONE error is a legitimate answer here: this host has no authoritative identity at all
605
+ // (no pi carrier inherited, no trusted marker written), so the sibling would have nowhere
606
+ // to call home. That normalises to the named refusal.
607
+ //
608
+ // Everything else — a corrupt record, an unreadable store, a broken marker — keeps its
609
+ // own diagnosis and fails loud. Folding those into "you are anonymous" would relabel a
610
+ // store defect as a wiring choice, and the operator would go looking in the wrong place.
611
+ if (!(err instanceof EntwurfEnvelopeWiringError))
612
+ throw err;
613
+ callerGardenId = null;
614
+ }
615
+ try {
616
+ const rendered = renderFreshCall(freshCall({ backend, model, task, callerGardenId }));
617
+ return rendered.isError ? textErr(rendered.text) : textOk(rendered.text);
618
+ }
619
+ catch (err) {
620
+ return textErr(`entwurf_fresh_call error: ${err instanceof Error ? err.message : String(err)}`);
621
+ }
622
+ });
623
+ // The composition root for a visible resume: this surface is the only layer that may know BOTH
624
+ // halves, so it is where the mux launcher is handed to the v2 composition. `entwurf-v2-visible-
625
+ // resume` cannot open a window on its own and `mux-resume-call` cannot read a record — that is
626
+ // the import fence in docs/mux-launch-rail.md §11, expressed as a function argument.
627
+ const visibleResumeLaunch = (input) => {
628
+ const launched = resumeCall({ cwd: input.cwd, runtimeArgs: input.runtimeArgs });
629
+ return launched.ok
630
+ ? { ok: true, handle: launched.receipt }
631
+ : { ok: false, reason: launched.reason, hint: RESUME_CALL_REJECT_HINT[launched.reason] };
632
+ };
633
+ server.tool("entwurf_resume_call", "Reopen ONE DORMANT pi citizen under its OWN garden id, in a visible window in the operator's own tmux " +
634
+ "session. The record supplies everything — which transcript, which model, which provider, which cwd — so " +
635
+ "the only input is the target id: there is no model override, no task, and no prompt. This runs NO turn: " +
636
+ "the window comes back with the conversation and waits, and talking to it is still entwurf_v2 " +
637
+ "fire-and-forget on the socket this call stands up. You get TWO receipts and they mean different things: a " +
638
+ "LAUNCH receipt (tmux made a window and was asked to start pi) and an OBSERVATION receipt (the control " +
639
+ "socket answered under the same id, or resume-unobserved). Unobserved is a real outcome, not an error to " +
640
+ "retry — the window is visible, so read it. A citizen that is already LIVE is refused: address it with " +
641
+ "entwurf_v2 instead. Only pi citizens have a same-id resume, because only they stand a control socket up. " +
642
+ "Requires that this agent itself runs inside tmux.", {
643
+ target: z
644
+ .string()
645
+ .min(1)
646
+ .regex(/^\d{8}T\d{6}-[0-9a-f]{6}$/)
647
+ .describe("Garden id of the DORMANT pi citizen to reopen (discover with entwurf_peers)."),
648
+ }, async ({ target }) => {
649
+ try {
650
+ const result = await visibleResume(target, makeVisibleResumeDeps(visibleResumeLaunch));
651
+ const rendered = renderVisibleResume(result);
652
+ return rendered.isError ? textErr(rendered.text) : textOk(rendered.text);
653
+ }
654
+ catch (err) {
655
+ return textErr(`entwurf_resume_call error: ${err instanceof Error ? err.message : String(err)}`);
656
+ }
657
+ });
538
658
  // ============================================================================
539
659
  // Main
540
660
  // ============================================================================