@junghanacs/entwurf 0.12.10 → 0.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/AGENTS.md +6 -3
  2. package/BASELINE.md +56 -163
  3. package/CHANGELOG.md +35 -0
  4. package/CONTRIBUTING.md +4 -2
  5. package/DELIVERY.md +117 -261
  6. package/README.md +74 -399
  7. package/VERIFY.md +59 -85
  8. package/demo/README.md +1 -1
  9. package/docs/acp-backend-rail.md +231 -0
  10. package/docs/external-mcp-host.md +132 -0
  11. package/docs/fresh-cut-policy.md +99 -0
  12. package/docs/setup-clean-host.md +123 -328
  13. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +21 -48
  14. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/acp-client.js +12 -0
  15. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/backend-adapter.js +155 -11
  16. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/config.js +16 -4
  17. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/engraving.js +42 -1
  18. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/models.js +66 -7
  19. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/overlay.js +190 -5
  20. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/tool-surface.js +12 -4
  21. package/mcp/entwurf-bridge/src/index.ts +21 -48
  22. package/package.json +14 -9
  23. package/pi/settings.reference.json +1 -1
  24. package/pi-extensions/acp-provider.ts +20 -10
  25. package/pi-extensions/entwurf-control.ts +20 -49
  26. package/pi-extensions/lib/acp/acp-client.ts +22 -0
  27. package/pi-extensions/lib/acp/augment.ts +99 -2
  28. package/pi-extensions/lib/acp/backend-adapter.ts +190 -14
  29. package/pi-extensions/lib/acp/backend.ts +310 -37
  30. package/pi-extensions/lib/acp/config.ts +19 -5
  31. package/pi-extensions/lib/acp/engraving.ts +46 -2
  32. package/pi-extensions/lib/acp/event-mapper.ts +26 -16
  33. package/pi-extensions/lib/acp/models.ts +69 -7
  34. package/pi-extensions/lib/acp/overlay.ts +234 -5
  35. package/pi-extensions/lib/acp/tool-surface.ts +12 -4
  36. package/prompts/engraving.md +13 -5
  37. package/run.sh +386 -119
  38. package/scripts/check-acp-carrier-augment.ts +246 -10
  39. package/scripts/check-acp-cortex.ts +668 -0
  40. package/scripts/check-acp-prompt-lifecycle.ts +565 -0
  41. package/scripts/check-acp-provider-surface.ts +50 -6
  42. package/scripts/check-acp-sdk-surface.ts +60 -21
  43. package/scripts/check-acp-session-reuse.ts +64 -1
  44. package/scripts/check-acp-stop-reason.ts +342 -0
  45. package/scripts/check-entwurf-v2-surface.ts +41 -0
  46. package/scripts/check-gate-qualification.ts +7 -2
  47. package/scripts/check-probe-cli-shim.ts +879 -0
  48. package/scripts/check-probe-ordering.ts +2482 -0
  49. package/scripts/check-release-gate-outcomes.ts +332 -0
  50. package/scripts/check-shell-quote.ts +4 -4
  51. package/scripts/fixtures/probe-cli-shim +20 -0
  52. package/scripts/fixtures/probe-mcp-server.ts +168 -12
  53. package/scripts/lib/live-skip.ts +33 -0
  54. package/scripts/lib/probe-acp-turn.ts +228 -0
  55. package/scripts/lib/probe-cli-shim.ts +464 -0
  56. package/scripts/lib/probe-cli-target.ts +165 -0
  57. package/scripts/lib/probe-event-log.ts +383 -0
  58. package/scripts/lib/probe-verdict.ts +1213 -0
  59. package/scripts/lib/step-outcome.sh +88 -0
  60. package/scripts/mutants/acp-augment.json +106 -0
  61. package/scripts/mutants/acp-cortex.json +196 -0
  62. package/scripts/mutants/acp-prompt-lifecycle.json +100 -0
  63. package/scripts/mutants/acp-stop-reason.json +80 -0
  64. package/scripts/mutants/probe-ordering.json +1054 -0
  65. package/scripts/mutants/release-gate.json +105 -0
  66. package/scripts/mutants/v2-surface.json +26 -15
  67. package/scripts/smoke-acp-bundled-mcp-live.ts +4 -3
  68. package/scripts/smoke-acp-carrier-augment-live.ts +2 -2
  69. package/scripts/smoke-acp-cortex-live.ts +386 -0
  70. package/scripts/smoke-acp-long-turn-live.ts +185 -0
  71. package/scripts/smoke-acp-mcp-live.ts +2 -2
  72. package/scripts/smoke-acp-memory-containment-live.ts +2 -2
  73. package/scripts/smoke-acp-ordering-probe-live.ts +848 -0
  74. package/scripts/smoke-acp-overlay-live.ts +2 -2
  75. package/scripts/smoke-acp-provider-live.ts +2 -2
  76. package/scripts/smoke-acp-raw-turn-live.ts +3 -3
  77. package/scripts/smoke-acp-session-reuse-live.ts +2 -2
  78. package/scripts/smoke-acp-skill-live.ts +2 -2
  79. package/scripts/smoke-acp-socket-citizen-live.ts +4 -3
  80. package/scripts/smoke-acp-v2-send-live.ts +4 -3
  81. package/scripts/smoke-agy-native-push-live.ts +4 -3
  82. package/scripts/smoke-claude-native-resume-live.sh +13 -3
  83. package/scripts/smoke-entwurf-chain-live.ts +352 -0
  84. package/scripts/smoke-entwurf-v2-matrix-live.ts +2 -2
  85. package/scripts/smoke-entwurf-v2-spawn-resume-live.ts +2 -4
  86. package/scripts/smoke-meta-install-state.sh +4 -0
@@ -10,10 +10,18 @@
10
10
  // if pi has excluded a built-in the backend will still expose, the declared
11
11
  // surface diverges from the actual one — we fail fast rather than lie.
12
12
  //
13
- // Claude-only scope (NEXT §스코프). The function keeps a `backend` field so the
14
- // honest-divergence logic stays explicit and the matrix gate can exercise both
15
- // the "claude narrows via tools" and "native always exposes" branches, but the
16
- // only backend this lane supplies is claude.
13
+ // The module's TWO jobs have different backend scopes do not collapse them:
14
+ // - `buildClaudeSessionMeta` is claude-only by CONSTRUCTION, with exactly one
15
+ // caller (`claudeAdapter.buildSessionMeta`). Cortex exposes no
16
+ // `_meta.systemPrompt` and no tools/permission declaration surface (native
17
+ // tools + its own overlay mcp.json projection), so its adapter returns
18
+ // `undefined` and that function is never reached for a cortex turn.
19
+ // - `assertExcludeToolsHonored` is BACKEND-INVARIANT: backend.ts runs it for
20
+ // every adapter before any spawn, passing `backend: adapter.backend` and the
21
+ // resolved `config.tools`. A cortex turn DOES enter this module through it.
22
+ // That split is why the `backend` field exists: the honest-divergence logic stays
23
+ // explicit and the matrix gate can exercise both the "claude narrows via tools"
24
+ // and "native always exposes" branches.
17
25
  //
18
26
  // Carrier guard (NEXT §S2-scout 핀1): `buildClaudeSessionMeta` only attaches
19
27
  // `_meta.systemPrompt` when a caller passes one. The shipped S2d path passes the
@@ -344,54 +344,27 @@ function abbreviateHomeMcp(cwd: string): string {
344
344
  // sender replyability).
345
345
  server.tool(
346
346
  "entwurf_v2",
347
- "CANONICAL DELIVERY SURFACE for garden ids. When you have a garden id and want to reach " +
348
- "whoever it names message / reply / hand-off use THIS verb. A garden id alone does " +
349
- "not tell you which rail that citizen answers on a live socket citizen, a dormant one, a " +
350
- "mailbox-backed self-fetch session, or a native-push session and entwurf_v2 is the one " +
351
- 'surface that reads that for you and routes correctly (so "when unsure which transport, use ' +
352
- 'entwurf_v2"). You give the target ' +
353
- "garden id + your intent; the decider picks the transport from the target's liveness " +
354
- "(live socket citizen → control-socket send; dormant socket citizen → spawn-bg resume; active " +
355
- "deliverable self-fetch citizen meta-bridge mailbox; probe-alive native-push citizen direct " +
356
- "injection into its live conversation) under the v2 lock policy, and reports ONE outcome " +
357
- "(delivered / rejected / lock-retained / delivered-but-lock-dirty). LOCK POLICY (do not " +
358
- "over-generalize it): the per-target lock is taken for a control-socket-DOMAIN dispatch, which is " +
359
- "both the live send AND the dormant cell's spawn-bg resume spawn-bg is a separate relaunch " +
360
- "transport yet it still runs under that domain's lock. The mailbox and native-push rails are " +
361
- "lock-free: the mailbox is guarded instead by active-receiver deliverability, and native-push by " +
362
- "its adapter probe. The decider not the " +
363
- "caller chooses the transport. Note: entwurf_v2 dispatches to EXISTING targets; " +
364
- "brand-new sibling creation is deferred to a later v2 lane. " +
365
- "CHOOSING INTENT (read thispicking wrong is rejected, never auto-fixed): to message / " +
366
- "reply / hand off a peer that entwurf_peers shows as liveness=alive (a live socket citizen, " +
367
- "currently backend pi) use intent: fire-and-forget it routes to the live control-socket; set " +
368
- "wants_reply:true if you need an answer (wants_reply is NOT owned-outcome). Replies to a citizen " +
369
- "with NO socket liveness (liveness=unsupported) are ALSO fire-and-forget, and the decider picks " +
370
- "that citizen's own rail: a self-fetch backend (e.g. Claude Code) gets the meta-bridge mailbox, " +
371
- "while a native-push backend (e.g. Antigravity) gets direct injection into its live conversation " +
372
- "and has NO mailbox at all — do not assume mailbox semantics for every unsupported citizen. A " +
373
- "native-push target IS measured by its own adapter probe, and that probe is THREE-valued, so " +
374
- "the send is never silently queued: alive → injected; dead → rejected as " +
375
- "native-push-target-dead; indeterminate → rejected as native-push-probe-indeterminate. " +
376
- "Those two rejects stay distinct because 'we could not establish it' is not 'it is gone' — " +
377
- "collapsing them reports a guess as a fact. THERE IS A " +
378
- "THIRD RESULT, so do not read liveness=unsupported as 'reachable by some rail': the mailbox path " +
379
- "delivers only to a DELIVERABLE citizen (a self-fetch backend whose receiver is live and armed), " +
380
- "so a terminated Claude Code session — and any record whose backend has no adapter on this lane, " +
381
- "e.g. codex — is rejected as mailbox-undeliverable rather than queued into an inbox nobody drains. " +
382
- "unsupported means only 'this backend has no control-socket probe'. " +
383
- "owned-outcome is ONLY for waking a DORMANT socket-domain citizen (spawn-bg resume, currently " +
384
- "backend pi); on a live target it is rejected as owned-live-no-autosend. Neither self-fetch nor " +
385
- "native-push has resume authority, but they reject under DIFFERENT reasons — self-fetch as " +
386
- "backend-liveness-unsupported, native-push as native-push-no-resume-authority. " +
387
- "It is NEVER auto-converted — so pick the right intent up front. " +
388
- "mode applies to a CONTROL-SOCKET send only — it is the injection style for a live pi turn, and " +
389
- "the mailbox, native-push, and spawn-bg plans carry no mode at all, so setting it for those " +
390
- "targets changes nothing (a native-push send IS live and still ignores it). wants_reply rides " +
391
- "every rail. Use entwurf_peers to discover targets. " +
392
- "Payload guidance: message hard cap 16000 chars. For larger reviews/logs, write an " +
393
- "artifact and dispatch its path plus a short digest; avoid multi-part sends because " +
394
- "mailbox doorbells may coalesce.",
347
+ "CANONICAL DELIVERY SURFACE for garden ids: message, reply, or hand off to whoever an id names. The id " +
348
+ "alone does not say which rail that citizen answers on. Give target + intent; the decider picks transport " +
349
+ "from liveness (live socket citizen control-socket send; dormant socket citizen spawn-bg resume; " +
350
+ "deliverable self-fetch citizen meta-bridge mailbox; probe-alive native-push citizen direct injection " +
351
+ "into its conversation) and reports ONE outcome (delivered / rejected / lock-retained / " +
352
+ "delivered-but-lock-dirty). EXISTING targets only; discover with entwurf_peers. INTENT — picking wrong is " +
353
+ "rejected, never auto-converted. A peer entwurf_peers shows as liveness=alive fire-and-forget. A " +
354
+ "citizen with NO socket liveness (liveness=unsupported) is ALSO fire-and-forget unsupported means only " +
355
+ '"no control-socket probe" and the decider picks its own rail: a self-fetch backend (e.g. Claude Code) ' +
356
+ "gets the mailbox, a native-push backend (e.g. Antigravity) gets direct injection and has NO mailbox at " +
357
+ "all. THERE IS A THIRD RESULT: the mailbox delivers only to a DELIVERABLE citizen, so a terminated " +
358
+ "session, or a backend with no adapter here (e.g. codex), is mailbox-undeliverable, not queued for an " +
359
+ "inbox nobody drains. The native-push probe is 3-valued: alive injected; dead " +
360
+ "native-push-target-dead; indeterminate native-push-probe-indeterminate (unestablished gone). " +
361
+ "owned-outcome wakes a DORMANT socket-domain citizen by spawn-bg resume ONLY live target → " +
362
+ "owned-live-no-autosend, self-fetch backend-liveness-unsupported, native-push " +
363
+ "native-push-no-resume-authority. LOCK: taken for a control-socket-DOMAIN dispatch the live send AND " +
364
+ "the dormant cell's spawn-bg resume, a separate transport that still runs under that domain's lock. The " +
365
+ "mailbox and native-push rails are lock-free deliverability and the adapter probe guard them. mode " +
366
+ "applies to a CONTROL-SOCKET send only; other plans carry no mode. wants_reply rides every rail. message " +
367
+ "caps at 16000 chars; send an artifact path + digest for more.",
395
368
  {
396
369
  target: z.string().min(1).describe("Target garden id (use entwurf_peers to discover)"),
397
370
  intent: z
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@junghanacs/entwurf",
3
- "version": "0.12.10",
3
+ "version": "0.13.1",
4
4
  "description": "Garden-citizen dispatch substrate and meta-bridge for Claude Code, Codex, Antigravity, and pi harnesses.",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -42,6 +42,9 @@
42
42
  "demo/demo.sh",
43
43
  "demo/demo-baseline.sh",
44
44
  "docs/setup-clean-host.md",
45
+ "docs/external-mcp-host.md",
46
+ "docs/fresh-cut-policy.md",
47
+ "docs/acp-backend-rail.md",
45
48
  "docs/assets/entwurf-hero.jpg",
46
49
  "docs/assets/entwurf-demo.gif",
47
50
  "docs/assets/entwurf-doomemacs.gif",
@@ -71,23 +74,23 @@
71
74
  "claudeCodeFloor": ">=2.1.217"
72
75
  },
73
76
  "dependencies": {
74
- "@agentclientprotocol/claude-agent-acp": "0.62.0",
77
+ "@agentclientprotocol/claude-agent-acp": "0.64.0",
75
78
  "@agentclientprotocol/sdk": "1.3.0",
76
79
  "@anthropic-ai/sdk": "0.100.1",
77
80
  "@modelcontextprotocol/sdk": "^1.12.1",
78
81
  "zod": "^3.25.0 || ^4.0.0"
79
82
  },
80
83
  "peerDependencies": {
81
- "@earendil-works/pi-ai": ">=0.82.1 <0.83",
82
- "@earendil-works/pi-coding-agent": ">=0.82.1 <0.83",
83
- "@earendil-works/pi-tui": ">=0.82.1 <0.83",
84
+ "@earendil-works/pi-ai": ">=0.83.0 <0.84",
85
+ "@earendil-works/pi-coding-agent": ">=0.83.0 <0.84",
86
+ "@earendil-works/pi-tui": ">=0.83.0 <0.84",
84
87
  "typebox": "*"
85
88
  },
86
89
  "devDependencies": {
87
90
  "@biomejs/biome": "^2.4.13",
88
- "@earendil-works/pi-ai": "0.82.1",
89
- "@earendil-works/pi-coding-agent": "0.82.1",
90
- "@earendil-works/pi-tui": "0.82.1",
91
+ "@earendil-works/pi-ai": "0.83.0",
92
+ "@earendil-works/pi-coding-agent": "0.83.0",
93
+ "@earendil-works/pi-tui": "0.83.0",
91
94
  "@types/node": "^24.3.0",
92
95
  "husky": "^9.1.7",
93
96
  "typescript": "^6.0.2"
@@ -121,6 +124,8 @@
121
124
  "check-acp-carrier-augment": "./run.sh check-acp-carrier-augment",
122
125
  "check-agy-permission-matrix": "./run.sh check-agy-permission-matrix",
123
126
  "check-gate-qualification": "./run.sh check-gate-qualification",
127
+ "check-probe-ordering": "./run.sh check-probe-ordering",
128
+ "check-probe-cli-shim": "./run.sh check-probe-cli-shim",
124
129
  "check-pack": "./run.sh check-pack",
125
130
  "check-pack-install": "./run.sh check-pack-install",
126
131
  "check-install-container": "./run.sh check-install-container",
@@ -130,7 +135,7 @@
130
135
  "format": "biome check --write .",
131
136
  "check-meta-manifest-schema": "./run.sh check-meta-manifest-schema",
132
137
  "check-package-source-routing": "./run.sh check-package-source-routing",
133
- "check": "pnpm lint && pnpm typecheck && ./run.sh check-shell-quote && ./run.sh check-install-surface && ./run.sh check-entwurf-session-identity && ./run.sh check-meta-session && ./run.sh check-meta-v3-record && ./run.sh check-mailbox-receipt-state && ./run.sh check-entwurf-capabilities && ./run.sh check-capability-bundle-reach && ./run.sh check-bridge-delivery && ./run.sh smoke-pi-attach && ./run.sh check-fresh-cut-gate && ./run.sh check-meta-mailbox-state-write && ./run.sh check-meta-receiver-marker && ./run.sh check-hook-launch-topology && ./run.sh check-meta-identity-consumers && ./run.sh check-meta-capability-source && ./run.sh check-socket-probe && ./run.sh smoke-meta-honesty && ./run.sh smoke-meta-install-state && ./run.sh check-meta-doctor-oracle && ./run.sh smoke-agy-install-state && ./run.sh check-agy-permission-matrix && ./run.sh smoke-agy-statusline-state && ./run.sh smoke-agy-hooks-state && ./run.sh smoke-pi-provider-state && ./run.sh smoke-user-scope-citizen && ./run.sh smoke-meta-prune && ./run.sh smoke-meta-keyset-guard && ./run.sh check-meta-manifest-schema && ./run.sh check-package-source-routing && ./run.sh check-model-lock && ./run.sh check-dep-versions && ./run.sh check-node-floor-coherence && ./run.sh check-claude-floor-coherence && ./run.sh check-install-preflight && ./run.sh check-pi-import-surface && ./run.sh check-env-namespace && ./run.sh check-pi-runtime-version && ./run.sh check-pi-preflight && ./run.sh check-project-trust-handler && ./run.sh check-entwurf-v2-contract && ./run.sh check-entwurf-v2-lock && ./run.sh check-entwurf-v2-decider && ./run.sh check-entwurf-v2-matrix && ./run.sh check-entwurf-v2-release && ./run.sh check-entwurf-v2-send && ./run.sh check-entwurf-v2-send-fallback && ./run.sh check-entwurf-v2-mailbox && ./run.sh check-entwurf-v2-native-push && ./run.sh check-entwurf-v2-runner && ./run.sh check-entwurf-control-rpc && ./run.sh check-entwurf-v2-production && ./run.sh check-entwurf-v2-surface && ./run.sh check-entwurf-bridge-boot && ./run.sh check-entwurf-bridge-pi-free && ./run.sh check-entwurf-v2-spawn && ./run.sh check-entwurf-resume-args && ./run.sh check-entwurf-v2-spawn-production && ./run.sh check-entwurf-facts && ./run.sh check-control-socket-path && ./run.sh check-socket-discovery && ./run.sh check-meta-listing && ./run.sh check-entwurf-fact-provider && ./run.sh check-entwurf-peers-surface && ./run.sh check-entwurf-self-address && ./run.sh check-entwurf-deliverability && ./run.sh check-native-push-adapter && ./run.sh check-native-push-register && ./run.sh check-agy-sender-identity && ./run.sh check-auth-boundary && ./run.sh check-acp-provider-surface && ./run.sh check-acp-sdk-surface && ./run.sh check-acp-overlay && ./run.sh check-acp-tool-surface && ./run.sh check-acp-event-mapper && ./run.sh check-acp-prompt-builder && ./run.sh check-acp-config && ./run.sh check-acp-session-store && ./run.sh check-acp-backend-preflight && ./run.sh check-acp-session-reuse && ./run.sh check-acp-carrier-augment && ./run.sh check-gate-qualification && ./run.sh check-pack"
138
+ "check": "pnpm lint && pnpm typecheck && ./run.sh check-shell-quote && ./run.sh check-install-surface && ./run.sh check-entwurf-session-identity && ./run.sh check-meta-session && ./run.sh check-meta-v3-record && ./run.sh check-mailbox-receipt-state && ./run.sh check-entwurf-capabilities && ./run.sh check-capability-bundle-reach && ./run.sh check-bridge-delivery && ./run.sh smoke-pi-attach && ./run.sh check-fresh-cut-gate && ./run.sh check-meta-mailbox-state-write && ./run.sh check-meta-receiver-marker && ./run.sh check-hook-launch-topology && ./run.sh check-meta-identity-consumers && ./run.sh check-meta-capability-source && ./run.sh check-socket-probe && ./run.sh smoke-meta-honesty && ./run.sh smoke-meta-install-state && ./run.sh check-meta-doctor-oracle && ./run.sh smoke-agy-install-state && ./run.sh check-agy-permission-matrix && ./run.sh smoke-agy-statusline-state && ./run.sh smoke-agy-hooks-state && ./run.sh smoke-pi-provider-state && ./run.sh smoke-user-scope-citizen && ./run.sh smoke-meta-prune && ./run.sh smoke-meta-keyset-guard && ./run.sh check-meta-manifest-schema && ./run.sh check-package-source-routing && ./run.sh check-model-lock && ./run.sh check-dep-versions && ./run.sh check-node-floor-coherence && ./run.sh check-claude-floor-coherence && ./run.sh check-install-preflight && ./run.sh check-pi-import-surface && ./run.sh check-env-namespace && ./run.sh check-pi-runtime-version && ./run.sh check-pi-preflight && ./run.sh check-project-trust-handler && ./run.sh check-entwurf-v2-contract && ./run.sh check-entwurf-v2-lock && ./run.sh check-entwurf-v2-decider && ./run.sh check-entwurf-v2-matrix && ./run.sh check-entwurf-v2-release && ./run.sh check-entwurf-v2-send && ./run.sh check-entwurf-v2-send-fallback && ./run.sh check-entwurf-v2-mailbox && ./run.sh check-entwurf-v2-native-push && ./run.sh check-entwurf-v2-runner && ./run.sh check-entwurf-control-rpc && ./run.sh check-entwurf-v2-production && ./run.sh check-entwurf-v2-surface && ./run.sh check-entwurf-bridge-boot && ./run.sh check-entwurf-bridge-pi-free && ./run.sh check-entwurf-v2-spawn && ./run.sh check-entwurf-resume-args && ./run.sh check-entwurf-v2-spawn-production && ./run.sh check-entwurf-facts && ./run.sh check-control-socket-path && ./run.sh check-socket-discovery && ./run.sh check-meta-listing && ./run.sh check-entwurf-fact-provider && ./run.sh check-entwurf-peers-surface && ./run.sh check-entwurf-self-address && ./run.sh check-entwurf-deliverability && ./run.sh check-native-push-adapter && ./run.sh check-native-push-register && ./run.sh check-agy-sender-identity && ./run.sh check-auth-boundary && ./run.sh check-acp-provider-surface && ./run.sh check-acp-sdk-surface && ./run.sh check-acp-overlay && ./run.sh check-acp-tool-surface && ./run.sh check-acp-event-mapper && ./run.sh check-acp-stop-reason && ./run.sh check-acp-prompt-lifecycle && ./run.sh check-acp-prompt-builder && ./run.sh check-acp-config && ./run.sh check-acp-session-store && ./run.sh check-acp-backend-preflight && ./run.sh check-acp-session-reuse && ./run.sh check-acp-carrier-augment && ./run.sh check-release-gate-outcomes && ./run.sh check-probe-ordering && ./run.sh check-probe-cli-shim && ./run.sh check-acp-cortex && ./run.sh check-gate-qualification && ./run.sh check-pack"
134
139
  },
135
140
  "pi": {
136
141
  "extensions": [
@@ -32,7 +32,7 @@
32
32
  "WebFetch",
33
33
  "WebSearch"
34
34
  ],
35
- "codexDisabledFeatures": ["image_generation", "tool_suggest", "tool_search", "multi_agent", "apps", "memories"],
35
+ "cortexConnection": "MY_SNOWFLAKE_CONNECTION",
36
36
  "mcpServers": {
37
37
  "entwurf-bridge": {
38
38
  "command": "/path/to/entwurf/mcp/entwurf-bridge/start.sh",
@@ -2,11 +2,19 @@
2
2
  //
3
3
  // This is the pi-extension entry point that registers `entwurf` as a pi
4
4
  // session provider/model. It is intentionally THIN: it stands up the provider
5
- // surface (curated Claude anchor + no-auth sentinel) and wires streamSimple to
6
- // the real ACP backend (lib/acp/backend.ts — spawn-per-turn claude-agent-acp).
7
- // It does NOT build a socket/peers/citizen protocol or touch the v2 core —
8
- // socket-citizenship is supplied by the host `--entwurf-control` pi session
9
- // (AGENTS §ACP Plugin Boundary).
5
+ // surface (every registered adapter's curated rows + the no-auth sentinel) and
6
+ // wires streamSimple to the real ACP backend (lib/acp/backend.ts — a
7
+ // spawn-per-turn ACP child, `claude-agent-acp` or `cortex acp serve` depending
8
+ // on which adapter the model id routes to). It does NOT build a
9
+ // socket/peers/citizen protocol or touch the v2 core — socket-citizenship is
10
+ // supplied by the host `--entwurf-control` pi session (AGENTS §ACP Plugin
11
+ // Boundary).
12
+ //
13
+ // The model set comes from `allCuratedModels()` — the union across the adapter
14
+ // registry (backend-adapter.ts), NOT a per-backend list spelled here. Adding a
15
+ // backend therefore never edits this file. check-acp-provider-surface pins that
16
+ // the compiled entry really registers the EXACT union
17
+ // ([QK:CORTEX-PROVIDER-SIX-ROW-SURFACE]).
10
18
  //
11
19
  // Fence: this entry rides the emit-capable root tsconfig (it is not in the root
12
20
  // `exclude` list); its lib modules are imported with `.js` suffixes (the root
@@ -49,14 +57,16 @@ export default function (pi: ExtensionAPI) {
49
57
  apiKey: ENTWURF_ACP_NO_AUTH_SENTINEL,
50
58
  api: "entwurf",
51
59
  models: allCuratedModels(),
52
- // S2c: real ACP backend. Spawn-per-turn claude-agent-acp drive + event
53
- // mapping (lib/acp/backend.ts). The S0 fail-loud stub is gone the
54
- // provider path is open. Backend auth still belongs to the operator's own
55
- // Claude CLI child (no-auth sentinel above); this plugin only orchestrates.
60
+ // S2c: real ACP backend. Spawn-per-turn ACP child + event mapping
61
+ // (lib/acp/backend.ts); the routed adapter decides WHICH child. The S0
62
+ // fail-loud stub is gone — the provider path is open. Backend auth still
63
+ // belongs to the operator's own backend CLI child (no-auth sentinel above);
64
+ // this plugin only orchestrates.
56
65
  streamSimple: streamShellAcp,
57
66
  });
58
67
 
59
- // Mark only AFTER a successful registration. If curatedClaudeModels() (a
68
+ // Mark only AFTER a successful registration. If allCuratedModels() (which
69
+ // fail-fasts on an unowned/duplicate curated id, and whose claude rows carry a
60
70
  // fail-loud anchor check) or registerProvider throws, the runtime is not left
61
71
  // poisoned with a "registered" marker — a retry can register cleanly.
62
72
  markRegisteredOnRuntime(pi);
@@ -1322,55 +1322,26 @@ function registerEntwurfV2Tool(pi: ExtensionAPI): void {
1322
1322
  registerTool({
1323
1323
  name: "entwurf_v2",
1324
1324
  label: "Dispatch (v2)",
1325
- description: `CANONICAL delivery surface for a garden id. When you have a garden id and want to
1326
- reach whoever it names message / reply / hand-off use THIS verb. A garden id alone does not
1327
- reveal which rail that citizen answers on a live socket session, a dormant one, a mailbox-backed
1328
- self-fetch session, or a native-push session
1329
- and entwurf_v2 is the one surface that reads that and routes correctly (so "when unsure which
1330
- transport, use entwurf_v2"). It dispatches to EXISTING targets; brand-new sibling creation is deferred
1331
- to a later v2 lane. Dispatch to a garden citizen through the unified entwurf_v2 verb: the 5b decider
1332
- picks the transport (live control-socket send / spawn-bg resume / meta-mailbox enqueue / native-push
1333
- direct injection) from the target's liveness + your intent, runs it under the v2 lock policy, and reports
1334
- one outcome (delivered / rejected / lock-retained / delivered-but-lock-dirty).
1335
-
1336
- LOCK POLICY (do not over-generalize it): the per-target lock is taken for a control-socket-DOMAIN
1337
- dispatch, which is BOTH the live send AND the dormant cell's spawn-bg resume spawn-bg is a separate
1338
- relaunch transport yet it still runs under that domain's lock. The mailbox and native-push rails are
1339
- lock-free: the mailbox is guarded instead by active-receiver deliverability, and native-push by its
1340
- adapter probe.
1341
-
1342
- - target: the garden id of the citizen to reach (required).
1343
- - intent: fire-and-forget (a send with no owned result) or owned-outcome (you own the result).
1344
- - message: the message/prompt to dispatch (required).
1345
- - mode: steer or follow_up for a CONTROL-SOCKET send (optional). The mailbox, native-push, and
1346
- spawn-bg plans carry no mode, so it has no effect on those rails — a native-push send IS live and
1347
- still ignores it.
1348
- - wants_reply: reply hint; it rides every rail (optional, default false).
1349
-
1350
- CHOOSING INTENT (picking wrong is rejected, never auto-fixed): to message / reply / hand off a peer
1351
- that entwurf_peers shows as liveness=alive (a live socket citizen, currently backend pi), use intent:
1352
- fire-and-forget — it routes to the live control-socket; set wants_reply:true if you need an answer
1353
- (wants_reply is NOT owned-outcome). Replies to a citizen with NO socket liveness
1354
- (liveness=unsupported) are ALSO fire-and-forget, and the decider picks that citizen's own rail: a
1355
- self-fetch backend (e.g. Claude Code) gets the meta-bridge mailbox, while a native-push backend
1356
- (e.g. Antigravity) gets direct injection into its live conversation and has NO mailbox at all — do
1357
- not assume mailbox semantics for every unsupported citizen. A native-push target IS measured by its
1358
- own adapter probe, and that probe is THREE-valued, so the send is never silently queued: alive →
1359
- injected; dead → rejected as native-push-target-dead; indeterminate → rejected as
1360
- native-push-probe-indeterminate. Those last two stay separate on purpose — "we could not establish
1361
- it" is not "it is gone". THERE IS A THIRD RESULT, so do not read
1362
- liveness=unsupported as "reachable by some rail": the mailbox path delivers only to a DELIVERABLE
1363
- citizen (a self-fetch backend whose receiver is live and armed), so a terminated Claude Code session —
1364
- and any record whose backend has no adapter on this lane, e.g. codex — is rejected as
1365
- mailbox-undeliverable rather than queued into an inbox nobody drains. unsupported means only "this
1366
- backend has no control-socket probe".
1367
- owned-outcome is ONLY for waking a DORMANT socket-domain citizen (spawn-bg resume, currently backend
1368
- pi); on a live target it is rejected as owned-live-no-autosend. Neither self-fetch nor native-push
1369
- has resume authority, but they reject under DIFFERENT reasons — self-fetch as
1370
- backend-liveness-unsupported, native-push as native-push-no-resume-authority.
1371
- It is NEVER auto-converted — so pick the right intent up front.
1372
-
1373
- The decider — not this surface — chooses the transport.`,
1325
+ description: `CANONICAL DELIVERY SURFACE for garden ids: message, reply, or hand off to whoever an id names. The id alone
1326
+ does not say which rail that citizen answers on. Give target + intent; the decider picks transport from
1327
+ liveness (live socket citizen control-socket send; dormant socket citizen spawn-bg resume; deliverable
1328
+ self-fetch citizen meta-bridge mailbox; probe-alive native-push citizen → direct injection into its
1329
+ conversation) and reports ONE outcome (delivered / rejected / lock-retained / delivered-but-lock-dirty).
1330
+ EXISTING targets only; discover with entwurf_peers. INTENT picking wrong is rejected, never
1331
+ auto-converted. A peer entwurf_peers shows as liveness=alive fire-and-forget. A citizen with NO socket
1332
+ liveness (liveness=unsupported) is ALSO fire-and-forget unsupported means only "no control-socket probe"
1333
+ and the decider picks its own rail: a self-fetch backend (e.g. Claude Code) gets the mailbox, a native-push
1334
+ backend (e.g. Antigravity) gets direct injection and has NO mailbox at all. THERE IS A THIRD RESULT: the
1335
+ mailbox delivers only to a DELIVERABLE citizen, so a terminated session, or a backend with no adapter here
1336
+ (e.g. codex), is mailbox-undeliverable, not queued for an inbox nobody drains. The native-push probe is
1337
+ 3-valued: alive injected; dead native-push-target-dead; indeterminate native-push-probe-indeterminate
1338
+ (unestablished gone). owned-outcome wakes a DORMANT socket-domain citizen by spawn-bg resume ONLY live
1339
+ target owned-live-no-autosend, self-fetch backend-liveness-unsupported, native-push
1340
+ native-push-no-resume-authority. LOCK: taken for a control-socket-DOMAIN dispatch — the live send AND the
1341
+ dormant cell's spawn-bg resume, a separate transport that still runs under that domain's lock. The mailbox
1342
+ and native-push rails are lock-free deliverability and the adapter probe guard them. mode applies to a
1343
+ CONTROL-SOCKET send only; other plans carry no mode. wants_reply rides every rail. message caps at 16000
1344
+ chars; send an artifact path + digest for more.`,
1374
1345
  parameters: entwurfV2Parameters,
1375
1346
  async execute(
1376
1347
  _toolCallId: string,
@@ -26,6 +26,17 @@ export interface AcpConnectionLike {
26
26
  newSession(params: unknown): Promise<{ sessionId?: string }>;
27
27
  prompt(params: { sessionId: string; prompt: AcpTextBlock[] }): Promise<{ stopReason?: string }>;
28
28
  setSessionConfigOption?(params: unknown): Promise<unknown>;
29
+ /**
30
+ * ACP `session/cancel` — the PROTOCOL way to end an in-flight prompt turn.
31
+ *
32
+ * The spec requires the agent to answer the pending `session/prompt` with
33
+ * `stopReason: "cancelled"` after this notification, so a user abort ends the
34
+ * turn as a protocol event (backend.ts maps cancelled → aborted) instead of a
35
+ * signal race. Fire-and-forget by contract: it is a JSON-RPC notification, so
36
+ * there is nothing to await and a send failure on an already-closed connection
37
+ * must not mask the abort the caller is executing.
38
+ */
39
+ cancel?(params: { sessionId: string }): void;
29
40
  /**
30
41
  * Closes the underlying SDK connection before child process teardown. With
31
42
  * the fluent SDK connection this is load-bearing: otherwise a successful
@@ -76,6 +87,17 @@ export function connectAcpClient(stream: Stream, handlers: AcpClientHandlers): A
76
87
  prompt: (params) =>
77
88
  agent.request(AGENT_METHODS.session_prompt, params as never) as Promise<{ stopReason?: string }>,
78
89
  setSessionConfigOption: (params) => agent.request(AGENT_METHODS.session_set_config_option, params as never),
90
+ cancel: (params) => {
91
+ // Notification, not a request: nothing resolves it, and the connection
92
+ // may already be closing when the operator aborts. Swallow both the sync
93
+ // throw and the rejected send — the caller's abort path continues either
94
+ // way (it escalates to teardown after a bounded grace).
95
+ try {
96
+ void Promise.resolve(agent.notify(AGENT_METHODS.session_cancel, params as never)).catch(() => {});
97
+ } catch {
98
+ // connection already closed — the abort path escalates on its own.
99
+ }
100
+ },
79
101
  close: (error) => {
80
102
  // Best-effort by contract (see AcpConnectionLike.close): a teardown-path
81
103
  // close that threw would mask the turn's real error and skip the child
@@ -30,14 +30,71 @@
30
30
 
31
31
  import { existsSync, readFileSync } from "node:fs";
32
32
  import { homedir } from "node:os";
33
- import { join } from "node:path";
33
+ import { join, resolve } from "node:path";
34
34
  import { ENTWURF_PROJECT_CONTEXT_OPEN_TAG } from "../../../protocol.js";
35
35
  import type { AcpTextBlock } from "./context.js";
36
36
 
37
37
  const MAX_AUGMENT_BYTES = 50 * 1024;
38
38
 
39
+ // Carrier-less backends expose no `_meta.systemPrompt` (docs/acp-backend-rail.md,
40
+ // “Cortex Code audit”). claude folds the operator engraving into that carrier; a carrier-less
41
+ // backend (cortex, the first — precisely: SYSTEM-PROMPT-carrier-less, since
42
+ // cortex does read `_meta` for an unpromoted caller-session-id seam)
43
+ // has nowhere to put it — so its operator engraving
44
+ // rides HERE, prepended as the LEADING section of the first-user augment. It
45
+ // travels on the WIRE only (new-only, never the config signature), so an
46
+ // engraving change applies to the NEXT new cortex session rather than
47
+ // invalidating a live reused one — the documented asymmetry vs claude, where the
48
+ // carrier feeds bridgeConfigSignature.
49
+ const CARRIER_LESS_BACKENDS: ReadonlySet<string> = new Set(["cortex"]);
50
+
51
+ // The one sentence both rails owe the model: WHAT this block is and what it is
52
+ // NOT. Shared so neither rail's frame can quietly lose it (and so the gate has a
53
+ // single anchor to mutate). Everything after it is rail-specific, because the
54
+ // rails really do differ — see the frame in buildPiContextAugment.
55
+ const PROVENANCE_LEAD =
56
+ "This block is prepended to the FIRST USER MESSAGE of this session. It is not your system prompt.";
57
+
58
+ // The env var an operator points at a personal engraving file. Shared surface
59
+ // with the claude carrier (engraving.ts) BY NAME, but read INLINE here (not
60
+ // imported): augment.ts is loaded raw by the strip-types carrier-augment gate,
61
+ // which cannot resolve a `.js` VALUE import of the sibling `.ts` engraving
62
+ // module (augment.ts only value-imports real `.js` files). So the carrier-less
63
+ // override read is self-contained. Unlike engraving.ts's shipped-default path,
64
+ // this ONLY ever reads the operator OVERRIDE file: the shipped claude default
65
+ // (a preset-replacement / auto-memory lever) is meaningless for a non-claude
66
+ // backend and is never injected into a carrier-less augment.
67
+ const ENGRAVING_OVERRIDE_ENV = "ENTWURF_ACP_ENGRAVING_PATH";
68
+
69
+ /**
70
+ * Render the operator engraving OVERRIDE for a carrier-less backend, or null
71
+ * when unset / empty / whitespace / unreadable. Pure w.r.t. (override file
72
+ * contents, backend, sorted mcpServerNames). Mirrors engraving.ts's `{{backend}}`
73
+ * / `{{mcp_servers}}` token substitution so the operator's file behaves the same
74
+ * on the augment as it would on the claude carrier.
75
+ */
76
+ function loadCarrierlessOperatorEngraving(backend: string, mcpServerNames: readonly string[]): string | null {
77
+ const envPath = process.env[ENGRAVING_OVERRIDE_ENV]?.trim();
78
+ if (!envPath) return null;
79
+ let source: string;
80
+ try {
81
+ source = readFileSync(resolve(envPath), "utf8");
82
+ } catch {
83
+ return null;
84
+ }
85
+ const names = [...mcpServerNames].sort();
86
+ const mcpList = names.length > 0 ? names.join(", ") : "(none registered)";
87
+ const rendered = source
88
+ .replace(/\{\{backend\}\}/g, backend)
89
+ .replace(/\{\{mcp_servers\}\}/g, mcpList)
90
+ .trim();
91
+ return rendered.length > 0 ? rendered : null;
92
+ }
93
+
39
94
  export interface PiContextAugmentParams {
40
- /** Claude-only this cut; kept as a field so the narrative names the backend. */
95
+ /** The routed backend ("claude" | "cortex"). Load-bearing, not decorative: the
96
+ * narrative names it AND `CARRIER_LESS_BACKENDS` keys off it to decide whether
97
+ * the operator engraving leads the augment. */
41
98
  backend: string;
42
99
  cwd: string;
43
100
  mcpServerNames: readonly string[];
@@ -58,6 +115,46 @@ export function buildPiContextAugment(params: PiContextAugmentParams): string {
58
115
 
59
116
  const sections: string[] = [];
60
117
 
118
+ // System-prompt-carrier-less backend (cortex): the operator engraving leads the augment,
119
+ // since there is no `_meta.systemPrompt` carrier to hold it. null when no
120
+ // override is configured — the shipped claude default is never injected here.
121
+ if (CARRIER_LESS_BACKENDS.has(params.backend)) {
122
+ const engraving = loadCarrierlessOperatorEngraving(params.backend, params.mcpServerNames);
123
+ if (engraving) sections.push(engraving);
124
+ }
125
+
126
+ // PROVENANCE FRAME — leads the bridge narrative, and it is not decoration.
127
+ //
128
+ // Measured 2026-07-30: asked where its instructions came from, the Claude ACP
129
+ // model asserted that the two paragraphs below were its system prompt. They
130
+ // are not — they are text prepended to its FIRST USER MESSAGE. Nothing in the
131
+ // block said otherwise, and a model has no other way to tell: on the wire a
132
+ // long first user message and a system prompt read the same. So the block now
133
+ // states its own provenance, per rail, because the rails genuinely differ
134
+ // (backend-adapter.ts: claude's buildSessionMeta carries `_meta.systemPrompt`,
135
+ // cortex's returns undefined so no `_meta` key is sent at all).
136
+ //
137
+ // Honest about scope: this makes the boundary STATEABLE, not enforced. It
138
+ // tells the model what it cannot otherwise observe; it does not stop a model
139
+ // from guessing anyway.
140
+ sections.push(
141
+ CARRIER_LESS_BACKENDS.has(params.backend)
142
+ ? [
143
+ "# entwurf: where this text comes from",
144
+ "",
145
+ PROVENANCE_LEAD,
146
+ `The ${params.backend} rail carries no system-prompt carrier at all — entwurf sends no \`_meta.systemPrompt\`, so everything entwurf tells you, including any operator engraving above, arrives here as user-message text.`,
147
+ "If you are asked what your system prompt says, do not quote this block as one.",
148
+ ].join("\n")
149
+ : [
150
+ "# entwurf: where this text comes from",
151
+ "",
152
+ PROVENANCE_LEAD,
153
+ `The ${params.backend} rail does have a system-prompt carrier (\`_meta.systemPrompt\`), and entwurf keeps it deliberately tiny: it carries the operator engraving only — never this narrative, never AGENTS.md.`,
154
+ "So: system prompt = the short engraving; everything below = first-user-message text. If you are asked what your system prompt says, do not quote this block as one.",
155
+ ].join("\n"),
156
+ );
157
+
61
158
  // Bridge identity + caller-side sibling stance. The closing line is a
62
159
  // caller-perspective statement: when THIS session throws an entwurf, the peer
63
160
  // on the other end is a sibling, not a worker.