@sema-agent/core 7.5.2 → 7.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/dist/agents/agent-transcript-tool.d.ts +2 -2
  3. package/dist/agents/cascade.d.ts +4 -5
  4. package/dist/agents/cascade.js +12 -10
  5. package/dist/agents/repair-loop.d.ts +7 -5
  6. package/dist/agents/repair-loop.js +13 -15
  7. package/dist/agents/retain-ledger.d.ts +2 -3
  8. package/dist/agents/send-message-tool.d.ts +2 -2
  9. package/dist/agents/session-util.d.ts +2 -2
  10. package/dist/agents/subagent.d.ts +27 -46
  11. package/dist/agents/subagent.js +119 -105
  12. package/dist/agents/suspend-guard.d.ts +31 -19
  13. package/dist/agents/suspend-guard.js +14 -8
  14. package/dist/agents/teacher.d.ts +2 -2
  15. package/dist/agents/teacher.js +9 -9
  16. package/dist/agents/team.d.ts +6 -5
  17. package/dist/agents/team.js +10 -8
  18. package/dist/agents/verify.d.ts +8 -9
  19. package/dist/agents/verify.js +17 -17
  20. package/dist/core/a2a.js +2 -1
  21. package/dist/core/agent-definition.d.ts +172 -0
  22. package/dist/core/agent-definition.js +1 -0
  23. package/dist/core/ask-origin.d.ts +60 -7
  24. package/dist/core/ask-origin.js +26 -1
  25. package/dist/core/checkpoint-store.d.ts +78 -76
  26. package/dist/core/checkpoint-store.js +17 -1
  27. package/dist/core/delegation-frames.d.ts +298 -0
  28. package/dist/core/delegation-frames.js +21 -0
  29. package/dist/core/engine-notice.d.ts +555 -0
  30. package/dist/core/engine-notice.js +55 -0
  31. package/dist/core/gate-fold.d.ts +12 -0
  32. package/dist/core/gate-fold.js +158 -0
  33. package/dist/core/gate-lanes.d.ts +93 -0
  34. package/dist/core/gate-lanes.js +626 -0
  35. package/dist/core/gate-outcome.d.ts +189 -0
  36. package/dist/core/gate-outcome.js +70 -0
  37. package/dist/core/hands-band.d.ts +134 -0
  38. package/dist/core/hands-band.js +1 -0
  39. package/dist/core/hooks.d.ts +22 -177
  40. package/dist/core/hooks.js +53 -851
  41. package/dist/core/mcp-failure.d.ts +142 -0
  42. package/dist/core/mcp-failure.js +145 -0
  43. package/dist/core/mcp-server-spec.d.ts +217 -0
  44. package/dist/core/mcp-server-spec.js +1 -0
  45. package/dist/core/mcp.d.ts +21 -77
  46. package/dist/core/mcp.js +76 -150
  47. package/dist/core/model-seat.d.ts +99 -0
  48. package/dist/core/model-seat.js +1 -0
  49. package/dist/core/pause-registry.d.ts +131 -0
  50. package/dist/core/pause-registry.js +27 -0
  51. package/dist/core/reminder-mint.d.ts +10 -0
  52. package/dist/core/reminder-mint.js +3 -0
  53. package/dist/core/runner/assemble-result.d.ts +32 -41
  54. package/dist/core/runner/assemble-result.js +55 -74
  55. package/dist/core/runner/contracts.d.ts +427 -69
  56. package/dist/core/runner/denial-limit-arms.d.ts +1 -1
  57. package/dist/core/runner/denial-limit-arms.js +3 -3
  58. package/dist/core/runner/gate-exit.d.ts +242 -0
  59. package/dist/core/runner/gate-exit.js +124 -0
  60. package/dist/core/runner/park-commit.d.ts +17 -23
  61. package/dist/core/runner/park-commit.js +14 -15
  62. package/dist/core/runner/prepare-ask-lane.d.ts +0 -3
  63. package/dist/core/runner/prepare-ask-lane.js +3 -5
  64. package/dist/core/runner/prepare-boundary-parks.d.ts +3 -6
  65. package/dist/core/runner/prepare-boundary-parks.js +3 -3
  66. package/dist/core/runner/prepare-caps-and-workflow.d.ts +2 -7
  67. package/dist/core/runner/prepare-caps-and-workflow.js +1 -1
  68. package/dist/core/runner/prepare-delegation-surface.d.ts +2 -7
  69. package/dist/core/runner/prepare-gate-stations.d.ts +4 -7
  70. package/dist/core/runner/prepare-gate-stations.js +29 -54
  71. package/dist/core/runner/prepare-inherited-gate.js +1 -1
  72. package/dist/core/runner/prepare-memory.d.ts +44 -26
  73. package/dist/core/runner/prepare-park-ask.d.ts +2 -4
  74. package/dist/core/runner/prepare-park-ask.js +5 -5
  75. package/dist/core/runner/prepare-task.d.ts +2 -2
  76. package/dist/core/runner/prepare-task.js +8 -9
  77. package/dist/core/runner/prepare-wiring-manifest.d.ts +7 -15
  78. package/dist/core/runner/prepare-wiring-manifest.js +9 -10
  79. package/dist/core/runner/runtask.d.ts +20 -102
  80. package/dist/core/runner/runtask.js +119 -121
  81. package/dist/core/runner/terminal-projection.d.ts +22 -0
  82. package/dist/core/runner/terminal-projection.js +28 -0
  83. package/dist/core/runner-deps.d.ts +1416 -0
  84. package/dist/core/runner-deps.js +1 -0
  85. package/dist/core/runtime-caps.d.ts +164 -0
  86. package/dist/core/runtime-caps.js +1 -0
  87. package/dist/core/store-contracts/checkpoint-store-contract.d.ts +4 -1
  88. package/dist/core/store-contracts/checkpoint-store-contract.js +8 -2
  89. package/dist/core/task-event.d.ts +910 -0
  90. package/dist/core/task-event.js +1 -0
  91. package/dist/core/task-limits.d.ts +110 -0
  92. package/dist/core/task-limits.js +1 -0
  93. package/dist/core/task-result.d.ts +809 -0
  94. package/dist/core/task-result.js +1 -0
  95. package/dist/core/task-spec.d.ts +1370 -0
  96. package/dist/core/task-spec.js +1 -0
  97. package/dist/core/task-stream.d.ts +382 -0
  98. package/dist/core/task-stream.js +1 -0
  99. package/dist/core/terminal-cause.d.ts +137 -0
  100. package/dist/core/terminal-cause.js +9 -0
  101. package/dist/core/tool-policy.d.ts +43 -139
  102. package/dist/core/tool-policy.js +79 -112
  103. package/dist/core/tool-spec.d.ts +1174 -0
  104. package/dist/core/tool-spec.js +1 -0
  105. package/dist/core/types.d.ts +27 -7789
  106. package/dist/core/types.js +2 -76
  107. package/dist/core/warm-resume.d.ts +2 -2
  108. package/dist/core/wiring-manifest.d.ts +6 -3
  109. package/dist/core/workflow-journal-store.js +3 -4
  110. package/dist/engine/harness/agent-harness.d.ts +1 -1
  111. package/dist/index.d.ts +12 -8
  112. package/dist/index.js +9 -6
  113. package/dist/orchestration/builtin-workflows.d.ts +2 -2
  114. package/dist/orchestration/builtin-workflows.js +1 -1
  115. package/dist/orchestration/goal.d.ts +2 -2
  116. package/dist/orchestration/goal.js +8 -7
  117. package/dist/orchestration/run-spec.d.ts +2 -2
  118. package/dist/orchestration/run-spec.js +5 -3
  119. package/dist/orchestration/run-workflow-tool.d.ts +4 -4
  120. package/dist/orchestration/run-workflow-tool.js +4 -4
  121. package/dist/orchestration/workflow-governance.d.ts +4 -4
  122. package/dist/orchestration/workflow-governance.js +4 -2
  123. package/dist/orchestration/workflow-primitives.d.ts +1 -1
  124. package/dist/orchestration/workflow-primitives.js +1 -1
  125. package/dist/orchestration/workflow.d.ts +15 -4
  126. package/dist/orchestration/workflow.js +64 -39
  127. package/dist/prompts/supervisor.d.ts +1 -1
  128. package/dist/prompts/supervisor.js +3 -3
  129. package/dist/scenarios/scenario-registry.d.ts +3 -3
  130. package/dist/scenarios/scenario-registry.js +1 -1
  131. package/dist/scenarios/teacher-quickstart.d.ts +2 -2
  132. package/dist/server/http.d.ts +2 -2
  133. package/dist/stores/file/fs-atomic.d.ts +88 -12
  134. package/dist/stores/file/fs-atomic.js +184 -55
  135. package/dist/stores/file/index.d.ts +1 -0
  136. package/dist/stores/file/index.js +1 -0
  137. package/package.json +3 -1
  138. package/test/export-surface.snapshot.json +82 -22
package/CHANGELOG.md CHANGED
@@ -1,5 +1,72 @@
1
1
  # Changelog
2
2
 
3
+ ## 7.6.1 — 2026-09-07
4
+
5
+ Patch: three bug fixes from the 7.6.0 field and black-box rounds (#608 P1, #609, #610) and the first refactor wave (design/393 S0, #594, #598), all byte-invariant on the runtime except where named. One public d.ts narrowing is called out below.
6
+
7
+ ### Fixed
8
+ - **#608 (P1)** — the file store's single-writer directory lock judged its owner by pid number alone, so a dead holder's pid recycled to an unrelated process kept the lock "in use" and every engine start failed with `store.dir_in_use` until an operator deleted the lock by hand (the cli production shape: two LOCK files reading `79814`, no trailing newline). The lock record now carries a process fingerprint `{pid, startedAtMs, startedSinceBootMs, host, v: 2}`; liveness is "pid alive AND not falsified by BOTH clocks" (a wall-clock start and an uptime-relative start — each unreliable exactly where the other is sound); a record from another host, or one without a comparable measure, keeps the pre-fingerprint verdict (a false "dead" reclaims a working writer's directory; a false "live" costs one refusal an operator can act on); a reboot the host has outlived is the uptime measure's second way of saying gone. Bare-pid locks are read by pid and rewritten on reclaim. What the guards cannot see (a same-boot backward clock correction hiding a genuine reuse; a reboot the machine has since outlived with no comparable measure) falls back to the pre-existing refusal, never a wrong reclaim — a boot-scoped kernel start token is the design-level fix (#612).
9
+ - **#609** — a resume of a parked policy ask that still spells the retired decision facts (`settledBy` / `approver` as top-level keys) is refused before the CAS with a coded error naming the `hostDecision` shape, instead of having the keys silently ignored (found by the 7.6.0 black-box round, G2.b).
10
+ - **#610** — every connect-phase MCP failure reports `delivered: "no"` — the dial has dispatched nothing of the caller's, so the verdict is a fact, not the in-flight guess; the two kinds a server itself answered (`protocol`, `http_status`) keep the table verdict. One verdict table (`DELIVERED_BY_KIND`, fenced over the closed set) replaces the per-row spellings. A stdio child that exits before the handshake is now `connection_closed / no` (was `unknown`); a 401 endpoint stays `http_status / unknown`; the request phase is unchanged. The dial's `no` promises that no call of the caller's was dispatched — it does not promise that no user data crossed the line during the handshake's elicitation (#611).
11
+ - **#594-b** — the #594 station relocation had put one microtask between the fold and the lanes (and one more inside the screening wrapper), so an org rule cleared on that tick was read as absent and a call 7.6.0 denied was let through; the three station-group seams (screening→fold, fold→lanes, lanes→exit) are back to the one-body form's await boundaries — the next group runs inside the previous group's continuation via a `GatePass.afterFold` seat. Five boundary pins record the microtask counts against the 7.6.0 tree.
12
+ - 7.6.0 carry-over disclosure: the D-8 refusal text `Resume it via checkpointToken` became `Resume it via the pause's token` (wording only).
13
+
14
+ ### BREAKING (d.ts face only — runtime unchanged)
15
+ - Option types and orchestration entry points that used to name the `Runner` CLASS for their `runner` member / parameter (`TaskServerOptions`, `SubagentToolOptions`, `SendMessageToolOptions`, `RunWorkflowToolDeps`, `warmResume`, `runGoal`, `runCascade`, `runRepairLoop`, `verify*`, `runWithTeacher`, `startWorkflow`, `runWorkflow`) now name **`RunnerSelfSeat`** — the one seat the Runner implements and every consumer names (`sessions`, `runTask`, `runTaskStream`, `resume`, `resumeStream`, `agentCatalog`, `emitTaskOutcome`, `gateBaseline`). A host that read `opts.runner.swapModels` or `opts.runner.sideQuery` THROUGH an option type no longer type-checks; call them on the Runner instance it constructed. No shipped consumer does (server calls `swapModels` on its own instances). Passing a `Runner` where a seat is expected still compiles.
16
+
17
+ ### Changed (refactor wave 1 — byte-invariant on the runtime)
18
+ - **design/393 S0** — the run loop's seven contract declarations move down to `contracts.ts`; runtask.ts takes its contracts from their declaring modules (orchestrator contract edges 2→0; G2 exemptions 3→1); the G5 phase-api gate is driven by a `PHASE_FAMILIES` table; LAYERING gains the "run lanes" stratum. runtask.ts 12 215 → 11 946 lines. dist `runtask.js` differs only in import lines.
19
+ - **#594** — the 23 gate stations (+4 exits) leave hooks.ts for the three-layer shape: `gate-fold.ts` (L1, the policy fold), `gate-lanes.ts` (L1, the org + ask-resolution lanes), `runner/gate-exit.ts` (L0, the exits + the one mint); hooks.ts keeps only the hook machinery and a six-call dispatch; `GatePass` is the one record the three read and write. hooks.ts 4 047 → 2 291 lines. Station-file closed-set pin + wiring pin. Words, order, deny layers, settlement kinds: unchanged and pinned.
20
+ - **#598** — the 7 721-line public type hub `types.ts` becomes a 54-line re-export hub over fourteen layer-0 domain modules (`model-seat`, `task-spec`, `tool-spec`, `hands-band`, `agent-definition`, `mcp-server-spec`, `task-limits`, `task-result`, `task-event`, `task-stream`, `runtime-caps`, `delegation-frames`, `runner-deps`, `engine-notice`); every import path and the public export surface are byte-identical; a re-export closed-set pin refuses a declaration re-appearing in the hub.
21
+
22
+ ### Added
23
+ - `processFingerprint(pid?)`, `isProcessLive(record)`, `type ProcessFingerprint` — the file store's process-identity pair, exported for a host's own liveness arm.
24
+ - `type RunnerSelfSeat`.
25
+
26
+ ### Unchanged and pinned
27
+ - Public export surface: 7.6.0's 2 098 names plus the four above, zero removed. Wire frames, the durable park row, the 23 gate stations, `TaskResult.terminal`, the MCP failure record's kinds: unchanged.
28
+
29
+ ## 7.6.0 — 2026-09-07
30
+
31
+ **BREAKING.** design/390 S6 (three slices, merged): the tool gate's settlement/origin words become ONE `GateOutcome` record, `TaskResult` becomes ONE terminal record, MCP failures become ONE structural record with a delivery verdict, and the durable off switch becomes a word. Every consumer that read the retired keys must migrate (server 7.64.0, cli/client-core, web-admin, desktop — the criteria post lists the frames old→new).
32
+
33
+ ### BREAKING — exports removed (10)
34
+ - `APPROVAL_SETTLED_BY_VALUES`, `ASK_DENY_RESOLUTION_VALUES`, `isApprovalSettledBy`, `isAskDenyResolution`, `type ApprovalSettledBy`, `type AskDenyResolution`, `type PermissionDeniedSource` (S6-A — replaced by `GateOutcome` / `Settlement` / `DeniedBy`).
35
+ - `MCP_FAILURE_CODES`, `mcpFailureCodeOf`, `type McpFailureCode` (S6-B — replaced by `McpFailure` / `MCP_FAILURE_KINDS` / `classifyMcpFailure`).
36
+
37
+ ### BREAKING — same-name shape changes
38
+ - `TaskResult`: the eight plane fields `status` / `errorCode` / `errorMessage` / `blockedReason` / `checkpointToken` / `checkpointId` / `checkpointGate` / `workspaceRestoreMode` are deleted; the run's end is ONE required `terminal: TerminalCause` (`completed` | `failed{code?, message?, nestedPause?}` | `blocked{reason}` | `paused{gate, token, checkpointId?, restoreMode?}`). `terminalProjection(terminal)` is the explicit projection for a face that keeps the old shape. The `done` frame, the HTTP task body and SSE `done.result` are the result itself, so they lose the eight keys and carry `terminal`; a nested boundary's pause is `failed` + `nestedPause` (its token no longer rides `checkpointToken`).
39
+ - `TaskSpec.checkpointStore`: the per-run durable off switch is the word `"disabled"`; the retired `null` is refused at the first prepare with `config.invalid_checkpoint_store` (also through a worktree baseline overlay). `engine_notice` cause `task_store_null` → `task_store_disabled`.
40
+ - `Checkpoint` is a type (`CheckpointRow & CheckpointPause`) whose gate × pendingAction pairing is fixed by `PAUSE_REGISTRY`; hand-built rows go through `checkpointFrom` / `pauseOf`. A dry-run `needs_review` row carries its own `pendingAction.kind: "review"`.
41
+ - `ResumeOutcome.policy_ask.hostDecision` is required: a host resolving a park supplies decision FACTS (`{decision, decidedBy: "person" | "sla_timeout", …}`) and core mints every settlement word — a host never spells a kind.
42
+ - `RealApprovalGateBit.origin` is the full `AskOrigin` set (was a five-word subset) and gains the org FACT member `org?: "rule" | "unavailable"`; read governance through `realApprovalOrgFact(bit)`, never through the routing word. `AskOrigin` gains `safety_tighten` and `shell_gate_tighten`.
43
+ - `TeacherRunResult` and `VerificationResult` (they extend `TaskResult`) lose the same eight plane keys; `ResumeOutcome` loses `settledBy` / `approver` (the host supplies `hostDecision` facts instead).
44
+ - `ResolvedAsk` loses its four settlement keys; `RepairResult.terminal` → `repairTerminal`; `McpErrorMarks.errorKind` is replaced by `McpFailure{kind, delivered, httpStatus?}`; `SubagentErrorKind` drops `overloaded` and gains `unknown`.
45
+
46
+ ### BREAKING — wire frames
47
+ - `tool_end`: `settledBy` / `resolution` / `autoDenied` / `approver` deleted → `gate?: GateOutcome` (`disposition: {kind:"allowed"} | {kind:"denied", deniedBy}`, `settlement?: Settlement` (12 kinds, `who` fixed by `kind`), `origin?: AskOrigin`); `errorCode` speaks `McpFailureKind` (`connection_failed` / `connection_closed` / `timeout` / `protocol` / `http_status` / …; `network` and `http_<n>` retired) or a result code (`mcp.task_required`, `mcp.tool_error`, `mcp.server_revoked`); new `delivered?: "yes" | "no" | "unknown"`.
48
+ - `permissionDenied` observer payload: `source` / `resolution` / `autoDenied` deleted → `gate: GateOutcome` (frozen). `DeniedBy` = policy / hook / org / classifier / plan_mode / compliance / write_protection / ask_resolution.
49
+ - `wiring_manifest.mcp[]`: `errorCode` in the new words, plus `delivered` and `httpStatus`.
50
+ - Checkpoint rows: `realApproval.origin` full set + `org`; resolved rows record `resolvedOutcome.gateOutcome`; a `review` pending kind exists.
51
+ - Agent card `error_kind`: `overloaded` → gone, `unknown` added (no structured code ⇒ `unknown`, not retryable).
52
+
53
+ ### Changed (narrowings, named)
54
+ - An approval-factory approver's bare refusal on the parent thread halts the batch like a live approver's; a person's yes vetoed by a later layer is recorded as `human_allowed` beside the vetoing `deniedBy`; the post-CAS belt's refusal carries the veto record.
55
+ - The HTTP MCP transport no longer follows redirects (3xx ⇒ `http_status`, delivered `unknown`); a listing requiring task support is refused before any send (`mcp.task_required`, delivered `no`); a server's `isError:true` reply is `mcp.tool_error` with delivered `yes`; same-raw-name listings mount from one accepted view; a bare `TypeError` classifies as `unknown`, never `connection_failed`.
56
+ - A resume journal entry whose result carries no closed-set terminal cause (a journal written before `TaskResult.terminal`, or a malformed entry) refuses the resume with `WorkflowJournalIncompatibleError` (`workflow.journal_incompatible`) before any agent is dispatched — it no longer diverges into a live re-run of completed work. `TERMINAL_CAUSE_IS_REPLAYABLE` is the one table behind admission and replay.
57
+ - A background child's "durable pause without a token" arm is retired (unconstructible).
58
+
59
+ ### Added
60
+ - `GateOutcome` / `Settlement` / `DeniedBy` (+ `SETTLEMENT_KINDS`, `DENIED_BY_VALUES`, `SETTLEMENT_IS_REFUSAL`, `DENIED_BY_MAY_VETO`, `isSettlementKind`, `isDeniedBy`, `screenGateOutcome`), minted once at the gate exit (`mintGateOutcome`) and projected onto three faces; invariants I1–I4 screened at the wire entry; `ORIGIN_IMPLIES_REAL_APPROVAL`.
61
+ - `PAUSE_REGISTRY` (+ `GateKind`, `PendingKind`, `ResumeGate`, `isGateKind`, `resumeGateMatches`, `checkpointFrom`, `pauseOf`, `CheckpointFields`, `CheckpointPause`, `CheckpointRow`).
62
+ - `TerminalCause`, `PausedCause`, `terminalProjection`, `realApprovalOrgFact`, `resolveCheckpointStore`.
63
+ - `McpFailure`, `MCP_FAILURE_KINDS`, `MCP_DELIVERY_VERDICTS`, `McpDelivered`, `classifyMcpFailure` (ordered structural classifier, no text regex).
64
+ - `WorkflowJournalIncompatibleError`.
65
+ - Gates: `gate:closed-sets` (`docs/CLOSED-SETS.md` registry + `AssertAllKeysHandled` fences + switch exhaustiveness) and `gate:contract-sentences` (`@contract` ids, `docs/CONTRACT-SENTENCES.md`, duplicate-sentence baseline); invariant pins `test/gate-outcome-invariants.test.ts`; `test/design390-d8-terminal-only.test.ts`.
66
+
67
+ ### Unchanged and pinned
68
+ - The suspendAsk 13-parameter `AskCarry`; the 23 gate stations (words, order); delegation lifecycle frames, `task.end` trace, agent cards, reports and workflow records keep their shape through the explicit projection; `engine_notice` codes other than the renamed cause; `wiring_manifest` keys other than `mcp[]`.
69
+
3
70
  ## 7.5.2 — 2026-09-06
4
71
 
5
72
  Maintenance release: design/390 slices S3–S4 complete the `prepareTask` decomposition (4 756 → 1 666 driver lines, 34 phase modules), one settlement-path narrowing (#587), and the wire-manifest tooling that the 7.5.1 review asked for (#588 / #589). **No BREAKING change**: the export surface is byte-identical to 7.5.1; the only behaviour delta is that a reserved-tool-name collision now disposes an acquired protocol handle once instead of twice (below).
@@ -1,10 +1,10 @@
1
1
  import { Type } from "typebox";
2
- import type { Runner } from "../core/runner/runtask.js";
2
+ import type { RunnerSelfSeat } from "../core/runner/contracts.js";
3
3
  /** Model-facing name of the child-transcript read tool (residual lane C). */
4
4
  export declare const AGENT_TRANSCRIPT_TOOL_NAME = "AgentTranscript";
5
5
  export interface AgentTranscriptToolOptions {
6
6
  /** Runner whose `sessions` store holds the child's persisted history. */
7
- runner: Runner;
7
+ runner: RunnerSelfSeat;
8
8
  /** The unified registry the background Agent lane registered its a* handles in (scopes reads to lineage). */
9
9
  registry: import("../core/task-registry.js").TaskRegistry;
10
10
  /** design/151 S1b: the durable ledger — when the live row/retain entry is gone (restart, terminal GC,
@@ -1,6 +1,5 @@
1
- import type { Runner } from "../core/runner/runtask.js";
2
- import type { RunInternals } from "../core/runner/prepare-task.js";
3
- import type { ModelRef, TaskResult, TaskSpec } from "../core/types.js";
1
+ import type { RunInternals, RunnerSelfSeat } from "../core/runner/contracts.js";
2
+ import type { ModelRef, TaskResult, TaskSpec, TaskStatus } from "../core/types.js";
4
3
  /**
5
4
  * Quality-gate cascade (design/27). Run the SAME task across a ladder of models (cheapest → strongest);
6
5
  * after each rung a caller-supplied **decidable gate** judges "good enough" — pass = keep it, fail =
@@ -109,7 +108,7 @@ export interface CascadeAttempt {
109
108
  * F4) — a fabricated 0 here both misreported the attempt and let escalation ride under a finite
110
109
  * ceiling the engine could not actually enforce. */
111
110
  costMicroUsd?: number;
112
- status: TaskResult["status"];
111
+ status: TaskStatus;
113
112
  /** The rung's own failure code (e.g. "auth") if its `runTask` failed — distinct from "gate rejected it". */
114
113
  errorCode?: string;
115
114
  /** Set when the gate THREW (vs returned false) — surfaces a broken verifier-gate (expired key, etc.). */
@@ -154,7 +153,7 @@ export interface CascadeRunResult extends TaskResult {
154
153
  * (`effect: "write"`), those execute once PER rung — ensure idempotency, or gate before the side effect
155
154
  * (e.g. a structured-output gate). `TaskSpec` never guaranteed idempotency; cascade makes it explicit.
156
155
  */
157
- export declare function runCascade(runner: Runner, spec: TaskSpec, config: CascadeConfig,
156
+ export declare function runCascade(runner: RunnerSelfSeat, spec: TaskSpec, config: CascadeConfig,
158
157
  /**
159
158
  * The TRUSTED, run-scoped internals channel — the same object `Runner.runTask` takes as its second
160
159
  * argument — forwarded to EVERY rung under the composition forward contract (identity, with the one
@@ -1,3 +1,4 @@
1
+ import { terminalProjection } from "../core/runner/terminal-projection.js";
1
2
  import { releaseSession } from "./session-util.js";
2
3
  import { mapNestedSuspend, isDurablePause } from "./suspend-guard.js";
3
4
  import { buildCumulativeStats } from "./cumulative-stats.js";
@@ -10,9 +11,13 @@ function cascadeConfigError(message) {
10
11
  e.code = CASCADE_CONFIG_ERROR_CODE;
11
12
  return e;
12
13
  }
14
+ function attemptTerminal(result) {
15
+ const face = terminalProjection(result.terminal);
16
+ return { status: face.status, errorCode: face.errorCode };
17
+ }
13
18
  function createDefaultGate(spec) {
14
19
  const requiresStructured = spec.outputSchema != null;
15
- return (result) => result.status === "completed" && (!requiresStructured || result.structuredOutput !== undefined);
20
+ return (result) => result.terminal.kind === "completed" && (!requiresStructured || result.structuredOutput !== undefined);
16
21
  }
17
22
  export async function runCascade(runner, spec, config, internals) {
18
23
  const ladder = config.ladder;
@@ -126,9 +131,9 @@ export async function runCascade(runner, spec, config, internals) {
126
131
  nestedTasks += s.nested.tasks;
127
132
  nestedCostMicro += s.nested.costMicroUsd ?? 0;
128
133
  }
129
- if (isDurablePause(result.status)) {
134
+ if (isDurablePause(result.terminal)) {
130
135
  const mapped = mapNestedSuspend(result);
131
- attempts.push({ index: i, model: rung.model, passed: false, costMicroUsd: rungCost, status: result.status, errorCode: mapped.errorCode });
136
+ attempts.push({ index: i, model: rung.model, passed: false, costMicroUsd: rungCost, status: attemptTerminal(result).status, errorCode: attemptTerminal(mapped).errorCode });
132
137
  return {
133
138
  ...mapped,
134
139
  stats: cumulativeStats(result.stats),
@@ -145,8 +150,7 @@ export async function runCascade(runner, spec, config, internals) {
145
150
  model: rung.model,
146
151
  passed: false,
147
152
  costMicroUsd: rungCost,
148
- status: result.status,
149
- errorCode: result.errorCode,
153
+ ...attemptTerminal(result),
150
154
  ...(deadlineFired ? { timedOut: true } : { cancelled: true }),
151
155
  });
152
156
  notifier.notify(() => config.onRung?.({ index: i, model: rung.model, passed: false, result }), CASCADE_ON_RUNG_SITE);
@@ -170,7 +174,7 @@ export async function runCascade(runner, spec, config, internals) {
170
174
  passed = false;
171
175
  gateError = e instanceof Error ? e.message : String(e);
172
176
  }
173
- const statusRejected = passed && config.acceptPartial !== true && result.status !== "completed";
177
+ const statusRejected = passed && config.acceptPartial !== true && result.terminal.kind !== "completed";
174
178
  if (statusRejected)
175
179
  passed = false;
176
180
  attempts.push({
@@ -178,8 +182,7 @@ export async function runCascade(runner, spec, config, internals) {
178
182
  model: rung.model,
179
183
  passed,
180
184
  costMicroUsd: rungCost,
181
- status: result.status,
182
- errorCode: result.errorCode,
185
+ ...attemptTerminal(result),
183
186
  gateError,
184
187
  diagnostics,
185
188
  ...(statusRejected ? { statusRejected: true } : {}),
@@ -198,9 +201,8 @@ export async function runCascade(runner, spec, config, internals) {
198
201
  return {
199
202
  taskId: spec.taskId ?? "",
200
203
  sessionId: "",
201
- status: "failed",
202
204
  result: `runCascade: the overall wall-clock budget (totalTimeoutMs ${config.totalTimeoutMs}ms) was already spent when the ladder was entered — no rung dispatched`,
203
- errorCode: CASCADE_NO_DISPATCH_ERROR_CODE,
205
+ terminal: { kind: "failed", code: CASCADE_NO_DISPATCH_ERROR_CODE },
204
206
  stats: { tokens: 0, turns: 0, costMicroUsd: 0 },
205
207
  cascadeOutcome: "exhausted",
206
208
  escalated: false,
@@ -1,4 +1,4 @@
1
- import type { Runner } from "../core/runner/runtask.js";
1
+ import type { RunnerSelfSeat } from "../core/runner/contracts.js";
2
2
  import type { TaskSpec } from "../core/types.js";
3
3
  import type { ExecutionEnv } from "../internal/harness-types.js";
4
4
  import type { VerificationResult } from "./verify.js";
@@ -179,7 +179,7 @@ export interface RepairLoopConfig {
179
179
  /** Per-attempt callback (observability). */
180
180
  onAttempt?: (info: {
181
181
  attempt: number;
182
- terminal?: RepairTerminal;
182
+ repairTerminal?: RepairTerminal;
183
183
  oracle: OracleResult;
184
184
  }) => void;
185
185
  /**
@@ -191,8 +191,10 @@ export interface RepairLoopConfig {
191
191
  onWarn?: (warning: Error) => void;
192
192
  }
193
193
  export interface RepairResult extends VerificationResult {
194
- /** The projected terminal (§2). SAFE-tier never returns `fixed`. */
195
- terminal: RepairTerminal;
194
+ /** The projected REPAIR terminal (§2) the loop's own word (fixed / candidate_only / gave_up / …), distinct
195
+ * from the inherited run terminal `terminal` (a {@link import("../core/types.js").TerminalCause}, WHY the
196
+ * worker's last leg ended). SAFE-tier never returns `fixed`. */
197
+ repairTerminal: RepairTerminal;
196
198
  /** The final repair state (in-memory unless an orthogonal suspend serialized it — §4). */
197
199
  bundle: RepairBundle;
198
200
  /**
@@ -276,4 +278,4 @@ export declare function terminalForTier(oracle: OracleResult): RepairTerminal |
276
278
  * SAFE-tier: the projection caps every PASS at `candidate_only` — this NEVER auto-accepts. `runRepairLoop` is a
277
279
  * SIBLING of {@link verifyCompleted}: it composes over `runner.runTask` and never touches the Runner core.
278
280
  */
279
- export declare function runRepairLoop(runner: Runner, implSpec: TaskSpec, config: RepairLoopConfig): Promise<RepairResult>;
281
+ export declare function runRepairLoop(runner: RunnerSelfSeat, implSpec: TaskSpec, config: RepairLoopConfig): Promise<RepairResult>;
@@ -1,4 +1,4 @@
1
- import { isDurablePause } from "./suspend-guard.js";
1
+ import { isDurablePause, pauseFamily } from "./suspend-guard.js";
2
2
  import { delimitUntrusted, sanitizeUntrustedText } from "../core/untrusted-text.js";
3
3
  import { assertOracleIsolation } from "../core/oracle-isolation.js";
4
4
  import { buildCumulativeStats } from "./cumulative-stats.js";
@@ -48,12 +48,11 @@ export async function runRepairLoop(runner, implSpec, config) {
48
48
  const oracle = { tier: "none", passed: false, flaky: false, retries: 0 };
49
49
  return {
50
50
  taskId: implSpec.taskId ?? "",
51
- status: "failed",
52
51
  result: `oracle isolation check failed: ${reason}`,
53
- errorCode: "oracle.unprotected",
52
+ terminal: { kind: "failed", code: "oracle.unprotected", message: `oracle isolation check failed: ${reason}` },
54
53
  sessionId: "",
55
54
  stats: { tokens: 0, turns: 0, costMicroUsd: 0 },
56
- terminal: "oracle.unprotected",
55
+ repairTerminal: "oracle.unprotected",
57
56
  ...(isolationClass ? { isolationClass } : {}),
58
57
  bundle: { ...freshBundle(), attemptCount: config.resumeBundle?.attemptCount ?? 0 },
59
58
  oracleCostMicroUsd: 0,
@@ -157,12 +156,11 @@ export async function runRepairLoop(runner, implSpec, config) {
157
156
  };
158
157
  const exhaustedBeforeDispatch = (message, errorCode) => ({
159
158
  taskId: implSpec.taskId ?? "",
160
- status: "failed",
161
159
  result: message ?? `repair loop: attempt budget already spent (attemptCount ${bundle.attemptCount} >= maxAttempts ${maxAttempts}) — no attempt dispatched`,
162
- errorCode: errorCode ?? "repair.attempts_exhausted",
160
+ terminal: { kind: "failed", code: errorCode ?? "repair.attempts_exhausted", message: message ?? `repair loop: attempt budget already spent (attemptCount ${bundle.attemptCount} >= maxAttempts ${maxAttempts}) — no attempt dispatched` },
163
161
  sessionId: implSpec.sessionId ?? "",
164
162
  stats: { tokens: 0, turns: 0, costMicroUsd: 0 },
165
- terminal: "gave_up",
163
+ repairTerminal: "gave_up",
166
164
  bundle,
167
165
  ...(isolationClass ? { isolationClass } : {}),
168
166
  oracleCostMicroUsd,
@@ -208,22 +206,22 @@ export async function runRepairLoop(runner, implSpec, config) {
208
206
  accumulate(result.stats);
209
207
  lastResult = result;
210
208
  bundle.attemptCount += 1;
211
- if (isDurablePause(result.status)) {
209
+ if (isDurablePause(result.terminal)) {
212
210
  return {
213
211
  ...result,
214
212
  stats: cumulativeStats(result.stats),
215
- terminal: "gave_up",
213
+ repairTerminal: "gave_up",
216
214
  bundle,
217
215
  ...(isolationClass ? { isolationClass } : {}),
218
216
  oracleCostMicroUsd,
219
- verification: { verdict: "unverified", unverifiedReason: result.status === "needs_review" ? "needs_review" : "suspended", rounds: bundle.attemptCount, findings: [] },
217
+ verification: { verdict: "unverified", unverifiedReason: pauseFamily(result.terminal), rounds: bundle.attemptCount, findings: [] },
220
218
  };
221
219
  }
222
- if (result.status !== "completed") {
220
+ if (result.terminal.kind !== "completed") {
223
221
  return {
224
222
  ...result,
225
223
  stats: cumulativeStats(result.stats),
226
- terminal: "gave_up",
224
+ repairTerminal: "gave_up",
227
225
  bundle,
228
226
  ...(isolationClass ? { isolationClass } : {}),
229
227
  oracleCostMicroUsd,
@@ -237,12 +235,12 @@ export async function runRepairLoop(runner, implSpec, config) {
237
235
  lastOracle = oracle;
238
236
  bundle.oracleTier = oracle.tier;
239
237
  const projected = terminalForTier(oracle);
240
- config.onAttempt?.({ attempt: bundle.attemptCount, terminal: projected, oracle });
238
+ config.onAttempt?.({ attempt: bundle.attemptCount, repairTerminal: projected, oracle });
241
239
  if (projected !== undefined) {
242
240
  return {
243
241
  ...result,
244
242
  stats: cumulativeStats(result.stats),
245
- terminal: projected,
243
+ repairTerminal: projected,
246
244
  bundle,
247
245
  ...(isolationClass ? { isolationClass } : {}),
248
246
  oracleCostMicroUsd,
@@ -266,7 +264,7 @@ export async function runRepairLoop(runner, implSpec, config) {
266
264
  return {
267
265
  ...lastResult,
268
266
  stats: cumulativeStats(lastResult.stats),
269
- terminal,
267
+ repairTerminal: terminal,
270
268
  bundle,
271
269
  ...(isolationClass ? { isolationClass } : {}),
272
270
  oracleCostMicroUsd,
@@ -1,6 +1,5 @@
1
- import type { Runner } from "../core/runner/runtask.js";
1
+ import type { RunInternals, RunnerSelfSeat } from "../core/runner/contracts.js";
2
2
  import type { TaskSpec } from "../core/types.js";
3
- import type { RunInternals } from "../core/runner/prepare-task.js";
4
3
  /** design/122 D2 — per-child resume cap (default 8): a child can be revived at most this many times. */
5
4
  export declare const SUBAGENT_RESUME_CAP = 8;
6
5
  /**
@@ -25,7 +24,7 @@ export interface SubagentRetainEntry {
25
24
  * resume.evicted, the 1.283 ruling). Lifetime: the entry's `release` closure already captures the same
26
25
  * runner, so this adds no new retention class.
27
26
  */
28
- runner?: Runner;
27
+ runner?: RunnerSelfSeat;
29
28
  agentName?: string;
30
29
  /** fix-C5: PER-ENTRY retain TTL override (ms). A SESSION-scoped ledger is shared by two purposes with
31
30
  * different windows (failure-evidence retain = FAILED_SESSION_RETAIN_TTL_MS vs a session-scoped bg
@@ -1,5 +1,5 @@
1
1
  import { Type } from "typebox";
2
- import type { Runner } from "../core/runner/runtask.js";
2
+ import type { RunnerSelfSeat } from "../core/runner/contracts.js";
3
3
  import type { TaskNotificationPayload } from "../core/task-notification.js";
4
4
  import { type ToolCtxEnricher } from "../core/tools.js";
5
5
  import { type TaskAccess } from "../core/task-registry.js";
@@ -12,7 +12,7 @@ import { type PeerAdmissionConfig, type PeerInboundChainRef, type PeerSelfRef }
12
12
  export declare const SEND_MESSAGE_TOOL_NAME = "SendMessage";
13
13
  export interface SendMessageToolOptions {
14
14
  /** Runner used to execute the resumed child run (the design/122 resume face's delegation runner). */
15
- runner: Runner;
15
+ runner: RunnerSelfSeat;
16
16
  /** The unified task registry the background Agent lane registered its a* handles in. */
17
17
  registry: import("../core/task-registry.js").TaskRegistry;
18
18
  /** design/122 D1 — the parent run's retain ledger (present only when `TaskSpec.retainSubagentSessions`
@@ -1,7 +1,7 @@
1
- import type { Runner } from "../core/runner/runtask.js";
1
+ import type { RunnerSelfSeat } from "../core/runner/contracts.js";
2
2
  /**
3
3
  * Release a throwaway session, swallowing errors — best-effort cleanup for the verifier/cascade-rung
4
4
  * sessions that agents spin up and discard. Always call this in a `finally` so a throwing `runTask`
5
5
  * result handler or caller hook (`onRung`/`onRound`) can't leak the session.
6
6
  */
7
- export declare function releaseSession(runner: Runner, sessionId: string): Promise<void>;
7
+ export declare function releaseSession(runner: RunnerSelfSeat, sessionId: string): Promise<void>;
@@ -1,7 +1,6 @@
1
- import type { Runner } from "../core/runner/runtask.js";
2
- import type { AgentDefinition, Model, ModelRef, TaskLimits, ToolSpec } from "../core/types.js";
1
+ import type { RunInternals, RunnerSelfSeat } from "../core/runner/contracts.js";
2
+ import type { AgentDefinition, Model, ModelRef, TaskLimits, TerminalCause, ToolSpec } from "../core/types.js";
3
3
  import { type ExecutionEnv } from "../internal/harness.js";
4
- import type { RunInternals } from "../core/runner/prepare-task.js";
5
4
  import type { TaskNotificationPayload } from "../core/task-notification.js";
6
5
  import { RETAIN_DEFAULT_TTL_MS, RETAIN_DEFAULT_MAX } from "../config/defaults.js";
7
6
  /**
@@ -215,45 +214,28 @@ export declare function createSubagentWorktreeHelper(baseEnv: ExecutionEnv, repo
215
214
  /** Cap for the inline-defused worker-controlled report metadata lines (blocked_reason / error). Matches the
216
215
  * ask-question.ts inline-value convention (300) — a one-line operator-facing reason, not a bulk payload. */
217
216
  export declare const REPORT_FIELD_MAX = 300;
218
- /** The machine-readable failure class stamped on a FAILED sub-agent report (sema form — see below). */
219
- export type SubagentErrorKind = "rate_limit" | "overloaded" | "timeout" | "network" | "logic" | "governance";
220
217
  /**
221
- * RB-302 ATTRIBUTION CORRECTED (taxonomy and behavior unchanged; this is a SEMA form, not CC
222
- * parity). The old header called this "CC 2.1.201 parity (追平批 α 项5)". Re-verified against the CC
223
- * 2.1.220 corpus, three things are true and none of them support that claim:
224
- * CC's `errorKind` lives on an ERROR CLASS (`AgentApiErrorTerminationError` @409103-409110), not
225
- * on the delegation's report card the Agent result schema (@409040-409090: usage / toolStats /
226
- * modelsUsed / resolvedModel) has no `error_kind` field at all;
227
- * its DOMAIN is three values, `{rate_limit, overloaded, server_error}` (`G2_` @409113) — no
228
- * `timeout`, no `network`, no `logic`;
229
- * its USE is not a retry signal. It gates PARTIAL-OUTPUT HANDBACK (`V2_` @408558-408570): when the
230
- * kind is in that set and the child produced text before dying, CC returns the salvaged history
231
- * with the cutoff note "Everything below is PARTIAL output recovered from the agent before it was
232
- * cut off. The agent did NOT finish its task — treat these results as incomplete."
233
- * sema's pair is a different mechanism answering a different question (re-delegate or not), derived
234
- * from sema's OWN errorCode taxonomy. It is kept as-is: it serves a real parent-side decision, and
235
- * changing the domain now would break every consumer for a parity that was never there.
236
- *
237
- * Residual (registered, not done here): CC's partial-output handback has no sema equivalent on this
238
- * lane sema's `salvagedOutput` covers two other terminals only. And `authentication_failed`-style
239
- * auth faults still fold into `logic`, which is correct for "don't retry" but coarse for a caller
240
- * that wants to re-auth and retry.
241
- *
242
- * Classification (unchanged) — a FAILED child run into a coarse `error_kind` +
243
- * `retryable` pair so the PARENT model (and an orchestrating deployment) can react correctly without
244
- * string-matching the report: a `rate_limit`/`overloaded`/`timeout`/`network` failure is transient
245
- * (re-delegating the same subtask may succeed); a `logic` failure is not (auth/invalid_request/budget/
246
- * output.* — re-delegating unchanged just burns tokens). Derived from the EXISTING taxonomy: the
247
- * child's `TaskResult.errorCode` (assemble-result's priority chain — brain `[code]` prefixes lifted
248
- * via `extractErrorCode`, `limits.max_walltime_exceeded`, `budget.*`, …) plus a message sniff for the two classes our
249
- * BrainError codes fold together ("server" covers overloaded_error/529 AND plain 5xx; "network"
250
- * covers both timeouts and resets). Returns undefined for a non-failed child (no error to classify).
218
+ * The machine-readable failure class stamped on a FAILED sub-agent report (the Agent card's `error_kind`
219
+ * beside `retryable`). A sema form, not a CC one: CC's kind lives on an error class and gates
220
+ * partial-output handback, not retry; sema's pair answers the parent's one question re-delegate or not.
221
+ * · `rate_limit` / `network` / `timeout` transient (re-delegating the same subtask may succeed);
222
+ * · `governance` a registered policy-plane refusal; retryability is the
223
+ * governance table's transient/terminal split;
224
+ * · `logic` — a coded failure re-delegating unchanged cannot fix
225
+ * (auth / invalid_request / http / output.* / budget axes);
226
+ * · `unknown` — the child reported NO code: nothing structural to classify,
227
+ * and the class is not guessed from the message (not retried).
228
+ */
229
+ export declare const SUBAGENT_ERROR_KINDS: readonly ["rate_limit", "timeout", "network", "logic", "governance", "unknown"];
230
+ export type SubagentErrorKind = (typeof SUBAGENT_ERROR_KINDS)[number];
231
+ /**
232
+ * Classify a FAILED child run into its `error_kind` + `retryable` pair from the child's STRUCTURED
233
+ * terminal code alone (the `TerminalCause.failed.code` taxonomy: brain codes, `limits.*`, `budget.*`,
234
+ * governance codes). The message is never read: a class read out of prose moved when the prose moved,
235
+ * and a remote's own error text could steer it. A child with no code is `unknown`. Returns undefined
236
+ * for a non-failed child (no error to classify).
251
237
  */
252
- export declare function classifySubagentError(child: {
253
- status: string;
254
- errorCode?: string;
255
- errorMessage?: string;
256
- }): {
238
+ export declare function classifySubagentError(terminal: TerminalCause): {
257
239
  errorKind: SubagentErrorKind;
258
240
  retryable: boolean;
259
241
  } | undefined;
@@ -315,13 +297,12 @@ export declare function classifySubagentError(child: {
315
297
  export declare function completedAgentCard(child: {
316
298
  taskId: string;
317
299
  sessionId: string;
318
- status: string;
300
+ /** The child's terminal cause. The card speaks its projection (`status` + the failure-attribution keys), never the
301
+ * cause's own members: a paused token, or the pause a nested boundary wrapped on `nestedPause`, is the resume CAPABILITY. */
302
+ terminal: TerminalCause;
319
303
  model?: string;
320
304
  result: string;
321
305
  salvagedOutput?: string;
322
- blockedReason?: string;
323
- errorMessage?: string;
324
- errorCode?: string;
325
306
  /** The child run's provider-boundary fault assertion, verbatim from its `TaskResult.apiFailure`.
326
307
  * PRESENCE is the claim ("this child's terminal failure came from the transport/provider, not from
327
308
  * this deployment refusing to send, a limit, or unusable model output"); the members are whatever
@@ -451,7 +432,7 @@ export declare function markerFragment(): string;
451
432
  export declare function createSubagentResume(deps: {
452
433
  ledger: SubagentRetainLedger | undefined;
453
434
  parentToolCallId: string;
454
- runner: Runner;
435
+ runner: RunnerSelfSeat;
455
436
  notify?: (n: TaskNotificationPayload, opts?: {
456
437
  priority?: "now" | "next" | "later";
457
438
  }) => void;
@@ -634,7 +615,7 @@ export interface SubagentToolOptions {
634
615
  * .jsonl` + a `.meta.json` sidecar), so "the child conversation is readable afterwards" is a
635
616
  * property CC never makes conditional on a store tier.
636
617
  */
637
- runner: Runner;
618
+ runner: RunnerSelfSeat;
638
619
  /**
639
620
  * design/115 P3 — background sub-agents (CC `run_in_background`). When set, the tool exposes the
640
621
  * `run_in_background` parameter: the call returns an `a*` task_id immediately, the child runs