@junghanacs/entwurf 0.52.0 → 0.53.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 (68) hide show
  1. package/AGENTS.md +1 -1
  2. package/BASELINE.md +116 -397
  3. package/CHANGELOG.md +131 -2
  4. package/CONTRIBUTING.md +4 -4
  5. package/DELIVERY.md +2 -2
  6. package/ENTWURF-CLI.json +1 -1
  7. package/README.md +7 -4
  8. package/VERIFY.md +332 -142
  9. package/demo/demo-baseline.sh +1 -1
  10. package/demo/demo.sh +1 -1
  11. package/docs/acp-backend-rail.md +248 -34
  12. package/docs/setup-clean-host.md +50 -65
  13. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/acp-client.js +1 -1
  14. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/backend-adapter.js +90 -14
  15. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/config.js +4 -9
  16. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/context.js +15 -0
  17. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/model-ids.js +9 -12
  18. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/models.js +41 -56
  19. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/tool-surface.js +33 -6
  20. package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-composition.js +34 -3
  21. package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-fresh-call.js +3 -1
  22. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +2 -1
  23. package/package.json +4 -4
  24. package/pi/settings.reference.json +0 -1
  25. package/pi-extensions/acp-provider.ts +12 -6
  26. package/pi-extensions/lib/acp/acp-client.ts +6 -7
  27. package/pi-extensions/lib/acp/backend-adapter.ts +139 -15
  28. package/pi-extensions/lib/acp/backend.ts +261 -32
  29. package/pi-extensions/lib/acp/config.ts +4 -9
  30. package/pi-extensions/lib/acp/context.ts +17 -0
  31. package/pi-extensions/lib/acp/event-mapper.ts +236 -17
  32. package/pi-extensions/lib/acp/model-ids.js +9 -12
  33. package/pi-extensions/lib/acp/models.ts +46 -57
  34. package/pi-extensions/lib/acp/tool-surface.ts +34 -6
  35. package/pi-extensions/lib/fresh-call-composition.ts +37 -3
  36. package/pi-extensions/lib/herdr-fresh-call.ts +3 -1
  37. package/pi-extensions/lib/mux-fresh-call.ts +3 -1
  38. package/run.sh +25 -86
  39. package/scripts/check-acp-carrier-augment.ts +3 -21
  40. package/scripts/check-acp-prompt-lifecycle.ts +24 -3
  41. package/scripts/check-acp-provider-surface.ts +17 -26
  42. package/scripts/check-acp-session-reuse.ts +23 -4
  43. package/scripts/check-acp-stop-reason.ts +22 -3
  44. package/scripts/check-acp-stream-hooks.ts +24 -3
  45. package/scripts/check-acp-usage-accounting.ts +53 -24
  46. package/scripts/check-copilot-receive-arm.ts +19 -2
  47. package/scripts/check-fresh-call-dispatch.ts +9 -3
  48. package/scripts/check-gate-qualification.ts +5 -5
  49. package/scripts/check-herdr-fresh-call.ts +1 -0
  50. package/scripts/check-install-surface.ts +6 -0
  51. package/scripts/check-probe-ordering.ts +34 -24
  52. package/scripts/check-release-gate-outcomes.ts +9 -23
  53. package/scripts/lib/probe-cli-target.ts +3 -2
  54. package/scripts/mutants/acp-augment.json +0 -26
  55. package/scripts/mutants/acp-cortex.json +10 -183
  56. package/scripts/mutants/acp-foreground-only.json +11 -0
  57. package/scripts/mutants/acp-launch-namespace.json +3 -1
  58. package/scripts/mutants/acp-prompt-lifecycle.json +2 -2
  59. package/scripts/mutants/acp-set-model-readback.json +488 -0
  60. package/scripts/mutants/mux-fresh-call.json +12 -1
  61. package/scripts/mutants/pi-floor.json +2 -2
  62. package/scripts/mutants/release-gate.json +3 -3
  63. package/scripts/raw-acp-compaction-measure/probe.ts +8 -2
  64. package/scripts/smoke-acp-cortex-live.ts +4 -408
  65. package/scripts/smoke-acp-ordering-probe-live.ts +7 -1
  66. package/scripts/smoke-acp-session-reuse-live.ts +11 -2
  67. package/scripts/smoke-herdr-raw-install-live.sh +4 -4
  68. package/scripts/check-acp-cortex.ts +0 -710
@@ -15,7 +15,7 @@
15
15
  # recordings actually used — they are kept as the historical record, not as a
16
16
  # recommendation. Two things are stale about them: `entwurf/gpt-5.4` is no
17
17
  # longer a curated `entwurf` ACP id at all (that provider serves only the
18
- # claude-* and cortex-* rows in pi-extensions/lib/acp/models.ts), and GLG's
18
+ # Claude rows in pi-extensions/lib/acp/models.ts; Cortex ACP is disabled), and GLG's
19
19
  # live-spend default moved to `openai-codex/gpt-5.6-luna` on a Plus plan. Do
20
20
  # NOT copy these ids into a live harness default — see VERIFY.md and NEXT.md.
21
21
  # Layout (tmux, 220x50):
package/demo/demo.sh CHANGED
@@ -12,7 +12,7 @@
12
12
  # recordings actually used — they are kept as the historical record, not as a
13
13
  # recommendation. Two things are stale about them: `entwurf/gpt-5.4` is no
14
14
  # longer a curated `entwurf` ACP id at all (that provider serves only the
15
- # claude-* and cortex-* rows in pi-extensions/lib/acp/models.ts), and GLG's
15
+ # Claude rows in pi-extensions/lib/acp/models.ts; Cortex ACP is disabled), and GLG's
16
16
  # live-spend default moved to `openai-codex/gpt-5.6-luna` on a Plus plan. Do
17
17
  # NOT copy these ids into a live harness default — see VERIFY.md and NEXT.md.
18
18
  # Layout (tmux, 220x50):
@@ -5,9 +5,11 @@ It is not a second harness and does not create another citizen or socket layer.
5
5
  host pi session already owns the record-backed citizen identity; each backend owns
6
6
  its own process, auth, transcript, and native tool surface.
7
7
 
8
- Claude is the reference adapter. Snowflake Cortex Code is the second shipped adapter.
9
- Implementation history and audit chronology live in CHANGELOG, issues, and git; this
10
- document keeps the current contract and its open evidence boundary.
8
+ Claude is the supported adapter. Cortex Code ACP is disabled: its retired model ids
9
+ are not registered and a direct selection refuses before launch, without a Claude fallback.
10
+ Existing Cortex settings, credentials and native citizen records are not removed.
11
+ Dated Cortex observations below are historical, not a current support claim.
12
+ Implementation history and audit chronology live in CHANGELOG, issues, and git.
11
13
 
12
14
  ## Boundary
13
15
 
@@ -55,6 +57,29 @@ backend-specific keys; `adapterSettings` remains opaque until handed back to its
55
57
  A connection, model, carrier, MCP declaration, or overlay-relevant setting change must
56
58
  change the reuse signature rather than mutate a live incompatible session.
57
59
 
60
+ **Session reuse needs a resident pi.** Reuse — the live ACP session kept across turns, each
61
+ later turn sending only the new user message — exists only in a pi process started with
62
+ `--entwurf-control` (`entwurf pi` adds it). Every other pi process, plain interactive `pi`
63
+ and `pi -p` included, is turn-scoped: each turn opens a new ACP session, sends the pi
64
+ transcript rendered as one text prompt, and tears the child down when the turn ends
65
+ (`resolveLifecyclePolicy`, `pi-extensions/lib/acp/session-store.ts`). This boundary is as
66
+ old as reuse itself (S2c, hang-safe one-shot exit); it is stated here so that "reuse without
67
+ resending the transcript" is not read as a property of every pi process. Nothing in a pi
68
+ profile or AGENTS file changes it.
69
+
70
+ **What a rebuilt prompt carries (`context.ts`).** The rebuild is a TEXT rendering of pi's own
71
+ transcript, not a replay: user text, assistant text and tool results become `User:` /
72
+ `Assistant:` / `Tool result (…)` / `Tool error (…)` lines; assistant thinking, structured tool calls, the system
73
+ prompt and lifecycle notices are left out; an image becomes the marker `[image omitted: <mime>]`
74
+ on both the rebuild and the reuse delta. Because no path carries image bytes to the backend, every
75
+ curated Claude ACP row declares `input: ["text"]` (`ACP_ROW_INPUT` in `models.ts`,
76
+ `[QK:ACP-ROWS-TEXT-ONLY-INPUT]`), although pi's catalog lists the Anthropic models as
77
+ `["text", "image"]`: that is the model behind the API, not this transport. What pi does with an
78
+ attachment offered to a text-only row is pi's behaviour and is unmeasured here. Real image
79
+ passthrough is a separate decision ([#161](https://github.com/junghan0611/entwurf/issues/161)); a
80
+ row gains `image` only together with that path and its proof. A backend's own file tools (Claude's
81
+ Read on an image file) are a different surface and are not affected.
82
+
58
83
  A streaming message begins with `stopReason: "pending"`. ACP's terminal set is mapped
59
84
  explicitly: `end_turn → stop`, `max_tokens → length`, `cancelled → aborted`; refusal,
60
85
  exhausted turn budget, unknown, and absent reasons end as errors. The original ACP
@@ -73,6 +98,25 @@ none.** A turn ends only on a lifecycle event:
73
98
  | the operator aborts | ACP `session/cancel` first — the agent closes its own turn (`cancelled → aborted`); process-group teardown only after a bounded grace, so an abort always returns |
74
99
  | the child dies / stdio ends | the turn fails naming the exit status and the session-scoped stderr tail — or an honest bounded absence when transport EOF wins the race — on both the new and the reuse path |
75
100
 
101
+ **What an abort leaves (#142).** After the turn has settled, the operator is told one of two
102
+ things: `[acp: stopped — changes already made stay; next message continues this session]` when
103
+ the agent closed a reused turn on `session/cancel` (or ended a new turn `cancelled` with no
104
+ operator abort, so the session was retained), so the live ACP session stays and the next
105
+ message is a delta; or `[acp: stopped — changes already made stay; next message opens a new
106
+ session]` when the session was dropped — a cancelled first turn (only a live, un-aborted
107
+ session is retained), escalation teardown, or an abort before the prompt was sent — so the next
108
+ message rebuilds from the pi transcript. That rebuild states the cut to the new session: an
109
+ assistant turn pi recorded as `aborted` is followed by one harness line, `[turn status: aborted —
110
+ actions this turn had started may already have taken effect; cancellation is
111
+ not rollback]`, and claims nothing about what ran (`context.ts` `ABORTED_TURN_STATUS`; a reused
112
+ session gets the user delta only). Neither notice rolls anything back, and neither speaks
113
+ for the private Claude transcript behind the live session. An abort while a reuse turn waits
114
+ for a changed effort's answer settles on the abort, not on the answer or the 30s bound; no
115
+ prompt is sent and the session is dropped, since the effort the driver holds is unknown. Gates:
116
+ `check-acp-set-model-readback` (`backend.thinking.test.ts`, QKs `ACP-THINKING-REUSE-ABORT`,
117
+ `ACP-CANCEL-NOTICE-REUSE-KEPT`, `ACP-CANCEL-NOTICE-NEW-CLOSED`, `ACP-THINKING-REUSE-ABORT-LATE`;
118
+ `context.test.ts`, QKs `ACP-REBUILD-ABORTED-STATUS`, `ACP-REBUILD-ABORTED-EMPTY`).
119
+
76
120
  Elapsed time is not evidence of failure, and a silent turn is not a failed turn: tool
77
121
  use, reasoning, and provider queueing all legitimately outrun any number we could pick.
78
122
  Suspected stalls are handled by exposing progress, never by a killing timer.
@@ -86,6 +130,27 @@ turned one long turn into four in 0.13.0. Gates: `check-acp-prompt-lifecycle` (b
86
130
  with pi's own classifier as the oracle), `check-probe-ordering` (no production prompt
87
131
  cutoff in source).
88
132
 
133
+ ### Upstream 0.106.0 cancel and effort contracts → this rail (#142)
134
+
135
+ Upstream tests read at immutable `v0.106.0` = `51f514d7` (source read, not executed here; its CI
136
+ runs `vitest run` without the two opt-in env flags below, so a file's presence is not a run).
137
+ **Scope** is the reach of the upstream contract: `common` when it is ACP turn/cancel behaviour any backend's adapter must settle, `Claude` when it rests on a Claude adapter field, env or config; a Claude n=1 or
138
+ a fixture is never inherited by another backend. **Status**: covered (a QK kills on it), open (our
139
+ touchpoint is reachable and unmeasured), not exposed (our surface cannot reach it — not a backlog,
140
+ and not proof the vendor can do nothing).
141
+
142
+ | Upstream contract | Scope | Our touchpoint and proof | Status |
143
+ |---|---|---|---|
144
+ | A running tool's interrupted result is delivered before `cancelled`, verbatim, `_meta.claudeCode.nonExecutionKind:"user-rejected"` ([incomplete-tools:764](https://github.com/agentclientprotocol/claude-agent-acp/blob/51f514d73c8bfd786979c84adb7a8517dbc22695/src/tests/v1/incomplete-tools.test.ts#L764-L780)) | Claude | rendered as `[tool:failed] …` text; a rebuild adds the `aborted` status line (`ACP-REBUILD-ABORTED-STATUS`/`-EMPTY`). Real n=1: the rejection was delivered while `start` was already on disk — the kind is not evidence of no effect | covered (fake) + n=1 |
145
+ | Results that arrive after the cancelled turn ended are dropped ([incomplete-tools:921](https://github.com/agentclientprotocol/claude-agent-acp/blob/51f514d73c8bfd786979c84adb7a8517dbc22695/src/tests/v1/incomplete-tools.test.ts#L921-L972)) | common | measured on a scripted partner (#142 private probe, n=1 per cell) with an update of the cancelled turn A on a KEPT session: (P1) before the next turn B starts there is no handler, so it is dropped; (P3) while B sets effort, before B's prompt is sent, a reuse turn now opens its stream only when its own prompt goes out, so the update is neither shown nor sealed as B's (`[QK:ACP-REUSE-LATE-UPDATE-PRE-PROMPT]`); (P2) once B's prompt is out, `session/update` carries no turn id, so routing cannot tell A's update from B's and relies on the adapter not speaking for a turn it already answered (this upstream contract; the real Claude producer is unmeasured here) — a local counter cannot restore an attribution the wire does not carry | P1 measured (private probe n=1, no QK) · P3 pre-prompt window measured → fixed · P2 upstream dependence, open |
146
+ | A tool that finished as the cancel landed is reported completed ([incomplete-tools:940](https://github.com/agentclientprotocol/claude-agent-acp/blob/51f514d73c8bfd786979c84adb7a8517dbc22695/src/tests/v1/incomplete-tools.test.ts#L940-L947)) | common | rendered `[tool:done]`; the turn still seals `aborted` | open — not measured cancel-specific |
147
+ | A background subagent keeps running past a foreground cancel ([incomplete-tools:822](https://github.com/agentclientprotocol/claude-agent-acp/blob/51f514d73c8bfd786979c84adb7a8517dbc22695/src/tests/v1/incomplete-tools.test.ts#L822-L833), [:974](https://github.com/agentclientprotocol/claude-agent-acp/blob/51f514d73c8bfd786979c84adb7a8517dbc22695/src/tests/v1/incomplete-tools.test.ts#L974)) | Claude | background tasks are off at launch (`CLAUDE_CODE_DISABLE_BACKGROUND_TASKS=1`, foreground-only lane) | not exposed |
148
+ | A queued prompt cancelled before insertion is removed from Claude Code's queue and never runs ([turn-events:312](https://github.com/agentclientprotocol/claude-agent-acp/blob/51f514d73c8bfd786979c84adb7a8517dbc22695/src/tests/v1/turn-events.test.ts#L312-L418)) | common | one prompt per session at a time (`inFlightKeys`); queueing is pi's (`PI-QUEUE-ABORT-DROPS-QUEUED`) | not exposed |
149
+ | A force-cancel while an update is being sent still ends the turn ([turn-events:419](https://github.com/agentclientprotocol/claude-agent-acp/blob/51f514d73c8bfd786979c84adb7a8517dbc22695/src/tests/v1/turn-events.test.ts#L419-L450)) | common | our own bound: `session/cancel` first, teardown after the grace (`ABORT-SENDS-PROTOCOL-CANCEL`, `ABORT-ESCALATION-BOUNDED`); a reuse turn the agent closes keeps its session (`ACP-REUSE-COOPERATIVE-CANCEL`, `ACP-CANCEL-NOTICE-REUSE-KEPT`) | covered for our own bound (fake), not upstream's internal force-cancel; n=1 settle 25–65 ms |
150
+ | Effort applies through flag settings, the answer carries `currentValue`, a rejected selection keeps the old state, `default` is `null`, no update notification ([session-config-options:552](https://github.com/agentclientprotocol/claude-agent-acp/blob/51f514d73c8bfd786979c84adb7a8517dbc22695/src/tests/v1/session-config-options.test.ts#L552-L655)) | Claude | set + read back, refusal fails before the prompt and drops a reused session (`ACP-CLAUDE-EFFORT-READBACK`); an abort while the answer is pending settles at once (`ACP-THINKING-REUSE-ABORT`, `-LATE` — beyond upstream). We never send `default`: no level refuses | covered (fake) + readback n=1 |
151
+ | A real Bash turn cancelled mid-sleep settles every announced tool call ([live-capability-session-audit:367](https://github.com/agentclientprotocol/claude-agent-acp/blob/51f514d73c8bfd786979c84adb7a8517dbc22695/src/tests/v1/live-capability-session-audit.test.ts#L367); opt-in `RUN_LIVE_CAPABILITY_AUDIT`) | Claude | our private ESC runs (reuse and first turn) also observe the file effect, the next input and the session kept/closed | open (LIVE n=1 each, #142; no fake QK) |
152
+ | Session load/resume lifecycle ([session-load:21](https://github.com/agentclientprotocol/claude-agent-acp/blob/51f514d73c8bfd786979c84adb7a8517dbc22695/src/tests/v1/session-load.test.ts#L21); opt-in `RUN_INTEGRATION_TESTS`) | common | persisted resume/load is off — bootstrap decides only `new`/`reuse` | not exposed (canonical resume after restart is separate design) |
153
+
89
154
  ### Typed Claude prompt rejection — no automatic whole-prompt replay (#127)
90
155
 
91
156
  Claude ACP 0.85.1 can reject a prompt with the wire SDK's `RequestError`, code `-32603`,
@@ -130,29 +195,30 @@ equal the published 0.36.1–0.37.1 manifests (`claude-agent-acp 0.90.0`, `@agen
130
195
  1.8.0`); 0.31.0–0.36.0 shipped `0.85.1` / `1.6.0` and 0.30.0 shipped `0.79.0` / `1.4.0`
131
196
  (`[측정 2026-10-16]` `git show <tag>:package.json`). The transitive Claude Agent SDK `0.3.296`
132
197
  row has no direct manifest entry; its bump is recorded in CHANGELOG 0.36.1 (#128) and the ROADMAP
133
- Dep bump ledger. 0.38.0 (#145) changes only the pi SDK set; the ACP rows are unchanged from 0.37.1.
198
+ Dep bump ledger. 0.38.0 (#145) changes only the pi SDK set; the ACP rows are unchanged from 0.37.1
199
+ through the published 0.52.0. #142 landed `0.106.0` / `2.6.0` / `0.3.322` on main; #163 moves the
200
+ adapter to `0.107.0` and the transitive SDK to `0.3.323` (wire SDK unchanged). No npm release carries
201
+ either set, so the rows below are not a shipped manifest.
134
202
 
135
203
  | Surface | Declaration | Class | What a green actually says |
136
204
  |---|---|---|---|
137
205
  | Entwurf package | `package.json` `version` | checkout declaration, not a release receipt | names the package label these rows sit beside; whether that label shipped, and with which rows, is CHANGELOG's record |
138
- | pi runtime | SDK exact `1.5.2`, host `>=1.5.2 <1.6` | **exact** oracle + **closed range** | the #158 source candidate's declared SDK set and derived host range (the published 0.51.0 shipped SDK 1.5.0, #155), not a release or operating-host acceptance; an ordinary Pi host inside the range is accepted by the source's version predicate, and the ceiling moves only on measurement. The published 0.36.0 through 0.37.1 shipped the 1.2.0 set (#141); 0.38.0 (#145) is the first to carry 1.3.0. Historical: the published 0.33.0 through 0.35.1 ship SDK exact 1.1.0, 0.32.0 ships SDK exact 1.0.4 with the next-minor ceiling (ROADMAP's 2026-10-07 bump entry), and 0.31.0 declared the earlier peer floor 1.0.2 |
139
- | ACP wire SDK | `@agentclientprotocol/sdk 1.8.0` | **exact** | the shared wire oracle both adapters speak |
140
- | Claude ACP adapter | `@agentclientprotocol/claude-agent-acp 0.90.0` | **exact**, bundled | shipped since 0.36.1; resolved before any PATH fallback; declaration is not compatibility evidence |
141
- | Claude Agent SDK | `0.3.296` (transitive) | **exact** oracle | the runtime risk surface behind the adapter |
206
+ | pi runtime | SDK exact `1.5.2`, host `>=1.5.2 <1.6` | **exact** oracle + **closed range** | the SDK set the published 0.52.0 ships (#158) and its derived host range (0.51.0 shipped SDK 1.5.0, #155), not an operating-host acceptance; an ordinary Pi host inside the range is accepted by the source's version predicate, and the ceiling moves only on measurement. The published 0.36.0 through 0.37.1 shipped the 1.2.0 set (#141); 0.38.0 (#145) is the first to carry 1.3.0. Historical: the published 0.33.0 through 0.35.1 ship SDK exact 1.1.0, 0.32.0 ships SDK exact 1.0.4 with the next-minor ceiling (ROADMAP's 2026-10-07 bump entry), and 0.31.0 declared the earlier peer floor 1.0.2 |
207
+ | ACP wire SDK | `@agentclientprotocol/sdk 2.6.0` | **exact** | the Claude wire oracle (protocol v1; the experimental `Usage` type comes from the `./unstable` entry) |
208
+ | Claude ACP adapter | `@agentclientprotocol/claude-agent-acp 0.107.0` | **exact**, bundled | #163 bump over #142's 0.106.0, unshipped (0.90.0 shipped 0.36.1–0.52.0); resolved before any PATH fallback; declaration is not compatibility evidence |
209
+ | Claude Agent SDK | `0.3.323` (transitive) | **exact** oracle | the runtime risk surface behind the adapter |
142
210
  | Anthropic SDK | `0.100.1` | **exact**, peer-resolution only | satisfies the Agent SDK peer floor (0.93.0+); never an API client here (gate L4) |
143
211
  | Claude Code runtime | `>=2.1.306` (`entwurf.claudeCodeFloor`) | **floor** | below it, hook args are silently dropped; entwurf enforces this itself |
144
212
  | Node | `>=24` (`engines.node`) | **floor** | single axis, derived everywhere else |
145
- | Cortex Code | operator-installed CLI | **on-demand**, external | NOT a pinned dependency. Each LIVE record must name the exact CLI version it measured |
213
+ | Cortex Code | operator-installed CLI | **disabled** | No supported Cortex ACP model or turn; old host receipts remain historical. |
146
214
 
147
- Current Cortex host reading: **`Cortex Code v1.1.52`, observed on the reference host `oracle`
148
- (2026-08-19)**. This is an OBSERVATION of what happens to be installed, not a pin, not a range, and
149
- not a direct LIVE certification — no Cortex evidence record is created by writing it down. It is
150
- recorded here only so the on-demand row names a concrete number instead of an abstraction.
215
+ Historical host observation: **`Cortex Code v1.1.52` on `oracle` (2026-08-19)**.
216
+ It is not a current installation, support or LIVE certification claim.
151
217
 
152
218
  Reading rules that do not bend:
153
219
 
154
- - **Claude PASS never certifies Cortex, and a Cortex SKIP never becomes a PASS.** They are separate
155
- evidence axes with separate records; Cortex stays outside the Claude release floor.
220
+ - **Claude PASS never certifies Cortex, and an old Cortex SKIP never becomes a PASS.**
221
+ Cortex ACP is now disabled; historical receipts remain bound to their own subjects.
156
222
  - **Exact** means one measured build. **Range** means a closed accepted interval. **Floor** means a
157
223
  minimum entwurf enforces itself. **On-demand** means the operator supplies the runtime and the
158
224
  evidence names its version — presence of an adapter certifies no external release.
@@ -201,6 +267,9 @@ different reasons, and collapsing them would hide a real risk**:
201
267
  the effort selector that ride it are unreachable for a second, independent reason as well:
202
268
  `[측정 2026-09-10]` `git grep -c configOptions pi-extensions/lib/acp/` is **0**, and entwurf
203
269
  has never set `EFFORT_CONFIG_ID`, which is the other gate on the SDK-side `applyFlagSettings`.
270
+ That second reason no longer holds from #142: entwurf now sets the `effort` config option on every
271
+ Claude session (§Claude effort). `recommendedValue` itself stays unreachable through the empty
272
+ `clientCapabilities`.
204
273
  The rest of 0.76.0 is one refactor: `resolveModelPreference` moved into a new
205
274
  `dist/session-model.*` (diff is comments and formatting), `dist/session-effort.*` is new, and
206
275
  `setSessionConfigOption` — our only model-forcing wire call — is byte-identical across
@@ -210,6 +279,9 @@ different reasons, and collapsing them would hide a real risk**:
210
279
  `resolvedProvider` and up onto the unconditional `session/new` path. entwurf passes `settings`
211
280
  as an OBJECT (`tool-surface.ts:153`), so the branch is never taken. That makes "entwurf never
212
281
  hands `settings` as a path" a contract rather than an accident; it is stated at that call site.
282
+ - **Advertised but never called — 0.106.0 adds `session/options`** (#1592) and setup-request
283
+ `configOptions`. entwurf calls neither; effort goes through `session/set_config_option` and its
284
+ readback, because upstream skips a refused setup entry silently.
213
285
  - **0.77.0's BREAKING change does not reach us, twice over.** `claudeCode.options.agent` is no
214
286
  longer forwarded — the adapter now shallow-copies the options object and `delete`s `agent` on
215
287
  the unconditional `session/new` path (`[측정 2026-09-18, 0.79.0 dist/acp-agent.js:5934-5941 직독]`).
@@ -396,6 +468,49 @@ different reasons, and collapsing them would hide a real risk**:
396
468
  the agent (#1218); a new session's first prompt carries our augment ahead of the user text,
397
469
  a reuse turn does not.
398
470
 
471
+ - **0.90.0 → 0.106.0 (#142) moves the wire SDK a major and changes what reaches us as follows.**
472
+ `[측정 2026-11-13, oracle aarch64, private candidate install, no session/prompt]` receipts are
473
+ host-local under `.tmp-verify/acp106-readback-20261113/` (stage1 no-auth; stage2–4 authenticated
474
+ through the production `ensureClaudeConfigOverlay(realDir, overlayDir)` whitelist into a sandbox
475
+ overlay; account email redacted).
476
+ - **Wire and launch.** The adapter depends on `@agentclientprotocol/sdk` 2.6.0 (major since 0.94);
477
+ the client moves with it so the L2c shared-wire contract holds. Protocol stays v1: `initialize`
478
+ answered `protocolVersion: 1`, `loadSession: true` and session capabilities `additionalDirectories,
479
+ close, delete, fork, list, options, resume, status, systemPrompt, title`, plus
480
+ `_meta.steering.supported`. 0.103.0 narrowed `exports` to `.` and `./package.json`; the production
481
+ launcher anchors on `package.json` and imports the bin by file URL, so it booted 0.106.0 unchanged.
482
+ Two LIVE probe scripts that deep-imported `dist/` were repointed the same way.
483
+ - **Production `_meta` passes 0.100's request validation.** Three `session/new` calls carrying
484
+ `model/tools/settingSources/settings/disallowedTools/extraArgs` and the shipped carrier succeeded.
485
+ - **Initial config options** (0.106 #1592) arrive on `session/new`: `mode` = `bypassPermissions`
486
+ (the overlay `defaultMode` still beats 0.101's built-in Auto default), `model`, `effort`
487
+ (category `thought_level`, values `default, low, medium, high, xhigh, max`), `fast`, `ultracode`,
488
+ and on a later session `agent`. `session/options` and setup-request `configOptions` are
489
+ advertised but never called here; a refused setup entry is skipped silently upstream, so effort is
490
+ set by `session/set_config_option` and read back instead.
491
+ - **Model rows (authenticated).** `claude-opus-5-5` → `opus`/"Opus 5.5", `claude-sonnet-5-5` →
492
+ `sonnet`/"Sonnet 5.5", `claude-fable-5-1` → its own row "Fable 5.1", `claude-fable-5-2` → its own
493
+ row "Fable 5.2" (the alias `fable` also names Fable 5.2). Unauthenticated, setting a non-alias id
494
+ fails `-32603` "Could not resolve authentication method", which is an auth fact, not support.
495
+ The offered list is not stable inside one adapter process: 5 rows on the first sessions, 14 on a
496
+ later one, and a later session listed `claude-fable-5-1[1m]` beside `claude-fable-5-2`. Only the
497
+ set-model answers above are measured support; an offered row is not.
498
+ - **Effort on a live session.** On one session `high → low → max` each read back exactly with the
499
+ model unchanged; `minimal` was refused `-32602 Invalid params` and the session stayed usable.
500
+ Readback is the adapter's acceptance, not evidence of applied reasoning depth.
501
+ - **Idle compaction** (0.101 #1522) can send the adapter's own `/compact` prompt on an idle session
502
+ (1h cache TTL, ≥100k–200k context, ~54 min idle). 0.106 #1555 makes the inline `settings`
503
+ `idleCompaction` key win when no managed tier sets it. Our retained-session callbacks drop
504
+ notifications that arrive with no active turn, so such a turn would be invisible to pi. Our
505
+ inline `idleCompaction: false` is held only as a `_meta` shape (`ACP-CLAUDE-NO-IDLE-COMPACTION`);
506
+ its runtime effect on an idle session is unmeasured, and a managed tier would win over it.
507
+ - **Inert for us:** #1430 replays pre-compaction history on `session/load` (a client-view replay,
508
+ not a prompt re-dispatch; we never call `session/load`); #1589 concerns the bun single-file
509
+ release executable, not the npm dist we import; auth-status/account description changes are
510
+ notifications we ignore. `bypassPermissions` also shadows `canUseTool`
511
+ (`CLAUDE_SDK_CAN_USE_TOOL_SHADOWED` on stderr), so the permission callback is not a gate for
512
+ ordinary tool calls; the declared `tools`/`disallowedTools` surface is.
513
+
399
514
  - **The one 0.73.0 → 0.75.1 change that DOES reach us:** context compaction is now surfaced as a
400
515
  synthetic ACP tool lifecycle (0.75.0, #991) — a `tool_call` with `kind: "think"`, title
401
516
  `Compact conversation`, and `_meta.contextCompaction` schema v1 — where it used to arrive as
@@ -416,8 +531,8 @@ contract. An optional upstream feature is not a core-value gap.
416
531
  - accumulated inactive minors are best folded into the next meaningful certification.
417
532
 
418
533
  Otherwise record and defer. Every certification separates declared pins, deterministic/package
419
- evidence, Claude LIVE aggregate evidence, and Cortex direct on-demand evidence (with its CLI
420
- version). Per-bump measurements live in the ROADMAP **Dep bump(별도 트랙)** ledger.
534
+ evidence and Claude LIVE aggregate evidence. Cortex ACP is disabled; its old LIVE observations
535
+ remain historical. Per-bump measurements live in the ROADMAP **Dep bump(별도 트랙)** ledger.
421
536
 
422
537
  ### Configured Claude ACP permissions
423
538
 
@@ -444,11 +559,15 @@ operator's repair named.
444
559
 
445
560
  ## Shipped adapters
446
561
 
447
- | Seam | Claude | Cortex Code |
562
+ Claude alone is supported. The former Cortex column below is a historical comparison,
563
+ not an executable route; Cortex model admission and direct adapter use now refuse.
564
+
565
+ | Seam | Claude | Former Cortex Code (historical) |
448
566
  |---|---|---|
449
- | Model ids | unprefixed `claude-sonnet-5-5`, `claude-opus-5-5`, `claude-fable-5-1` | `cortex-auto`, `cortex-claude-opus-5`, `cortex-claude-sonnet-5`, `cortex-openai-gpt-5.4`; prefix stripped before set-model |
567
+ | Model ids | unprefixed `claude-sonnet-5-5`, `claude-opus-5-5`, `claude-fable-5-1`, `claude-fable-5-2` | `cortex-auto`, `cortex-claude-opus-5`, `cortex-claude-sonnet-5`, `cortex-openai-gpt-5.4`; prefix stripped before set-model |
450
568
  | Launch | bundled `claude-agent-acp`; `CLAUDE_AGENT_ACP_COMMAND` override | `cortex acp serve`; optional connection; `CORTEX_ACP_COMMAND` override; never a launch-time `-m` |
451
569
  | Model authority | per-turn ACP set-model, its answer read back before the prompt | per-turn ACP set-model; an unavailable curated id fails before prompt; the answer is not read back |
570
+ | Thinking level | pi's level → `effort`, set and read back before the prompt (§Claude effort) | not declared: no `thinkingLevelMap`, `reasoning` is not carried |
452
571
  | Carrier | engraving in `_meta.systemPrompt` | no system-prompt carrier; engraving rides the first-user augment |
453
572
  | Overlay | `CLAUDE_CONFIG_DIR` whitelist, configured-empty hooks, native memory hidden | session-scoped isolated HOME + `SNOWFLAKE_HOME`, private `cortex/mcp.json`, measured-minimum auth passthrough |
454
573
  | MCP | explicit wire `mcpServers` | explicit declarations projected to private `mcp.json` because Cortex ignores the wire field |
@@ -459,11 +578,10 @@ operator's repair named.
459
578
 
460
579
  The unprefixed Claude curation uses exact Opus/Sonnet 5.5 ids; the ACP adapter does
461
580
  not route the retired exact ids `claude-opus-5` or `claude-sonnet-5`, alias them to
462
- a newer model, or rewrite existing session records. Cortex retains its separate
463
- native 5 ids. Pi owns fuzzy CLI model selection before the adapter: with this
464
- seven-row set, its 1.1.0 resolver matches the old bare 5 patterns to the Cortex rows
465
- instead of refusing them (measured with the pure upstream resolver and synthetic rows,
466
- not a vendor turn). So the launches Entwurf builds admit only an EXACT curated id
581
+ a newer model, or rewrite existing session records. Pi owns fuzzy CLI model selection
582
+ before the adapter: an older seven-row fixture matched old bare 5 patterns to Cortex
583
+ rows (measured with the pure upstream resolver, not a vendor turn). Cortex rows are
584
+ now disabled. The launches Entwurf builds admit only an EXACT curated id
467
585
  (`pi-extensions/lib/acp/model-ids.js`): `entwurf_fresh_call` with backend `pi` and an
468
586
  `entwurf/<id>` model, and `entwurf_resume_call` of a session whose emitted provider is
469
587
  `entwurf`, refuse anything else as `acp-model-not-curated` before a pane opens. A human
@@ -473,8 +591,8 @@ The adapter reads its set-model answer back before the prompt. The driver resolv
473
591
  requested id onto one of the rows it offers and answers with that row's `value` as the
474
592
  model option's `currentValue` (0.85.1 `dist/acp-agent.js:5459-5499`). Measured on
475
593
  2026-10-12 against 0.85.1, and again unchanged on 2026-10-14 against 0.90.0 (the second
476
- time also with the production session meta), with no prompt sent: `claude-fable-5-1`
477
- answers its own row,
594
+ time also with the production session meta), and on 2026-11-13 against 0.106.0 authenticated
595
+ (#142), with no prompt sent: `claude-fable-5-1` and `claude-fable-5-2` each answer their own row,
478
596
  while `claude-sonnet-5-5` and `claude-opus-5-5` answer the alias rows `sonnet` and
479
597
  `opus`, named `Sonnet 5.5` and `Opus 5.5`. The id an alias row resolves to is not on the
480
598
  ACP wire, so `CLAUDE_SET_MODEL_ALIAS_ROWS` (`backend-adapter.ts`) declares, for each
@@ -485,6 +603,38 @@ its own measured answer before it ships. `check-acp-set-model-readback` holds th
485
603
  contract offline; it does not certify the installed driver's runtime, and no 5.5 prompt
486
604
  turn is claimed by this measurement.
487
605
 
606
+ #### Claude effort (#142)
607
+
608
+ Pi's thinking level is carried as the adapter's `effort` session config option. Each curated
609
+ Claude row registers a `thinkingLevelMap` (`claudeAcpThinkingLevelMap`, `models.ts`): `low`,
610
+ `medium`, `high`, `xhigh` and `max` map to the effort value of the same name, `off` and `minimal`
611
+ are `null`. So pi's own selector offers only `low..max`, and pi's normal clamp moves a saved `off`
612
+ or `minimal` to `low` before any turn. The registry still decides which levels a model has: a
613
+ level it marks `null` stays unsupported, and `xhigh`/`max` appear only where it maps them. The
614
+ effort value `default` is the vendor's own choice; no pi level maps to it.
615
+
616
+ At turn entry the adapter resolves the level before any process or session is touched. A turn
617
+ that arrives with no level, an unsupported level, or a map value outside the effort set refuses as
618
+ `acp-thinking-level-unsupported`, with nothing spawned. "No level" covers two cases pi does not
619
+ distinguish on the wire: the agent sends no `reasoning` for `off`, and none when no level is set.
620
+ Pi's own turn and compaction paths always carry a level for these rows after the clamp; the
621
+ refusal is for callers outside that path, and it says so rather than run at a depth nobody chose.
622
+
623
+ A new session sets effort right after set-model; a reused session sets it again only when the
624
+ level changed since the value it last read back. The level is not part of the reuse signature, so
625
+ changing it keeps the live conversation. The answer must read the value back as the `effort`
626
+ option's `currentValue`; a refused set (`-32602` for an unoffered value), a missing option or
627
+ another value fails the turn before the prompt, and a reused session is dropped, so the next turn
628
+ opens a new session and resends the transcript. The text we author reads as neither transient nor
629
+ overflow to pi; the sealed message still appends the session's stderr tail like any other
630
+ pre-prompt failure, and a bootstrap timeout keeps its `timed out` wording, so either can classify as
631
+ transient. A replay of that turn re-dispatches no prompt that ran. Each accepted set shows the operator a lifecycle notice `[acp: effort <value> read back]` (a new
632
+ session always, a reused one only when the value changed; a refused set shows none). Like the other
633
+ lifecycle notices it is excluded from the transcript a later turn sends and from the reuse signature.
634
+ Readback is the adapter's acceptance of the value, not evidence of the
635
+ reasoning depth applied or of turn speed. `check-acp-set-model-readback` holds the contract
636
+ offline (`backend.thinking.test.ts`, five effort mutants).
637
+
488
638
  The bundled adapter resolves before any PATH fallback. Its overlay retains only the
489
639
  auth/runtime state required by the Claude Agent SDK and hides operator memory, hooks,
490
640
  agents, history, and local settings. Rich project/operator context rides the first-user
@@ -504,6 +654,9 @@ rendering contract, not a capability bit flip.
504
654
 
505
655
  ### Cortex Code audit (D1–D10)
506
656
 
657
+ **Historical:** this audit describes the former Cortex ACP adapter, not a supported
658
+ route. The current product refuses Cortex selection before launch.
659
+
507
660
  The original audit labels remain useful coordinates for source comments and gates:
508
661
 
509
662
  | Audit | Landed contract |
@@ -638,15 +791,11 @@ Live axes:
638
791
 
639
792
  ```bash
640
793
  LIVE=1 ./run.sh release-gate /path/to/scratch --cut
641
- LIVE=1 ENTWURF_ACP_CORTEX_CONNECTION=<conn> \
642
- ./run.sh smoke-acp-cortex-live
643
794
  ```
644
795
 
645
- The aggregate release gate is Claude-backed so a host without Cortex/Snowflake auth
646
- can run the package floor. That means Cortex is **on demand**, not optional evidence:
647
- a cut that changes or ships the Cortex rail must run and read its dedicated smoke.
648
- Per-cut counts, digests, versions, and host observations belong in BASELINE/CHANGELOG,
649
- not this standing contract.
796
+ Cortex ACP is disabled; its former direct LIVE smoke refuses without probing a vendor.
797
+ This does not waive the aggregate's Claude, Codex or native-citizen axes. Per-cut
798
+ counts, digests, versions, and host observations belong in BASELINE/CHANGELOG.
650
799
 
651
800
  ## 11-8. Compaction is a tool lifecycle (0.75.0 onward)
652
801
 
@@ -719,8 +868,73 @@ The adapter keeps its own turn-settlement bookkeeping internally, and our event
719
868
  update kinds it does not handle (`event-mapper.ts:377-378`), so nothing above is a claim that
720
869
  Entwurf must replicate vendor internals. The reason to stay put is the five contracts, not a ledger.
721
870
 
871
+ ## 11-10. Pi's own summary is a one-shot, tool-less turn (#163)
872
+
873
+ Pi summarizes for `/compact`, the compaction threshold, overflow recovery and `/tree` branch
874
+ summaries through one call, `completeSummarization` (pi-coding-agent 1.5.2
875
+ `dist/core/compaction/compaction.js:477-489`). That request declares no tools and carries
876
+ `cacheRetention: "none"`; an ordinary agent-loop turn carries no `cacheRetention`. Until #163 the
877
+ tool-surface preflight read the empty surface as "read/bash/edit/write excluded" and refused every
878
+ summary on a Claude ACP model.
879
+
880
+ - **Admission.** `isPiSummaryRequest` (`tool-surface.ts`) needs BOTH an empty replayed tool
881
+ surface and `cacheRetention === "none"`. An ordinary turn with every tool excluded keeps the
882
+ refusal. Pi's prompt text and session id are not read; Pi fills the id with a fresh `uuidv7()`.
883
+ - **The turn.** A new child under the turn-scoped policy, closed after its one prompt whatever the
884
+ process policy says. `tools`, `mcpServers`, skill plugins, the permission allow list and
885
+ `settingSources` are all empty, so no CLAUDE.md, hook or plugin is read from disk. The operator's engraving carrier stays, rendered with an empty MCP list. No first-message
886
+ augment. Pi's summarization instructions arrive as a system message, which the transcript
887
+ flattening drops, so their text (`getCurrentSystemPrompt`) is sent as the first prompt block.
888
+ No notice of any kind: the summary Pi stores is exactly the model's text, and the session file
889
+ is the embedding source. Pi keeps an `aborted` summary (it refuses only `error` and `length`,
890
+ pi-coding-agent 1.5.2 `compaction.js:454-462`), so a `cancelled` stop that Pi did not abort is
891
+ sealed as a non-transient error instead.
892
+ - **After compaction.** `acp-provider.ts` subscribes to `session_compact` and closes that Pi
893
+ session's retained child at once (GLG 2026-11-14), unless a turn is in flight. A session is busy
894
+ only inside its key's in-flight claim, so the in-flight check covers both. The next turn opens a
895
+ new child from the compacted transcript.
896
+
897
+ Proof: `pi-extensions/lib/acp/backend.summary-turn.test.ts` drives Pi's own `generateSummary` and
898
+ `generateBranchSummary` through `streamAcpTurn`, so the request shape is Pi's, not a fixture's. A Pi
899
+ upgrade that stops marking summaries with `cacheRetention: "none"` turns it red. Not covered here:
900
+ the adapter's own compaction inside a prompt we sent (§11-8), and the threshold value.
901
+
902
+ ## 11-11. What a running turn shows (#162, #163)
903
+
904
+ The mapper's tool lines come in two kinds, and the kind is a contract with two readers outside
905
+ this file.
906
+
907
+ | Line | Signed | Replayed into a new session | Reuse signature |
908
+ |---|---|---|---|
909
+ | `[tool:start\|done\|failed\|cancelled] <title>[ — <summary>]`, `[permission:<d>] <title>` | no | yes | yes |
910
+ | `[tool:running] <title>[ — <elapsed>]`, `[tool:output] <title>` + preview, `[acp: …]` | yes | no | no |
911
+
912
+ - `[tool:running] <title>` without elapsed time names a call once its title is known: a Bash call
913
+ opens as "Terminal" and its command arrives as a title-only update. A beat in the same update
914
+ already names the title, so it prints alone.
915
+ - `[tool:output]` shows at most 5 lines and 400 UTF-8 bytes of a finished call's output: the head
916
+ on success, the tail on failure. A line keeps its indentation (tabs expand to three spaces), so a
917
+ stack trace or a diff reads as in Pi. When the whole output fits on the one-line summary, it is
918
+ not shown.
919
+ - Every line, one-line or preview, is cleaned the way Pi cleans tool output before showing it
920
+ (pi-coding-agent 1.5.2 `getTextOutput`: `stripAnsi`, then `sanitizeBinaryOutput`, then CR
921
+ dropped), with DEL and C1 also removed. Pi's own range would leave an 8-bit OSC (`0x9D` … `0x9C`)
922
+ intact. Pi does not export these helpers from its root, so the mapper carries the same
923
+ expression, and a test compares it with Pi's installed functions. pi-tui's public
924
+ `stripTerminalSequences` is not a substitute: it ends a CSI only on `m/G/K/H/J` and scans ahead
925
+ for one, so on tool output it deletes text (`"\x1b[2A keep this text then Hm"` → `"m"`, measured
926
+ 2026-11-14 at pi-tui 1.5.2). No strip-ansi or ansi-regex package is in the dependency tree.
927
+ - `[acp: compaction <trigger>[ failed] <pre>[→<post>][ in <duration>]]` reports the backend's own
928
+ compaction from the `rawOutput` facts of its `compact` tool call, once per compaction.
929
+
930
+ The second reader is andenken's `control-v1` selection (andenken `session-selection.ts`, read at
931
+ `e5165ecc`, 2026-11-14). It drops every signed lifecycle block whatever its text, and drops an
932
+ unsigned block only when it is exactly one sanitized `[tool:start|done|failed|cancelled]` or
933
+ `[permission:*]` line. An unsigned line with a new label, or a changed unsigned grammar, is
934
+ embedded. So a new tool line is signed, and changing the unsigned grammar goes to GLG first;
935
+ andenken is not changed from here.
936
+
722
937
  ## Open work
723
938
 
724
939
  - causal MCP-readiness diagnosis and, only with proof, a backend-invariant fence;
725
- - broader installed-host and cross-machine Cortex evidence;
726
940
  - persisted ACP resume/load, which is not implemented by today's in-memory reuse.