@sema-agent/core 2.13.0 → 4.0.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 (184) hide show
  1. package/CHANGELOG.md +2345 -0
  2. package/dist/agents/cascade.d.ts +6 -1
  3. package/dist/agents/cascade.js +76 -32
  4. package/dist/agents/cumulative-stats.d.ts +29 -0
  5. package/dist/agents/cumulative-stats.js +29 -0
  6. package/dist/agents/observer.d.ts +4 -0
  7. package/dist/agents/observer.js +11 -10
  8. package/dist/agents/repair-loop.d.ts +2 -0
  9. package/dist/agents/repair-loop.js +110 -3
  10. package/dist/agents/retain-ledger.d.ts +20 -2
  11. package/dist/agents/retain-ledger.js +38 -8
  12. package/dist/agents/roster-store.d.ts +4 -0
  13. package/dist/agents/roster-store.js +20 -1
  14. package/dist/agents/subagent.js +66 -64
  15. package/dist/agents/teacher.d.ts +1 -9
  16. package/dist/agents/teacher.js +23 -24
  17. package/dist/agents/team.js +7 -4
  18. package/dist/agents/verify.d.ts +15 -2
  19. package/dist/agents/verify.js +39 -3
  20. package/dist/bin/sema-tb.js +2 -2
  21. package/dist/brain/anthropic.js +24 -21
  22. package/dist/brain/circuit-breaker.d.ts +1 -0
  23. package/dist/brain/circuit-breaker.js +26 -9
  24. package/dist/brain/degrading.js +13 -1
  25. package/dist/brain/failover.js +2 -0
  26. package/dist/brain/openai.js +24 -21
  27. package/dist/brain/repetition.js +10 -1
  28. package/dist/brain/routing.js +5 -1
  29. package/dist/brain/status-sink.js +5 -1
  30. package/dist/brain/stream-engine.js +5 -2
  31. package/dist/brain/stream-shared.d.ts +1 -0
  32. package/dist/brain/stream-shared.js +14 -0
  33. package/dist/brain/terminal-cause.d.ts +4 -0
  34. package/dist/brain/terminal-cause.js +12 -0
  35. package/dist/brain/tool-call-id.d.ts +1 -0
  36. package/dist/brain/tool-call-id.js +3 -0
  37. package/dist/brain/tool-call-repair.js +34 -15
  38. package/dist/core/auto-compaction.js +33 -32
  39. package/dist/core/background-agent-store.d.ts +22 -22
  40. package/dist/core/background-agent-store.js +19 -23
  41. package/dist/core/checkpoint-store.d.ts +7 -13
  42. package/dist/core/checkpoint-store.js +39 -15
  43. package/dist/core/consolidate-scope.js +4 -3
  44. package/dist/core/context-edit.js +3 -0
  45. package/dist/core/context-guard.js +3 -0
  46. package/dist/core/file-snapshot-store.d.ts +8 -0
  47. package/dist/core/file-snapshot-store.js +4 -3
  48. package/dist/core/git-worktree-env.d.ts +6 -1
  49. package/dist/core/git-worktree-env.js +12 -1
  50. package/dist/core/hooks.d.ts +4 -1
  51. package/dist/core/hooks.js +35 -7
  52. package/dist/core/lsp-diagnostics.js +13 -7
  53. package/dist/core/lsp-protocol.d.ts +1 -1
  54. package/dist/core/lsp-protocol.js +20 -8
  55. package/dist/core/lsp-session.d.ts +12 -2
  56. package/dist/core/lsp-session.js +114 -49
  57. package/dist/core/lsp.d.ts +13 -1
  58. package/dist/core/lsp.js +65 -9
  59. package/dist/core/mailbox-store.d.ts +7 -10
  60. package/dist/core/mcp.d.ts +7 -10
  61. package/dist/core/mcp.js +7 -3
  62. package/dist/core/memory-engine/data-plane.js +4 -0
  63. package/dist/core/memory-engine/engine.d.ts +2 -2
  64. package/dist/core/memory-engine/engine.js +96 -31
  65. package/dist/core/memory-engine/file-backend.d.ts +2 -2
  66. package/dist/core/memory-engine/file-backend.js +38 -39
  67. package/dist/core/memory-engine/layout.d.ts +8 -0
  68. package/dist/core/memory-engine/layout.js +71 -4
  69. package/dist/core/memory-engine/scope-contract.js +9 -3
  70. package/dist/core/memory-engine/types.d.ts +1 -1
  71. package/dist/core/memory.js +3 -0
  72. package/dist/core/permission-rules.d.ts +2 -1
  73. package/dist/core/permission-rules.js +24 -3
  74. package/dist/core/runner/active-skill-scope.d.ts +0 -1
  75. package/dist/core/runner/active-skill-scope.js +1 -15
  76. package/dist/core/runner/assemble-result.d.ts +3 -8
  77. package/dist/core/runner/assemble-result.js +3 -5
  78. package/dist/core/runner/compaction-call-options.d.ts +7 -0
  79. package/dist/core/runner/compaction-call-options.js +30 -0
  80. package/dist/core/runner/prepare-task.d.ts +6 -23
  81. package/dist/core/runner/prepare-task.js +31 -53
  82. package/dist/core/runner/runtask.d.ts +1 -0
  83. package/dist/core/runner/runtask.js +86 -178
  84. package/dist/core/runner/session-rule-policy.js +4 -1
  85. package/dist/core/runner/teardown-bounded.d.ts +7 -0
  86. package/dist/core/runner/teardown-bounded.js +36 -0
  87. package/dist/core/runner/tool-disclosure.d.ts +2 -5
  88. package/dist/core/runner/turn-attachments.d.ts +5 -15
  89. package/dist/core/runner/turn-attachments.js +1 -1
  90. package/dist/core/runner/usage-accounting.d.ts +6 -0
  91. package/dist/core/runner/usage-accounting.js +9 -8
  92. package/dist/core/safe-notify.d.ts +17 -0
  93. package/dist/core/safe-notify.js +59 -0
  94. package/dist/core/secret-env.js +5 -1
  95. package/dist/core/sensitive-path-policy.js +11 -4
  96. package/dist/core/store-contracts/checkpoint-store-contract.js +1 -1
  97. package/dist/core/store-contracts/session-repo-contract.js +40 -0
  98. package/dist/core/stub-env.d.ts +2 -6
  99. package/dist/core/surrogate-safe-slice.d.ts +4 -0
  100. package/dist/core/surrogate-safe-slice.js +18 -0
  101. package/dist/core/task-registry-monitor.js +15 -14
  102. package/dist/core/task-registry-shared.d.ts +22 -0
  103. package/dist/core/task-registry-shared.js +16 -0
  104. package/dist/core/task-registry.d.ts +1 -0
  105. package/dist/core/task-registry.js +40 -57
  106. package/dist/core/tool-errors.js +5 -4
  107. package/dist/core/tool-name-aliases.js +2 -1
  108. package/dist/core/tool-policy.js +72 -19
  109. package/dist/core/tool-result-store.d.ts +1 -0
  110. package/dist/core/tool-result-store.js +17 -1
  111. package/dist/core/trace.d.ts +3 -9
  112. package/dist/core/types.d.ts +16 -12
  113. package/dist/core/untrusted-egress.js +7 -5
  114. package/dist/core/workflow-journal-store.d.ts +9 -20
  115. package/dist/core/workflow-run-store.d.ts +11 -10
  116. package/dist/core/workflow-run-store.js +22 -16
  117. package/dist/engine/compaction/compaction.d.ts +1 -0
  118. package/dist/engine/compaction/compaction.js +14 -5
  119. package/dist/engine/compaction/utils.js +4 -2
  120. package/dist/engine/execution-env/node-execution-env.d.ts +2 -24
  121. package/dist/engine/harness/agent-harness.d.ts +1 -0
  122. package/dist/engine/harness/agent-harness.js +13 -33
  123. package/dist/engine/harness/types.d.ts +29 -67
  124. package/dist/engine/llm/types.d.ts +1 -1
  125. package/dist/engine/loop/agent-loop.js +11 -4
  126. package/dist/engine/loop/types.d.ts +11 -9
  127. package/dist/engine/lsp/frame-decoder.js +6 -3
  128. package/dist/engine/lsp/node-lsp-manager.d.ts +2 -0
  129. package/dist/engine/lsp/node-lsp-manager.js +103 -26
  130. package/dist/engine/lsp/stdio-lsp-transport.js +16 -7
  131. package/dist/engine/session/memory-repo.d.ts +2 -6
  132. package/dist/engine/session/session.d.ts +5 -30
  133. package/dist/index.d.ts +3 -1
  134. package/dist/index.js +2 -1
  135. package/dist/internal/harness-types.d.ts +3 -1
  136. package/dist/internal/harness.d.ts +1 -1
  137. package/dist/internal/harness.js +1 -1
  138. package/dist/orchestration/run-spec.js +7 -4
  139. package/dist/orchestration/run-workflow-tool.js +3 -35
  140. package/dist/orchestration/workflow-governance.js +43 -9
  141. package/dist/orchestration/workflow-sandbox-conformance.js +85 -51
  142. package/dist/orchestration/workflow-script-store.js +34 -4
  143. package/dist/orchestration/workflow.d.ts +3 -11
  144. package/dist/orchestration/workflow.js +164 -204
  145. package/dist/prompt-assembly/artifact-store.d.ts +2 -0
  146. package/dist/prompt-assembly/artifact-store.js +39 -24
  147. package/dist/prompt-assembly/assemble.js +38 -7
  148. package/dist/prompt-assembly/epoch.js +19 -2
  149. package/dist/prompt-assembly/event-registry.js +3 -3
  150. package/dist/prompt-assembly/explain.js +1 -1
  151. package/dist/prompt-assembly/tool-catalog.d.ts +10 -9
  152. package/dist/prompt-assembly/tool-catalog.js +16 -1
  153. package/dist/prompt-assembly/turn-snapshot.d.ts +2 -5
  154. package/dist/prompt-assembly/turn-snapshot.js +4 -1
  155. package/dist/stores/file/background-agent-store.d.ts +3 -12
  156. package/dist/stores/file/background-agent-store.js +3 -41
  157. package/dist/stores/file/checkpoint-store.js +6 -6
  158. package/dist/stores/file/fs-atomic.d.ts +3 -0
  159. package/dist/stores/file/fs-atomic.js +53 -8
  160. package/dist/stores/file/index.d.ts +1 -0
  161. package/dist/stores/file/index.js +7 -0
  162. package/dist/stores/file/mailbox-store.d.ts +2 -6
  163. package/dist/stores/file/session-policy-store.js +15 -1
  164. package/dist/stores/file/session-store.d.ts +4 -6
  165. package/dist/stores/file/session-store.js +28 -1
  166. package/dist/stores/file/workflow-journal-store.d.ts +3 -11
  167. package/dist/stores/file/workflow-run-store.d.ts +3 -7
  168. package/dist/stores/file/workflow-run-store.js +4 -17
  169. package/dist/tools/fs/bash-readonly-classifier.d.ts +2 -0
  170. package/dist/tools/fs/bash-readonly-classifier.js +73 -21
  171. package/dist/tools/fs/fs-bash.d.ts +1 -1
  172. package/dist/tools/fs/fs-bash.js +407 -91
  173. package/dist/tools/fs/fs-pdf.d.ts +2 -12
  174. package/dist/tools/fs/fs-shared.js +2 -2
  175. package/dist/tools/fs/notebook.d.ts +2 -5
  176. package/dist/tools/fs/safety.d.ts +3 -0
  177. package/dist/tools/fs/safety.js +43 -14
  178. package/dist/tools/fs/search.js +5 -5
  179. package/dist/tools/loop-tick.js +1 -1
  180. package/dist/tools/monitor.d.ts +1 -0
  181. package/dist/tools/monitor.js +57 -9
  182. package/dist/tools/worktree.d.ts +4 -7
  183. package/dist/tools/worktree.js +235 -143
  184. package/package.json +9 -2
package/CHANGELOG.md ADDED
@@ -0,0 +1,2345 @@
1
+ # Changelog
2
+
3
+ ## 4.0.0 (2026-08-02)
4
+
5
+ _Refactor campaign (50 candidates, three new discipline gates) + a test-discriminance pass + a three-class defect sweep + the closing four-lens review (22 findings, all dispositioned). Major because four config sentinels change MEANING under unchanged types — ENGINEERING-CODE §J2: same name, same type, new behavior is the semver case that compiles clean and computes wrong._
6
+
7
+ **BREAKING — sentinel values (`0` / non-finite) mean what they say now**
8
+
9
+ Each of these was a value a deployment could legitimately have written, folded into "disable this bound". Types are unchanged, so nothing goes tsc-red: check your config for a literal `0`.
10
+
11
+ 1. **`approvalTimeoutMs: 0` denies immediately** (was: wait indefinitely). Omitting the field is the documented "no deadline"; a written `0` is the tightest deadline. Migration: delete the field. A deployment that spelled "no timeout" as `Number(cfg.x ?? 0)` would otherwise deny every `requireApproval` call before a human sees it.
12
+ 2. **`ToolSpec.offloadThresholdChars: 0` disables offload for that tool** (was: offload every non-empty result), matching the global knob. Non-finite disables too. Migration: "always offload" is `1`.
13
+ 3. **`AgentDefinition.maxTurns: 0` is ignored** (was: remove the turn ceiling) across the delegated-child, observer and workflow lanes — the tool-level limit or engine default binds (1000 / 8 / spec). Migration: omit the field or set a large number. `0` also no longer erases a deployment-set `limits.maxTurns`.
14
+ 4. **`InMemoryToolResultStore({maxTotalChars: 0})` is the tightest bound** (was: unbounded) — each put evicts everything but the just-written ref. `NaN` now throws at construction (new failure point for `Number(<unset env>)` wiring; omit the field for unbounded).
15
+
16
+ Non-finite workflow hard caps (`maxAgents` / `maxLogChars` / `maxResultChars`) and tool-result-store caps now throw at construction instead of silently disabling their bound.
17
+
18
+ **BREAKING — `AssistantMessage.errorKind` union widened**
19
+
20
+ - Adds `"degenerate"` and `"walltime_cutoff"` (RB-464: the terminal cause goes typed; the sentinel message strings stay as display text and are read as a transitional fallback). A consumer switching exhaustively over this union with a `never` assertion gets two new arms — this one IS tsc-red, deliberately.
21
+
22
+ **Fixed — never-settle and lost-decision classes (P0)**
23
+
24
+ - `resume()` / `runTask()` could hang forever: every best-effort teardown leg on the settle path (registry settlement, background sweep, subagent disposal, adapter destroy, workspace-state write, unpin, memory harvest) and in prepare's fail-fast paths is now bounded (15s) with a typed, leg-named incident routed to `onError`. A wedged store or adapter no longer leaves a completed-looking stream whose `result()` never settles, and no longer poisons the session lock for every later resume of that session. (RB-470, extended by the closing review)
25
+ - A negative decision (`plan_review`/`dry_run_review` reject, `policy_ask` deny) no longer reopens its checkpoint once **delivered**, and a leg that re-parks (`needs_review`) counts as progressed — the reject → re-plan loop no longer mints a zombie gate beside the new park. An UNDELIVERED negative decision (the resume died before the decision reached the run body) still reopens, so it is never silently lost. (RB-471 + FR-C1)
26
+ - The walltime hard watchdog and the approval deadline keep the event loop alive: an `unref`'d rescue timer fails exactly when the work it guards is wedged (no other handles, loop goes empty, process exits mid-await). (RB-466 second wave, FR-X3)
27
+
28
+ **Fixed — boundary and containment**
29
+
30
+ - **`bash_readonly`'s read boundary judges what a glob EXPANDS to** (RB-474, reported downstream). An operand carrying `*`, `?` or `[…]` names no existing file, so it resolved to its deepest existing ancestor — the root — and passed, after which the shell expanded it and handed the command every match, including an in-root symlink pointing out of root: `cat vendor/secret.txt` was refused while `cat *` over the same link was not. The enforcing layer now expands each pattern against the filesystem and resolves every match. An unmatched pattern still falls through to the shell's own error; an expansion that cannot be established (unreadable directory, unmodellable construct, or past the enumeration bounds) is refused, since this face has no approval channel. Remote environments stay lexical, as before.
31
+ - **Narrowing**: `ls .*` / `cat .*` now refuse, because the shell really does expand `..` there and `<root>/..` is outside — the same answer `ls ..` already gave, now reached through expansion.
32
+ - **Host gate**: `classifyCompoundReadonlyDetailed` (public export) now reports those operands in additive `undecidedPaths` and keeps them out of `checkedPaths`. A gate that auto-allows on containment must treat a verdict carrying that field as undecided — ask, or expand the patterns itself. `readonly` semantics are unchanged; `cd` with a glob fails closed.
33
+ - The memory scope-placement gate folds segment case like its sibling gates — a re-cased in-repo `memoryDir` trips the fail-closed check on case-insensitive hosts. (CLS-B-1, FR-C6)
34
+ - The shell-overflow spool fence folds separators before parents, closing a spelling that re-minted the very `..` key the fence rejects. (CLS-B-7, FR-B3)
35
+ - `deleteBySession` removes only files whose BYTES fail to parse; a transient read error (EACCES/EMFILE/EIO/EISDIR) on the shared directory no longer deletes another session's intact rules while reporting success. (RB-469-c, FR-B1)
36
+ - Lexical-accommodation sweep across the personal-scope placement gate, transcript-dir comparison, worktree dual-authority containment, MCP directory listings and the shell spool fence: all compare canonical segment keys. (CLS-B)
37
+
38
+ **Fixed — reporting honesty**
39
+
40
+ - Six terminal `usage` faces (revive-cycle observer + notification, fork bg lane, agent bg lane) keep cost knownness: `costMicroUsd` is ABSENT when any leg was unpriced instead of a fabricated `0` — "no price list" stays distinguishable from "declared free". (RB-368 contract, CLS-C-5, FR-C4/X1)
41
+ - Malformed SSE frames are disclosed: an unparseable `data:` frame is counted per stream and surfaced as a trailing note; a fully-lost stream folds the count into `[stream_torn]`. (RB-469-b)
42
+ - Memory-scan filesystem failures are disclosed through `onSkip("unreadable")` / a new `HarvestRejectionCode` member / the announcement queue, instead of silently shrinking the authoritative set. (RB-469-a)
43
+ - A failed workflow-agent attempt's observed spend stays on the budget across the retry loop. (RB-468)
44
+ - An injected turn boundary DELIVERS its batch with attribution (`injectedThisTurn` on `postToolBatch`) instead of suppressing it. (RB-467)
45
+ - Monitor's persistent-watch receipt reflects spec-level `retainBackgroundProcesses` (`details.retainedBySpec`). (RB-465-b)
46
+ - Field-mismatch sweep: `canonicalToolName`/tombstone lookups are total over every string (prototype-chain names no longer throw); run-spec's frozen deny gate canonicalizes tool names and write-target field order; revive terminal frames carry nested cost and resume residual like their spawn-cycle pairs; workflow-agent frames stamp `parentToolCallId`. (CLS-C)
47
+
48
+ **Added**
49
+
50
+ - Root exports: `cacheFamilyOf`, `promptTokensOf`, `uncachedInputTokensOf`, `type CacheFamily`.
51
+ - `MonitorToolOptions.retainBackgroundProcesses`; `monitor-start` card field `retainedBySpec`.
52
+
53
+ **Internal**
54
+
55
+ - Host callbacks converge on one isolation primitive with uniform per-site failure counting; two verify `onRound` arms documented as swallowed but actually unguarded are fixed, and the workflow `onBackgroundChildEvent` leg joins the same scope. The primitive's disclosure channel is opt-in and no owner wires it yet (RB-473). (RB-463)
56
+ - Three mechanical gates added (fail-loud swallow scan, domain lexicon, single-mint registry) and the CI discipline gates now run on the release lane too — previously the one run that published was the one run that skipped them. Fast/slow CI lanes: ordinary pushes run tsc + gates (~5 min), release pushes keep the full price.
57
+
58
+ ## 3.0.0 (2026-08-01)
59
+
60
+ _Hardening wave: 105-row ledger (8 domains + LSP + codex cross-review), all HIGHs hand-mutation-verified. Major because usage-field semantics flip (below) — was slated as 2.14.0; ENGINEERING-CODE §J2 forces major for same-name-new-meaning on the npm face._
61
+
62
+ **BREAKING — usage faces carry protocol semantics (RB-457-a)**
63
+
64
+ - `promptTokens` / `turn_end.usage.inputTokens` / `brain.call.promptTokens` used to carry the CACHE-INCLUSIVE prompt total under names the Anthropic protocol reserves for the cache-MISS count. Consumers computing the standard `cacheRead / (input + cacheRead)` double-counted the cache subset: hit rate was bounded above by 50% (a real 98% surfaced as 49.5%; confirmed end-to-end with a capture proxy). These fields now carry the protocol meaning (non-cached input); the cache-inclusive total moved to the new **required** `totalInputTokens` on the same faces. Migration is mechanical: old `inputTokens` ≡ new `totalInputTokens`; dashboards/billing keyed on the old meaning switch field name, hit-rate formulas keep the formula and go correct. The required field makes constructors tsc-red (deliberate — tier-① propagation).
65
+
66
+ **BREAKING — `pruneWorktrees` returns an outcome**
67
+
68
+ - `Promise<void>` → `Promise<{ok:true} | {ok:false; detail:string}>` (exported face). Callers that ignore the return value compile and behave unchanged; callers that awaited-and-assumed-success can now see a failed prune (previously indistinguishable from success).
69
+
70
+ **BREAKING — cascade behavior narrowings**
71
+
72
+ 1. A custom cascade gate's positive verdict counts only on a **completed** rung: a status-blind gate ("text non-empty") could keep a blocked/failed/timeout rung and report `cascadeOutcome: "passed"`. Rejection is recorded (`passed:false`, `attempts[].statusRejected`) and the cascade escalates. The "partial answer is the deliverable" use stays behind the explicit `CascadeConfig.acceptPartial` opt-out.
73
+ 2. `totalTimeoutMs` now bounds the rung **in flight** (AbortSignal armed with the remaining budget, combined with the caller's signal), and the pre-dispatch deadline check applies to every rung including the first. Previously the ceiling was only a check between rungs: a single long rung ran unbounded.
74
+
75
+ **Security (agents/team)**
76
+
77
+ - A team discussion topic (caller/external text) no longer interpolates into member SYSTEM prompts (`Topic: <text>` spoke with system authority to every member every round). It rides the member's user-lane turn prompt inside the same cleaned `<topic>` data frame the synthesizer already used.
78
+
79
+ **Fixed — process survival & never-settle (LSP, HRD-LSP-1..20)**
80
+
81
+ - `defaultLspSpawn`: cancelling during spawn no longer leaves a zero-listener `'error'` emitter (an async ENOENT then crashed the HOST process), and `kill()` is guarded on `pid !== undefined` (killing a never-spawned child signals the caller's own process group — observed killing the test runner's shell).
82
+ - Six never-settle seams closed with two new primitives, exported for reuse: `SharedAbortScope` (a shared dedup'd job aborts only when EVERY caller has abandoned it — never inherits the first caller's signal) and `settleOnAbort` (each caller settles on its own abort while the shared work continues). Three silent-wrong-answer seams: "binary missing" vs "initialize failed" no longer collapse to one `undefined`; a failed doc-sync no longer serves stale queries as fresh.
83
+ - Rescue-timer class fix (RB-466, extends 2.13.0's `withRetry` fix): five more foreground-awaited rescue timers in the runner were `unref()`d — the guard failed exactly when the guarded work was wedged (idle loop → process exit mid-await, intermittent hang). Watchdog timers that only fire side effects stay unref'd; a source-scan pin holds the line.
84
+
85
+ **Fixed — orchestrator (repair loop / monitor / worktree)**
86
+
87
+ - Repair loop: never grades an unfinished worker attempt; enforces the attempt ceiling before dispatch (budget re-checked before a post-fix verifier); a resumed loop feeds restored diagnostics into attempt 1; oracle spend is accounted and reported cumulatively; a verifier's own durable pause surfaces instead of orphaning its checkpoint.
88
+ - Monitor: discloses `min(watch timeout, env background wall)` instead of the larger lie; a session-less/retained persistent Monitor no longer claims session-bounded lifetime; registration failure reports the real kill outcome; a failed watcher start rolls the monitor row back; the autonomous-loop appendix stops routing Monitor ids through TaskList (deliberate divergence from CC, registered).
89
+ - Worktree: `EnterWorktree` on a shared session ref is serialized (CAS-reject — the public export is reachable concurrently); the ref clears only at a terminal exit outcome; `ExitWorktree` reports what actually happened (three false-success faces fixed) and its no-op sentence names its one exception.
90
+ - Bundled roster stores get an opt-in growth bound; retain-ledger detached evictions can no longer unhandled-reject.
91
+
92
+ **Store contracts**
93
+
94
+ - The SessionRepo kit discriminates delete / fork / export-import (RB-448); ownership rows follow the same axes as 2.13.0's fork rules.
95
+
96
+ **Process (ships with this release)**
97
+
98
+ - `docs/ENGINEERING-CODE.md` v3.1 — the three-repo engineering standard (every rule cites a real defect). Three new mechanical gates: `gate:ledger` (hardening ledger completeness), `gate:compensation` (compensations expire or die; baseline **0 rows**), `gate:message-branching` (no branching on error TEXT — AST walk, identity allowlist, enumerator floor assertion). `docs/RELEASE-TIERS.md` — gate weight scales with blast radius.
99
+
100
+ _Pre-release full review wave: 14 function points independently re-reviewed (self + cross-check), 32 findings dispositioned; plus the black-box specials' five reds. Six behavior changes called out below._
101
+
102
+ **Fixed — hang (report: black-box test line, [2236])**
103
+
104
+ - `withRetry`'s default backoff timer is no longer `unref`'d. A timer with a foreground awaiter is a link in control flow: on an otherwise-empty event loop Node shut down mid-backoff and the caller's `resume()` never settled (exit 13, "unsettled top-level await") — observed as an ~80% intermittent hang on the remote transient-retry path; deterministic in a bare child process. The timer is bounded by the backoff itself, so it cannot wedge a shutdown. Pins run a real child process (an in-worker pin cannot see the defect).
105
+
106
+ **Behavior changes (called out)**
107
+
108
+ 1. A **rejected `plan_review` re-arms read-only plan mode** on the resume leg; approve/edit stay writable. The park itself proves the run was in plan mode (present_plan is that mode's only exit), so no schema change. Previously the rejected plan's very next write executed — the veto was prompt-only. Affected: any resume flow relying on reject-then-write; the re-plan exit is present_plan → approve.
109
+ 2. A **crashing `userPromptSubmit` hook fail-closes the task as `blocked`** (reason names the hook; `onError` gets `phase:"hook"`) instead of `failed` with the hook's exception text impersonating a model error. A crashed prompt FILTER no longer waves the prompt through.
110
+ 3. The **offload/tool-result store namespace follows the trust axis** (`spec.principal ?? "default"`), not the registry domain: a mount-declared `registryScope` no longer moves it. A deployment that used `registryScope` as tenant isolation for offloaded refs should use `principal` (in-memory refs don't cross processes — no migration).
111
+ 4. **`allowed-tools` specifier classification normalizes whitespace before matching**: `Bash(git:* )` (trailing space) now enforces the word-bounded git prefix — it used to fall into the wildcard branch, denying `git status` while admitting argv[0]s like `git:status`.
112
+ 5. **`createSearxngSearchBackend`'s `q`/`format` keys are reserved**: an `extraParams` entry keyed on them loses (it used to silently clobber the query/JSON contract with a misleading diagnostic). Base URLs carrying a query string now keep their pairs AND reach the `/search` path; an unparseable base fails at construction.
113
+ 6. **`fork()` registers the ownership row** (explicit owner, else inherited from the source — an explicit `null` deliberately does not downgrade an owned source); `sweep()` drops the row only after the history delete actually lands; a first `register()` whose owner would read back as `undefined` is refused. Fork takeover by a second principal is closed.
114
+
115
+ **postToolBatch / hooks (black-box specials follow-through)**
116
+
117
+ - `postToolBatch` is suppressed only at the boundary a final-verify injection landed on (delta, not the cumulative counter) — one injection used to silence the deployment observer for the task's remaining turns. The model-facing reminder gate keeps its permanent latch (politeness, not audit data).
118
+
119
+ **bash_readonly (review follow-through)**
120
+
121
+ - grep's pattern-supplied-by-flag detection understands GNU long-option **abbreviations** (`--reg=` … `--regexp=`) and digit-prefixed clusters (`-2e`), judged by option NAME per getopt semantics instead of literal prefixes — both directions (a swallowed file operand escaping the boundary check; an abbreviated pattern payload with a slash being wrongly refused). Same-family: `cut`'s glued delimiter and `--file=-` stdin forms.
122
+ - The lexical root-boundary check's disclosure is now bidirectional (the symlink-through-the-root leak direction was undisclosed; the resolution upgrade is designed and filed, not in this release).
123
+
124
+ **web_fetch summarizer (upgrades RB-440, same release)**
125
+
126
+ - `WebFetchConfig.summarize` may return `{ text, truncated }` (additive union; plain strings still valid): the truncation fact rides the structured form so the tool renders the disclosure OUTSIDE the untrusted fence and stamps `details.truncated`. The reference summarizer also flags `partialFinalized` "stop" messages (salvaged mid-stream failures) — previously returned as complete summaries on the stream-derivation path.
127
+
128
+ **Scatter fixes (review follow-through)**
129
+
130
+ - CronCreate's upsert discriminator is serialized (same pattern as ScheduleWakeup's existing lock); the character-whitelist refusal no longer claims 6-field cron is acceptable.
131
+ - The terminal retry status frame distinguishes a wall-clock deadline cutoff from "retries exhausted".
132
+ - `EAI_AGAIN` (underscored errnos generally) now classifies as never-delivered on the MCP transport-death path; the directory-read InvalidParams return path collapses double-stamped `McpError` messages like every other face.
133
+ - MCP death disclosure, direct-lane hardening: consumed direct-call placeholders can't be replayed by toolCallId reuse; `tool_search` and direct activation share one serialized chain (`createToolSearchTool` gains optional `serializeActivation`, additive); a workflow agent's terminal frame falls back to the row's published coordinate; replay-side activation needs an explicit non-error result; a late progress tick from an abandoned attempt is dropped by session token.
134
+ - `probeSearchBackend` clears its race timer on settle (a fast probe no longer holds a short-lived script open for the full budget) and survives null-prototype rejections; malformed SearXNG result rows drop instead of throwing.
135
+ - The rewind `too_large` latch is first-refusal-wins under racing snapshot attempts; `rewind.conflicting_targets` joins the service guide's prepare-throw contract and its pin test (plus a 2.11.0 errata: that change was a narrowing, not merely additive).
136
+ - `Brain.complete` carries its contract in the type (`CompleteSimpleFn`): implementors see the summary-path recovery requirements where they read the signature; `brainToRuntime` forwards by reference (never re-wraps — recovery keys on `errorKind`).
137
+ - `AgentTranscript` gets the RB-409 `enrichCtx` wiring (its `ctx.subagentRetain` read was permanently undefined).
138
+ - `addWorktree` registers `/.sema-worktrees/` in the git-resolved `info/exclude` (idempotent, best-effort — `git add -A` in the base repo no longer stages managed worktrees as gitlinks).
139
+ - `detectSecret` documents its boundary: a best-effort tripwire for high-signal token formats, NOT a DLP boundary (known-through shapes listed; per-format widening verdicts filed).
140
+
141
+ **Test infra**
142
+
143
+ - A `noUncheckedIndexedAccess` ratchet (frozen baseline, regression-only) and six typed stub factories (~367 double-hop casts across 103 test files collapsed; two factories are assertion-free — a new required interface member fails compilation at the factory).
144
+
145
+ **New API (additive)**: `WebFetchConfig.summarize` object return form; `CompleteSimpleFn` (Brain.complete's named type); `createToolSearchTool` `serializeActivation` option.
146
+
147
+ ## 2.12.0 (2026-08-01)
148
+
149
+ _Emergency direct-publish path; pre-tag `gate:blackbox` + post-publish fresh-install smoke. One capability narrowing called out (isSuspendable requires the full restore surface)._
150
+
151
+ **MCP (black-box audit follow-through)**
152
+
153
+ - Streamable-HTTP transport death now gets the same honest translation the stdio leg has had: the server name, MCP context, a delivered-vs-unknown verdict (never-delivered errnos like ECONNREFUSED say "the call was not attempted"; mid-flight failures say the outcome is unknown, with the write-capable caution), and the raw error preserved as `cause` with remote-controlled text fenced. Previously a dead HTTP endpoint threw a bare `TypeError: fetch failed` with no context at all — the model could not tell a dead MCP server from any network hiccup. `details.httpStatus` rides along (additive).
154
+ - `MaterializedMcp.statuses` documents its snapshot semantics (connect-time verdict; liveness via `refresh()`), and a new optional `McpServerStatus.transportClosed` mirrors the engine's own fail-fast knowledge where a close notification exists (stdio; the HTTP transport has no close event — documented). A first-cut implementation stamped every healthy server at dispose() and was caught in-car: the mark is a MID-TASK effect only.
155
+ - Double-stamped `McpError` messages ("MCP error -32602: MCP error -32602: …") collapse to one stamp on both rendering faces; differing leading stamps are server data and stay.
156
+
157
+ **Remote execution environments (black-box audit follow-through; one narrowing)**
158
+
159
+ - The eleven remote error codes stop collapsing: suspend failures land on the new `TaskResult.remoteEnvFailures` (op/code/retryable/attempts — the terminal `errorCode` deliberately keeps its recoverability semantics), resume failures carry their specific code via the error's `remoteEnvFailure` seat, the transient family (`RETRYABLE_REMOTE_ERROR_CODES`) gets one backoff retry on `resumeVM` ONLY (the one op whose seam declares idempotence; snapshot-taking ops are never re-fired, just labeled retryable), and the Bash exec lane gains `suspended` ("ask for a resume") and `auth_failed` ("do not retry") guidance arms beside the existing transport_lost one.
160
+ - **`isSuspendable` requires the complete restore surface** (`resumeVM` + `postResumeInit`): an adapter declaring `suspendable: true` without them used to suspend fine and explode on the resume leg with a bare TypeError; it is now refused at suspend time with the missing methods named — and deliberately does NOT fall back to park (park's invariant is workspace persistence outside the engine, which a snapshot-backend adapter does not have). Park itself becomes caller-visible: `TaskResult.workspaceRestoreMode` / `CheckpointSummary.restoreMode` (`"snapshot"` / `"park_only"`) tell a consumer whether the VM is still running and billing.
161
+ - Workspace rebase after a mount divergence canonicalizes through the ENV's own `canonicalPath` (a remote path realpath'd on the host resolves to the wrong machine's answer — worse than not resolving), accepts both spellings of the old root when truly moved, and degrades to exact-spelling migration with disclosure when the old root cannot be resolved. This also un-breaks alias-spelling resumes carrying pending approvals, which used to fail closed and burn the human decision.
162
+
163
+ **Scheduler (black-box audit follow-through)**
164
+
165
+ - `CronCreate` discloses upserts: colliding with an existing job's schedule+label answers "Updated scheduled task X … whose prompt is now gone" (`details.replaced`), detected against a pre-schedule listing snapshot (the backend seam has no created-vs-updated echo; two registered limitations documented in-tree).
166
+ - The cron intake matches what actually fires: 5-field expressions only — a 6-field (seconds) expression is refused loudly naming the granularity floor, instead of being accepted, persisted, and silently dropped by the scheduler's first tick. `CronList` strips the backend's display prefix before rendering (`details.cron` now carries the bare expression — a small wire change).
167
+
168
+ **New API (additive)**: `TaskResult.remoteEnvFailures` / `RemoteEnvFailureNote` / `TaskResult.workspaceRestoreMode` / `CheckpointSummary.restoreMode`; `missingRestoreSurface` / `isRetryableRemoteErrorCode` / `RETRYABLE_REMOTE_ERROR_CODES`; `ExecutionErrorCode` gains `"suspended"` / `"auth_failed"`; `McpServerStatus.transportClosed`; `details.httpStatus` on MCP transport failures.
169
+
170
+ ## 2.11.0 (2026-08-01)
171
+
172
+ _Emergency direct-publish path; pre-tag `gate:blackbox` + post-publish fresh-install smoke. One behavior change called out (delegation-tree registry scope)._
173
+
174
+ **Security / multi-tenancy (deployment-incident follow-through)**
175
+
176
+ - `SessionStore` gains the session-ownership capability face (optional methods, additive): `ownerOf(sessionId)` is THREE-state — `undefined` = no ownership row, `null` = exists-and-anonymous, `string` = owned — and the states must never collapse (collapsing breaks a deployment's owner gate in both directions at once); `register(sessionId, owner)` is an idempotent, atomic, first-write-wins upsert that makes NO authorization decision (the authorizer re-reads and judges). `TtlSessionStore` implements both, with the ownership row's lifetime following the HISTORY (never dying before the session — an evicted-but-durable session must not read as re-claimable). This closes the default-store half of a deployment-side incident where the authorizer's capability probe fail-opened on the memory store.
177
+
178
+ **Delegation tree (behavior change, called out)**
179
+
180
+ - **One delegation tree, one registry scope domain**: a deployment that declares `background.scope` on its mount but passes no run `principal` used to spawn children whose registry-facing tools operated in `"default"` scope while their rows lived in the mount's scope — the agent (and the HOST's own poll tools) could not see its own tree. Spawn and revival now resolve one `treeScope` (`revived row's scope ?? ctx.principal ?? mount scope`) feeding both the row registration and a new trusted `RunInternals.registryScope` seat (additive). Deliberately NOT folded into `principal` itself: that axis also keys checkpoint namespaces and runtime caps, and widening it moves an unattended safety park out of its isolation bucket (a design/153 no-widening pin catches exactly that — the fix uses a dedicated axis instead). Revival keeps row-first resolution, same rule as parentage.
181
+ - The two revival entry points' tool-face divergence (spec snapshot vs reviver ctx) is REGISTERED as intentional — the durable row is forbidden from carrying a serialized spec (lookup keys only), so a third lossy source would be worse than the documented fork; the row's own FACTS (parentage, scope) are pinned identical on both legs.
182
+
183
+ **Rewind**
184
+
185
+ - `resumeAt` and `rewindFilesTo` together now fail loud (`rewind.conflicting_targets`, naming both fields) instead of silently dropping the code-only target.
186
+ *(Errata, added 2.13.0: this is a behavior NARROWING, not merely additive — a previously-accepted request shape (`resumeAt` set, `rewindFilesTo` incidentally set, `rewindFiles` off) that used to complete now fails with this code. Affected callers: grep your request builders for `rewindFilesTo` threaded unconditionally next to `resumeAt`.)*
187
+
188
+ **Type-hygiene (audit follow-through — gate rebuilt, three quick wins)**
189
+
190
+ - The type-hygiene gate now counts on the TypeScript AST instead of line regexes: prose/comment false positives are OUT of the baseline (25 files carried them), per-assertion counting is exact, `.d.ts` exclusion actually exists, and split-across-lines spellings can't slip through. Two new standing rules: `src/` bans `@ts-ignore`/`@ts-nocheck` (currently zero), and `any` type positions are frozen to a 12-entry allowlist. The B14 export-name rule reads AST export names (four spelling bypasses closed).
191
+ - `isToolResult` and the consolidation-capability checks are real type guards now (11 loose assertions dissolved at their consumers); `checkStale` accepts `ReadEntry | undefined` and fails closed (three cross-await non-null assertions gone; the concurrent-eviction shape turns from a TypeError into the tool's own honest re-read refusal). One real defect surfaced en route: `getByIds` was consumed by every consolidation batch but guarded by no capability gate — a store with cursors but no `getByIds` threw a bare TypeError inside a never-throws function; it now degrades to a warned no-op inside the gate.
192
+
193
+ **New API (additive)**: `SessionStore.ownerOf` / `SessionStore.register` (optional interface methods + `TtlSessionStore` implementations); `RunInternals.registryScope`; error code `rewind.conflicting_targets`.
194
+
195
+ ## 2.10.0 (2026-08-01)
196
+
197
+ _Emergency direct-publish path; pre-tag `gate:blackbox` + post-publish fresh-install smoke. Two behavior-narrowing changes called out below (rewind fail-loud, revive settle throw)._
198
+
199
+ **Rewind disclosure (black-box audit findings — behavior change)**
200
+
201
+ - **A restore with no snapshot now FAILS the task** instead of completing with an untouched tree: the `"at"`-mode `resumeAt+rewindFiles` and the code-only `rewindFilesTo` entrypoints join `"before"` on `rewind_snapshot.unresolvable`; requesting a file rewind with no `fileSnapshotStore` configured fails with the new code `rewind.store_unconfigured` naming the missing dep. Both were silent-swallow paths (completed + host-only onError, or nothing at all).
202
+ - Soft-but-disclosed (deliberate exemptions, argued in-tree): a snapshot-capable request on a stub env, and capture-side no-store, surface through the new additive `TaskResult.rewindNotes` (`conversation_only` / `files_env_unsupported` / `snapshot_store_unconfigured`) — including the plain `resumeAt`-without-`rewindFiles` case, which now says "conversation rewound, files not touched".
203
+ - The `too_large` snapshot refusal tells the truth about its 30-minute cooldown latch (the old text said "shrink the tree to re-enable", which the latch made false); skips inside the window announce once per window instead of never.
204
+
205
+ **Background agents (behavior change)**
206
+
207
+ - `settleBackgroundAgent` on a revived row with an ABSENT cycle stamp now **throws** (naming `settleRevivedAgent` and the cycle to pass) instead of silently no-oping — an uninformed caller learns which line to change the moment it matters, while a STALE stamp (the legitimate racer that lost) still no-ops silently. The engine's own spawn legs stamp `cycle: 0` explicitly.
208
+ - Workflow-agent (`wa*`) fleet rows publish `sessionId`/`transcriptId` from SPAWN and on every tick (previously terminal-only) — the running-period transcript seed consumers were waiting for.
209
+ - Background children's fleet rows beat INSIDE a turn (tool-lifecycle activity ticks, same `tick` frame shape, `usage` carries `toolUses` only — no fabricated token counts), throttled by the same knob the observer lane already uses. A child inside one long turn previously published nothing.
210
+
211
+ **Deferred tools (direct-call visibility)**
212
+
213
+ - A direct-call activation now carries the agent-types listing ride on the real tool's result tail (same channel and rules as the ToolSearch leg), and transcript replay recognizes direct activations: a deferred tool's SUCCESSFUL tool call is itself the activation evidence (paired by toolCallId, error results never count), so the next leg re-materializes it instead of silently reverting to a placeholder. No new carrier; old transcripts replay correctly.
214
+
215
+ **Search (design/162, the zero-key rung)**
216
+
217
+ - `createSearxngSearchBackend(baseUrl, options?)` — a `WebSearchConfig.search` backend speaking SearXNG's JSON API; the deployment assembles it from a base URL, core owns no process management. `allowed_domains` maps to a `site:` prefix as an optimization (the tool re-enforces the domain floor either way). Plus `probeSearchBackend` (never throws; classification is the answer) and `SearxngBackendOptions`.
218
+
219
+ **New API (additive)**: `createSearxngSearchBackend` / `probeSearchBackend` / `SearxngBackendOptions`; `TaskResult.rewindNotes`; error codes `rewind.store_unconfigured` (new) and `rewind_snapshot.unresolvable` (reach widened to all three rewind entrypoints).
220
+
221
+ ## 2.9.0 (2026-08-01)
222
+
223
+ _Emergency direct-publish path (CI publishing resumes 8/1); pre-tag `gate:blackbox` + post-publish fresh-install smoke. No BREAKING changes; one behavior-narrowing contract change (revive settle cycle stamp) called out below._
224
+
225
+ **API retry chain (user-reported, rate-limited third-party providers)**
226
+
227
+ - `maxRetries` defaults to 10 (was 2), configurable via explicit brain options (highest priority, uncapped) or `SEMA_MAX_RETRIES` (clamped to [0,15]); `retryDelayMs` base is now 500.
228
+ - Backoff is increasing-with-light-jitter — `min(500·2^(n-1), 32000) + rand·0.25·backoff` — replacing full-jitter (attempt N could previously wait less than attempt 1). `Retry-After` keeps its existing max-composition semantics; `anthropic-ratelimit-unified-reset` is now honored the same way (header values clamped to 60s against hostile headers).
229
+ - Retry liveness reaches the task event stream: `status` frames carry `attempt`/`maxRetries`/`retryInMs`, waits longer than 30s re-announce per slice, recovery emits a `recovered` terminal and exhaustion a `gave_up` terminal (phases added to `BrainStatusPhase`), and `circuit_open` carries the remaining cooldown. Clients can finally clear a stale error row the moment the stream recovers.
230
+
231
+ **Security (read-only bash boundary — bypass closure, disclosure correction for 2.8.0)**
232
+
233
+ 2.8.0's boundary checked the token stream but not what the SHELL does to it before argv exists. An adversarial-enumeration pass (red-pinned before fixing) closed: brace/tilde expansion forms (`cat {<abs>,}` — exercised end-to-end and refused with no content leak), bare `-` operand slot accounting (`grep - <path>` read the file as grep's stdin-pattern slot went unconsumed), attached flag payloads (`--file=<rel>`, `-f<path>`) and clustered short options (`-if` = `-i -f`), spool-exemption scope (the advertised `tail -c` recovery command now re-plays across tool instances via a fenced spool-area shape check — forged same-name paths outside the engine's spool area still refuse), and a `(0,1)` timeout-value float that validated as 0. An integration pass over the batch then caught and fixed one over-reach the batch itself introduced: grep PATTERN payloads (`-e/…`, `--regexp=/…`) are not path candidates (getopt cluster ownership: `-ie/x` is a pattern, `-fe/x` is the file `e/x`).
234
+
235
+ **Deferred tools (direct-call lane hardening)**
236
+
237
+ - The direct lane resolves the real tool from the LIVE roster at call time — after an MCP refresh replaces a server's tools, a still-deferred name executes the refreshed tool under the refreshed schema (previously a prepare-time snapshot adjudicated with, and called into, the withdrawn object).
238
+ - Direct-form placeholders mirror the real tool's `executionMode` (a deferred read tool schedules parallel again; the hand-written form declared nothing and everything went sequential), activation is staged with rollback (a failed re-materialization no longer leaves a poisoned half-activated name), and `onUpdate` progress forwards through the direct lane (MCP progress notifications were going dark).
239
+ - `alwaysLoadTools` now crosses the delegation tree end to end: a `ToolExecuteContext.alwaysLoadTools` seat (additive), the enricher fills it, and all five spawn-clone sites thread it — a pin the operator declared no longer silently re-defers in every delegated child. The auto-defer trip budget measures the whole inline face (pinned bytes count as pressure; previously one large pin held the valve shut exactly when the request was heaviest). An operator's explicit `deferTools` entry now beats an MCP server's self-declared `alwaysLoad` (trust order: operator > server metadata).
240
+ - ToolSearch/placeholder wording matches the direct-call reality per posture (strict mode keeps the absolute "activate first" phrasing, which is true there).
241
+
242
+ **SendMessage addressing parity (completes the 2.4.0 teammate-reachability work)**
243
+
244
+ - The durable-roster rung and the not-found diagnostics (did-you-mean, running-agents footer) now retry with the parent view — a sibling that could already deliver by name gets the same resolution ladder and the same diagnostics as the main conversation.
245
+ - A parked target answers with parked wording (`parked_pending_approval`) instead of "just finished — send again to continue it from its transcript" (park is a primary producer of that race window, and the old wording taught a wrong mental model).
246
+ - Delivery frames carry ONE addressable sender: `task_id` now matches `teammate_id` (both the sender label) instead of exposing an unaddressable internal host id beside it.
247
+ - A sibling-initiated tier-3 revival no longer re-parents the woken agent under the reviver: parentage (task/session/root axes) follows the durable row, while the completion-notification seat stays with the reviver (it asked for the work; per-axis pins isolate each line). The revive clear-list is single-sourced (`REVIVED_ROW_CLEARED_FIELDS`) across both revival entry points — `finalOutputFull` and the error-classification triplet clear with everything else.
248
+ - The sender-NAME axis is ctx-first like every other identity axis (`ctx.spawnedAgentName`), so a mount whose identity arrives only through the context enricher is recognized as a child.
249
+
250
+ **Background agents / workflow (contract change called out)**
251
+
252
+ - **`TaskRegistry.settleBackgroundAgent` is cycle-stamped**: a settle carrying a stale revive cycle is a no-op (the original spawn's late settle can no longer masquerade as the revived cycle's terminal). External callers settling a revived row must pass `cycle` or use `settleRevivedAgent`; settling never-revived rows is unchanged (cycle defaults to 0 on both sides).
253
+ - The durable-fallback poll face carries the same `details` field set as the live face (the `error` key was missing), single-sourced with the state gates (killed ⇒ `stoppedBy`, failed ⇒ error triplet).
254
+ - `isolation` treats falsy (`null`/`false`/`""`) as absent; a truthy non-`"worktree"` value still refuses loudly.
255
+
256
+ **Compaction**
257
+
258
+ - The pair-containment guard gained a forward-fold fallback: when protecting a tool pair would push the floor back to the previous compaction's start (leaving nothing to summarize — the pass silently refused to compact at all), the pair folds into the summary instead; termination of the fold scan is mutation-proven.
259
+ - The provider-reuse leg carries the same disclosure load as the LLM leg: `persistedOutputRefs` (offloaded-output handles survive — previously permanently unaddressable after a reuse compaction) and `elidedMessages` (the cumulative fold count no longer resets across a reuse pass).
260
+ - End-of-task compaction failures are visible: a `compaction_outcome{outcome:"failed"}` stream event (the boundary and breaker legs already had one; the `finish()` leg only reached the host `onError`), and the rapid-refill disable notice now follows the `compacted` event it refers to instead of preceding it.
261
+ - Skills loader (2.6.0 follow-through): `allowed-tools` accepts the spec's comma/flow-sequence forms (whitespace-only splitting shredded them into garbage names, which the tighten-only rule then turned into an all-tools-denied skill frame), the deployed-tools intersection canonicalizes names like the enforcement layer does, block scalars (`|`/`>-`) parse instead of storing the indicator as the description, symlinked skill directories resolve (with the resource walk fenced to the skill root after following links), `disallowed-tools` is enforced as pure subtraction (with a warning when nothing can enforce it), and `allowed-tools: "*"` means "no narrowing" (previously became an all-deny). MCP protocol-range error-code naming reaches all four rendering points.
262
+
263
+ **New API (additive)**: `ToolExecuteContext.alwaysLoadTools`; `BrainStatusPhase` gains `recovered`/`gave_up`; `BrainStatus` gains `attempt`/`maxRetries`/`retryInMs`; `SkillsDirectoryWarningCode` gains `disallowed_tools_unenforced`/`read_failed`.
264
+
265
+ ## 2.8.0 (2026-07-31)
266
+
267
+ _Emergency direct-publish path (CI publishing resumes 8/1); pre-tag `gate:blackbox` + post-publish fresh-install smoke. No BREAKING changes._
268
+
269
+ **Security (read-only bash leg — enforcement, completes the 2.7.0 boundary work)**
270
+
271
+ - `bash_readonly` now refuses reads outside the workspace roots. 2.7.0 shipped the classifier's SIGNAL face for the approval-gated full-bash leg (out-of-root read → narrow-grant prompt); the read-only leg has no approval channel to escalate to — `effect:"read"` is what lets it run ungated — so out-of-root reads there are now hard-refused before anything executes. Refusal is an `errorResult` naming the offending path with `details { code: "readonly_out_of_root", paths }`, and the tool card states the boundary up front. Unresolvable operands (`~user`, bare `cd`) refuse conservatively. In-root and path-free command handling is byte-identical (regression-pinned), and the engine's own truncation-overflow recovery file stays readable (exact-path, per-instance) so the advertised `tail -c` recovery route keeps working. The check is lexical (no filesystem round-trip): a non-canonical spelling of an in-root path (e.g. a symlinked `/tmp` prefix) refuses with both the path and the roots named, so recovery is a one-call respell. Internally the boundary scan + tokenizer are single-sourced between the compound and simple faces (`classifySimpleCommandReadBoundary`), keeping the two bash legs on one verdict-minting point.
272
+
273
+ **Behavior**
274
+
275
+ - First-party SendMessage mounts now execute with the full per-call tool context: `defineTool(spec, options?)` gained a mount-side context-enricher seat (internal factory option, not on the public surface), and the engine's own SendMessage mount passes the same rich-context builder the `spec.tools` wrapping path uses — the tool's ctx-first resolution arms, previously structurally unreachable on first-party mounts, are live. Per-call identity (`toolCallId`/`signal`) is stamped AFTER enrichment, so a misbehaving enricher cannot alter the call's id or abort signal. Hosts wiring the tool directly (opts fallback) are unaffected; both sources resolve to equal values on first-party mounts.
276
+ - `fileSnapshotStoreContract` accepts `options.blobGc: "immediate" | "eventual"`. Manifest invisibility immediately after `reap` stays a HARD assertion in both modes; the immediate byte-GC assertion runs only under `"immediate"` (the default — matches both bundled backends). A durable/object-storage backend that hands byte GC to an asynchronous sweeper declares `"eventual"` and owns proving its sweeper — the kit stays an honesty check, not an implementation mandate.
277
+
278
+ **New API (additive)**
279
+
280
+ - `assertSafeToolResultRef` — the contract-level ref-safety predicate every `ToolResultStore` backend must apply at `put` (uniform reject set). Exported so a third-party store calls the single source instead of mirroring the reject list (mirrors drift; the contract kit already enforces the reject set, this gives implementers the same primitive the bundled backends use).
281
+
282
+ ## 2.7.0 (2026-07-31)
283
+
284
+ _Emergency direct-publish path (CI publishing resumes 8/1); pre-tag `gate:blackbox` + post-publish fresh-install smoke. No BREAKING changes._
285
+
286
+ **Security (manual-mode bash read boundary)**
287
+
288
+ - The bash readonly classifier now checks PATH arguments against the task's containment roots (upstream-parity: a read command reaching outside the workspace prompts for approval instead of sailing through a name-only allowlist). `classifyCompoundReadonlyDetailed(cmd, allow, boundary?)` returns `{ reason?, outOfRootRead?, outOfRootPaths? }`: `outOfRootRead: true` means "this command is fine EXCEPT for reading outside the roots" — the narrow-grant signal a manual-mode gate renders as an approval option (wording single-sourced via `formatOutOfRootReadApprovalOption`, matching the upstream option text verbatim). Unresolvable paths (`~user`, bare `cd`, un-based relatives) degrade conservatively WITHOUT the signal (unknown ≠ known-outside). Compound segments, `cd` targets, `--flag=/abs` values, and `..` relatives are all covered; bare-word arguments (cwd-bound by construction) and pattern/delimiter slots are not false-positived. **Passing no boundary keeps the previous behavior byte-for-byte** — existing gate deployments are unaffected until they opt in.
289
+ - Failed background-agent rows archived durably now carry the error classification triplet (`errorCode`/`retryable`/`errorKind`) across restarts — the post-restart poll face renders the same clause as the live one; a revive clears the archived triplet too (stale-cycle leak found and fixed in the same change).
290
+
291
+ **New API (additive)**: `classifyCompoundReadonlyDetailed`, `formatOutOfRootReadApprovalOption`, `BashReadonlyRootBoundary`, `CompoundReadonlyVerdict`; `classifyCompoundReadonly` / `bashReversibilityProbe` accept an optional boundary argument.
292
+
293
+ ## 2.6.0 (2026-07-30)
294
+
295
+ _Emergency direct-publish path (CI publishing resumes 8/1); pre-tag `gate:blackbox` + post-publish fresh-install smoke. No BREAKING changes._
296
+
297
+ **Behavior**
298
+
299
+ - **Deferred tools: the gate is now shape validation, not activation state** (upstream-parity, black-box cross-audit finding). A call on a still-deferred tool whose arguments VALIDATE against the real tool's schema executes the real tool directly — verbatim result, one turn — and activates it (full schema on the next boundary). An invalid-shape call keeps the teaching rejection, which is now honestly worded for a model that has never seen the schema (ToolSearch guidance, never "fix the arguments"). Governance is unaffected: policy/effect/irreversibility gates key on the tool NAME maps and run upstream of any execute. Opt out with `TaskSpec.deferSelfResolve: false` (restores the strict always-reject placeholder verbatim).
300
+ - ToolSearch `select:` misses no longer assert a tool "does not exist" — the wording now says the name is not in the deferred registry and notes the lookup is case-sensitive.
301
+ - **MCP 2026-07-28 readiness** (spec triple-diff follow-ups; the protocol itself stays at 2025-11-25 — same revision Claude Code ships): spec-legal non-object `structuredContent` (array/string/number/null) no longer fails the whole `tools/call` result (the SDK's strict record schema was lifted around); resource-not-found `-32602` is disambiguated from a genuine argument error before the "use the other tool" steer renders; protocol-range error codes `-32020/-32021/-32022` map to readable names on both the call path and the connect/handshake warning path; remote `$ref` in tool schemas is pinned non-dereferenced (SSRF boundary), local-anchor-only with cycle termination.
302
+ - Deprecation immunity registered: roots / sampling / logging / HTTP+SSE (all deprecated by MCP 2026-07-28) were never implemented here — recorded as zero-debt with a do-not-add bar.
303
+
304
+ **New API (additive)**
305
+
306
+ - `createSkillsFromDirectory(dir, options?)` — Agent Skills (SKILL.md) directory loader producing plain `SkillSpec[]`; `allowed-tools` maps TIGHTEN-ONLY onto a `SkillManifest` (names outside the deployment's mounted set are dropped with a warning; an empty intersection stays an empty allow list, fail-closed). Plus `SkillsDirectoryOptions` / `SkillsDirectoryWarning` / `SkillsDirectoryWarningCode`.
307
+ - `TaskSpec.deferSelfResolve` (above).
308
+ - Trace: `tool.call` frames carry `toolCallId` and `turn`; `brain.call` frames carry `turn` (OTel GenAI correlation mapping documented as a mapping note, not a stability promise — the upstream `gen_ai.*` namespace is still Development).
309
+
310
+ **Scoring-round hardening**
311
+
312
+ - The SendMessage sibling-resolution SESSION axis is pinned (the one genuinely un-gated leg found by the 2.4.0 scoring round); the ctx-vs-opts pair form carries a premise sentinel (RB-409 tracks the dead ctx arm's disposition).
313
+
314
+ ## 2.5.0 (2026-07-30)
315
+
316
+ _Emergency direct-publish path (CI publishing resumes 8/1); pre-tag `gate:blackbox` + post-publish fresh-install smoke. No BREAKING changes._
317
+
318
+ **New API (additive)**
319
+
320
+ - **Store contract kits on the public surface** (design/159 S2): `checkpointStoreContract` / `sessionRepoContract` / `toolResultStoreContract` / `fileSnapshotStoreContract` / `mailboxStoreContract` (+ companions `mailboxAckOwnershipContract`, `mailboxBundledOnlyContract`, `MAILBOX_CONTRACT_SCOPE`) + `CONTRACT_KIT_ENGINE_VERSION` + `ContractAssertionRunner`. Vitest-free (runAssertion callback injection) so bare-runner consumers can validate their own backends; docs/sdk/10-extension-points.md §7 has both consumption forms and a semantics-since-version table.
321
+ - `ToolSpec.alwaysLoad` / `TaskSpec.alwaysLoadTools` — the defer face's subtract valve (CC `alwaysLoad` parity): pins a tool inline past EVERY deferral source, including the MCP constant-defer arm (previously no inline-keep channel existed). MCP servers can declare it per tool via `_meta["anthropic/alwaysLoad"]`.
322
+
323
+ **Behavior**
324
+
325
+ - **Compaction × ToolSearch activation amnesia fixed** (test cross-audit, deterministic red): tools activated via ToolSearch no longer fall back to name-only placeholders at the first task boundary after a compaction — the activation set now rides the compaction entry's structured details and the boundary re-derivation consumes it (prose summaries never fed the extractor).
326
+ - Compaction (continued): summary wrapper disclosures carry the folded-message count from write-side accounting (render-time tree walks disagreed between bounded and full wakes); the count is import-validated like every sibling carrier.
327
+ - ToolSearch description teaches the CC-parity protocol: "when anything names a deferred tool, activate it first" + batch activation guidance.
328
+ - bg agent `stoppedBy` attribution: a USER-initiated interrupt now reaches background agent rows as `"user"` (previously flattened to `"parent"`, indistinguishable from a routine parent teardown).
329
+ - bg agent failure faces (residuals): failed rows poll with `isError: true`; `(error_kind, retryable)` rides the notification text, XML, and poll text faces; revive cycles clear stale error classification.
330
+ - bash timeout: `onClamp` telemetry receives the pre-cap requested value; the schema text explains over-max capping; a clamp that the command survived is disclosed on the result.
331
+ - Workflow isolated agents: the worktree directory is on the RUNNING run record (`agents[].worktreeDir`) via a new trusted `RunInternals.onWorkspaceResolved` observation seam — crash recovery no longer guesses directories from `.sema-worktrees/` listings.
332
+ - SendMessage read-side gates hardened (scoring-round finding): the sibling-resolution SESSION axis is now pinned (it was the one genuinely un-gated leg); the ctx-vs-opts pair form is shape-pinned with a premise sentinel.
333
+
334
+ **Gates**
335
+
336
+ - `internal-wording-gate` (RB-401 ruling): internal collaboration wording in published source comments is frozen per file, ratchet-down only; new code keeps provenance neutral (RB ids stay).
337
+ - Retro/cross-audit follow-ups: RB-404 arbitration confirmed the 2.4.0 sessionId fix real (test-side read error); scoring deduction RB-406 re-attributed (dead-arm removal, not gate absence) with the real hole gated.
338
+
339
+ ## 2.4.0 (2026-07-30)
340
+
341
+ _Emergency direct-publish path (CI publishing resumes 8/1); pre-tag `gate:blackbox` + post-publish fresh-install smoke. No BREAKING changes._
342
+
343
+ **Correction to the 2.3.0 notes**
344
+
345
+ - 2.3.0 claimed "Explicit `TaskStop` now emits a terminal notification frame" without qualifying the lane: that release covered the **monitor lane only**. This release completes the class: background **bash** explicit stops now emit the terminal frame too (RB-389), and background **agent** explicit stops were verified already-covered via the RB-375 latch (pinned, no behavior change).
346
+
347
+ **New API (additive)**
348
+
349
+ - `resolveBashTimeoutCaps(opts?)` — the engine's REAL bash timeout caps as a readable face (cli [2088]: hosts should render tool descriptions from this instead of holding their own constants). `HandsToolkitOptions.bashDefaultTimeoutMs` / `bashMaxTimeoutMs` make both caps deployment-configurable (CC-parity env fallbacks `BASH_DEFAULT_TIMEOUT_MS` / `BASH_MAX_TIMEOUT_MS`; the max resolves widen-only, matching CC). The zero-config values stay 120s/600s (CC-same).
350
+ - `SendMessageToolOptions.parentTaskId` / `parentSessionId` (trusted host seam) — see RB-390 below.
351
+
352
+ **Behavior**
353
+
354
+ - **Teammate-to-teammate SendMessage by name now works** (RB-390, test [2110] T6/D4): the sibling-resolution leg existed but the first-party mount's minimal adapter ctx never carried the parent axes — structurally unreachable on every auto-mounted child. Sender attribution to teammates is now the addressable `"main"` instead of an internal task id. A dead named teammate resolves the same from a sibling as from main (revive continuation).
355
+ - bg agent failure faces (RB-386, test [2090]): failure notifications carry the reason (`error`/`errorCode`), TaskOutput poll details add `error`/`errorCode`/`retryable`, and network causes survive the fold (ECONNREFUSED / ENOTFOUND / mid-stream socket loss are distinguishable instead of a bare "fetch failed").
356
+ - bg agent reap (RB-375 A1/A3/A4): session-release reap emits the terminal frame synchronously (once-latched against the child's own unwind); collateral grandchildren carry a minimal reason instead of a bare `killed`; `reaped` counts are documented as direct-hit-only.
357
+ - Workflow: RUNNING agent rows carry `sessionId` from spawn (RB-393①); `isolation` values other than `"worktree"` are rejected loudly at runtime — including through the script membrane, which previously silently dropped them (RB-394); resource-limit clamps are disclosed on the run's log stream (RB-378); team-discussion caps are documented and disclosed (RB-380).
358
+ - Disclosure sweep (RB-370①/381/382/383/385): bash timeout messages report the CALLER's true request plus the engine ceiling (foreground + background legs); Monitor timeout clamps and persistent-ignore are disclosed with structured details; SendMessage summary truncation is marked; WebSearch maxResults truncation says "showing N of M".
359
+ - Read: `offset`/`limit` validated at the schema (CC parity — negative/fractional values are argument errors, not silent floor/round) (RB-379); content sniffing outranks the extension blacklist (a plain-text `.dat` reads; magic-number detection is reachable again; read-before-edit unblocked) (RB-372); unchanged-file omitted reads carry a structured `file_unchanged` marker (CC-same tag) (RB-373).
360
+ - bash: a ran-then-cut execution with ZERO captured output is now `isError: true` (the has-data criterion applied to its own edge; the exit-code boundary is untouched) (RB-377). Path-boundary refusals name a sanctioned way out and the canonical target, carry structured details, and a `cd` outside the roots is disclosed on the result (RB-371).
361
+ - deferred tools: the offload pageback hint is reachability-aware (no self-referential dead end when `ReadToolResult` is itself deferred) (RB-374①).
362
+ - settle_race receipts distinguish "still starting up" from "just finished" (RB-391). LSP spawn failures are negative-cached (no unbounded respawn; `clearFailed()` escape hatch) (RB-376). Enter/ExitWorktree return structured `worktree` details (RB-387). Unpriced runs: `costMicroUsd` absence semantics shipped in 2.3.0 are unchanged.
363
+
364
+ **Wire note**: three NEW `tool_end.structured` type values — `worktree`, `path_not_in_root`, `monitor-start` (the latter two were previously silently dropped by the whitelist). `task_notification` gains optional `error`/`errorCode`.
365
+
366
+ **Retro review (subagent-code audit since v2.0.0)**: 13 findings fixed — including a transport-code regex typo that made ECONNREFUSED/ECONNRESET permanently unmatchable, and a bytes/chars axis mismatch in the monitor spill cap (now `MONITOR_SPILL_CAP_CHARS`). Two new mechanical gates: `type-hygiene-gate` (loose-assertion ratchet + B14 naming) and `line-anchor-gate` (sibling line-number anchors frozen, ratchet-down only).
367
+
368
+ ## 2.3.0 (2026-07-30)
369
+
370
+ _Published via the emergency direct-publish path (CI publishing resumes 8/1); pre-tag `gate:blackbox` + post-publish fresh-install smoke both ran. No BREAKING changes._
371
+
372
+ **New API (additive)**
373
+
374
+ - `MonitorToolOptions.toolResultStore` — wires the monitor spool's spill backend (design/158 S2, below). Absent ⇒ previous behavior exactly.
375
+
376
+ **Behavior**
377
+
378
+ - **Unpriced runs report NO cost instead of a fabricated 0** (RB-368). When the serving model has neither a `RunnerDeps.pricing` entry nor a `Model.cost` declaration, `stats.costMicroUsd`/`stats.costBreakdown` and the `task.end`/`brain.call` trace `costMicroUsd` are ABSENT (the declarations were already optional — implementations now match). An explicit all-zero `Model.cost` still reports 0: "declared free" and "no price table" are now distinguishable at the source. The internal budget-gate coordinate is unchanged. Consumers should render absence as "unpriced", not $0.
379
+ - **Task-output file spill** (design/158 S1+S2; RB-205-B/RB-364): a clipped `background_agent` result and monitor spool segments rolled past the retention window now spill to the `ToolResultStore` (segment-chain refs, 64 MiB per-task cap) instead of being dropped; truncation disclosures carry the ref. Ref-based (not path-based) by design — works across TOC/TOB deployments; registered as a deliberate CC divergence. No-file-backend deployments keep the previous drop-with-disclosure behavior.
380
+ - **Explicit `TaskStop` now emits a terminal notification frame** (RB-365) — multi-consumer observers no longer miss a stop that raced their subscription. Terminal frames carry no `seq`; a natural-terminal × explicit-stop double frame is structurally prevented (shared once-latch).
381
+ - **Workflow resume-claim covers default deployments** (RB-367): `InMemoryWorkflowJournalStore` gains a reference `resumeClaim` implementation (in-process, 1h TTL), and the engine adds a store-agnostic in-process fallback gate when a journal store lacks the hook — same-process double resume is refused everywhere; implementing the hook still buys cross-process single-flight.
382
+ - **RUNNING workflow agent records carry live tool facts** (RB-369, cli [2070]②): `WorkflowRunStore` agent records fold `toolCalls`/`activity` on activity beats (first beat persists immediately, then every 4th beat — bounded write amplification) instead of only at terminal settle; the terminal fold remains authoritative.
383
+ - `defineTool` products placed in `TaskSpec.tools` are seat-discriminated via an internal brand (RB-362) — a JS/BYOM deployment passing an `AgentTool` where a raw `ToolSpec` is expected no longer silently mis-executes.
384
+
385
+ **Tests / gates**
386
+
387
+ - Memory layered/selective-recall dead coverage rebuilt (15 pins, RB-363); `importManifest` refusal parity (RB-361); wall-clock-race declaration added to the flaky guard; per-test temp-dir isolation for the notebook/MCP families.
388
+
389
+ ## 2.2.0 (2026-07-30)
390
+
391
+ _Published via the emergency direct-publish path (CI publishing resumes 8/1); pre-tag `gate:blackbox` + post-publish fresh-install smoke both ran._
392
+
393
+ **Security**
394
+
395
+ - `/proc` sensitive-file blocking is now any-depth (`/proc/<pid>/task/<tid>/environ` etc. no longer bypass) (RB-346). The bash read-only classifier performs full pairing-aware quote removal at its token split — quoted (`"…"`/`'…'`) and adjacent-concatenation (`/proc/1/env"iron"`) spellings of a blocked path are refused like the plain form (RB-347/357).
396
+ - The retained-resume SendMessage leg now neutralizes teammate-message tags in raw message/summary before they enter the resume prompt (third injection path, closing RB-289's family) (RB-348).
397
+
398
+ **New API (additive)**
399
+
400
+ - `TaskSpec.oneShot` now reaches background-launch guidance everywhere: `HandsToolkitOptions.oneShot`, `TaskToolOptions.oneShot`, and `ToolExecuteContext.oneShot` (trusted, Runner-filled — same seat as `principal`). Four receipt sites (Bash run_in_background, ctrl+b detach, TaskOutput non-blocking poll, subagent async-launch card) swap "end your turn and wait" for an active `block: true` wait when set (RB-220).
401
+ - `gate:blackbox` npm script — pre-tag consumer smoke: `npm pack` → clean-dir tarball install → a full agent loop driven only through public exports. `verify-fresh-install` runs the same probe post-publish.
402
+
403
+ **Behavior**
404
+
405
+ - **Memory `SessionRepo.create({id})` on an existing id is now an idempotent REOPEN** — it previously installed a fresh empty storage, silently destroying the session's history on the one backend without a durable copy (File and Pg already reopened; contract pinned across backends) (RB-360).
406
+ - Monitor storm control: the overload grace anchor re-arms per episode (a long-healthy monitor can no longer be zero-grace-killed off a stale anchor); an undisclosed suppressed-batch count now rides natural terminals AND explicit TaskStop receipts (read-and-reset — never double-reported) (RB-349/350/359).
407
+ - SendMessage: a synchronous `still_running` fast refusal answers before entering the per-target lane — a wedged resume no longer blocks honest refusals to the same target (RB-353).
408
+ - Sync subagent completion reports carry a unified footer: `<usage>subagent_tokens/tool_uses/duration_ms</usage>`, plus (retained children) an internal-ID-framed transcript line teaching the wired inspection path (AgentTranscript; continuation is the deployment's resume handle — the transcript id is NOT SendMessage-addressable) (RB-256).
409
+ - Cleared-attachment notes are type-bucketed: `1 image attachment …` / `2 attachments (1 image, 1 document) …` — a model can judge whether the loss is worth re-reading (RB-212 ext).
410
+ - Workflow resume-claim hardening: atomic claim publish (write-then-link), no grant without real on-disk possession, release grace + byte-compare, throttled orphan sweep, and a 10s finalize ceiling on the terminal `await` (RB-354~356). Observer pairing drains and discloses residual batches on non-terminal delivery faults; the resume-state degradation report waits for the restart outcome (no reverse-lies, no double counts) (RB-351/352).
411
+ - Notebook read-dedup gates carry `!isPartialView` (cross-version durable-restore refusal fixpoint closed) (RB-343). Snapshot `importManifest` refusal messages are byte-identical across backends (unsafe-hash early refusal; fetch failures name the blob) (RB-361).
412
+
413
+ **Gate hardening (server review [2026], all five deductions closed)**: prompt-golden fails loudly on a missing fixture (no self-baselining); doc-contract pins are behavior + comment-stripped call-site checks; the knob-liveness map's value side is now an existence-checked ratchet (32 dead pointers repointed, dead coverage debt-marked as RB-363); the terminal wording table is pinned verbatim 6/6; `MONITOR_MAX_BATCHES_PER_MINUTE` has value + default-behavior pins. Retroactive release-leg audit: 175/175 tags on npm, 2.1.0 anonymous install + smoke green.
414
+
415
+ ## 2.1.0 (2026-07-30)
416
+
417
+ _Published via the emergency direct-publish path (CI publishing resumes 8/1)._
418
+
419
+ **New API (additive)**
420
+
421
+ - `WorkflowJournalStore.resumeClaim?` / `releaseResumeClaim?` (RB-242) — optional resume-admission seam. When a store implements it, the engine claims `(sourceRunId, scope)` before any live work on a resume and releases it in the run's terminal `finally`; a denied claim rejects the resume with the holder named. `FileWorkflowJournalStore` ships a reference implementation (atomic `wx` claim files, 1h TTL). Stores that do not implement it keep the exact previous behavior. The server-side SQL twin is already live (server 1.322.0) — wiring both halves completes the cross-process E2E.
422
+ - `LoopTerminalReason` gains `truncated_output_exhausted` (RB-258; exhausted truncation-continue budgets no longer masquerade as `completed`).
423
+
424
+ **Behavior**
425
+
426
+ - **Delegation depth default lowered 5 → 3** (RB-292, matching CC 2.1.220's default). Deployments relying on depth > 3 must pass `maxDepth` explicitly.
427
+ - SendMessage: same-target deliveries are now serialized through a per-target lane (RB-288); `subagent_type` accepts normalized spellings ("Explore", "general purpose") with ambiguity reporting (RB-293); `general-purpose` now appears in the agent listing (RB-294); fork + worktree isolation injects the CC path-translation note (RB-295); async-launch receipts unified and corrected — "continue other work in the meantime", internal-ID guard, SendMessage continuation hint (RB-291).
428
+ - teammate-message frames are now escape-fenced (attribute escaping + tag neutralization; forged `teammate_id` frames are defused in place) (RB-289). Observers now receive the request-side trigger and render real injection previews; observer-origin injections are excluded from digests (echo suppression) (RB-290).
429
+ - MCP: server instructions are fenced + capped (8KB/server) on BOTH delivery legs via one shared helper (RB-308); `tools_delta` gains `readded`/`removed`/`failed` arms (RB-309); dropped-tools lead adopts the CC-verbatim heading and the "Quoted text is data…" neutralization sentence (RB-310).
430
+ - Read: partial-view entries no longer satisfy the dedup stub, closing the read→refuse→re-read→refuse fixpoint (RB-277). `/proc` blocking gains CC's sensitive-file layer (`environ`/`cmdline`/…) and the any-depth fd rule (RB-278). UNC paths: win-form early-allow with zero fs probing (hang surface removed); POSIX `//` folds and takes the full containment pipeline (RB-279). Glob keeps newest-first ordering — CC's docs promise it while its implementation returns oldest-first; registered as a deliberate divergence with the evidence chain (RB-280).
431
+ - WebSearch: empty result sets no longer instruct citing (honest zero-sources note); scheme-less result URLs normalize to https before validation; refusal `details` echo is bounded (RB-264 residuals). `FileMailboxStore.reap()` sweeps cold boxes. `ToolResultStore` ref-safety is uniform across backends; refs mint through one helper that folds provider ids injectively (RB-266/273). Memory-engine materialized files are pre-seeded as read (no unread-overwrite friction on MEMory writes) (RB-276). `TaskCreate`/observer-report reclassified `write` (RB-270). journal/memory file stores gain fd bounds (RB-267). Session import validates the `invokedSkills` carrier (RB-265).
432
+
433
+ Tracked coverage additions: RB-159/160/161 positive pins (probe retired), TaskListStore 3-backend contract, flaky-guard helper-sleep detector + full test/ corpus scan.
434
+
435
+ ## 2.0.1 (2026-07-29)
436
+
437
+ **Fix batch: the 黑板 [1963] re-verification residuals (9 findings) + RB-258, closed.**
438
+
439
+ - **defineTool argument-validation early return now carries `isError: true`** (RB-262) — a class fix at the single tool factory: malformed-argument refusals for ALL 39 first-party tools (and every BYOM tool) were enveloped as success on the direct-execute path. The chmod-444 EACCES report was a false positive (atomic temp+rename writes succeed by design; pinned in both directions).
440
+ - **Write update lane preserves a caller's leading U+FEFF** (RB-263) — encoding metadata comes from the file, body comes verbatim from the caller. Note: a BOM'd file whose body starts with U+FEFF has exactly one byte shape, so "disk always has one marker" and "read-back fidelity" are mutually exclusive; sema keeps read-back fidelity (deliberate divergence from CC, registered).
441
+ - **`HAND_TOOL_EFFECTS.Write` reclassified `idempotent` → `write`** (RB-264 W1) — Write replays are gated by read-before-write freshness, not unconditionally safe; interrupted-session guidance now matches Edit's. Write's not-read refusal joins the shared renderer (gains the partial-view escape hint).
442
+ - **Glob honesty** (RB-264 G2): extension-level ignore skips are counted and named on empty results; budget-exhausted empty results say so (with a remedy that actually works); a nonexistent `path` is now `isError: true` (an empty directory still reads `No files matched.`).
443
+ - **WebSearch** (RB-264 S1/S2/S3): every failure carries a retry verdict (429/408 classified before generic 4xx; honest `unknown`); domain-filter drops are disclosed alongside unusable-URL drops; `query` gains a 2000-code-point cap.
444
+ - **Notebook/Edit partial-view refusals name the escape route** (RB-264 N1).
445
+ - **`FileMailboxStore.reap()` sweeps cold boxes** (RB-264 mailbox-reap A) — on-disk enumeration joins the in-process cache arm; same answer either way, RB-90 seq high-water preserved.
446
+ - **`LoopTerminalReason` gains `truncated_output_exhausted`** (RB-258, additive) — truncation-continue budget exhaustion no longer masquerades as `completed`; full parity with `malformed_tool_use_exhausted`.
447
+
448
+ ## 2.0.0 (2026-07-29)
449
+
450
+ _Major release: the design/157 pre-production structure-debt campaign, complete. Published via the emergency direct-publish path (CI publishing resumes 8/1)._
451
+
452
+ **BREAKING** (each family independently listed; migration is mechanical — tsc names every site):
453
+
454
+ - **Factory naming unified `make*` → `create*`** (37 declarations). npm face: `makeWebFetchSummarizer` → `createWebFetchSummarizer`, `makeImageDownsampler` → `createImageDownsampler`, `makeFrameDecoder` → `createFrameDecoder`. No deprecated aliases retained.
455
+ - **`@deprecated` surface removed entirely** (now zero in src/):
456
+ - `assembleFullBodyTools` / `FullBodyToolsConfig` / `FULL_BODY_ROLE` / `FULL_BODY_SYSTEM_PROMPT`
457
+ - `SessionSummary` type alias (use `SessionStoreSummary`)
458
+ - `ToolDecision` (use `PermissionResult`)
459
+ - `toSkill`, `createRememberTool`, `createRecallTool`
460
+ - The retired design/138 memoryStore seam, root and branch: `RunnerDeps.{memoryStore,memoryConsolidation,memoryRecall,dynamicRecall}`, `TaskSpec.memoryConsolidation`, `MemoryConsolidationConfig`, `dynamic-recall.ts` (whole module), 4 deprecated fns in memory-recall.
461
+ - **Float-USD cost fields removed**: `TaskStats.costUsd`, `TaskStats.nested.costUsd`, `AgentToolContext.reportUsage`'s `costUsd` param, `CheckpointState.nestedStats.costUsd` (persisted checkpoint schema — pre-v1 rebuild, no migration), `teacherStats.costUsd`. `costMicroUsd` integers are the sole cost figures.
462
+ - **`PromptProvider.system?()` (the legacy free-form prompt leg) removed** — every provider path now composes structurally. `PromptBuildContext` removed; `AssembledPrompt.constitution` no longer has a `"legacy"` value.
463
+ - **`RunnerDeps.utilityGate` removed** (zero consumers after the memoryStore seam removal; `FileStorageBackendOptions.utilityGate` is unaffected and remains).
464
+ - **`TaskStream.consolidation()` removed** (was a permanently-settled no-op; zero behavior change).
465
+ - Model-visible wire aliases (`BashOutput`/`KillShell`/`WorkflowStatus`, …) are NOT affected.
466
+
467
+ **Behavior**:
468
+
469
+ - `canAccess` gains a root-session arm for background agents (RB-236): the host session now reaches its whole delegation tree (poll/stop/revive/SendMessage) while grandchildren are RUNNING — aligned with the durable predicate. Note for services calling `markStopSourceForOwner` with a sessionId: the arm widens which rows match.
470
+ - TaskOutput/TaskStop declaration surfaces are now capability-composed from one source (`task-tool-shape.ts`): descriptions and schemas honestly reflect mounted capabilities (no block/timeout params on the env-direct shell; completion-notification promises only when notification is actually wired).
471
+ - Spool reclaim narrowed to three tiers (RB-235): a live writer's spool is only truncated at the hard cap; live rotations are disclosed once on the terminal read.
472
+
473
+ **Internal** (no API change): fs toolkit split into 7 modules; task-registry split into kernel + shared + monitor/workflow/agent lanes (5020 → 2159-line kernel); subagent tools extracted (retain-ledger / send-message / agent-transcript); brain stream dedup with live-getter walltime gate; five file stores composed over a shared ledger base; mailbox contract now runs all three backends; runLocked mutable state made explicit (RunState); 5 megatest files split into 72; event-sequence snapshot pins established for the megafunction campaign.
474
+
475
+ ## 1.452.0 (2026-07-28)
476
+
477
+ _Published via the emergency direct-publish path (GitHub Actions private-repo quota exhausted, per 黑板 [1959]; gates 4/5 — registry curl verification + anonymous fresh-install — completed manually. CI publishing resumes 8/1.)_
478
+
479
+ **Fix batch: the 黑板 [1937] black-box tool-surface scan — 25 findings closed in one release (E1 + wave-1).**
480
+
481
+ - **isError contract, toolkit-wide (E1 / RB-223, RB-247)**: a `ToolSpec.execute` returning a bare error string was unconditionally `isError:false` on the wire. New `errorResult()` envelope; ~190 genuine-failure returns across fs/Read/Edit/Write/NotebookEdit/Grep/Glob/RepoMap/Bash/TaskOutput/TaskStop/RunWorkflow/WebFetch/WebSearch/scheduler/worktree/ask-question/gitea/monitor/observer/tool-result-store/task-registry/SendMessage/AgentTranscript/RefreshMcpTools/LSP/memory now carry `isError:true`. Deliberately NOT flipped: ran-then-cut partial answers (Bash timeout/abort with captured output, WebFetch mid-body salvage), idempotent no-ops, listing-zero outcomes. MailboxStore.ack is now owner-fenced across all three backends (**BREAKING** for direct MailboxStore implementors: `ack(scope, handle, owner, upToSeq)` — the engine's own call sites are updated).
482
+ - **Write is atomic (RB-221)**: staging + fsync + rename (was in-place O_TRUNC — a crash mid-write corrupted the target). `idempotent` effect wording split from read-only ("safe to REPLAY", not "no risk").
483
+ - **Write/Edit BOM correctness (RB-222, RB-224)**: read-state hashes unified on the round-trip decode coordinate (a just-created BOM file no longer false-stales every subsequent write); overwrites respect the NEW content's BOM instead of stamping the old file's; `old_string:""` create-branch now passes requireRead/checkStale like every other edit.
484
+ - **Glob (RB-225, RB-226)**: relative prefixes anchor to the root (`src/*.ts` no longer matches `**/src/*.ts`); `[ab]` character classes expand; a path segment the pattern explicitly names pierces DEFAULT_IGNORE_DIRS/gitignore (position-aware); completeness fields stop claiming `countIsComplete:true` on pruned walks.
485
+ - **Read serves .ipynb as a notebook projection (RB-227)**: cell-level `<cell id>` blocks, per-cell 10K output cap with a `jq` pointer (CC-parity shape). Unlocks the Read↔NotebookEdit deadlock on notebooks with large outputs. New module `src/tools/fs/notebook.ts`.
486
+ - **WebFetch/WebSearch (RB-229/230/231)**: transport/validation failures are errors on the wire; trailing-dot host normalization; result-field clipping is code-point safe.
487
+ - **LSP (RB-232/233)**: a "none" result splits into unsupported/crashed/timed-out/cancelled/empty with per-reason wording and isError; the 10MB file cap is a model-visible tool check; initialize declares `workspaceFolders` (CC-exact `false` shape).
488
+ - **Task registry (RB-234, RB-237-240, RB-244/245)**: reap attribution renders through TaskOutput; truncation/timeout disclosure on poll; monitor event clipping single-sourced; stop receipts key on entry state ("Terminated" vs an honest "nothing to stop").
489
+ - **Workflow journal (RB-243)**: oversized results tombstone instead of corrupting the journal; unknown-runId / cross-scope / torn-log resumes each get an honest disclosure instead of silent replay gaps.
490
+ - **Mailbox stores (RB-246, RB-248, RB-250, RB-251)** + task-list fixes.
491
+
492
+ Every fix mutation-verified (break→red→restore→green); each domain independently reviewed (adversarial, worktree-isolated); 5 domains were revised after review findings before landing.
493
+
494
+ ## 1.451.0 (2026-07-28)
495
+
496
+ **Feature (blackboard [1909]⑧/[1910]/[1911], cross-repo agreed, RB-215 candidate ①): `TaskSpec.oneShot?: boolean` — a per-request signal that this submission has no later turn for an async background notification to land in.** The archetypal case is a headless `sema -p` invocation: the process exits once the turn ends, so the `RunWorkflow` tool's default launch guidance ("end your turn, you will be notified") was actively wrong there — a real, observed data-loss scenario (BGB drilldown case 2: a model that followed exactly this guidance lost background results; switching to an active blocking poll in the same turn reliably got the full result). Deliberately per-request rather than per-connection/per-process: whether a given submission expects to be steered or continued is a property of that submission, not of the channel it arrived on — a persistent connection can still mix interactive and one-shot submissions, which a connection-level flag couldn't express.
497
+
498
+ Currently consumed by `RunWorkflow`'s launch-note composition only: when `oneShot` is true, the guidance switches to an active `TaskOutput({ block: true })` wait, checked ahead of every other notifier-capability tier so it applies regardless of what notification wiring the deployment happens to have. Threaded from `TaskSpec.oneShot` through `prepare-task.ts`'s tool-mount call site into `RunWorkflowToolDeps.oneShot`, verified end-to-end (not just at the direct-dependency level) via a real `runner.runTask()` chain that reads back the actual tool-result text a model would see. Absent/`false` leaves today's behavior unchanged for interactive sessions. One independent review pass found no functional defect in scope; it also surfaced that the same root cause exists at 8 further call sites (Bash background-launch receipts, `TaskOutput`'s non-blocking still-running reply, several Task-tool background-delegation receipts) that don't yet read this signal — filed as RB-220 for follow-up, not blocking this release.
499
+
500
+ No breaking changes.
501
+
502
+ ## 1.450.0 (2026-07-28)
503
+
504
+ **Fix (RB-200 F1, form-one audit, CC 220 parity verified against `@368590-368592`/`@368402`/`@367957-367965`): Grep and Glob treated every hidden file and directory as invisible, not just VCS metadata.** Claude Code's own noise-reduction is `--hidden` (hidden files are searched by default) plus a named exclusion of exactly six VCS metadata directories (`.git`/`.svn`/`.hg`/`.bzr`/`.jj`/`.sl`) — not a blanket "anything starting with `.` doesn't exist" rule. Sema's ripgrep leg ran without `--hidden` (ripgrep's own default skip-dotfiles behavior), and the JS-fallback walker excluded every dotfile/dotdir outright. A model asking about `.github/`, `.claude/`, or `.env.example` got a silent, uncaveated empty result — indistinguishable from "genuinely nothing there," in direct conflict with this codebase's own convention that an unflagged empty result is read as exhaustive. The Grep tool's own description text asserted the false "skips hidden files" behavior as a promised contract.
505
+
506
+ Two call sites fixed to match: the shared JS-fallback walker `buildIgnore` (used by Grep's fallback, Glob, and repo-map) dropped its blanket dotfile rule and now excludes directories by name via `DEFAULT_IGNORE_DIRS`, extended with the three previously-missing VCS names (`.bzr`/`.jj`/`.sl` — `.git`/`.hg`/`.svn` were already present as pre-existing "never worth crawling" entries). Grep's real-ripgrep path (`rgGrepDetailed`, which does not go through `buildIgnore`) now passes `--hidden` plus a `--glob '!<dir>'` per VCS directory, matching CC's own flag construction one-for-one — deliberately not reusing the broader `DEFAULT_IGNORE_DIRS` list, since ripgrep already respects `.gitignore` on its own and doesn't need the build/dependency-directory exclusions redundantly. The tool description's false promise is corrected; the prompt golden-freeze snapshot and the internal CC-parity divergence registry were updated to match.
507
+
508
+ One independent review pass found three real issues, all fixed before shipping: (1) the new rg-leg integration test asserted exclusion for `.git` only — the other five VCS names were exercised solely on the JS-fallback path, so a single missing `--glob` flag on the real-ripgrep invocation would have shipped undetected; reproduced by manually stripping one flag from a live invocation, confirmed the gap, extended the test fixture to all six VCS directories with independent per-directory assertions on the real rg path. (2) a module-level doc comment describing the old, now-false "skips binary/hidden" behavior was missed when the same false claim was corrected in three other places in this diff. (3) the two new `it.runIf(rgAvailable)` tests had no corresponding `test/skip-baseline.json` entry, which would fail `npm run gate:skips` on any machine without `rg` installed — reproduced with a PATH shim forcing `rg --version` to fail, confirmed the gate error, fixed by generating a real skip report under the simulated no-rg condition and merging it in via the baseline script's own `--merge` mechanism (not hand-typed), then cross-verified the gate passes clean both with and without `rg` present.
509
+
510
+ No breaking changes.
511
+
512
+ ## 1.449.0 (2026-07-28)
513
+
514
+ **Fix (RB-200 F2, form-one audit, CC 220 parity verified against `@367198`/`@367671`/`@515151`): the read-before-edit/write safety gate (design/44 §4 inv 1) was satisfied by a partial read.** If a model read a large file with no explicit `offset`/`limit` and the output hit the token cap, Read served only the first page (clearly labeled as a partial view in the reply text) — but the read-state entry that recorded it was indistinguishable, at the gate, from a full read: `requireRead` only checked whether *any* read had happened, not whether it was complete. A subsequent Edit or Write on that file was allowed to proceed having genuinely seen only a fragment.
515
+
516
+ Added a new field, `ReadEntry.isPartialView`, deliberately narrower than the pre-existing `truncated` flag (which also fires for a fully-intentional, successful explicit `offset`/`limit` slice — folding the auto-truncation case into that broader flag would have broken the documented escape hatch for editing oversized files in slices). `requireRead` now refuses on `isPartialView`, matching Claude Code's own gate exactly: both `Edit` and `Write` there refuse identically on `!p || p.isPartialView`, with no softer rule for `Edit` despite its narrower per-call footprint. `NotebookEdit` shares the same `requireRead` call and is fixed by the same change. Verified against the actual CC 2.1.220 source corpus, not from memory. One independent review pass found no defect (edge cases, the partial→full state transition, and all three `requireRead` call sites were separately re-verified); two non-defect observations were logged for future reference, neither actionable.
517
+
518
+ **Fix (RB-218, LOW-MED, RB-210 independent-review derivative): a third-party tool returning a `content` value that was neither a string nor an array crashed with a raw, unhandled `TypeError` instead of a structured, actionable error.** `normalizeContent`'s non-string branch asserted the value's type without a runtime `Array.isArray` check; the resulting non-array reached `isEmptyToolContent`'s `.every()` call and threw — escaping `defineTool`'s own try/catch, which wraps only the tool body itself, not the normalization that runs after it. Since tool authors are only type-checked at compile time (BYOM), this was a real, reachable shape, not a hypothetical one. Now returns a structured `isError:true` result explaining the actual contract instead of an opaque internal error message.
519
+
520
+ No breaking changes.
521
+
522
+ ## 1.448.1 (2026-07-28)
523
+
524
+ **No functional change.** `v1.448.0`'s tag exists but was never published — CI failed before the publish step ran (`npm ci` refuses when `package-lock.json` isn't in sync with `package.json`, and 1.448.0's new `optionalDependencies.sharp` entry hadn't been reflected there; the author's own oversight, not a flake). Recorded in `scripts/verify-published-tags.mjs`'s deliberate-skip allowlist; this version carries 1.448.0's actual content.
525
+
526
+ **Caught while fixing it**: the `^0.33.0` version range 1.448.0 declared for `sharp` permits resolving to versions with known HIGH-severity CVEs (CVE-2026-33327/33328/35590/35591, inherited from libvips, fixed in sharp 0.35.0) — `npm audit` flagged it immediately once the lockfile actually pulled a real install rather than resolving the one that happened to already be on this machine outside the project tree. Corrected to `^0.35.0` in the same fix.
527
+
528
+ ## 1.448.0 (2026-07-28)
529
+
530
+ **Fix (RB-213, MED, blackboard [1870] C5): `sharp` — the optional native downsampler `sharpImageDownsampler` dynamically imports — was invisible to every package manager, with no `optionalDependencies` entry anywhere in `package.json`.** RB-189 already settled the actual design question this looks adjacent to ("core won't force a native module on every consumer") and that stands unchanged; this is narrower — the visibility gap itself. Added `optionalDependencies: { "sharp": "^0.33.0" }`: `npm install` now best-effort-installs it and never fails the parent install if the native build fails, identical runtime behavior to before (the dynamic `import("sharp")` catch-and-degrade path is untouched) with the option now actually discoverable by tooling.
531
+
532
+ **Fix (RB-214, MED, blackboard [1870] D1): `sema-tb`'s model spec unconditionally declared `input: ["text"]` for every provider/model, with no override — unlike the sibling fields (`reasoning`/`contextWindow`/`maxTokens`), which all had one.** Both brains derive vision support from this array (`model?.input===undefined ? true : model.input.includes("image")`), so any image tool-result produced while running under `sema-tb` was silently swapped for a placeholder regardless of whether the underlying model actually served vision, with no way to override it. Added `TB_VISION=1` (`tbModelInput` in the new `src/bin/tb-env.ts` seam, alongside `positiveIntFromEnv`, so it stays unit-testable without importing the bin's `main()`-executing entry point) — unset keeps the exact prior behavior (`["text"]`), matching the opt-in-only posture of every other TB env override in this file.
533
+
534
+ No breaking changes.
535
+
536
+ ## 1.447.0 (2026-07-28)
537
+
538
+ **Fix (RB-209, HIGH severity, blackboard [1870] C1 / test AI `repro/secret-redaction-blind`, 13-angle black-box audit): five mechanical/rule-level gaps in secret redaction.** ANG-5: HuggingFace `hf_…` tokens walked through `scrubSecrets` completely unpatterned — the one credential shape in the audited matrix that was a pure leak rather than an over-redaction. ANG-6: the PEM private-key rule matched only its own `-----BEGIN…-----` delimiter line; the key material on the following lines — the actual sensitive bytes — walked through untouched. ANG-7 (two sites): the keyword-adjacent catch-all (`arg-summary.ts`) and the URL-userinfo rule (`untrusted-egress.ts`) both consumed the LABEL along with the secret — `GITHUB_TOKEN=ghp_xxx` became bare `[redacted]` instead of `GITHUB_TOKEN=[redacted]`, and `ftp://user:pass@host` became `ftp://[redacted-credentials]@host` instead of preserving the username — destroying the identifier a reader needs to know WHICH credential leaked. ANG-9: `boundedString`'s size-bounded truncation could silently drop an already-produced `[redacted]` marker past the cutoff, so a consumer counting markers to judge "is this clean" read a truncated result as cleaner than it actually was. ANG-13: `SECRET_ENV_RE` missed the `AUTH` word-sense and couldn't cross a trailing numbered/`_ID` variant (`GH_TOKEN_2`, `AWS_SECRET_ACCESS_KEY_ID`).
539
+
540
+ ANG-12 (`scrubSecretEnv` deletes rather than masks a matched key) was investigated and confirmed correct, intentional behavior for its one real caller (child-shell-env fail-closed scrubbing before spawning a shell for an autonomous model) — not changed. ANG-1/ANG-2/ANG-3/ANG-4 (typed+indexed markers, a findings channel, an envelope announcement, confidence tiering) need a new export shape and are deferred as a separate architectural follow-up (tracked in `docs/REVIEW-BACKLOG.md`); ANG-8/ANG-10/ANG-11 likewise deferred to the same follow-up.
541
+
542
+ Three rounds of independent review, each catching something real: the first found that the ANG-9 fix's truncation-notice logic had no fallback shape, so a range of `max` budgets too narrow for the new "N marker(s) dropped" disclosure (but plenty wide for the original plain "…[+N chars]" notice) collapsed all the way to a bare, count-less ellipsis — worse than the pre-fix behavior on that whole range, not just incomplete; fixed with a three-tier fallback (full disclosure → plain count → bare ellipsis, each tier its own independently-converging attempt). The second found a real credential leak the ANG-7 URL-username-preservation fix introduced relative to the pre-fix baseline (a bare, vendor-prefix-less token used AS a URL username — the standard `git clone https://<token>@host/…` idiom — read as "just a username" and survived unredacted where the old code redacted the whole userinfo blob unconditionally), fixed with a length floor shared with this file's other length-anchored credential shapes; and a new false-positive class the first cut of the ANG-13 fix introduced (a generic `AUTH` suffix misclassified boolean feature-toggle env vars — `SKIP_AUTH`, `DISABLE_AUTH`, etc — as credentials, and `scrubSecretEnv` deletes a matched key outright, silently changing spawned-shell behavior), fixed by reverting the generic suffix and handling the original motivating case (`NPM_AUTH`) via the existing exact-name mechanism instead. A third, focused round re-audited both fixes and found no further defect (confirmed via extensive fuzzing), surfacing two known, bounded, and now explicitly documented residuals instead of new bugs: the URL-username length floor cannot structurally close every short unprefixed-token case (bounded to the same-principal tier only — the human-display tier's blanket URL redaction already covers it), and a pre-existing (not introduced by this batch) ambiguity in `SECRET_ENV_RE`'s bare `KEY` suffix is tracked separately as RB-219, since properly fixing it needs a new mechanism this batch didn't build.
543
+
544
+ No breaking changes.
545
+
546
+ ## 1.446.0 (2026-07-28)
547
+
548
+ **Rename: `SessionSummary` → `SessionStoreSummary` (blackboard [1913]/[1914]).** This package's own `SessionSummary` type (a `SessionStore.list()` projection: `sessionId`/`createdAt`/`lastActiveAt`/`lastTaskId`/`forkedFrom`) shared its name with an unrelated, differently-shaped `SessionSummary` in a downstream SDK package (the wire GET /v1/sessions row) — the exact "same name, two shapes" pattern a cross-repo cleanup was independently eradicating elsewhere in the same window. Found while cross-checking that cleanup against this package's own export surface, confirmed with the downstream maintainer (zero consumption of this package's `SessionSummary` on their side — its fields are re-projected under different names before ever reaching the wire) before renaming.
549
+
550
+ `SessionSummary` is kept as a deprecated type alias (`export type SessionSummary = SessionStoreSummary`) for this one version — every existing import keeps compiling unchanged, with an editor-visible deprecation notice. The alias is removed in the next minor. No runtime behavior changes anywhere (a pure type-level rename); `SessionStore.list()` and `TtlSessionStore.list()` return the exact same shape as before, just under the new type name.
551
+
552
+ No breaking changes this version (the deprecated alias covers every existing consumer); the rename itself becomes breaking when the alias is dropped in the next minor.
553
+
554
+ ## 1.445.0 (2026-07-28)
555
+
556
+ **Fix (RB-195, LOW severity): the bash-classifier's "does this stdin reader have enough file arguments to avoid blocking" floor check missed a real file operand mixed with an explicit trailing `-` — `cat realfile -` classified as safe to auto-allow, then actually ran and blocked reading stdin forever (until the tool timeout) exactly like a bare `cat` does.** POSIX/GNU convention treats a bare `-` argument to `cat`/`head`/`tail`/`wc`/`cut`/`grep` as an explicit, additive request to also read stdin at that position — not a substitute for other file arguments. The floor check's argument count excluded `-` (it looks option-shaped), which correctly still caught a bare `cat -` alone, but let a real file argument mask the independent, additive stdin request sitting alongside it.
557
+
558
+ Fixed with a check for a bare `-` anywhere in the arguments, independent of whether the floor is otherwise satisfied — a real file argument no longer masks it. Position-sensitive, not "anywhere in the command": a bare `-` only means "read stdin" in a file-argument position, not wherever it happens to appear, so it's scoped to skip `cut`'s `-d`/`--delimiter` value (`cut -d - -f1 file` sets the delimiter to a literal hyphen — a real idiom — and never touches stdin) and `grep`'s default pattern position (`grep - file1 file2` searches for the literal pattern `-`, never touches stdin either, unless `-e`/`-f` supplied the pattern via flag instead — in which case every non-flag token is a file, including this position). `tr` is excluded entirely — always blocked regardless via its own floor, and it has no file-argument syntax at all.
559
+
560
+ Three independent review rounds, each catching something real: two (run in parallel, blind to each other) converged on the same position-blind over-correction in the first draft; a third, focused specifically on the resulting position-aware logic, found that `grep`'s `-f`/`--file` flag fused with a `-` value into a single token (`-f-`, `--file=-`) was swallowed whole by the generic option-flag skip without its embedded `-` ever being examined — the space-separated form (`-f -`) already worked, only the concatenated forms slipped through. `-e`'s value is deliberately NOT given the same treatment: it's a literal pattern string (never reads stdin) even when that string happens to be `-`, unlike `-f`/`--file`'s file-path argument where `-` specifically means stdin.
561
+
562
+ No breaking changes.
563
+
564
+ ## 1.444.0 (2026-07-28)
565
+
566
+ **Feature (cross-repo request, blackboard [1900]/[1902]): `WebFetchConfig.summarize` — the seam a deployment wires up to have fetched web content extracted through a model instead of dumped whole into the calling model's context — had a working reference implementation, but it was private, living inside this package's own `sema-tb` CLI entry point rather than its public API. A deployment assembling its own tools (a server composing scenarios, for one) had no way to reuse it short of re-deriving \~90 lines from source, so in practice `summarize` went unwired and every fetch dumped the whole page.**
567
+
568
+ `makeWebFetchSummarizer(brain, model)` is now exported from the package root, unchanged byte-for-byte in its logic (only relocated, from `src/bin/sema-tb.ts` into `src/tools/web.ts` next to the interface it implements, and promoted from `function`/`const` to `export function`/`export const`) — reuses a caller-supplied brain/model rather than requiring a dedicated second model, truncates oversized content at 100,000 characters before summarizing, and throws (rather than returning something empty or misleading) on an error/aborted response or an empty result, so the tool's own catch can fall back to the honest raw-dump-with-a-note path it already has. Two supporting constants are exported alongside it — `WEBFETCH_SUMMARY_MAX_CONTENT` (the truncation bound) and `WEBFETCH_SUMMARY_GUIDELINES` (the fixed CC-parity guidance block the prompt is built around) — so a deployment wiring this in, or composing an equivalent summarizer of its own, doesn't need a hand-copied value of either that can silently drift from this one.
569
+
570
+ Never had test coverage as private code; ships with it now (13 cases, each independently mutation-verified): normal summarization, truncation (including the exact-boundary case and confirming the untruncated tail never reaches the model), prompt passthrough, empty/whitespace-only response, error and aborted stop reasons (via both the `brain.complete` and `brain.stream` paths), `brain.complete` precedence over `brain.stream`, and a `brain.stream` that itself returns a `Promise` rather than a bare stream. Two independent review passes (blind to each other) found no defect in the migration or in how the summarized text is bounded before reaching the model (still fenced as untrusted, same as an unsummarized page); one found the fixed guidance block wasn't exported despite being explicitly requested (closed here), the other found the `Promise`-returning-`brain.stream` shape untested (closed here too).
571
+
572
+ No breaking changes — the export surface only grew (design/87 L3 export-surface snapshot updated accordingly).
573
+
574
+ ## 1.443.0 (2026-07-28)
575
+
576
+ **Fix (RB-206, form-one audit): a run whose model kept producing malformed tool calls, exhausted its bounded retry budget, and gave up looked identical — from the terminal reason alone — to a run that finished normally with nothing left to do.** The loop already nudges the model once (bounded, configurable) when a `toolUse`-stopped turn parses to zero tool calls (the brain already dropped the malformed call and left a visible note); once that budget runs out, a further malformed turn used to fall through to the generic no-more-work path and report a plain `completed`. A caller — telemetry, a test, any future classification logic — had no way to tell "the model genuinely had nothing more to do" apart from "the model believed it acted, nothing ran, and recovery gave up."
577
+
578
+ Fixed by giving that case its own terminal reason, matching a reference implementation identified by reading the current CC corpus directly (not secondhand): the same function has an explicit exhaustion branch there, and a downstream classifier buckets it with other failure/degraded-completion reasons, explicitly excluding it from a clean "completed." Scoped tightly to that one gap — the retry-vs-exhausted decision only restructures an existing `if` into `if`/`else`, an existing test that had been silently encoding this exact bug (asserting the old "falls through to completed" behavior as its expected outcome) now asserts the corrected reason instead.
579
+
580
+ Two independent review passes (blind to each other) found no functional defect — one traced, brain by brain, that the triggering shape ("toolUse" stop with zero tool calls) has no legitimate non-malformed origin, ruling out a false-positive termination; the other independently re-verified the CC reference against the actual corpus and confirmed no hidden downstream consumer could be affected by the new value. Both surfaced test-coverage gaps rather than bugs, closed here: a `maxRetries:0` boundary, and steer arriving at the exact moment the budget would otherwise report exhausted (confirmed to still win, via the same pre-existing guard the retry path already had) — closing the second one caught its own bug in the first draft (a wrong assumption about when steering gets drained mid-turn, corrected against the actual behavior rather than left as a guess).
581
+
582
+ No breaking changes.
583
+
584
+ ## 1.442.0 (2026-07-27)
585
+
586
+ **Fix (RB-210, form-one audit): the `tool_end` event stream — a live-UI/observability channel, never re-fed to a model — degraded an oversized non-string tool result by slicing its JSON-escaped serialization directly, corrupting the visible text (a literal `\n`/`\t` where a real newline/tab belonged) and misreporting how much was cut (a real case: 17298 chars reported dropped vs 13696 actually present, escape overhead inflating the count).** Rewritten to truncate an honest, unescaped rendering instead: each text block contributes its own literal text, and non-text blocks (image/document — base64 bulk that can't be sliced without corrupting it) contribute a short disclosure marker rather than their data. A new `totalChars` field discloses the true original size, trustworthy even without parsing the inline "…[+N chars truncated]" marker out of the string.
587
+
588
+ Two independent review passes (blind to each other) converged on the same real defect in the first cut of this fix, from different angles: `totalChars` used two different, inconsistent yardsticks depending on which of two adjacent branches ran — one measured the (already-lossy) rendered text's own length, silently under-counting by the exact amount of any inlined image once non-text bulk was involved; the other measured the untouched JSON serialization, which reintroduces this same fix's own original complaint (escape-overhead inflation) for ordinary text containing quotes, tabs, or newlines. Resolved by defining `totalChars` as the sum of each block's own true size (a text block's real character count; an image or document block's actual base64 payload length) — never the JSON wrapper around either. A related false-positive was fixed alongside it: many small text blocks can push the JSON-serialized form over the size cap through structural overhead alone (punctuation, field names) while the honest rendering — 100% of the substantive content — fits comfortably; this no longer reports `truncated:true` when nothing was actually lost.
589
+
590
+ Investigating a third review finding (a contract-violating tool returning `content` that is neither a string nor an array) surfaced a separate, unrelated defect one layer upstream — filed as RB-218, not fixed here: `normalizeContent`'s handling of that same shape throws a raw, low-level `TypeError` from a helper the tool-execution wrapper's own try/catch doesn't cover, rather than the structured, actionable validation error every other malformed-input path in the same function already produces.
591
+
592
+ No breaking changes.
593
+
594
+ ## 1.441.0 (2026-07-27)
595
+
596
+ **Fix (RB-215, form-one audit): a workflow launched via the `Workflow` tool had no way to survive past the turn that launched it, even though the tool's own guidance explicitly tells the model it will — "end your turn, you'll be notified" only holds if something is still there to notify.** Three other kinds of background work this library manages (a background shell, a persistent watch, a delegated background agent) already have an established mechanism for this: opt into `sessionScoped`, and the work is tied to the session's lifetime rather than the specific call that launched it, reaped only when the deployment explicitly signals the session is over. Workflow was the one kind structurally excluded from that mechanism — its handle had no field for it at all.
597
+
598
+ Fixed by bringing workflow into the same mechanism, not by inventing a new one: workflow now defaults to session-scoped whenever a session is known (unlike the other three, which need an explicit opt-in — a workflow's entire purpose is asynchronous, cross-turn operation, so there's no sensible default-off mode worth preserving), and the session-release reap that already exists for the other three kinds now covers it too. Nothing about how or when an individual task run ends was touched — this is scoped entirely to the explicit, deployment-invoked "this session is over" signal, not the per-call teardown path every ordinary turn goes through.
599
+
600
+ Two independent review passes (running in parallel, blind to each other) each found a real issue in the same underlying area, both fixed here: the new session-reap check read the wrong way on an omitted scope — treating "no scope specified" as "any tenant's workflow matches" instead of "deny," the opposite of how every other check in this same function already treats a missing scope. And the full-registry sweep (used at process shutdown) collected work to reap keyed only by session, silently reaping just one of two tenants' work when a single session legitimately had scoped work under more than one tenant — a gap that, once found, turned out to affect the three pre-existing kinds equally, not just workflow, and was untested for any of them.
601
+
602
+ No breaking changes.
603
+
604
+ ## 1.440.0 (2026-07-27)
605
+
606
+ **Fix (RB-212, form-one audit): compaction's stale-tool-result clearing dropped image and PDF content with zero trace — a cleared result that was entirely (or partly) visual looked identical to one that never had any.** `clearStaleToolResults` replaces the content of aged-out tool results with a short marker once a request nears its context budget; an existing mechanism already offloads a cleared result's TEXT to a store and embeds a retrievable reference in the marker, so the model can page it back. Non-text content had no such path — and, worse, no acknowledgment at all: it was silently discarded alongside the marker swap, indistinguishable from a result that was always empty. The underlying session transcript was never at risk (this pass only edits the outgoing request view), but nothing let the model ask for the visual content back mid-conversation, or even know it had existed.
607
+
608
+ Fixed: a cleared result whose content included any non-text block now gets an explicit note ("N attachments no longer visible after this clear") composed alongside the existing text-offload reference note when both apply. The bare, pre-existing marker is preserved byte-for-byte when neither applies — zero behavior change for the common text-only case.
609
+
610
+ Two review rounds, each catching a real issue before release: the reference note's original wording ("full content persisted") overclaimed for a mixed text+media result — only the text is ever offloaded, so the note now says "full text persisted," accurate in every case. And the first cut only recognized image blocks, missing PDF (`document`-typed) content from the Read tool's PDF branch — silently reproducing the exact bug this fix exists to close, just for PDFs; broadened to treat every non-text block type uniformly instead of enumerating known ones one at a time.
611
+
612
+ No breaking changes.
613
+
614
+ ## 1.439.0 (2026-07-27)
615
+
616
+ **Fix (RB-216, form-one audit): a subagent that committed its work inside an isolated git worktree, and left a clean working tree, was indistinguishable from one that touched nothing at all — and got its worktree deleted the same way, turning the commit into a dangling git object.** `isolation:"worktree"` gives a parallel-safe child agent its own worktree so concurrent children don't collide on the same files; on completion, the helper decides whether to prune (delete) or keep the worktree based on whether the child changed anything. That decision only ever checked `git status --porcelain` (uncommitted files) — never whether any commits existed on top of the worktree's base. A child that did real work, committed it, and left nothing uncommitted looked exactly like a no-op child, and both got pruned; pruning removes the only ref keeping a detached-HEAD commit alive, silently discarding the work.
617
+
618
+ Fixed by porting an existing, already-shipped reference implementation of the identical judgment call from this package's own `ExitWorktree` tool (a different, session-level worktree mechanism solving the same "did anything change" question correctly): the base commit is now captured when the worktree is created, and "changed" is `dirty files > 0 OR new commits > 0`, with the same fail-safe posture as the reference — any probe failure (capturing the base commit, checking status, counting commits) keeps the worktree rather than ever guessing "unchanged."
619
+
620
+ Independent review (codex + a blind subagent) surfaced one further real issue, fixed in the same release: the new base-commit capture itself had no guard against its underlying command throwing (as opposed to resolving to a clean failure result) — on an `ExecutionEnv` implementation where that's possible, the already-created worktree directory would end up permanently orphaned (unreachable by the cleanup logic, and not reclaimed by the existing prune helper, which only clears bookkeeping for directories already gone from disk). Now degrades gracefully to the same fail-safe "unset" path as any other probe failure.
621
+
622
+ No breaking changes.
623
+
624
+ ## 1.438.0 (2026-07-27)
625
+
626
+ **Fix (RB-211, form-one audit): a tool that rejected a call by returning `{isError:true, ...}` (rather than throwing) was silently recorded as a success on the wire, in every path — live per-turn execution, the durable-approval resume path, and a deferred-tool-not-yet-activated placeholder rejection.** Root cause traced deeper than either originally-reported call site: `ToolReturn` (the type a `ToolSpec.execute` may return) had no `isError` field at all, and `defineTool`'s internal result-normalization step built a fresh `{content, details, terminate}` object from whatever `execute` returned, unconditionally discarding any other property — so no `ToolSpec`-based tool could ever signal a returned rejection, only a thrown one. This silently defeated an entire pre-existing message family (`createSubagentTool`'s ~20 "Sub-agent not started: …" soft-rejection returns) that a code comment already claimed worked this way.
627
+
628
+ Fixed at the root: `ToolReturn` gained an optional `isError` field, threaded through `normalizeContent`/`defineTool` end to end (omitted/false stays omitted — every existing tool's wire shape is byte-identical). The two originally-reported symptoms are now real consequences of the same fix landing correctly: a deferred tool called before its schema is loaded now throws (carrying its full recovery instructions) instead of returning an unmarked-success placeholder string; the durable-resume execution path now reads a resumed call's actual `isError` instead of hardcoding `false`, and mirrors the live path's write-anchor/projection-ingest split exactly (a rejected write-family call still resets its "haven't written recently" reminder window, since that fires on the tool call's mere presence, but its untrustworthy details no longer feed the task/todo projection with something a rejected call never actually produced).
629
+
630
+ Two further issues surfaced during independent review and are fixed in the same release: a tool rejecting with `isError:true` and no usable content text got a success-flavored "(name completed with no output)" sentinel sitting next to its own `isError:true` — a self-contradiction newly reachable only after this fix started letting returned rejections through at all, now branched on `isError`; and the resume-path write-anchor/projection split had zero test coverage able to catch a regression in it (confirmed by reverting it and finding the full suite unaffected), closed with a dedicated end-to-end resume test that drives a rejected write-family call through enough further turns to cross the real reminder-cadence threshold.
631
+
632
+ No breaking changes — the new `ToolReturn.isError` field is optional and every existing tool's output is unaffected unless it starts using it.
633
+
634
+ ## 1.437.0 (2026-07-27)
635
+
636
+ **Fix (RB-197②, form-one audit): post-compaction working-file re-attachment no longer wastes attachment slots on files the model can already see, or duplicates content a deployment already re-seeds every turn.** Two exclusions now apply to the re-attachment candidate list before it's capped: a file whose most recent read is still visible verbatim in the compaction's kept tail is skipped (re-attaching it is pure redundancy), and a file matching one of the deployment's declared instruction-source paths (e.g. a project/user guide file re-seeded via the system-prompt lane) is skipped too — unless that source is currently declared-but-absent from disk, in which case it's still attached since nothing else is showing the model its content. A file skipped for either reason keeps its existing read-state entry instead of having it wiped by the same clear that drops summarized-away entries, so the read-before-edit gate and the next read's dedup both keep working for it.
637
+
638
+ Three review rounds (each run blind to the others, per this repo's standing practice), each catching real issues before release:
639
+
640
+ - Both exclusions initially compared paths in mismatched coordinates — the preferred candidate source reports canonical (resolved, absolute) paths, while the exclusion sets were built from the model's raw, often-relative tool-call arguments — so the comparison silently never matched in the common case, leaving the whole feature inert. Fixed with an optional path-normalization callback that puts every side in the same coordinate before comparing, reusing the same resolution logic the read/edit/write tools themselves use (including the tracked working directory, not just the task root, so a `cd` mid-session still resolves correctly).
641
+ - A file's own read-state entry could still be silently wiped even when it was the RIGHT file to keep: the kept-tail scan trusted a tool call's mere presence as proof its result was substantive, but a paired result that was itself an error or a dedup stub (its own original content already summarized away) doesn't actually show the model anything — treating it as visible reintroduced the exact stale-stub failure a previous fix in this same area (RB-197①) existed to close. Now requires a non-error, non-stub result before trusting a path as visible, matched to its originating call by ID rather than assumed to be the next message in sequence (a single turn issuing more than one read in the same batch broke the sequential assumption outright and was caught by an existing regression test going red).
642
+ - The read-state preservation for excluded files initially only ever fired for the kept-tail-visible reason, never the instruction-source reason — a file declared as an instruction source that the model also explicitly read had a live, ordinary read-state entry with no special protection, and got wiped anyway.
643
+ - An unbounded per-compaction cost: the exclusion normalization initially ran a real filesystem resolve on every candidate a task had ever read over its whole lifetime, when at most a handful would ever be attached. Now bounded to a small multiple of the actual attachment cap.
644
+
645
+ No breaking changes.
646
+
647
+ ## 1.436.2 (2026-07-27)
648
+
649
+ **No functional change.** Records `v1.436.0` and `v1.436.1` in `scripts/verify-published-tags.mjs`'s deliberate-skip allowlist — both tags exist in git but were never published (1.436.0's CI hit the flaky test fixed in 1.436.1; 1.436.1's own CI then correctly refused to publish because *this exact guard* saw 1.436.0's still-unrecorded gap first). Both versions' actual content ships here. This is the guard doing precisely what it was built for — see its own file header for the incident history it exists to prevent from repeating silently.
650
+
651
+ ## 1.436.1 (2026-07-27)
652
+
653
+ **Fix: a CI-only-flaky assertion in the RB-198 F3 process-reap regression test, found while shipping 1.436.0** (the tag exists but was never published — CI failed before the publish step ever ran; see the standing "tag ≠ published" caution in this repo's own release notes). An intermediate assertion checked that a SIGTERM-immune escaped descendant was still alive at a specific moment between its leader dying and the kill escalation's grace period elapsing — timing-fragile because the escalation's grace timer starts when the kill is requested, not when the leader actually exits, so a slow CI runner could let the grace window close before the leader-death poll loop even finished. It reproduced consistently (twice) against the same commit on GitHub's runners while passing locally every time. Removed: the check was never load-bearing for what the test actually proves — the descendant ignores SIGTERM outright, so the only way it can die at all is via the escalation's own carried-forward reap, which the test's two remaining (non-racy) assertions already establish end to end. Verified the trimmed test still fails correctly against a reintroduced defect in that reap path before restoring it.
654
+
655
+ No functional changes; unrelated to the 1.436.0 background-agent fix it ships alongside.
656
+
657
+ ## 1.436.0 (2026-07-27)
658
+
659
+ **Fix (RB-205): a background agent's final answer over ~4,000 characters was being silently cut to that length, with no indication anything was missing, well before it ever reached the display/recovery layer that was supposed to handle exactly this case.** Every settle call site (a resumed run, a forked background run, and a plain background run — all three) sliced the agent's own free-form answer down to a bare 2,000 or 4,000 characters with no truncation marker at all, before handing it to the task registry. The registry's own 32,000-character clip (with its file-pointer/offload-aware design) never saw anything remotely close to large enough to actually engage — a background agent exists specifically to produce substantive written output, which routinely exceeds a couple thousand characters. The registry now receives the agent's full answer (capped only by a 200,000-character defensive ceiling, mirroring the same two-tier shape already shipped for workflow results) alongside the existing short display value, and prefers the full one when polled. The separate live-push notification payload (a genuinely different, smaller size budget) keeps its existing cap but now says so explicitly instead of cutting silently.
660
+
661
+ This went through two review rounds, each catching a real issue before release:
662
+
663
+ - The first implementation copied the workflow-result precedent verbatim, including skipping the registry's own clip entirely in favor of a generic downstream size-based offload. That downstream mechanism turned out not to be universally present (a second tool-mounting path has no such wrapper) and, where present, made a deployment's own configured output-length limit stop applying to this task type — trading a bounded-but-lossy result for a potentially unbounded one. Reworked to keep the registry's own clip in the loop, just feeding it the actual full answer instead of an artificially pre-shrunk one.
664
+ - Reviving a previously-completed agent for another run cleared its short display result but not the paired full one — a second, short run's answer could end up served alongside (and read as visible before) the first run's stale, much longer answer.
665
+
666
+ No breaking changes.
667
+
668
+ ## 1.435.0 (2026-07-27)
669
+
670
+ **Fix (RB-198 F4, form-one audit): a foreground `bash` command whose output exceeds the display cap now preserves the full captured text to disk instead of discarding whatever the inline head+tail clip omits.** Before this, the omitted middle of a long build log or verbose command was gone for good the moment the tool result was built — the only way to see it was a blind full re-run. The inline display itself is unchanged (same head+tail-with-honest-marker shape, still additive); a reply that got clipped now also gets a trailer pointing at a file with everything that was actually captured, plus a copy-pasteable command to inspect it. Applies to both a clean exit and a ran-then-cut (timeout/abort) result. Deliberately not wired into the Read tool's containment-root exemption the way the existing background-task output file is — that exemption is tied to a registry row with its own lifecycle, and a one-shot foreground command has no natural row to hang one off; instead the reply points at using `bash` itself, which was never containment-fenced at the command-text level to begin with.
671
+
672
+ Two rounds of independent review (run in parallel, each blind to the other, per this repo's standing practice) each found real issues before release:
673
+
674
+ - The recovery-file trailer originally said the "full" output was preserved. Not quite: if the raw output ever hit the execution environment's own internal 8MiB rolling-tail cap before reaching this layer, the text was already missing its head by the time this code ever saw it, and nothing downstream can get those bytes back. Reworded to "captured" — no promise beyond what this layer actually received (the text itself still carries that cap's own honest truncation marker when it fired, so nothing is hidden either).
675
+ - The example recovery command wasn't shell-quoted, so a temp path containing a space or a quote would produce a broken or misinterpreted command if copied verbatim.
676
+ - The read-only variant of the bash tool shares the same underlying execution path but has a much narrower allowed-command list that does not include the command the recovery example suggested — on that variant the "fix" would have been unusable advice. The example now branches by which variant is running.
677
+ - A large command whose entire stdout is a recognized inline image (already delivered in full as an image content block, not text) was still being redundantly written to a recovery file — wasted work pointing at data the model already fully has. Now gated on the output stream that can actually still be missing something.
678
+
679
+ No breaking changes.
680
+
681
+ ## 1.434.0 (2026-07-27)
682
+
683
+ **Fix (RB-198 F3, CC 2.1.212 parity): `killProcessTree` now also catches descendants that escaped the target's process group, closing the other half of a previously-partial fix.** A process-group kill (`kill(-pgid)`) only reaches processes still in that group — a descendant that starts its own session (a build tool spawning workers in a fresh process group, for example) leaves the group entirely and survives untouched. `killProcessTree` now also enumerates the full process table, walks the parent-pid chain from the target to find every descendant regardless of which group it now belongs to, and signals each directly — matching the reference implementation's own two-part kill mechanism.
684
+
685
+ This shipped after three rounds of review, each catching a real, distinct defect before release — recorded here because the fixes changed the shape of the final mechanism materially:
686
+
687
+ - Enumeration must happen *before* any signal is sent, not after: once a target dies, the kernel immediately reparents any escaped descendant to init, so a post-kill scan can no longer find it as the target's descendant at all. Caught by a real multi-process-tree test, not by inspection.
688
+ - That ordering fix, built on an async scan, quietly broke three existing timing guarantees: it could collapse the SIGTERM-then-grace-period window on a busy host, and made `force: true` — which callers rely on to mean "already dead" the instant it returns, to synchronously free a concurrency slot or mark state terminal — no longer actually immediate. Fixed by making the scan synchronous instead (a brief, bounded block of the event loop; this call is not on a hot path and nothing awaits it today), which restores every prior timing contract exactly.
689
+ - The descendant set found before a grace period must be carried into the SIGKILL that follows it, or the exact scenario this fix targets — a well-behaved leader exiting normally while its escaped descendant ignores the signal — loses that descendant permanently (the leader is gone, so there's no longer anything to re-scan from). But a pid carried across a wait is a name, not an identity: if that process exited and its exact pid was reused by something unrelated in the meantime, blindly re-signaling it would hit a stranger — the same class of problem an existing pid-reuse guard already handles for the primary target, extended here to cover descendants too (using the process's elapsed-running-time as a lightweight identity check, since there's no retained handle for a process this code never itself spawned).
690
+
691
+ No public API changes.
692
+
693
+ ## 1.433.0 (2026-07-27)
694
+
695
+ **Fix (RB-198 F2, CC 2.1.212 parity): a foreground command killed by an abort or its own timeout now gets a SIGTERM-first grace period instead of an immediate SIGKILL.** CC gives every managed kill the same SIGTERM→grace→SIGKILL sequence; three call sites here — all sharing `exec()`'s own closure (abort, timeout, and the auto-background-adoption-rejected fallback) — went straight to an immediate SIGKILL instead, with no SIGTERM at all. The concrete, previously-undocumented cost: a killed git/npm child gets no chance to remove its own lock file, which then blocks every subsequent operation until removed by hand. The existing graceful kill path (SIGTERM → 3s grace with early-exit polling → SIGKILL, plus a pid-reuse guard for the escalation) already existed and was already tested — it just had only one caller.
696
+
697
+ The scope stops there deliberately. Independent cross-review (codex + a second, independent pass) found that four other `killProcessTree` call sites in the same file (a background shell's own timeout, `spawnBackground`'s hard-wall timeout, an explicit `killBackground`, and session teardown) share a different problem that graceful conversion would make worse: each flips the shell's polled `status` to `"killed"` *before* the kill actually lands. Under an immediate SIGKILL this is harmless (the flip and the death are effectively simultaneous); under a grace period it is not — a process that ignores SIGTERM would read as already-terminal to a poller for the whole grace window, prematurely freeing a concurrency slot, while teardown's own `status === "running"` safety net has already stopped covering it. The correct fix is decoupling the status transition from the kill attempt; that is real, separate scope and is left for a follow-up. All four sites keep their existing immediate-SIGKILL behavior, now with that reasoning on record instead of no rationale at all (`git blame` traced the original `force:true` everywhere to the initial bulk import).
698
+
699
+ One Windows-specific regression this review also caught before it shipped: the pid-reuse guard added to the three converted sites, if forwarded to the Windows kill path, would cancel the *entire* escalation — including the platform's own MSYS-descendant supplement, which exists specifically for the case where the guard's own signal ("the original process already exited") is true but wrong (a Git-Bash job whose Windows parent chain is already broken while its real work continues under an orphaned MSYS process). Fixed by not forwarding the guard on Windows, leaving that platform's kill behavior exactly as it was.
700
+
701
+ No public API changes.
702
+
703
+ ## 1.432.0 (2026-07-27)
704
+
705
+ **`BackgroundChildEvent` gains `parentToolCallId` — the delegating Agent tool call's own id, present from the spawn frame onward (cross-repo [1832]/[1839] P1-2).** Parent attribution already rode every forwarded child content event (`TaskEvent`'s `TaskEventIdentity`), but this separate observer/fleet event family — the one a deployment publishes fleet rows and completion pushes from — never carried it, so a consumer had to reconstruct which spawn frame a later tick/terminal belonged to from arrival order and shape alone. Threaded through all three spawn lanes (plain background, fork, revive/resume) at every frame kind (spawn/tick/terminal, both the settle and reject legs) — the same shape as the `transcriptId`/`currentTool` additions shipped over the past two days, closing the identical class of gap a third time.
706
+
707
+ No breaking changes.
708
+
709
+ ## 1.431.0 (2026-07-27)
710
+
711
+ **Fix: `MAX_EDIT_BYTES` (added in 1.430.0) is now actually reachable from the package root.** It was defined and exported from the `tools/fs` submodule but never added to `src/index.ts`'s curated re-export list, so `import { MAX_EDIT_BYTES } from "@sema-agent/core"` — exactly as 1.430.0's own changelog entry described it — threw a SyntaxError. Caught by running the same anonymous fresh-install check CI runs, by hand, against the already-published 1.430.0 tarball, using a **named** import instead of a namespace import.
712
+
713
+ **The fresh-install verification script itself is strengthened for the same reason it missed this the first time.** `scripts/verify-fresh-install.mjs` used to check for exactly one hardcoded name (`Runner`) — a namespace import (`import * as core`) never fails on a missing named export, so nothing in CI's own release gate could have caught a newly-added-but-unreachable export like this one. It now derives the full list of value exports directly from `src/index.ts`'s own `export { ... } from "..."` clauses (609 names today, type-only entries excluded) and checks every one of them by name on the freshly installed package. Verified against the real, already-published 1.430.0 tarball before this fix: it correctly and specifically reported `MAX_EDIT_BYTES` as the only missing export.
714
+
715
+ No breaking changes.
716
+
717
+ ## 1.430.0 (2026-07-27)
718
+
719
+ **Security fix (RB-201, CC 220 `Ipd`/`ein` parity): a sub-agent spawn now has its prompt+toolset reviewed by the auto-mode classifier BEFORE it runs, and its completed work reviewed before handing control back to the parent.** Previously auto-mode's classifier only covered the ordinary per-tool-call ask path — a main agent blocked from a dangerous action directly could write the same action into a sub-agent's prompt and have the child execute it entirely unreviewed, then hand back a clean-looking summary with no scrutiny. New `ToolExecuteContext.autoModeReview` (Runner-filled, reuses the SAME classifier instance — and its live breaker state — as the existing per-call ask review) threaded through every subagent-spawning path; a classifier BLOCK on spawn refuses to start the child at all, a classifier BLOCK on handback prepends CC's verbatim SECURITY WARNING text to the report without discarding it. Absent auto-mode configuration, this is a complete no-op. Fixed during codex + independent cross-review, both confirmed by reproducing each issue against real code before the fix and again after: a child whose dangerous tool activity was covered by an empty or benign final summary no longer bypasses handback review (now also consults the child's actual recorded tool steps/edited files, not just its self-reported result text); a spawn blocked by the classifier now releases the worktree checkout and observer marker it had already provisioned instead of leaking them; an abort arriving while the classifier call is in flight is rechecked and now aborts the spawn instead of proceeding as if the classifier had gone unavailable; the classifier's actual judged input (not just the truncated display text) is now capped, closing a path where a large enough prompt/system-prompt/result could make the classifier's own call error out — which fails open — silently disabling the review with no visible refusal. Declared, not-yet-covered gaps: background/fork/retained-resume handback paths, and workflow fan-out's own spawn point (CC's original sole call site for this mechanism) — recorded in `docs/REVIEW-BACKLOG.md`.
720
+
721
+ **Fix (RB-200 F7, CC 220 `ned`/`pl()` parity): Edit now refuses a target file over a 1GiB size cap instead of loading it whole into memory, closing an unguarded OOM lever any Edit call on a large enough file could pull.** Stats the target before touching it (pre-read gate) and rechecks the actual read size (post-read TOCTOU gate, covering both the normal find/replace path and the `old_string:""` full-overwrite path) — the same two-gate pattern Read already uses for itself. Found during independent review and fixed before release: the 1GiB cap sits above Node's own internal string-length ceiling (~512MiB on this engine), so a file in between the two limits passed both size gates but crashed the decode step with an uncaught engine error instead of the intended refusal message — now caught and mapped to the same "too large to edit" response regardless of which layer fails.
722
+
723
+ No public API removed; both fixes are additive (`ToolExecuteContext.autoModeReview` optional field, `MAX_EDIT_BYTES` new export).
724
+
725
+ ## 1.429.0 (2026-07-26)
726
+
727
+ **`BackgroundChildEvent`'s tick frame gains `currentTool: { toolName: string; target?: string }` — the same tool/target pair `currentAction` already concatenates into a prose line, exposed separately so a consumer can look `toolName` up in its own tool registry instead of parsing text (CC `renderToolActivity` parity).** The structured data was already computed one line before the string concatenation that builds `currentAction` — no new derivation needed. New `SubagentStepRecorder.currentActionStructured()` accessor, same undefined-until-first-tool-call lifecycle as `currentAction()`.
728
+
729
+ No breaking changes.
730
+
731
+ ## 1.428.0 (2026-07-26)
732
+
733
+ **Security fix (RB-203): the two always-on auto-mode safety rules (unverifiable recursive delete, session-transcript tampering) now use CC 220's real SOFT BLOCK semantics — escapable by a named+specific auto-mode classifier verdict, but never by a deployment's blanket `onAsk: "allow"` bypass mode.** Previously both rules were an unconditional `deny` that short-circuited before any classifier ran, on a CC-206 citation that does not appear in the 220 corpus for these two rules; 220 puts them in the SOFT BLOCK bucket (User Intent Rule can clear them), which sema's own classifier prompt already implements correctly but had no path to reach. The straightforward first fix (`deny` → `ask`) reused the existing generic per-ask classifier arm with no new plumbing — but independent cross-review (codex, confirmed P1) found it introduced a real regression: a deployment using the documented `onAsk: "allow"` bypass mode (e.g. this package's own `sema-tb.ts` sandbox harness) would have silently rubber-stamped these asks with no classifier and no judgment applied at all, strictly weaker than the deny it replaced. Fixed with a new `requiresRealApproval` marker (`PermissionResult`/`AskRequest`) that `resolveAsk` treats as "no approver at all" specifically for a blanket `"allow"` string, while a live classifier verdict or a genuine approver *callback* (even one that always approves) still resolves normally. No public API removed; `PermissionResult`'s `ask` variant and `AskRequest` gain one new optional field.
734
+
735
+ **Fix (RB-198 F1, CC 220 `Zry`/`WZi.#m` parity): a foreground Bash command that outruns its timeout can now be converted to a background task instead of always being SIGKILLed, closing the gap where a non-idempotent long command (a build, a migration) lost all progress and forced a blind re-run.** Deployment-level opt-in (default `false`, byte-compatible): `RunnerDeps.hands.autoBackgroundOnTimeout`, threaded through `HandsToolkitOptions`/`createHandsToolkit` the same way `bashReadonlyAllow`/`commitCoAuthor` already are. `canAutoBackground()` is a deliberate, documented simplification of CC's ~1700-line bash static analyzer (no `git` invocation anywhere in the command; first word isn't `sleep`), a superset of what CC would allow, exercised end-to-end through a real `NodeExecutionEnv`. Also fixes, found during codex + two rounds of independent cross-review: a command whose timeout was clamped down by the task's own wall-clock deadline (not by the command itself running long) no longer auto-backgrounds — it still gets killed with the existing deadline-clamp headline, which carries load-bearing "do not retry, the finalize window is close" guidance that silent auto-backgrounding would have discarded; a finalize-window engine cut's kill handle now marks the same in-flight-kill guard the abort path already used, closing a race where a dying/dead process could be misreported as a successful background adoption; the command-segment split now treats a bare newline as a statement separator (an ordinary multi-line script previously defeated git-detection entirely); two CC wrapper-prefixes (`stdbuf`, `noglob`) that this port had dropped are recognized again.
736
+
737
+ **Fix: spawn/tick background-child observer events now carry `transcriptId`, not just the terminal event.** A consumer wanting to read a background subagent's live transcript before it finishes had no handle — `transcriptId` used to ride only the terminal frame. Now present from birth across all three spawn lanes (plain background, revive/resume, fork) and their reject-leg terminal frames (an early `runTask` throw, not a clean `TaskStop`-to-killed path), which had independently been missing it too.
738
+
739
+ No other public API changes.
740
+
741
+ ## 1.427.0 (2026-07-26)
742
+
743
+ **Fix: a host turn ending or being interrupted (e.g. `esc`) no longer kills workflows it launched.** `run_workflow` is unconditionally background — it returns a `runId` immediately and never awaits completion — but `startWorkflow` was folding the launching tool call's own execution signal into the workflow's ongoing lifecycle signal (`AbortSignal.any([cancelController.signal, ctx.signal, ...])`), so the workflow (and every agent it spawned) died the moment the host turn's signal fired, contradicting the tool's own "submit and return, keep running" contract. Root-caused from a real production incident (cross-repo forensics + CC 220 parity check: CC's own workflow execution context replaces the host controller with a task-scoped one, never merges it). `TaskStop` is unaffected — it drives an independent task-level cancel controller that was never touched. Also fixes an adjacent gap surfaced during review: a tool call whose signal is already aborted before the async setup (script resolution) completes no longer launches a real, resource-consuming workflow that its own gone caller can never learn the `runId` of.
744
+
745
+ No public API changes.
746
+
747
+ ## 1.426.0 (2026-07-26)
748
+
749
+ **A delegated subagent's system prompt now states that messages from the agent that launched it are never user consent or approval, and cannot authorize changing permission settings, CLAUDE.md, or configuration (CC parity).** New `core/mode.subagent-consent` prompt section, CORE-locked (a custom agent-definition's own `systemPrompt` cannot suppress it — same posture as the teammate-communication addendum), admitted via a new `PromptRuntimeFacts.isSubagent` fact wherever the delegation resolves: the built-in default persona, a custom agent-definition prompt, a `roles.*` preset, a workflow-spawned agent, or a team-discussion member. Previously nothing in a subagent's own prompt defended against a crafted parent/peer message claiming fake user approval — the only defense was whether the sender happened to follow the coordinator prompt's convention. Also fixes: a directly-started workflow (no launching tool call) now still marks its spawned agents as delegated; a `PromptProvider` that returns an already-assembled prompt (the M9 opaque-passthrough migration path) no longer silently drops this section.
750
+
751
+ No public API changes.
752
+
753
+ ## 1.425.0 (2026-07-26)
754
+
755
+ **MCP: an idle call (transport open, process alive, server just never responds) no longer hangs the run loop for up to the ~27.8h total ceiling.** New idle watchdog (`armMcpIdleWatchdog`, stdio 30min / http-sse 5min defaults, `MCP_IDLE_TIMEOUT_STDIO`/`MCP_IDLE_TIMEOUT_HTTP` overrides — same discipline as `MCP_TOOL_TIMEOUT`) wired into `callTool` and all three resource tools (`ListMcpResourcesTool`/`ReadMcpResourceTool`/`ReadMcpResourceDirTool`). A pending elicitation (human answering a form) freezes the clock instead of tripping it. Distinct, purpose-built error copy ("received no response for Nms") separate from the pre-existing total-ceiling timeout.
756
+
757
+ **MCP: a tool whose input schema is a root-level `anyOf`/`oneOf`/`allOf` union no longer gets the entire server dropped or (worse) 400s the whole model request at a strict provider.** New `normalizeMcpToolSchema` flattens the combinator into a plain `{type, properties, required}` shape before the existing structural gate runs (properties unioned across branches first-writer-wins, `required` unioned from top-level + `allOf` only — never `anyOf`/`oneOf`, which are alternatives). Building this surfaced a deeper pre-existing issue: the SDK's own `client.listTools()` uses strict validation that rejects the *whole* `tools/list` response — not just the offending tool — on this same shape; a lenient wire-level parser (`listToolsLenient`) replaces it, restoring the SDK's `cacheToolMetadata` side effect explicitly (`callTool`'s structured-output validation and `execution.taskSupport === "required"` guard both silently no-op without it).
758
+
759
+ **Compaction: a Read on an unchanged file, made right after a compaction summarized it away, no longer answers with "content omitted to save context" pointing at content that no longer exists anywhere in the model's context.** The Read-dedup stub's cache (`readFileState`) is now cleared at every compaction landing site (turn-boundary, PTL-recovery, and finish lanes), except entries seeded from the system prompt (untouched by compaction) and files re-attached whole into the summary (re-registered against the real re-read window).
760
+
761
+ No public API changes.
762
+
763
+ **BREAKING: the PostgreSQL reference adapter left the npm surface.** `PgSessionRepo`, `PgCheckpointStore`, `PgMemoryStore`, `PgToolResultStore`, `ensurePgAgentSchema`, `PG_AGENT_TABLES`, `isUniqueViolation` and the `Pg*` types are no longer exported and no longer ship in the package (owner's call: the pg/tidb adapters are repo examples, not a supported production surface — a server deployment uses its own hardened stores). They remain in the repo under `src/examples/adapters/` and are read/imported by path from a checkout; the pg-mem test bed still exercises them on every run.
764
+
765
+ **Package hygiene: no source maps in the tarball.** The package ships no `src/`, so `.js.map`/`.d.ts.map` pointed at files consumers do not have — ~40% of the unpacked size for zero function. With the examples removal: 6.4 MB / 981 files → **3.4 MB / 491 files** unpacked (822 kB tarball). Stack traces cite dist coordinates; each version's dist is reproducible from its tag.
766
+
767
+ **`CheckpointStore.listScopes?` — the enumeration face a host's scope registry can be rebuilt from (server [1800]).** Optional like `listByScope`; in-memory and file impls provide it. Returns the distinct scopes with ≥1 PENDING checkpoint — deliberately matching `listByScope`'s visible surface, so `listScopes() × listByScope()` is exactly the full cross-scope inbox. Fixes the failure shape where a corrupt sidecar registry silently emptied the approval inbox with the pending rows still on disk.
768
+
769
+ ## 1.423.0 (2026-07-26)
770
+
771
+ **BREAKING (schema): the pg adapter's DDL is now the complete schema — the incremental `ADD COLUMN IF NOT EXISTS` seams are gone, and every identity key column pins `COLLATE "C"`.** Pre-v1 schema policy (owner's call, 2026-07-26): nothing is in production, so an existing database is recreated, not migrated — `sessions.forked_from` and the checkpoints/memory legacy-table seams were dead weight that let a stale table silently keep its old shape. Newly pinned: `memory.id` (a PRIMARY KEY member) and `sessions.forked_from` (holds session ids) get `COLLATE "C"`, closing the case-fold collision a nondeterministic-ICU database default would allow within a scope. Once a schema version is declared, changes return to additive idempotent migrations. The collation guard's list now covers every text PK member with a structural backstop; its known-gap ledger is empty.
772
+
773
+ **`SessionPolicyStore.deleteBySession?` — the erase half of session sync (server [1796], C1b/E21).** Optional like `listBySession`; both bundled impls (InMemory + File) implement it: remove every `(principal, rules)` record for a session, idempotent, other sessions untouched. Without it an importer that overwrites a session could not erase the abandoned branch's policy rows (they kept enforcing the old branch's rules), and a server-side session purge was silently incomplete on the file-backed store. A row rewritten after the delete restarts at rev 1 — the delete removes, never hides.
774
+
775
+ **BEHAVIOUR: `addWorktree`'s `destroy()` is fail-loud — `git worktree remove` is result-checked, retried once, and a second failure rejects with the orphan detail (server [1796], with injected reproduction).** The old fire-and-forget `.catch(() => undefined)` never even saw a failure (exec reports one as a Result, not a rejection), and its "pruneWorktrees reaps the orphan" justification was wrong: prune only deregisters worktrees whose directory is GONE, so a transient failure (spawn EAGAIN under parallel load) left dir + registration behind permanently. Every Runner destroy call site already wraps in try/catch and surfaces via `onError`. The `rootEnvAt`-failure cleanup path is result-checked + retried too, with the original error kept authoritative.
776
+
777
+ ## 1.422.0 (2026-07-26)
778
+
779
+ **BEHAVIOUR: an `additionalContext`-only Stop-hook push-back now counts toward the consecutive cap, and no longer resets it (RB-186).** Returning `additionalContext` without `block` makes the turn not end — which is precisely what the cap exists to bound — yet it was exempt from the count, and worse, it set the count back to zero. A hook alternating `block` and `additionalContext` produced a sawtooth (0, 1, 0, 1, …) that never reached the cap at all; a hook returning `additionalContext` every time ran until `maxTurns` or `timeoutSec` was exhausted, looking busy rather than stuck because every iteration was a real model round-trip.
780
+
781
+ This was carrying a CC-parity claim (`CC 2.1.201 :472050-472077`, "the cap is driven by `block` alone") pinned by three tests. Re-read against the CC 2.1.220 binary, CC does the opposite: the `additionalContext` branch pushes into the same array as the blocking-error branch, that array is returned as `blockingErrors`, and the main loop counts any non-empty `blockingErrors` against `CLAUDE_CODE_STOP_HOOK_BLOCK_CAP` (default 8, the same value this package uses). CC also never resets mid-run — every branch that does not continue simply ends the run. The three tests are re-anchored to 2.1.220 and a fourth pins the alternating shape. One difference is left in place deliberately: CC orders the blocking-error message before the additional-context one and this package does the reverse, which is presentation, not safety.
782
+
783
+ Hooks that legitimately need more than eight consecutive push-backs should pace themselves on `ctx.stopHookActive` / `ctx.consecutiveBlocks`; the override message now says that `additionalContext` counts, so nobody goes looking for a block that was never there.
784
+
785
+ ## 1.421.0 (2026-07-26)
786
+
787
+ **Untrusted-egress redaction was quadratic, and it is on a synchronous path (RB-183).** `\w+` before a literal `://` cannot fail cheaply: on a run of word characters with no colon in it, the quantifier consumes to the end of the run, needs a `:`, and every character it gives back is by definition another word character that also is not a `:` — O(k) wasted work at each of the k start positions. A downstream repo hit it as a purely synchronous test timing out, then measured it: 32 000 characters cost ten seconds of CPU, which on a shared replica is the event loop rather than one request. The scheme is now RFC 3986's production with a bounded quantifier, capping backtracking at 64 steps instead of k. Measured on the same shape, 64 000 characters go from 1613 ms to 7.5 ms, and growth is linear.
788
+
789
+ Worth stating precisely, because it changes where a size limit belongs: the cost is quadratic in **the longest unbroken run of word characters**, not in total length. At 32 000 characters, prose, log lines, paths and URLs all cost ~0 ms, and standard base64 costs ~8 ms — its alphabet contains `+` and `/`, which break the run every few characters. What is expensive is one uninterrupted token: a hex digest, base64url, a long `_`-joined identifier. Coverage was diffed case by case before changing the pattern: three inputs differ and none redact less — an invalid digit-initial scheme and an over-64-character scheme keep a few leading characters of the bogus scheme while the URL itself is still redacted, and `git+ssh://…` is now covered whole where `\w+` could not cross the `+`.
790
+
791
+ **Two release gates stopped being procedures (RB-182, RB-184).** The live check is env-gated, so a missing variable made all nine tests skip while vitest exited 0 — and the gate was reading that exit code. `npm run gate:live` now names a missing variable instead of skipping, fails on any skipped test, and fails on zero passing tests, because an empty gate is not a passing gate. Separately, `npm run gate:skips` pins the *set* of tests allowed to skip rather than the count: a suite reporting 68 skips gives every future skip a place to hide, and coverage can leave without a red anywhere.
792
+
793
+ **A load-sensitive threshold stopped producing uninformative reds (RB-185).** A test proving an auto-allowed shell form neither terminates nor bounds its output asserted that more than 100 MiB streamed inside its window; on a loaded machine the same unbounded stream delivered 95.9 MiB and the test failed, saying nothing about the behaviour under test. Non-termination is already carried by the kill signal; the byte count only has to exceed what any bounded form could produce, and the largest output cap in the tree is ~32 KB.
794
+
795
+ ## 1.420.0 (2026-07-26)
796
+
797
+ **A release can no longer finish green with nothing published (RB-179).** On the release leg, an absent npm token used to print a warning and exit 0 — and so did the registry-verification step behind it. The result was the worst outcome arriving as the quietest one: tag pushed, release announced, job green, package absent from the registry. The six historically unpublished releases at least went red. A missing token on a tagged release is now a failed release, and the verification step keeps no escape hatch of its own: it is the criterion that decides whether the version exists, so nothing may short-circuit it. Ordinary pushes are unaffected — they never reach this leg.
798
+
799
+ **The `TaskEvent` branch table is frozen at compile time (RB-178).** Adding a branch is addition and stays welcome; what this gate blocks is adding one *without anyone being forced to notice*. The existing export-surface snapshot compares name → kind, so a new union branch, a new interface member, and a new optional parameter are all invisible to it — which is how `context_usage` shipped, formally requested by a downstream repo, with every gate green and no mention in the release notes. A two-directional `Exclude<>` difference set now names the offending branch by literal, and the frozen list is a single `as const` array that both the type gate and the source-scan read.
800
+
801
+ ## 1.419.0 (2026-07-26)
802
+
803
+ **A Stop hook can now see the run it is being asked to judge (RB-177).** Its context carried two counters and nothing else — enough for a hook asking "have I blocked too often?", and structurally insufficient for any hook asking something *about the run*, which is the entire class that a settings-declared prompt-shaped hook belongs to. A downstream repo measured the consequence end to end: the carrier was constructed, invoked, and returned without error, and the model answered "I don't have direct access to your file system…" — so no decision was ever parsed and the hook never blocked once. Every layer reported success and the feature could not work.
804
+
805
+ Ownership checked before fixing: the carrier that renders a prompt and calls a model is not in this package (zero hits), but only this package holds the session, so the gap was here rather than in any of the layers dutifully forwarding an empty view. `StopHookContext` now offers `getBranch?()`, **lazily** — a hook that reads only the counters pays nothing, and one that must judge the run gets it in full rather than through a truncation policy the engine would have to invent on the caller's behalf. That decision carries a budget and a trust tier, and both belong to the caller.
806
+
807
+ **A self-check that came back clean, and got pinned anyway (RB-176).** A downstream report described a defect shape worth checking for: reading a background command's output, awaiting, and *then* reading its status leaves a window where a terminal state lands after the read — and since consumers stop polling once they see a terminal state, the tail of the output is never read by anyone. Not an extra poll; silently lost output. `pollBackground` here is structurally immune: the whole method is one synchronous turn, so there is no interleaving point between reading the tail and reading the status. Structurally immune is not the same as guarded, so it now is: an `await` appearing in that method fails the suite with the reason.
808
+
809
+ ## 1.418.0 (2026-07-26)
810
+
811
+ **Two defect families across the file stores, fifteen reproducible instances, one fix each (RB-167, RB-168).** An independent diagnosis mapped every store in the tree into a matrix. The fifteen reds were not fifteen bugs — they were two rules, each implemented separately in four or five stores, and therefore gotten wrong separately in each.
812
+
813
+ **The compaction hand-off had no compensation.** Every store that compacts does close → rewrite → reopen, and the rewrite can fail (EACCES, ENOSPC, a read-only mount). Three stores left the log closed forever, so one transient I/O error meant `log_closed` on every subsequent write — and since these logs are now shared per directory, that bricks every instance on it, not just the one that compacted. The fourth reopened in a `finally`, which a differential probe showed covers the "directory is unwritable" shape but not "the file itself is unwritable", and which replaces the original error with the reopen's own when it fails.
814
+
815
+ The fix belongs to the shared primitive rather than to four call sites: `AppendLog.closeForSwap()` plus a lazy reopen. `close()` keeps meaning "this holder is finished"; a swap-closed log reopens at its next write, so a cleared fault resolves itself and a persistent one surfaces at the write that needed it, with the compaction's own error already propagated unmasked. Three stores also called `compact()` bare from `commit()` — compaction is housekeeping that runs *after* the operation is durable and applied, so letting it throw reported an already-committed CAS as a failure; the caller retries, hits the new revision, and fails again. One success reported as two failures.
816
+
817
+ **Cross-instance authority was keyed on unnormalized paths, in five places.** The workflow journal's open ledgers lived on the instance, so one store's `deleteByRun` unlinked a ledger while another kept appending to the orphaned inode — reporting success, fsyncing, and being invisible to every reader including itself. The snapshot store's in-flight set lived on the instance, so a concurrent reap collected blobs a peer had already published while the import still reported success. The CC mailbox adapter's lease table lived on a factory closure, so two stores over one team directory granted the same lease twice — in a file whose own header calls that state "IN-PROCESS". The file mailbox's shared key went through realpath but not the case fold. The CC task list's lock artifact sits *beside* the directory it protects, so two spellings produced two locks and a lost update.
818
+
819
+ That last one produced the rule worth keeping: **a lock or authority artifact kept inside the directory it protects is canonicalized by the filesystem for free; one kept beside it, or in memory, has to canonicalize itself.**
820
+
821
+ Two shared rules that lived in only one implementation moved to their contract modules: the journal's oversize cap (whose own documentation promised that all backends degrade identically, while the in-memory reference could not reach it) and the task list's dependency arrays (the in-memory store rejected a row missing them only by accident — a spread that happened to throw — while the file store stored it, leaving a row that detonated later at `TaskUpdate`). The arrays now **normalize** rather than reject on both sides: the harm was a stored row that explodes at read, not an incomplete one, and rejecting would have been a behaviour change dressed as a fix.
822
+
823
+ **The first guard I wrote for all this had no discriminating power at all (RB-169).** Three independent reasons: an assertion written as `expect(...).resolves.not.toThrow` — no call parentheses, so a property access that asserts nothing; a fault injection that never triggered the compaction path it was aiming at; and a cross-instance case built from symlinks, which realpath already handled and which therefore said nothing about the case fold that had just been added. All three are the same mistake — writing a gate and then assuming it holds. Replaced with the diagnosis probe itself, whose discrimination is measured (fifteen red before the fixes, seventeen green after), and re-verified by mutation.
824
+
825
+ **A probe that judged a different command than the one that runs (RB-170, HIGH).** The irreversibility tier calls a deployment-supplied `reversibilityProbe` to decide whether a "maybe irreversible" tool may auto-allow. This module states the same invariant twice — run the policy on the FINAL input, never the model's stale args — and an earlier fix restored it for the *hook* rewrite while leaving the *policy* rewrite out of reach: the policy's rewrite is captured before the tier runs but only merged into the current input further down, on the ask path. So a policy that rewrote `ls` into `rm -rf <path>` was probed as `ls`, reported reversible, and auto-allowed — while the tool executed the rewrite. The probe's own contract says it is never trusted to auto-allow past the gate; handing it a different command than the one that runs is the one way to make it do exactly that.
826
+
827
+ **A tag went out whose CI was red, for the second time this week (RB-171).** Promoting the diagnosis probe, I reviewed its discriminating power and not its environment assumptions. One case wrote its precondition as an assertion — `expect(caseInsensitiveFs(base)).toBe(true)` — which is always true on the author's macOS and becomes a failing test on the Linux runner, turning "not applicable here" into "broken here". The gate went red, the publish leg never ran, and the package did not reach the registry: precisely the shape recorded two releases ago, re-enacted by me. Two lessons: a precondition must be expressed as a skip, never as an assertion (the two are indistinguishable on the machine that wrote them and differ everywhere else); and promoting someone else's probe means auditing its environment assumptions, not only whether it can go red. Verified under both filesystem semantics before re-tagging.
828
+
829
+ **And the case fold went into a filesystem path (RB-173, HIGH).** The cross-instance fix above gave the CC task list's lock target `canonicalStoreKey`, which realpaths *and lowercases*. Lowercasing is right for an in-memory key and wrong for a path: on a case-sensitive filesystem the lowered path names a different, usually nonexistent directory. The author's filesystem folds case, so every local run was green and CI went red on Linux — the publish leg never ran and this version did not reach the registry on its first two attempts. The rule now sits in the code: **folding belongs to keys; paths get realpath and keep their case.**
830
+
831
+ Worth recording how that was found, because the first diagnosis was wrong. Told only that CI failed, I inferred the cause from what I had most recently touched — a promoted probe that wrote a precondition as an assertion (real, and fixed as RB-171) — moved the tag, and it failed again. Pulling the actual CI logs took one API call and named a completely different file. **When a gate fails, read its output before reasoning from what you changed**; with two plausible candidates I picked the wrong one, and the log would have settled it immediately.
832
+
833
+ The guard for RB-173 is source-level, and deliberately so: a behavioural pin was written first (create a mixed-case directory, assert no lowercased shadow appears) and mutation testing showed it cannot fail on a case-insensitive filesystem, because the lock artifact is deleted on release before anything can observe it. Rather than ship a pin that cannot go red, the tracked guard forbids handing `canonicalStoreKey`'s result to a path-consuming call, and the file says plainly that the behavioural guard is CI itself — which runs on the case-sensitive filesystem, and is what caught this.
834
+
835
+ **Then a third environment assumption in the same promoted file (RB-175).** It hardcoded `/private/tmp` — macOS's real path for `/tmp`, absent on Linux — so CI hit ENOENT and the publish leg again never ran. This happened immediately after writing down that promoting someone else's probe means auditing its environment assumptions: I fixed the one I had found and did not sweep for the rest. Fixing an instance while recording the lesson about fixing the class. Now `tmpdir()`, plus an enumerative guard that forbids handing a hardcoded absolute path to a call that actually touches the filesystem. It scans tracked files only — an untracked probe cannot break CI, and the moment it becomes tracked is exactly when this bites, which is also exactly when the guard fires.
836
+
837
+ Tracked tests 7136 → 7174.
838
+
839
+ ## 1.417.0 (2026-07-26)
840
+
841
+ **A user's background job vanished mid-run, and the only thing the engine could tell them was `stopped-by:"system"` (RB-165).** That is not a cause — it is the default, i.e. "nothing recorded who did this". Every kill that goes through the task registry records who did it; `disposeBackgroundShells` is an execution-env-level blanket sweep that cannot reach the registry, so the one class of kill a user actually notices was the only one with no attribution at all. Establishing even that much took a cross-repo investigation and a timeline argument.
842
+
843
+ Attribution has to be applied by the caller, immediately before the sweep — and there were **seven** call sites. This repository has spent the week learning what happens when a rule is applied at some of a surface's entry points, so there is now one function, `sweepBackgroundShells`, and an enumerative guard forbidding any direct call elsewhere in `src/core`. (That guard strips comments before matching: the method name appears throughout the comments on this surface — it is what the incident is about — and an `includes` predicate would have flagged every file. It is the same trap that made a different guard vacuous two releases ago, approached from the opposite side.)
844
+
845
+ `markStopSourceForEnvSweep(env, source, except)` claims exactly the rows the sweep is about to take: same env, still running, not on the keep-list. The keep-list passes through unchanged — marking a spared shell would tell the next reader that a live process had been killed. Attribution failure can never prevent the teardown it describes.
846
+
847
+ The companion ask for a production log is deliberately **not** implemented: it conflicts head-on with a pinned contract that the shutdown forensics channel is silent by default, and that contract is right — a verbose trace channel should stay opt-in. The log was a proxy for the attribution, and the attribution now lands in the field the user already reads, which is better than a line on stderr that nobody captures.
848
+
849
+ **What is not changed, and why (RB-166).** The same report asks for the sweep's keep-list to exempt every handle the registry still tracks, rather than only session-resident ones — a cross-lane background shell (a workflow child's, owned by the child's task id) matches none of the three current sources. The direction is right, but the reporter honestly recorded that all four of their reproduction shapes SURVIVED and that the sweep was never called in any of them: nobody yet knows which sweep fired in the incident. Exempting every tracked handle would make the sweep close to a no-op and leak processes, so changing the rule while the trigger is unknown trades one failure for another. What this release does deliver is the diagnostic half — a swept row now carries `env_sweep` alongside its owner and lane, so the next occurrence identifies itself.
850
+
851
+ Tracked tests 7128 → 7136.
852
+
853
+ ## 1.416.0 (2026-07-26)
854
+
855
+ ⚠️ **`sessionLogDigest` moves to `sema-log-v3`.** Still nothing consumes it — a downstream gate keys on the scheme rather than a version number, so it picks this up with no action. The same reasoning as last time applies with more force: the fix moves the hashed bytes, the module's rule says that requires a new scheme, and doing it before anyone is on the wire costs nothing.
856
+
857
+ **The v2 fix landed one level too shallow (RB-158).** `wireForm` degraded per key at the entry's top level — and every session entry keeps its content *below* that level (`data`, `message`, the `details` carriers, all typed `unknown` and supplied by the host). So one bigint or cycle anywhere inside `data` erased all of `data`, and two logs differing in `data.cmd` digested identically: a false "already in sync". It is also a regression against the serializer v2 replaced, whose per-key isolation held at any depth. The guard written for exactly this property put its bad value at the entry's top level — the one depth where the fix worked — so it passed. Now recursive, pinned by depth, with the fixture carrying an unserializable payload so the marker path is frozen too, and a new pin that the golden values are **literals** (nothing previously stopped someone replacing them with a computed expression, which would be green against any implementation while reading as a refactor).
858
+
859
+ **An imported session could steer the engine's disk reads (RB-159, HIGH).** A compaction entry's `details.modifiedFilesByRecency` seeds the next compaction's file set, which the working-file attachment reader then reads off disk and splices into the summary the model is told to trust. That reader calls `readBinaryFile` directly — measured: an absolute path outside the task root reads fine, with no policy check and no fence — because it was written for paths the engine derived from files the task itself had touched. Import is what breaks that assumption. Both ends are now closed: the import door validates the shape and bounds of the three path lists, and the reader fences to the task root plus the deployment's extra roots. Guarding one and not the other would leave the other's failure unwatched, which is how this family of rules has been landing all week.
860
+
861
+ **Two more doors that a two-door fix had counted as all of them.**
862
+
863
+ - **RB-160 (HIGH):** the third channel into the branch's active model. The entry scan writes it from an assistant message's `provider`/`model`, and that branch runs *after* the `model_change` branch in the same loop — so it is the last writer and overwrites the gated one. The previous fix counted two channels and closed two; the property it stated ("the ungated channel overwrites the gated one") therefore stayed true, with a different entry type doing the overwriting. Measured before this gate: `provider: 42, model: {}` imported cleanly and made the declared type of the active model a false statement.
864
+ - **RB-161 (HIGH):** `timestamp` had no door at all, and the compaction/custom-message readers put it through a parser that throws a bare `Error` — so one unparseable timestamp makes every later context build throw, permanently, with no recovery through the API. That is verbatim the outcome two earlier gates each cite as their reason for existing. Both enumerated the fields they were chasing; neither looked at the one every entry carries.
865
+
866
+ **The guard I have been citing all week was itself vacuous (RB-163).** Its predicate was `src.includes("canonicalStoreKey")` — which a `{@link}` reference in a comment satisfies, and in the one store where that was the only occurrence, the property was false (RB-162: the shared-state key went through realpath but not the case fold, so two spellings of one directory each allocated their own sequence numbers over one physical file). Its entry condition tested for an *implementation shape* rather than the property, exempting four files outright. And it scanned one of the two store directories, leaving the CC adapters entirely outside its view.
867
+
868
+ Rewritten to strip comments, require a call site, and cover both directories, it named three stores on its first run — the same three an independent diagnosis had found real cross-instance defects in:
869
+
870
+ - the workflow journal's open ledgers and scope cache lived on the instance, so one store's `deleteByRun` unlinked a ledger while another kept appending to the orphaned inode — reporting success, fsyncing, and being invisible to every reader including its own;
871
+ - the file-snapshot store's in-flight set lived on the instance, so a concurrent `reap` collected blobs a peer had already published while the import still reported success with a manifest referencing them;
872
+ - the CC mailbox adapter's lease table lived on the factory closure, so two stores over one team directory granted the same lease twice — a file whose own header calls that state "IN-PROCESS".
873
+
874
+ All three now key a module-level table by the canonical path, with the journal's disposal refcounted so one instance's shutdown does not close fds another is still using.
875
+
876
+ Tracked tests 7101 → 7128.
877
+
878
+ ## 1.415.0 (2026-07-26)
879
+
880
+ ⚠️ **`sessionLogDigest` changes its wire value and its scheme tag (`sema-log-v1` → `sema-log-v2`) one release after being introduced.** Nothing is consuming it yet, which is exactly why it is being fixed now: two of the corrections move the hashed bytes, and the module's own rule is that moving them requires a new scheme. Doing that today costs nothing; doing it after two repositories have wired it costs a real cross-version skew.
881
+
882
+ **The digest contract was not sound (RB-154).** An adversarial review of the version shipped yesterday found four defects, all confirmed by measurement:
883
+
884
+ - `sessionLogDigestsComparable(null, x)` **threw**. JSON has no `undefined`, so "the peer sent no digest" arrives as `null` — and this function sits on the classification path, where a throw is a 500. That is a direct violation of the contract it exists to serve, which says a digest problem must degrade to "do the full sync", never to an error.
885
+ - The scheme was derived from a prefix, so any string starting the right way — including a value cut short by a column width — passed as a valid digest. Two peers holding the same malformed string would have compared **equal** and skipped the sync without a byte of content being compared.
886
+ - **The one that decided whether the mechanism worked at all:** the engine's canonical serializer distinguishes `{a: undefined}` from `{}` by design, and core produces own-keys-set-to-undefined routinely (clearing a label, a custom entry with no data). So the same log digested differently in memory than after the JSON round-trip the wire performs — meaning the short-circuit would essentially never have fired on a real session, and the only symptom would have been "the full sync always runs".
887
+ - That serializer's number token has no terminator, so a numeric value can run into the following key: two genuinely different payloads produced one canonical form.
888
+
889
+ v2 hashes a **stable, key-sorted JSON serialization of the JSON-representable form** instead. JSON's grammar is unambiguous, lone surrogates are escaped rather than folded to U+FFFD at the hashing step, depth is unlimited, and a Date or Map has already become its wire form before hashing — so the adhesion, the host-object collapse and the depth sentinel all go away together. It also means this module no longer shares a serializer with the approval-binding path at all, which resolves that tension at the root instead of arguing about blast radius. The approval serializer itself is untouched: it mints values persisted on live checkpoints, and changing it would invalidate approvals in flight.
890
+
891
+ Also hardened: full-shape validation instead of a prefix match; `null`/`undefined` digests as the empty log (that is how "no entries" arrives) while any other non-array throws where the mistake is; and unserializable values degrade **per key** rather than collapsing the whole entry, so two entries differing in an ordinary field stay distinguishable. The golden fixture is now a scheme→value table with a pin that it contains the current scheme — bumping without freezing was the hole, and the previous fixture exercised four of the serializer's branches while four mutations left it byte-identical.
892
+
893
+ **The size-bound was only half fixed (RB-152).** A downstream repo measured that `untrustedEgressForHuman`'s size disclosure was missing at **every** overflow, not just just-over-the-cap as the previous release's notes claimed — output length was constant, so a payload 1 char over the budget and one 50 000 over looked identical. Root cause: `boundedString` overshot its stated `max` by 13-15 chars on every truncation, and the fence enforcing the same `max` then chopped exactly that overshoot, which was the `…[+N chars]` notice. The notice now fits inside the budget; `truncateMcpErrorText` (40 over) and `truncateForSummary` (38 over) had the same shape and are fixed with it.
894
+
895
+ The guard could not have caught this: the invariant it enforced, `|out| ≤ max(|in|, limit)`, permits exceeding the limit whenever the input is larger. It now also asserts that **when a clipper actually truncates, `|out| ≤ limit`**. Two in-tree tests that pinned magic numbers were rewritten to assert the properties they were named for — the bound holding, and the notice's count matching what was actually omitted.
896
+
897
+ **Shell gate: three arms of one classifier, one defect class (RB-153).** The termination classifier's device arm carried its own three-entry literal list while `safety.ts` held the repo's authoritative twelve for the identical hazard, so nine devices the filesystem read boundary refuses were auto-allowed here — and the list was matched verbatim, so `//dev/zero`, `/dev/./zero` and `/dev/../dev/zero` walked past it. Its `tail` follow test could not cross a digit, so the obsolete `tail -1f` and `tail +0f` read as "no follow flag". Its blocking-stdin arm ran only on the first segment, justified by "mid-segment readers are pipe-fed" — true of `|`, false of `;`/`&&`/`||`, so `ls; cat` was auto-allowed while a bare `cat` was not. One device list, lexical path normalization, whole-token option reading, and the segmenter now records which connector preceded each segment. The `head -c <small>` carve-out narrowed to devices that *produce* data: a bounded read of `/dev/stdin` blocks exactly like an unbounded one. Re-ran the 3000-command black-box harness: sound, zero fail-open, approval rate unchanged.
898
+
899
+ **A gate that denied the safe spelling and allowed the dangerous one (RB-155, HIGH).** The unverifiable-delete rule learned in the previous release that `${!VAR}` is indirect expansion — and learned it at one of its two entry points. The target scan's variable pattern starts at `[A-Za-z_]`, which `!` is not, so `${!TARGET}` read as "no variable reference at all". The result was inverted: `rm -rf $TARGET` denied, `rm -rf ${!TARGET}` — strictly less knowable — allowed. This gate is always-on with no ask tier, so a pass is final. Fifth time this family has landed on one of its entry points.
900
+
901
+ It was found by the guard written to close a bookkeeping hole (RB-156): a sweep for which cleared backlog entries actually have a tracked test found that four always-on security gates had none, in violation of the rule this project adopted two releases ago. The replacement guard groups by *entry point* rather than by command sample, and caught RB-155 on its first run.
902
+
903
+ Tracked tests 7022 → 7090.
904
+
905
+ ## 1.414.0 (2026-07-25)
906
+
907
+ A self-review release. Three independent adversarial reviews of 1.413.0 — design intent, test quality, and outward contract — plus my own audit of the reds I had classified as "already fixed". Between them they found that one of that release's fixes was inert, one introduced an availability regression, one repeated a defect family it was supposed to be closing, and that the release notes described about a third of what actually shipped.
908
+
909
+ **First, the correction to 1.413.0's notes, because other repositories are acting on them.** That release's CHANGELOG and its cross-repo announcement were written from the release *narrative* — the surfaces worked on that day (RB-139, RB-142/143/144). The commit actually shipped **thirteen** cleared backlog entries; the rest had been sitting completed-but-unreleased. Everything in the list below was in 1.413.0 and went unannounced:
910
+
911
+ - **`context_usage` is a new first-class `TaskEvent`** (`{usedTokens, windowTokens, compactAtTokens}`), emitted at *every* compaction boundary. A downstream repo had formally requested this value and proposed carrying it on `turn_end.usage`; that location is wrong (the boundary check runs *after* `turn_end`, over the flushed message set, so it would be a different, earlier measurement wearing the same name) and core delivered it on its own event instead — without telling anyone. `usedTokens > compactAtTokens` is exactly the predicate the engine feeds its own `shouldCompact`. `windowTokens` is the *autocompact* window, not the physical request window.
912
+ - **`COARSE_SHELL_TOOLS` gained `Monitor`** (RB-130). Monitor rides the same execution seam as Bash and was a side door around two always-on gates — an end-to-end probe ran the same `rm -rf $(cat targets)` under both, with Bash denied and Monitor executing ungated. Deployments calling `createCoarseCommandNamePolicy` with no explicit `tools` get the wider default on upgrade.
913
+ - **The always-on unverifiable-delete rule denies more forms** (RB-131/132/133): wrapper prefixes (`sudo`/`env`/`exec`/`doas`/`setsid`/`nice`/`stdbuf`/`chroot`), `\rm`, indirect expansion `${!VAR}`, and values that escape their safe root through `..` — including via an intermediate variable. This gate has no ask tier, so these are hard denials. Existing automation using those forms will start being refused.
914
+ - **The session import gate tightened** (RB-129/136): a compaction's `firstKeptEntryId` must now be an *ancestor* of the branch, not merely present in stream order; `thinking_level_change` / `model_change` / `label` / `session_info` are validated on import; identifiers are capped at 256 chars and labels at 4096. Bundles that imported before may now be rejected.
915
+ - Behavioural, no migration: over-budget batches now show the model shorter tool results, starved turns deliver fewer attachment frames, and `untrustedEgressForHuman`'s output bytes moved in both directions (no `…[+N chars]` marker for inputs just over the cap; the fenced body is now hard-bounded).
916
+
917
+ The process failure behind that omission is recorded as RB-148: **a pickup slip's input is the set of entries the release cleared, not the story the release wants to tell.** A related trap is worth naming — the export-surface snapshot compares only name→kind, so it is blind to added union branches, added interface members, and added optional parameters. Its green cannot be cited as evidence that a contract did not change.
918
+
919
+ **Then the fixes.**
920
+
921
+ - **fix (RB-145, HIGH — 1.413.0's compensation for this terminal was inert).** The returned-result exit of a resume leg got its reopen compensation from inside `setResult`, a synchronous `(r: TaskResult) => void` callback, as fire-and-forget. The `errorCode` therefore landed on the result object after the caller already held it, and after the `task.end` trace had recorded its absence — wired in appearance, doing nothing in fact. The compensation now runs where the result is minted, awaited, before the trace. Its failure arm gets the three-outcome honest verdict rather than silence. Two method notes: I had classified this probe as "inverted red, i.e. fix evidence" without reading what it asserted — it was asserting the *post*-fix state; and my first two attempts to mutation-test the fix were unfaithful (swapping `await` for `void …then()` while leaving the assignment in the synchronous block), which produced the false conclusion that the guard could not catch it. A mutation has to reproduce the defect's *shape*, not its keyword.
922
+ - **fix (RB-146, MED-HIGH — an availability regression 1.413.0 introduced).** That release made an all-shadowed center artifact fail closed when the session was already pinned, reasoning that quietly unpinning rewrites what an existing session says about itself. The reasoning holds, but unpinning and killing the session were not the only options, and the throw created a face that did not exist before: one routine deployment change (adding a provider declaration whose id matches a published center section) would leave new sessions healthy while every existing pinned session died on its next turn — the publish-side outage the shadow mechanism exists to prevent, merely relocated onto older sessions. Nothing hybrid is being recomposed here either, so the fail-loud precedent for a missing artifact does not apply. A pinned or inherited session now keeps its pin and runs, with the mismatch reported on the prompt-constitution channel.
923
+ - **fix (RB-147, LOW).** `delimitUntrusted`'s new `maxBody` cut by UTF-16 code unit, so a budget boundary inside a non-BMP character left a lone surrogate — the same family 1.412.0 fixed in the task-output clipper, reintroduced one release later in a function whose sibling in the same commit was deliberately cutting by code point.
924
+
925
+ **And the guards themselves got audited.** The third review asked one question — are these tests real, or are they theatre? Eleven mutations, eleven caught, no probe assertion edited, both in-tree test changes verified as following the new contract rather than relaxing an old guard. But three structural gaps, all now closed (RB-149):
926
+
927
+ - **One mutation survived.** Reverting the lane key at two of its four entry points — the per-task quota and the disclosure match — escapes the source scan if the code is merely *written differently* (spaces removed around `===`, the map hoisted to a local), and **no behavioural test in the repo notices**. 1.413.0's announcement made a point of the enumerative scan catching what behaviour pins could not; that was true and it understated the other half — when the scan is the *only* guard, it constrains spelling, not behaviour. Two behavioural pins added (an external flood must not spend a child's quota; an external survivor must not absorb or suppress the internal task's disclosure line), the scan now normalises whitespace, and both now fail on that mutation.
928
+ - **One registry row could not reach the boundary it was guarding.** The `clipTaskOutput` entry named the wrong environment variable and the wrong default (30 000 against a real 32 000), so nineteen of its twenty probes never crossed the limit at all — a deliberately broken clipper fed through that row reported zero violations. It also never passed a `fullOutputPath`, leaving the header/pointer arm — where two of the historical defects this file cites actually lived — with no coverage. Fixed, plus a second row for that arm, plus a new cross-check that proves a declared limit *is* the function's real limit. That last one matters beyond this row: `slack` was capped and required a reason, but `limits` and `run` were author-supplied with nothing checking them, so the constraint was guarding the least abusable lever.
929
+ - **Two enumerative scans passed silently on zero matches.** Both now assert a floor first. One also had a title claiming "exactly one point" while never checking the count.
930
+
931
+ **New (additive): a shared content digest for session logs.** A downstream repo reproduced, over real HTTP, that the sync classifier compares only the SET of entry ids — so "same ids, different payload" returns `identical` with a 200 while the destination keeps its own text, and the `fast_forward` arm, which documents itself as "clean append, safe to apply", destroys corrections on the destination without the 409 that promise rests on. Entry ids are uuidv7, not content addresses, so that state is ordinary rather than exotic. Both peers asked core to own the canonicalisation, because two hand-written serialisers would manufacture false conflicts.
932
+
933
+ `sessionLogDigest(entries)`, `sessionEntryDigest(entry)`, `sessionLogDigestsComparable(a, b)` and `SESSION_LOG_DIGEST_SCHEME` are now exported. Two things about them are load-bearing:
934
+
935
+ - **Why this may be recomputed on both sides when `boundInputHash` may not.** There is a standing ruling against letting two runtimes each compute a canonical form and compare. Read at its source rather than through its paraphrase in the serializer's header, it rests on two legs: that no vetted canonical serializer existed (the same ruling's remedy was to ship one — it exists now), and that a false mismatch on the approval path *fail-closes a legitimate human decision*. The second leg is what decides whether it transfers. Here the asymmetry runs the other way: equal digests short-circuit, unequal digests mean "do the full sync". A divergence therefore costs extra work, never a wrong answer and never a refusal — and consumers must preserve that: **a digest mismatch may never be turned into an error, a 409, or a refused sync.**
936
+ - **The one promise the serializer did not make.** Its contract is explicit that it is only *internally* deterministic — same value, same bytes, within one runtime. Nothing compared its output across a wire until now. So this module takes that promise on itself: the digest of a fixed entry set is frozen in a golden test, and the documented response to that test going red is to bump the scheme tag, never to update the frozen value, because peers on different core versions compare these strings to each other. A scheme mismatch is reported as "not comparable", which degrades to the full sync path exactly like a value mismatch.
937
+
938
+ Tracked tests 6988 → 7012.
939
+
940
+ ## 1.413.0 (2026-07-25)
941
+
942
+ Three surfaces from the defect backlog, each closed the same way: enumerate every entry point of the rule first, fix them together, then write a guard that SCANS for members of the class rather than trusting that the list was complete. That last step is not ceremony — on two of the three surfaces the scan immediately named a member the enumeration had missed.
943
+
944
+ **Prompt assembly — a durable pin that claimed content the model never received (RB-139, HIGH).** `centerMounted` is documented as "true ⇔ the supplied centerDeclarations were actually COMPILED into this prompt … a pin that claims an artifact the model never received is a durable lie". On the `stableBlocks` path it was computed from the UNFILTERED input, three lines after the code shadows out every center section whose id collides with a provider declaration. All sections shadowed ⇒ zero center bytes compiled ⇒ the flag still said `true`, prepare-task's fail-closed gate passed, and `centerArtifactDigest` was persisted on the prompt-epoch pin and threaded to children — where a resume that cannot resolve that artifact fails the whole session.
945
+
946
+ The flag is now derived from the realized mount set at a single point, and every exit of `assemblePrompt` answers it explicitly. **The behavioral judgment call matters more than the derivation:** an all-shadowed artifact does NOT throw. That shadow exists (codex S1-F4) precisely so one colliding publish cannot take down every affected session, and making the gate throw would have handed that publish-side outage straight back. What is missing in this case is the pin, not the session — the composed prompt is real, complete, and byte-identical to a no-center run. So a fresh session drops the adoption with a loud warning and proceeds unpinned; a session that ALREADY carries a durable pin (or inherited one from a spawning parent) still fails closed, because quietly unpinning it would rewrite what that session says about itself. The gate's error copy now names the actual cause instead of diagnosing every caller as a whole-prompt-owning provider.
947
+
948
+ **Truncation — the stated bound was not the enforced bound, in five places (RB-142).** This repo has now restored the invariant `|out| ≤ max(|in|, limit)` five separate times, always one clipper at a time. So this release enumerates the class by *signature* — every function in `src/` that is handed a bound — and pins the invariant over a matrix that concentrates on the interval just past the limit, which is where every instance of this defect has lived (the elision notice costs more than it saves). A name-shaped scan was tried first and rejected: it both missed `truncateForSummary` (the fifth instance, on the summarization prompt path) and matched dozens of booleans like `wasClamped`.
949
+
950
+ - `boundedString` (the shared size-bound of `boundedRedactedSummary` / `untrustedEgressForHuman`) and `truncateMcpErrorText` both returned MORE than they were given for inputs slightly over the cap; both now decline to elide when the elision cannot pay for itself.
951
+ - `inlineUntrusted` applied its cap BEFORE two expanding defusing transforms, so `maxLen` was never a bound on the returned string — while `task-notification` consumes it as a stated budget and attribute-escapes on top. Now re-capped on the way out.
952
+ - `untrustedEgressForHuman` had the same shape one level up, so the fence itself now owns the bound it creates: `delimitUntrusted` takes an optional `maxBody` enforced AFTER neutralization.
953
+ - `markTruncated` interpolated the module's default cap into "showing last N" — but both it and `RollingTailBuffer`'s `maxBytes` are public exports so a downstream adapter can retain a different amount, and the marker was misreporting for all of them. It now states the retained text's real byte length.
954
+
955
+ **Budget and priority — mechanisms that did not do what they declared (RB-142 cont.).**
956
+
957
+ - The aggregate tool-result budget documents "repeating until the batch fits", and could not reach its budget in the exact shape its own docs name as motivation: every preview has a fixed ~2.1K floor and nothing below the candidate gate is touched at all, so 100 parallel 2 200-char results stayed 2.2× over budget with nothing capped and no signal. A second pass now runs only when the batch is still over, using a compact preview (`buildPreview` takes an optional size; omitted is byte-identical to before, so pass 1 and its prompt-cache determinism are untouched). When even that cannot converge — N results cannot be smaller than N × the minimum preview — the new `onBudgetUnreachable` callback says so instead of shipping an oversized batch silently. `onCapped` is deduped by result, so two passes cannot inflate a caller's degradation count.
958
+ - `capAttachments` grants budget in evidence-strength order, and its starve arm dropped an over-budget frame WITHOUT closing the budget — so a later, by-construction weaker frame rode the bytes the stronger one was just denied. The leftover is now forfeited: deferring a weak frame to a quieter boundary is the designed, non-lossy outcome, whereas showing the model the weaker signal while withholding the stronger one is a claim about the turn that is not true.
959
+
960
+ **Notification lane isolation — one rule, four entry points, one of them implemented (RB-142 cont.).** `taskNotificationDedupKey` gives the external lane its own key domain because `task_id` is caller-supplied free-form there. The pending store's per-task quota, its eviction victim predicate and its drop ledger were all the bare `task_id` — so an external injector picking a real child's id spent the child's quota, evicted the child's frames, and merged into one ledger row that could name only ONE `taskType`, which is how a disclosure minted for a real child's losses went out wearing the external lane. Extracted as `taskNotificationLaneKey` and applied at all four; the ledger keys by lane and carries the victim's real id back out for rendering. Worth recording: in mutation testing only the enumerative source scan caught a partial revert of this fix — the behavioral pins did not, because lane-scoping the eviction predicate alone still keeps the flood inside its own lane.
961
+
962
+ Also: RB-143 (a whole-session tombstone evicted by its own cap) had its fix in the tree with only an untracked probe guarding it — a tracked pin now exists, per the rule this project adopted last release. RB-144 records a self-inflicted one: the first `inlineUntrusted` fix bounded the output at exactly `maxLen`, which changed the byte count on the non-expanding path and broke a byte-exact tracked pin for no safety gain; the bound is back to the documented `maxLen` + one ellipsis, and the invariant table's new `slack` field is capped at 1 with a mandatory reason so it cannot become a way to loosen the test.
963
+
964
+ Tracked tests 6917 → 6988.
965
+
966
+ ## 1.412.0 (2026-07-25)
967
+
968
+ A fourth defect-hunt pass at the previous release's fixes, and a correction to how this project has been keeping its own books.
969
+
970
+ **The books first, because it invalidates a claim the last three releases implied.** Defect-hunt probes were written as `*.local.test.ts` to keep them out of the working tree — and that suffix is gitignored. So three releases fixed 84 defects while the tracked test count went 6440 → 6440: **CI had zero regression protection for any of them**, and the entire body of evidence existed on one machine. All 37 probe files are now tracked as `test/defectscan-*.test.ts`. 27 cases whose assertions pin PRE-fix behavior are marked `it.skip` with the reason — not one assertion was edited, because those inversions are the fix evidence. The backlog now states the rule: an RB entry may only be marked CLEARED when its guard is already a tracked test. Test count 6440 → **6917**.
971
+
972
+ Then the round itself. Five of the fifteen findings were introduced BY 1.411.0's fixes, and the first is the worst thing in this arc:
973
+
974
+ - **fix (RB-116, HIGH — a fail-open I created): `rm -rf $HOME/..` was allowed.** RB-106 added `HOME`/`PWD` to the unverifiable-delete rule's known-safe roots to stop a false positive, but the carve-out only ever excluded glob metacharacters — it had no opinion about `..`. `$HOME/..` is `/Users`; `$HOME/../..` is `/`. This gate has no ask tier and no human backstop, so a pass is final. It was harmless while `$TMPDIR` was the only safe root; widening the roots turned it into a hole. The carve-out now requires the literal remainder to stay below the root. Same finding's second half: an unterminated quote made `assignmentTokenSpan` return the rest of the statement, advancing the cursor past everything, so `X=' rm -rf $Y` stopped being scanned at all.
975
+ - **fix (RB-117, HIGH): the alias mirror's winner was the opposite of dispatch's.** RB-105's comment claimed it "mirrors dispatch exactly"; dispatch is `find(name) ?? find(aliases.includes(name))` — the FIRST tool whose alias list matches — while a `.set()` per tool makes the LAST writer win. With two tools sharing an alias, dispatch ran the `irreversibility: "always"` one while the gate read the other's `"never"` and skipped the tighten; the same inversion let a shared alias hand an `egress` tool someone else's `effect: "read"`, defeating the egress-requires-write validation from the other side.
976
+ - **fix (RB-121, HIGH): the two carriers RB-103 added to a compaction's `details` had no import gate.** The two carriers already on that object each have one, and the comment explaining why says it plainly — a compaction entry travels through `exportEntries`/`importEntries`, so its `details` is foreign input on the session-sync path. Without the gate, an imported entry could name any string as the branch's thinking level and any provider/model as its active model, and `buildSessionContext` seeded both verbatim, deciding the restored session's reasoning budget and model routing. A strict normalizer now backs both the seed and a fail-closed import check.
977
+ - **fix (RB-122): the clipper started splitting UTF-16 surrogate pairs.** Budgeting the omission marker made `half` odd at the default limit, where the previous even `floor(limit/2)` had accidentally avoided it — emoji and astral text came back with a lone high surrogate at the end of the head.
978
+ - **fix (RB-123): the abort compensation swallowed a reopen failure and then asserted the reopen happened** — manufacturing exactly the class of untrue verdict RB-109 exists to prevent, and collapsing "still pending, retry it" and "consumed, the work is gone" onto one errorCode. `reopen` already fails loudly for this reason; that signal is now honoured, and the two outcomes get different codes.
979
+
980
+ The rest were incomplete class-fixes, each in the same shape as the ones before them: **RB-118** (the mailbox reap's under-lock re-check read the snapshot object rather than the live one, though this file's own `close()` discipline says "identity is checked, not just the path" — a drop-and-rebuild interleave then overwrote a committed message and resurrected a seq high-water `drop` had retired); **RB-119** (RB-101 took RB-62's realpath and not RB-67's case folding, whose stated reason is identical, so case-variant session ids on a case-insensitive filesystem were one file with two authorities — RB-58's silent fork, again); **RB-120** (a caller key whose value is `undefined` erased RB-103's restatement, which is the ordinary `{ thinkingLevel: cfg.level }` config shape); **RB-124** (RB-99's guard threw a bare `Error`, less diagnosable than the `EBADF` it replaced, and RB-113's note claimed a slow backend delays only its own row while `Promise.allSettled` still waits for all — the sole caller is the parent run's terminal cleanup, so one wedged store hung the whole teardown; the wait now has the same ceiling core applies to other injected probes).
981
+
982
+ ## 1.411.0 (2026-07-25)
983
+
984
+ Three independent review agents were pointed at 1.410.0's own fixes, forbidden to touch source, and asked to falsify them. They produced 23 reproducible defects — including two of my class-fixes that had landed on one implementation out of two, and two fixes that made things WORSE than the bug they closed. All are fixed here, plus RB-95/96 (the two items 1.410.0 recorded as open) and a gap in the release gate itself.
985
+
986
+ The two regressions are the release's headline, because both were introduced by the previous release's fixes:
987
+
988
+ - **fix (RB-99, HIGH): a closed `AppendLog` kept writing — into whoever opened next.** 1.410.0's session-storage eviction closed the append descriptor, and POSIX immediately hands that number to the next opener. A deleted session's stale handle therefore appended entries into an unrelated LIVE session's file, producing a tree whose parent links point into another session — a file that then failed both the import gate and `buildContext`, permanently. Before 1.410.0 the same handle merely wrote into the void (its own unlinked inode); the fix upgraded a silent loss into cross-session corruption. `close()` now marks the descriptor dead before the syscall and `append()` refuses afterwards, which fixes the class for all five stores that hold one.
989
+ - **fix (RB-105, HIGH): an alias-invoked call lost its safety tighten-ups again, the other way round.** RB-79's resolver disagreed with dispatch in both halves: `findToolByName` matches the exact name first, then aliases, and never consults the old→new rename table, while the resolver consulted aliases first and then the rename table. So a deployment tool whose own name happens to BE a rename-table key — `web_fetch`, `submit_output`, `bash`, `grep`, and about twenty more — had its `egress`/`irreversibility` lookup redirected to a name nothing registered, losing both tighten-ups while the call still executed; and one tool's alias could shadow another tool's real name. The resolver is gone. The safety sets are now alias-complete at build time (each fact registered under the tool's name and its aliases, never overwriting a real tool's own name), so every lookup site is correct without knowing aliases exist — including the two on the lines adjacent to RB-79's own edit, which it had missed.
990
+
991
+ The two incomplete class-fixes, both "landed on one of two implementations, in a file that promises parity with the other":
992
+
993
+ - **fix (RB-100, HIGH): the mailbox `reap` copied one step of a four-step recipe.** `atomicWriteFile` replaces the file by rename, so the box's descriptor kept pointing at the old inode and every append after a reap landed on it — durable-first reported the seq as safe and the message was gone at the next replay, a more direct loss than the stale-ack the fix was written for. It also evaluated the age predicate before taking the lock, so a message arriving during the wait was cleared without ever being examined. The sweep now delegates to `compact()` (the one place that knows the whole recipe) and re-takes the decision under the lock.
994
+ - **fix (RB-111): the same double-count RB-87 fixed in memory was still live in the file backend**, whose header promises its reap predicate is byte-for-byte the in-memory one. Same shape as RB-74 in the previous release; the interface's own doc line ("Returns rows deleted+flipped") was what the file backend had followed, and is corrected too.
995
+
996
+ Also fixed: **RB-101** (the realpath keying RB-62 established never reached `FileSessionRepo`, so RB-58's silent-fork CAS was authoritative only for callers who spelled the path the same way — symlinked data dirs, `/var` vs `/private/var`, bind-mounts); **RB-102** (`fork` is a third file-replacing operation and RB-73 accounted for two); **RB-103** (with the cut-point back-off gone, `thinking_level_change` / `model_change` fall below a bounded-tail floor and nothing restated them — the compaction entry now carries them, computed by the session from its own branch); **RB-104** (re-rooting a headless fork log could not tell a legitimate bounded window from a corrupt log, so it laundered corruption past the gate built to reject it — now gated on the storage's declared floor); **RB-106** (three more grant paths in the unverifiable-delete rule — `for VAR in $(…)`, an opaque REassignment that never revoked, and a quoted right-hand side inspected only up to its first space — plus a new false positive on `$HOME`/`$PWD`-derived paths, on a gate with no ask tier); **RB-107** (the MCP clamp computed a negative slice, which trims from the tail instead of truncating and returns something LONGER than the limit); **RB-108** (`head`'s device exemption treated `-n` as a bound, but the hazard is a read that never terminates and `/dev/zero` has no newlines — and clustered GNU short options bypassed the parser entirely); **RB-109** (the aborted-signal gate only narrowed the window: an abort landing during the cross-process `CheckpointStore` call still burned the approval, and the gate ran ahead of the CAS so a terminally-consumed token got an error message that was a plain falsehood); **RB-112** (the output clipper's other leg still returned more than its limit — reproducible at the default budget with no configuration at all); **RB-113** (`disposeAll` survived a rejecting `release` but not a hanging one, and swallowed a rejection whose value was nullish); **RB-114** (a whole-session notification loss was folded into a per-task ledger under a sentinel key, which collided with free-form external task ids, overwrote instead of accumulating, and told the model to fetch something that does not exist).
997
+
998
+ Two findings were adjudicated rather than coded around, with the reasoning recorded: **RB-110** (RB-81's comment claimed a `ToolPolicy` resolves `ToolSpec.aliases`, which is structurally impossible — a policy receives a request and no roster; the note now says where alias resolution actually happens) and **RB-115** (the roster's upsert domain does not span `entryAccessible`'s session arm, but the uncovered row is a still-registered binding rather than a stale one, and collapsing across owners would be wrong for two sessions each holding their own agent — the over-claiming comment is narrowed and the trigger recorded).
999
+
1000
+ Carried over from 1.410.0's open list: **RB-95** (the compaction cut-point's back-off loop walked the floor onto a first-class state entry, breaking `preserved_segment` resolution and misreading a clean turn boundary as a split turn — an extra model call and a weaker prompt; the loop is removed, since nothing consumed the lowered index) and **RB-96** (`fork` copied a bounded-tail session's window verbatim, producing a headless tree).
1001
+
1002
+ - **fix (RB-98): the live orchestration e2e could never run under the discipline that requires it.** It built its brains in three places with no API key, so pointing it at the authenticated gateway the release gate mandates produced a 401 on the first call and four agents that "failed" in ~100 ms. Auth resolution is now single-sourced with the other live suite; both the authenticated and the unauthenticated lane pass 4/4.
1003
+
1004
+ ## 1.410.0 (2026-07-25)
1005
+
1006
+ The divergent-attack round: four more probe agents, again forbidden to touch source, produced 44 reproducible defects across the approval chain, the session tree, the tool surface and the agent-team stores. 22 are fixed here, 2 are recorded as open with their reds resident. One of them was mine — 1.409.0's own fix introduced it, and it is the most serious thing in this release.
1007
+
1008
+ **Note on 1.409.0**: its tag was pushed but CI went red on `flaky-guard` and it never reached npm. The cause was not the release content — the guard's stale-allowlist check flagged a declaration for an untracked `*.local.test.ts` probe, a file that exists on a developer's machine and, by design, not in CI. The check now skips entries whose file is absent from the scan. Everything in 1.409.0 ships here.
1009
+
1010
+ - **fix (RB-73, HIGH — a regression I shipped in 1.409.0): the shared session-storage table had no invalidation and a refcount that only ever incremented.** `SessionRepo` has no release point, so "refcounted" was decorative, and `delete()` — the `TtlSessionStore({evict: "delete"})` production path — removed the FILE while leaving the cached storage answering for it. The next `acquire` of that id resurrected the deleted history, and appends onto it went through a descriptor pointing at the unlinked inode: a write that fsync'd and reported success was invisible to every reader. `importEntries` had the same shape, making a session-sync import silently a no-op for any already-opened session. The table is now a WEAK cache — authority for exactly as long as somebody holds the storage — with a `FinalizationRegistry` closing the append fd on collection and explicit eviction from the two file-replacing operations.
1011
+ - **fix (RB-79, HIGH): an alias-invoked call lost both safety tighten-ups.** Every safety set is keyed on the tool's canonical `name`, but the gate looked them up with the raw model-emitted name — and `ToolSpec.aliases` exists precisely so a model may emit something else. A tool declared `egress: true` or `irreversibility: "always"` therefore got no approval gate and no headless auto-deny the moment the model used an alias.
1012
+ - **fix (RB-80, HIGH): a PreToolUse `ask` + `updatedInput` had the policy adjudicate the model's stale arguments while the hook's rewritten ones executed.** The rewrite was threaded on the non-ask branch only, though both shapes feed the executed and checkpointed args, and `ask` + `updatedInput` is a deliberately supported combination. Two of this module's stated invariants were false on that path.
1013
+ - **fix (RB-77, HIGH): a resume handed an already-aborted signal burned the approval and lost the work.** The CAS consumed the checkpoint, the run aborted before executing the approved tool, and nothing reopened it — the compensation path covers `env_failed` and `tool_unavailable` only. The caller got `failed` with no errorCode and every later resume said `already_resolved`. It is now refused pre-CAS (`checkpoint.resume_aborted`), so the checkpoint stays pending and is resumable with a live signal. An existing test that pinned the abort propagating into `resumeVM` is rewritten with the reasoning: what it protected is "a caller's cancellation is honored", which the guard does earlier and without losing anything.
1014
+ - **fix (RB-74, HIGH): RB-68's class-fix had landed on one of the three sweep implementations.** The file backend's header promises its CAS predicate is byte-for-byte the in-memory one; a probe showed it was not, and an uncomparable deadline stayed immortal there. Both it and the PostgreSQL reference adapter now treat a bound nobody can evaluate as reapable.
1015
+ - **fix (RB-75): an empty `durableApproval.scope` became a shared bucket every tenant landed in.** The rule "an empty string must not become a shared scope bucket" is stated twice in that file and was enforced on the principal half (`||`) but not the scope half (`??`). In that bucket, `listByScope("")` reads both tenants' pending approvals including each one's argument preview, and `reap`/`resolve` act across both.
1016
+ - **fix (RB-76): the anti-re-vote guard compared the verdict but not its payload.** `editedPlan` is to a plan review what `updatedInput` is to a tool approval — the substance of the decision — so an `env_failed` reopen could replay the same `edit` verdict carrying a completely different plan, which went straight into the continuation prompt.
1017
+ - **fix (RB-78): a repeated tool-call id turned one human approval into two irreversible executions**, plus two toolResults sharing an id, which a strict provider rejects for every later request on that session. The resume leg resolved the pending call once per occurrence; the batch is now a set in emission order.
1018
+ - **fix (RB-82, RB-84, RB-81, RB-83), the tool surface**: the unverifiable-delete rule only inspected the left-hand side of an assignment, so `T=$(cat targets); rm -rf $T` and `rm -rf $(cat targets)` — identical blast radius — were decided by spelling; `head`'s device-source exemption claimed its output was "bounded by construction" when the bound is whatever the model passed, so `head -c 999999999999 /dev/zero` auto-allowed and streamed over 14 GB in 1.5 s before being killed; the coarse command-name gate did not canonicalize the tool name its siblings canonicalize; and `normalizeMcpName` enforced the charset half of `^[a-zA-Z0-9_-]{1,64}$` but neither bound, so an over-long assembled name would 400 the whole model request — the exact failure the MCP intake gate exists to contain.
1019
+ - **fix (RB-85): the output clipper made text LONGER and claimed it had truncated.** Below a header-sized limit the tail-slice argument goes positive and slices from the front (a 100 000-char input came back 100 050 long); at exactly the header size it is `-0`, which returns the whole string under a "Truncated" banner. `parseInt` being prefix-lenient is how a deployment reaches that region: an operator writing `1e9` to widen the budget got `1`.
1020
+ - **fix (RB-86, RB-90), the mailbox**: the CC backend had no empty-box branch, so a claim on an empty box both returned a meaningless lease AND installed one, fencing out the next real consumer until the TTL lapsed. And `reap` was grouped with `drop` in the "the box's lifetime ends, seq restarts" clause though only `drop` follows a row's death — restarting seq during an age sweep let a consumer holding a pre-sweep lease ack away a brand-new message no consumer had ever seen. Both bundled backends now keep the high-water mark across a reap; the contract text is split accordingly.
1021
+ - **fix (RB-89, RB-88, RB-93, RB-94, RB-91, RB-92, RB-87), the agent team**: the in-memory roster lacked the file backend's upsert domain, so a name reused across generations let a superseded dead agent resurface as the answer after the live one was released; `disposeAll` abandoned every remaining entry when one release threw, leaking exactly the orphan runs it exists to prevent; a child's spawn and resume cycles reported different `task_id`s, breaking the premise of the whole `task_id:status:seq` mechanism; a fork background child promised `resumable: true` on a lane with no retain entry and a revival path that refuses forks by type; a whole-session notification eviction disclosed nothing to its victim; the not-found envelopes omitted their identity fields; and `reap` counted one row twice.
1022
+ - **open, with reds resident (RB-95, RB-96)**: the compaction cut-point's back-off loop predates first-class state entries and walks the floor onto one of them, which both breaks `preserved_segment` resolution and misreads a clean turn boundary as a split turn (a weaker summary prompt plus an extra model call); and `fork` copies a bounded-tail session's whole tree without a floor, producing a log that fails this repo's own import invariants. Recorded rather than rushed.
1023
+
1024
+ ## 1.409.0 (2026-07-25)
1025
+
1026
+ A multi-dimensional red hunt (five independent probe agents + a first-party pass, none allowed to touch source) found 17 reproducible defects; this ships the fixes. Two are HIGH, and both are the same shape as 1.408.0's: a correctness argument that holds for the reference implementation but not for the one people actually deploy.
1027
+
1028
+ - **fix (RB-58, HIGH): `FileSessionRepo.appendEntry` had an `await` between the optimistic-lock check and the commit** — so two concurrent appends both passed `assertExpectedLeaf` and both wrote, forking the session tree silently. The in-memory reference implementation is atomic only because it happens to contain zero awaits; the file backend inherited the reasoning without the property. The check and the durable write are now one uninterrupted sequence (`persistSync`). The store's shared state also moved to a module-level table keyed on the resolved path, refcounted — same treatment as 1.408.0's class.
1029
+ - **fix (RB-63, HIGH): a durable `/decide` resume re-checked an operator's `updatedInput` edit against session/ancestor/skill rules only, never against the task's own base `toolPolicy`** — while the synchronous onAsk-edit path documents, and performs, a re-run of BOTH boundaries on edited args. An edit to a path the deployment's policy denies outright therefore executed. The base policy now re-adjudicates the edited args, narrowed to `deny`: a re-check that comes back `ask` does NOT block, because `ask` means "a human should decide this" and a human just did — the wider form was a real regression against an existing test, not a test that needed changing.
1030
+ - **fix (RB-64): the anti-re-vote guard was nested entirely inside the `policy_ask` arm**, so a `plan_review` checkpoint reopened after an `env_failed` retry accepted a decision contradicting the one the human actually made — and `winnerFromOutcome` recorded no winner for that gate, leaving nothing to replay against. Winners are now recorded for the review gates (with a synthetic `gate:plan_review` sentinel that can never collide with a real toolCallId) and the guard is gate-agnostic.
1031
+ - **fix (RB-68): `durableApproval.ttlMs` reached `Date.now() + ttlMs` unvalidated** — `Number(unset_env_var)` produced `deadline = NaN`, and `NaN <= cutoff` is always false, so a checkpoint that carries a TTL and looks bounded could never be reaped by any cutoff. Fixed in both layers: mints normalize a non-finite/non-positive TTL to the documented default (a deliberate `undefined` still means unbounded), and `reap` treats an uncomparable deadline as reapable, since checkpoint rows are also written by deployment-side stores and older versions.
1032
+ - **fix (RB-70): `resumeStream` never set `requireExistingSession`**, so the headline durable scenario — a restarted process resolving a checkpoint while only the CheckpointStore was wired durably — fabricated a brand-new empty session under the checkpoint's id and pointed a dangling leaf at it. Every other resume-shaped call site already set it. The resulting `resume.session_not_found` now reopens the checkpoint as `env_failed`: the action never ran and the human's decision is still valid, so the approval is retryable rather than burned.
1033
+ - **fix (RB-71): one throwing getter collapsed an entire object to a single `<unserializable>` sentinel**, so any two hostile payloads hashed identically — and `boundInputHashOf` exists precisely to prove the args being executed are the args a human saw. The sentinel is now scoped to the offending key; totality is unchanged, resolution costs one key instead of the whole payload.
1034
+ - **fix (RB-57): `findOrphanToolCalls` resolved tool calls against a global id set**, so a provider that reuses a toolCallId across turns (OpenAI-compatible gateways that number per request) hid a genuine orphan — and an unclosed `tool_use` makes every subsequent request on that session fail at a strict provider, not just one reconcile. Resolution is now positional.
1035
+ - **fix (RB-65): `tightenTaskSpec` left `excludeTools`/`deferTools` outside its tighten-only guard** — an override replaced the base list instead of unioning with it, and `[]` cleared every base exclusion outright.
1036
+ - **fix (RB-69): the CC task-list store's addressable id set and its enumerable id set were different** — a `007.json` counted toward the allocation high-water yet was unreachable through get/set/delete, and `list()` reported it as the id `"7"`. Checked against CC 2.1.219 itself (`edr`/`Uid`/`ste`): CC constrains a character class, not a numeric shape, and its list returns filenames verbatim, so `007` is a first-class id there. Both sides now use CC's charset; sema keeps the stricter half of the divergence (an off-charset id throws rather than being silently rewritten into a neighbouring file).
1037
+ - **fix (RB-72): the CC sidecar transcript was read with a lossy UTF-8 decode**, so a corrupted byte became U+FFFD, the line parsed as good JSON, and silently-altered content was counted as healthy — the one thing a reader promising "verbatim lines and an honest malformed count" must not do. Decoding is now strict and per line; a genuine U+FFFD stays a healthy line, which a replacement-character scan could not have distinguished.
1038
+ - **fix (RB-59 / RB-60 / RB-61 / RB-62 / RB-66), the file-backend sweep continued**: `FileWorkflowRunStore` had 1.408.0's per-instance-authority CAS defect; the background-agent composite key used a space separator where its core counterpart uses `\u0000`; the workflow journal wrote through a bare fd whose short write left a torn line for the next record to concatenate onto; the mailbox store called realpath before the directory existed, so `/var` and `/private/var` became two shared-state slots; and `AppendLog.append` did not loop on a short write.
1039
+ - **adjudicated, not fixed (RB-67)**: a probe claimed two case-variant mailbox handles sharing one box was a cross-recipient leak. It is not — the addressing layer's `normalizeAgentName` already lowercases, so the two handles ARE one agent. The real defect was that the in-memory key and the on-disk file disagreed about that (different behavior before and after a restart; on a case-sensitive filesystem, one agent's mail split across two boxes). The path is now case-folded so the storage layer states the alignment instead of depending on an upstream coincidence.
1040
+ - Test discipline: every original red probe was left byte-for-byte untouched, including the four whose assertions pin the pre-fix behavior and now read red in the opposite direction — that inversion IS the fix evidence. Where a probe's intermediate assertion pinned something a fix removed upstream, a separate adjudication pin records the reasoning rather than the probe being edited.
1041
+
1042
+ ## 1.408.0 (2026-07-25)
1043
+
1044
+ A concurrency defect CLASS in the file backends (state and locks on the object, backend shared wider) + the PG reference adapters move to `examples/`.
1045
+
1046
+ - **fix (RB-55, HIGH for the single-machine profile): `FileBackgroundAgentStore`'s rev-CAS was per-instance, so two same-rev CASes over one directory BOTH won** — a double-claim on the optimistic-concurrency floor the whole design/151/153 parked state machine (claim / rollback / consume-flip) stands on. Root cause is the class shape, not the algorithm: the authoritative row map AND the id lock lived on the instance while the backend (a directory) is shared wider. State and locks now live in a module-level table keyed on the resolved directory — any number of instances over one directory collapse into one authority — with refcounting so the append fd is released only when the last instance closes. Exposure today is narrow (SQL backends are used in production, single-instance assembly does not trigger it) but the desktop loopback form runs exactly this backend.
1047
+ - **fix (RB-55, same class): `FileTaskListStore` lost updates across a multi-op read-modify-write** — proven by a red pin (two concurrent single-field edits, one silently overwritten). Same directory-keyed lock, plus the `TaskListStore.mutate` transaction boundary the interface JSDoc has always required of "anything shared wider than this object" and this store never implemented.
1048
+ - **test: the whole `stores/file/` family is now probed for this class by real concurrency**, not by reading code — CAS-type backends must have exactly one winner, collection-type backends must keep every concurrent write. Five were already correct (checkpoint / memory / workflow-run / roster / the agent store's put face); the two above were not. Both fixes are stash-verified (removing them turns the pins red again).
1049
+ - **refactor (clay's ruling): the PostgreSQL reference adapters move to `src/examples/adapters/pg-adapter.ts`**, next to the TiDB one, with the layering stated at the top of the file: core owns the CONTRACTS, the deployment owns third-party backends. `@sema-agent/server` ships its own Pg/TiDB stores with connection management, retention and multi-tenant hardening that a reference adapter deliberately lacks — the two same-named classes coexist on purpose. Export surface unchanged (no BREAKING); only the path and the positioning notes changed.
1050
+
1051
+
1052
+ ## 1.407.0 (2026-07-25)
1053
+
1054
+ Collation discipline on identity/tenant key columns + RB-2 closed by execution evidence.
1055
+
1056
+ - **fix (RB-53, server [1674] class-fix): every identity/tenant key column pins its collation** — both adapters previously declared none, inheriting whatever the deployment's database defaults to. The sharpest exposure is not the tenant scope but `tool_results.ref` = `tr_<sessionId>_<toolCallId>`: a provider's toolCallId carries case (Anthropic `toolu_01ABC…`), so under MySQL 8's default `utf8mb4_0900_ai_ci` two DISTINCT tool calls collide on the primary key and one result silently overwrites the other. PostgreSQL `text` equality is byte-exact under a deterministic collation (safe by default), but a database created with a nondeterministic ICU collation folds `scope`/`token`/`ref` equality case-insensitively — a real cross-tenant read. Now pinned (`COLLATE "C"` / `COLLATE utf8mb4_bin`); `CREATE TABLE IF NOT EXISTS` is a no-op on existing tables, so this changes NEW deployments only — no migration, no behavior change for live ones. A guard pin scans the DDL source so a future key column that forgets the collation goes red (it immediately caught columns the manual sweep had missed).
1057
+ - **RB-2 CLEARED by probe, not by argument** — the ledger's three "known boundaries" were adjudicated from comments; a real probe over 8 consecutive double-oversize boundaries shows `agent / skills+agent / agent / skills+agent …`, a stable two-cycle with the byte cap always fully used. ① "the pair can stay permanently uncommitted" refers to the intact-commit predicate, NOT to what the model receives: an oversize frame is clipped-and-kept with a `…[truncated]` marker, so the model knows its listing is incomplete; the residual (a genuinely oversized roster's tail never shown) is deployment-owned sizing, as the original note said — now with evidence. ② the two-cycle rotation is alive and stable (intended fairness, closed). ③ the staleness face is measured harmless: a quiet boundary neither flips the latch nor swallows that boundary's frames (closed). Four pins stay resident. Ledger path corrected too (the file lives at `src/core/runner/turn-attachments.ts`).
1058
+
1059
+
1060
+ ## 1.406.0 (2026-07-25)
1061
+
1062
+ RB-49 partial: the two highest-leverage projection-antipattern remainders (B-1 guard, B-3 single-source).
1063
+
1064
+ - **fix (RB-49 B-3): `AccessibleTaskRow` is one exported type instead of five hand-copied literals** — the row-metadata projection's field list was spelled out verbatim in the registry signature, an internal read cast, the SendMessage `target` declaration, a `byName.handle` cast, and (found by the guard pin itself) a second registry cast. Adding a row field meant editing five spots with zero compiler enforcement — the same projection-antipattern family as server [1622] / core [1640]. Tightening the type also surfaced two real slacknesses the hand copies hid: `type`/`status` had been widened to bare `string`, and one `target` optionality was never proven — now formalized as an honest `not_found` reply instead of an implicit assumption.
1065
+ - **test (RB-49 B-1): a reflective guard over the bare `{...child}` spread into a subagent's host-face `details`** — deliberately NOT converted to a whitelist (the shell renders from that structured face; dropping fields would silently starve it). The guard pins the whole `TaskResult` field set, so a new field goes red and forces an explicit decision; verified it actually fires by injecting a probe field.
1066
+ - RB-50 (1.405.0) and these are the CC 2.1.220 study's actionable half; B-4…B-8 and the C-level list stay recorded.
1067
+
1068
+
1069
+ ## 1.405.0 (2026-07-25)
1070
+
1071
+ RB-50: the two prompt-shape axes resolve at ONE decision point (internal consolidation, zero behavior change).
1072
+
1073
+ - **refactor (RB-50, CC 2.1.220 study):** `promptProfile` (a TaskSpec field) and `fableMitigations` (a raw model-id prefix test) were resolved in two different places by two different mechanisms — "which shape does this task speak" had no single place to read. Both now come from `resolveModelPromptTraits(model, spec, internals)` (exported), with the resolution RULES unchanged: profile = spec > inherited internals > `"simple"`; mitigations = model family; the axes stay orthogonal. CC's counterpart is a model-registry `capabilities` array driving `lean_prompt`/`fable_5_mitigations` from one table — sema stays BYOM (no capability table for arbitrary model ids), so this is consolidation only, not a capability-table adoption.
1074
+ - Pins: equivalence of the unified entry with both former paths, a four-combination orthogonality pin, and a protective E2E (one Runner running fable → non-fable → mythos in sequence keeps the mitigations axis per-task correct — guards against a refactor that caches the first task's shape).
1075
+ - Study record: `anchors/2.1.220/CC-218-220-DIFF.md`; design/156 (memory-prompt convergence) opened as a follow-up; RB-51 records the capability-bundling shape for when scale warrants it.
1076
+
1077
+
1078
+ ## 1.404.0 (2026-07-24)
1079
+
1080
+ Projection-antipattern sweep ([1630] cross-repo class-fix, core half) + the resume() symmetry fix.
1081
+
1082
+ - **fix (sweep A-①): the deployment `WorkflowCompletionNotifier` now receives `diagnostics`** — the hand-written subset interface + per-field re-listing at the projection seam silently dropped the field added in 1.353 (per-agent-rows route / journal locator / resumeFromRunId guidance); the in-process lane always had it. Redacted like the other cross-replica fields. Red-first pin.
1083
+ - **fix (sweep A-②): killed background bash/monitor notifications flag salvaged output `partial: true`** — the one-meaning contract with the poll face's `partial_result` held on the agent lane but was never set at the four bash/monitor construction sites (watcher terminal + owner-teardown arms). Red-first pin.
1084
+ - **fix (sweep A-③): a RESUME cycle's settle notification carries the residual lanes** (`recentSteps`/`editedFiles`/`resumable`) — the spawn leg's recorder closure was out of reach of `makeSubagentResume`, so revived children's notifications permanently lacked them; the resume leg now runs its own step recorder on the forward boundary. Red-first pin.
1085
+ - **fix (sweep A-④): synthetic tool frames carry the display `label`** on the interrupt-reconcile, deferred-reissue, and resumed-batch legs (label≠name tools, e.g. MCP `server:tool`, lost their display key on those frames). Stash-verified red.
1086
+ - **fix (sweep B-2, RB-42② class-fix): `Checkpoint.durableApproval` is recorded at EVERY mint point** — the resource_limit and plan_review mints consumed the opt-in (ttl → deadline) without recording it, so parked-resume continuity fell back to legacy inference on those gate kinds. Both legs pinned red-first.
1087
+ - **fix (RB-48②): the convenience `resume()` forwards its optional 4th `internals` arg to `resumeStream`** — the thin wrapper silently dropped it, so a cross-process constraint re-supply through `resume()` always failed `resume.parent_constraint_missing` while looking wired. Red-first pin; RB-48① adjudicated closed (the test/ typecheck gate now rejects the mis-placed literal).
1088
+ - RB-49 records the sweep's B/C-level remainder (bare-spread details, triple-maintained hand-written literals, duplicated payload builders).
1089
+
1090
+
1091
+ ## 1.403.0 (2026-07-24)
1092
+
1093
+ BREAKING session-identity cleanup + the test/ directory joins the typecheck gate (424 real errors cleared).
1094
+
1095
+ - **BREAKING: one session type, no compromise names** (clay's ruling — the `SessionApi` interim from the de-vendoring DIP era is retired):
1096
+ - `Session` is now THE session contract (the interface formerly named `SessionApi`) — annotate and implement against it. It gains an optional `getPromptEpoch?()` member (the built-in class always implements it; external implementers stay compatible, consumers call via `?.()`).
1097
+ - The constructable storage-backed class formerly exported as `Session` is now **`StoredSession`**. Migration: type positions keep `Session` (now stricter/interface-typed); `new Session(...)` → `new StoredSession(...)`; every `SessionApi` reference → `Session`.
1098
+ - `maybeCompact`'s `session` option now takes the `Session` contract (was the concrete class). CC has no equivalent to anchor (its session is transcript-file + in-memory history, not a library contract) — sema-owned shape.
1099
+ - **test/ typecheck gate ([1621] cross-repo class-fix, server first-reported):** `tsconfig.json` `include` only covered `src/` — the whole `test/` tree (vitest transpiles without checking) was never type-checked by any gate. Folding it in surfaced **424 real errors across 117 files**, now all cleared with test semantics preserved. Real latent test bugs caught in the sweep: a `text_delta` assertion read `.text` (field is `delta` — would throw if the branch ever ran), a `Runner({checkpoints})` mis-named field silently never wired the checkpoint store, a `ToolPolicy` literal (`{default:"deny"}`) that isn't the `{check}` contract, a 1-of-2-required-args `makeBashTool` call, and a `resume()` call passing a 4th `internals` arg the wrapper never forwarded.
1100
+ - Recorded for follow-up (not shipped here): `toolExecution` exists only on the engine loop config — a `TaskSpec`-level literal is a silent no-op (whether a public force-sequential knob should exist is a design question); `Runner.resume()` vs `resumeStream()` internals-parameter asymmetry.
1101
+
1102
+ ## 1.402.0 (2026-07-24)
1103
+
1104
+ [1614] C-class adjudication batch: C1 exonerated with executable proof, C2 copy unified, C3 mislabel pinned out of core.
1105
+
1106
+ - **C1 adjudicated NOT-A-CORE-DEFECT (executable proof):** the parent SESSION transcript persists the completion notification's `<result>` FULL TEXT on both delivery legs — the live lane (followUp drain at model-stop) and the cross-run lane (idle-park → next run's turn-open `nextTurn` injection). By construction the model face and the persisted entry are the SAME rendered XML string, so a "delivered full / persisted summary-only" divergence cannot originate in core; the reported journal gap is the shell's own projection face. Probe methodology recorded: a scripted-brain world must stay SYNCHRONOUS (an async brain silently yields empty turns) and child slowness must come from a slow TOOL.
1107
+ - **fix ([1614] C2): the workflow completion summary no longer contradicts the launch note** — the notification payload CARRIES the (bounded) result, yet the summary tail said `result via TaskOutput(...)`, reading as "the result lives elsewhere" against the launch note's "the notification carries the result; do not poll". The tail now says `full result + per-agent rows via TaskOutput(...)` — TaskOutput is the full-form/detail route, both copies point the same way. Red-first pin.
1108
+ - **C3 adjudicated NOT-A-CORE-DEFECT (exoneration pin):** `local_bash` has zero hits in core (and server) source — it is the shell's own task-type vocabulary; a new pin locks core's TaskOutput-over-workflow envelope to `taskType:"workflow"` + non-empty `task_id`/`status`.
1109
+ - Test hygiene: mcp-refresh temp dirs ride one cleanup ledger (the old single-slot `growDir` leaked the first dir when written twice) + a load-time sweep of stale `mcp-grow-*` strays from interrupted runs.
1110
+
1111
+ ## 1.401.0 (2026-07-24)
1112
+
1113
+ Neighbor-report batch: workflow forward-lane self-identification ([1611]), CC-alias display labels resolve to sema tiers ([1613]), RB-45 v2, RB-46 adjudicated closed.
1114
+
1115
+ - **feat ([1611], server field-proof): a workflow child's FORWARDED `task_progress` self-identifies** — the foreground SSE lane previously carried a bare uuid with no workflow identity (only the fleet lane had `wa*`+workflowRunId), reading as an unknown nested subagent. Additive `workflowRunId` + `workflowAgentLabel` fields ride every forwarded tick from both spawn legs (conditional-install wrappers; sink-less spawns still get NO sink — pinned).
1116
+ - **fix ([1613], clay's ruling): display labels never show a pre-resolution CC alias** — `"haiku"` on a DeepSeek run read as a vendor-model claim. `resolveModelDisplayLabel` maps a CC tier alias to its sema tier name (haiku→lite, sonnet→flash, …) at the workflow model label and all four roster-spawn model fields; a non-alias string is the deployment's own key and passes through. Routing (`expandTiers`) untouched.
1117
+ - **fix (RB-45 v2): the spool-rotation pin's discriminator is now rotation EVIDENCE** (a sampled total that ever SHRINKS — truncate-to-zero is impossible without rotation), immune to arbitrary scheduler lag; the peak bound retires to the pure no-rotation ceiling (28MB). A fourth concurrent red past the widened bound showed any peak bound races the scheduler.
1118
+ - **RB-46 adjudicated CLOSED (not reachable):** the review's "gateless deployment" premise does not exist — the built-in integrity policies are composed unconditionally, so the tool_call gate is ALWAYS registered; an adjudication pin proves a refreshed egress-hinted tool on the most minimal assembly is truly gated (headless auto-deny), never a bare execution. The 1.400 warning branch stays as a zero-cost refactor guard.
1119
+ - Test-suite updates: the run-workflow spawn-attribution pin now asserts DELIVERY-through-the-enrichment-wrapper (the [1611] identity change) instead of sink reference equality.
1120
+
1121
+ ## 1.400.0 (2026-07-24)
1122
+
1123
+ Backlog re-adjudication batch (clay's "why wait" ruling): RB-41 and RB-45 cleared, RB-46 narrowed, RB-44 closed as not-a-bug.
1124
+
1125
+ - **fix (RB-41, availability): blocking-green shell shapes demote to ask.** The classify face auto-allowed commands that hang to the tool timeout with zero output: a HEAD-segment stdin-reader with no file argument (bare `cat`; `grep pattern` with no file; `tr` always), `tail` in follow mode (any segment, `-f`/`-F`/combined/`--follow`), and unbounded device sources (`/dev/urandom`/`zero`/`random`, `head` exempt — bounded by construction). Mid-segment readers stay green (pipe-fed). Red-first (3 red pins + a protective-green pin); the 5000-case real-execution black-box harness re-run stays SOUND with zero fail-open.
1126
+ - **fix (RB-45, test-only): the spool-quota rotation pin's peak bound absorbs full-suite scheduler lag** (16MB → 24MB; without rotation the peak always reaches the 28MB total, keeping a hard 4MB discrimination margin — the day's three concurrent-suite reds were the recorded red).
1127
+ - **honesty (RB-46, narrowed): a refresh that folds irreversible/egress hints on a deployment whose prepare registered NO gate now says so loudly on the receipt** (previously the new tool ran silently ungated); the full lazy-registration fix stays recorded.
1128
+ - RB-44 adjudicated closed (not-a-bug): within the JSON-image equivalence classes the provider-wire form is identical — no actionable divergence surface.
1129
+
1130
+ ## 1.399.0 (2026-07-24)
1131
+
1132
+ [1608] fix: the secret-scrub pattern table gains word-boundary anchors — ordinary technical words are no longer silently redacted.
1133
+
1134
+ - **fix: every credential-prefix pattern in `SECRET_PATTERNS` is left-anchored with `(?<![A-Za-z0-9])`** — without it, any word whose TAIL spelled a prefix was silently rewritten (server's field find: `NETWORK-attached` → `NETWO[redacted]`, `RTMARK-100-…` test markers eaten; the same missing anchor also bit `task_categories`/`risk_assessment` (lowercase), `MonkeyJumping…` (eyJ), `FAKIA…` (AKIA), and — on the key=value arm — `monkey=abcd1234`/`turkey: …`), with triggering depending on suffix length rather than anything credential-like. The `_` stays OUT of the anchor class deliberately: `api_key=…`, `STRIPE=sk_live_…` glued assignments are real credential shapes that must keep scrubbing.
1135
+ - **fix: the prefixed-token arm drops its `i` flag** (sk/pk/rk/gh[opsur] — the real vendors are lowercase-only; case-insensitivity is what turned every UPPERCASE word ending in RK/SK/PK into a false positive). `Bearer` keeps `i` (HTTP header words are case-insensitive).
1136
+ - Blast radius (why this mattered): `scrubSecrets` is the single scrub point behind `boundedRedactedSummary` — workflow agent errors, run-level output aggregation, workflow systemPrompt/objective summaries, structured-output caches, transcript summaries (15+ call sites) all silently corrupted matching user content with no warning.
1137
+ - Red-first: 4 pins (the server's four-case repro + the lowercase/eyJ/AKIA/xox/AIza/key=value families, and a protective pin over 11 REAL credential shapes incl. glued JSON/env assignments — all still scrub); stash-verified 3 red → green.
1138
+
1139
+ ## 1.398.0 (2026-07-24)
1140
+
1141
+ RefreshMcpTools (CC 2.1.218 parity, [1605] anchor): refresh a connected MCP server's tool list mid-task — the refreshed tools are callable on the very next step.
1142
+
1143
+ - **feat: `MaterializedMcp.refresh(server?)`** — re-reads the tool list of one server (or every connected server) over the EXISTING connection, never dialing or re-dialing (CC-anchor semantics): a dead/never-connected/named-unknown server reports `not_connected`, a live listTools failure reports `failed` with no reconnection attempt. A refreshed entry runs the SAME intake pipeline as connect (schema gate, namespacing, axis derivation, execute-closure construction — extracted verbatim into `intakeListedTools`) and carries the per-server added/removed namespaced-name diff plus the fresh tools/axes/dropped. `McpRefreshResult` exported.
1144
+ - **feat: the `RefreshMcpTools` tool** (mounted when `spec.mcp` is present) — `{server?}`; per refreshed server it folds the new axes into the live gate sets (tighten-only; a config-contradiction fold error downgrades that server's refresh honestly instead of failing the gate), swaps the server's tools in the live pool (the removed∪current name union is the exact old domain), and rebuilds the harness toolset — through the deferred-tools `rematerialize` when that machinery is armed (placeholder semantics survive a refresh) or a bare `setTools` + fingerprint/turn-snapshot sync otherwise. Registered `effect:"read"` (reads the server's list; mutates only this session's toolset).
1145
+ - **fix (pre-existing, exposed by the e2e red): `setTools` without `activeToolNames` kept the harness's construction-time name set** — fine for the deferred placeholder swap (same names), but a tool whose NAME is new was silently unreachable ("not found") no matter how it was added. Both rebuild arms now pass the full list's names (a zero-drift no-op for the deferred-only case).
1146
+ - Red-first: all six pins fail without the src changes (stash verification); the e2e pin drives the full chain — turn 1 refreshes, turn 2 calls a tool that did not exist at task start, real stdio servers throughout (including a growing-tool-list server).
1147
+
1148
+ ## 1.397.0 (2026-07-24)
1149
+
1150
+ Server cross-probe batch ([1598] F1/F2/F3 + adversarial-review F-1/F-3/F-4): the parked state machine's replay, stranded-orphan, and stop-verdict honesty fixes — all red-first (11 new pins, every fix stash-red-verified).
1151
+
1152
+ - **fix (F2, the heaviest): a ROLLED-BACK claim ticket could fully redeem** — the drive lane's durable seed pinned at ticket-mint time won `adoptOwnRow` (writerId+writerEpoch match; rollback never bumped the epoch, violating the §7.2c contract note), rewrote the row back to ticket shape (resurrecting the dead claim, and in the race shape erasing a legally seated NEW claim), and the resolve CAS (anchored on cp.rev only) consumed the approval. Three-layer fix: ① the rollback/sweep REDEEMABLE-shape write-backs now mint a new epoch (`rollbackParkedClaim` restored arm, both sweep rollback arms) — a dead lane reads "foreign" and poisons itself by design; ② `driveParkedResume` gains a CLAIM-SEAT preflight (fresh row read, `parkClaimId === ticket.claimId`) BEFORE the resolve CAS — a dead ticket is rejected with zero checkpoint side effects; ③ terminal-shape write-backs deliberately do NOT bump (a terminal row is flip-guarded; the live stop lane's converging re-adopt stays legal) and now carry a `status:"parked"` CAS guard (review F-3: a cross-process replay in the flip→finalize window can no longer rewrite a RUNNING redemption to failed).
1153
+ - **fix (review F-1, HIGH, field-proven): the epoch bump no longer foreignizes the SAME process's surviving lane** — after claim→rollback the parked handle's live lane record is synced forward, so a later legitimate terminal write (TaskStop killed / sweep failed) still lands durably instead of poisoning the lane.
1154
+ - **fix (F1): the stranded flip-lost orphan is now reaped** — `parked` + NO claim + checkpoint RESOLVED previously slipped both reconcile arms (arm 1 keys on expired/missing; arm 2 required an in-flight claim) and stayed parked forever, unredeemable. A third arm settles the AGED shape honestly (`failed`, approval-consumed/outcome-unknown); the freshness gate keeps the live resolve→flip window untouched. Docs now state the arm-2/3 opt-in dependency (`staleClaimMaxAgeMs` + out-of-process sweep — review F-5).
1155
+ - **fix (F2-secondary): the stop's killed verdict is DURABLE truth** — the parked-stop win now lands the terminal row with a direct guarded CAS (the lane write stays as the retry path); previously the lane's first CAS lost on a claim-bumped rev and a losing redemption's register poisoned it in the 50ms retry window, after which the loser's rollback rewrote the row to failed (receipt said killed, store said failed).
1156
+ - **fix (F3): an unreachable arbiter is no longer reported as "a resume won the arbitration"** — a throwing approval store now surfaces `park_arbiter_unreachable` (retryable, row stays parked); the park/re-park `resolveStop` closures propagate store failures instead of folding them into "not won" (the best-effort `expireByStoreScope` swallow stays where best-effort is right).
1157
+ - Honesty edits: the claim-seat preflight's residual-window note states the real bound (several store round-trips; an over-stale claim can lose its seat mid-drive — claim then drive promptly); the terminal-arm no-bump rationale states the true convergence mechanism (chain order + out-of-process sweep, review F-4).
1158
+
1159
+ ## 1.396.0 (2026-07-24)
1160
+
1161
+ [1596] core half: the cross-Runner parent-constraint re-supply seat — parked redemption survives a server restart.
1162
+
1163
+ - **Field-proven gap (probe-first, red before report):** a checkpoint minted under inherited parent-policy constraints records `requiresParentConstraint` (live closures cannot persist); the same-process drive is silently re-supplied from the Runner's in-memory `parentConstraintRegistry`, but a FRESH Runner (server restart / rolling update — the production shape where an operator decides hours after the park) has an empty registry, so every drive is rejected pre-CAS (`resume.parent_constraint_missing`) and the row is honestly-but-permanently unredeemable. Production host tasks almost always carry a toolPolicy ⇒ after a restart the whole parked-redemption surface was dead.
1164
+ - **feat: `reviveClaim.parkedResume.inheritedGate`** — the deployment's REBUILT constraint chain (its policies are config-driven, so an equal-semantics rebuild is possible) rides the parked-resume seat; `driveParkedResume` threads it into the documented `resumeStream(..., internals)` re-supply channel. The resume-side recorded-count shape check adjudicates it verbatim (a partial/mismatched chain is rejected — the seat never widens, it reopens the existing channel to the parked-decide lane). Absent ⇒ byte-for-byte pre-seat behavior.
1165
+ - Red-first: the WITH-seat pin fails on the pre-seat engine (stash red-verification); three pins land — the pre-seat reality (fresh-Runner drive rejected, row converges back to redeemable parked, approval survives), the seat redemption (fresh Runner + rebuilt chain → completed), and the shape guard (over-length chain rejected).
1166
+ - Server half (wiring the seat from /decide with a config-rebuilt chain) and the cli cross-restart pty leg are picked up on the blackboard ([1596]).
1167
+
1168
+ ## 1.395.0 (2026-07-24)
1169
+
1170
+ RB-40 closed: inherited-chain ask frame dedup — one human frame for one consent (the single-Runner embedded 2-frame shape).
1171
+
1172
+ - **feat: per-(toolCallId, approver-identity) ask-grant dedup.** When an inherited-constraint layer resolves a call's ask as a CLEAN allow (zero `updatedInput` anywhere on that leg) at a live approver FUNCTION, the child's own caller-slot ask reuses that consent instead of consulting the same function again. Both policy layers still adjudicate (tighten-only holds; a deny/narrowing on either layer stands) — only the duplicate HUMAN consultation collapses. Reuse guards (any mismatch re-asks):
1173
+ - **identity**: the approver function reference (never a name);
1174
+ - **shape**: byte-equality against the [1462] r5 PRESENTED SNAPSHOT (the structuredClone the human actually saw) — reuse re-emits that snapshot via `presentedInput`, so the executed shape rides the same schema-revalidated binding as a main-gate approval (shown == executed by construction; review F-1);
1175
+ - **source**: only a caller-slot policy ask may reuse (`decisionReason` ∈ {absent, "rule"}) — a safety tighten (egress/irreversible/shellGate), a hook-promoted ask (now stamped `"hook"` at the promotion site), or a classifier ask keeps its own frame (review F-2);
1176
+ - **ledger**: the one real consultation is booked once, with the wait actually timed on the inherited arm (review F-3 — never zero, never double);
1177
+ - **lifecycle**: evaluation-scoped only — consumed on reuse, swept at the gate call site, purged at handler head on toolCallId reuse (review F-5); a REOPENED checkpoint's re-adjudication never sees a stale grant. Cyclic/unserializable shapes skip the grant (re-ask, never a gate failure).
1178
+ - Red-first: the two dedup pins fail on the pre-fix engine (stash red-verification: 4 red → green); 8 pins land (dedup ×2, distinct-authority preservation, [1462] edit disarm, snapshot binding, egress-frame preservation, ledger honesty, no cross-call reuse). The depth-2 chain pin's expectation updates from 2 deps-approver frames to 1 (the intended behavior change, annotated in place).
1179
+ - Adversarial review (subagent): 1 HIGH + 2 MED + 2 LOW, all folded pre-ship; residual F-4 (JSON-serialization-image equality classes) recorded as RB-44.
1180
+
1181
+ ## 1.394.0 (2026-07-24)
1182
+
1183
+ Test-only: E2E depth batch — the recorded gaps close (no runtime changes).
1184
+
1185
+ - **Reopen-compensation deterministic fixture** (the 1.385 recorded debt): the P-7 fault (`agent-park`) — the approved tool vanishes from the delegation pool between park and drive (the pool is re-read per execute, so the injection is a one-line toolset rotation). Pinned: the row converges back to parked with the claim residue cleared, the checkpoint returns to `pending` (the approval is never burned), and a re-claim after the tool is restored completes the SAME approval to terminal — the full self-heal loop, executed for real (previously logic-reviewed only).
1186
+ - **Parked chain over FILE twin stores**: park → claim → drive → completed over `FileCheckpointStore` + `FileBackgroundAgentStore`, with FRESH store instances re-reading the row and checkpoint from disk between steps — the park is disk truth, not process memory (the cross-process persistence shape core owns; the TiDB leg stays with cli).
1187
+ - **Parked chain over `PgCheckpointStore`** (pg-mem): the park mints a real Postgres checkpoint row; claim → drive → completed consumes it there (row-level SQL assertions before and after).
1188
+ - **CC team-tree handover E2E** (`cc-team-handover.e2e`, design/152 D-3): engine A spawns a NAMED teammate through the real Agent tool (the spawn writes CC `config.json` via the roster seam), files tasks through the real TaskCreate tool (CC `tasks/<n>.json` + `.highwatermark`), parks inbox messages; a completely fresh adapter+tool set over the same tree (engine B) resolves the teammate, lists and extends the task list under the CC hwm discipline, and leases/acks the inbox with per-message read flags as disk truth — the engine-consumption face the store-level suites (cc-stores, Matrix C) never exercised.
1189
+ - `agent-park` rig gains store/tool injection seams (test-only parameterization).
1190
+
1191
+ ## 1.393.0 (2026-07-24)
1192
+
1193
+ design/152 S4b D-2: the READ-ONLY CC transcript-sidecar adapter — the last unbuilt member of the CC-file adapter family.
1194
+
1195
+ - **feat: `src/stores/cc/sidecar-transcript.ts`** — enumerate + parse + project Claude Code subagent transcript sidecars (`subagents/agent-<id>.jsonl` + companion `agent-<id>.meta.json`) so a sema engine taking over a CC-created team (D-3 sequential handover) can read the teammates' transcripts as resume context. Strictly one-way CC→sema (D-2 ruling): this module never writes; SessionStore stays the only transcript truth source.
1196
+ - `listCcSidecarAgents(subagentsDir)` — recursive enumeration (flat + parent-segment nesting + `workflows/<runId>` + `remote-agents`), symlinks not followed, deterministic order; companion meta rides the handle (absent/unparseable meta and a missing directory are fail-soft — the CC tree is a PARTIAL tree).
1197
+ - `readCcSidecarTranscript(path)` — verbatim envelope lines + an honest `malformedLines` count (a torn trailing append is counted, never a throw and never a silent drop).
1198
+ - `ccSidecarToMessages(lines)` / `readCcSidecarMessages(handle)` — projection into sema `Message` shapes: user string/text-block lines → `UserMessage`; `tool_result` blocks → `ToolResultMessage` rows (toolName recovered from the preceding `tool_use` id, honest `"unknown"` when the pairing is outside the file); assistant text/thinking/tool_use blocks → `AssistantMessage` with `api:"anthropic-messages"`, `provider:"claude-code"` (an origin label, not a sema brain), carried-over model/usage/stop_reason (usage cost stays unpriced-zero; absent usage ⇒ zero shell + `usageMissing`). Envelope lines without a projectable message are counted in `skippedLines`.
1199
+ - Golden real-CC sample pinned (`test/fixtures/cc-golden/sidecar/`, cc-teams-diskform@67a7cef): enumeration + meta verbatim, 4-line transcript projection (attachment line skipped, thinking signature carried, usage figures exact).
1200
+
1201
+ RB-42① closed: the inherited-unavailable marker channel drops its FIFO eviction.
1202
+
1203
+ - **fix: over-cap marks are REFUSED, never evicted** — evicting the oldest entry could strip an IN-FLIGHT mark (its call would become re-adjudicable by a synchronous layer — the widening direction the marker exists to forbid); refusing the NEW mark instead downgrades exactly that call to the pre-153 in-fold fail-closed deny (availability only, never a widening). All four mark sites (both mandate arms, both unavailable arms) fall through to an explicit deny on refusal.
1204
+ - **fix: marker lifecycle-end sweep** — the gate call site now deletes the call's marker entry when its evaluation settles (any path: execute/deny/suspend/hook short-circuit). Previously a marked call short-circuited before reaching a consumer (e.g. a PreToolUse hook deny) leaked its entry forever — the real pressure source that pushed the set toward the cap in long sessions. Steady-state size now tracks live concurrent evaluations.
1205
+ - Investigation recorded: the review-suggested request-object-identity carrier (WeakSet) is NOT viable — `combinePolicies` rebuilds the request object on rewrite cascades, and an identity miss there would be fail-open. A 300-call over-cap batch probe against the PRE-fix engine could not construct the eviction fail-open (gate evaluations don't interleave enough); the fix is defensive tightening plus the real leak repair, pinned by a 300-call batch invariant test (zero executions, zero classifier consultations).
1206
+
1207
+ ## 1.391.0 (2026-07-24)
1208
+
1209
+ Test-only: red-verification + e2e depth for the 1.389/1.390 cars (no runtime changes).
1210
+
1211
+ - **Reverse red-verification (recorded)**: the RB-42②③ and review-F-1 fix pins were written AFTER their fixes (pin-style, not red-first) — now verified red against a checked-out 1.388.0 worktree: the mixed-park continuity, ttl-round-trip, and TaskStop-on-re-parked tests all fail there (the latter with the exact `park_resume_won` artifact), and pass on main. The red-first evidence chain for both cars is closed.
1212
+ - **PG-backend e2e** (`worktree-continuation`): the cd-continuation chain over `PgSessionRepo` (pg-mem), with two SEPARATE session stores over the same repo so turn 2 re-loads from DB rows — proving the `workspace_state` entry round-trips the jsonb column, plus a direct row-level assertion.
1213
+ - **Cross-root rebase e2e**: a session continued under a DIFFERENT `rootPath` restores the tracked cwd REBASED onto the new root (the design/155 review's deferred test-gap #3).
1214
+ - **PG checkpoint round-trip pin** (`pg-stores`): `Checkpoint.durableApproval` (the 1.389.0 parked-resume continuity source) round-trips `PgCheckpointStore` verbatim — presence with scope+ttlMs, and absence as absence.
1215
+
1216
+ ## 1.390.0 (2026-07-24)
1217
+
1218
+ design/155 (cli [1580]): worktree/cwd session state now survives PLAIN continuation turns. Previously `handsCwdRef` + the EnterWorktree session ref were rebuilt every prepare and restored only from the durable-checkpoint seed — an ordinary same-session turn (a new task on `spec.sessionId`, the shell's every-turn form) silently reset the cwd to the task root, made ExitWorktree a no-op, and leaked the git worktree.
1219
+
1220
+ - **feat: `WorkspaceStateEntry`** (`type: "workspace_state"`) — a first-class session-tree entry recording the settle-time workspace state (`taskRoot` + optional `handsCwd` + optional `activeWorktree`), with a single strict shape gate (`normalizeWorkspaceState`) shared by the public append, the read walk, and the import-validate door (the design/150 announced-listing posture). `Session.appendWorkspaceState` / `Session.getWorkspaceState` (branch-nearest snapshot; a CLEAR snapshot — both fields absent — is a real hit that shadows older state; rewind gets correct semantics free via the path-to-root walk).
1221
+ - **feat: continuation restore ladder** (prepare) — the checkpoint seed stays FIRST on a durable resume (atomic with the leaf); the branch-nearest `workspace_state` entry is the SECOND rung, consulted only when there is no resume; fresh root defaults last. Cross-root continuation rebases entry paths (`rebaseWorkspacePath(p, entry.taskRoot, rootCanonical)`). One session read shared by the cwd and worktree seeds.
1222
+ - **feat: settle write** (runtask, post-assemble) — a non-suspended settle appends the entry whenever the final state is NON-DEFAULT (every such turn, so the snapshot stays inside a bounded-tail backend's load window) or when it changed back to default (a CLEAR snapshot). Awaited (the RB-25 leaf-CAS discipline) and best-effort: a failure degrades to next-turn root defaults — the pre-155 behavior — never fails the run. A durable suspend skips the write (the checkpoint lane owns that state).
1223
+ - **fix (adversarial-review M1, pre-ship): suspend-interleaved chains leaked a stale snapshot** — a resume leg's settle baseline (the checkpoint seed) is blind to an unredeemed non-default entry still on the branch, while the suspend leg (correctly) skips its own settle write; the two legs' hand-off then never wrote the CLEAR snapshot, so a pre-suspend `{handsCwd: sub}` entry revived on the next plain turn even though the run had `cd`-ed back before suspending. A resume leg now marks its baseline unknown and writes a CLEAR snapshot even when it settles at the default state. Red-first pin: cd→continue→cd-back→durable-suspend→resume→continue must land at the root.
1224
+ - **hardening (adversarial-review L1)**: the shape gate enforces its stated contract — absolute POSIX paths only (`/`-prefixed, the same contract `rebaseWorkspacePath` and the bash cwd trust rule speak) and a hex-shaped `baseSha` (4-128 chars). Not a privilege boundary (the shell may already `cd` anywhere; file-tool containment lives in `resolveKey`) — a contract alignment.
1225
+ - Red-first verification: `test/worktree-continuation.test.ts` reproduces cli's pty evidence deterministically (real git fixture + real bash, zero model) — Enter→continue→pwd-in-worktree→Exit-removes, tracked-`cd` carry-over, fresh-session baseline, the CLEAR-snapshot pin (a post-Exit turn starts back at the root), the M1 interleaved chain, a shape-gate unit table, the per-turn-write pin (two off-root turns ⇒ two entries — the bounded-tail visibility assumption made executable), and the import-door fail-closed pin. Known residual (documented): turns run with read-only/no-shell hands write nothing, so a bounded-tail backend's load floor can slide past the last snapshot — degrading honestly to root defaults (the pre-155 behavior).
1226
+
1227
+ ## 1.389.0 (2026-07-24)
1228
+
1229
+ Second-pass adversarial review of the 1.387.0/1.388.0 cars (subagent review, per the standing channel policy) + fixes and test hardening. One MED found-and-fixed, RB-42 ②③ cleared, twelve new behavior pins.
1230
+
1231
+ - **fix (review F-1, MED, reproduced): drive-leg settle watcher expired checkpoints under a stale scope** — on the `/decide`-driven resume leg, the watcher's expire consumers derived scope from the DRIVE spawn's spec (`checkpointScopeOf(bgSpec)` — no `durableApproval` seat ⇒ the default isolation scope), while the re-minted checkpoint carried the re-derived opt-in scope. `expire(token, scope)` is a scope-guarded CAS, so every consumer silently no-opped: `TaskStop` on a re-parked row reported `park_resume_won` while the row stayed parked and the checkpoint pending (stop-face vs storage contradiction, retries never converge), and the veto/abort orphan-compensation expires failed silently — resurfacing on the drive leg the very orphan class the 1.388.0 MED fix removed. All three consumers (abort branch, `resolveStop` arbitration, veto/lost-CAS compensation) now expire by STORE-truth scope (`get(token)` → `expire(token, cp.scope)`, the same shape the re-park `resolveStop` already used). Red-first pin: `TaskStop` on a re-parked row must report `killed` with the checkpoint `expired`.
1232
+ - **fix (RB-42 ② ③): `Checkpoint.durableApproval` — the verbatim opt-in recorded at the approval-suspend mint** (scope + ttlMs; engine-minted, never a tool arg; rides the jsonb/JSON row so Pg/file stores need no migration). The parked-resume revive-continuity re-derivation now reads it EXPLICITLY instead of inferring from `gate.kind === "human"`: a SAFETY-TIER first park (`irreversible_ask`) no longer loses forwarding continuity on the resumed leg (②), and `ttlMs` carries across a re-park so the re-minted checkpoint re-arms its reaper `deadline` (③, previously unbounded). No widening: an unattended park records nothing, so a spawn that never opted in can never gain the opt-in through a resume — pinned by a dedicated test (the re-park stays `irreversible_ask` under the default isolation scope). Legacy pre-1.389 rows keep the human-kind scope-only inference (pinned via a schema-downgrade probe).
1233
+ - **design/154 second review: SOUND, no new findings** — the compound classifier's fail-open axis re-verified (connector completeness, quote-coarsening, whitespace-mismatch fail-safe, hard-reject completeness, Monitor-face parity, declaration-face untouched). Its implicit load-bearing invariant is now pinned by tests: JS-whitespace-only pseudo-separators (VT/FF/U+3000/U+2028) classify green AND fuse under real bash into a data-arg or command-not-found — never a second program (real-execution assertions); plus nine expansion/substitution/redirection smuggling forms pinned RED, and trailing-terminator/degenerate-connector boundary pins.
1234
+ - **new 件4 pins (review gaps)**: the marked-call classifier skip covers the UNAVAILABLE family with no facility (honest deny, classifier never consulted); a marked mandate ask never falls back to the child's own live approver (park-priority); the agentStore-absent eligibility cell (no forwarding, no park, no checkpoint); forwarding transitivity at the seam (a forwarded child re-exposes the seat verbatim to its own tools); the forwarded seat's durableQuestionFace flip (AskUserQuestion mounts and a gated question parks durably).
1235
+ - Backlog: RB-42① augmented (marker-Set eviction under an ARMED autoMode classifier re-admits the marked call to machine adjudication — same trigger, upgraded consequence; candidate fix = request-object-identity carrier, its own reviewed car); RB-42 ②③ CLEARED; RB-43 (F-1) recorded CLEARED; a scope-semantics note documented (mandate park adjudicates in the CHILD's own `durableApproval.scope` — deployment-trusted-domain semantics, aligned by construction on the forwarding arm).
1236
+
1237
+ ## 1.388.0 (2026-07-23)
1238
+
1239
+ design/153 §7.4 (件4, approval-chain campaign core-half — the campaign's final piece): the durableApproval FORWARDING arm + the three-valued mandate consumer. A named background child of a durable-approval task can now itself durably suspend a plain policy ask instead of always headless-denying it, and an inherited-ancestor durable mandate resolves through a durable park when the child has its own park facility — instead of an unconditional fail-closed deny.
1240
+
1241
+ - **feat: `ToolExecuteContext.durableApprovalForChildren`** — the parent's `durableApproval` opt-in (value copy) rides a new trusted ctx seat, Runner-filled, never a model/tool argument. The Agent tool forwards it into an eligible background child's spec: named non-fork child + durable row + checkpoint store + `ensureChildSessionDurable` attested (the same §7.3 park-eligibility family the settle-time mint point checks). Fork and synchronous children are excluded by design. Tighten-only: forwarding grants a PARK capability, never a permission widening — the resume decision stays with the operator.
1242
+ - **feat: mandate consumer, three-valued** — the two inherited-constraint wrapper arms' `durableMandate === true` branch no longer unconditionally denies: when THIS task has its own park facility armed (a wired checkpoint store + `durableApproval`/`forceDurableGate`), the ask is marked and floated back into the fold, where the main gate's park-priority leg suspends it durably (joining the existing §2 "inherited-unavailable" marker family — both mean "no synchronous layer may resolve this ask"). No facility ⇒ the pre-件4 fail-closed deny, byte-for-byte.
1243
+ - **feat: revive continuity** — the `/decide`-driven resume path carries no forwarding ctx seat, so a second plain ask on a resumed leg re-derives the durable-approval opt-in from the parked checkpoint itself (scope only — no TTL), but ONLY when the park was a plain-ask park (`gate.kind === "human"`), never a safety-tier park, to avoid silently widening a resume leg's capability beyond what the original spec opted into.
1244
+ - **fix (adversarial-review MED, pre-ship): auto-mode classifier bypassed the marker contract** — a marked ask (inherited-unavailable OR the new mandate-park case) reached the auto-mode classifier BEFORE the park/fail-closed legs; an `allow` verdict would execute the call with no operator or fail-closed leg ever consulted. `ToolGateInput.isMarkedUnresolvable` now skips the classifier entirely for a marked call.
1245
+ - **fix (adversarial-review MED, pre-ship): an aborted settle skipped checkpoint-orphan compensation** — the park-eligibility guard excluded `abort.signal.aborted` from the whole park/expire block, so a parent-abort racing a just-minted checkpoint (commit landed, then abort, then the settle watcher ran) left the checkpoint pending forever (an undiscoverable orphan pinning the session until its own TTL/reconciliation sweep). The abort branch now still runs the expire compensation; it just never attempts a fresh park (which would race the very abort that preempted it).
1246
+ - Verification: adversarial subagent review (overall SOUND; two MED findings above fixed pre-ship, three LOW residuals recorded as RB-42) + real-Runner/real-delegation pins — a 16-cell eligibility permutation sweep (park fires in exactly the all-eligible+opted-in cell, action never executes pre-decision in any cell), full E2E chain (headless-durable parent → ctx injection → forwarding → ancestor mandate floats to park → claim → `/decide` drive → the pending action executes), two-cycle revive continuity (park → drive → re-park on a fresh token → drive → completed, zero claim residue), the classifier-bypass regression (an armed classifier configured to ALLOW never gets consulted for a marked call), and the abort-race orphan regression (checkpoint winds up expired, not pending).
1247
+ - Backlog: RB-42 (LOW — marker-Set cap fail-open on eviction under very high concurrency, pre-existing and unchanged by this batch; mixed safety-then-plain park sequences still lose forwarding continuity on resume; re-park carries no reaper TTL — all documented).
1248
+ - design/153 approval-chain campaign core half is now COMPLETE (件1-件4 all shipped).
1249
+
1250
+ ## 1.387.0 (2026-07-23)
1251
+
1252
+ design/154 (shellGate classify granularity — server [1558]④): compound read-only classification for the CLASSIFY face. The 18-name allowlist stays; what changes is that `;`/`&&`/`||`/`|`-connected compounds of listed readers now auto-allow instead of always asking (the ask-storm main case: `cat x | grep y`).
1253
+
1254
+ - **feat: `classifyCompoundReadonly(command, allow)`** (exported) — textual scan-split on `;`/`&&`/`||`/`|`, every segment re-vetted by the single shared `parseLeadingCommandName` + bare-name allowlist; all-green ⇒ read-only. Whole-string hard rejects stay for `< > $ ( )` backtick newline CR backslash; a LONE `&` is hard-rejected too (backgrounding outlives the shell and escapes TaskStop lifecycle governance; `|&` rejects via the same rule). Soundness: with escapes/expansion/redirection hard-rejected, quotes can only HIDE connectors (real boundaries ⊆ textual boundaries), so every executed argv[0] is a vetted segment head — a quote-carrying head is parser-rejected, a clean head equals the executed program name. Error direction is strictly false-negative.
1255
+ - **feat: `bashReversibilityProbe` upgraded** to the compound classifier (signature unchanged). The `bash_readonly` tool's `effect:"read"` declaration face deliberately KEEPS the strict single-command check (documented fork). The Monitor face intentionally shares the upgraded probe (design/135 G2: same env seam, classification is about the command text; a polled read-only compound stays read-only) — pinned by two wiring tests.
1256
+ - **NOT included by design:** no git/`find`/subcommand allowlisting — "zero-flag" vetting operates on pre-expansion text while the shell expands quotes/braces/globs into argv, and the candidate git subcommands are not provably read-only under repo config (`core.fsmonitor`, `diff.external`/textconv, promisor fetches, index refresh). `git status` still asks — recorded false-negative; widening needs a dedicated review.
1257
+ - Verification: adversarial review (SOUND across five attack directions) + a black-box harness in the server-[1558] methodology — red-first detector self-check, then 5000 seeded fuzz commands with EVERY green actually executed under real bash in a snapshot sandbox: zero filesystem mutations, zero surviving process groups, zero hangs; curated adversarial family all red or verified-harmless. Ask-rate on a realistic coding-task corpus: 69% → 43%.
1258
+ - Backlog: RB-41 (availability residue — stdin-blocking greens like bare `cat` hang to tool timeout; not a reversibility issue, documented).
1259
+
1260
+ ## 1.386.0 (2026-07-23)
1261
+
1262
+ design/152 S4c (CC↔sema interop matrix — the campaign's closing piece): cross-adapter interop pins beyond the per-store shape suite.
1263
+
1264
+ - **test: `test/cc-interop-matrix.test.ts`** — Matrix A: a full CC team `config.json` creation shape (lead keys, tmux panes, subscriptions, backendType) resolves through the roster adapter, and a sema `record()` preserves EVERY CC-only key byte-visible. Matrix B: the zero-noise tenure invariant — reading a REAL CC task file (golden fixture) and `set()`-ing it back unchanged is BYTE-IDENTICAL (key order, 2-space indent, terminator), and the hwm self-heal writes CC's pure-decimal text form. Matrix C: roster + task list + mailbox drive the SAME team tree with zero cross-store interference (allocate/deliver/lease/ack round trip; config face untouched).
1265
+ - No runtime changes. design/152 interop line closes; S4a live-form follow-ups remain tracked on the design doc.
1266
+
1267
+ ## 1.385.0 (2026-07-23)
1268
+
1269
+ design/153 件3c (approval-chain campaign, RB-39③ CLEARED — the campaign's core half is complete): the parked-resume DRIVE — a parked background child's approval decision now redeems into a real resumed run, end to end.
1270
+
1271
+ - **feat: `RunInternals.afterCheckpointResolve`** — the trusted post-consume hook: `resumeStream` calls it after its resolve CAS WON (master arbitration decided) and before the resumed leg starts. The parked-resume drive's ONLY legal parked→running flip site; a throw aborts the resume loudly (the caller's rollback then reads `resolved` → honest outcome-unknown terminal).
1272
+ - **feat: parked-born registration** — `RegisterBackgroundAgentInput.initialStatus:"parked"` (+ token + stop-arbitration closure): the resume registration keeps the live face PARKED through the pre-consume window (TaskStop expire still wins there — r6 H-1 honored on the live face too) and licenses replacing an in-process parked prior handle (the park→resume handoff, lane poisoned like the terminal replace). `TaskRegistry.adoptParkedResume` = the consume flip's live adoption (handle running, arbitration dropped, durable lane swapped onto the post-flip record/rev — the r6 H-3③ lane-swap-before-any-write ordering); `finalizeParkedResume` = attach-time claim clear; a resumed-cycle terminal settle clears all park/claim residue as the bounded fallback.
1273
+ - **feat: the drive itself (`ctx.reviveClaim.parkedResume`)** — trusted ctx: `{ticket, outcome}` from the server's /decide path (claim first via 1.384's `claimParkedAgent`). Sequence: require-existing session PREFLIGHT before the token is consumed (an authoritative miss expires the checkpoint — never consumed into a doomed run; r6 H-4) → `resumeStream` with the consume-flip hook (fresh-read + field-guard CAS: claim/status/token bind the semantics, the fresh rev only closes the window — the seeded lane's initial no-op CAS legitimately bumps rev) → stream driven to a runTask-shaped result for the SHARED watcher (settle, notifications, and RE-PARK of a re-suspending resumed child all reuse the existing chain; resume cycle seq = flip-bumped) → ANY throw converges through ONE `rollbackParkedClaim` (checkpoint-truth dispositions sort pre-consume rolled_back from post-consume failed automatically). The revival startup barrier surfaces the drive's honest convergence message (`parked_resume.startup_failed`: rolled_back=retryable vs failed=terminal) instead of the generic revival text.
1274
+ - **fix: `rollbackParkedClaim` fresh-reads the row** — binding moved from `reservedRev` (invalidated by the seeded lane's benign rev bump) to the claim-id field guard + live rev; a claim-id mismatch reads "lost" honestly.
1275
+ - **codex adversarial batch (2 HIGH + 1 MED, folded before ship):** (HIGH) the consume flip's direct store write raced the seeded lane's session-bind CAS — either the already-consumed resume aborted spuriously, or the flip's epoch bump made the in-flight lane write read "foreign" and POISON the lane (finalize/terminal/re-park writes permanently dropped); the flip now runs INSIDE the lane chain (`TaskRegistry.consumeParkedFlip` — ordered after every seeded write, swap in the same chain step). (HIGH) prepare-family resume failures (env restore, pending tool unavailable) return a failed TaskResult while the Runner REOPENS the checkpoint to pending — the watcher would settle the row terminal (claim/token cleared) with the approved action still pending (stranded, or later executed row-less); the drive now detects the reopened-pending truth and RE-PARKS the row on the still-live token (plainly redeemable; the re-park clears claim residue), surfacing the retryable message. (MED) rollback could verdict "rolled_back" (=retryable) while a TaskStop expired the token in its read→CAS gap; the pending arm now re-reads the checkpoint post-CAS and converges the just-restored row to the terminal disposition instead.
1276
+ - 3 end-to-end pins (real Runner, real delegation, real checkpoint): full redemption (allow → flip epoch+1/seq+1 → pending action executes → completed, zero claim residue, token consumed exactly once), stop-wins-then-drive (no zombie run; honest terminal), preflight authoritative miss (checkpoint expired, never consumed). The reopen-compensation leg is logic-reviewed (codex) — a deterministic reopen fixture (env-restore fault injection on resume) is recorded for the next test batch.
1277
+
1278
+ ## 1.384.0 (2026-07-23)
1279
+
1280
+ design/153 件3b (approval-chain campaign): the parked-resume RESERVATION claim + rollback dispositions.
1281
+
1282
+ - **feat: `TaskRegistry.claimParkedAgent(stores, handle, access)`** — pure reservation (r6 H-1): writes `parkClaimId` via the guarded CAS (`rev + status:"parked" + token + claim-ABSENT` — single-claimer by construction); the row STAYS parked, the live handle keeps its TaskStop arbitration (an expire during the reservation window still wins — the 件3c token-consume flip is the only thing that can beat it). Binding: token comes FROM the row, checkpoint must be pending, `cp.sessionId === row.sessionId`. Returns a compensable `ParkedClaimTicket` (exported type). Authorization layering doc-pinned: `access` = row visibility only; operator /decide authority is the server's front gate.
1283
+ - **feat: `TaskRegistry.rollbackParkedClaim(stores, ticket)`** — disposition follows the CHECKPOINT truth re-read at call time (never blind): pending → "rolled_back" (claim cleared, row redeemable again, same token, no ownership/epoch churn); expired/missing → "failed"; **resolved → "failed"/outcome-unknown (r6 H-4 non-compensable — a consumed approval is NEVER re-armed)**; store outage → "retry". A local live parked handle follows the failed dispositions (poll/stop stay coherent).
1284
+ - **fix: reconciliation arm 2 covers BOTH stale-claim shapes** — parked+claimId (a reservation whose claimer died pre-consume → claim cleared, still redeemable) and running+claimId+token (the post-consume pre-finalize window → pending rollback / expired failed / **resolved failed-outcome-unknown, never back to parked**); all transitions moved onto the guarded CAS. Registry-side exclusion switched from writerId batch to the LIVE-HANDLE SET (r6 H-3⑤: a writerId batch over-excluded rows this process once wrote but no longer manages).
1285
+ - 6 pins: reservation invariants (row/live both stay parked, single-claimer), claim guard ladder (not_parked/binding_broken/checkpoint_not_pending), pending-rollback redeemability (re-claimable), expired-rollback dual-face settle, stop-vs-reservation race (never "rolled_back"; eventual truth = the stop's killed), dual-shape stale-claim reconciliation.
1286
+
1287
+ ## 1.383.0 (2026-07-23)
1288
+
1289
+ design/153 件3a (approval-chain campaign, claim-glue substrate): the guarded CAS + ownership-generation layer the r6 design review demanded before any claim can be written.
1290
+
1291
+ - **feat (BREAKING for store implementers): `BackgroundAgentStore.updateIf`** — a REQUIRED guarded CAS: atomic `WHERE rev [AND status] [AND parkedCheckpointToken] [AND parkClaimId]` (a string expects equality, `null` expects ABSENCE; same bump-rev-by-1 success semantics as `update`). The plain rev-only CAS is not enough for the park/claim lanes — reservation, rollback and finalize bind to FIELD state, not just the revision (r6 H-3: a rev can move for unrelated reasons while the park-critical fields changed meaning). A required method so a legacy implementation fails LOUD instead of failing open on the exact CAS the approval chain's safety rests on. Both bundled stores (InMemory/File) implement it; the File form evaluates the guards under the same per-id lock as its rev CAS.
1292
+ - **feat: `BackgroundAgentRecord.writerEpoch`** — the monotonic ownership generation (mint = 1 at registration; every ownership transfer bumps it — the tier-3 claim does now; the 件3b resume claim will). The durable-lane adopt now matches `writerId + writerEpoch` BOTH: the r6 H-3 self-clobber ABA is closed (an in-process ownership transfer left the old lane holding the same writerId — its next heartbeat flush would CAS-fail, adopt the NEW rev on the writerId match alone, and replay its stale snapshot over the transferred row). Legacy rows (epoch absent on both sides) keep pre-epoch adopt semantics unchanged.
1293
+ - Pins: updateIf guard matrix (wrong-status/wrong-token refusals, claimId null-absence single-claimer form, bound-claimId rollback form) + the lane fence end-to-end (register → transfer CAS with epoch bump → stale-lane settle flush reads foreign and poisons instead of clobbering the row back).
1294
+
1295
+ ## 1.382.0 (2026-07-23)
1296
+
1297
+ design/153 二拍 B 件1+件2 (approval-chain campaign): the PARKED background-agent state machine — a named bg child that durably suspends on an approval now parks redeemably instead of settling failed.
1298
+
1299
+ - **feat: `"parked"` state** — `SemaTaskStatus` and `BackgroundAgentRecord.status` gain a `"parked"` arm (downstream-visible union expansion, announced [1556]): durably suspended on a pending approval checkpoint, neither live nor terminal. Row keys `parkedCheckpointToken`/`parkedAt`/`parkClaimId` (additive). The checkpoint token CAS is the MASTER arbitration domain (r4 F-05): the row never adjudicates a park outcome alone.
1300
+ - **feat: park mint (regular-bg watcher)** — a suspending child parks iff the FULL §7.3 predicate holds: NAMED child (anonymous/fork never park, F-07), `background.agentStore` + `background.checkpointStore` (NEW key, must be the RunnerDeps instance) wired, and the deployment's `background.ensureChildSessionDurable(sessionId)` capability resolves (NEW key — the capability PROTOCOL replacing any config-boolean form, F-09; a transient-session deployment migrates the child session here — the server park-前-迁移 hook; rejection VETOES the park). A vetoed/lost park EXPIRES the already-minted checkpoint (no-orphans compensation, F-03); ineligible children keep the pre-153 lifecycle byte-for-byte (pinned).
1301
+ - **feat: registry live faces** — `parkBackgroundAgent()` single transition point (first-writer vs TaskStop/settle); poll serves an honest `parked` waiting projection (live + durable-fallback arms); TaskStop on parked arbitrates through the checkpoint (`expire` wins ⇒ killed; a consumed token refuses with `park_resume_won`; a cross-process row refuses to the durable approval inbox). Teardown/session-reap kill sweeps are `running`-filtered — a parked child structurally survives its host.
1302
+ - **feat: `reconcileParkedAgents(stores, scope, now, opts?)`** — the deployment-cadence reconciliation sweep (parked rows are REAP-EXEMPT in every store policy — this owns their cleanup): checkpoint expired/missing ⇒ row fails honestly; a stale resume claim (`parkClaimId` past its lease) rolls BACK to parked while the checkpoint still pends (F-04 compensation half; the claim mint itself is 件3).
1303
+ - **fix: `checkpointScopeOf()` single-sourced** — both durable-suspend mint sites and the park lane's expire/arbitration now share one scope derivation (`durableApproval.scope ?? (principal || "irreversible")`); the park tests caught a re-derived copy expiring against the wrong scope (silent no-op — exactly the drift class the shared export closes).
1304
+ - **codex adversarial batch (1 CRITICAL + 2 HIGH, folded before ship):** (CRITICAL) BOTH pre-153 revival paths were unfenced — tier-3 SendMessage's terminal-claim CAS and the 1.332 retained wake would each flip a parked row/handle straight to running WITHOUT consuming its approval checkpoint (restarting the child while the approval pends, racing TaskStop's expire); parked now refuses on both (`parked_pending_approval` / `still_running`) — parked→running exists ONLY through the 件3 claim glue. (HIGH) `TaskRegistry.reapDurableAgents` selected every non-running row as terminal — a parked row could be DELETED (mailbox dropped, transcript released) with its approval still pending; parked is now excluded at selection and both rechecks. (HIGH) store-level reconciliation against a row whose parking process is ALIVE split-brained the live handle (poll kept saying parked over a failed row); new `TaskRegistry.reconcileParkedAgents` settles its OWN handles live (poll/stop/row coherent) and delegates foreign dead-writer rows to the store half via `excludeWriterId` (multi-replica residual documented, RB-38③ posture).
1305
+ - 16 pins (real Runner + real delegation): park mint + token binding + capability sees the child session id, TaskStop-wins-by-expire, veto compensation, ineligible/anonymous byte-for-byte, reconciliation three arms + registry live-coherence + foreign-row split, both revival fences, both reap exemptions.
1306
+
1307
+ ## 1.381.0 (2026-07-23)
1308
+
1309
+ CI-carrier release: the 1.379.0/1.380.0 tag runs FAILED the test gate in CI (flaky-guard: test/cc-stores.test.ts introduced two undeclared wall-clock sleeps after the local full-suite run), so neither reached npm — registry latest stayed 1.378.0. This release carries their content to npm unchanged plus:
1310
+
1311
+ - **test: flaky-guard declaration for cc-stores.test.ts** — the CC lockfile mutex is mkdir + OS mtime (the REAL filesystem clock, which fake timers cannot drive; same class as memory-txn-lock): one bounded 30ms "second acquire still blocked" sleep + one 20ms stale-aging sleep against an injected 10ms staleMs, asserting on lock OWNERSHIP outcomes, never elapsed time. Allowlisted with justification.
1312
+ - **docs: RB-40 scope narrowed after the [1555] 对表** — cli's live 4-frame flutter was the shell's `updatedInput` reference-compare (always-true → recheckApprovedEdit re-approval chain; fixed shell-side, live = 1 frame). A clean-allow core re-probe confirms the 2-frame form exists ONLY in single-Runner embedding (the child's own deps.onAsk === the inherited approver → both fold layers consult the same function once each); split assemblies (server/cli: subRunner has no own onAsk route) converge to 1 frame, matching live.
1313
+
1314
+ ## 1.380.0 (2026-07-23)
1315
+
1316
+ Two anti-drift + interop items: the L2 golden-fixture layer, and the CC mailbox adapter that completes the S4b store trio.
1317
+
1318
+ - **feat: `@sema-agent/core/fixtures`** — golden SHAPE fixtures (the [1549]/[1552] L2 layer). `ASK_REQUEST_FIXTURES.host` / `.subagent` are the two AskRequest forms with every volatile value normalized to an angle-bracket placeholder; downstream (server/SDK/cli) consumes THESE shapes instead of hand-building requests, so an upstream shape change breaks a test instead of shipping a silent mismatch (the [1546] "bare req = false-green" class). Each fixture is PINNED against the live engine by test/fixtures-golden.test.ts (real gate → real object → same normalization → deep-equal), so it can never drift. Contract: shape truth, not value truth — the host form proves `sourceTaskId` is present on host asks too (NOT a discriminator) while `fromSubagent` is absent; the subagent form proves the reverse.
1319
+ - **feat: `createCcFileMailboxStore`** — `MailboxStore` over CC's `teams/<team>/inboxes/<sanitizedName>.json` shape, completing the S4b store trio ([1553]⑤ binary-anchored 定盘: the inbox filename is the sanitized member NAME via `sanitizeCcAgentName`, not the agentId). Maps CC's per-message `read` flag onto the core ack/peek contract ([1545] 纠偏#1: the inbox has NO high-water mark — that's the task-list face); `seq` = 1-based array position (honest under the D-3 sequential-handover ruling); `ack` flips `read:true` in place (a CC-side reader sees it read too — the interop point); foreign entries (idle_notification, color/summary) round-trip losslessly; ENOENT and corrupt files read as an empty box (CC's own tolerance); lease exclusion is in-process (CC has no lease concept, documented); D-1 `scope:"default"` enforced loudly per verb.
1320
+ - **test: golden real-CC samples** — test/fixtures/cc-golden/ carries a real CC task list (from sema-comms cc-teams-diskform@67a7cef): hwm=11 alongside a 16.json is live proof of 纠偏#2, and the store self-heals to allocate 17 (anti-rot). 12 new mailbox/golden pins.
1321
+
1322
+ ## 1.379.0 (2026-07-23)
1323
+
1324
+ design/152 S4b first car — the CC-file adapter family (`src/stores/cc/`): a Claude Code-created team's task list and member roster now drive the SAME on-disk files from a sema engine ([1497] B-line; shapes per cli's [1545] 定盘考据, 207 form).
1325
+
1326
+ - **feat: `createCcFileTaskListStore`** — `TaskListStore` over CC's `tasks/<listId>/` shape (`<n>.json` 2-space JSON + `.highwatermark` + directory lock). nextId SELF-HEALS per 纠偏#2: `max(hwm, directory scan) + 1` with the hwm file repaired in the same locked window (hwm=10 alongside 11.json is CC-normal state; trusting the file alone would reuse ids). High-water never regresses on delete. Foreign keys a live CC wrote survive a sema `set()` (read-modify-write of the full parsed object, looseObject discipline both directions); sema-only `owner`/`metadata` ride the file verbatim. `mutate` = one directory lock held across the sequence.
1327
+ - **feat: `createCcTeamsRosterAdapter`** — `RosterStore` over CC's `teams/<team>/config.json` (member array; joinedAt ⇄ createdAt; sema-only columns ride as `sema*` extra member keys, preserved by CC's looseObject reads). D-1 enforced loudly: non-`"default"` scope refuses at construction (CC files carry no tenant axis). 纠偏#3: an absent config (headless-born CC tree) reads as an EMPTY roster fail-soft — never a throw; the first sema-side `record` mints CC's initial shape. Latest-wins at read time; filter-before-reduce access predicate (same family as the bundled stores).
1328
+ - **feat: `acquireCcLock`/`withCcLock`** — the proper-lockfile COMPATIBILITY layer (mkdir `<target>.lock` directory = atomic acquire; stale-reclaim past 10s; bounded jittered retries at CC's own inbox-lock parameters). The cc/ family is the ONLY file-store family licensed to share files across processes — it speaks CC's lock protocol; every `stores/file` backend stays single-process by contract.
1329
+ - **codex adversarial batch (1 CRITICAL + 3 HIGH, all folded before ship):** (CRITICAL) task ids reach the store from MODEL-FACING tools — an unvalidated id like `../victim` escaped the list directory (read/overwrite/delete of any reachable JSON); ids are now confined to the canonical CC numeric form (read face = honest miss, write face = loud throw, mutate tx included). (HIGH) the lock gained OWNERSHIP FENCING beyond stock proper-lockfile: an `owner` nonce inside the lock dir + a stale/2 heartbeat (a long `mutate` hold no longer drifts past the stale window) + a reclaim mtime double-check (ABA: never remove a fresh holder's lock) + a release fence (a stale-reclaimed holder's release THROWS instead of silently removing the new holder's lock — a raced critical section must never report success). (HIGH) the roster access predicate is now the SHARED `entryAccessible` export (the local approximation widened empty-access/missing-scope reads AND false-missed legitimate session-scoped resolution — access.sessionId matches the entry's OWNER, the session-scoped spawn convention). (HIGH) `record` refuses a non-default-scope entry loudly BEFORE touching disk (silently re-stamping it `"default"` on read would make a tenant-x binding resolvable cross-tenant).
1330
+ - Deferred to the sample drop (152 §5.6 残点): `CcFileMailboxStore` (the inbox KEY semantics — name vs agentId — are not pinned by the [1545] text alone) and the golden real-CC fixtures (tests currently use hand-built [1545]-schema fixtures, marked for reconciliation). 23 pins incl. traversal confinement, release-after-reclaim fencing, predicate parity, cross-instance allocation under contention, and the three 纠偏.
1331
+
1332
+ ## 1.378.0 (2026-07-23)
1333
+
1334
+ design/153 second-beat A (approval-chain campaign, RB-39①② settled):
1335
+
1336
+ - **feat: explicit ask-source identity** — `AskRequest` gains Runner-filled readonly `fromSubagent?: true` (present exactly when the asking gate belongs to a delegated subagent; trusted `internals.parentTaskId` fact, unforgeable) and `sourceAgentName?: string` (display identity, UNTRUSTED-for-display). THE discriminator for "from a background agent" attribution — replaces presence/equality heuristics on `sourceTaskId` ([1546] MED-1: the host's own asks always carry `sourceTaskId` = its session id; JSDoc now pins "NOT a subagent discriminator"). Minted by ONE factory at all FOUR construction sites — the main gate's resolveAskBound, both inherited-wrapper arms, and recheckApprovedEdit's re-ask (codex M-2: the fourth site a three-site sweep missed).
1337
+ - **fix: an inherited ask's "unavailable" now reaches the durable park** (RB-39①). Pre-153, an ancestor's frozen approver answering `"unavailable"` was collapsed to an in-fold deny — unreachable by the G1 re-route, so even a park-equipped child denied. Worse (codex M-1 kill-scenario): any float-back form that rode the ask object would be dropped by combinePolicies' first-ask selection whenever the caller slot asked first, and the child's OWN approver could then resolve that ask `allow` and execute — silently bypassing the ancestor's unresolved authority. The landed form uses an out-of-fold marker channel: the wrapper floats the ask back and marks the toolCallId; `suspendAsk` keeps park priority for marked calls (durable park when armed), and `resolveAskBound` refuses a marked call WITHOUT consulting the child's approver (honest fail-closed deny carrying `approverUnavailable`, G1 re-route stays armed). Ancestors that ANSWER keep pre-153 behavior byte-for-byte. Pins: the M-1 kill-scenario (caller ask first + child approver would allow → tool never runs, child approver never consulted), park-and-resume redemption, answering-ancestor regression, and the fourth-mint-site identity ride.
1338
+
1339
+ - **feat (server [1532]§三1 ask): the in-process live poll/stop arms serve the current revive-cycle `seq`** — same key, same settle-generation meaning as the durable-fallback arm (1.373), so tail meta is complete whichever arm answers. The handle carries a live cycle projection: spawn = 1; a tier-3 seeded registration inherits the claim's pre-minted next cycle; a 1.332 retained wake bumps in step with the ledger; the settle re-aligns the projection to the authoritative `outcome.seq`. Pin: live poll reads 1 on the spawn cycle, 2 across a tier-3 revival (running and settled), and the stop face projects the same number.
1340
+
1341
+ ## 1.376.0 (2026-07-23)
1342
+
1343
+ The [1534] question-gate hardening batch (codex adversarial follow-up on 1.375: 2 HIGH + 1 MED, all folded) + a dead-code sweep.
1344
+
1345
+ - **fix (codex HIGH-1): the DEGRADED paths can no longer route a question to the permission approver.** 1.375 exempted only the healthy sync-first decline; a pre-commit exception or a failed checkpoint put still fell through to the synchronous `resolveAsk` leg — recreating the original deadlock exactly when the park facility was broken or absent. The gate (hooks.ts) now intercepts a surviving `ask` on the reserved AskUserQuestion name BEFORE the sync chain and answers a typed honest refusal ("the question was NOT shown to anyone; proceed with your best judgment") — a question terminates as park or refusal, never as a permission verdict. Pins: no-checkpoint-facility form and throwing-store form, both asserting the approver saw ZERO asks.
1346
+ - **fix (codex HIGH-2): inherited ancestor policies can no longer arbitrate a question at the frozen permission approver.** Both parent-constraint wrappers (the shared-instance adapter and the distinct-policy arm) resolved an ancestor `ask` directly via the ancestor's frozen `onAsk` — so a HOST whose spec composes `createDurableQuestionPolicy` (the server durable deployment shape) sent every delegated CHILD's AskUserQuestion to the permission card, and the `durableMandate` arm could collapse it to a fail-closed deny. A content ask now floats back through the fold untouched (mandate included — the child's own park IS the durable semantic the mandate protects) and terminates at the main gate's park/refusal. Pin: a real delegated child under an inherited question policy — the child's question answers the refusal, the frozen approver sees zero asks.
1347
+ - **docs (codex MED): the reserved-name contract is now pinned on `ASK_USER_QUESTION_TOOL_NAME`** — the entire engine keys content-ask routing on this name (main gate, degraded fall-through, inherited wrappers, checkpoint `contentKind`); permission-gating the tool is UNSUPPORTED by design (CC parity: no permission card on questions), and a caller-supplied tool reusing the name inherits the same routing.
1348
+ - **chore: dead-code sweep (247 lines, tsc-verified, zero behavior)** — removed the orphaned vendored loop wrappers (`agentLoop`/`agentLoopContinue` + their private stream/failure helpers), `runAgentLoopContinue`, the unwired `OBSERVER_FRESH_START_NOTE`, unused Result helpers (`getOrThrow`/`getOrUndefined`) and orphaned types (`AgentState`/`JsonlSessionRepoApi`/`AgentHarnessPromptOptions`), and the unused `rgGrep`/`findRedosRisk` wrappers. Public surfaces untouched (package-root and `/bench` exports verified against neighbor consumption; deep imports are sealed by the exports map).
1349
+
1350
+ ## 1.375.0 (2026-07-23)
1351
+
1352
+ - **fix (blackboard [1534], cli-diagnosed deadlock): the question gate ALWAYS parks — a live permission approver no longer captures AskUserQuestion.** The `suspendAsk` sync-first preference (a live `onAsk` resolves a plain policy `ask` in-stream) treated a durable-question-policy `ask` like a permission ask, routing it onto the synchronous approval frame — whose allow/deny vocabulary structurally cannot carry an ANSWER: allow executed into the `QUESTION_AWAITS_RESUME` placeholder (config error leaked to the model), deny told the model its question had been shown when it never was. The decline leg now exempts the reserved `AskUserQuestion` tool name (the same discriminator the checkpoint face uses for `contentKind: "content_ask"`), so with a durable park facility armed the question suspends durably regardless of a live approver; permission asks on ordinary tools keep the sync-first leg byte-for-byte. CC parity: AskUserQuestion never raises a permission card. Live-human sync answering is untouched (it happens via `onQuestion` inside the tool when the policy allows). Pins: live-onAsk park + zero approver consultations + answer resume; ordinary-tool sync-first unchanged.
1353
+
1354
+ design/151 S3c — tier-3 lazy revival (the behavior car: agent-team resume lands). A message to a SETTLED named teammate now revives it through the FULL Agent spawn chain instead of the honest refusal, when the deployment wires the durable seams.
1355
+
1356
+ - **feat: SendMessage tier-3 delivery rung** (`§7.1 r2` ladder, exact order): durable-row resolution (exact `a*` handle, or roster name → agentId) behind the fail-closed predicate → teammate gate (durable `row.name` single-arm; anonymous/fork rows keep the 1.373 texts byte-for-byte) → status arms (killed = permanent refusal mirroring live S5; running = writer-aware honest texts incl. the F-7 host-gone arm — no arm ever parks a message it cannot deliver) → claim-CAS (FULL inherited row: identity verbatim, `writerId` = reviving instance, terminal payload cleared, `seq` = pre-minted next cycle) → mailbox append (claim-winner window ONLY) → session preflight (miss ⇒ CAS rollback + `resume.session_not_found`) → claim/lease → `reviveSpawn` → ack at spawn success. Receipt: "revived … (N earlier pending message(s) delivered with it)".
1357
+ - **feat: Agent-tool revival arm** (`ToolExecuteContext.reviveClaim`, trusted-internals seat): register-with-id on the ORIGINAL handle + `requireExisting` on the ORIGINAL transcript session; identity inherited verbatim from the claimed row (never the reviver's ctx); durable lane seeded post-claim (`RegisterBackgroundAgentInput.durableSeed` — no initial put, first settle CASes forward from the claim; a leftover terminal in-memory handle is replaced with its stale lane silenced); name-clamp bypass (a sibling teammate reviving a peer is the primary case), `isolation` suppressed, background surface probed explicitly (`revive.no_background` — never a silent sync downgrade); session-scoped lifetime semantics (a revived teammate never dies with the reviving turn); seq continuity (`row.seq` rides frames and the durable settle — `task_id:status:seq` stays collision-free across restarts); revived children are durable-lane-only (no retain entry, no eager release — the proven row anchors the transcript).
1358
+ - **feat: `RunnerDeps.mailboxStore`** — prepare-task wires the tier-3 seams into the auto-mounted SendMessage; the revival spawner wraps the mounted delegation tool identified by OBJECT (`agentListing` marker — a caller's same-named shadow tool can never receive the trusted claim) and calls it through the SAME ctx enrichment as the spec.tools injection map (`enrichSpecToolCtx`, extracted to one source). Tier-3 activates only with ALL of `backgroundAgentStore` + `mailboxStore` + a mounted delegation tool.
1359
+ - **feat: claim-window mutual exclusion** (`TaskRegistry.beginDurableClaim`/`endDurableClaim`) — reap, the 1.332 retained revive, and tier-3 claims are three-way excluded in-process; `reapDurableAgents` gains the F-13 companion arm (a winning row delete drops the row's mailbox in the same sweep; new `deps.mailbox`).
1360
+ - **docs/golden:** SendMessage description + Agent background bullet speak the mount's truth (durable named-agent revival named only when the seams are wired).
1361
+ - **fix (tooling-hazard class): literal control characters in source escaped** — the 1.372 composite-key separator landed as a literal NUL byte, making grep treat the file as binary (silently invisible to every source sweep; observed during this car's survey) and confusing editor language services. Swept the whole tree: 6 files (roster-store upsert key, prompt-assembly epoch probe strings, tool-catalog digest separator, three tests) now use `\uXXXX` escapes — runtime strings byte-identical, digests/keys unchanged.
1362
+ - **codex adversarial batch (4 findings, all folded before ship):** (HIGH) the revival receipt now speaks ATTACH truth — a startup barrier races injector-ready (prepare succeeded: session really acquired, batch in the run) against an early failed settle; a pre-attach failure returns `revive.startup_failed`, the lease is RELEASED (messages stay deliverable) and nothing is acked — the old form acked on launch-acceptance and could silently delete a message whose session raced away. (HIGH) `durableAgentWrite` re-checks lane poison at every attempt boundary and after each adopt — an in-flight pre-revival write blocked across the durableSeed replacement could otherwise adopt the claim's rev (same process = same writerId) and permanently re-write the old terminal snapshot over the revival. (MED) session preflight moved BEFORE the mailbox append (a "not sent" receipt now leaves NOTHING parked; retries cannot double-park) + store-fault classification: only an attested `not_found` is permanent, anything else answers the retryable `session_store_unavailable`. (MED) `reviveSpawn` forwards `row.model` as the per-call override — the revived cycle runs on its ORIGINAL model where the roster resolves it (honest modelNote degrade otherwise, never a silent identity swap).
1363
+ - **panorama adversarial batch (10 findings: 1 fixed, 4 folded cheap, 3 ledgered, 2 recorded):** (fixed — the sweep's own class member) `InMemoryMailboxStore`'s composite key was still space-joined — scope `"x a1"` + handle `"a2…"` merged with scope `"x"` + handle `"a1a2…"` (cross-scope mailbox merge); now NUL-separated like the agent/roster stores (the File backend's sha256 scope dirs were already injective). Folded: the store contract pins `update` bumps rev by EXACTLY 1 (the claim derives `expect.rev + 1`); the claim/revive clear set gains the S2-era content fields (summary/recentSteps/editedFiles/usage — defensive today, load-bearing the day S2 mints them; both revival lanes, same set); the failure receipts state the at-least-once truth ("your message remains queued … do not resend") so a retry cannot double-park; the fork-shadow edge and the parked-vs-live summary frame divergence are recorded in place. Ledgered (RB-38③④⑤): multi-replica 1.332×tier-3 split-brain (PLAUSIBLE, single-replica unreachable), the pre-existing sync-throw launch window, the ack-to-first-persist crash bar.
1364
+ - **feat: `assertJsonMetadata` exported** (server pg-twins review case) — a durable TaskListStore twin applies the SAME metadata gate as the bundled impls instead of a drifting private mirror.
1365
+ - Recorded residuals (RB-38): a TASK-scoped revived teammate's live handle keeps its original (dead) owner, so a mid-run follow-up to it answers the durable still_running text instead of 1.324 delivery (session-scoped teammates — the deployment norm — are unaffected); a preflight-failed revival leaves the parked message for the mailbox reap.
1366
+
1367
+ ## 1.373.0 (2026-07-23)
1368
+
1369
+ server[1526] ask batch (two items, additive).
1370
+
1371
+ - **feat: `HARNESS_SECTION_ANCHOR` exported** — the "# Harness" section heading was a bare literal on BOTH sides (core's assemble guards and the server's prompt mirror); a core rename would have silently split the mirror. One constant, both consumers (CYBER_RISK/URL_SAFETY precedent); core's three internal sites now key on it.
1372
+ - **feat: durable-fallback polls serve `seq`** (`UnifiedTaskOutput.seq` from the row's 1.371 settle mint) — a tail consumer keys revive cycles without the notify lane (the server's tail-meta seq pass-through unblocks).
1373
+
1374
+ ## 1.372.0 (2026-07-23)
1375
+
1376
+ server[1523] peer-review response batch (the server team reviewed core 1.364-1.371 from the consumer seat; every finding verified and folded) + the [1522] contract follow-up.
1377
+
1378
+ - **fix (peer finding 1, the one implementation defect): FileMailboxStore stale-`touched` refcount** — after a drop/reap deleted the shared box, another instance's close() could decrement a REBUILT box's refs and close an in-use append fd (EBADF / extreme-case fd-reuse mis-write). `touched` now records the exact BoxState identity; a stale entry no longer touches a rebuilt box.
1379
+ - **fix: mailbox reap scope filtering** — InMemory filtered on a composite string key (ambiguous for scopes containing spaces: scope "a" swept scope "a b"); now filters on the record field. File backend's directory-prefix match is boundary-terminated.
1380
+ - **contract rulings (peer findings 2a/2b — both bundled impls are the source of truth, the text follows):** same-owner re-claim RENEWS the lease and picks up newer messages (the crash-retry form); seq is never reused WITHIN a box's lifetime — `drop`/`reap` end the lifetime and a rebuilt box restarts at 1 (dedup keys carry the handle). The F-13 sweep-companion sentence is re-worded PLANNED/S3c (it read as present-tense; 1.371 has no consumer yet).
1381
+ - **docs:** `bgAgentId` nesting-overwrite semantics (each bg boundary overwrites the tag — the host wire carries the OUTERMOST a* for the whole subtree; per-frame nesting rides `parentToolCallId`); `resolveBackgroundAgentByName` records the durable-row exclusion (canonical-only); `reapDurableAgents` sessions contract gains the "must truly end addressability or omit" sentence + the dual-store explicit-ruling arm ([1522]/[1523] case); revive-lane recorder wording note.
1382
+
1383
+ ## 1.371.0 (2026-07-23)
1384
+
1385
+ design/151 S3a+S3b — the seam car for agent-team resume (zero behavior: nothing is wired by default; the tier-3 revival that consumes these ships as S3c). Design §7 (r2) folded a two-track adversarial review (codex 5 high + 2 medium, panorama subagent 4 high + 8 medium) before a line was written.
1386
+
1387
+ - **feat: `MailboxStore` seam + InMemory/File impls.** The durable third rung of the SendMessage delivery ladder: claim/lease + ack — NEVER a destructive drain (a failed/crashed revival lets the lease expire and messages return visible with their ORIGINAL seq; a parked message can be re-leased forever, never lost). Append happens only on the revival claim-winner path by design, so a receipt never promises a delivery with no consumer. File backend: per-(scope,handle) event-stream JSONL (append/lease/ack/release), **canonical-path-keyed SHARED state + mutex** (two store instances over one directory serialize AND share seq state — the review's exact instance-race), compaction with a seq high-water pin, torn-tail class fix inherited.
1388
+ - **feat: `createFileTaskListStore`** — the design/147 §6 team task list's restart-surviving backend: one atomic-replace `{version, nextId, tasks}` document; `nextId` is a durable high-water mark updated in the same locked replace as every allocation, never derived from surviving tasks (deleting the highest task and restarting cannot reuse its id). Single-process contract explicit.
1389
+ - **feat (revival mint points): durable row `model` (+`RosterEntry.model`) minted at spawn; `seq` (stop-cycle number) minted at settle** (`settleBackgroundAgent` gains `outcome.seq`; both settle arms pass their cycle number, first cycle = 1) — the tier-3 revival's lookup keys and dedup-continuity base (`task_id:status:seq` never collides across restarts).
1390
+
1391
+ ## 1.370.0 (2026-07-23)
1392
+
1393
+ design/151 S2 close-out — the live-tail routing key (MED-10 form (b), the last core half of "watch a background agent WHILE it runs"). Additive.
1394
+
1395
+ - **feat: `TaskEventIdentity.bgAgentId`** — every event a BACKGROUND child's lane forwards up the host `onForwardEvent` channel (text/reasoning deltas, tool events, progress) now carries the child's `a*` registry handle, stamped at the forward boundary on all three lanes (regular bg, fork-bg, retained-resume revive) and UNCONDITIONALLY (independent of whether a BCE observer is mounted). A serving layer pipes "what is agent aXX doing right now" straight off the host forward stream — no uuid→a* alias table. Sync/steer children carry no tag (no a* row); `wa*` workflow rows stay on their own observer lane. The recorder/stats see the original frame (copy at forward, never mutate).
1396
+ - With this, design/151 §3.2 S2 is complete: replay = the durable store's transcript anchor (1.364), root/time anchors (1.367), live tail = this tag over the already-flowing forward channel.
1397
+
1398
+ ## 1.369.0 (2026-07-23)
1399
+
1400
+ - **feat ([1518] server ask): `DURABLE_AGENT_HANDLE_RE` exported from the package root** — the durable `a*` handle shape gate, shared so a serving layer's fallback applies the SAME form judgment as the engine's poll/stop durable arms before its own durable branch (server 1.250's HIGH: a bare exact-key miss let padded/legacy/name-shaped ids leak into live branches; a mirrored private copy drifts — roster-normalize precedent).
1401
+
1402
+ ## 1.368.0 (2026-07-22)
1403
+
1404
+ Neighbor-ask batch ([1513] server asks ①②③ + [1496]/[1494]⑥ cli answer). All additive.
1405
+
1406
+ - **feat ([1513]①): `WorkflowRunStore.listByScope` gains `opts.session`** — store-side push-down filter on `originatingSessionId` (strict equality, field-less rows excluded, applied before `limit`), so a serving layer's "this session's workflows" is full-history rather than page-window. Both bundled impls.
1407
+ - **feat ([1511] ask a): `BackgroundAgentStore.listScopes?()`** — enumerate row-holding scopes so a deployment-op retention loop can drive `reapDurableAgents` per scope without external bookkeeping (a local backend under multi-tenant use otherwise grows principal-scoped rows forever). Optional method; both bundled impls implement it.
1408
+ - **docs ([1511] ask b / RB-37①):** the dual-mount symptom table on `RunnerDeps.backgroundAgentStore` now records the THIRD consumer — delegated children's auto-mounted TaskOutput/TaskStop/AgentTranscript read the child runner's deps.
1409
+ - **prompt ([1496]/[1494]⑥): the Workflow tool's `script` parameter now TEACHES the `meta.phases` shape** (array of `{ title, detail?, model? }` objects, never bare strings) — the static validator already answers in the same turn (structured error at parse, plus the sync body precompile), so the only avoidable round trip was the untaught shape on first submission.
1410
+
1411
+ ## 1.367.0 (2026-07-22)
1412
+
1413
+ δ batch — root-session anchor + spawn time anchor (scan finding A-3; the [1498]⑦ server / [1491]③c cli coordination fields). All additive.
1414
+
1415
+ - **feat: `rootSessionId` — the delegation tree's ROOT host session, threaded as a fixed point** (`ctx.rootSessionId ?? ctx.sessionId` at every spawn: depth 1 gets the host session, every deeper level inherits it verbatim) down the trusted internals chain (`RunInternals`/`ToolExecuteContext`). Carried on: `BackgroundChildEvent` spawn frames (fork/regular/revive/workflow `wa*` legs), the registry handle + `RegisterBackgroundAgentInput`, the durable `BackgroundAgentRecord`/`RowSummary`, and `RosterEntry` (stored verbatim). A nested grandchild's `parentSessionId` is an intermediate — post-restart, dead — session; recovery/enumeration faces group the whole tree under the root without alias walks.
1416
+ - **feat: `canAccessAgentRecord` ROOT arm + `listBySession` root match (both store impls).** The host session reads and enumerates its whole delegation tree on the durable face (same fail-closed posture: absent field = no arm; scope wall first).
1417
+ - **feat: `BackgroundChildEvent.startedAt`** (spawn frames, epoch ms at launch; revive frames carry the revive cycle's own launch instant) — the fleet row's elapsed anchor without waiting for the first tick ([1491]③c).
1418
+ - **feat: `runWorkflow` opts gain `rootSessionId`** (tool threads `ctx.rootSessionId ?? originatingSessionId`) — `wa*` observer frames group under the root host session too.
1419
+ - **hardening (codex δ round, 2 high + 1 medium folded):** the auto-mounted tool ctx is harness-native (no internals enrichment) — the root now rides `RunWorkflowToolDeps.rootSessionId` (prepare-task passes `internals?.rootSessionId ?? sessionId`), so a NESTED `run_workflow` no longer resets the tree to the intermediate session; workflow `spawnAttribution` threads the root into every workflow-agent's `RunInternals` (an Agent call inside a workflow agent inherits the true root); the SendMessage name-resolution projection keeps `rootSessionId` (name-addressed revival frames match exact-id ones).
1420
+ - **test:** nested fixed-point pin (grandchild frame carries the root, not the intermediate), durable root-arm + tree-enumeration pins, spawn-frame field pins.
1421
+
1422
+ ## 1.366.0 (2026-07-22)
1423
+
1424
+ γ batch — workflow session-axis completion (scan findings B-1 HIGH / B-2 / B-3 / B-4; coordinated with server 1.247's wire-face acceptance).
1425
+
1426
+ - **fix (B-1 HIGH): a later turn of the same session can now poll/stop its own workflow.** `canAccessWorkflowRun` rewritten to positive-match — scope must match, then the caller hits the OWNER arm (spawning task) or the new SESSION arm (`run.originatingSessionId`). Per-turn taskIds die with their turn while the completion notification routes to the session — before this arm, the session's later turns read their own workflow as `not_found` (default-deny functional breakage). Live leg too: `RegisterWorkflowInput`/the workflow handle carry `originatingSessionId`, and the registry's `canAccess` gains a type-gated workflow-only session arm (deliberately unlike background agents, whose sibling-turn live invisibility is a channel-discipline ruling). Exported `canAccessWorkflowRun` for serving layers.
1427
+ - **BREAKING (β polarity ride-along):** the old `access.owner === undefined ⇒ scope-only pass` residue in `canAccessWorkflowRun` is gone — every arm is positive-match.
1428
+ - **BREAKING (B-3): `subscribeWorkflow(runId, scope)` / `markWorkflowActive(runId, scope)`.** The live event stream carries prompts/outputs but had no scope arm while its same-layer sibling `getWorkflowRun` enforced one; a wrong-scope subscribe now reads exactly like a run that never existed (empty stream, info-hiding).
1429
+ - **BREAKING (B-4): `WorkflowScriptStore.persist/load` gain a `scope` parameter; the file store partitions per-scope subdirectories.** The CC anchor is a per-session directory and the port had dropped the axis — a model-supplied `scriptPath` could read another scope's script; `load` now bounds containment to the caller's partition. `resolveName` stays root-level BY RULING (the deployment-tier saved-name registry, built-ins' tier) — a per-run script is no longer implicitly a saved name.
1430
+ - **feat ([1510] server request): `WorkflowRunSummary.originatingSessionId`** (additive) — the list face can apply a session acceptance/filter arm without an N+1 `get`.
1431
+ - **ruling (B-2 FINAL, replaces the 1.365.0 interim note): the resume face gets NO engine-side session clamp — permanently.** Cross-session resume is `resumeFromRunId`'s core legitimate use (a fresh session picking up yesterday's run differs on every anchor by construction); a recheck attempt was built and REVERTED on the [1455] prefix-replay pins. Layering: engine owns the scope hard-wall (the journal's WHERE), the session acceptance arm belongs to the serving layer's session-bound connection (server 1.247 wire form). Poll/stop vs resume divergence is by design: current-identity gate vs prior-leg continuity.
1432
+ - **fix (ctx-first ride-along):** the run row's `scope` now follows `ctx.principal ?? d.scope` (matching the registry handle) — the two lanes' access answers can no longer diverge for the same caller.
1433
+ - **hardening (codex γ round, 3 high folded):** `canAccessWorkflowRun` re-exported from the package root (the server-facing predicate was unreachable — drift risk defeated the export's purpose); `WorkflowScriptStore` gained a REQUIRED `scopePartitioned: true` conformance marker (structural typing let a pre-partition adapter with fewer parameters stay assignable and silently ignore the scope argument — the marker breaks that at compile time, and the tool refuses/skips unmarked stores at runtime on the read/write sides); `resolveName` never serves the runId namespace (`wf_*` reads as unknown — a per-run script cannot be invoked as a deployment workflow; no legacy migration exists BY RULING: pre-partition root-level files were never shipped to any deployment).
1434
+ - **test:** new `workflow-session-axis` suite (predicate arms, live-leg second-turn poll, scope-gated subscribe, summary projection, marker discriminant, runId-namespace refusal) + script-store partition pins (cross-scope load refused).
1435
+
1436
+ ## 1.365.0 (2026-07-22)
1437
+
1438
+ β batch — access-predicate polarity unification (scan finding A-2; ruling: one step to the final form, no compatibility shims).
1439
+
1440
+ - **BREAKING: default-deny on BOTH access axes, everywhere.** `TaskRegistry.canAccess` and the roster's `entryAccessible` no longer SKIP a missing axis (the old polarity: owner-less handle = readable by every same-scope caller; scope-less handle/access = readable across scopes) — a missing scope or owner now reads as denial, matching `canAccessAgentRecord` (the durable-row predicate) so the engine has ONE polarity: absent axis = deny. The split polarity was the same session-axis defect class the neighbor repos just swept.
1441
+ - **BREAKING: registration requires both axes.** `assertOwnership` (all four register sites: bash/agent/monitor/workflow) now requires owner AND scope — an axis-less registration would be unreachable by every caller under default-deny, and a loud registration error beats a silently dead row. `SubagentToolOptions.background.owner`/`scope` and `RosterEntry.owner`/`scope` promoted from optional to required. Single-tenant spelling is an explicit `scope: "default"` (matching the engine chain's `principal ?? "default"`) — never implied by omission. The now-unreachable missing-axis degrade branch in `registerBackgroundAgent` was deleted.
1442
+ - **ruling (no code): workflow journal `load` stays scope-only** — same-scope cross-session replay is BY RULING (same trust domain today; runIds are engine-minted, not enumerable), recorded in the interface JSDoc. The convergence path (optional session arm once the γ workflow-session-axis batch anchors `originatingSessionId` on every row) is scheduled there; ad-hoc session clamps before that batch would break legitimate next-day resumes.
1443
+ - **Migration:** direct registry callers add `scope` (use `"default"` for single-tenant); deployments composing their own Agent tool add `background.owner`/`scope`; roster store implementations (pg) make both columns NOT NULL.
1444
+
1445
+ ## 1.364.0 (2026-07-22)
1446
+
1447
+ design/151 S1 — durable background agents (the store-native half of "async subagent details stay visible"): `BackgroundAgentStore` seam + lifecycle flip + file backend. Opt-in end to end: no store wired ⇒ the exact pre-151 lifecycle, byte for byte.
1448
+
1449
+ - **feat: `BackgroundAgentStore` seam (S1a).** One durable execution row per background agent (`a*` handle): status/terminal snapshot/`sessionId` transcript anchor/`writerId`/OCC `rev`. Contract: create-once `put`, rev-CAS `update`, content-free `listBySession`/`listByScope` projections, conditional `delete`, explicit-policy `reap` (stale-running flip + double-bound retention). Fail-closed read predicate `canAccessAgentRecord` (scope both ways; owner ∥ session anchor — the durable-only `parentSessionId` leg is the restart read face, recorded divergence from live `canAccess`). `InMemoryBackgroundAgentStore` reference impl; `RunnerDeps.backgroundAgentStore` (reader half) + `SubagentToolOptions.background.agentStore` (writer half — same instance, see JSDoc).
1450
+ - **feat: registry durable lane.** Write points register→bind→settle (+ direct-kill, killed-backfill): serialized per-row chain, bounded retry, read-back writerId adjudication (adopt own row across ambiguous commits/stale-flips; poison only on a genuinely foreign row), 60s heartbeat lease (row age = writer-process silence, not run length). Poll/stop gain a durable fallback behind the predicate: terminal snapshots serve in the live shape; a cross-instance `running` row serves the honest "outcome unknown here", never fabricated liveness.
1451
+ - **feat: lifecycle flip (S1b).** With a CONFIRMED durable row (awaitable probe: flushed + written + not poisoned + not flush-failed), a non-retained background child's transcript session survives the settle — TaskOutput/AgentTranscript keep serving full details after completion; disposal transfers to `reapDurableAgents` (the blessed joint reap). AgentTranscript gains a durable fallback for restart-evicted rows.
1452
+ - **feat: `FileBackgroundAgentStore`** (stores/file family): per-key async mutex, fsync'd JSONL ledger append BEFORE the in-memory flip, snapshot compaction, contract-identical CAS/reap.
1453
+ - **fix (pre-ship adversarial batch — two independent reviewers, all findings verified against code):** `AppendLog` physically truncates a torn ledger tail before O_APPEND (an acknowledged post-crash append could concatenate onto the fragment and vanish on the next replay — class fix, every file-store ledger benefits); joint reap reordered to conditional-delete-first (release-first could destroy a live transcript when a concurrent revive won the CAS; a winning-delete-then-release-failure now leaks to the session TTL sweep instead) + in-process live-handle gate; `reviveBackgroundAgent` persists a durable running transition (rev bump arms the reaper's CAS; clear-keys erase the prior cycle's terminal payload); both settle arms decide probe/release AFTER the settle enqueued the terminal write (the probe verdict now covers the terminal row; a released transcript clears the row's `sessionId` anchor); heartbeat re-drives settled flush-failed lanes (a >150ms store outage at settle no longer strands a fabricated "interrupted" over a real completion); poll/stop fallbacks refuse own-writer running rows (no existence window past the live predicate); ctx-first session identity for retain-ledger lookups (SendMessage/AgentTranscript); register-with-id namespace validation; terminal stop fallback no longer mislabeled `not_local`.
1454
+ - **fix (incremental re-review of the fix batch, 2 high + 2 medium folded):** reap and revive are mutually exclusive (`reapingHandles` — a revive landing inside the sweep's adjudication window is refused `not_found` instead of racing the conditional delete with an unflushed rev; regression pins the interleaving); every RETAINED disposal arm (TTL/LRU/evict/disposed-during-pin) clears the durable transcript anchor on a probe-false release (the non-retained fix alone left the class half-swept); the anchor is cleared ONLY after a successful session release (a throwing release keeps the row pointing at the surviving session); the settled flush-failed heartbeat re-drive gained its own ceiling (~1h, then a loud abandon) — terminal GC is prepare-driven and could not bound it.
1455
+ - **session-axis consistency sweep (two scanners, cross-checked):** consumer tools' access assembly is ctx-first on the session axis (4 sites); AgentTranscript identity resolution moved off the display-capped `list()` to `getAccessibleTask`; roster_only rejection details no longer carry a session coordinate. Remaining findings batched: workflow-lane session arm (HIGH, separate batch), predicate-polarity unification, docs/observability (RB-37 records the residuals).
1456
+ - **test:** 42-test suite (store contract, predicate, lane failure models, real-Runner lifecycle-flip e2e, file parity + torn-tail restart trilogy, reap race pins, heartbeat re-drive under fake timers).
1457
+
1458
+ ## 1.363.0 (2026-07-21)
1459
+
1460
+ design/147 §6 — agent-team CC 2.1.216 refresh (D-table adjudicated in the design doc; campaign S1-S3 shipped 1.316-1.332).
1461
+
1462
+ - **feat: shared team task list — `TaskListStore` seam (design/147 §6 D2, CC 216 team task-list parity).** New storage-tier seam on the task-list family (same layering as `RosterStore`/checkpoint store): `createTaskListTools(store?)` — no store keeps the private per-run closure (unchanged default, zero BREAKING); one shared store mounted into several bundles is the CC 216 team coordination artifact (cross-teammate visibility, store-owned id allocation, `owner` claim semantics). Bundled `createMemoryTaskListStore` (detached copies both ways); methods may be async (pg is the deployment's half); multi-step read-modify-write sequences serialize per store (module-level chain — separate bundles sharing a store still serialize; cross-process transactionality documented as the store's half). `CodeToolsConfig.taskListStore` passthrough.
1463
+ - **prompt: teammate/coordinator 216 refresh (design/147 §6 D3/D8).** Teammate addendum gains the 216 `team_context` name-vs-agentId guidance (sema-trued: a NAME resolves for running and completed teammates; the id is the fallback address). New `TEAMMATE_TASK_LIST_ADDENDUM` text export (216 task-list paragraph, tool pointers instead of file paths) — deliberately NOT pack-composed: core cannot see whether the mounted family shares a store, a deployment that does appends it. Coordinator role prompt: one named-worker bullet (216 planmode "consider spawning named teammates" ported to the coordinator seat; registered divergence).
1464
+ - **hardening (codex adversarial round 1, 2 high + 2 medium folded):** atomic claim guard `TaskUpdate.ifOwnerIs` (string | null CAS inside the serialized sequence — two teammates can no longer both win a claim; description + addendum teach the guarded form); `TaskListStore.mutate?` transaction boundary (an adapter whose backend outlives one wrapper instance — pg, shared files — owns sequence atomicity there; the in-process chain is the memory-store fallback, not a cross-process promise); delete now prunes reciprocal edges BEFORE removing the target (a mid-prune failure leaves a retryable delete, never permanently dangling edges); `snapTask` deep-clones metadata (nested objects were aliased between stored state and emitted cards).
1465
+ - **hardening round 2 (3 medium folded):** mutate availability is resolved INSIDE the per-store chain (a lazily-installed `mutate` can no longer bypass operations already queued — the chain always serializes, `mutate` adds backend atomicity on top); `owner: null` is the canonical release-to-unowned (empty-string owner rejected — it rendered as ownerless yet failed the `ifOwnerIs: null` claim, a stranded state); metadata narrowed to JSON-shaped at the tool AND bundled-store boundaries (`assertJsonMetadata` — structuredClone would share SharedArrayBuffer backing and throw opaquely on functions/proxies for direct store users).
1466
+ - **design/147 §6:** full 216 delta table (D1-D8): TeamCreate/TeamDelete = no Team primitive (ruling upheld; roster namespace + deployment lifecycle), teammateMode/comms-MCP = shell domain, plan-approval bridge = already covered (plan_review park + tighten-only seam), Frame/FrameRead = watch. Backlog hygiene: RB-25/RB-29 rows marked CLEARED (landed 1.357/1.358; ledger rows were stale).
1467
+
1468
+ ## 1.362.0 (2026-07-21)
1469
+
1470
+ CC 2.1.216 alignment batch (anchor: pretty.js build 2026-07-20, teardown table 2026-07-21).
1471
+
1472
+ - **feat: MCP dropped-tools announce face (CC `mcp_dropped_tools_delta` parity).** New intake gate `mcpToolSchemaProblem` (conservative, deterministic: non-object schema / non-"object" root type / circular — anything a provider might accept rides through) drops ONLY the malformed tool at materialization, fail-open for siblings. Drops are never silent, two faces: operator (`onError(phase:"mcp")` per entry) and model (`mcp_dropped_tools` boundary attachment on the existing `mcpInstructions` opt-in lane, CC qny line shape `"tool" (MCP server "s"): "reason"`, ambient tail, intact-survival consumption like the rest of the announce family). `MaterializedMcp.droppedTools` carries `{server, tool, reason}` (names neutralized at the producer; reasons core-authored). Event registry row added (trust: external).
1473
+ - **honesty note (RB-36):** with the bundled TS MCP SDK, a bad ROOT schema fails the SDK client's own zod at `listTools` — the whole server is skipped (warnings face) and healthy siblings are lost with it (SDK-upstream, ground-truth pinned). NESTED structural garbage passes that zod and reaches the per-tool gate today (e2e pinned: per-tool drop, sibling survives) — the gate is live for the nested class and defense-in-depth for the root class.
1474
+ - **prompt: Skill tool description follows CC's settled A/B winner** (2.1.216 ships the calmer descriptive form as the single string; the "BLOCKING REQUIREMENT"/"NEVER mention" register retired with the losing arm). Structure: definition → when-to-call → param bullets → validity tail; sema-phrased where upstream is CLI-/product-specific (dropped: leading-slash framing, plugin:skill, subagent-run skills, built-in-CLI-commands clause — each per capability honesty). Golden refrozen; sentence-evidence index row updated.
1475
+ - **verdicts (no code):** memory-extraction "no investigation" discipline sentence N/A — sema's consolidation is a single-shot JSON reconcile with no tools, the constraint holds constructively; background-roster dead-record pruning N/A — core never persists OS pids, resume legs use orphaned-task notice + `resumeFacts` honesty + fail-closed reconnect/reopen.
1476
+ - **hardening (codex three rounds, 2 high + 4 medium folded):** the intake gate recursively meta-validates the schema via `validateJsonSchemaShape` (the nested-garbage class — e.g. `properties.x.type: 7` — passes the SDK zod but 400s the whole provider request; root checks alone forwarded it); dropped-tools delivery is a BOUNDED batch (`selectMcpDroppedBatch`, 4KB/frame, always ≥1 entry) so an oversized list drains monotonically across boundaries instead of wedging forever on cap clipping. Round 2: the structural-error reason embeds server-controlled property names — neutralized + bounded (single-line, ≤240 cps, fences defused) at the intake boundary before it can ride the `<system-reminder>` announce; a validator stack overflow on legal-but-absurd nesting (deep `allOf` chains past the Node call stack) is caught and isolated as that tool's drop instead of escaping to the per-server catch (sibling loss); the producer bounds guarantee a single-entry frame always fits one announce frame (no >8KB single-entry wedge). Round 3: the `server` field (caller-trusted but unbounded) gets the same producer bound — frame arithmetic, not trust, is the axis; worst-case bounded single entry pinned under the frame bound.
1477
+ - **test:** mcp-dropped-tools suite (schema-gate quartet + nested-structural sextet, SDK-wholesale-rejection ground-truth pin, attachment render/opt-in pair, oversized-list monotonic-drain pin); registry trust pin now covers the MCP announce pair.
1478
+
1479
+ ## 1.361.0 (2026-07-21)
1480
+
1481
+ The engine-side `sideQuery` verb ([1463]① — cli side-channel governance endgame).
1482
+
1483
+ - **feat: `Runner.sideQuery` / `runSideQuery`.** A one-shot brain-routed utility query for shell/deployment utilities (session titles, recaps, tool summaries, permission explanations …): preserves the REAL request shape — system prompt, multi-turn messages (roles/content blocks unflattened), tool DEFINITIONS — routes through the deployment's brain with the same model resolution as tasks (pool name / role / @mention; role default-chain when absent), and returns honest attribution (resolved pool model id, provider `responseModel`, real `usage` + `usageMissing`, `stopReason`, `errorMessage` on error instead of a throw — pre-stream throws still propagate). This replaces the `POST /v1/tasks` objective-text tunnel whose transport losses ([1463]: role/content-block flattening, forced-tool synthesis, fake-zero usage) motivated the verb.
1484
+ - **contract: deliberately NOT an agent run.** No session, no tool EXECUTION (definitions are exposed; any calls in the reply return as `toolCalls` data for the caller), no policy gate (no engine-side side effect to gate), no TB accounting, no walltime budget, no streaming (v1), no tool_choice (v1 — the brain seam has no such primitive yet; callers keep expressing "answer as JSON" in the prompt, now without transport flattening). Caller-trusted: the server half owns authentication.
1485
+ - **ergonomics:** `SideQueryMessage` relaxes `timestamp` to optional (request shapes, not transcript entries — stamped at intake). New exports: `runSideQuery`, `SideQuerySpec`, `SideQueryResult`, `SideQueryToolDef`, `SideQueryDeps`.
1486
+ - **hardening (codex round, 1 high + 2 medium folded):** degraded-serve attribution — `servedModel` follows the message's own model stamp (a degrading-brain fallback that answered must be billed/audited as itself, not as the routing intent) with the structured `degraded` marker (from/to/reason/chain) exposed; the resolved ROLE's preset systemPrompt applies under task-path semantics (`spec.systemPrompt ?? role preset` — a role configured for strict JSON keeps its instructions); `modelRole` is typed `ModelRole` (an unknown role is a compile error, not a runtime TypeError).
1487
+ - **hardening round 2 (1 medium folded): true REQUEST-shape input union.** `SideQueryMessage` is a dedicated input union — the assistant arm takes `content: string | blocks` with response metadata (`api`/`provider`/`model`/`usage`/`stopReason`) fully optional (requiring them would force callers to fabricate attribution, the exact forging this verb kills), `toolResult.isError` defaults false; intake normalizes to full engine Messages with inert placeholders that never reach the provider wire. Exported.
1488
+ - **hardening round 3 (1 medium folded):** the task-path thinking chain applies in full — `spec.thinking ?? role default ?? model.defaultThinking` (a model configured to think by default behaves identically here and on the task path; explicit `"off"` still wins).
1489
+ - **test: 9 pins** — shape fidelity (system/multi-turn/tool-defs reach the brain unflattened + resolved-model/usage attribution), tool calls return as data (nothing executes), pre-stream throw propagates, empty-messages fail-fast, pure pass-through.
1490
+
1491
+ ## 1.360.0 (2026-07-21)
1492
+
1493
+ CWD case core B-half ([1451]/[1452]/[1461]④): resume workspace-root fidelity + resume-continuity honesty + cwd alignment guard.
1494
+
1495
+ - **feat: `resumeVM` root fidelity — `VmLifecycleOptions.priorHandle`.** The engine passes the CHECKPOINTED `WorkspaceHandle` on the resume leg so the adapter can bind the restored workspace root to the root the task was suspended under (not its current deployment config — a mount-path knob rotated mid-suspend must not land resumed execs/artifacts in the new root). Contract: an adapter that cannot honor the prior root returns its ACTUAL `mountPath`; the engine compares and surfaces a non-fatal `workspace-root divergence` observation via `onError(phase:"config")`. Closes the [1461]④(a) resumeVM contract gap (codex-server high).
1496
+ - **feat: `TaskSpec.envFacts.resumeFacts` — per-lane resume-continuity honesty.** Deployment-stated facts (`processes`/`scratch`: `preserved`|`lost`, plus a free-form `note`) render one honest `# Environment` sentence each, ONLY on a durable-resume leg (a fresh task never claims a resume). Core hardcodes NO lane table and renders NOTHING for absent fields — per [1452]③, a blanket "lost" wording would misinform the model on lanes (ssh/adb) where the state genuinely persists. Union-validated at the JS boundary; `note` sanitized + bounded.
1497
+ - **feat: pwd/cwd explicit alignment guard.** The env probe gains a 9th line (`pwd -P`); prepare canonicalizes BOTH sides through the env's own canonicalizer (symlinked roots are not misalignments) and surfaces a non-fatal `cwd misalignment` observation via `onError(phase:"config")` when the shell's actual pwd differs from the task root — the CF-SANDBOX login-home fallthrough shape becomes observable at task start instead of via stray artifacts. Observation only: initial-value alignment is lane A's job; the sandbox fence stays the hard guarantee.
1498
+ - fs-resolve single-source with per-exec cwd injection confirmed on the server side ([1461]③: exec/execStream/background and fs tools share the same root on all five lanes) — no core change needed, recorded here.
1499
+ - **hardening (codex round, 1 high folded): the RESTORED root re-roots the whole task.** `taskRootPath` was derived from the freshly-minted env / current deployment config BEFORE the restore — an adapter correctly honoring `priorHandle` would then leave every downstream consumer (file-tool fence, prompt cwd, LSP, session-rule policies, subagent inheritance) on the ROTATED root while the workspace content sat at the checkpointed one (a silent root split). After a successful restore the adapter-reported `mountPath` re-roots `taskRootPath`; the park-only leg applies the checkpointed `mountPath` the same way (the workspace persists on the target at that root).
1500
+ - **hardening round 2 (1 high folded): checkpoint path state REBASES on a divergent restore.** When the adapter cannot honor `priorHandle` and the workspace lands at a rotated root, the checkpointed absolute paths (shell `handsCwd`, `readFileState` keys, `activeWorktree` dirs) still named the old root — the shell would start from a root the workspace no longer occupies while the fence points at the new one. A divergence now arms a prefix rebase applied at each restore site, so every path consumer follows the restored root.
1501
+ - **hardening round 3 (1 high folded): separator-family-exact rebase.** `rebaseWorkspacePath` (exported for unit pinning) recomposes as target-root + exactly one separator + relative suffix — a trailing-slash root no longer welds the suffix (`/app/` → `/workspace-v2sub`), bare `/` keeps filesystem-root semantics, and backslash-family roots rebase their sub-paths (mixed-separator tolerated).
1502
+ - **hardening round 5 (1 high folded): a divergent restore under a PENDING APPROVED ACTION fails closed.** The approved args were authored + human-approved against the checkpointed root — internal path-state rebase cannot rewrite tool args (`rm /app/…` would run against a root the workspace no longer occupies, or burn the approval on a fence refusal). Divergence + pending approval → `resume.env_failed` → the existing reopen leg (resolved→pending) keeps the approval retryable until the adapter honors `priorHandle` or the knob rolls back (pinned end-to-end: fail → reopen → knob-back retry executes exactly once at the right root). The guard keys on the RESUME OUTCOME (`tool_approval` × an `allow` winner — `PrepareResume.executesApprovedAction`), not on the checkpoint kind: a DENY winner executes nothing and resolves under the diverged root without wedging (round 6); non-approval suspends (resource_limit etc.) likewise proceed under the rebase.
1503
+ - **hardening round 4 (2 high folded):** the rebase contract is POSIX-ONLY — every remote lane's `mountPath` is a Linux container path; a backslash anywhere marks the value outside the domain and the path passes through UNCHANGED (un-rebased is observable via the divergence observation; wrongly-guessed Windows drive/UNC recomposition would silently corrupt cwd/read-state). And the divergence/misalignment `onError` observations are try/catch-isolated — a throwing observer sink must never turn an already-consumed (resolved) checkpoint into a terminal failure (observe-only means observe-only).
1504
+ - **residuals ledgered (RB-35, adversarial round 7, LOW):** two documented tails of the narrow "rotation window × non-honoring adapter × durable suspend" pathology — canonical-vs-logical root prefixes can miss the rebase on symlinked mounts (observable degradation: cd failure / fence refusal / read-state miss, never a silent wrong write), and the fail-closed guard keys on the allow winner before zero-execution branches (walltime exhaustion, tightened rules) are known (a permanent rotation can loop fail→reopen with an explicit remedy in every error). Both wait on a real deployment shape.
1505
+ - **test: 13 new pins** — priorHandle rides resumeVM, mount-rotation divergence observation (non-fatal), POSITIVE honor leg (prompt cwd = checkpointed root across a rotation, zero divergence noise), resumeFacts render trio (honest sentences / hostile note / fresh-task-never), resume-leg e2e gating, cwd-alignment guard both ways (honoring env silent; dropping env observed, task proceeds).
1506
+
1507
+ ## 1.359.0 (2026-07-20)
1508
+
1509
+ Approval-edit object arm ([1458]/[1462] ctrl+g leg) + workflow journal honest-door teaching ([1459]).
1510
+
1511
+ - **feat: `OnAsk` object arm — `{ allow, updatedInput? }`.** A live approver may now return a verdict PLUS an operator EDIT of the presented args (whole-replacement form, e.g. "edit script in $EDITOR"): `resolveAsk` folds the object arm STRICTLY (`allow === true`; a malformed truthy never reads as approval) and carries the edit as the resolved decision's own rewrite, which the gate applies through the existing rewrite channel (schema re-validation included). A deny edit is dropped — it never executes anything. Back-compat: boolean/truthy folding for the existing arms is unchanged; new `AskOutcome` type exported.
1512
+ - **fix: inherited-parent-policy lanes honor the live edit.** Both ancestor-approver adapters previously returned verdict-only — an operator edit would have been silently discarded and the UN-EDITED args executed (the exact consent break of the cli case, one layer deeper). A live edit (authored against the presented, fully-cascaded shape) now supersedes the cached/ask rewrite; the trailing narrowing layers still re-check the final args (tighten-only holds).
1513
+ - **fix: workflow diagnostics journal teaching carries the fallback.** A FILE-path journal locator often points outside the agent's readable roots (the door was taught without the key — [1455] forensics friction). The path form now carries "if Read is denied there, use the TaskOutput route above instead" inline; API-route locators are untouched. Chosen over widening read roots to the journal dir (sibling runs' journals live there — cross-scope read hazard).
1514
+ - **hardening (codex adversarial ×4 rounds, 5 high + 2 medium folded): the approved edit re-runs the FULL restriction chain, and a re-check ask RE-ROUTES.** An operator edit is new un-adjudicated input — before execution the gate re-runs BOTH boundaries on it: the **PreToolUse hook** (a hook deny stands — its boundary is not editable-around; a hook ask on the edit denies fail-closed; a hook rewrite applies in phase-1 order) and the **policy** (deny stands — an edit retargeting into a protected path is blocked, not merely schema-checked). A re-check **ask is NEVER satisfied by the prior approval** (r4: the approval router routes on the REQUEST — including args — and the edit changed them; a manager's edit into security-officer territory must reach the security officer): it re-enters the normal ask resolution with the edited args and the new gate's own message presented, a further edit re-runs the chain (identical bytes short-circuit as approved-as-is), capped at 3 rounds → fail-closed deny. A policy rewrite of the edit (redaction) is the final shape (B-1 posture). The inherited-parent-policy lanes run the same re-route loop at the EMITTING ancestor policy under the ancestor's own frozen approver.
1515
+ - **hardening: exact, fail-closed alias isolation at the approval boundary.** The approver receives a `structuredClone` of the presented args and the returned edit is cloned on intake; unclonable values deny (presented side before the approver is invoked; edit side on return — no by-reference acceptance). Shared memory is rejected outright: `structuredClone(SharedArrayBuffer)` duplicates the object but ALIASES the memory (TypedArray views likewise) — and the scan runs on the CLONE, after it (r4: scan-then-clone is TOCTOU-able by a stateful getter; clone-first also lands a throwing getter in the fail-closed catch instead of faulting the gate).
1516
+ - **hardening round 5 (1 high + 1 medium folded): shown == executed, by construction.** A plain approval EXECUTES the presented snapshot (`ResolvedAsk.presentedInput`), not the original args object — a stateful getter or an external alias holding the original can no longer make the executed action differ from the approved one, and non-enumerable smuggled members never reach execution (the snapshot is the execution input, routed through the standard schema re-validation). The r4 identical-edit short-circuit is REMOVED (a lossy equality that collides distinct values would drop an explicit operator edit — any further edit re-runs the chain; the round cap bounds it). Scope note: the snapshot execution binding lives at the MAIN gate (the execution boundary); the inherited-lane arbitration adapters stay verdict/edit-only — emitting the snapshot there re-enters the shared-instance final-form refresh (fold contract), and the child's own gate carries the binding.
1517
+ - **hardening round 6 (1 high folded): two detached clones at the approval boundary.** The approver's view and the retained execution snapshot are SEPARATE clones — an approver that keeps its `req.args` reference and mutates it after returning (a queued microtask runs before the await continuation) only ever touches the disposable view; the retained snapshot is what executes. Inherited-lane scope note ledgered RB-34 (LOW, non-blocking per adversarial round 6).
1518
+ - **test: 23 new pins** — resolveAsk object-arm quartet + alias isolation + unclonable duo + shared-memory duo + getter duo + plain-approval snapshot, gate e2e (edited args execute; deny edit executes nothing; edit into policy-denied territory blocked; re-check ask re-routes with edited args and executes on second approval; escalated ask reaches the router with its own message + edited args; stubborn per-round edits hit the cap; hook-boundary edit blocked; post-approval mutation of the original args never reaches execution), inherited-lane edit re-check both ways, and the locator-teaching caveat split (path vs API route).
1519
+
1520
+ ## 1.358.0 (2026-07-20)
1521
+
1522
+ design/150 S2 — the announced-listing snapshot rides every compaction (bounded-tail safety).
1523
+
1524
+ - **feat: compaction restatement carrier.** Each compaction restates the current announced-listing snapshot in its own `details.announcedListings` (same-CAS with the new baseline, mirroring the `promptEpoch` restatement) — a bounded-tail floor that cuts the standalone `announced_listing` entry still recovers the announced state from the compaction carrier. `getAnnouncedListing` honors BOTH carriers (nearest wins; a pre-S2 compaction without the key is skipped). Import door: a present-but-invalid restatement is rejected fail-closed; absent passes untouched (zero migration).
1525
+ - **honest bounds (codex round, 2 medium, ledgered RB-33):** the carrier read degrades to the lower seed rungs (duplicate re-announce, never a loss) when a bounded-tail floor cut every carrier; the cross-instance restatement/leaf window is documented (regenerable state, conflict-guarded critical path).
1526
+ - **test: 3 new pins** — compaction carrier wins as nearest, pre-S2 compaction falls through to the older entry, import gate accept/absent/reject. Plus the [1455] tool-lane resume probes (prefix replay through the MOUNTED Workflow tool, explicit + inherited-model lanes) land as permanent pins.
1527
+
1528
+ ## 1.357.0 (2026-07-20)
1529
+
1530
+ design/150 — the listing announced state becomes a first-class session entry (RB-25 endgame; RB-29 closed).
1531
+
1532
+ - **feat: `announced_listing` typed session entry.** The announced name-sets (agent roster / skills / model catalog) persist as a SNAPSHOT entry — `Session.appendAnnouncedListing`/`getAnnouncedListing` (nearest-on-branch read, malformed entries skipped), written best-effort at every listing commit point (boundary bundle survival, first-frame delivery). The resume-seed ladder gains a FIRST rung: entry (structural read — works on plain sessionId continuations with no checkpoint) → checkpoint mirror → transcript probe (both kept for pre-entry sessions; zero migration). Rung-pure seeding: agents and models come from the same rung (no fresh-roster/stale-catalog mixing).
1533
+ - **fix (RB-29 closed): deferred-roster legs fall to the entry** when the checkpoint mirror is absent (plain continuation / pre-c209-C legacy checkpoint) — the four-condition narrow tail (legacy checkpoint × defer leg × compaction × re-suspend) dies: announced state now survives structurally instead of via text probe of frames a compaction may have cut.
1534
+ - **contract: `normalizeAnnouncedListing` single shape gate** (bounded: ≤500 names × ≤200 chars) shared by the read walk and the import door; import-validate rejects a structurally invalid entry fail-closed (same posture as `prompt_epoch`); `SessionApi` carries both methods.
1535
+ - **hardening (codex round, 3 findings folded):** entry appends are AWAITED at all three commit sites — `appendTypedEntry` advances the session leaf under the optimistic lock, so a fire-and-forget append could win the CAS against the next critical message write (the entry's own failure stays swallowed); the seed-ladder order puts the CHECKPOINT MIRROR first on durable resume (cut atomically with the checkpoint leaf) with the entry as the second rung (cp-group-absent continuations) and the transcript probe last; and the public `appendAnnouncedListing` goes through the same shape gate as the import door (invalid input throws `invalid_entry`; only the normalized copy persists — no self-generated non-importable sessions).
1536
+ - **test: 12 new pins** — normalize accept/reject, append/get roundtrip (last-wins), import door both ways, entry written on announce, RUNG counterfactual proof (transcript stripped of engine stamps → entry alone dedups; agents + skills arms), malformed-entry skip with probe fallback intact, and the three fold pins (append-gate reject, priority documentation, serialized-append continuation).
1537
+
1538
+ ## 1.356.0 (2026-07-20)
1539
+
1540
+ design/149 — workflow agents join the BackgroundChildEvent family ([1422]③ ruling a; TOOLS-PERFECT).
1541
+
1542
+ - **feat: process-level observation lane for workflow-spawned agents.** Every `ctx.agent`/`ctx.agentStream` child mints a synthetic `wa*` row (`wa` + 16-hex of sha256(runId:callKey) — stable within a run, per resume leg) on `RunnerDeps.onBackgroundChildEvent` — the SAME process-anchored observer background subagents use, so frames survive host-run settlement by construction (the [1419]③/[1414]#2 frames-all-missing shape dies at the root). Stations: spawn at concurrency-slot acquisition (both lanes), tick mirrored from the child's `task_progress` (taskId=`wa*`, `progressTaskId`=child uuid), terminal on all five settle legs (agent success/catch, stream success/catch, replay) + a run-finalize backstop that closes any still-live row as failed ("abandoned by run finalize") — no dangling rows.
1543
+ - **contract: `BackgroundChildEvent` two-domain row key + `workflowRunId` annotation.** `taskId` prefix is the domain discriminator (`a*` registry/fleet-footer lane; `wa*` read-face row — NOT addressable via TaskOutput/TaskStop; per-agent read face = the workflow's agent_runs projection + journal API). Attribution per [1432]② ruling b: `parentTaskId` = the HOST task id (task domain — parent resolvers that only forward resolved rows keep the linkage); workflow grouping rides the new `workflowRunId` field (all three kinds); `owner` = the `w*` runId, `sessionScoped` false. Display contract per the CC anchor ([1433]): CC's footer renders only the workflow aggregate row — `wa*` rows must not enter the footer fleet tree (shells filter on the prefix).
1544
+ - **wiring: `RunWorkflowToolDeps.onBackgroundChildEvent`** threaded prepare-task → run-workflow-tool → `startWorkflow` (opt-in; absent sink = zero overhead). A throwing observer never faults the run. Replay legs re-mint per-leg rows marked `(replayed)`.
1545
+ - **hardening (codex ×2 rounds, 4 findings folded):** late `task_progress` after a settled row emits nothing (terminal is always the row's last frame); a sync stream-startup failure closes the row immediately with the real reason (no phantom running row); ticks keep nested attribution (`progressParentTaskId` + the frame's own name); and the terminal summary goes through the standard redact-then-bound egress exit (a raw provider/startup error can carry credentials — never a bare slice). The tick/forward wrapper installs ONLY when a BCE sink is configured — sink-less runs keep the exact original `onForwardEvent` identity.
1546
+ - **test: 12 new pins** — frame sequences (spawn×2/tick/terminal×2, distinct stable ids), ruling-b attribution, no-parent honesty, failed settle (no double terminal), replay marker, opt-in/throwing-sink contracts, all four hardening folds, and the full mount-lane thread (RunnerDeps → mounted Workflow tool → `wa*` frames).
1547
+
1548
+ ## 1.355.0 (2026-07-20)
1549
+
1550
+ TOOLS-PERFECT convergence car II — the steerable-journal case closed ([1419]②/[1422]②), the CC completion-text anchor ([1423]#2/[1427]), and the [1426] batch.
1551
+
1552
+ - **fix ([1422]②): the STEERABLE agent lane journals.** With `onWorkflowAgentSpawn` set (every steer-capable deployment), `agent()` routes through `agentStream` — which recorded NOTHING, so the journal read empty on every workflow, silently (the [1419]② zero-write case; both sides' probes were right — the divergence was this documented interaction, now load-bearing since the journal read-face shipped). Both stream settle legs now append (success + synthetic failed entry on the catch leg). RECORDED but NOT REPLAYED: `resumeFromRunId` still diverges on a stream call — steer input is not deterministic and a cache would replay an operator-steered result. Pinned with a steer-form rig (handle emitted + append fired).
1553
+ - **feat ([1423]#2/[1427] CC verbatim): completion notifications wear the CC wrapper** — `Agent "<description>" finished · 2s` (completed→finished, killed→stopped, other statuses keep the honest word; elapsed tail `· Ns`/`· NmSSs`). All eight minting sites: bg both legs, fork-bg both legs, revive both emits + both notifies (revive form: `Agent "…" (resumed) finished · 2s`). The resume marker is out of user-facing text entirely ([1425]#2) — it stays a forensics value (spawn-frame description, ledger key).
1554
+ - **diag ([1422]② forensics): `SEMA_DEBUG_WORKFLOW_JOURNAL=1`** logs the journal append station's three-state verdict (SKIP no-store / APPENDED / APPEND-ERROR) — a mis-threaded store is a five-minute diagnosis instead of a cross-repo case.
1555
+ - **test: journal-append-on-mount pin + steerable-journal pin** (the [1419]② refutation evidence, permanent).
1556
+
1557
+ ## 1.354.0 (2026-07-20)
1558
+
1559
+ TOOLS-PERFECT forensics car — two confirmed minting-side fixes from the campaign's ghost/teaching probes ([1413]②b / [1414]#3); [1413]②a refuted by e2e probe (deep validation confirmed on five schema shapes).
1560
+
1561
+ - **fix ([1414]#3): a subagent's uuid lane gets ONE terminal `task_progress` tick at settle.** Per-turn ticks fired with a hardcoded `status:"running"` and nothing ever said "finished" on that lane — a workflow child (no bg_notification, no BackgroundChildEvent) rendered running→removed, never showing a terminal state. The settle now emits a final tick (`status` value space additively widened to `"completed" | "failed"`; timeout/blocked fold to failed; a suspended durable pause emits nothing — the honest running face), forwarded through the same recursive display-sink chain. A consumer switching on `"running"` keeps its exact old behavior.
1562
+ - **fix ([1413]②b): ToolSearch answers "Already available — call it directly" for a miss that IS a live callable tool.** The generic not-found ("no deferred tool has this exact name") read as absence — the observed model treated a mounted Workflow as unavailable and recovered only by guessing. New `mountedNames` closure (harness list minus unactivated placeholders) splits the miss note per name; truly unknown names keep the not-found text.
1563
+ - **forensics ([1413]②a, refuted): output-schema validation is DEEP.** E2e probes (real Runner, scripted submit of `n:4` against `minimum:5,maximum:3`) across five schema shapes — direct, no-type-keyword, anyOf-nested, integer — all reject at the harness (`Value.Check` full JSON Schema semantics), the task fails, `structuredOutput` never mints. The "required+type only" shallow-validation claim does not hold on the core engine face; the observed pass-through is being re-scoped with the reporter.
1564
+
1565
+ ## 1.353.0 (2026-07-20)
1566
+
1567
+ CC workflow-notification convergence car ([1409] first-person CC ground truth; TOOLS-PERFECT campaign, cli as PM per [1410]).
1568
+
1569
+ - **feat ([1409]②): the workflow completion notification's `usage` is the CC `<usage>`-parity structured block** — `agent_count` / `agents_done` / `agents_error` / `agents_empty_result` / `subagent_tokens` / `tool_uses` / `duration_ms` on top of the existing run stats, so "which agents failed" is zero-query knowledge in the notification itself. Honest keys only: sema has no user-skip semantic, so no `agents_skipped` is forged; `agents_empty_result` counts completed agents with empty recorded output (the "looked fine, returned nothing" trap).
1570
+ - **feat ([1409]②b): `TaskNotificationPayload.diagnostics`** — a new engine-minted slot rendered as `<diagnostics>` in the model-facing XML: the "read the per-agent rows before assuming an empty/unexpected result" teaching + the journal coordinate + the full `resumeFromRunId` recovery command. Minted on all four workflow settle legs (completed + failed, both notifier tiers). Producers must mint it from engine values only — never model-influenceable text.
1571
+ - **feat ([1409]③): `WorkflowJournalStore.locator?(runId, scope)`** — additive contract face for the diagnostics coordinate, one slot two forms: `FileWorkflowJournalStore` returns the run's on-disk jsonl path (CC "Read journal.jsonl" local form); a service store returns its API route (cloud form, the [1402] read-face). Absent ⇒ diagnostics teaches only the always-live TaskOutput route.
1572
+ - **teaching ([1409]①): the launch receipt's note carries the "before assuming empty" line** — a terminal TaskOutput reply carries per-agent rows (+ the journal coordinate when the store advertises one).
1573
+
1574
+ ## 1.352.0 (2026-07-20)
1575
+
1576
+ PAIR-REVIEW-01 repair car (core half) + the workflow diagnostic dead-end batch ([1400], clay dogfood 019f7c95).
1577
+
1578
+ - **fix ([1371]②, PAIR-REVIEW C-2): the revive spawn frame replays the row's parent linkage.** The registry now persists `parentTaskId`/`parentSessionId` at both register sites (the same [1344]②-clamped values the original spawn frame carried) and the revive projection echoes them — a SendMessage-revived child nests under its parent in a fleet view instead of rendering as a top-level orphan. Zero server changes needed for the happy path; tombstone-inheritance stays a server hardening.
1579
+ - **feat (PAIR-REVIEW C-3 half): `BackgroundChildEvent` terminal frames carry `seq`** — the stop-cycle number, mirror of `TaskNotificationPayload.seq` (same settle, same X3 pre-resumable snapshot). The fleet lane is minted from THIS frame (consumers pick, never spread), so multi-cycle events of one taskId were key-indistinguishable on the wire. Honest downgrade preserved: no retain entry ⇒ no seq (a single-cycle child never forges a period); the fork durable-branch leg stays intentionally seq-less. Wire prerequisite for the server's bg_notification seq leg.
1580
+ - **fix (PAIR-REVIEW lane-2 #4): revive-cycle terminals carry `stoppedBy`.** Both revive settle legs + the resume notification frame now stamp the registry's stop attribution on a killed cycle (first-run legs already did); a completed cycle forges nothing.
1581
+ - **feat ([1400]a): the TaskOutput workflow projection inlines per-agent rows** (`agent_runs`: ordinal/label/phase/status/errorCode/fenced error first-line/elapsedMs/replayed) — same shape while running and at terminal. The counts-only face (`agents:{done,total,failed}`) named no failed agent and no reason, so the observed model behavior was a blind re-run. Bounded at 60 rows; on overflow every non-completed row survives and the trim is disclosed.
1582
+ - **fix ([1400]b): the failed-path workflow summary hands over the diagnostic route** — `TaskOutput("<runId>")` for per-agent rows, `resumeFromRunId` to replay completed agents from the journal. Redaction posture unchanged (the tail carries only the engine-minted runId).
1583
+ - **fix ([1400]c): AgentTranscript teaches the route for a workflow run id** (`w`+16hex, or a workflow-typed registry row) instead of a bare not-found — new `details.error: "workflow_id"`.
1584
+ - **diag (PAIR-REVIEW backlog): the fork source-miss receipt's details carry `forkLane` (`host` | `child-runner`)** so the "source session not found or fork unavailable" two-cause text is machine-splittable.
1585
+
1586
+ ## 1.351.0 (2026-07-20)
1587
+
1588
+ Observer-lane completeness car: revive projection ([1358]) + the [1347] residual triplet. One adversarial review round folded (2 HIGH + 1 MED).
1589
+
1590
+ - **feat ([1358]): a SendMessage resume re-emits the spawn→tick→terminal event family.** The revive cycle was invisible on the process-level observer lane (`onBackgroundChildEvent`) — a fleet view's tombstoned row never came back. Now a successful registry revive emits `kind:"spawn"` (taskId = the ORIGINAL `a*` row key, sessionId = the retained session — spawn-after-tombstone = row revival) BEFORE the stream starts (spawn strictly precedes ticks), mirrors `task_progress` as ticks, and re-terminalizes on both settle legs. New `SendMessageToolOptions.onBackgroundChildEvent` (mount-closure seat, like `notify`); prepare-task wires the deployment sink automatically. No revived row ⇒ no frames.
1591
+ - **fix ([1347]②): `additionalDirectories` + `envFacts` inherit down the delegation tree.** Deployment properties (fs-fence widening, sandbox facts) now ride the trusted ctx into every child spec (copy-at-spawn, same seat as `principal`) — a delegated child's Write to the advertised scratchpad passed `path_not_in_root` only in the parent before. E2e pinned.
1592
+ - **fix ([1347]①): every bg child's session id is fixed at spawn (pre-mint) and rides the spawn frame.** The spawn frame now carries the uuid-domain `sessionId` anchor from birth (fork-lane parity) — closes the residual window where a fast child finished before its first tick left no joinable anchor. Companions: the recorder locks unconditionally; the reject leg's terminal frame + notification finally carry `sessionId` too ([1336]转② parity — historically unknowable there); a THROWN throwaway bg run now releases its session (was leaked to the TTL reaper).
1593
+ - **fix ([1347]③): a not-yet-existing advertised scratchpad is MATERIALIZED at prepare.** Remote deployments create the scratchpad lazily (in-sandbox mkdir at first exec) — prepare saw a ghost and narrowed, so the fence never admitted the advertised dir. Prepare now creates it (portable: `writeFile` marker with parent-dir creation) and re-canonicalizes; a dir that STILL cannot exist narrows exactly as before (fail-closed preserved).
1594
+ - `TaskRegistry` rows persist `agentType` at registration and `getAccessibleTask` echoes the full row metadata (`sessionScoped`/`description`/`name`/`agentType`/`owner`/`scope`) — the revive projection reproduces the ORIGINAL spawn frame's metadata (review F3: a revived row must never go blank; revive ticks honor the every-tick `agentType` contract).
1595
+ - Review hardening: the bg spawn frame is emitted IMMEDIATELY after row registration — session id minted first, retention (with its awaitable store pin) moved after, so a stalled pin can no longer leave a registered `a*` row with no spawn frame (F1); scratchpad materialization is gated on a definitive `not_found` and uses ONLY the atomic exclusive-create primitive — a concurrently planted marker (symlink or not) is never written through, and envs without the primitive narrow as before (F2).
1596
+
1597
+ ## 1.350.0 (2026-07-20)
1598
+
1599
+ Spawn-fork id/store addressing pair ([1345]/[1344]②), hardened through two adversarial review rounds (3 HIGH + 1 HIGH + 1 MED folded) plus an independent second-reviewer pass.
1600
+
1601
+ - **fix ([1345]): fork works on split-runner deployments — source rides the HOST store.** New `ToolExecuteContext.hostSessionFork` (Runner-injected): the fork arm branches the session through the store of the Runner RUNNING the task, not `opts.runner` (a deployment's child-execution runner, whose store may never have held the session — every fork missed with "source session not found"). Single-runner deployments keep the byte-identical `opts.runner.sessions.fork` fallback.
1602
+ - **Zero-arg capability shape:** source session id AND principal are bound in the Runner's closure at injection — a mounted third-party tool cannot copy a foreign session or choose a principal. The returned handle carries a branch-scoped `release`, so failure cleanup goes through the store that owns the branch.
1603
+ - **Fail-closed child execution:** both fork lanes (sync + bg) now pass `requireExistingSession: true` — if the two runners do not share session backing, the child run fails LOUD (`resume.session_not_found`, "refusing a silent fresh run") instead of silently running an empty session under the forked id. Split-runner deployments must share session backing (same store instance or same durable backend). Pinned with two REAL Runners over real stores: shared backing → child inherits the parent transcript; split backing → loud failure, child brain never runs.
1604
+ - The sync fork-run-failed receipt now carries `isError: true` (spawn-failure family consistency).
1605
+ - **Stillborn-branch cleanup (review round 2):** a fork child that NEVER ran (`resume.session_not_found` — the split-store fail-closed signature) releases the host-side branch in both lanes and is marked non-resumable; repeated split-store forks no longer accumulate unusable copies of the parent transcript. A child that RAN and failed keeps the durable branch (§6 forensics). Pinned against the real host store.
1606
+ - **fix ([1346]②): every `tick` frame carries the row's resolved `agentType`.** Server-side fleet TYPE columns forward tick frames (K-7), where only the design/99 display `name` (description-backed) existed — the TYPE column showed the description. `agentType` now rides spawn AND tick (row-level: this spawn's resolved type; nested origin stays on `progressTaskId`); `name` remains a display label and was never a type field.
1607
+ - **`BackgroundChildEvent.parentSessionId` is now a declared field** (review round 2): the always-on parent linkage the spawn frames were already emitting rode an undeclared spread — typed consumers could not see it while `parentTaskId` became conditional.
1608
+ - Second-reviewer pass (independent, real-store probes; 3 findings folded): the fork availability gate now accepts EITHER fork face (`ctx.hostSessionFork` or the child-execution store — gating only on the latter refused forks the host face fully serves); the observer honesty clamp gained its own pin (fallback shape omits `parentTaskId`, `parentSessionId` compensates); a stillborn bg fork's terminal frame no longer advertises `transcriptId` for the just-released branch (`sessionId` stays as the row-join anchor).
1609
+ - **fix ([1344]②): observer spawn frames stop laundering a session id into `parentTaskId`.** The `spec.taskId ?? sessionId` fallback made `BackgroundChildEvent.parentTaskId` carry a SESSION id when no task id was declared — an orphan pointer no fleet view could resolve. The honesty clamp applies ONLY to the observer sinkEmit spawn frames (a deliberately declared `taskId === sessionId` is the same cross-domain shape and stays suppressed; `parentSessionId` is the always-on linkage). Internal delegation state (`RunInternals.parentTaskId`, the extraTools factory context) keeps the CANONICAL host task id — it is the child-ness bit itself (`isSubagent`, SendMessage sibling resolution, roster/registry owner keys) and is never clamped.
1610
+
1611
+ ## 1.349.0 (2026-07-19)
1612
+
1613
+ Review-batch repair car — six field-anchored fixes from the cross-repo review night ([1331]-[1339]).
1614
+
1615
+ - **fix ([1331]③): returned spawn-failure receipts carry `isError`.** `AgentToolResult.isError?` additive flag, honored by the loop; the whole "Sub-agent not started" family (21 sites: fork failure/source miss/unknown type/invalid name/worktree failure) marked. A non-thrown failure marked non-error walked the shell's success rendering and vanished (the invisible-card chain).
1616
+ - **fix ([1339]): `envFacts.scratchpadDir` is admitted into the fs root fence.** The env block TOLD the model to use the scratchpad while Write/Edit rejected that very path (`path_not_in_root`) and Bash could write there — a contradictory gate that taught bypassing the tools. Fail-closed canonicalization (a missing dir narrows, never admits raw); e2e pins both arms.
1617
+ - **fix ([1336]A/[1338]): spawn observer frames carry `agentType`** (the RESOLVED type — general-purpose/Explore/roster name/fork — never the description) **and the named-spawn `name`**; the fleet TYPE column lights up server-side with zero server changes. The fork failure-leg completion notify now carries `sessionId` ([1336] 转②: without it a failed child's fleet row never settled). Pre-existing duplicated `parentSessionId` spread deduped.
1618
+ - **teaching ([1316]③/[1334]{cli}③)**: the Workflow card routes the verification instinct into the FIRST script (execution-backed VERIFY stage; document claims are hypotheses); the SendMessage not-found text teaches the CC-tested fg lifecycle rule (completed fg agents are gone by design on both sides) without an existence oracle.
1619
+ - **forensics ([1337])**: the fork source-miss receipt's details now carry `sourceSessionId` so the three-repo store-identity triage reads from the frame. Suite green, tsc 0, deepseek live 9/9.
1620
+
1621
+ ## 1.348.0 (2026-07-19)
1622
+
1623
+ [1310] verdict (clay: option b) — the Workflow card states agent()'s return shape explicitly; divergence registered.
1624
+
1625
+ - **fix (result:null family root)**: the card left agent()'s RETURN SHAPE unstated and the CC-trained prior filled the gap ("agent() returns the validated object") — scripts read `r.bugs` on a TaskResult → undefined → null results end-to-end (clay live case). The card now states the sema contract explicitly: agent() ALWAYS returns the full TaskResult; with schema the validated object rides `r.structuredOutput`; a schema'd prose completion THROWS. Declared divergence `workflow-agent-returns-taskresult` (cc-parity ledger #37): the sema shape stays because its failure semantics are strictly richer (r.status/errorCode/prose-carrying throw vs CC's bare null) and unwrapping only on schema-success would tear success/failure into two shapes. Doctrine (clay): copy CC by default, state customizations explicitly.
1626
+
1627
+ ## 1.347.2 (2026-07-19)
1628
+
1629
+ Release-engineering fix car for 1.347.0 (content unchanged; that car's code is what ships here).
1630
+
1631
+ - 1.347.0 was CI-blocked on an unfrozen export-surface fixture (`redactSecrets` unregistered); 1.347.1 fixed the fixture but omitted its own CHANGELOG header (P-17 gate). Root cause both times: the local release gate ran through a piped tail that swallowed vitest's exit code (false green) — sentinel discipline fixed (explicit exit-code capture; gates re-run at the final commit state).
1632
+
1633
+ ## 1.347.1 (2026-07-19)
1634
+
1635
+ CI-blocked (no CHANGELOG header — P-17). Export-surface fixture registration for `redactSecrets`; superseded by 1.347.2.
1636
+
1637
+ ## 1.347.0 (2026-07-19)
1638
+
1639
+ Redaction tiering ([1295]② — the assertion-killer) + workflow schema teaching face. CC 2.1.212 alignment with a declared secret-arm superset.
1640
+
1641
+ - **fix: same-principal faces stop redacting paths/URLs.** `boundedRedactedSummary` (workflow agent outputs/results/errors flowing back to the launching model+script; run rows behind the scope-gated observe API) now applies `redactSecrets` only — the old blanket `[redacted-path]`/`[redacted-url]` strip killed the cross-agent assertion pattern ("return the path of the file you wrote") while protecting nothing on faces whose isolation is ACCESS CONTROL. CC redacts nothing here; the secret arm stays as a declared superset. Three host-error sites in the Workflow tool (script-path read failures) join the tier — their paths are the HOST'S own error text.
1642
+ - **`redactSecrets` rides the centralized `scrubSecrets` scanner** (codex HIGH: a second hand-rolled vocabulary WAS the bypass — SAS `sig=`, JWTs, sk-prefixed path tokens all slipped it): prefixed tokens, JWT, PEM, Bearer, `secretKey=value`, plus two URL arms that matter once URLs survive — userinfo credentials stripped to the LAST `@` of the authority (a first-`@` regex left `p@ssword` half-visible and let an empty-username form escape entirely, codex HIGH), and signed-URL params (`sig`/`signature`/`sas`/`code`) redacted by key.
1643
+ - **External faces keep the FULL strip** (codex HIGH): `untrustedEgressForHuman` (human display; URL stripping also disarms exfil-lure links) and the cross-replica `WorkflowCompletionNotifier` payload (its contract promises host paths never escape) still apply `redactHostLeaks`. `subscribeWorkflow` remains in-process/by-runId — a deployment exposing it over HTTP scope-gates first (as `getWorkflowRun` does).
1644
+ - **teaching ([1295]①④)**: the Workflow tool card now states standard-JSON-Schema union support (`{"type":["string","null"]}` / `anyOf` — typebox handles both, verified) and that cross-agent assertions belong in the SCRIPT body (plain JS between stages). Suite green, tsc 0, deepseek live 9/9.
1645
+
1646
+ ## 1.346.0 (2026-07-19)
1647
+
1648
+ Config catalog (clay ruling) — the cross-client configuration scale becomes one shared, observable contract; RB-23 roster verdicts ride along.
1649
+
1650
+ - **feat: `describeConfigCatalog()`** (`sema-config@1`) — the machine-readable recommended scale for cross-client task-launch knobs (limits/budget/delegation/session families). Recommended values are IMPORTED from the live engine constants (zero hand-copy; pinned by test) — the answer to the same knob carrying five different values across five layers (maxTurns: 1000/80/500/200/env) with no layer aware of the others. Clients inherit recommendations instead of hand-writing numbers; changes to the baseline are announced on the shared blackboard (living scale, deliberately evolved).
1651
+ - **feat: `TaskSpec.configOverrides`** — declared host-layer overrides (`{key, value, reason}`, host-namespaced keys allowed). Advisory observability metadata: never changes an effective value, but rides the manifest so an env cap / adapter flag / harness default stops being invisible. Bounded + sanitized (32 entries, 200 chars, control chars stripped) before touching the trace stream (codex).
1652
+ - **feat: `config.assembled` trace** — emitted once after `prompt.assembled`: the run's EFFECTIVE task-launch config with per-field provenance (`default` / `spec` / `derived` / `host-declared`) + `overrideReasons`. "Who set 80" becomes a trace read. The manifest mirrors ENGINE truth (codex): `maxOutputTokens` reports the model's own cap as `derived` (the real fallback), a customized `deadlineNudge` object reports `spec`, and the cross-slice allocations (`totalWalltimeSec`/`totalBudgetUsd`) ride the effective face.
1653
+ - rides along — RB-23 roster verdicts (core half): release semantics settled (row lifetime follows durable-session ADDRESSABILITY — deliberately no terminal/retain-evict wiring; deployment session-GC owns `releaseAgent`) + `RosterGcOptions.maxAgeMs` bounded-staleness backstop on both bundled stores (expired = miss on read, opportunistic write sweep; default no TTL). Also: removed a dead duplicate `prompt.snapshot_changed` member from the trace union. Docs: `docs/CONFIG-CATALOG-GUIDE.md`. Suite green, tsc 0, deepseek live 9/9.
1654
+
1655
+ ## 1.345.0 (2026-07-19)
1656
+
1657
+ Per-call auth REPLACES construction-time auth ([1282] verdict a — closes the multi-model 401 family for real).
1658
+
1659
+ - **fix: a construction-time `Authorization: Bearer <boot token>` no longer overrides a per-call per-model `apiKey`.** Both brains' `buildRequest` now strip every auth-bearing header (case-insensitive `authorization` / `x-api-key`) from the merged header bag when a per-call `options.apiKey` rides in, then hard-lock the provider-native credential (anthropic `x-api-key`, openai `Bearer`). Previously the boot header survived the merge and anthropic-compat endpoints prefer Bearer over `x-api-key` → 401 on a correctly-routed per-model URL even after 1.344's key forwarding ([1275]/[1281] fence, exact match). The openai lane had the sibling defect: a capital-A construction `Authorization` rode as a case-variant duplicate of the hard-locked lowercase form (fetch folds both onto the wire).
1660
+ - **Contract** (per-call auth = full replacement): `{apiKey}` → provider-native credential, stale auth stripped; `{headers}` only → prior behavior, untouched (header-only auth flows like `ANTHROPIC_AUTH_TOKEN` keep working); `{apiKey, headers}` → non-auth headers merge, auth rides the key. Construction-time dual-auth pairings (config `apiKey` + config `Authorization`, e.g. gateway fronting) never trigger the strip — a per-call dual-auth resolver returns explicit `{headers}` without `apiKey`.
1661
+ - **Empty-string per-call key fails CLOSED** (codex): the strip gates on presence (`!== undefined`), the hard-lock on truthiness — a degraded secret produces a clean unauthenticated 401 that attributes the misconfig, instead of silently riding the boot credential (the exact pathology this release fixes). Suite 6210 green, tsc 0, deepseek live 9/9.
1662
+
1663
+ ## 1.344.0 (2026-07-19)
1664
+
1665
+ Per-model API key actually reaches the wire ([1264] v3 — the root of the multi-model 401 family).
1666
+
1667
+ - **fix: the auth hook's `apiKey` was silently DROPPED at the harness stream seam** — `createStreamFn` merged only `auth.headers`, so every brain fell through to `config.apiKey` (the boot env token) on a correctly-routed per-model URL → 401 on main AND child lanes alike (clay production incident + the cli key-fence reproducer, exact match). `auth.apiKey` now forwards into the brain call options; the brain contract (`options.apiKey ?? config.apiKey`) makes per-model keys STRICTLY win, with the env/config key as the fallback for models without one. Deliberately not routed through the `before_provider_request` hook snapshot (credentials stay off the hook-rewritable surface).
1668
+ - Third member of the curated-whitelist-dropped-field class (design/119 `maxTokens`, design/130 `callDeadlineMs`) — the forwarding text-lint's OLD exemption row ("auth path handles it") was itself the lie; the row now points at the real forwarding and a harness-level fence pins hook-key delivery + no-hook fallback.
1669
+ - Together with 1.337 (Model-object routing) and 1.341 (hook inheritance to workflow children) this closes the multi-model workflow case end to end. Suite 6204 green, tsc 0, deepseek live 9/9.
1670
+
1671
+ ## 1.343.0 (2026-07-19)
1672
+
1673
+ Center prompts S3 — the nine-element cache identity, exhaustive input classification, and the lowering record (RB-17③④ cleared; S2 invariant pins ride along).
1674
+
1675
+ - **feat: `TurnPromptSnapshot`** (protocol §10.2) — a first-class, classified identity of the run's request prefix: nine elements (provider / model / artifactDigest / policyDigest / layoutDigest / stableSystemDigest / toolWireDigest / requestPolicyDigest / loweringVersion+form+api) digested over the shared canonical encoding. Observability identity only — the design/31 cache-break detector keeps its raw feed (per-component diffing beats digest comparison for attribution). Live face on `prepared.turnSnapshot`; initial copy + the run's **lowering record** (§10.1: version, wire form, known intentional divergences per brain family) ride the `prompt.assembled` manifest trace.
1676
+ - **feat: exhaustive input classification as a TYPE-LEVEL CI gate** — `PREFIX_INPUT_CLASSIFICATION satisfies Record<keyof RawPrefixInputs, …>`: adding a prefix-shaping field without classifying it (cache-identity / volatile-no-reuse / proven-irrelevant) is a compile error; runtime pins hold the identity set to exactly the nine sources.
1677
+ - **identity follows every declared mid-run wire change** (codex): deferred materialization (`refreshTools`), the RB-31 adoption swap (`refreshStableSystem` now carries the COMMITTED epoch digest — never a mixed old-artifact/new-prompt record), the finalize thinking flip (`refreshRequestPolicy`), and model swaps (`refreshModel`); each refresh emits a **`prompt.snapshot_changed`** trace so the stream never keeps claiming a superseded prefix.
1678
+ - **prompt-text digests are KEYED** (codex): `stableSystemDigest` uses the per-process manifest salt (full-length) — a fixed public prefix + short unknown suffix made an unsalted full-text sha enumerable, recreating the disclosure the salted block hashes prevent. Same-process comparability holds; cross-process reconciliation rides the code-class digests (artifact/toolWire, unsalted). A pin asserts public traces never carry an unkeyed digest of composed prompt text.
1679
+ - rides along: the two S2 invariant pins (mid-run candidate immunity; atomic same-batch materialization) and the `event-batch-as-session-messages` divergence entry (registered in cc-parity). codex 1 round, 4 findings folded. Suite 6203 green, tsc 0, deepseek live 9/9.
1680
+
1681
+ ## 1.342.0 (2026-07-19)
1682
+
1683
+ Center prompts S2 first piece — compaction-boundary candidate adoption (RB-31; protocol §9.3).
1684
+
1685
+ - **feat: a center-pinned session ADOPTS a newer validated candidate at a successful compaction** — the one legal mid-run boundary. The advanced epoch descriptor (candidate declarations + provenance) rides the SAME `appendCompaction` CAS as the new baseline; the live swap (`harness.setSystemPrompt` — a new setTools-mirror seam picked up at the next turn snapshot, never mid-request; cache-fingerprint + overhead + child-thread `centerAdoption` follow) runs only after the append lands, and a throwing swap never fails the committed compaction (the pin self-heals at the next leg).
1686
+ - **feat: explicit center REVOCATION rolls pinned sessions back to bundled** at the same boundary (codex F1): the source face gains a tri-state (`sourceState()`: active / disabled / unavailable) — `applyDisabled` (a validated revocation, reboot-surviving) clears the pin's center fields and drops the section from the prefix; an UNAVAILABLE source (store-miss boot, outage) restates conservatively — an outage never strips policy, only an explicit revocation does.
1687
+ - **fix: adoption overhead bookkeeping is exact** (codex F2): the swapped prompt's contribution is repriced at the model's real `charsPerToken` against the CURRENTLY-accounted prompt (repeated adoptions v1→v2→v3 each subtract the current, never the original), and the in-flight compaction's post-measurement uses the post-swap overhead — PTL recovery and the anti-thrash floor reason about the prefix that actually exists.
1688
+ - **fix (S1 gap): a plain epoch ADVANCE now carries the current pin's center fields** — an engine-upgrade-mid-session compaction previously dropped center provenance, silently recomposing BUNDLED on the next leg.
1689
+ - Scope v1 (documented): bundled-pinned sessions never adopt at compaction (fresh-session adoption stays the only bundled→center door); center-pinned sessions only. Runner-level rows: adopt (prefix swap + pin advance + next-leg v2), restate (same candidate), disable rollback, unavailable restate. Suite 6192 green, tsc 0, deepseek live 9/9; codex 1 round, 2 findings folded. RB-31 CLEARED.
1690
+
1691
+ ## 1.341.0 (2026-07-19)
1692
+
1693
+ Workflow credential inheritance ([1258] v2 — the second half of the multi-model incident).
1694
+
1695
+ - **fix: workflow-spawned agents inherit the HOST's per-model auth hook** (`TaskSpec.getApiKeyAndHeaders` → `RunWorkflowOptions.defaultGetApiKeyAndHeaders`, folded into both `agent()`/`agentStream()` child specs when the fold chain carries none). 1.337 fixed the ROUTING half (children got the resolved Model object, URL correct) but children still authenticated with the boot env token — in a per-model-key deployment every child died 401 on the right gateway. The subagent lane has inherited this hook since [893]④a; the workflow lane never did. Scripts can never set the hook (the governance whitelist strips it — unchanged), so the inheritance is always host→child. Not call identity (auth, not behavior). codex verified: retries preserve the reference, journals never serialize it, degradation clears it for independently-credentialed fallback brains (approve, no findings).
1696
+ - pin: both spawn lanes carry the same hook reference into the child spec.
1697
+
1698
+ ## 1.340.0 (2026-07-19)
1699
+
1700
+ Hardness prompt sections ([1246]④⑤, GAP5 forensics) + the [1254]③ reopen contract unification.
1701
+
1702
+ - **feat: two sema-authored behavior sections** (simple profile; classic bytes frozen; replaceAll-excluded; golden re-frozen; epoch digest moves once — documented legacy_migration wave):
1703
+ - `core/sema.verify-fresh` — deliverables that outlive the session are verified HERMETICALLY (`env -i` class), never only in the shell that exported the state; a login shell is explicitly NOT a clean check (it sources the profiles you may have just written — the exact failure class observed); prefer standard install locations. Field acceptance = sema-test gate PH probe.
1704
+ - `core/sema.evidence-audit` — a search hit is evidence until you have seen WHY it matched (open the file at a truncated hit, never dismiss it); literal whole-target re-search after recovering a NON-SENSITIVE identifier; SENSITIVE values (credentials/tokens/keys) are never interpolated into command arguments or echoed — search from the source, report locations/counts only (codex: the naive form would have leaked secrets into tool traces).
1705
+ - **fix ([1254]③): a THROWING checkpoint reopen now surfaces `checkpoint.reopen_failed`** like the definitive arm — the old shape kept the bare `resume.*` code (claiming "retryable infra failure") while the checkpoint may be terminally consumed. Two honest arms, one code: definitive (store said so) vs state-UNKNOWN (store threw mid-reopen; the message says confirm via the checkpoint store before retrying). Original resume failure chains as `cause`.
1706
+ - test recalibration: one budget-slice pin's ceiling moved 0.03→0.035 (the new sections' ~1.3K chars shift the precall overhead estimate; real-spend semantics unchanged and documented in-line).
1707
+ - codex adversarial round (both prompt findings folded: login-shell false-clean + credential-leak exception). Suite 6187 green, tsc 0, deepseek live 9/9.
1708
+
1709
+ ## 1.339.0 (2026-07-19)
1710
+
1711
+ Grep honesty fixes ([1249]/[1246]① — the GAP5 forensics' two tool-evidence killers, JS-fallback lane).
1712
+
1713
+ - **fix: truncated output lines carry an explicit marker** — the JS fallback sliced every emitted line to 500 chars with NO marker, so a real hit inside a 70K-char line read as a false positive (the model could not see why the line matched). All five clip sites now append `… [+N chars]`; the rg lane already disclosed via `--max-columns-preview` (parity restored). The multiline `only_matching` 2,000-char match cap discloses its omitted tail the same way (a long multiline match of short lines previously lost everything past 2,000 chars silently).
1714
+ - **fix: `path` naming a FILE searches exactly that file** — the walk treated the start as a directory (listDir fails → zero files → a false "No matches." whose directory-flavored hint cemented the wrong conclusion). A stat-probe routes file paths to a single-file scan — under the SAME memory/binary policy as the walk (an explicitly named oversize file lands in the `skippedLarge` caveat instead of an unbounded read; binary extensions go name-only), so the fix cannot be turned into a multi-GB allocation.
1715
+ - codex adversarial 2 rounds (explicit-file safety bypass + multiline cap disclosure folded). Suite 6186 green, tsc 0, deepseek live 9/9. Pins: marker presence, single-file relative/absolute, oversize/binary explicit files, >2000-char multiline -o disclosure.
1716
+
1717
+ ## 1.338.0 (2026-07-19)
1718
+
1719
+ Approval display projection ([1245] — the "Run a dynamic workflow?" confirmation's engine half).
1720
+
1721
+ - **feat: `approvalPreview` seam** — a tool may declare `approvalPreview(args)` (AgentTool/ToolSpec, mechanism-neutral): a pure display projection of its args for human approval surfaces. The gate mints it through ONE helper — alias-aware tool lookup, throw-swallowed, 16KiB serialized clamp, and **control-character sanitization on every string leaf** (terminal escapes/CR can never reach an approval renderer raw). Delivered on BOTH lanes: `AskRequest.preview` (live onAsk dialogs) and the durable checkpoint's `pendingAction.preview` + **`CheckpointSummary.preview`** (the one-call `listByScope` inbox — no N+1 get).
1722
+ - **Trust contract (explicit)**: the preview is UNTRUSTED, ADVISORY display metadata — renderers must contextually escape it and approval surfaces should show the bound raw args alongside (args + boundInputHash remain the sole execution contract; adjudication and resume never read the preview).
1723
+ - **run_workflow implements it**: script form → statically parsed meta (name/description/phases[{title,detail}], scriptChars, hasArgs); named/scriptPath forms → identity stubs; malformed meta → bounded parse-error head. Runtime agent prompts are statically unknowable — phase detail is the honest ceiling. Also pinned: run_workflow rides the tool gate with NO exemption (an ask-policy naming it parks/asks like any tool — the [1230] "manual mode ran without confirmation" was a policy name-list gap, not an engine bypass).
1724
+ - codex adversarial 2 rounds (hostile-metadata sanitization, alias-invoked preview loss, durable-summary projection all folded). Suite 6181 green, tsc 0, deepseek live 9/9.
1725
+
1726
+ ## 1.337.0 (2026-07-19)
1727
+
1728
+ Incident fixes ([1238] — workflow children 404-dead in multi-model deployments + swallowed provider errors).
1729
+
1730
+ - **fix: workflow-spawned agents inherit the HOST's RESOLVED Model object** (`RunWorkflowOptions.defaultModel`, threaded from the run's live model through the Workflow tool into both `agent()` and `agentStream()`): a child whose fold chain (script spec → agentType → governance baseline) produced no model previously fell to a string/role re-resolution that LOSES per-model routing — in a deployment where the session model rides a per-model baseUrl, every child hit the base gateway with the wrong id and died in ~300ms. Explicit models still win everywhere. The inherited model is CALL IDENTITY: snapshotted once at `ctx.agent` entry, folded into the replay `callKey` (a resume under a different host model diverges instead of replaying old-model results), and shown on the agent record.
1731
+ - **fix: provider failures surface on every display face** — `WorkflowAgentRun` gains `errorCode`/`errorMessage` (bounded+redacted); ONE shared terminal fold (`agent()` normal, `agentStream` terminal, salvaged-stall) puts them on the record, falls an empty output back to the error message, writes a run-log line, and stamps `errorCode` on `agent_end` events. Previously a dead agent showed `output:""` with no machine code anywhere while the engine had the error body all along.
1732
+ - **fix: stable fallback errorCode `"provider.error"`** — an UNCLASSIFIED provider/model failure (no canonical `[code]` prefix — e.g. a raw gateway 404 for an unknown model id) now mints a stable generic code on `TaskResult.errorCode` instead of nothing; canonical classes keep winning. Declared behavior change (SF1b contract updated): consumers see a code where they previously saw `undefined`.
1733
+ - codex adversarial 2 rounds (5 findings folded: stream-lane settle unification, inherited-model call identity, salvaged-event code, plus the two originals). Suite 6174 green, tsc 0, deepseek live 9/9.
1734
+
1735
+ ## 1.336.0 (2026-07-19)
1736
+
1737
+ Center prompts campaign S1 (design/148; clay kickoff) — the artifact store + candidate state machine (protocol §9.2 v1 subset).
1738
+
1739
+ - **feat: center prompt artifacts** — an immutable, by-digest publishable unit (`PromptEpochArtifact`: schemaVersion/assemblyApi/catalogDigest/compatibleCore/closure with overlay sections on the bundled base). Full verify pipeline (`verifyPromptArtifact`): strict shape, id grammar, slot vocabulary, MANDATORY per-section sha256 `contentHash`, hard limits, dotted-triple core-compat range, and both digests recomputed over normalized shapes (publisher-hashed extra keys reject via digest mismatch; empty catalogs reject — "publish nothing" is `applyDisabled`, not an empty artifact). Adoption rides the PROVEN declaration lane: a verified artifact compiles to `PromptTextDeclaration`s (same mount loop, manifest, epoch and contentHash faces as server stableBlocks — the [1209] reconciliation chain applies unchanged).
1740
+ - **feat: by-digest stores + source-state machine** — `MemoryPromptArtifactStore`/`FilePromptArtifactStore` (atomic tmp+rename; reads RE-VERIFY bytes — disk is never trusted by location), `PromptSourceState` (active/disabled, opaque `sourceRevision`, transitions only on verified responses; network failure/parse failure/304 keep state; explicit disable survives reboot), `CenterPromptSource` (applySnapshot → verify→put→persist→swap; restore re-resolves through the verified store).
1741
+ - **feat: adoption semantics (`RunnerDeps.promptSource`)** — a truly-FRESH session (empty branch — session lifecycle fact, caller-minted create-on-miss ids included) adopts the current candidate and pins provenance (`centerArtifactDigest`/`sourceRevision`, additive `prompt_epoch` fields); a PINNED session resolves its exact artifact by digest forever (a candidate bump never leaks in); children inherit the PARENT's closure through the trusted internals chain on every spawn leg (subagent sync/bg/fork + workflow agents) and never self-adopt — one tree, one closure.
1742
+ - **fail-loud, never hybrid**: an unresolvable pinned artifact throws `prompt.snapshot_unavailable` (recomposing from same-named newer content is exactly what the pin exists to prevent); a center adoption on a provider shape that owns the whole prompt (legacy `system()` / assembled-identity pass-through) throws `prompt.center_unmountable` instead of persisting a pin the model never received.
1743
+ - **deterministic collision rule**: a center section whose id collides with a provider stableBlocks declaration YIELDS (deployment-local wins, operator-visible warn) — a publish can no longer fail affected sessions via the duplicate-id pack gate.
1744
+ - codex adversarial 2 rounds, 5 findings folded (compaction-boundary candidate adoption deliberately moved to S2 — it requires mid-run re-assembly, i.e. the TurnPromptSnapshot chain; ledgered RB-31 with the stale-prompt tradeoff stated). Live-verified on deepseek: fresh adoption composes and BEHAVES (watchword probe), continuation stays pinned across a candidate bump.
1745
+ - exports: artifact types + verify + `buildPromptArtifactEnvelope` (fixture/publish helper) + stores + `CenterPromptSource` (server integration: hand `CenterPromptSource` a root-path file store pair and call `applySnapshot`/`applyDisabled` — see [1236]).
1746
+
1747
+ ## 1.335.0 (2026-07-19)
1748
+
1749
+ RB-28b terminal closure — an already-active roster face un-defers its listing producer.
1750
+
1751
+ - **fix: a roster-bearing tool that enters a leg ALREADY ACTIVE (materialized via ToolSearch on a prior leg, transcript-replay/checkpoint seeded) no longer counts as a deferred listing face** — its full schema is inlined at prepare, so byte-deferral is over for that tool; the agent-types boundary producer runs again and the normal seed ladder (checkpoint mirror → transcript probe) dedups. This closes the 1.334 residual's loss arm: a leg-1 listing ride that was hook-stripped, budget-cropped, or lost to a `setTools` failure now gets an honest re-announcement on the next leg instead of a durable roster absence. Symmetric cost (accepted, duplicate-class): a DELIVERED ride also re-announces once on the next leg — the transcript probe deliberately cannot read tool results (untrusted-embed forge protection). Pure-deferred legs that never materialized stay fully silent (the [1131]④ defer-silence ruling is unchanged). Codex adversarial review: approve, no material findings (exclusion still wins; removal-to-zero arm untouched; mirror dedup verified).
1752
+ - ledger: RB-28b CLEARED; RB-29 narrowed to its four-condition legacy-checkpoint tail (real-world population ≈0, terminal = RB-25's first-class announced entry).
1753
+
1754
+ ## 1.334.0 (2026-07-19)
1755
+
1756
+ Review-backlog batch clearance + the RB-28 terminal fix (roster rides the ToolSearch delta).
1757
+
1758
+ - **feat/fix (RB-28 terminal): a DEFERRED delegation tool materialized mid-run via `ToolSearch` now receives its agent-types roster in the search's own result content** (the design/36 delta channel — tail of the log, cache prefix untouched). Previously a `deferTools:["Agent"]` leg suppressed the listing producer entirely, so the materialized tool's description pointed at a listing that never arrived. `makeToolSearchTool` gains an optional `listingRide` seam; prepare-task wires it on a deferred roster face. Live-verified end to end (deepseek: placeholder → ToolSearch → roster → real delegation). Directly load-bearing for the defer-by-default rollout.
1759
+ - **fix: `ToolSearch` activation is now staged, rolled back on failure, and serialized** — a throwing `setTools` no longer leaves the name falsely active (every retry saw "already active" and never re-attempted the swap), and concurrent same-name searches in one parallel batch can no longer interleave a sibling's "already active" claim across a rollback (single activation chain; `newly` computed inside the critical section). Announced-listing state is deliberately NOT committed by the ride: every failure arm (hook rewrite, budget crop, re-suspend) stays duplicate-class — the roster may be re-announced later but is never falsely recorded as delivered (codex 2 rounds; the durable stripped-ride residual is ledgered as RB-28b, terminal fix rides the RB-29 batch).
1760
+ - **RB-22 CLEARED (deliberate ruling + pin)**: a human-approved gated tool call executes on resume even when the cost budget is already exhausted — tool execution spends no model budget and the next turn-boundary gate still stops the run; refusing would waste the sunk human approval. Walltime deliberately differs (RB-21: not exempt — the write-out window is protected).
1761
+ - **RB-16 CLEARED (runner-level epoch rows)**: pins prove a real within-task compaction RESTATES the same prompt-epoch artifact (no second pin), and a preCompact-blocked (failed/noop) pass — including a PTL-forced escalation that ignores the block — never mints a new epoch. The CAS-conflict row is structurally unreachable (per-session lock serializes minting) — documented, no pin.
1762
+ - **RB-17② CLEARED (legDate pair)**: a forced/auto compaction REBUILD across midnight keeps the frozen leg date byte-identical in the prefix ⇄ a session resumed the next day starts a new leg with the new date (the intentional A1 semantic, both halves pinned).
1763
+ - **RB-18 CLEARED**: `ARCHITECTURE.md` gains the prompt-assembly section (§3.1 + module-table row), `THIRD-PARTY-INTEGRATION.md` gains the prompt-customization surface (§2.1); the CC209 loadability fixture pack moved to `test/fixtures/cc209-fixture-pack.ts` (reusable); the explain fixture-diff face is OBE (its diff target retired with the golden fixture — drift sentinel duty lives in `prompt-golden-freeze`).
1764
+ - bookkeeping: RB-27①② and RB-30 marked CLEARED (shipped in 1.331/1.332/1.333); RB-8 ruled accept-as-documented with explicit re-open conditions; RB-28b ledgered.
1765
+ - live suite grows a standing RB-28 multi-turn probe (deferred Agent → ToolSearch → roster → delegation, mechanical anchors only).
1766
+
1767
+ ## 1.333.0 (2026-07-18)
1768
+
1769
+ RB-30 terminal fix + agent-team chaos validation (clay's order: full-spectrum abnormal-path testing).
1770
+
1771
+ - **fix: the final-drain stranding window closes losslessly** — engine-note payloads still sitting in the harness steer/followUp queues at `agent_end` are handed back through the new `onUndrainedEngineNotes` sink and PENDED per session; the session's next run redelivers them at turn-open (the existing turn-open dedup guards double-delivery). Receipt semantics untouched (no new race surfaces — the RB-30 ledger entry's terminal fix, exactly as documented).
1772
+ - **payloads ride a WeakMap sidecar, never a message field** — the first cut put the payload on the UserMessage and it SERIALIZED into the transcript/model face (caught immediately by the existing [781]⑤a wire×2 pin — a live double-match); the sidecar is identity-keyed, GC-friendly, never persisted.
1773
+ - **chaos validation (deterministic seeded harness, `chaos-agent-team.local.ts` untracked)**: randomized op sequences (launch/mid-run send/resume probe/TaskStop at arbitrary points/tool failures/hangs/throwing sinks) × invariants (no hangs, no contradictory states, legal receipts, zero terminal leaks, legal row transitions, no unhandled rejections) — 3 seeds × 240+60+60 rounds ALL-GREEN.
1774
+ - **codex folds (3)**: hard `abort()` runs the same recovery sweep BEFORE clearing the queues (a hard abort would have lost accepted notifications behind their queued receipts); recovered payloads keep DELIVERY order (steer before followUp — the reversed order could have presented "later" frames ahead of "now/next"); drained sidecar payloads are released immediately (the message object lives on in the session cache — the WeakMap would have pinned every notification payload for the session's cache lifetime). Residual (bar-accepted): the loop-owned pending window (drained but unprocessed at an abort instant) — abort-kill semantics, extremely narrow.
1775
+ - **codex round-2 fold**: the pre-prompt exit window closes too — turn-open drained frames ride the same sidecar into `nextTurnQueue` (released on real prompt consumption), the recovery sweep covers all THREE queues (nextTurn first — chronological order), and a belt-and-braces `recoverUndrainedEngineNotes()` runs in the runner's finally for exit paths reaching neither agent_end nor abort (idempotent — swept entries are gone).
1776
+ - residual (RB-30b, bar-accepted after three rounds): a turn-open frame dequeued into the first prompt whose run then fails between dequeue and loop start (a `before_agent_start` hook rejection / transient first append failure) is lost on the model face (the SDK event already surfaced it); narrowing further would tie sidecar lifetime to message_end — complexity out of proportion to the window.
1777
+ - pins: pend→next-run turn-open redelivery liveness; abort-path recovery with exact delivery order.
1778
+
1779
+ ## 1.332.0 (2026-07-18)
1780
+
1781
+ agent-team S2b second cut — the resume cycle re-enters the registry lifecycle (RB-27② clearance; the campaign's closing mechanism piece).
1782
+
1783
+ - **feat: a retained-session RESUME revives its background-agent row** — `reviveBackgroundAgent` flips the settled row back to `running` with a fresh `attaching` channel and a bumped revive-cycle stamp; the resumed run attaches a fresh injector through the same spawn seam (cycle-stamped so a stale cycle's late attach/settle can never clobber a newer one); both the settle and reject legs re-terminalize the row (`settleRevivedAgent`, cycle-gated).
1784
+ - **behavior: a message sent WHILE a resumed child runs now gets real running delivery** (the row reads `running`, the fresh injector queues it for the child's next turn boundary) instead of the previous `steering.still_running` refusal — the SendMessage lifecycle is now uniform across first-spawn and every resume cycle.
1785
+ - **codex folds (2)**: (high) `reviveBackgroundAgent` swaps in the RESUME's own AbortController — TaskStop during a revived cycle now aborts the running resumed child (previously it aborted the finished spawn-cycle controller and reported termination while the child kept executing); (med) revival clears the prior cycle's terminal payload (result/error/partial/stop attribution) so TaskOutput never serves stale cross-cycle state.
1786
+ - **codex round-2 folds (2)**: (high) an unsuccessful revival (terminal-GC'd row / concurrent-resume still_running) is a PRE-LAUNCH refusal — the child never launches uncontrolled behind a success receipt; the cycle claims roll back (no resume slot burned) and SendMessage maps the honest texts; (med) resumed failures re-supply their diagnostics — `settleRevivedAgent` carries `error` on both the resolved-failure and reject legs (revival cleared the prior cycle's error, so a bare "failed" would have lost the retry-safety signal).
1787
+ - **codex round-3 fold**: the resumed cycle's completion notification speaks the registry's WINNING terminal status — `settleRevivedAgent` returns first-writer-wins truth, so a TaskStop landing between the child's completion and the settle keeps "killed" everywhere (notification, summary, completed-child gate) instead of a contradictory "completed" frame.
1788
+ - end-to-end pins: spawn→settle→resume(re-hang)→row revived→second message delivered mid-resume (`queued` receipt)→cycle settles back to `completed`; TaskStop mid-resume kills the RESUMED run; revival payload hygiene; TaskStop-wins settle returns killed.
1789
+
1790
+ ## 1.331.0 (2026-07-18)
1791
+
1792
+ agent-team S2b first cut — acceptance-aware delivery receipts (RB-27① clearance; design/147 S2b, CC mailbox posture). Five codex adversarial rounds; the batch deliberately CONVERGED on the stable-honesty surface (anti-Goodhart: three rounds proved that closing the last stranding window either pins the sender to the child's turn cadence or breeds new race surfaces — RB-30 documents the residual).
1793
+
1794
+ - **feat: SendMessage running-delivery receipts speak the injector's REAL disposition** — the run-scoped injector returns `Promise<"queued" | "parked" | "dropped_duplicate">` (queued on enqueue acceptance — fast, never pinned to the child's boundary; parked on the idle tail-race, resolved only after the pend actually happened); `deliverToRunningAgent` is async and awaits it; receipts and `details.disposition` fork accordingly.
1795
+ - **feat: parked acknowledgements only when redeemable** — `markRetainedContinuation` flips AFTER `tryRetainChild` secures the ledger slot + pin (register-time declaration was a lie window: capacity/pin failures and session-scope don't retain; the fork lane's retain is decided at settle and never marks). Non-retained rows read the lane-dead race as `not_running`, steering senders to the honest settled path.
1796
+ - **feat: startup-window receipts defer to the flush truth** — "buffered" is retired; `preAttachQueue` entries carry deferred resolvers, the attach flush awaits each injector outcome IN ORDER with the retained-park gate, entries STAY on the handle (peek-then-shift) so a terminal settle/stop mid-flush (or a stalled injector) sweeps the remainder as `not_running` — a sender can never block on a never-settling injector.
1797
+ - compat: `attachAgentNotify` accepts legacy void notifiers (reads as queued — the pre-upgrade contract); `SystemInjection` unchanged on the wire.
1798
+ - honest ledger: RB-30 — the final-drain stranding window (accepted but never drained before agent_end) LOSES the message (it stays in the ending harness's private queue; teardown does not migrate it — codex R5 corrected an earlier retained-recovery claim); the queued receipt therefore hedges ("may not survive — resend on the completion notification if unanswered"). Documented residual; terminal fix rides RB-27②'s harness-teardown queue migration.
1799
+ - **codex round-5 fold**: direct (post-attach) delivery awaits are TRACKED on the handle — terminal settle/stop sweep them as not_running, so a never-settling/degraded async injector can no longer pin the sender's tool call (single-shot resolution discards the late result).
1800
+ - **codex round-6 fold**: the channel stays "attaching" until the startup backlog fully DRAINS — a delivery landing mid-flush joins the same serialized queue (routing keys on channelState, not notifier presence), so a newer message can never overtake buffered ones (the A,C,B reorder); ordering pin included.
1801
+ - **codex round-9 fold**: a timed-out direct delivery DETACHES its tracked resolver (the stalled injector op itself is untouched) — repeated sends on a degraded channel no longer accumulate unbounded per-delivery state; pin asserts the tracking set drains to zero across multiple timeouts.
1802
+ - **codex round-8 fold**: bounded delivery-confirmation wait (10s, test-tunable) — a stalled startup (attach never arrives) or a never-settling injector on a still-running row resolves the receipt as "pending" (unconfirmed; the message STAYS queued and still delivers if the channel binds) instead of pinning the sender's tool call — a parent sender could otherwise deadlock the very teardown expected to sweep it.
1803
+ - **codex round-7 fold**: retention is REVOCABLE — the spawner wraps the retain entry's release closure with `unmarkRetainedContinuation`, so every eviction path (TTL/LRU/abandon/parent-teardown disposeAll) revokes park acceptance in the same step that releases the continuation; a sibling SendMessage racing the teardown reads `not_running`, never a park promise against a released session.
1804
+ - pins: disposition arms (queued/parked/refusal), retained vs non-retained park, legacy void adapter, stop/settle-during-buffer, stalled-flush sweep, in-order flush, mid-flight end-to-end delivery.
1805
+
1806
+ ## 1.330.0 (2026-07-18)
1807
+
1808
+ Finalize forces a non-thinking write-out turn ([1076]③a — the last open item of the TB turn-management loss family: thinking models spent the graceful-finalize window reasoning and the deliverable never landed).
1809
+
1810
+ - **fix: both finalize injection lanes (walltime-cutoff latch + turn-boundary) now call `setThinkingLevel("off")`** alongside the persist-first steer — the FINAL turn's whole token budget goes to the write-out (the in-memory level flips synchronously; the task is ending, no restore). Complementary mechanisms unchanged: the call-cap budget-driven thinking skip and the thinking-only empty-stop nudge.
1811
+ - **codex folds (2)**: (high) an explicit `"off"` now survives to the wire instead of being folded into "unset" — the engine passes it through and the openai brain translates it to the provider's DISABLE key on the binary enable formats (`enable_thinking: false` for qwen/zai, `chat_template_kwargs.enable_thinking: false` for qwen-chat-template) — default-on Qwen gateways reason on absence, so absence was not "off" there (the live-caught compaction case); formats with no safe disable key (openai/openrouter/deepseek/together) keep absence semantics with the call-cap squeeze as backstop (documented fallback). `undefined` stays pure provider-default. (med) the end-to-end pin now covers the finalize call itself (off-by-one) and asserts a non-empty post-finalize slice.
1812
+ - **codex round-2 folds (3 propagation seams)**: the walltime-cutoff write-out RETRY reads the live harness level via the new `liveThinkingLevel` seam (it re-calls the brain without prepareNextTurn — the one permitted retry previously kept the old level and could reason through its reserved window); the initial loop config passes "off" through (a task/subagent configured thinking:"off" now sends the disable key on its very first request); compaction forwards explicit off on reasoning models (summaries on default-on Qwen no longer silently re-enable); `SimpleStreamOptions.reasoning` widened to include "off" (both brains guard — off never enables).
1813
+ - 2 pins: end-to-end reasoning flip + wire-body assertions for all five thinking formats under explicit off.
1814
+
1815
+ ## 1.329.0 (2026-07-18)
1816
+
1817
+ Prompt-anchor campaign R3 (the main piece): CC 2.1.212's SIMPLE-profile system sections, verbatim-anchored, shipped through the 1.328 `promptProfile`/`fableMitigations` axes (anchor doc = internal anchors/2.1.212/simple-system-sections.md — the full H$ section roster).
1818
+
1819
+ - **feat: nine CC-verbatim system sections on the simple profile** (`src/prompts/simple-sections.ts`; pack ranks 250-266; absent on `promptProfile: "classic"`): communicating (fable full form ⇄ lean single sentence — the exact split CC's Q$_ serves), pronouns, action-caution, task-continuity, tool-param-json (CC's one-liner), investigate-first, context-management (additionally gated on the REAL within-task-compaction mechanism — §6.3; CC ships it unconditionally because CC always compacts), act-dont-rederive, and the fable autonomous-operation section (sU_, `fableMitigations` only). CC's e0s security sentence was already anchored as CYBER_RISK (not duplicated); CC's fable_identity brand section is not copied (sema identity lives in the role base).
1820
+ - **legacy face mirrored 1:1** — `harnessContext` + `StablePromptContext.promptProfile/fableMitigations` + prepare-task threading; the S1 composer⇄legacy byte-equivalence suite holds (45/45).
1821
+ - measured faces (bare runTask): lean simple 8,690c / fable 12,392c / classic 6,114c (CC simple anchor = 6,116c; the ~2.6K excess is sema superset harness sections — a future trimming candidate, deliberately not this batch).
1822
+ - three token-budget-calibrated mechanism tests (compaction breaker C3, mid-stream budget suspend, resume-at-through-compaction) pinned to `promptProfile: "classic"` with comments — they test mechanisms whose windows were calibrated to pre-R3 bytes, not prompt faces.
1823
+ - **codex folds (2 high)**: the eight BEHAVIOR sections joined `REPLACE_ALL_EXCLUDED` — a `replaceAll` persona receives none of them (M4 sovereignty; mechanism-truth context-management deliberately stays); and known-constant narrowing in assemble — a first-party `CODE_SYSTEM_PROMPT` role on a non-classic profile serves the profile-neutral `CODE_AGENT_PROMPT` base (its old short-form appendices are the classic-only shape now; deployment-authored strings are never rewritten, classic keeps the persona byte-identical).
1824
+ - **codex rounds 2-3 folds**: the narrowing carries an EXPLICIT provenance guard (`roleBaseFromProvider`, set at all three provider assignment sites) — a provider-supplied base (replaceAll, M9 passthrough, or the ordinary stableSystem role layer) is never rewritten even when its bytes equal the constant (string equality has no provenance; constitution ownership was not a valid proxy — an ordinary stableSystem provider keeps constitution "core").
1825
+ - 5 new pins (three-state faces, replaceAll sovereignty, CODE_SYSTEM_PROMPT dedup both profiles, replaceAll×constant intersection, ordinary-stableSystem×constant); prompt golden refrozen (declared).
1826
+
1827
+ ## 1.328.0 (2026-07-18)
1828
+
1829
+ Prompt-profile axis (clay's ruling: support simple/classic switching — CC 2.1.212 parity structure; behavior value takes precedence over verbatim anchoring, constitution amended). R2 main batch.
1830
+
1831
+ - **feat: `TaskSpec.promptProfile: "simple" | "classic"`** — which prompt face the task speaks. Default "simple" (what CC's `LT(model_id)` gate serves every BYOM model id); "classic" = the long-form face (≈ CC 4.x-era wording, switchable per task/model — e.g. serving classic to a model that scores better on it; center distribution rides the field). Presentation only, never policy. Inherited down the delegation tree (subagent + observer + fork snapshot + workflow lanes); child spec wins.
1832
+ - **feat: `ToolSpec.descriptionClassic`** — per-tool classic variant, swapped in at mount time (post-exclusion, shallow copy — the shared spec object is never mutated). Absent = one form serves both.
1833
+ - **dual faces shipped**: Read + Glob (classic = the pre-1.327 long form), Agent (simple = CC 2.1.212's When-to-use form, sema-factized — agent-definition source, model-override semantics, background availability by mount truth; classic = the briefing-guidance long form), TodoWrite (simple = CC's QWg short form with the honest optional-`activeForm` correction; classic = the prompt.ts long form). Bash/Edit/Write/Grep/WebFetch/WebSearch verified already at the simple anchor — single form.
1834
+ - **feat: `PromptRuntimeFacts.fableMitigations`** (clay's follow-up ruling: CC's simple prompt further forks a fable-specific variant — the b9e/`fable_5_mitigations` axis, orthogonal to simple/classic) — resolved from the model id (claude-fable-*/claude-mythos-5 stems, provider prefixes tolerated); R3 system sections fork on it (autonomous-operation paragraph, fable_identity, tool_param_json — anchors captured).
1835
+ - Constitution amendment recorded (behavior value first; hardness-optimal is the constitution's premise): Grep delegation line conflict settled as RETAINED per clay's standing 44%-vs-0% ruling.
1836
+ - **codex unified-review folds (3 findings)**: (high) the Agent simple face's capability bullets are now conditional on mounted truth — background launches described as RECEIPTS (not final messages), the notification sentence only when the notify sink is wired (TaskOutput-polling wording otherwise), completed-agent continuation phrased "when session retention is enabled", model-override sentence only with a configured catalog; (med) the workflow worktree overlay no longer receives the injected parent profile — base-only injection, so isolation can never flip presentation over an explicit baseline; (med) fable detection extracted to the exported boundary-aware `isFableFamilyModelId` (deceptive superstrings like "not-claude-fable-5"/"claude-mythos-50" rejected, case-normalized, provider prefixes honored).
1837
+ - **codex round-2 folds (2 findings)**: the background face's retrieval claims now carry mount-conditional qualifiers — TaskOutput "where mounted", SendMessage "where the SendMessage tool is mounted" (this public factory cannot prove the host task mounts either control; conditional truths hold on every configuration); a DEFINED-EMPTY model catalog (`models: {}`) no longer advertises the override sentence.
1838
+ - **codex round-3 folds (2 findings, schema half)**: the PARAMETER faces now agree with the description — `name` carries the SendMessage where-mounted qualifier, `run_in_background` promises notification only when the notify sink is wired (TaskOutput-where-mounted wording otherwise), and the `model` parameter's "Takes precedence" promise requires a non-empty catalog (defined-empty/absent catalogs get the honest cannot-resolve wording; the parameter itself stays for durable-replay shape stability).
1839
+ - **codex round-4 fold (1 finding, runtime surfaces)**: the CLASSIC background paragraph and BOTH launch receipts (ordinary + fork lanes) now speak the sink truth — with no notification sink (mount-time for the description, `ctx.onTaskNotification ?? bg.notify` for the receipts) they direct the model to retrieve via TaskOutput-where-mounted instead of promising an automatic notification and instructing it to end its response (a no-sink launch previously stranded retrievable results).
1840
+ - 8 new pins incl. schema-face and EXECUTION-level receipt assertions (dual-face swap, classic inheritance, Agent face fork, fable-id boundaries, defined-empty catalog desc+schema, background-face qualifiers desc+schema, no-sink receipt); prompt golden refrozen (declared).
1841
+
1842
+ ## 1.327.0 (2026-07-18)
1843
+
1844
+ Prompt-anchor campaign R2 batch 1: fs tool descriptions re-anchor to CC 2.1.212's SIMPLE profile (mechanism verdict: CC's `LT(model_id)` gate serves the simple short form to every non-claude model id — sema is BYOM, so simple is the anchor form, not the classic long form; anchor doc = internal anchors/2.1.212/simple-prompt-profile-LT.md). Descriptions only — no behavior changes.
1845
+
1846
+ - **Read**: classic-skeleton hybrid → CC simple skeleton verbatim (windowed-read line, cat -n line, images/PDF/notebook line, directory/missing/empty line, anti-re-read line). Sema fact corrections stay — divergence-registered: relative paths allowed (CC says absolute-only; sema resolves against the tracked cwd — codex catch: the CC sentence would have been false), whole-file default (not 2000 lines), .ipynb read as JSON text, "warning" carrier, 256KB refusal, read-before-edit.
1847
+ - **Glob**: 7 bullets → CC's simple one-liner + two sema fact lines (mtime-less alphabetical fallback; RELATIVE paths + ignored trees). Classic Agent-delegation tail cut (absent in the simple anchor).
1848
+ - **Grep**: delegation tail line RETAINED after review — it rides a standing clay ruling (2026-07-15, 44% vs 0% delegation-rate driver) that conflicts with the anchor-CC constitution; per the reversal discipline the standing ruling wins until re-adjudicated (flagged to clay, ledger updated). (Edit/Write/Grep bodies were already at the simple anchor from earlier batches.)
1849
+ - One codex round (3 findings: false absolute-path claim, dropped notebook sentence, ruling conflict — all folded). Prompt golden refrozen (declared change).
1850
+
1851
+ ## 1.326.0 (2026-07-18)
1852
+
1853
+ Agent-types listing rides the effective tool face ([1131]④ — closes the deferral's companion leak: `deferTools: ["Agent"]` previously saved only the 5.6K schema while the ~6.7K roster reminder still shipped). Three codex rounds (2 findings folded).
1854
+
1855
+ - **fix: excluded or deferred delegation tools no longer announce their agent-types roster** — the listing predicate now requires the roster-bearing tool's name absent from both `excludeTools` and `deferTools` (exact-name, the same predicates as the wire-face classification). Defer now saves schema + roster together (~12.3K on the Agent face).
1856
+ - **codex round-1 fold: defer ≠ removal** — on a continuation leg whose earlier leg announced the roster, a deferred face goes SILENT (no roster, no removal frame, announced state untouched — a later undeferred leg replays the transcript and does not resend); only true unmount (exclude) falls through to the honest removal-to-zero projection. Without the gate, defer-on-continuation announced "no longer available" — false while the tool stays ToolSearch-reachable — and the cleared state forced a full re-announce later.
1857
+ - **codex round-2 fold: Q5 state-only carry across deferred legs** — a deferred face suppresses the listing producer, which would have dropped the resume checkpoint's `announcedListings` mirror on a re-suspend (a post-compaction undefer leg would resend the full roster; an exclude leg would lose its removal frame). `announcedListingsRef` is now pre-seeded from the resume checkpoint when the roster face is deferred — state carried, nothing rendered.
1858
+ - honest ledger: RB-28 — after defer, a mid-run ToolSearch fetch gets a schema whose description points at a listing that never arrives this leg (fresh sessions); RB-29 — pre-`announcedListings` LEGACY checkpoints don't get the state-only carry (the transcript-probe fallback needs the rendering producer; a deferred leg that also compacts and re-suspends can still lose the mirror — conservative re-announce worst case). Terminal fix for both = roster rides the ToolSearch delta + probe decoupled from the producer (R2 batch).
1859
+ - quality: 6 new pins (fresh-session control/defer/exclude + continuation defer-silence/exclude-removal + durable defer→re-suspend→undefer chain); full suite green (370); tsc 0; DeepSeek live 7/7.
1860
+
1861
+ ## 1.325.0 (2026-07-18)
1862
+
1863
+ Prompt-anchor campaign, first ship batch (clay's constitution: prompts anchor to CC verbatim — sema-unique text only for superset capability or the sema brand; anchor ledger = PROD-VS-CC209-VERBATIM-DIFF 23 items + CC 2.1.212). Three codex rounds.
1864
+
1865
+ - **R1#7: Read windowed-read suppression (CC verbatim)** — `offset`/`limit` descriptions adopt CC 2.1.212's "Only provide if the file is too large to read at once" sentences (sema's honest facts — 1-based, whole-file default — stay appended). Golden refrozen (declared).
1866
+ - **R2/[1132]③: deferred orchestration short form** — when self-orchestration is active AND the Workflow tool is on `deferTools`, the `mode.orchestration` section serves a ~300-char pointer instead of the 5.7K how-to (cache-prefix parity with the schema deferral; the how-to arrives with activation). Threaded as the optional `PromptRuntimeFacts.orchestrationDeferred` (public-API compatible), pack + legacy forks, epoch probe vector (one-time digest move, documented). Exact-name predicate — the same one the wire-face defer classification uses, so alias spellings defer nothing on either face.
1867
+ - **§6.3 hardening (codex R2, also fixes a pre-existing gap)**: both orchestration prompt gates re-anchor on the POST-EXCLUSION mount — `excludeTools: ["Workflow"]` now yields neither the how-to nor the pointer (previously the full how-to survived a true unmount).
1868
+ - anchor-ledger status: #3/#4(core seam)/#12/#14/#19/#21/#23 verified already closed by earlier batches; R0 three-way frame resample recorded (internal anchors/prompt-anchor-campaign-R0).
1869
+ - quality: 50/50 assembly families; full suite green (370); tsc 0; DeepSeek live 7/7.
1870
+
1871
+ ## 1.324.0 (2026-07-18)
1872
+
1873
+ agent-team S2a: mid-run delivery ([1051]/design/147 S2 first cut — the last mechanism gap of the campaign: a message can now reach a RUNNING teammate). Seven codex adversarial rounds (7 findings folded, 2 ledgered).
1874
+
1875
+ - **feat: SendMessage delivers to a RUNNING background agent** — the message rides the child run's own notification lane (`RunInternals.onNotifyInjectorReady` hands the run-scoped injector back to the spawner; the registry parks it on the child's handle) and reaches the child's model at its NEXT TURN BOUNDARY, never mid-tool — CC 2.1.212's in-memory `pendingMessages` pedestal (anchors messaging-runtime.md §2.5). The text arrives wrapped in CC's verbatim `<teammate-message teammate_id=…>` carrier; receipts use CC's "queued for delivery at its next turn" shape with the honest park note (a run that finishes first parks the message per session — delivered when the agent is next continued). Both background lanes (spawn + fork) attach the channel.
1876
+ - **codex hardening (rounds 1-7)**: delivery rides the RESOLVING authority (sibling name AND exact-id ladders get the trusted parent-view fallback, incl. the parent's SESSION axis for session-scoped siblings); a bounded pre-attach buffer closes the startup race (flushed in order at lane bind) while `deliveryChannel: "attaching"` declarations keep never-attaching spawners at the honest immediate `no_channel` (no acknowledged-then-discarded loss); TaskStop's direct killed-flip releases the channel state; terminal settle clears injector + buffer.
1877
+ - honest ledger: RB-27① (acceptance-aware ack — the receipt's park wording is truthful for the idle-race today), RB-27② (resumed runs keep the honest still-running refusal; registry-lifecycle integration lands with S2b).
1878
+ - teammate addendum + SendMessage description updated to the shipped contract; prompt golden refrozen (declared change).
1879
+ - quality: 10+ new pins incl. an end-to-end "running child sees the carrier at its next turn" probe; full suite green (370); tsc 0; DeepSeek live 7/7. (Load-flaky reap pins hardened to invariant form along the way.)
1880
+
1881
+ ## 1.323.0 (2026-07-18)
1882
+
1883
+ Deadline-aware background waits ([1076]③d core half — the TB "blind wait" tax: a 9.1-minute TaskOutput block ate the write-out window; CC classifies wind-down waits, anchors/2.1.212/print-mode-drain.md).
1884
+
1885
+ - **feat: blocking TaskOutput waits are wall-clock aware** — when the task has an armed wall-clock deadline, a `block` wait is clamped so it releases strictly BEFORE the engine's unified tool-cut line (base = `toolCutDeadlineMs` incl. its dynamic TTFT reserve, minus a 5s internal reserve — codex round-1: a softExec-based clamp would lose the dispatcher race and the clamp note would be discarded with it). Clamps are VISIBLE (fidelity B1 discipline): the result carries "wait clamped to Xs" / "wait skipped — deadline reached; write out your results now", plus `details.waitClamped` for prose-free telemetry. `block:false` snapshots and deployments without an armed deadline are untouched.
1886
+ - quality: 2 codex rounds (1 finding folded); 2 pins; full suite green (370, incl. the flaky-guard wall-clock discipline); tsc 0; DeepSeek live 7/7.
1887
+
1888
+ ## 1.322.0 (2026-07-18)
1889
+
1890
+ Background-hosting lifetime guidance ([1108]② — the behavioral half of the reap campaign; kill semantics untouched).
1891
+
1892
+ - **prompt face: lifetime guidance at the decision moment** — every `run_in_background` start receipt (notify/poll/legacy forms, non-retain envs) now carries the lifetime truth inline: background processes do not survive the session; a deliverable service belongs in a FOREGROUND `nohup cmd >log 2>&1 &` (self-detaching, survives — the shape both engines' teardown spares). The tool description alone did not steer the model (TB [1104]/[1106]: delivered services died because the model hosted them via run_in_background — which CC kills at teardown too; CC's models just habitually use `nohup &`).
1893
+ - the description's `setsid` suggestion is removed (setsid does not exist on macOS — gate PY proved the model follows the suggestion and crashes); the portable nohup form replaces it.
1894
+ - golden refrozen (declared tool-face change); 2 new pins; full suite green (370); tsc 0; DeepSeek live 7/7.
1895
+
1896
+ ## 1.321.0 (2026-07-18)
1897
+
1898
+ Background-lane spool-ification ([1103]/[1104]/[1105] — the darwin-G / TB service-family losses: `reaped:1` at 3/57 live trials). Overturns the 1.318 "bg lane deliberately unchanged" ruling on live evidence; three codex adversarial rounds.
1899
+
1900
+ - **fix: bg shells spool stdio to files (CC 2.1.212 parity, unix)** — under the old pipe shape a `nohup server &` grandchild held the driver's pipes, `close` never fired, the entry read "running" forever, and the engine-exit reap (`reapAllSessionBackground`, running rows only) group-killed the completed command's delivered service. With file stdio the driver's exit closes the entry immediately — the task-liveness gate now covers the background lane too. A genuinely RESIDENT driver keeps every semantic: hard-wall timeout, explicit kill, dispose group-kill. Retain-mode spooling is preserved on every platform (incl. win32); only the non-retain spool-ification is unix-scoped (win32 keeps pipes).
1901
+ - **ephemeral spool lifecycle** — non-retain bg spools ride the detach-adoption reclaim chain (quota rotation + hard cap in syncSpool, final fold + unlink at close/kill/dispose), plus an engine-owned unref'd 1s quota pump so an UNPOLLED resident firehose stays disk-bounded (pipes never touched disk; spools must not regress that). RB-26 extended: a delivered descendant writing to the unlinked inode after driver close remains the documented accepted residual (no path, no truncate, no babysitter — same posture, third concession).
1902
+ - **shutdown forensics covers the reap lane** — `shutdownDebug` moved to `core/shutdown-debug.ts`; the task registry's session-reap arms log every collected row (the [1103]③ blind spot that read as "zero kill logs" while a service died).
1903
+ - quality: 4 new pins (bg exited + running counter-pin + spool reclaim + unpolled firehose bound); full suite green (370 files); tsc 0; DeepSeek live 7/7.
1904
+
1905
+ ## 1.320.0 (2026-07-18)
1906
+
1907
+ Shutdown forensics channel ([1093]② / [1076]④ — the "log stops at draining_started" investigation dead-end).
1908
+
1909
+ - **feat: `SEMA_DEBUG_SHUTDOWN=1`** — opt-in stderr trace (lands in the host's engine log) of every teardown kill action: `disposeBackgroundShells` per-entry verdicts (running → group-kill / everything else → spare, the task-liveness gate made visible), `killProcessTree` invocations (pid/force/grace), the bg hard-wall timeout, and explicit `killBackground`. Zero output and zero overhead by default.
1910
+
1911
+ ## 1.319.0 (2026-07-18)
1912
+
1913
+ Stable errorCodes for the in-band error family ([1086]③ — full bucketing coverage for shells with zero shell changes). Four codex adversarial rounds (3 findings folded).
1914
+
1915
+ - **feat: in-band terminal-output codes** — the brains now stamp the canonical `[code]` prefix on in-band terminal error messages: `stream_torn` (stream ended with no content and no finish/stop reason — response lost), `refusal` (policy refusal / provider content filter), `length_empty` (max_tokens exhausted with no answer text; pairs with the existing `errorKind`). `assembleResult`'s existing lift chain lands them as `TaskResult.errorCode` with the prefix stripped — downstream consumers bucket on the code instead of string-matching prose.
1916
+ - **behavior-neutral by construction**: the circuit breaker keeps BOTH identities — an explicitly configured raw code counts as itself (`countCodes: ["stream_torn"]` works), otherwise it collapses to the legacy `"http"` bucket, so a custom `countCodes: ["http"]` policy keeps its exact pre-code behavior and the default stays neutral (model-output problems are not provider outages). Degradation maps the new codes to nothing; subagent retry classification keeps them non-retryable ("logic"), exactly as the previously-uncoded messages behaved; compaction's budget-escalation keys the structured `errorKind`, untouched.
1917
+ - `errorClassOf` folds the new codes to class `"brain"`; `ALL_BRAIN_ERROR_CODES` exported and the enum drift guard is now EXHAUSTIVE (the old guard only covered http-derivable codes).
1918
+ - test hardening: the reap F-C detach pin switched to a byte-conservation invariant (load-stable; replay form stays red).
1919
+ - quality: 9 new pins; full suite green (370 files); tsc 0; DeepSeek live 7/7.
1920
+
1921
+ ## 1.318.0 (2026-07-18)
1922
+
1923
+ The reap mechanism fix ([1076]① — the test line's #1 TB axis): foreground exec now matches CC 2.1.212's task-liveness gate. Five codex adversarial rounds (11 findings folded, 1 documented residual).
1924
+
1925
+ - **fix: foreground exec settles at shell exit; deliberately detached descendants survive** — foreground stdio now goes to SPOOL FILES, not pipes (CC's non-streaming stdio shape). A `server & disown` grandchild inheriting the fds can no longer hold completion open — the old pipe shape made `close` wait for every fd holder, hung the exec to its timeout, and the timeout's group-kill then slaughtered the detached service (gate P1, `cc PASS / sema FAIL`). Now the command settles in a short drain window after `exit`, the completed command never enters any kill set (task-liveness gate), and a descendant's later writes land in a file — EPIPE-immune even after the host exits. POSIX-only: win32 (and spool-open failure) keeps the historic pipe lane with its `close`/timeout semantics — an early settle would leave live pipes and callbacks attached to a released process.
1926
+ - **spool quota + true tail** — a 200ms pump streams file increments into the rolling tails/callbacks; unread backlog is capped at one 8MB retention window (drop-OLDEST, byte-accounted into the `[... N bytes truncated ...]` disclosure); a fully-consumed spool past 8MB is truncated to zero (O_APPEND rotation), and a firehose writer that outruns the reader hits a 16MB hard cap (truncate-and-account). Finalization skips to the TRUE final window and drains to EOF — a fast burst can no longer make the result carry the head instead of the real tail. Ran-then-cut partials (timeout/abort) get the same true-tail capture.
1927
+ - **detach adoption carries the full story** — a mid-flight detach hands the spool files to the background entry (file cursor tracked separately from logical byte totals — quota rotation makes them diverge), the adopted entry keeps the quota while running (drop-OLDEST across zero-poll burst windows; end markers survive), pre-detach loss travels with the seed (honest `bytesFromStart`/`bytesDroppedBeforeCursor`, `markTruncated` on the detached exec result), and the adopted spool is reclaimed at terminal cleanup (close/kill/dispose backstop). The design/128 retain lane and the BACKGROUND shell lane are deliberately unchanged (gate P2 is `cc FAIL / sema FAIL` — a consistent hard-wall contract, not a divergence).
1928
+ - `RollingTailBuffer.recordSkippedBytes` — O(1) accounting for source-side drops, so truncation disclosures report the true number of missing bytes.
1929
+ - RB-26 (documented residual): a detached descendant that keeps logging grows its unlinked spool inode until it exits — same unbounded exposure as CC's named-file shape, minus the file outliving the writer.
1930
+ - quality: 10 deterministic reap/spool pins (grandchild survival, late writes, quota, hard cap, true tail, pre-aborted detach, rotation×detach coherence, zero-poll burst adoption, pipe-fallback semantics); full suite green; tsc 0; DeepSeek live.
1931
+
1932
+ ## 1.317.0 (2026-07-18)
1933
+
1934
+ agent-team S3 (prompt plane + sibling routing) + the session-continuation listing dedup fix (clay field report) + a 1.316 behavior correction. Two codex adversarial rounds on top of S3's own two (11 findings total folded); live multi-turn conversational probes established (with a live-CC trace comparison baseline).
1935
+
1936
+ - **fix: session-continuation listing re-spam** — a NEW task on an EXISTING session (the standard conversational shape: every server AI-endpoint turn) re-announced the full agent/skills listings EVERY turn (~2K tokens) and piled them into history. The transcript probe now seeds continuations too, and it REPLAYS the exact announced set frame-by-frame (initial/delta/removal) instead of header-presence guessing — additions still delta, removal-to-zero still fires (both listing arms, symmetric), and the model catalog replays alongside (A→B drift re-announces). Replay reads ONLY listing-carrier stamped text (`engineMinted` frames + first-message engine prefix/segments) — user speech, assistant text, and generic engine notifications (task-notification/LSP frames embed untrusted result text) can never forge listing state. CC parity: first + delta only, never per-turn (live-CC trace baseline recorded in the probe file). RB-25 ledgers the structural-session-state end-state.
1937
+ - **fix (1.316 behavior correction): teammate identity vs display label** — S1's teammate signal keyed on the display label (description/agent-type, present on nearly every spawn), so ordinary described children were hierarchy-clamped and uplink-attributed as teammates. Teammate semantics now key on the EXPLICIT `Agent({name})` identity only (`RunInternals.explicitAgentName`); the clamp was over-wide (fail-closed direction, no security impact).
1938
+ - **feat: `COORDINATOR_ROLE_PROMPT` + `TEAMMATE_COMMUNICATION_ADDENDUM`** — the CC 2.1.212 coordinator system prompt (6 chapters, sema-ized: BYOM-neutral head, CC-only tool bullets removed, full "Executing user-approved actions" iron rule kept verbatim) and the teammate communication addendum, exported for deployments; the addendum also composes automatically as `core/mode.teammate` (rank 405) for a NAMED child WITH SendMessage actually mounted, with honest v1 capability wording (mid-run teammate delivery arrives with S2). Bundled-digest note: the new probe gate moves the epoch artifact identity once at this release (one legacy_migration per resumed session, by design).
1939
+ - **feat: sibling routing (S3a)** — a named child's SendMessage resolves SIBLINGS via a parent-owner retry (delivery only; TaskStop/TaskOutput keep the narrow axis; scope unchanged), the parent's retain ledger is threaded so a COMPLETED retained sibling can actually be continued, and sibling messages carry sender attribution.
1940
+ - `normalizeAgentName` exported ([1074]② single-source for server pg/tidb roster keying).
1941
+ - quality: full suite green; tsc 0; DeepSeek live incl. the new multi-turn probe; 14 new pins across three test files.
1942
+
1943
+ ## 1.316.0 (2026-07-18)
1944
+
1945
+ agent-team S1 (design/147, first slice) + the epoch declaration axis ([1068] ruling a). Four adversarial-review rounds (codex): 3 high + 3 medium folded in, 2 lifecycle items ledgered (RB-23/24).
1946
+
1947
+ - **feat: SendMessage("main") uplink** — a delegated child's message to `"main"` now DELIVERS: it lands in the parent run's injection queue (dedicated `RunInternals.parentNotify` lane — deliberately not the child's own notification entry, so internal child notifications never tee into the parent) and reaches the parent model at the next turn boundary as a `background_agent`/`event` task-notification (task_id = the child, `message from <name>` attribution, process-unique seq, priority "next", 8k-char clip with an honest truncation note). Top-level runs keep the honest placeholder. Retained resumes rewire the lane to the RESUMING caller's live injector (a spawn-turn injector is stale after its turn).
1948
+ - **feat: hierarchy clamp (CC 2.1.212 parity)** — a NAMED child (teammate) cannot spawn further named agents: "teammates cannot spawn other teammates — the team roster is flat." Judged on the trusted internals chain (`ToolExecuteContext.spawnedAgentName`/`parentTaskId`); the anonymous lane (a sema superset — CC subagents have no spawn face) is unchanged.
1949
+ - **feat: `RosterStore`** — the durable name→agent roster seam (`RunnerDeps.rosterStore`): core ships `MemoryRosterStore` + `FileRosterStore` (atomic replace, upsert per (name, owner, scope), corrupt-file-safe writes), a server deployment plugs pg/tidb behind the same interface. Named background spawns are recorded (advisory — a store fault never faults a spawn); SendMessage consults it after the live registry misses, with access filtered INSIDE the resolve (filter-before-reduce; explicit sessionScoped flag, TaskRegistry.canAccess parity) — a cross-scope entry reads exactly like a miss. v1 resolves to an honest pointer (`roster_only`), not a cross-run resume.
1950
+ - **feat: epoch declaration axis** — provider `stableBlocks` declarations join the session prompt-epoch artifactDigest as `(id, slot, chars, contentHash?)` — a config-center publish now re-pins sessions at the next legal boundary ([1043]②e made real; server restart-to-apply can relax). Declaration TEXT never enters the digest (dictionary-preimage surface — session readers don't see system prompts): byte-precise re-pin detection rides the declarer's opt-in verified contentHash (the center path always mints it); private declarations degrade to length-level detection, documented.
1951
+ - quality: full suite green; tsc 0; DeepSeek live 6/6; 13 new pins; export surface re-frozen (additive).
1952
+
1953
+ ## 1.315.0 (2026-07-17)
1954
+
1955
+ Digest tiering for the assembly manifest ([1050]② / [1057]①a — the center↔manifest reconciliation anchor). Seven adversarial-review rounds (codex), 6 findings folded in.
1956
+
1957
+ - **feat: `PromptTextDeclaration.contentHash`** — OPT-IN reconciliation digest on typed `stableBlocks` declarations: `sha256:<64 hex>` of the declaration text, precomputed by the declarer (e.g. a config-center publish pipeline). When present and correct it passes through VERBATIM to the `prompt.assembled` manifest section as `contentHash` (unsalted, end-to-end auditable next to the always-present salted `hash`); on mismatch it is dropped with a warn — never invented or recomputed.
1958
+ - **security posture (codex F1/F2)**: core NEVER derives an unsalted digest itself — neither section owner (`core/role.base`/`core/role.append` are owner "deployment" yet carry caller-private text) nor the provider lane (it may forward `ctx.userSystemPrompt`) licenses fingerprinting; only the declarer's explicit, correct digest does. Salted-hash discipline for every other section is unchanged.
1959
+ - hardening (codex F3-F6): the provider-assembled pass-through path suppresses the digest (it would surface under the wrong section identity) with explicit guidance; assembly warnings are non-throwing (a throwing deployment `onError` sink can no longer abort prepare); declarations are snapshotted once at the boundary (a getter/Proxy cannot desync verified digest from shipped text); `explainPromptAssembly` renders `content=` markers.
1960
+ - quality: full suite green; tsc 0; DeepSeek live 6/6; 6 new pins.
1961
+
1962
+ ## 1.314.0 (2026-07-17)
1963
+
1964
+ Tool-face controls (clay rulings [1043]③/[1044]②) + the center default-pack description. Eight adversarial-review rounds (codex), 9 findings folded in.
1965
+
1966
+ - **feat: `TaskSpec.excludeTools`** — a per-scenario tool ROSTER with true-unmount semantics: listed wire names are removed from the mount BEFORE prompt assembly, the manifest, collision audit and deferral classification — their schema bytes never reach `tools[]` (a permission deny gates calls but ships every schema byte; measured zero token savings). Unknown names are ignored (center-distributed rosters may cover unmounted tools).
1967
+ - **feat: `TaskSpec.deferTools`** — per-request deferred disclosure for ALREADY-MOUNTED tools (built-ins included — `ToolSpec.defer` only reaches caller specs): listed names ship as design/36 placeholders (schema bytes off the cache prefix) and activate via ToolSearch. "Default-on but not exposed" (Workflow, clay ruling B): the shell stamps the name on requests where no activation source fired; omitting it mounts the full face.
1968
+ - **hardening: the inheritance invariant survives every spawn path** — Agent children, retained-child resume snapshots, workflow governance base, the worktree overlay, and the observer sidecar all inherit both controls (union, tighten-only); the snapshot is captured synchronously at task start (frozen — a tool mutating `ctx.excludeTools` throws) so neither a caller mutating its live arrays during preparation nor a sabotaging tool can re-enable an excluded tool for later children.
1969
+ - **feat: `describeDefaultPack()`** — the center-facing read-only default-pack description: section metadata (id/slot/rank/ownership/locked) + the default role-base text (the append-mode compile skeleton for the [1051] three-card UI). Structure yes, prose no ([989] line; the role base is the one deliberate public exception).
1970
+ - quality: full suite green; tsc 0; DeepSeek live 6/6; 12 new pins across five test files.
1971
+
1972
+ ## 1.313.0 (2026-07-17)
1973
+
1974
+ RB-21: the cross-slice walltime budget (`totalWalltimeSec`), enforced. Five adversarial-review rounds (codex), 8 findings folded in.
1975
+
1976
+ - **feat: `resourceSuspend.totalWalltimeSec`** — the walltime sibling of `totalBudgetUsd`: a TOTAL wall-clock allocation across the whole resume→re-suspend chain. Each slice runs `min(timeoutSec, total − alreadyElapsed)`; elapsed accumulates on the ledger (`spentWalltimeMs`, monotonic-measured, clamp-not-refund) so a resume can never refresh the clock. Advisory pacing (finalize/nudges) follows the clamped window.
1977
+ - **feat: exhausted-walltime fail-fast** — a 0-remaining slice (first run with total 0, or a spent chain) runs ZERO model turns and ZERO tools; on an approval resume the claim lands BEFORE the decision machinery, so a human-approved (potentially irreversible) tool never executes on a spent clock. Terminal is `timeout`/`limit.timeout`, single-claim against the hard timer (RB-20 F3 guard).
1978
+ - **feat: `WALLTIME_CHECKPOINT_VERSION` (v4)** — resource, approval AND plan-review checkpoints stamp v4 whenever their ledger carries a walltime total, so a pre-RB-21 worker (MAX_SUPPORTED=3, no enforcement) rejects the row pre-CAS in a rolling deployment instead of granting a fresh window or executing the approved side effect. No total ⇒ historic v2/v3 stamps (zero version movement for non-users). `MAX_SUPPORTED_CHECKPOINT_VERSION` is now 4.
1979
+ - **fix: plan-review pauses persist elapsed time** — the plan_review mint previously carried `priorLedger` unchanged (each present_plan→reject cycle silently refreshed the clock AND dropped $ spend); it now rides the approval-gate debit (`countSlice:false` — a review is not a resource slice).
1980
+ - **fix: a real pre-upgrade ledger doesn't eat a newly supplied total** — remaining is keyed on the ledger's own total (prior wins), falling through to the spec total minus recorded spend.
1981
+ - new exports: `remainingWalltimeMs`, `WALLTIME_CHECKPOINT_VERSION`.
1982
+ - registered: RB-22 ($ exhaustion still executes an already-approved tool on resume — pre-existing semantics, separate ruling).
1983
+ - quality: full suite green; tsc 0; DeepSeek live 6/6; 7 new pins (ledger arithmetic incl. legacy shapes, window clamp, exhausted approval/first-slice/resume, v4 stamping on all three mint sites).
1984
+
1985
+ ## 1.312.0 (2026-07-17)
1986
+
1987
+ Fire-side half of the autonomous-loop sentinels ([1017]② ruling: single-source with the sentinel constants).
1988
+
1989
+ - **feat: `resolveAutonomousLoopPrompt(prompt, opts?)`** — resolves the two autonomous-loop sentinels into the tick instructions at fire time (CC 2.1.209 P6c, byte-verified against the binary): the cron tick, the dynamic-pacing tick (with the Monitor/TaskList/TaskStop fallback-heartbeat appendix), an optional first-tick `# Autonomous loop check` preamble (steward + persistent variants, exported as `AUTONOMOUS_LOOP_PREAMBLE`/`_PERSISTENT`), and an opt-in PushNotification ping appendix. Stateless: the first-tick latch and the resolve-at-all decision stay with the calling scheduler runtime; non-sentinel prompts (including the loop.md family — shell domain) return `null`. Additive.
1990
+ - quality: full suite green; tsc 0; DeepSeek live 6/6; anchor byte-diff 3/3 (preambles + both ticks vs the CC carve).
1991
+
1992
+ ## 1.311.0 (2026-07-17)
1993
+
1994
+ RB-20 (TB [991] finalize b-shape "announced, never stopped"): the walltime budget becomes a single monotonic fact with a synchronous enforcement leg. Five adversarial-review rounds (codex), 6 findings folded in.
1995
+
1996
+ - **fix: synchronous turn-boundary walltime backstop (non-suspend)** — a task past its elapsed budget is now stopped at the next clean turn boundary with the exact terminal the hard timer promises (`timeout` / `limit.timeout`). Previously the ONLY enforcement was `setTimeout` (a starved event loop delays it unboundedly — the ~1667s overrun frame), the turn cap is disabled whenever `timeoutSec` is set, and finalize is advisory — an uncooperative model met no stop. Trace: `task.end` gains `walltimeSyncBackstop` (attribution) and reports `timerLatenessMs` when the backstop caught a late timer.
1997
+ - **fix: walltime enforcement moves to the monotonic clock** — the backstop, the clean-suspend slice boundary AND the hard timer's delay all derive from one `taskStartMonotonic` anchor. Epoch `Date` is skew-prone in both directions: a forward NTP step/VM-resume would fire a false terminal (and fake a starvation lateness); a backward step starved the clean suspend until the +120s hard timer killed a resumable task into a terminal. Epoch remains only for advisory pacing (finalize/nudges).
1998
+ - **fix: timeout ownership is single-claim in both directions** — the timer callback yields when the boundary already claimed the stop; the boundary claim disarms the still-armed timer; a timer-first stop keeps its attribution (no double abort, no re-emitted events).
1999
+ - **fix: initialization spends the budget** — the monotonic deadline anchors at the taskStart instant (not post-initialization), the hard timer arms with the REMAINING budget, and the suspend grace is added before the clamp (an initialization overrun can no longer re-grant a fresh window or fresh grace).
2000
+ - registered: RB-21 (resume × walltime/finalize budget reset — slice-boundary semantics need a separate ruling).
2001
+ - quality: full suite 6020 green; tsc 0; DeepSeek live 6/6; walltime/suspend/tool-cut families re-pinned (6 new pins incl. epoch-skew and timer-race legs).
2002
+
2003
+ ## 1.310.0 (2026-07-17)
2004
+
2005
+ Server contract requests ([1001]②) + attribution closure.
2006
+
2007
+ - **feat: `instructionSources[].contentHash` accepts `null`** — a declared-but-absent instruction source (e.g. AGENTS.md above CLAUDE.md in the precedence chain) can be watched for its appearance; the change probe renders "absent → hash". Type widening only — the runtime already handled null.
2008
+ - **feat: typed `stableBlocks` pass-through guard** — an identity declaration carrying all three constitution anchors (an already-assembled prompt riding the typed hook) now routes through the provider-assembled pass-through with an upgrade warning instead of double-appending the constitution (the M9 guard, typed edition).
2009
+ - A6 attribution closed (RB-19): sampling noise on a low-base-rate task; prompt wording unchanged per evidence.
2010
+ - quality: full suite 6014 green; tsc 0; DeepSeek live 6/6.
2011
+
2012
+ ## 1.309.0 (2026-07-17)
2013
+
2014
+ TB2.0 certification findings, fix batch ([991] triage — the two behavioral families).
2015
+
2016
+ - **fix: premature finalize (two polluted legs)** — with fewer than 3 call samples, BOTH the cycle estimator's p75 and the dynamic write-out cushion (2× peak/EWMA, capped at 40% of budget) were owned by a single long pure-reasoning first call, firing the finalize at ~27% of the budget (658s remaining vs 363s estimate + 360s cushion — full-predicate replay pinned). Small windows now clamp each leg to the 60s prior; a genuinely slow endpoint earns its large estimate/cushion from the third consistent sample (TB-feal semantics preserved).
2017
+ - **fix: background-process lifetime honesty** — `run_in_background`'s description now states that background processes do not survive the session (reaped at task end) and points to self-detaching starts for living deliverables; the sentence disappears when the env declares `retainBackgroundProcesses`. `ScheduleWakeup` states that wakeups only fire while the session is alive. (The reap-at-drain behavior itself is unchanged and deliberate — the gap was the model not knowing.)
2018
+ - **feat: `supportsSessionWakeup` capability bit (opt-out)** — a host with no resident wakeup leg (headless single-shot: the process exits with the final answer) declares `false` and `ScheduleWakeup` refuses at schedule time with guidance, instead of enqueueing an intent nobody will honor. Undefined keeps the existing working semantic (deliberately the reverse default of `supportsSessionLifetime` — session wakeups already work on daemon-backed hosts).
2019
+ - registered: RB-19 (A6 attribution — evidence requested from the eval line), RB-20 (finalize gate-miss b-shape, separate mechanism).
2020
+ - quality: full suite green; tsc 0; DeepSeek live 6/6; golden sentinel audited (Bash description = the one declared drift).
2021
+
2022
+ ## 1.308.0 (2026-07-17)
2023
+
2024
+ Post-campaign review batch: a full-diff adversarial pass (codex) plus an independent promise-vs-implementation cross-review; 6+6 findings, all addressed.
2025
+
2026
+ - **fix: legacy `system()` providers receive `model.promptGuidance` again** — the campaign's single-entry adapter dropped the guidance append on the legacy branch (an undeclared byte regression vs the pre-campaign path; the goldens tested the two shapes separately, never combined). Restored with the historic join semantics + manifest block; combination pinned.
2027
+ - **fix: provider hook precedence pinned** — `stableBlocks > stableSystem > system`; a provider adding a modern hook while retaining `system()` no longer falls into the legacy branch.
2028
+ - **fix: content-bound artifact digest, round 2** — probe vectors extended from 2 to 11 (all-off, all-on, one per single gate) so mixed-gate prose (e.g. the hooks-only deny line) binds the digest; `engineVersion` removed from artifact identity (a release that doesn't touch the pack no longer re-pins every session — existing pins take ONE legacy_migration re-pin on first use under this version).
2029
+ - **hardening: shape-drift contract downgrade** — a wrapper that changes a tool's structural schema no longer reports the attached core contract (deterministic legacy downgrade); description-only re-renders and execute-swaps keep it.
2030
+ - **hardening: explain/identifier hygiene** — `explainPromptAssembly` sanitizes every identifier (control chars, length caps); `stableBlocks` ids must match `^[a-z0-9][a-z0-9/._-]*$` (≤128).
2031
+ - **fix: harness head single-sourced** — the pack's `core/harness.head` section now calls the new `harnessHeadLines()` export instead of carrying a copied body (an unregistered dual surface of sentence-index-protected lines).
2032
+ - registry: deadline/final-verification rows re-scoped to `once-per-run` dedupe (their latches are per-task, not per-epoch).
2033
+ - docs: traceability rows for §6.2/§9.3/§10.1/§10.2/§7.1-7.2 and the CC209 loadability table rewritten to the honest partial-adoption state; deferred items registered as RB-15…RB-18.
2034
+ - quality: full suite green; tsc 0; DeepSeek live 6/6; S4 atomic-face guard's mismatch branches now directly exercised.
2035
+
2036
+ ## 1.307.0 (2026-07-17)
2037
+
2038
+ Prompt-assembly campaign close (slice S6): observability capstone + debt clearance.
2039
+
2040
+ - **feat: `explainPromptAssembly`** — read-only formatter over the assembly manifest (+ optional epoch pin): constitution mode, epoch, block/section/tool composition, registered event kinds. Never echoes prompt text (ids/sizes/digests only).
2041
+ - **fix: scheduler upsert identity contract** — the `SchedulerCapability.schedule` contract now states the four-part upsert key `(scope, when, label, lifetime)`; lifetime is part of job identity (a session-tier and a durable-tier job sharing `(when,label)` must never overwrite each other — the server 1.215 F1 class, now un-repeatable from the contract text).
2042
+ - review-backlog clearance: RB-1 (R5 fix batch — focused re-review via campaign-era adversarial passes + 75-test pin sweep), RB-7 (consumer-enumeration lesson institutionalized in the campaign checklist, effectiveness proven twice), RB-12, RB-13 (suite-count baseline reset), RB-14 (both halves, with server 1.218.0). Stale prompt-too-long wiring note corrected.
2043
+ - the composition/tools/first-frame golden retires its "1.302 reference" meaning per campaign ruling and stays as a standing drift sentinel (undeclared byte drift fails CI; declared changes refreeze in the same PR).
2044
+ - campaign summary (S0-S6, releases 1.303.0→1.307.0): one deterministic Composer over a structured pack; typed provider hook; tool contract sidecar; session prompt epoch with content-bound digests; typed event registry with a single-sourced default matrix; physical system-block face with block-aware Anthropic lowering. Assembly bytes unchanged except the two operator-ruled behavior changes shipped in 1.303.0.
2045
+
2046
+ ## 1.306.0 (2026-07-17)
2047
+
2048
+ Prompt-assembly campaign, slice S4: the system prompt gains a physical-block face with block-aware provider lowering.
2049
+
2050
+ - **feat: physical system blocks** — `Context`/`AgentContext` gain an additive `systemBlocks` face (text + per-block cache-span boundary); pack layouts route sections into blocks by rank range without reordering. `sema-default@1` emits ONE block whose text equals the string face — Anthropic wire bytes are identical (golden-pinned); multi-block layouts (CC209's metadata/persona/body shape) are the compat-pack capability, wire-pinned with per-block `cache_control`.
2051
+ - **atomic-face guard (adversarial review)** — blocks attach to a context only when they provably correspond to the string face: never alongside a callback system prompt, never across a mid-run prompt override, and only under byte-exact join identity; any mismatch degrades to the string face (which every brain reads — the M13 projection duty). An all-empty block face canonicalizes to the same no-system wire as an empty string.
2052
+ - **RB-3 cleared: `stripEngineMetadata`** — projection helper for custom Brains that whole-object-serialize messages; strips the four engine-provenance fields that must never reach a provider payload (exported via the internal facade, vendor-insulation-clean).
2053
+ - quality: full suite 6000 green; tsc 0; DeepSeek live 6/6; adversarial review REOPEN→fixed (2 findings, both pinned).
2054
+
2055
+ ## 1.305.0 (2026-07-17)
2056
+
2057
+ Prompt-assembly campaign, slice S5 (+ the RB-14 backend seam): the runtime injection surface is a typed catalog.
2058
+
2059
+ - **feat: event prompt registry** — every runtime message-injection kind (12 turn-attachment kinds + the deadline/finalize lane) is a registered `CompiledEventPrompt` row: carrier, trust, dedupe semantics, byte budget, and default policy. Mechanism/copy separation: rows index the render functions where they live (attribution comments untouched). The attachment default-on carve-out (agent/skills listings only) is now single-sourced from the registry (`eventDefaultOn`), consumed live by the run loop. Zero behavior change (golden + full attachment suites byte-pinned).
2060
+ - **feat: `SessionStorage.getEpochAnchor` (optional)** — bounded-tail durable backends can surface the below-load-floor epoch carrier; `Session.getPromptEpoch()` falls back to it (same strict normalization gate; in-branch carriers always win). Closes the core half of the S3 review's bounded-tail rewind boundary; file/in-memory backends unaffected.
2061
+ - exported: `EVENT_PROMPT_REGISTRY`/`eventDefaultOn`/`CompiledEventPrompt`/`CompiledMessageInjection`.
2062
+ - test infra: the prompt golden's date mask now covers both leg-date shapes (a UTC-midnight rollover between freeze and compare flipped the fixture red — caught live tonight).
2063
+
2064
+ ## 1.304.0 (2026-07-17)
2065
+
2066
+ Prompt-assembly campaign, slice S3: sessions pin their prompt-pack artifact.
2067
+
2068
+ - **feat: session prompt epoch** — every session records a first-class `prompt_epoch` entry (before any conversation entry) pinning the prompt-pack artifact it runs on; every compaction restates the pin in its `details.promptEpoch` (same-CAS with the new baseline, bounded-tail recoverable). v1 resolution is bundled-only: a pin from another engine build (or a pre-epoch session) records one `legacy_migration` re-pin — byte-identical to today's upgrade-in-place behavior, now observable. Assembly output is unchanged by this slice.
2069
+ - **feat: content-bound artifact digest** — the bundled artifact digest hashes each section's probe-rendered text under two runtime-fact states plus the structural declaration, so prose, admission-gate, structure, or build changes all change identity (adversarial review F1).
2070
+ - **hardening (adversarial review F2)** — both epoch carriers (first-class entries and compaction restatements) pass one strict normalization (full sha256 hex, bounded integer epoch, closed enums); the session-import gate rejects malformed carriers fail-closed; digest match alone is not identity (packId/assemblyApi must agree).
2071
+ - fix: `resumeAtMode:"before"` first-message rejection and the project-memory `fresh` phase now key on conversation content rather than physical root/empty-branch (the epoch pin precedes the first message).
2072
+ - exported: `PromptEpochDescriptor`/`computeBundledArtifactDigest`/`normalizePromptEpoch` (session-backend and observability consumers).
2073
+ - known boundary (RB-14): bounded-tail durable backends (pg/tidb) must keep an epoch carrier reachable for pre-compaction rewind cuts — pickup slip issued; file backend unaffected (full-tree walk).
2074
+
2075
+ ## 1.303.0 (2026-07-17)
2076
+
2077
+ Prompt-assembly campaign, slices S1+S2: a single deterministic prompt Composer replaces the five string-concatenation branches in task preparation, and every mounted tool gains a contract-identity sidecar. **Two deliberate behavior changes below.**
2078
+
2079
+ - **feat: prompt Composer + `sema-default@1` pack** — every system-prompt block is now a declared section (semantic slot, emission rank, owner/trust/mutability, render cadence, cache class) in a structured pack that indexes the existing prompt constants in place; assembly is one validated `compose()` path. Byte-compatibility with the previous assembly is pinned by an exhaustive feature-gate golden (`test/prompt-golden-freeze.test.ts`).
2080
+ - **feat: `PromptProvider.stableBlocks` typed hook** — providers can return structured `{id, slot, text}` declarations (identity/scenario/behavior) instead of one opaque string; each lands as its own attributed, digested manifest section. Implemented ⇒ `stableSystem` is ignored. The `core/` id namespace is reserved (collision = task failure, never a silent skip).
2081
+ - **feat: `prompt.assembled` manifest v2 (additive)** — the trace event gains a `sections` array (per-section slot/carrier/cadence/cacheClass + salted digest) and a `tools` array (per-tool contract id, durable-replay aliases, presentation-invariant shape digest, wire-schema digest). The v1 `constitution` + `blocks[].id` wordlist is unchanged.
2082
+ - **feat: tool contract sidecar (S2)** — `ToolSpec.contract` declares a versioned execution-contract identity (`core.read@1`-style); all 44 built-in tool spec declarations are in place (42 unique contract ids; readonly Bash and the dual TaskOutput/TaskStop faces share names deliberately). Undeclared caller tools report a deterministic `legacy:<shapeDigest>` contract; a caller tool shadowing a built-in wire name never inherits the core contract (identity follows the object lineage, surviving the mount pipeline's shallow copies). Zero wire-byte change (golden-pinned). `attachToolContract`/`getToolContract`/`projectToolManifest` exported for deployment factories.
2083
+ - **BREAKING(behavior): `replaceAll` narrowed to non-locked slots** — a `replaceAll` provider still owns the persona/identity face, but the locked sections (security policy, harness mechanism truth, context-management honesty) now always compose structurally and can no longer be dropped. Supersedes the 1.243 full-sovereignty semantics per operator ruling.
2084
+ - **fix(cache order): `model.promptGuidance` moved before the volatile tails** — guidance now composes as a `behavior` section ahead of the MCP/environment/memory tails instead of trailing the volatile memory block, so a memory change no longer invalidates the guidance suffix.
2085
+ - retired: the dead legacy memory constitution branch (`memoryEnabled` was pinned false since the memory-engine rearchitecture; zero byte change). `MEMORY_SAFETY`/`MEMORY_HYGIENE` constants remain exported.
2086
+ - compat: `TaskSpec.systemPrompt`/`RoleSpec.systemPrompt`/`appendSystemPrompt` precedence and bytes unchanged; role-layer `stableSystem`, the already-assembled-prompt guard, and the deprecated free-form `system()` path (incl. its memory-tail lint) behave identically. Downstream consumption surveys: cli/web report zero manifest/replaceAll consumers.
2087
+
2088
+ ## 1.302.1 (2026-07-17)
2089
+
2090
+ Docs-and-infrastructure patch (no code changes): single-plex CI (test gate + tag-triggered npmjs publish with registry verification + hygiene reminder), refreshed KNOWN-ISSUES (two stale entries resolved, two current limitations documented), working docs promoted (review backlog, prompt-assembly design pair, sentence-evidence index), internal design coordinates scrubbed from docs. First release published by the repository's own CI.
2091
+
2092
+ ## 1.302.0 (2026-07-17)
2093
+
2094
+ Project-instruction awareness, core half: memory loading gains session context and change detection; ingested entries get read-side integrity guarantees.
2095
+
2096
+ - **feat: `loadProjectMemory` receives call context** — the loader is now invoked with `{sessionId, phase}` (including a post-compact re-read phase), so hosts can cache per session and refresh instruction snapshots at the right moments instead of guessing.
2097
+ - **feat: instructions-change tail-attachment lane** — a new `probeInstructionSources` runner seam lets the host fingerprint instruction files (path + content hash); when a source changes mid-session, the engine attaches a tail note instead of re-rendering the cached prefix.
2098
+ - **fix: repo-ingested memory entries are not projected** — entries with repo provenance are excluded from the memory projection surface; they inform, they do not impersonate user memory.
2099
+ - **fix: read-side whitewash refusal** — reading an entry can no longer launder away its provenance/trust fields; the write-side refusal from 1.301.0 now has a read-side counterpart.
2100
+ - quality: full suite green; type-check clean; live smoke passed.
2101
+
2102
+ ## 1.301.0 (2026-07-16)
2103
+
2104
+ Contract-portability release: named agent addressing, scheduler lifetime tiers, and a prompt-cache-stable tool surface. **Several breaking changes below.**
2105
+
2106
+ - **feat: named background agents** — `Agent` gains `name` (addressable) and `cwd` (absolute path, enforced or fail-loud) parameters; `SendMessage` accepts a name or task id (newest agent wins a reused name; `"main"` is reserved), with did-you-mean suggestions on a miss.
2107
+ - **feat: scheduler lifetime tiers** — `CronCreate` accepts the compact `{cron, prompt, recurring, durable}` call shape alongside the schedule-object form; `durable: false` (the compact-form default) schedules a session-scoped task that is reaped when the session ends, while the object form stays persistent. Backends declare `supportsSessionLifetime`; without it, session-tier requests are refused honestly instead of silently becoming persistent. `CronList` now shows `[session]`/`[once]` tier markers.
2108
+ - **BREAKING: dynamic listings moved out of the cached prompt prefix** — agent/skill/model listings no longer ride tool descriptions or the system prompt; they arrive as reminders on the first user turn and as drift notes afterwards. Changing the roster/skills/models no longer invalidates the prompt cache (measured: ~10.6K tokens re-written per change before, byte-equal prefix now). `subagent_type` is a free string (unknown values get a corrective error listing valid types); listing reminders default on.
2109
+ - **feat: engine-content provenance on user messages** — messages the engine assembles or injects are tagged (`enginePrefixChars`/`engineSegments`/`engineMinted`/`provenance`), so the permission classifier, compaction summarizer, and prompt suggestions no longer attribute engine text to the user. Metadata never reaches the wire; malformed metadata degrades to verbatim, never crashes.
2110
+ - **feat: memory ingest groundwork** — entry frontmatter gains `provenance`/`trust` fields with a contract-enforced whitewash refusal (an update cannot strip provenance from a repo-ingested entry); the memory instruction template's project-file name is parameterizable; `ProjectMemoryLoad` can declare instruction-source fingerprints.
2111
+ - quality: full suite green (6374); type-check clean; live-model matrix passed (named-resume chain, both scheduler call shapes, first-turn listing visibility).
2112
+
2113
+ ## 1.300.0 (2026-07-16)
2114
+
2115
+ Tool-permission observability plus two behavior alignments. **Two breaking default changes below.**
2116
+
2117
+ - **feat: tool-policy name audit** — name-keyed policies (`createAllowDenyPolicy`, `createApprovalPolicy`) now expose their raw name lists (`nameSets`, aggregated through `combinePolicies`/`tightenTaskSpec` and the runner's `gateBaseline`); at prepare time the runner cross-checks them against the actually-mounted tool universe and emits a host-side advisory (`onError`, code `config.toolpolicy.unmatched_names`) for entries that cannot match anything this run — e.g. a deny rule written against a name the deployment advertises but does not mount. Advisory only: enforcement is unchanged. Advisories are deduplicated per deployment.
2118
+ - **BREAKING: the bash git guidance ships no commit-attribution trailer by default** — attribution is a deployment identity asset; set `hands.commitCoAuthor` to advertise one. The removed built-in default carried a third-party product name.
2119
+ - **BREAKING: `EnterWorktree` defaults `baseRef` to `"fresh"`** (default-branch tip) instead of `"head"`, so the same instruction starts from the same baseline everywhere; `"head"` remains available explicitly, and a repository without a remote default branch falls back to HEAD with an explicit note.
2120
+ - docs: divergence registry extended (scheduler family marked open pending first-hand re-anchoring; background-output overflow protection documented as mechanism-based by design).
2121
+ - quality: adversarially reviewed (three rounds); 13 new regression pins; full suite green; live-model probes passed.
2122
+
2123
+ ## 1.299.1 (2026-07-16)
2124
+
2125
+ Patch release: empty user-frame guards across the stack.
2126
+
2127
+ - **fix: message assembly self-heals empty frames** — an empty user message, an empty text block, an empty nested tool-result content, and an empty text part in a multimodal message are all backfilled or dropped at send time (strict endpoints reject them); stored history is untouched, so previously affected sessions recover on their next request.
2128
+ - **fix: an empty or whitespace-only `objective` on a fresh run fails loud** (`config.empty_objective`) instead of persisting an empty user frame; checkpoint resumes are unaffected.
2129
+ - **fix: empty `steer`/`followUp`/`nextTurn` injections are no-ops** — they carry no information and no longer mint a user frame or extend a finished run.
2130
+ - quality: adversarially reviewed (two rounds, all findings fixed); eight regression pins including the exact incident frame shape; full suite green; live-model probes passed.
2131
+
2132
+ ## 1.299.0 (2026-07-16)
2133
+
2134
+ Two engine fixes driven by production evaluation evidence, plus a design constitution.
2135
+
2136
+ - **fix: the system-prefix date is now frozen for the run leg** — matching verified upstream behavior, a mid-run date change no longer re-renders the prompt (which invalidated the whole prompt cache at midnight); the model learns the new date from a tail `date_change` reminder instead. Resumed legs render with the current date as before. Behavior note: consumers relying on the prefix always showing today's date should read the tail reminder.
2137
+ - **fix: the graceful-finalize cycle estimator is outlier-robust** — it now uses the p75 of the last 8 per-leg samples instead of `max(EWMA, decayed peak)`, so a single minutes-long command no longer triggers the final write-out instruction with nearly half the budget remaining. Sustained-heavy workloads still read heavy; under-estimates remain bounded by the absolute call deadline, process-group release, and the cutoff write-out window.
2138
+ - design governance: adopted mechanism-design ground rules — real-scenario needs are the only source of behavior changes; benchmark scores are observations, not KPIs; honesty over output when budgets run out.
2139
+ - quality: full suite green; live-model probes passed; adversarially reviewed.
2140
+
2141
+ ## 1.298.0 (2026-07-16)
2142
+
2143
+ Naming alignment: the coding-scenario constants are renamed to describe their content.
2144
+
2145
+ - **feat: `FULL_BODY_*` family renamed to `CODE_*`** — `CODE_SYSTEM_PROMPT` (the complete engineering persona, paired with the neutral `DEFAULT_SYSTEM_PROMPT`), `CODE_ROLE`, `assembleCodeTools`, `CodeToolsConfig`. The old names remain as deprecated identity aliases (same values, not copies) for one transition window.
2146
+ - quality: alias-identity tests; export-surface snapshot updated; full suite green; live-model smoke of the renamed role passed. No behavior change (same bytes).
2147
+
2148
+ ## 1.297.0 (2026-07-16)
2149
+
2150
+ Behavior alignment for per-task agent tool lists, verified against upstream and adversarially reviewed.
2151
+
2152
+ - **fix: unknown `allowTools` entries on `TaskSpec.agents` no longer fail the run** — they are filtered item-by-item at spawn (the agent stays listed and delegable), matching verified upstream behavior. The only disclosure is a host-side heuristic advisory (`onError`, phase `"config"`); nothing reaches model content. `denyTools` keeps its fail-loud validation (a deny typo silently widens capability — the opposite failure direction).
2153
+ - **fix: allow/deny entries now match through declared tool aliases** as well as primary names, so a deny naming an alias can no longer silently stop denying.
2154
+ - docs: the roster listing is documented as the authored declaration, not the effective child roster.
2155
+ - quality: full suite green; live-model probe of the delegation chain passed. Behavior change: specs previously rejected for an allow-list typo now run (the entry is filtered) — consume the host advisory if you relied on the rejection.
2156
+
2157
+ ## 1.296.1 (2026-07-16)
2158
+
2159
+ Patch release: final-review findings for 1.296.0 plus cross-repo policy fixes.
2160
+
2161
+ - **fix: retry backoff can no longer cross the absolute call deadline** — a past-deadline attempt is never sent, and a crossing backoff converts to the deadline cut immediately instead of overwriting it with the retry's own error.
2162
+ - **fix: kill-escalation identity anchor refined** — when the group leader exits during the grace window but its descendants keep the process group alive, the escalation now sends a group-directed SIGKILL instead of cancelling entirely; the direct-pid leg (where pid-reuse risk lives) stays cancelled.
2163
+ - **fix: write-target extraction is tool-aware** — canonical `NotebookEdit` is judged on `notebook_path` first in both the fs-write gate and the sensitive-path policy (a stray `file_path` key can no longer be the confined target), and the sensitive-path default guarded set now includes `NotebookEdit`.
2164
+ - **fix: `TaskSpec.agents` deny-list entries naming unknown tools now fail loud**, mirroring the allow-list validation.
2165
+ - **fix: delegated sub-agents inherit the parent task's `getApiKeyAndHeaders`** — a child pinned to a cross-provider model resolves its key through the same hook as the parent instead of falling back to the global key.
2166
+ - quality: full suite green; live-model probes for the auth-inheritance path and the wall-clock lane passed. No breaking changes.
2167
+
2168
+ ## 1.296.0 (2026-07-16)
2169
+
2170
+ Engine resilience batch driven by production evaluation-run evidence, plus an interactive-tool mounting gate aligned with upstream behavior.
2171
+
2172
+ - **feat: stream-stall monitors auto-armed on the wall-clock lane** — `StreamOptions.stallTimeouts` (additive): budgeted tasks get advisory connect/first-token/idle monitor defaults; an explicit brain-constructor value (including 0 = disabled) always wins. Unbudgeted tasks are unchanged.
2173
+ - **feat: absolute timer for the soft call deadline** — the deadline now also fires on a real timer (covering the connect phase), no longer relying on stream chunks arriving.
2174
+ - **feat: write-out-window tool cut now releases the underlying process** (finalize mode only) — so a heavy foreground command can no longer consume the salvage window after being cut; graceful escalation is identity-checked and never awaited. Ordinary cuts are unchanged.
2175
+ - **feat: scheduling-delay telemetry** — `timerLatenessMs` on task end and `callStartedAt` on call traces make environment-induced timer delays diagnosable from a single field.
2176
+ - **fix: first-call throughput prior recalibrated 15 → 40 tok/s** from measured evidence; the old prior truncated legitimate first-turn output on modern endpoints.
2177
+ - **feat: interactive-tool mounting gate** — `AskUserQuestion` / `present_plan` mount only when a deployment has a way to deliver the question to a person (live callback or a durable review surface); otherwise they are omitted from the tool roster, matching upstream non-interactive behavior. New `TaskSpec.interactiveTools` knob (always-on / always-off / auto).
2178
+ - **fix: sema-tb objective input contract** — new `--objective-file` and stdin legs for long task text; missing flag values are now a usage error.
2179
+ - quality: two independent review tracks, all reopened findings fixed and re-verified; ~50 new tests; full suite 6204 green; tsc clean; live-model wall-clock-lane probe passed. No breaking changes (all additive; the two documented mounting-gate behavior changes can be restored via the `interactiveTools` knob).
2180
+
2181
+ ## 1.295.0 (2026-07-15)
2182
+
2183
+ Upstream-alignment batch for the tool surface (⚠️ BREAKING: Grep semantics and defaults, Glob ordering, TodoWrite default unmount, Bash background receipts), background-process retention, provider max-tokens correctness, a large hardening pass on the Grep JS-fallback pattern screen, and four pulled-forward features (three-valued `OnAsk`, recommended sensitive-path patterns, session content defenses, per-task subagents + tool-activity timestamps). Adversarially reviewed across ten rounds to SHIP and verified end-to-end against a live model.
2184
+
2185
+ - **⚠️ BREAKING: tool-surface alignment**: ① **Grep** — `pattern` is now always interpreted as a regular expression (the literal-string auto-detection is removed), and the default `output_mode` is `files_with_matches` (previously content; pass `output_mode:"content"` explicitly for the old behavior). ② **Glob** results are sorted by modification time (newest first) instead of lexicographically. ③ **TodoWrite is unmounted by default** — the task-list tool family is the default; only an explicit `taskList:false` restores TodoWrite. ④ **Bash background receipts changed shape** — a `run_in_background` acceptance receipt now carries the output-file path, and `TaskOutput` is marked deprecated (terminal notifications carry the path for direct reads).
2186
+ - **feat: background-process retention** — `TaskSpec.retainBackgroundProcesses?: boolean`: background shells survive task teardown under an explicit retention ledger (registry rows are marked before session-scoped early exits; teardown settlement carries the session identity so deployments that set an explicit task id still reach session-resident rows; disposal keep-alive, eviction, and reaping all honor the mark; on win32 retention is best-effort and documented as such). Reads of a background shell's output file are exempted from the read gate by canonical key, bound to the exact environment instance.
2187
+ - **feat: provider max-tokens correctness** — the OpenAI-compatible lane now always sends a max-tokens field (`compat.maxTokensField` wins when set; a conservative model-id heuristic picks between `max_tokens` and `max_completion_tokens` otherwise — verified live that some providers silently ignore the wrong field rather than erroring), and empty-truncated responses report the actual limit value and its source lane.
2188
+ - **feat: Grep JS-fallback pattern screen** — a single-pass analyzer refuses pathological-backtracking shapes (nested unbounded quantifiers, unbounded × alternation, bounded-outer products over a ceiling, adjacent equivalent quantified atoms) with full escape-token and dialect awareness built up over nine review rounds: exact escape-token boundaries (`\xHH`/`\uHHHH`/`\cX`/octal vs. backreference via a capture-group prescan, named-backreference gating), no-`u` dialect readings (`\p`/`\P`/`\u{…}` bad-tail identities), legacy case canonicalization under `i`, Annex B digit-escape splitting, class-context single-codepoint equivalence (including `[\b]`, class-internal octals, degenerate ranges), and the ECMAScript empty-class rule (`[]` closes immediately — the POSIX leading-`]` reading is rejected). Patterns the static screen cannot classify fall into a gray zone guarded by a line-length gate and a per-line evaluation budget; the guards are injectable for embedders.
2189
+ - **feat: three-valued `OnAsk`** — an `onAsk` callback may now return `"unavailable"` in addition to a boolean: it signals, at the moment a specific ask arrives, that no live approver can be reached synchronously, and routes that ask to the durable-approval parking leg (equivalent to the pre-sync behavior; without a parking facility the fail-closed auto-deny stands). Boolean implementations are unaffected. Durable deployments can now wire `onAsk` and keep their approval center.
2190
+ - **feat: `RECOMMENDED_SENSITIVE_PATTERNS`** — an exported, documented default pattern set for the sensitive-path policy (dotenv family, key/credential files, `.ssh`, `.git` hooks and config, cloud credential directories, registry auth files, shell history), with tests pinning both hits and deliberate non-hits (e.g. `src/env.ts` is never matched).
2191
+ - **feat: session content defenses** — ① the session import gate now validates message content shape per role (a bare object written as a content block is rejected at the door with a precise path); ② a fail-soft normalization leg shared by the provider conversion and the compaction serializer turns structurally-invalid or role-illegal blocks into clearly-prefixed text blocks instead of throwing or silently dropping them (legal messages pass through by reference, byte-identical). The two layers are deliberately redundant: the gate keeps new sessions clean, the salvage leg keeps historically-poisoned sessions serviceable.
2192
+ - **feat: per-task subagents** — `TaskSpec.agents?: AgentDefinition[]` merges per-task agent definitions into the mounted delegation tool's roster (same-name per-task definitions win; the merged roster re-runs full assembly validation; unknown tool names in a per-task definition's allowlist fail loud at task start; per-task definitions cannot widen parent or deployment policy — the tighten-only inheritance chain is untouched; definition arrays are defensively snapshotted so post-mount mutation cannot desynchronize the advertised roster).
2193
+ - **feat: tool-activity timestamps** — `ToolActivity.at?: number` (epoch ms) on both start and end beats, enabling per-call duration and inter-call idle derivation downstream.
2194
+ - quality: ten adversarial review rounds to SHIP (including two independently-demonstrated defects fixed and re-verified: role-illegal content blocks silently dropped by the serializer, and roster aliasing through mutable definition arrays); ~90 new tests; full suite 6142 green, tsc clean; live-model end-to-end probes for the new surfaces (per-task agent delegation, three-valued ask parking + boolean regression, retention, max-tokens wire evidence) all passed.
2195
+
2196
+ ## 1.294.0 (2026-07-15)
2197
+
2198
+ Permissions batch: parent effective-policy inheritance for child agents (tighten-only), synchronous approval cards with a live approver, and a session-exemption seam for the file-write gate. Adversarially reviewed across six rounds (20+ findings, all addressed) and verified end-to-end against a live model.
2199
+
2200
+ - **feat: parent effective-policy inheritance**: child tasks (workflow `agent()`/`agentStream()`, and all four Agent-tool legs — sync/steer/background/fork) now inherit the parent task's **evaluated** final policy across four layers, tighten-only: ① the resolved request-level tool policy (`spec ?? deps`), carried as an opaque constraint chain where a shared policy instance is evaluated once and each ancestor's frozen arbitration context (approver, durable mandate) applies independently; ② session permission rules, carried as data snapshots re-read live when the store is reachable (authorized operator changes propagate; lagging replicas can never widen) and recompiled against the **child's** environment; ③ the skill-manifest layer (already inherited); ④ shell-gate strictness by max-rank. **Rewrite-safe end to end**: a final-form re-check guarantees that rewritten arguments never execute without every affected layer's consent; sanitizing rewrites encountered during re-check fail closed; approvers always see the arguments that will actually execute. **Durable half**: checkpoints persist the data projection plus a constraint count (resume supplies are length-validated), the opaque half auto-resumes in-process via a lifecycle-managed registry (terminal/re-suspend/destroy/failed-reopen all clean up), cross-process resume re-supplies via `resumeStream(..., internals)`, and a missing supply fails loud with the checkpoint kept pending. `extraTools` factories are documented as not being a supported spawn seam (the child's own gate is the backstop).
2201
+ - **feat: synchronous approval with a live approver**: when a live `onAsk` handler is present, non-safety asks resolve synchronously in-turn (zero checkpoint, prompt cache preserved); `durableApproval` is demoted to the no-approver fallback; safety asks and the `forceDurableGate` control-plane mandate still park. Inherited-constraint asks follow the same rule — with a live approver, a child agent's ask surfaces as an approval card instead of being silently denied (matching upstream child-agent behavior).
2202
+ - **feat: file-write gate exemption seam**: `createFsWriteGatePolicy` gains `isExempt?: (toolName, canonicalPath)` — canonical-key matching (symlink aliases hit the same key), consulted before emitting an ask, errors fail closed as not-exempt, and sensitive-path denies always win. A session-wide "allow all" exemption takes effect immediately for the parent and inheriting children within the same turn.
2203
+ - **fix: `reopen()` returning false is no longer swallowed** — surfaces as a typed `checkpoint.reopen_failed` (the original error stays on the cause chain).
2204
+ - quality: six adversarial review rounds (20+ findings, all addressed; HIGH/MED fixes rollback-verified); 90+ new tests across inheritance, gating, exemption, and registry lifecycles; full suite green; tsc clean; live-model end-to-end probes (parent deny propagating to delegated children, synchronous approval cards both legs, same-turn exemption, workflow inheritance coexisting with child naming) passed across multiple re-runs. No breaking changes (all additive; callers that pass no inheritance internals see zero change).
2205
+
2206
+ ## 1.293.0 (2026-07-15)
2207
+
2208
+ Skill truncation moved to the correct moment (full text at invoke, bounded retention at compaction), a cancellation seam for manual compaction, full child-agent naming for workflows, and skill file attachments. Adversarially reviewed in two rounds (2 HIGH + 6 MED + 4 LOW follow-ups, all addressed) and verified end-to-end against a live model.
2209
+
2210
+ - **fix: skill content is no longer truncated at invoke time**: a skill's full body now enters context on invoke (the previous 20k-character invoke gate silently cut large skills); the only load-time gate is 1MB — an oversized skill is rejected whole (diagnosable `onError(phase:"config")`, removed from both the disclosure surface and the tool; duplicate-name first-wins is preserved — an oversized winner does not yield to a same-name loser). Truncation now happens where it belongs: a new **invoked-skills retention region rebuilt at compaction** — per-skill 20k-character cap with an in-band marker pointing to re-invoke, a 100k-character total budget (over-budget skills are skipped individually so smaller older ones can still fill in), most-recently-invoked first. The region is **window-aware on both sides**: the writer clamps it against the model's actual headroom (window − reserve − keep-recent; the fixed caps are ceilings, not guarantees), and context assembly re-budgets defensively (compact on a large window, resume on a small one — never over-window). Skill bodies fed to the summarizer are replaced with bounded placeholders (tool-call pairing preserved; the placeholder honestly states whether the content made it into retention), so full skill text is neither double-counted nor re-summarized; model-supplied invocation arguments are stripped from retention (they are not skill instructions and no longer persist across compactions under that framing). Re-invoking after a truncated retention copy always serves the full text.
2211
+ - **feat: manual compaction cancellation**: `compact(opts?: {instructions?, signal?})` — an aborted request always resolves `"mooted"` and is never armed (the abort is re-checked after readiness waits); cancellation attribution rides an error marker (`isCompactionManualCancel`) rather than live signal state, so a genuine summarizer failure racing a late cancel still feeds the failure breaker and `onError`; when a walltime abort and a manual cancel race, the first cause wins (latched synchronously); with coalesced concurrent requests, a cancelling caller withdraws its own summarization instructions (each waiter carries its own override).
2212
+ - **feat: workflow child agents are named end-to-end**: the `label` from `agent()`/`agentStream()` (or the generated fallback name) now travels through the trusted internals naming channel (sanitized by the existing consumer-side chain), and the workflow tool threads its call id and event sink into children — child `task_progress` frames are actually emitted and bubble to the host stream, so fleet/monitor rows show real names (directly started workflows fabricate no ids); the supervisor prompt now teaches `label`/`phase` usage.
2213
+ - **feat: `SkillSpec.files`**: skills can carry file attachments, delivered inline on invoke — with a strict 50k fully-rendered budget (headers, separators, markers, and the omission disclosure all count), a 512-character per-path cap (ellipsis included), and an in-band omission count when over budget. Core never writes attachments to disk.
2214
+ - **feat: `VerifyConfig.verifierHandsReadOnly?`**: an explicit knob for the verifier's read-only clamp (defaults to `true`, zero regression).
2215
+ - **chore**: default max turns raised 500 → 1000.
2216
+ - quality: two adversarial review rounds (12 findings, all addressed, HIGH/MED fixes rollback-verified); resumeAt × compaction crossing pinned in both modes; ~50 new or rewritten tests; full suite green; tsc clean; live-model end-to-end probes (57k-skill codeword chain across invoke/compaction/re-invoke, workflow `task_progress` naming, compaction cancel both legs, 1MB rejection) all passed, re-run after the review follow-ups. No breaking changes (all additive; the invoke-truncation change turns silent data loss into recoverable behavior).
2217
+
2218
+ ## 1.292.0 (2026-07-15)
2219
+
2220
+ Rewind exclusive-mode P0 fix (UI history truncation previously left the removed message in the model's context) plus verification-gate provenance. Adversarially reviewed (2 HIGH + 2 MED follow-ups, all addressed).
2221
+
2222
+ - **fix: `TaskSpec.resumeAtMode?: "at" | "before"`**: exclusive UI transcript truncation previously mapped to the engine's inclusive resume — the removed user message stayed in context as the leaf and the model kept answering it. The new additive mode resolves the target and sets the leaf to its parent (exclusive); the default `"at"` keeps the existing inclusive behavior byte-for-byte. Boundary tightening from review: `"before"` accepts only plain user-message targets (other targets can have mid-turn parents); the first message is rejected (`before_root_unsupported` — a second root would break the session-import single-root invariant; "rewind to the start" = start a new session); with file rewind, the file anchor walks up the parent chain to the nearest snapshotted ancestor (context and file anchors stay consistent; unresolvable = loud failure, no silent no-op leg). UI consumers doing exclusive truncation must pass `resumeAtMode:"before"`.
2223
+ - **docs: verification-gate provenance**: header rewritten — distilled from the MIT-era upstream's dedicated verification agent (an internal A/B, third-party default OFF, since retired upstream; the current completion-gate shape is the Stop hook — at full parity here — plus a self-verify skill). Deployment posture recorded: opt-in library primitive only, never a scenario default.
2224
+ - quality: fast-track adversarial review (snapshot-anchor tearing and dual-root, both HIGH) with 4 follow-ups addressed; assertion strength upgraded (exact context toEqual, true default-mode comparison, root-count + export/import round-trip, joint file/context tri-state pins); 13 new/rewritten tests; full suite green; tsc clean. No breaking changes.
2225
+
2226
+ ## 1.291.0 (2026-07-14)
2227
+
2228
+ Workflow worktree seam batch: a per-isolation governance baseline plus fail-closed isolation contracts. Adversarially reviewed (2 HIGH + 3 MED follow-ups, all addressed).
2229
+
2230
+ - **feat: `WorkflowGovernanceBaseline.worktreeBase`**: worktree-isolated agents get an overlay baseline (shallow-merged over `base` — configure `{handsReadOnly:false}` to lift only the write clamp while keeping every other baseline field; wholesale replacement semantics were rejected in review as a footgun). Absent = zero change. Default deployments now run workflow agents with the same permissions as the main loop (upstream parity); this seam remains the precise knob for deployments that want a conservative posture.
2231
+ - **fix: isolation requests fail closed**: a workflow agent requesting `isolation:"worktree"` no longer silently degrades to the shared tree when the environment factory cannot isolate — preparation throws (`isolation.unavailable`, the child never starts). The factory contract is upgraded to MUST-throw (compliant factories see zero change). Core-side defenses reject the observable degradation shapes: no factory; the factory returning the shared static env by identity (the reference is dropped before the throw so cleanup never destroys a shared environment); an in-process env whose canonicalized cwd (realpath-native — symlink and case aliases normalize) hits the shared root; remote envs are exempt only with `capabilities.isolation === true`.
2232
+ - **docs**: worktree semantics stated honestly — cooperative file isolation (the file tools' cwd domain), not a shell jail (same posture as upstream); the residual trust boundary (a compliant-looking factory minting deep inside the shared tree) rests on the factory contract and is documented as such.
2233
+ - quality: fast-track adversarial review (symlink bypass and remote duck-typing exemption, both HIGH) with 5 follow-ups all addressed; 15 new tests (symlink/case-alias/dangling-path/non-isolated-remote/shared-env-preservation/overlay pins); every fix rollback-verified; full suite green; tsc clean. No breaking API changes (additive field; the isolation-unavailable change turns silent misbehavior into an honest failure).
2234
+
2235
+ ## 1.290.0 (2026-07-14)
2236
+
2237
+ Workflow observability/gateability plus the core mechanisms for the file-write permission gate. Adversarially reviewed (2 HIGH + 4 MED follow-ups, all addressed).
2238
+
2239
+ - **feat: workflow agent failures are now gateable**: the workflow tool card and supervisor prompt teach the contract (agent() never throws — check `r.status` and gate later phases, with a template); `WorkflowRun`/`WorkflowPhase`/`phase_end`/`run_end` gain an additive `agentFailures?: number` field (present only when >0 — adding an enum value would have broken terminal-status consumers); phase attribution is per-instance (repeated phase titles no longer cross-count) and re-stamped idempotently at run end (no settlement-order undercount).
2240
+ - **feat: runtime usage beat**: each agent turn pushes usage into `run.stats` and persists a snapshot (turn_end side-channel — list and detail views come alive with zero consumer changes); the steerable lane gets the same treatment (in-core event harvest with a capability probe: minimal stubs degrade honestly to terminal-only); a run-level provisional ledger settles at the terminal boundary, so **the finalized persisted snapshot always equals the authoritative accumulated total** (late-rollback fork closed); retry legs persist immediately after rollback; `budget.spent()` semantics documented (runtime figures are display-only; the budget gate charges on authoritative settlement).
2241
+ - **feat: file-write gate core mechanisms**: ① `createFsWriteGatePolicy({rootPath, acceptDirs, exemptDirs, defaultWrite})` — three-tier path-domain judgment for Write/Edit/NotebookEdit (exempt → accept → default), canonicalized targets (no symlink smuggling), fail-closed ask, emits only allow/ask (sensitive-path denies always win — layering documented); ② **sync-ask priority**: with a live synchronous ask handler present, a non-safety ask with no durable-approval requirement resolves in-turn (zero checkpoint, prompt cache preserved — interactive-grade latency; safety asks and explicit durable approval still park; headless behavior unchanged); ③ an outside-root Write negative-control pin (engine never crashes; `isError:false`; the `structured` field is absent entirely — a consumer contract: renderers must access it optionally).
2242
+ - quality: adversarial fast-track review (two HIGH findings: finalized-late-rollback fork, steerable-lane beat bypass) with 6 follow-ups all addressed; 24 new tests; full suite green; tsc clean. No breaking changes (2 additive exports).
2243
+
2244
+ ## 1.289.0 (2026-07-14)
2245
+
2246
+ Compaction trigger geometry aligned with the upstream anchor, plus a reserve-semantics split and a dual-window field for 1M-class models. Adversarially reviewed (1 HIGH + 4 MED follow-ups, all addressed).
2247
+
2248
+ - **Behavior change: compaction trigger geometry**: the trigger clamp moves from `≤0.7×window` to `contextEditFrontier(W) = max(W − 33000, floor(0.7W))` — for large windows this is the exact upstream geometry (the upstream trigger is "autocompact window − 33000": a 20k output reserve plus a 13k trigger buffer; on a 200k window that lands at 167k ≈ 83.5%, and the well-known 93.4% figure only appears on 1M models because a special-case table first sets the autocompact window to 967k = 1M − 33000). The 0.7W leg remains solely as a small-window floor (<110k windows are byte-for-byte unchanged). The clear-stale frontier and the compaction threshold now share one single-source function (the historical death-band invariant holds structurally, locked by a new integration regression on the original 262k pathology shape plus a cross-regime sweep). The destructive-trim guard moves in lockstep to `max(W − 23000, floor(0.85W))` (upstream hard-stop geometry; the gap stays ≥10000 for every window; the action semantics — dropping oldest messages rather than refusing the request — remain our own availability-first policy and are documented as such). Net effect: a 200k window now compacts at 167k instead of 140k; a 1M window at 967k (declare `autoCompactTokens: 967000` for exact upstream alignment at 934k).
2249
+ - **fix: reserve semantics split (HIGH)**: `reserveTokens` previously did double duty as the trigger margin AND the summary output budget base — the new geometry clamps the reserve to 33000, which would have silently shrunk summary output budgets from 48k to 26.4k (with a reachable partial-summary truncation path). A new single-source helper (`min(explicit knob ?? reserveTokens, model.maxTokens)`) feeds every consumer; sanitization now clamps only the trigger side. The model-cap leg also fixes a pre-existing defect where a 1M main window pushed a 240k output reservation into a small summary model's input clamp. New `CompactionSettings.summaryOutputBudgetTokens` knob (additive); the dry-run judgment stays parameter-identical; real summary-call `maxTokens` pinned across 200k/262k/1M windows.
2250
+ - **feat: `Model.autoCompactTokens`**: 1M-class models upstream use two windows — a 967k autocompact window and the physical 1M request window. The new additive field feeds only the trigger-side geometry; the guard always uses the physical window (1M + 967000 → trigger 934k, guard 977k — structurally isomorphic to upstream). Unset = falls back to the existing chain, zero regression.
2251
+ - quality: adversarial review with independent anchor re-verification and four-regime algebraic checks; 18 new pins including two integration regressions (compaction lands before any destructive clear-stale action; compaction-failure + >10k single-turn growth falls back to the guard observably); every fix rollback-verified; full suite green; tsc clean.
2252
+
2253
+ ## 1.288.0 (2026-07-14)
2254
+
2255
+ Two ecosystem-compatibility items plus an observer-isolation sweep. Cross-verified by two independent adversarial review tracks; 1 HIGH + 2 MED follow-up findings, all addressed.
2256
+
2257
+ - **feat: `subagent_type: "general-purpose"` alias folding**: the common hard-coded default agent-type literal no longer errors — it folds to the plain delegation path (full tool pool + default persona, semantically equivalent to the upstream built-in general-purpose agent with `tools:["*"]`). A deployment that defines its own `general-purpose` agent always wins. Folding happens before schema validation (the enum lists only defined names) and on the direct-execute leg; the workflow `agentType` lane gets the same fix. Contract note recorded: the roster/shadow decision is a mount-time snapshot — rebuild the tool after changing the agents directory.
2258
+ - **feat: `ReportFindings` engine-side tool**: the code-review findings output tool is now registered by the engine (schema, description, and result mapping are byte-for-byte parity with the upstream anchor: strict top-level object, ≤32 findings, non-strict finding objects with no per-field length caps — same shape as upstream, naturally bounded by the provider output cap). Execute is a synthetic echo: findings ride into `details` verbatim; the model sees only a count. A caller tool answering to the same name or alias suppresses the injection (deployment sovereignty).
2259
+ - **fix: onError observer isolation (sweep the class)**: `deps.onError` is documented as a best-effort observer, but ~67 call sites invoked it bare — a throwing deployment callback could fail a healthy task (proven on the end-of-task prompt-cache warning leg). Fixed at a single chokepoint wrapping the deps boundary in the Runner constructor: synchronous throws AND async rejections are both swallowed (an `async () => { throw }` sink previously escaped as an unhandledRejection — process-fatal under Node's default policy), with a metadata-only `task.onerror_sink_failed` trace breadcrumb so a persistently broken observer stays diagnosable. Per the documented contract this is a bug fix, not a breaking change.
2260
+ - quality: cross-verified adversarial review (11 upstream anchors independently re-verified; 4 rollback probes re-run in both directions); 25 new tests; full suite green; tsc clean. No breaking changes (3 additive exports).
2261
+
2262
+ ## 1.287.0 (2026-07-14)
2263
+
2264
+ Six-item batch: compaction sub-phase telemetry, degraded-turn pricing root fix, duplicate-notification folding, degraded-frame output fencing, snapshot race tolerance, and a dynamic tool-injection seam for sub-agents. Cross-verified by two independent adversarial review tracks plus a focused seam review; 1 HIGH + 5 MED follow-up findings, all addressed.
2265
+
2266
+ - **feat: compaction sub-phase telemetry**: `maybeCompact` results, the `compacted` wire event, and a new `compaction.phase_timings` trace frame carry `phaseDurations { prepareMs, summaryMs?, persistMs, ptlRetries }` — the three phases tile `durationMs` exactly (not within tolerance); provider-reuse passes honestly omit `summaryMs`; the retry counter covers split-turn dual legs. Single-source type derivation keeps the three surfaces from drifting.
2267
+ - **fix: degraded-turn pricing (root fix)**: reactive degradation recognition now runs before the same message's usage pricing, so the first degraded turn is priced by the model that actually served it. When the fallback model is unknown to the catalog, the cache family falls back to the conservative `input-excludes-cached` (a bare id carries no family signal; the conservative family never undercounts — budget gates can only trip early). The same gap affected every later turn's re-pricing, and the single-point fix covers both. The degradation observer callback is now isolated: a throwing deployment `onError` no longer loses the message's accounting.
2268
+ - **fix: duplicate wire notification folding**: a run-scoped delivered-key ledger registers frames drained at turn-open and folds re-injections of the same logical event (both the live re-emit and the torn-down re-park legs); keys include the stop-cycle seq so fresh-cycle frames always pass, and external/internal domains stay isolated.
2269
+ - **fix: degraded-frame output fencing**: partial stdout/stderr from ripgrep timeouts/high exits and cut-short shell commands is now wrapped in untrusted-content fencing (the caveat/headline stays outside the fence, so embedded content cannot forge or cancel it); forged fence sentinels are defused; clean full runs keep byte-identical raw output.
2270
+ - **fix: snapshot enumerate→read race tolerance**: the whole-tree snapshot now skips files that vanish between enumeration and read (`not_found`/ENOENT → omitted + a `skippedVanished` count; the omission equals a consistent slightly-later tree state, not a partial snapshot). Permission and I/O errors still fail the whole capture. Lock files, editor swap files, and in-flight markers no longer invalidate a turn's rewind snapshot.
2271
+ - **feat: `SubagentToolOptions.extraTools`**: a per-spawn factory that injects deployment tools (e.g. per-task sandbox-bound tools) into the sub-agent roster; products pass through the existing tool-subset whitelist unchanged (agent-definition subset constraints intact). Hardened by review: parent abort during evaluation takes the stillborn path; the reserved-name set covers primary names plus all aliases (no alias shadowing); factory errors surface to the model as a fixed generic note while the raw error goes to a host-side `onExtraToolsError` sink; a per-tree factory-call budget bounds amplification. Fully additive — without the seam, nothing changes.
2272
+ - quality: every fix locked by a mechanical-rollback-verified test (three re-verified independently in both directions); 32 new tests; full suite green ×2; tsc clean. No breaking changes.
2273
+
2274
+ ## 1.286.1 (2026-07-14)
2275
+
2276
+ Package-hygiene patch over 1.286.0: a few code comments introduced by the wake-fix batch carried internal collaboration coordinates; they were rewritten neutrally (technical content unchanged, zero behavior change).
2277
+
2278
+ ## 1.286.0 (2026-07-14)
2279
+
2280
+ Compaction window safety for the 1M-context era, plus a sub-agent wake fix for dual-runner deployments. Two independent adversarial review tracks (execution paths × state machine / upstream-parity anchoring) cross-verified the batch; 4 HIGH + 5 MED follow-up findings, all addressed.
2281
+
2282
+ - **feat: window-safety judgment (single source = clamp dry run)**: `dryRunSummarizationClamp` reuses the real clamp's exact parameters (prompt selection, instructions injection shape, both reserve legs, split-turn aggregation; the previous summary is charged to the budget, so multi-round passes are covered by construction). When an independent (smaller-window) compaction model would truncate the summarization input by more than `clampTolerance` (new knob, default 0.10; 0 = any truncation is a fallback candidate, 1 = never, the legacy escape hatch), the summary call **falls back to the main model**, which is always window-safe — the content grew inside its window. The arming condition compares char-domain capacity in the same coordinate the real clamp budgets (a token-domain filter missed three shapes, caught in review).
2283
+ - **feat: two gates on the fallback**: a **budget gate** (estimated cost = content tokens × main-model input price, including the full previous summary; over the cap ⇒ no fallback — prevents the "money spent and the task still dies" double loss) and a **walltime gate** (EWMA throughput projection with a conservative prior; the summary call itself gets a **soft deadline** at wall − cushion via an abort race, armed after auth so an auth failure leaks nothing). A gate denial keeps the small model and **discloses** the clamp. A soft-deadline abort is engine-initiated write-out protection, not a summarizer fault — it is marked (`isCompactionWalltimeAbort`) and never counted toward the consecutive-failure breaker. The end-of-task compaction lane consumes the same gates.
2284
+ - **feat: early warning + disclosure events**: `compaction.window_config_warning` (static per-task warning when the compaction model's window is smaller than the main model's, with a sanitized-reserve `fallbackAt` estimate), `compaction.model_fallback`, `compaction.clamp_disclosure` (reasons: budget / walltime / tolerance — silent mid-section drops now always have a voice), and `compaction.manual_in_finalize` (a manual compact inside the finalize window runs — user sovereignty — but is disclosed). The `compacted` wire event gains additive fields `modelFallback?` / `fallbackReason?` / `clampedRatio?` / `clampReason?` on all three emit sites.
2285
+ - **feat: `TaskStream.compact({ instructions? })`**: a per-call summarization-instructions override that replaces the spec-level/default instructions for that pass (hook additions still append; the injection shape matches the upstream anchor byte-for-byte). All four instructions channels pass through one hygiene choke point (sanitize + fence defusing + a 2048-code-point surrogate-safe cap; clean text under the cap passes byte-identical). Concurrent calls coalesce with last-provided-instructions-wins; requests arriving while a pass is in flight are claimed by the NEXT boundary, never resolved against a pass that ignored them.
2286
+ - **fix: sub-agent wake across two runners**: when the Agent tool is caller-mounted on a throwaway in-memory runner while SendMessage/AgentTranscript are core-mounted on a durable host runner, resume/transcript used to acquire the child session from the wrong store (404). `SubagentRetainEntry` now records the spawning runner; resume and transcript consume `entry.runner ?? deps.runner`.
2287
+ - **fix: throughput EWMA sampling** moved after the empty/oversized summary guards (a failed call no longer pollutes the sample).
2288
+ - quality: cross-verified adversarial review, every fix locked by a mechanical-rollback-verified test (three re-verified independently in both directions); 42 new tests; full suite green ×2; tsc clean. No breaking changes (fully additive).
2289
+
2290
+ ## 1.285.0 (2026-07-14)
2291
+
2292
+ Walltime write-out TTFT batch: an adversarial attribution pass over external benchmark telemetry (a recurrence of the walltime write-out signature previously addressed in 1.281/1.282) confirmed the failure can still reach the wall through three distinct paths, all sharing one root cause — **the fixed 5s write-out cushion silently assumed "first token in under 5 seconds", which does not hold for reasoning endpoints**. The abort-signal hypothesis was falsified again (the recovery leg's `!signal?.aborted` gate proves the abort had not tripped: an abort produces an aborted terminal, not an error frame). On the worst path — where the first finalization call itself burns through the whole window — the salvage merge chain already preserves the first frame's output (the substantive harm was fixed in 1.282; the double error frame was cosmetic).
2293
+
2294
+ - **fix(live write-out window gate on the recovery leg)**: the walltime cutoff recovery now checks a `hasLiveWriteoutWindow` predicate — when the write-out window is constructively expired, the engine no longer issues a second model call that is guaranteed to die; the first frame becomes the terminal result via the salvage merge chain (traces no longer show a confusing pair of empty error frames). With no TTFT samples the liveness floor is 1s (only provably dead windows are rejected — with zero evidence the engine never destroys its only write-out chance); once samples exist, liveness is judged against 2×TTFT.
2295
+ - **fix(TTFT-aware window lower bound)**: `CallCapState` gains a `ttftEwma` (sampled at the first content delta of each model call); `effectiveCushionMs` gains a second lower bound = margin + 2×TTFT (5s prior with no samples, sharing the existing 40%-of-budget clamp) — on slow-first-token endpoints the write-out window can now fit at least one real first token; on fast endpoints every value is bit-for-bit identical to 1.284 (zero-regression lock).
2296
+ - **fix(tool cut line shares the TTFT bound)**: `toolCutDeadlineMs` hardCap = wall − margin − max(RESERVE, 2×TTFT) (evidence-driven, no prior; fast endpoints keep wall−10s) — on slow endpoints tools yield earlier, so the write-out call is no longer squeezed into a dead window and a tool eating the tail of the budget can no longer leave a 5s-only write-out window behind.
2297
+ - **feat(forensic surface)**: `task.start` trace/events now carry `engineVersion` (read from package.json at runtime, memoized, `"unknown"` on failure); `task.end` traces echo scalar mechanism counters/flags (fields that may contain model text are stripped under the metadata-only rule) — external benchmark harnesses can verify the engine version and mechanism trigger history from traces alone.
2298
+ - quality: four attribution hypotheses tested one by one against telemetry (reproduction tests reconstruct the observed frame sequence at HEAD); each fix verified by mechanical rollback, one re-verified independently in both directions; full suite green; tsc clean. No breaking changes.
2299
+
2300
+ ## 1.284.0 (2026-07-14)
2301
+
2302
+ Second batch of the stop-notification loop (core half): a child-completion gate that defers completion notifications while a child agent still owns live background work, plus batching seams for memory sync with a cursor-prefill defense. All changes went through adversarial review (5 findings confirmed, 4 high-severity, all addressed).
2303
+
2304
+ - **feat: background-agent child-completion gate**: when a child run reaches `completed` but still owns live background tasks (background shells, monitors, grandchild agents — ownership matched by strict child-session identity), the completion notification is **parked** (deferred on the retain entry; the frame snapshots the current cycle seq at park time) and re-issued exactly once when the last background task reaches a terminal state (a zero-count quiescence subscription on the registry with a once-latch; the poke sites cover every terminal transition). `failed`/`killed` runs are never parked — they notify immediately. **The gate applies to every stop cycle** (spawn and resume alike): a resume cycle's completion parks the same way — there is no resume exemption (a review round corrected an earlier spec that wrongly exempted it, and the tests were rewritten to the correct semantics). If the task is woken while parked, the deferred frame is voided (exactly one frame per cycle); when no retain entry is available the code degrades to immediate delivery. **Eviction safety**: every entry-release path (TTL / LRU / abandon / reap) flushes a pending deferred notification (deliver rather than drop) and unsubscribes the watcher (no zombie frames, no leaks); watcher-capacity overflow uses cancellation semantics (`reason:"evicted"`, delivered asynchronously on a microtask, never falsely reporting quiescence).
2305
+ - **feat: memory-sync batching (fully additive — with no knobs set, wire bytes are unchanged)**: `SyncMemoryScopeOptions.maxPushEntries` (id-ascending prefix truncation + `pushTruncated`; deletes are never split) and `maxPullEntries` → wire `pull.limit` + `pullTruncated` passthrough; callers loop until both truncation flags clear (core does not embed the loop). **Cursor-prefill defense**: a rev in the response cursor is accepted only from a three-source whitelist (the value the request already carried / an uncontested push acknowledgement from this round / an actually delivered entry); anything else is discarded, the old baseline is kept, and the rejection is disclosed via `warnings` — a non-conforming server prefilling the cursor can no longer cause a permanent pull gap. **A push rev disputed by a conflict is rejected even if prefilled** (otherwise the next pull's CAS would overwrite the local unresolved version — data loss), while idempotent replay acknowledgements are allowed through to prevent a livelock; zero/negative knob values fail loud.
2306
+ - quality: adversarial review confirmed 4 HIGH + 1 MED, all addressed; every fix verified by mechanical rollback (one re-verified independently in both directions); the new knobs are registered in the public-knob liveness suite; full suite green; tsc clean. No breaking changes.
2307
+
2308
+ ## 1.283.0 (2026-07-14)
2309
+
2310
+ First batch of the stop-notification loop (core half): per-stop-cycle sequence numbers on background-agent notification frames, an external structured-notification injection verb on `TaskStream`, and a wake arm for resuming parked tasks. Two independent adversarial review tracks (injection surface / gate purity × state machine / lifecycle) confirmed 7 follow-up findings, all addressed.
2311
+
2312
+ - **feat: per-stop-cycle `seq` on background-agent notification frames**: `SubagentRetainEntry.cycleSeq` (1 at registration, +1 on each real resume start); all four spawn/resume settle legs now emit notifications carrying the seq — second-cycle frames in a "stop → wake → stop" sequence are no longer swallowed by dedup (frame contract unchanged, zero breaking: the seq field and key slot already existed; background_agent now mints values). When the retain entry is unavailable the code degrades without fabricating a cycle number; the seq is snapshotted at settle time (fixes a drain-window race caught in review); the dead `TaskRecord.notified` field is removed. Note for consumers folding streams: seq continuity holds only within one child-session identity — the spawn frame's sessionId field is the join anchor.
2313
+ - **feat: `TaskStream.notify()` — external structured notification injection**: `ExternalNotificationInput` (task_id/status/summary/result?/seq?/source?); the `task_type:"external"` discriminator is minted by core with a strict field whitelist preventing smuggling of extra keys; `source` passes untrusted-inline fencing plus attribute escaping, and every field goes through the existing sanitize fences. Delivery reuses the system-injection queue's three-state path (steer / follow-up / idle-park). **External frames dedup in their own key-domain lane** — external callers cannot craft keys that suppress the engine's own notifications; the ended-run park leg dedups by the same key (documented as limited to the cross-batch window). No deployment special-casing: daemon/webhook semantics stay on the deployment side.
2314
+ - **feat: `ResumeOutcome` gains a `{gate:"wake", message?}` arm**: non-decision wakeups for parked tasks — **whitelist gate purity** (only task-done parks are wakeable; every other gate returns `wake.gate_pending` pointing at the corresponding decide entry; the pendingAction family is an exhaustive switch with a never-check, so adding a gate type is a compile error and unknown kinds fail closed at runtime); an empty wake is rejected with `wake.nothing_to_deliver`; wake-with-message is atomically equivalent to park+resume (snapshot copy, zero mutation of the stored row) and **concatenates in two segments with an already-parked pendingSteer** (each trust domain keeps its own fence, so an undelivered supervisor instruction is not lost); CAS and context rebuild fully reuse the existing paths. `PendingAction` gains a true pure-park arm `{kind:"task_done"}`.
2315
+ - quality: two independent adversarial review tracks with cross-triage (1 HIGH + 3 MED + 3 LOW confirmed, all addressed; each track surfaced unique findings); 3 new test files with 28 cases, all event-driven with zero sleeps, zero flakes over 10 consecutive runs, each verified by mechanical rollback (two re-verified independently in both directions); export surface +1 (`ExternalNotificationInput`); the new knobs are registered in the public-knob liveness suite; full suite green; tsc clean.
2316
+
2317
+ ## 1.282.0 (2026-07-14)
2318
+
2319
+ Follow-up to the walltime finalization batch: a second adversarial review round (5 lenses × 3 falsification perspectives) confirmed 8 additional findings — all cross-mechanism interaction defects, with zero overlap with the previous review rounds.
2320
+
2321
+ - **fix(MED) cutoff latch dropped the write-out instruction**: the 1.281 latch only set flags and consumed the nudge table, relying on the loop recovery nudge to deliver the write-out instruction — but the steer-wins rule let any unrelated queued steer (mid-run user message, observer report, notification, diagnostics, recall) displace it, while the boundary lane was gated shut by `!finalizeInjected`: the model never received the persist instruction until the second-cutoff terminal, and telemetry falsely reported it as injected (in 1.280 the boundary lane could still re-inject, so this was a true regression introduced in 1.281). Fix: the finalize text is single-sourced in `buildFinalizeText()`, and the latch branch now **actually enqueues a steer** (synchronous enqueue ahead of the recovery drain; with an empty queue it drives the write-out immediately, with unrelated steers queued it re-injects at the next boundary); `finalizeInjected` is only set after a real enqueue, with a `steering_injected source:"finalize"` stream echo.
2322
+ - **fix(MED) cut batch-gate read a forgeable source**: three batch paths read the cut marker from the post-hook finalized result — a single details patch from a public tool_result subscriber could strip the engineCut marker and silently disable the batch gate (while the in-stream barrier read the raw result). Fix: all three paths now read the pre-hook raw executed result. **Legitimate hook uses are preserved** (details remains a disclosure surface and patches still apply to frames; control signals just no longer travel through it).
2323
+ - **fix(MED) clamped-state deadline inversion**: the clamped hardCap (wall−5s) was exactly equal to the finalizeMode write-out callDeadline (wall−5s) — on the clamped leg, the write-out call issued after a cut carried an already-expired deadline and the model call self-cancelled on the first chunk (the 1.281 walltime-write-out signature reappearing on the tool-cut path). Fix: `TOOL_CUT_WRITEOUT_RESERVE_MS=5s`, so the clamped cap is now wall−10s; a **four-quantity ordering invariant** (toolCut < write-out callDeadline < wall, with the grace leg as the only exemption) is written down as a call-cap contract comment plus a test enumerating all four states.
2324
+ - **fix(LOW)**: settleBatch now sets the cut latch before the hook round-trip (thunks queued inside the slot-release race window can no longer start after a cut).
2325
+ - **fix(LOW, trust surface)**: the cut control signal moved to an **engine-side WeakSet identity registry** — a third-party/MCP tool forging `details.engineCut` can no longer make same-batch sibling tools skip execution (denial-of-service surface closed); details is demoted to pure disclosure.
2326
+ - **fix(LOW)**: the task-end backstop terminal now publishes sessionId through the same ref mechanism as taskId (no more "unknown" breaking the linkage).
2327
+ - **test**: the stale-nudge consumption line is mechanically locked; the e2e stub brain now respects callDeadlineMs (expired → simulates a real model self-cancel) — the clamped-leg e2e went from "green only because the stub ignored deadlines" to a real verification, doubling as the mechanical-rollback test for the deadline-inversion fix.
2328
+ - quality: all 8 fixes verified individually by mechanical rollback (each must turn red when reverted; two re-verified independently in both directions); regression guards in three places (legitimate hook uses, write-out instruction reaching the model, unclamped-state numeric lock); full suite green; tsc clean. Behavior surface: on the Runner path the cutoff write-out instruction is now always a finalize steer (structurally replacing the generic recovery nudge; bare runAgentLoop is unaffected); no breaking changes.
2329
+
2330
+ ## 1.281.0 (2026-07-14)
2331
+
2332
+ Walltime finalization batch: three engine-level fixes surfaced by external benchmark telemetry (endpoint-independent). The "write out a final answer before hitting the wall" path now works end-to-end on the walltime route for the first time. All changes went through two rounds of adversarial review.
2333
+
2334
+ - **fix(walltime write-out always erroring, high impact)**: root cause — the walltime cutoff recovery path bypassed the turn boundary, so `finalizeMode` was never set and the finalization call went out with an already-expired `callDeadlineMs = deadline − cushion`; the model call self-cancelled on the first stream chunk (stopReason=error, missing usage, zero salvage). Fix: when the runner observes the walltime cutoff sentinel it enters finalizeMode, latches finalizeInjected, and consumes stale nudges; the write-out window now uses `deadline − 5s` of live headroom so verification rounds and expired nudges no longer steal the finalization window. The hard wall is untouched; suspend paths are unaffected.
2335
+ - **fix(non-bash tools could starve finalization)**: execClamp previously only clamped foreground shell commands — a hung custom ToolSpec/MCP/delegation tool (observed hanging ~9.5 min in telemetry) produced no turn boundary, no finalize injection, and a hard kill with zero write-out. Fix: a unified `Promise.race` at the loop dispatch point (sequential/partitioned/in-stream paths all covered) injects an honest ran-then-cut frame at the deadline — it states that the engine stopped waiting, the tool itself was not cancelled, side effects may still land, and non-idempotent operations must not be blindly re-issued (`details.engineCut`). `toolCutDeadlineMs` = max(soft+2.5s, now+10s), clamped to hard wall −5s; the bash foreground self-clamp keeps a lag margin to avoid double cuts.
2336
+ - **fix(tool-cut follow-ups)**: ① after an engine cut, remaining tools in the same batch/stream are no longer started — they receive a NOT-EXECUTED frame (`notExecuted:true`: provably zero side effects, safe to re-issue), giving a machine-readable split between re-issuable and not-blindly-re-issuable outcomes; in-stream admission raises a barrier at the cut instant. ② a non-positive post-clamp window arms a `now+2s` grace cut (`TOOL_CUT_FLOOR_GRACE_MS`) instead of not arming — any non-cooperative tool is eventually abandoned and the task always reaches a terminal state (a grace cut may run past the hard wall: the wall owns the status, the cut only releases the wait). ③ cut-tool lifecycle tracking: `mechanisms.pendingCutTools` reports tools still unsettled at task end (per-leg semantics; suspend terminals report it as well).
2337
+ - **fix(missing task.end tear)**: when a finish()-tail or teardown throw escaped to the outer catch, `done` was emitted without `task.end`. Fix: a run.catch backstop emits an honest failed terminal (with the real taskId, not "unknown"); an already-decided terminal is never overwritten (a teardown-tail throw no longer clobbers it into a synthetic failed plus a double done).
2338
+ - telemetry: `stats.mechanisms` gains `toolCuts`/`pendingCutTools`. Two falsifiable predictions are attached to this release: ① on walltime-bounded benchmark tasks the share of runs with non-empty salvaged output should rise significantly; ② after a walltime cutoff recovery, turn.end should no longer be uniformly stopReason=error with missing usage.
2339
+ - quality: initial hypothesis corrected against telemetry (not abort-signal reuse — the abort had not tripped; passing the `!signal?.aborted` gate proves it); two adversarial review rounds with all 10 findings addressed; 3 new test files with 20 cases, each verified by mechanical rollback; full suite green; tsc clean. Behaviorally additive; no breaking changes.
2340
+
2341
+ ## 1.280.0 (2026-07-13)
2342
+
2343
+ First public source release. Earlier release history (1.0–1.279.x) predates the
2344
+ public repository and is summarized in ROADMAP.md; the npm package @sema-agent/core
2345
+ has been published continuously since 2026-06.