@sema-agent/core 7.6.0 → 7.6.2
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.
- package/CHANGELOG.md +37 -0
- package/dist/agents/agent-transcript-tool.d.ts +2 -2
- package/dist/agents/cascade.d.ts +2 -3
- package/dist/agents/repair-loop.d.ts +2 -2
- package/dist/agents/retain-ledger.d.ts +2 -3
- package/dist/agents/send-message-tool.d.ts +2 -2
- package/dist/agents/session-util.d.ts +2 -2
- package/dist/agents/subagent.d.ts +3 -4
- package/dist/agents/teacher.d.ts +2 -2
- package/dist/agents/team.d.ts +2 -2
- package/dist/agents/verify.d.ts +5 -6
- package/dist/core/agent-definition.d.ts +172 -0
- package/dist/core/agent-definition.js +1 -0
- package/dist/core/checkpoint-store.d.ts +8 -4
- package/dist/core/delegation-frames.d.ts +298 -0
- package/dist/core/delegation-frames.js +21 -0
- package/dist/core/engine-notice.d.ts +555 -0
- package/dist/core/engine-notice.js +55 -0
- package/dist/core/gate-fold.d.ts +12 -0
- package/dist/core/gate-fold.js +158 -0
- package/dist/core/gate-lanes.d.ts +93 -0
- package/dist/core/gate-lanes.js +626 -0
- package/dist/core/hands-band.d.ts +134 -0
- package/dist/core/hands-band.js +1 -0
- package/dist/core/hooks.d.ts +20 -101
- package/dist/core/hooks.js +53 -854
- package/dist/core/mcp-failure.d.ts +43 -5
- package/dist/core/mcp-failure.js +31 -14
- package/dist/core/mcp-server-spec.d.ts +217 -0
- package/dist/core/mcp-server-spec.js +1 -0
- package/dist/core/model-seat.d.ts +99 -0
- package/dist/core/model-seat.js +1 -0
- package/dist/core/reminder-mint.d.ts +10 -0
- package/dist/core/reminder-mint.js +3 -0
- package/dist/core/runner/contracts.d.ts +382 -6
- package/dist/core/runner/gate-exit.d.ts +177 -9
- package/dist/core/runner/gate-exit.js +70 -1
- package/dist/core/runner/prepare-caps-and-workflow.d.ts +2 -7
- package/dist/core/runner/prepare-delegation-surface.d.ts +2 -7
- package/dist/core/runner/prepare-run-refs.d.ts +12 -0
- package/dist/core/runner/prepare-run-refs.js +5 -0
- package/dist/core/runner/prepare-task.d.ts +2 -2
- package/dist/core/runner/runtask.d.ts +4 -71
- package/dist/core/runner/runtask.js +18 -6
- package/dist/core/runner-deps.d.ts +1416 -0
- package/dist/core/runner-deps.js +1 -0
- package/dist/core/runtime-caps.d.ts +164 -0
- package/dist/core/runtime-caps.js +1 -0
- package/dist/core/task-event.d.ts +910 -0
- package/dist/core/task-event.js +1 -0
- package/dist/core/task-limits.d.ts +110 -0
- package/dist/core/task-limits.js +1 -0
- package/dist/core/task-result.d.ts +809 -0
- package/dist/core/task-result.js +1 -0
- package/dist/core/task-spec.d.ts +1370 -0
- package/dist/core/task-spec.js +1 -0
- package/dist/core/task-stream.d.ts +382 -0
- package/dist/core/task-stream.js +1 -0
- package/dist/core/tool-spec.d.ts +1174 -0
- package/dist/core/tool-spec.js +1 -0
- package/dist/core/types.d.ts +26 -7691
- package/dist/core/types.js +2 -76
- package/dist/core/warm-resume.d.ts +2 -2
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -1
- package/dist/orchestration/goal.d.ts +2 -2
- package/dist/orchestration/run-spec.d.ts +2 -2
- package/dist/orchestration/run-workflow-tool.d.ts +3 -3
- package/dist/orchestration/workflow.d.ts +4 -4
- package/dist/scenarios/scenario-registry.d.ts +3 -3
- package/dist/scenarios/teacher-quickstart.d.ts +2 -2
- package/dist/server/http.d.ts +2 -2
- package/dist/stores/file/fs-atomic.d.ts +88 -12
- package/dist/stores/file/fs-atomic.js +184 -55
- package/dist/stores/file/index.d.ts +1 -0
- package/dist/stores/file/index.js +1 -0
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +9 -1
|
@@ -0,0 +1,809 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The TERMINAL record of one run: `TaskResult`, the plane word it still projects (`TaskStatus`), the
|
|
3
|
+
* memory-scope summary it carries (`EffectiveMemoryScopes`) and the remote-workspace failure note
|
|
4
|
+
* (`RemoteEnvFailureNote`). The cause itself lives one module over in `terminal-cause.ts`, which owns
|
|
5
|
+
* the closed set and its replay table. Layer 0 vocabulary; `types.ts` re-exports every name below, so
|
|
6
|
+
* no consumer's import changes.
|
|
7
|
+
*/
|
|
8
|
+
import type { TerminalCause } from "./terminal-cause.js";
|
|
9
|
+
import type { NestedUsage } from "./tool-spec.js";
|
|
10
|
+
/**
|
|
11
|
+
* `"suspended"` (design/45) is a **non-terminal** outcome: the task hit a durable tool-policy gate
|
|
12
|
+
* (`ask` with a `CheckpointStore` wired) and persisted a checkpoint instead of finishing — resume it
|
|
13
|
+
* with `runner.resume(checkpointToken, outcome)`. v1 contract: it is only safe at the **top-level
|
|
14
|
+
* `runTask` boundary**; the orchestrators (cascade/teacher/verify/team) map an unexpected `suspended`
|
|
15
|
+
* to `failed` + `errorCode:"unexpected.suspended"` and pass the token up (§11 Q6, hard boundary).
|
|
16
|
+
*
|
|
17
|
+
* `"needs_review"` (design/76 §2.5, dry-run / shadow; design/80 D-B, plan-gate) is also a **non-terminal,
|
|
18
|
+
* resumable** outcome, but a DISTINCT one: it is the REVIEW-PAUSE terminal a human/judge must clear. Two
|
|
19
|
+
* disjoint gate kinds share it (carrying a `reviewRef`, errorCode `"review.pending"`): a `{kind:"needs_review"}`
|
|
20
|
+
* checkpoint — a profile's dry-run interception produced a predicted state-diff to REVIEW before it is applied,
|
|
21
|
+
* resumed with a `dry_run_review` outcome — and a `{kind:"plan_review"}` checkpoint (design/80 D-B) — a
|
|
22
|
+
* profile's plan-gate produced a proposed PLAN a human must approve/edit/reject BEFORE any step runs, resumed
|
|
23
|
+
* with a `plan_review` outcome. Both are DISJOINT from `"suspended"`: that is a PRE-ACTION approval of a TOOL
|
|
24
|
+
* CALL (resumed with `policy_ask`); these are human REVIEWS (the distinct gate.kind picks the resume outcome).
|
|
25
|
+
* Orchestrators that map an unexpected `suspended` should map an unexpected `needs_review` the same
|
|
26
|
+
* hard-boundary way (it is, like `suspended`, only safe at the top-level `runTask` boundary).
|
|
27
|
+
*/
|
|
28
|
+
export type TaskStatus = "completed" | "blocked" | "failed" | "suspended" | "needs_review";
|
|
29
|
+
/**
|
|
30
|
+
* RB-439-a — one remote-workspace lifecycle failure, reported as data on {@link TaskResult.remoteEnvFailures}.
|
|
31
|
+
* See that field for why the eleven-code taxonomy needed a structured channel to reach a caller at all.
|
|
32
|
+
*/
|
|
33
|
+
export interface RemoteEnvFailureNote {
|
|
34
|
+
/** Which VM-lifecycle call failed. */
|
|
35
|
+
op: "suspendVM" | "resumeVM" | "postResumeInit";
|
|
36
|
+
/** The adapter's verbatim code — the whole point of this note. */
|
|
37
|
+
code: import("./remote-env.js").RemoteExecutionErrorCode;
|
|
38
|
+
/** `code` is in the retryable family (`RETRYABLE_REMOTE_ERROR_CODES`) — a re-attempt can plausibly work. */
|
|
39
|
+
retryable: boolean;
|
|
40
|
+
/** How many times the engine called `op` before giving up (1 = it did not retry). */
|
|
41
|
+
attempts: number;
|
|
42
|
+
/** The adapter's message. Human-facing; branch on `code`, not on this. */
|
|
43
|
+
message: string;
|
|
44
|
+
}
|
|
45
|
+
/** Result returned when a task finishes or gets stuck. Designed to be machine-readable for an external AI. */
|
|
46
|
+
/**
|
|
47
|
+
* design/178 v2 §2.3 (件①) — the value of {@link TaskResult.effectiveMemoryScopes}: the memory
|
|
48
|
+
* visibility face a leg ACTUALLY ran under, as a DISCRIMINATED three-state union (deliberately no
|
|
49
|
+
* `"partial"` state — the engine session is built only after every plane materialized, so a
|
|
50
|
+
* half-mounted scene is a fail-open `memoryless` with residue, never a served half-face):
|
|
51
|
+
*
|
|
52
|
+
* - `"mounted"` — the memory engine session mounted. `scopes` is the EFFECTIVE VISIBILITY set in
|
|
53
|
+
* the effective serving order, both exactly as the engine mounted them: per plane, the
|
|
54
|
+
* admission-projected read layering PLUS a distinct write-only scope where one exists (the
|
|
55
|
+
* engine registers the write scope's dir and its entries ride the injected index — a write-only
|
|
56
|
+
* scope IS visible; the common writeScope∈scopes form dedups to the plain layering), project
|
|
57
|
+
* plane first on a dual root. Each row carries its admission ORIGIN (`"deployment"` = the
|
|
58
|
+
* operator's own declared set; `"request"` = the caller's spec — the two trust planes of the
|
|
59
|
+
* org admission door). `writeScope` is the effective write face as granted (an org writeScope
|
|
60
|
+
* not explicitly granted reads `null` here, exactly as it ran). `contract` names the
|
|
61
|
+
* scope-identity contract in force (`"v2"` typed keys / `"legacy"` opaque strings).
|
|
62
|
+
* SCOPE OF THE CLAIM (stated precisely): the rows are the MEMORY-ENGINE scope set this leg
|
|
63
|
+
* mounted — the dirs the engine registered and indexed. They deliberately do NOT cover the
|
|
64
|
+
* separate memory-adjacent faces (the shared-memory store pair, the project-context memory
|
|
65
|
+
* layer — each its own surface with its own disclosure), and two REGISTERED engine-plane
|
|
66
|
+
* channels can carry another scope's bytes without a row here: a root-owning scope's non-empty
|
|
67
|
+
* on-disk index is served verbatim to a later mount of that root ("live file wins"),
|
|
68
|
+
* and control-plane announcements carry no producing scope and drain plane-wide. Both are
|
|
69
|
+
* engine mount semantics under their own tickets, disclosed here so this face is never read as
|
|
70
|
+
* a complete cross-face visibility proof.
|
|
71
|
+
* - `"memoryless"` — memory was configured but did not mount. `reason: "mount-failed"` = the
|
|
72
|
+
* fail-open mount arm caught a fault anywhere in the mount phase (directory resolution through
|
|
73
|
+
* materialize — named for the whole captured span, not one step); `reason: "no-backend"` = the
|
|
74
|
+
* spec enables memory but no `RunnerDeps.memoryBackend` is configured. `materializedResidue`
|
|
75
|
+
* (mount-failed only) lists plane scopes whose PHYSICAL materialize had already completed when
|
|
76
|
+
* the fault hit — a LOWER bound (a plane that threw mid-materialize is not listed; on-disk
|
|
77
|
+
* residue is ≥ this list), and explicitly NOT a visibility face: an auditor must never read
|
|
78
|
+
* residue as mounted scopes, which is why it is a separate seat from the always-empty `scopes`.
|
|
79
|
+
* - `"none"` — memory was not in play at all: `"no-spec"` = the task carries no usable memory
|
|
80
|
+
* spec; `"disabled"` = a spec is present with `enabled: false`.
|
|
81
|
+
*
|
|
82
|
+
* Deliberate-refusal configurations (`config.memory_*` codes) fail the whole prepare and produce
|
|
83
|
+
* NO observation — this union never dresses a refusal as a state. Honesty note (design r7): the
|
|
84
|
+
* `?: never` members forbid non-`undefined` values at the type level; the repo does not compile
|
|
85
|
+
* with `exactOptionalPropertyTypes`, so explicit-`undefined` presence is a wire-validator concern,
|
|
86
|
+
* not a type-level one.
|
|
87
|
+
*/
|
|
88
|
+
export type EffectiveMemoryScopes = {
|
|
89
|
+
state: "mounted";
|
|
90
|
+
reason?: never;
|
|
91
|
+
contract: "v2" | "legacy";
|
|
92
|
+
scopes: Array<{
|
|
93
|
+
scope: string;
|
|
94
|
+
origin: "deployment" | "request";
|
|
95
|
+
}>;
|
|
96
|
+
writeScope: string | null;
|
|
97
|
+
materializedResidue?: never;
|
|
98
|
+
} | {
|
|
99
|
+
state: "memoryless";
|
|
100
|
+
reason: "mount-failed";
|
|
101
|
+
contract?: "v2" | "legacy";
|
|
102
|
+
scopes: [];
|
|
103
|
+
writeScope: null;
|
|
104
|
+
materializedResidue?: string[];
|
|
105
|
+
} | {
|
|
106
|
+
state: "memoryless";
|
|
107
|
+
reason: "no-backend";
|
|
108
|
+
contract?: "v2" | "legacy";
|
|
109
|
+
scopes: [];
|
|
110
|
+
writeScope: null;
|
|
111
|
+
} | {
|
|
112
|
+
state: "none";
|
|
113
|
+
reason: "no-spec" | "disabled";
|
|
114
|
+
contract?: never;
|
|
115
|
+
scopes: [];
|
|
116
|
+
writeScope: null;
|
|
117
|
+
materializedResidue?: never;
|
|
118
|
+
};
|
|
119
|
+
export interface TaskResult {
|
|
120
|
+
taskId: string;
|
|
121
|
+
/**
|
|
122
|
+
* #499 — the ENGINE-minted identity of THIS run (uuidv7), minted once at the top of prepare and
|
|
123
|
+
* never rewritten. The third id of the trio, and the only one the engine owns:
|
|
124
|
+
* - {@link taskId} — the HOST's task identity, or (when the host named none) the session id: it is
|
|
125
|
+
* `spec.taskId ?? sessionId` and therefore answers the SAME value for every run of one session;
|
|
126
|
+
* - {@link sessionId} — the conversation, shared by every run that continues it;
|
|
127
|
+
* - `runId` — this call. Two runs of one session ALWAYS differ here; a host-supplied `taskId` does
|
|
128
|
+
* not change it (the host's handle and the engine's run are different questions).
|
|
129
|
+
*
|
|
130
|
+
* Use it to join a run's per-run disclosures (`task.user_steer_undrained` /
|
|
131
|
+
* `task.user_followup_undrained` / `route.fallback_to_primary` / `steering.parked_input_blocked` /
|
|
132
|
+
* `task.turn_interrupted` all carry `detail.runId`) and its `task.start`/`task.end` trace pair to
|
|
133
|
+
* the result they belong to. Do NOT use it as an aggregation key across runs — that is what
|
|
134
|
+
* `taskId`/`taskSignature` are for.
|
|
135
|
+
*
|
|
136
|
+
* **In-presence condition**: present on every terminal of a run that ENTERED prepare — a prepare-time
|
|
137
|
+
* refusal included. The id is minted and published at prepare's FIRST statement precisely so this
|
|
138
|
+
* holds: prepare itself emits run-scoped disclosures (`route.fallback_to_primary`), and a disclosure
|
|
139
|
+
* naming a run that no terminal ever names would be unjoinable on exactly the failure path it is
|
|
140
|
+
* about. It is absent only where no run was entered — a throw above the prepare call — and on
|
|
141
|
+
* stub/synthesized results a host assembles itself. Optional for that reason, never fabricated, and
|
|
142
|
+
* never the `"unknown"` sentinel `taskId`/`sessionId` fall back to on that path.
|
|
143
|
+
*/
|
|
144
|
+
runId?: string;
|
|
145
|
+
/** Use this to continue the same conversation on the next call. */
|
|
146
|
+
sessionId: string;
|
|
147
|
+
/**
|
|
148
|
+
* WHY the run ended — the ONE tagged cause ({@link TerminalCause}), and the ONLY terminal record on this
|
|
149
|
+
* result: there is no `status` word and no `errorCode`/`errorMessage`/`blockedReason`/`checkpointToken`/
|
|
150
|
+
* `checkpointId`/`checkpointGate`/`workspaceRestoreMode` beside it (a second spelling of the same fact
|
|
151
|
+
* would be the one a reader reaches for, and the two would have to be kept equal forever). Branch on
|
|
152
|
+
* `terminal.kind`: `"paused"` carries the token and the gate together (a suspended run with no token
|
|
153
|
+
* cannot be constructed), `"failed"` carries the code and the message together, `"blocked"` its reason.
|
|
154
|
+
* A face that still speaks the five-word {@link TaskStatus} derives it with
|
|
155
|
+
* {@link import("./runner/terminal-projection.js").terminalProjection} — the one cause→word derivation.
|
|
156
|
+
*/
|
|
157
|
+
terminal: TerminalCause;
|
|
158
|
+
/** design/99 MF-25: the EFFECTIVE model id that served this task — the RESOLVED `Model.id`, not the requested
|
|
159
|
+
* `TaskSpec.model` ref (which may be a role / name / `@mention`). Lets a UI echo "served by X" instead of the
|
|
160
|
+
* requested ref. A mid-run degradation is observed separately (see the degraded-model fields). */
|
|
161
|
+
model?: string;
|
|
162
|
+
/**
|
|
163
|
+
* #327 — the leg's effective REASONING resolution: the result-face twin of {@link model} for the thinking
|
|
164
|
+
* knob. How the requested tier resolved against the serving model's real capability
|
|
165
|
+
* (`requested`/`effective`/`graded`/`clamped`/`format`/`endpoint`, plus `dropped:true` when a
|
|
166
|
+
* non-reasoning model dropped the request entirely — field semantics on
|
|
167
|
+
* {@link import("../brain/reasoning.js").ResolvedReasoning}). It is the SAME resolver output the
|
|
168
|
+
* `reasoning.resolved` trace frame carries — the two faces cannot tell different stories; this seat
|
|
169
|
+
* serves consumers without a tracer (the trace frame is the deployment-observability face, this is the
|
|
170
|
+
* caller face).
|
|
171
|
+
*
|
|
172
|
+
* **In-presence condition** — mirrors the trace frame exactly: present on every terminal of a leg that ran
|
|
173
|
+
* with a REQUESTED thinking tier other than off/unset (the `spec > role > model.defaultThinking` chain);
|
|
174
|
+
* absent when thinking was off/unset for the leg, and on prepare failures (the resolution is minted after
|
|
175
|
+
* prepare). On a resumed task each leg re-resolves against the leg's own serving model.
|
|
176
|
+
*
|
|
177
|
+
* **Per-request correction** — the leg-entry mint runs before any request exists, so it can only
|
|
178
|
+
* describe the model's CAPABILITY. When the leg's FIRST committed request reports wire facts that change
|
|
179
|
+
* that answer, the seat is re-resolved against them by the same resolver and the trace twin is re-emitted
|
|
180
|
+
* with it. Today that is one arm: the anthropic budget path's cap-wins skip — a HARD per-request output
|
|
181
|
+
* cap too small to host a legal thinking budget deletes the thinking block, so a leg capped that way now
|
|
182
|
+
* reports `effective:"off"`/`dropped:true` instead of claiming the gradient the wire never carried. Only
|
|
183
|
+
* the FIRST committed request is consumed: the seat stays a leg-ENTRY snapshot (see the degradation law
|
|
184
|
+
* below), and the leg's nested internal brain calls (compaction summary, tool-invoked side query) carry
|
|
185
|
+
* their own caps and must never restate the leg's posture.
|
|
186
|
+
*
|
|
187
|
+
* **Degradation law** — same as {@link model}, whose resolution this is: the seat describes the leg's
|
|
188
|
+
* PRIMARY serving model at leg entry. A mid-run degradation (reactive fallback / near-budget switch)
|
|
189
|
+
* changes the serving model WITHOUT re-minting this seat or its trace twin — read {@link degraded} to see
|
|
190
|
+
* the switch; the fallback's own reasoning capability is deliberately NOT re-reported here (re-resolving
|
|
191
|
+
* one face would desync it from the task-start `reasoning.resolved` frame). A consumer needing the
|
|
192
|
+
* fallback's reasoning posture resolves `degraded.to` itself (`resolveReasoning` is exported).
|
|
193
|
+
*/
|
|
194
|
+
effectiveReasoning?: import("../brain/reasoning.js").ResolvedReasoning;
|
|
195
|
+
/** Final assistant text. */
|
|
196
|
+
result: string;
|
|
197
|
+
/**
|
|
198
|
+
* Best-effort salvaged assistant text from a terminal FAILURE. Consumption recipe: on a non-`completed`
|
|
199
|
+
* status read `result` FIRST — most failure terminals (max-turns, budget, tool errors, …) still carry
|
|
200
|
+
* the final assistant text there — and fall back to `salvagedOutput` only when `result` is empty:
|
|
201
|
+
* `const out = r.result || r.salvagedOutput || ""`. Do NOT treat `salvagedOutput` as "the output on
|
|
202
|
+
* failure": it is populated on one CLOSED list of terminals, and on all of them the assembler fills it
|
|
203
|
+
* from the SAME final-message text that `result` carries, so a consumer that reads only `salvagedOutput`
|
|
204
|
+
* on failure discards real work on every other failure path.
|
|
205
|
+
*
|
|
206
|
+
* The populating terminals, and what the text means on each:
|
|
207
|
+
* - `errorCode === "output.degenerate"` (degenerate-repetition cutoff): the model's text up to the
|
|
208
|
+
* loop, with the degenerate tail TRIMMED at the brain stream layer (byte-identical repeats dropped,
|
|
209
|
+
* one instance of the repeating unit kept — see `trimDegenerateTail`). Declared LOSSY normalization:
|
|
210
|
+
* the trim never drops unique bytes, but the repetition COUNT is not preserved — a payload whose
|
|
211
|
+
* meaning lives in how many times a unit repeats (fixed-length padding, "output N copies") comes
|
|
212
|
+
* back as one instance; this is not the raw failure snapshot.
|
|
213
|
+
* - the `limits.max_*_exceeded` family (a per-slice window was exhausted on a task that did not opt
|
|
214
|
+
* into resource-suspend): whatever text the run had produced before the loud terminal.
|
|
215
|
+
* - `"env.lifetime_expired"` / `"usage.window_exhausted"` (ruled 2026-08-04): the same, for the two
|
|
216
|
+
* EXTERNAL stop causes — an expiring environment or a closed deployment window that could not be
|
|
217
|
+
* suspended into a checkpoint. Nothing about the run itself failed, so its work is handed back on the
|
|
218
|
+
* same seat the ceilings use. (An entry-time `usage.window_exhausted` refusal ran nothing, so there
|
|
219
|
+
* is nothing to salvage and the field is absent.)
|
|
220
|
+
*
|
|
221
|
+
* Undefined on every other terminal and when there was nothing to salvage.
|
|
222
|
+
*/
|
|
223
|
+
salvagedOutput?: string;
|
|
224
|
+
/**
|
|
225
|
+
* RB-439-a — the remote-workspace lifecycle failures this run hit, as DATA. The seam declares eleven
|
|
226
|
+
* distinct `RemoteExecutionErrorCode`s, but every one of them used to reach the caller as the same
|
|
227
|
+
* thing: a `suspendVM` refusal was swallowed into the `onError` side channel and the result read
|
|
228
|
+
* `limits.max_turns_exceeded` (identical for a transient auth blip and a permanently-unsupported adapter), while
|
|
229
|
+
* a failed restore reported the single code `resume.env_failed` with the real code buried in prose. A
|
|
230
|
+
* deployment that wants to distinguish "refresh the credential and re-resume" from "this adapter can
|
|
231
|
+
* never do it" had nothing but a string to parse.
|
|
232
|
+
*
|
|
233
|
+
* - `op` — which lifecycle call failed.
|
|
234
|
+
* - `code` — the adapter's verbatim `RemoteExecutionErrorCode`.
|
|
235
|
+
* - `retryable` — whether `code` is in the retryable family (`RETRYABLE_REMOTE_ERROR_CODES`). For the
|
|
236
|
+
* IDEMPOTENT restore ops the engine has already spent its own bounded retries (see `attempts`); this
|
|
237
|
+
* flag is what a caller acts on for the ops the engine deliberately does not retry — a `suspendVM`
|
|
238
|
+
* takes a snapshot, so re-driving it is the caller's decision, not the engine's.
|
|
239
|
+
* - `attempts` — how many times the engine called it (1 = no retry was made or none was warranted).
|
|
240
|
+
* - `message` — the adapter's message, for humans.
|
|
241
|
+
*
|
|
242
|
+
* Present only when at least one lifecycle call failed (absent = nothing to report), so a consumer that
|
|
243
|
+
* ignores the field is unchanged. It is DIAGNOSTIC: {@link terminal} keeps its existing meaning —
|
|
244
|
+
* a failed suspend still ends the task the way it always did, it is just no longer anonymous.
|
|
245
|
+
*/
|
|
246
|
+
remoteEnvFailures?: RemoteEnvFailureNote[];
|
|
247
|
+
/**
|
|
248
|
+
* Present when this run's terminal failure came from the PROVIDER BOUNDARY — the transport or the
|
|
249
|
+
* provider failed the model call, as opposed to this deployment refusing to send one, a limit being
|
|
250
|
+
* reached, or the model's own output being unusable. Its PRESENCE is the assertion; the members are
|
|
251
|
+
* whatever the failing attempt actually stated about itself:
|
|
252
|
+
* - `status` — the failing HTTP status, when the provider answered with one. Absent for a
|
|
253
|
+
* connect failure, a stall, a mid-stream tear, and an in-band error frame delivered inside a
|
|
254
|
+
* 200 — never zeroed, since 0 reads as a number a consumer may format.
|
|
255
|
+
* - `requestId` — the provider's own request identifier for that attempt, when it stated one in a
|
|
256
|
+
* shape worth carrying. This is the handle a provider's support channel asks for.
|
|
257
|
+
* Both may be absent together: that is a provider failure nobody could label further, which is a
|
|
258
|
+
* different statement from "not a provider failure" (the field itself absent).
|
|
259
|
+
*
|
|
260
|
+
* Nested rather than three loose keys so the assertion and its evidence cannot drift apart — there
|
|
261
|
+
* is no state in which a consumer sees the claim beside a status belonging to some other attempt.
|
|
262
|
+
* The same family rides the assistant message itself as `isApiErrorMessage` / `apiErrorStatus` /
|
|
263
|
+
* `requestId`; this seat is its RUN-LEVEL projection, lifted from that message at the one assembly
|
|
264
|
+
* point rather than re-derived, and the two spellings are named here so the mapping is written down
|
|
265
|
+
* instead of inferred.
|
|
266
|
+
*
|
|
267
|
+
* PRESENCE, deliberately narrow: filled only when the provider failure is the terminal that NAMED
|
|
268
|
+
* this result. A run that hit a provider failure and then a higher-ranked terminal (a budget
|
|
269
|
+
* ceiling, a walltime cut) reports that other cause and leaves this absent — the same rule
|
|
270
|
+
* {@link usageWindowRetryAfterMs} states for itself, and for the same reason: a diagnostic seat
|
|
271
|
+
* beside a cause it does not belong to is worse than a missing one.
|
|
272
|
+
*/
|
|
273
|
+
apiFailure?: {
|
|
274
|
+
status?: number;
|
|
275
|
+
requestId?: string;
|
|
276
|
+
};
|
|
277
|
+
/**
|
|
278
|
+
* How long (milliseconds) until the deployment usage window that stopped this task frees up — the wait
|
|
279
|
+
* hint a scheduler needs to decide WHEN to re-submit, rather than polling.
|
|
280
|
+
*
|
|
281
|
+
* **In-presence condition:** set on exactly two terminals — `errorCode === "usage.window_exhausted"`
|
|
282
|
+
* (either moment: the entry refusal that ran nothing, or the running terminal that could not suspend)
|
|
283
|
+
* and `errorCode === "memory.admission_required"` (the prepare-throw path sets it there too — see
|
|
284
|
+
* assemble-result's accepted-code pair). Absent everywhere else, INCLUDING when a usage window did
|
|
285
|
+
* stop the run but a higher-ranked terminal
|
|
286
|
+
* named the result (a budget ceiling crossed on the same turn): the reported cause is then that other
|
|
287
|
+
* code, and a wait filed under it would describe something the code does not name.
|
|
288
|
+
*
|
|
289
|
+
* **Do not branch on its presence** to work out WHY a task stopped — read `errorCode` for that (the
|
|
290
|
+
* standing rule for every optional field on this type). A consumer treating "has `retryAfterMs`" as
|
|
291
|
+
* "was rate-limited" misreads both directions: the same code can arrive without one, and the field
|
|
292
|
+
* says nothing about which of the two governance moments produced it (`stats.turns`/`result` do).
|
|
293
|
+
*
|
|
294
|
+
* A DURABLE pause carries its own wait elsewhere: a `suspended` result's hint rides the checkpoint gate
|
|
295
|
+
* (`resumeAfterMs`), which the resume path consumes — the two are deliberately separate seats for the
|
|
296
|
+
* two different resumption stories (re-submit the task vs resume the checkpoint), never aliases.
|
|
297
|
+
*
|
|
298
|
+
* The same number is also delivered to `RunnerDeps.onError` on the typed error (unchanged) — this seat
|
|
299
|
+
* is for the caller that holds the result and never wired that sink.
|
|
300
|
+
*/
|
|
301
|
+
retryAfterMs?: number;
|
|
302
|
+
/**
|
|
303
|
+
* Present when the task was served — at least once — by a **degraded** (cheaper/different) model
|
|
304
|
+
* because the primary was rate-limited, its circuit breaker was open, or it failed with a
|
|
305
|
+
* `server_error`/`last_resort` class error (design/126 fallback chain, opt-in via
|
|
306
|
+
* `createDegradingBrain`). Lets a caller know the result was produced at reduced quality and alert/
|
|
307
|
+
* track it. Records the **first** degradation only (degradation is one-way within a task). Set even
|
|
308
|
+
* when the fallback also failed — so `status:"failed"` + `degraded` means "primary down, fallback
|
|
309
|
+
* down too". `reason="budget"` is reserved for the v2 near-budget trigger.
|
|
310
|
+
*/
|
|
311
|
+
degraded?: {
|
|
312
|
+
from: string;
|
|
313
|
+
to: string;
|
|
314
|
+
/** design/126 widens the union: `server_error` (5xx/529 escaped the retry budget) and `last_resort`
|
|
315
|
+
* (404/other non-retryable statuses) — the CC 批 β fallback-chain triggers. Additive on this
|
|
316
|
+
* OUTPUT field; consumers switching exhaustively should add the two arms. */
|
|
317
|
+
reason: "breaker_open" | "rate_limit" | "budget" | "server_error" | "last_resort";
|
|
318
|
+
/** design/126 — the fallback-chain model ids attempted after the primary, in order (absent on the
|
|
319
|
+
* single-hop legacy path's pre-126 markers and on the budget path). */
|
|
320
|
+
chain?: string[];
|
|
321
|
+
/** 1-based turn during which degradation was triggered. Reactive (rate_limit/breaker): the turn the
|
|
322
|
+
* cheaper model first served. Budget: the turn whose cumulative cost crossed the threshold (the
|
|
323
|
+
* switch takes effect from the next turn). */
|
|
324
|
+
atTurn: number;
|
|
325
|
+
};
|
|
326
|
+
/** The validated object the model submitted via `submit_output` when `TaskSpec.outputSchema` was set
|
|
327
|
+
* (1.41). Undefined if the model answered in prose instead, or the task didn't finish via the output
|
|
328
|
+
* tool. Cast it with `Static<typeof yourSchema>`. (Exhausting the output-retry cap → `failed` +
|
|
329
|
+
* `errorCode="output.invalid"`, and this stays undefined.) */
|
|
330
|
+
structuredOutput?: unknown;
|
|
331
|
+
/**
|
|
332
|
+
* design/101 §E19 + design/381 — NON-FATAL rewind disclosures for this run: the legs where the rewind
|
|
333
|
+
* machinery did something other than what the request's plain reading implies, reported as data instead
|
|
334
|
+
* of silence. Present only when at least one applies (absent = nothing to disclose), so a consumer that
|
|
335
|
+
* ignores the field is unchanged. A rewind that could NOT be delivered as asked is NOT a note — it is a
|
|
336
|
+
* terminal failure (`rewind_snapshot.unresolvable` / `rewind.store_unconfigured` /
|
|
337
|
+
* `rewind.restore_failed` / `rewind.invalid_spec` / `rewind.rewind_files_retired` /
|
|
338
|
+
* `rewind.conflicting_targets` / `rewind.child_scope_unsupported`).
|
|
339
|
+
*
|
|
340
|
+
* - `conversation_only` — {@link TaskSpec.resumeAt} branched the transcript WITHOUT
|
|
341
|
+
* {@link TaskSpec.restoreFiles}, so the working tree was deliberately left where it was (CC's
|
|
342
|
+
* "Restore conversation" mode). Legal and useful; the note is what makes the two axes' divergence
|
|
343
|
+
* visible.
|
|
344
|
+
* - `files_env_unsupported` — a file restore was requested but this deployment mounts no
|
|
345
|
+
* filesystem-capable `ExecutionEnv` (stub env), so the file axis was inert. Not an error: such a
|
|
346
|
+
* deployment has no working tree to rewind.
|
|
347
|
+
* - `restore_partial` — {@link TaskSpec.acceptPartialRestore} tolerated a partial restore: ≥1 tracked
|
|
348
|
+
* file was refused or failed while the others converged; the message carries the per-file ledger
|
|
349
|
+
* summary (applied / identical / refused / failed). Present ONLY under that explicit opt-in — the
|
|
350
|
+
* default posture is the loud terminal `rewind.restore_failed` (DV-15).
|
|
351
|
+
*
|
|
352
|
+
* (design/381 closed-set surgery: `snapshot_store_unconfigured` is RETIRED — its only producer was
|
|
353
|
+
* the per-task capture request, and capture is now always-on when a store is wired; a restore request
|
|
354
|
+
* with no store keeps the fail-loud terminal instead.)
|
|
355
|
+
*/
|
|
356
|
+
rewindNotes?: Array<{
|
|
357
|
+
code: "conversation_only" | "files_env_unsupported" | "restore_partial";
|
|
358
|
+
/** Human-readable statement of what did NOT happen and why — safe to show a user verbatim. */
|
|
359
|
+
message: string;
|
|
360
|
+
}>;
|
|
361
|
+
/**
|
|
362
|
+
* The files THIS RUN's hands actually mutated, with how many times each — the same
|
|
363
|
+
* `{path, edits}` shape the delegated-child projection publishes as
|
|
364
|
+
* {@link import("../agents/subagent-steps.js").SubagentEditedFile}, so a host can read the two
|
|
365
|
+
* seats side by side. A run is one host-side user message, which is the granularity a
|
|
366
|
+
* "restore the code this message changed" affordance needs; the per-model-turn beat
|
|
367
|
+
* (`turn_end`) deliberately has no such seat.
|
|
368
|
+
*
|
|
369
|
+
* **Source**: the hands band's own mutation lane. One entry is bumped when a `Write`/`Edit`/
|
|
370
|
+
* `NotebookEdit` call's FINAL env write returns success — not when a tool call starts, not from a
|
|
371
|
+
* tool-name table over the event stream. The consequences are the contract:
|
|
372
|
+
* - a refused, gated or FAILED write is not counted (a write whose bytes never landed is not an
|
|
373
|
+
* edit — the same lane point that retracts its first-touch history record);
|
|
374
|
+
* - a `Bash` command that changed a file is not counted: it does not go through those lanes.
|
|
375
|
+
* This matches the reference implementation's own trigger set;
|
|
376
|
+
* - one tool CALL counts once, so a batch `Edit` (multiple `edits[]` against one file in one
|
|
377
|
+
* call) is one edit here, exactly as the child projection counts it.
|
|
378
|
+
*
|
|
379
|
+
* **`path` form**: the model-supplied path argument, verbatim — the same coordinate
|
|
380
|
+
* `SubagentEditedFile.path` uses, deliberately not the canonical containment key, so both seats
|
|
381
|
+
* spell a file the way the transcript does. IDENTITY, however, is the canonical FILE, not the
|
|
382
|
+
* spelling: a relative argument resolves against the run's LIVE cwd, so one spelling can name two
|
|
383
|
+
* different files across a `cd` (two entries, each showing that spelling) and two spellings can
|
|
384
|
+
* name one file (ONE entry, showing the first spelling that reached it). Insertion order = order
|
|
385
|
+
* of first edit.
|
|
386
|
+
*
|
|
387
|
+
* **Three-valued on the failure edge, upper bound never lower**: a write failure is three-valued.
|
|
388
|
+
* A failure whose error code's contract states nothing was written is a proven no-op and is NOT
|
|
389
|
+
* counted. An AMBIGUOUS failure or a thrown write IS counted: an `ExecutionEnv` whose write is not
|
|
390
|
+
* atomic can truncate a file and then report an error, that file may really have changed, and the
|
|
391
|
+
* first-touch history record for it is kept for exactly that reason — so this list names it too,
|
|
392
|
+
* and a `rewindFilesTo` will restore it. (The reference env replaces whole files by stage+rename —
|
|
393
|
+
* fully old or fully new — so this edge belongs to its in-place fallback arms and to third-party
|
|
394
|
+
* envs.) A host therefore reads this list as "files that may differ from before this run", never
|
|
395
|
+
* as "writes the model saw succeed"; the tool answers carry that.
|
|
396
|
+
*
|
|
397
|
+
* **In-presence condition**: present iff this run's hands landed ≥1 such write — zero edits means
|
|
398
|
+
* the key is ABSENT, never an empty array. It does NOT depend on whether a
|
|
399
|
+
* `RunnerDeps.fileHistoryStore` is wired: this is an observation of what this run did, not a
|
|
400
|
+
* durable history, and a deployment with no history store still gets it (what a store adds is the
|
|
401
|
+
* ability to REWIND, not the ability to say what changed). It covers this run's OWN band only — a
|
|
402
|
+
* delegated child's writes ride the child's own result and the delegation projection, not this
|
|
403
|
+
* key.
|
|
404
|
+
*
|
|
405
|
+
* **Bound**: at most 1000 distinct files. Past that, already-listed files keep counting and new
|
|
406
|
+
* ones are not added — a result seat cannot be unbounded, and a single user message reaching a
|
|
407
|
+
* thousand distinct files is already outside what this observation is for. A consumer that must
|
|
408
|
+
* know whether it is reading a saturated list can compare the length against that ceiling.
|
|
409
|
+
*/
|
|
410
|
+
editedFiles?: import("../agents/subagent-steps.js").SubagentEditedFile[];
|
|
411
|
+
/**
|
|
412
|
+
* Present (`true`) exactly when the run's FINAL turn was halted by a person's BARE rejection of a
|
|
413
|
+
* tool call — the parent-thread control-flow boundary: the rejected call's same-message siblings
|
|
414
|
+
* that had not started were settled un-executed (each an error result coded `gate.batch_halted`),
|
|
415
|
+
* and the engine deliberately did NOT re-invoke the model, so the run ends awaiting the user's
|
|
416
|
+
* direction. On this path `status` is still `"completed"` (nothing failed, nothing is suspended,
|
|
417
|
+
* the session is continuable as ever) — this field is what tells such an end apart from a natural
|
|
418
|
+
* finish: `result` text is whatever the model had produced BEFORE the rejection (often empty), and
|
|
419
|
+
* a consumer surface should read the state as "stopped by the user, awaiting their direction",
|
|
420
|
+
* never as "the task finished its work". Absent everywhere else — including when queued user input
|
|
421
|
+
* (a steer/follow-up) continued the run past the rejection and it later ended naturally.
|
|
422
|
+
*/
|
|
423
|
+
haltedOnUserRejection?: true;
|
|
424
|
+
/**
|
|
425
|
+
* design/373 (#504) — present (`true`) exactly when a {@link TaskStream.halt} was ACCEPTED while
|
|
426
|
+
* this run was live: the person issued the bare user interrupt (the CC Esc form — cut the
|
|
427
|
+
* in-flight turn, stop at that boundary, wait for their next input). The ruled terminal form is
|
|
428
|
+
* completed-with-marker, this seat being the marker: `status` is `"completed"` on the ordinary
|
|
429
|
+
* path (a clean, resumable ending — nothing failed, nothing is suspended, the session continues
|
|
430
|
+
* via the ordinary next-run front door), and this field is what tells a person-stopped ending
|
|
431
|
+
* apart from a natural finish — same reading discipline as {@link haltedOnUserRejection}, its
|
|
432
|
+
* sibling seat ("stopped by the user, awaiting their direction", never "the task finished its
|
|
433
|
+
* work"). `result` is whatever the model had produced before the halt (possibly empty — a halt
|
|
434
|
+
* can land before any output; the run then completes empty rather than failing). Pass-through on
|
|
435
|
+
* EVERY terminal the run still reaches (a halt ACCEPTED first that then raced a real
|
|
436
|
+
* failure/limit truthfully says a person also stopped it), and — the stated honest window — on
|
|
437
|
+
* a natural completion the halt arrived too late to prevent (past the loop's final commit
|
|
438
|
+
* point): the fact reported is the ACCEPTED HALT, and the transcript says how far the model
|
|
439
|
+
* got. First-writer-wins against the run's abort (the `interrupt()` attribution law, the same
|
|
440
|
+
* gate): a halt landing AFTER the abort signal already fired neither cut nor stopped anything —
|
|
441
|
+
* that ending belongs to the abort, and this seat stays ABSENT rather than signing someone
|
|
442
|
+
* else's stop with the halt caller's name. Absent everywhere else; never `false`.
|
|
443
|
+
*
|
|
444
|
+
* design/384 slice 2 (TRANSITIONAL narrowing): a `"suspended"` / `"needs_review"` terminal does
|
|
445
|
+
* NOT carry this seat even when a halt was accepted — those statuses mean a durable park WON its
|
|
446
|
+
* race with the halt (the row is committed and redeemable; the run is waiting to continue), and
|
|
447
|
+
* "stopped by the person" beside "waiting to resume" was a self-contradictory pair. The halt's
|
|
448
|
+
* own receipt (`{turnCut}`) and the `task.turn_interrupted` notice still stand — a seat WAS cut.
|
|
449
|
+
* Transitional: once halt-boundary source accounting lands (slice 3), the boundary-CONSUMED
|
|
450
|
+
* suspension arms flip to signing (a probe pinning today's suppressed shape goes red then, by
|
|
451
|
+
* design). The pass-through law is untouched for every OTHER terminal: a halt racing a real
|
|
452
|
+
* failure/limit — including one that outranks a committed park — still signs.
|
|
453
|
+
*/
|
|
454
|
+
haltedByUser?: true;
|
|
455
|
+
/**
|
|
456
|
+
* The deliveries of AskUserQuestion calls a person ANSWERED but whose call never executed to collect
|
|
457
|
+
* the answer (the leg ended first — abort, batch teardown, or a loop failure). Rides EVERY terminal
|
|
458
|
+
* when non-empty, because the alternative is an answer a human produced vanishing with nothing to
|
|
459
|
+
* show for it: the answers were NOT delivered to the model and are gone with this leg — re-ask if
|
|
460
|
+
* the decision is still needed. `RunnerDeps.onError` (classification `"unconsumed-human-answer"`)
|
|
461
|
+
* carries the same fact as an additional alert; this field is the mandatory face.
|
|
462
|
+
*/
|
|
463
|
+
strandedHumanAnswers?: ReadonlyArray<{
|
|
464
|
+
/** The engine-minted per-delivery identity (AskQuestionRequest.deliveryId) — the KEY: call ids can
|
|
465
|
+
* repeat across deliveries, so two lost answers on one call id are still two records here. */
|
|
466
|
+
deliveryId: string;
|
|
467
|
+
toolCallId: string;
|
|
468
|
+
}>;
|
|
469
|
+
/**
|
|
470
|
+
* #481 — HOST-SUPPLIED: the approval plane's report that this leg ended with human approvals still
|
|
471
|
+
* OUTSTANDING (the ask identities it is holding, and when the OLDEST of them was created).
|
|
472
|
+
*
|
|
473
|
+
* **Who writes it, and why not the engine.** Core mints no ask identity — an ask id belongs to the
|
|
474
|
+
* deployment's approval store, and `resolveAsk` only awaits the host's `onAsk` — and core starts no
|
|
475
|
+
* approval clock. So this seat is filled by the `Runner` a deployment hands to
|
|
476
|
+
* `runWorkflow`/`startWorkflow`: that call both RAN the leg and owns the pending-approval map, which
|
|
477
|
+
* is the only place the two facts meet without inventing an attribution. Core's own Runner never
|
|
478
|
+
* writes it. ABSENT is therefore the ordinary world — an older deployment, or one whose approvals are
|
|
479
|
+
* always answered inline — and every such leg behaves exactly as it did before this seat existed.
|
|
480
|
+
*
|
|
481
|
+
* **Three-state, deliberately.** Absent = "this deployment is not reporting outstanding approvals, or
|
|
482
|
+
* there are none"; present = "these asks are outstanding". An EMPTY `askIds` is neither and is
|
|
483
|
+
* refused: it would let a half-written record read as "waiting on nothing", which is precisely the
|
|
484
|
+
* misdiagnosis the seat exists to prevent. A present-but-malformed value is announced and then
|
|
485
|
+
* treated as no report — never silently folded into absence, and never granted the exemption below.
|
|
486
|
+
*
|
|
487
|
+
* **What core does with it** (`src/orchestration/workflow.ts`): a workflow agent leg whose progress
|
|
488
|
+
* watchdog fired while an approval was outstanding was NOT stalled — it was waiting on a person, by
|
|
489
|
+
* design — so that attempt is not charged against the stall-retry budget. The run's own
|
|
490
|
+
* `totalTimeoutMs` remains the backstop for a wait nobody ever answers.
|
|
491
|
+
*/
|
|
492
|
+
pendingApproval?: {
|
|
493
|
+
askIds: readonly string[];
|
|
494
|
+
oldestCreatedAtMs: number;
|
|
495
|
+
};
|
|
496
|
+
/**
|
|
497
|
+
* #485 — HOST-SUPPLIED: how long this leg spent BLOCKED on human approvals, in ms, as the deployment's
|
|
498
|
+
* approval plane measured it. Same writer and same absence contract as {@link pendingApproval}: the
|
|
499
|
+
* approval clock belongs to whoever holds the ask, so the figure is SINGLE-SOURCED there and core neither
|
|
500
|
+
* estimates it nor accumulates it (two sides publishing two numbers for one wait is the drift this seat
|
|
501
|
+
* exists to avoid). ABSENT is the ordinary world.
|
|
502
|
+
*
|
|
503
|
+
* Already CUMULATIVE for the leg — a receipt states the leg's total wait so far, not that attempt's
|
|
504
|
+
* increment — so a later receipt SUPERSEDES an earlier one. Adding successive receipts double-counts.
|
|
505
|
+
*
|
|
506
|
+
* **Absence is not zero.** `0` is "nobody was waited on"; absent is "this deployment does not supply the
|
|
507
|
+
* fact". A consumer that defaults absence to `0` reports a deployment's silence as a measurement.
|
|
508
|
+
*
|
|
509
|
+
* What it is FOR: a workflow that hit its total timeout cannot otherwise say whether it was SLOW or
|
|
510
|
+
* WAITING. `WorkflowRun.timeoutInterruption.approvalWaitedMs` carries the largest leg's figure onto the
|
|
511
|
+
* terminal disclosure for exactly that reading — near `timeoutMs` ⇒ the window went on a person, not on
|
|
512
|
+
* work.
|
|
513
|
+
*/
|
|
514
|
+
approvalWaitedMs?: number;
|
|
515
|
+
/**
|
|
516
|
+
* #240 (design/199 v1.1) — the READ-face this leg's read surfaces actually judged under, as an
|
|
517
|
+
* engine-filled OBSERVATION (never a knob: writing it on a spec does nothing). It is the run's ONE
|
|
518
|
+
* resolved face — the same value the hands toolkit enforced and the delegation carriers rode — with
|
|
519
|
+
* the hands-less resume pin folded in (a row resumed on a hands-less worker still reports the
|
|
520
|
+
* checkpoint-frozen `"roots"`).
|
|
521
|
+
*
|
|
522
|
+
* **In-presence condition (#242 widened):** present on every leg that completes prepare — hands
|
|
523
|
+
* mounted (any run with the built-in fs band) AND every hands-less leg (resume legs fold the
|
|
524
|
+
* checkpoint seed stricter-wins; non-resume legs report the live resolution). Absent only on
|
|
525
|
+
* prepare-failure terminals (nothing ran). Consumers that keyed on absence = "no posture" must
|
|
526
|
+
* re-key: absence now means only "prepare never completed". Rides every OTHER terminal, not just
|
|
527
|
+
* `completed` — the posture is a fact about the leg that ran, whatever ended it.
|
|
528
|
+
*
|
|
529
|
+
* Purpose: a POST-COMPLETION follow-on leg spawned OUTSIDE the delegation tree (the verify gate's
|
|
530
|
+
* verifier and fix legs are the canonical case) cannot see the checkpoint fold the completed leg ran
|
|
531
|
+
* under — its spawner holds only the resume-side config, which never contained the frozen posture.
|
|
532
|
+
* Folding this seat into the follow-on spec (stricter-wins: only `"roots"` ever tightens; an
|
|
533
|
+
* `"open"` observation is never forwarded) closes that escape for ANY follow-on, not just the
|
|
534
|
+
* verifier. See {@link effectiveReadDenyPatterns} for the deny-set half.
|
|
535
|
+
*/
|
|
536
|
+
effectiveReadFace?: import("../tools/fs/read-face.js").ReadFace;
|
|
537
|
+
/**
|
|
538
|
+
* #240 (design/199 v1.1) — the sensitive-path deny ADDITIONS in force on this leg beyond the
|
|
539
|
+
* built-in table, normalized (deployment ∪ task ∪ checkpoint-frozen seed), as an engine-filled
|
|
540
|
+
* OBSERVATION. "In force" = judged by this leg's own read surfaces where they mounted, and carried
|
|
541
|
+
* to its delegation subtree either way (a hands-less resume leg reports the entries its children
|
|
542
|
+
* judge under).
|
|
543
|
+
* This seat is the only place a checkpoint-FROZEN deny entry becomes visible after the resumed leg
|
|
544
|
+
* completes: the resume-side caller's own config never contained it. A follow-on leg's spawner
|
|
545
|
+
* unions these into the child's `readDenyPatterns` (add-only; the child's compile folds exact
|
|
546
|
+
* duplicates, so re-supplying deployment-shared entries is idempotent) — that is how the frozen
|
|
547
|
+
* posture rides every leg that continues this work, not just the delegation subtree.
|
|
548
|
+
*
|
|
549
|
+
* **In-presence condition:** present iff non-empty. Built-in entries are never listed (they are in
|
|
550
|
+
* force on every run and re-compile locally). Same terminal coverage as
|
|
551
|
+
* {@link effectiveReadFace}: rides any terminal of a leg that ran; absent on prepare failures.
|
|
552
|
+
*
|
|
553
|
+
* On a DURABLE-PAUSE hand-back these seats are additionally the RE-SUPPLY hint: a checkpoint
|
|
554
|
+
* minted by a leg with no face resolution (a hands-less non-resume continuation) carries no
|
|
555
|
+
* frozen face section of its own, so a caller resuming that token must fold these entries back
|
|
556
|
+
* into the resume-side config (`readDenyPatterns` / `readFace: "roots"`). The resumed leg unions
|
|
557
|
+
* them into its own judgment and delegation carriers; the union is NOT serialized into any
|
|
558
|
+
* further checkpoint that leg may mint (a hands-less RESUME mint re-freezes only its original seed; a non-resume hands-less pause mints its live-resolved posture, #242), so
|
|
559
|
+
* fold them on EVERY resume — the standing resume contract. The seats describe the posture
|
|
560
|
+
* governing the WORK, never the contents of any checkpoint row.
|
|
561
|
+
*/
|
|
562
|
+
effectiveReadDenyPatterns?: readonly import("../tools/fs/read-deny.js").NormalizedReadDenyEntry[];
|
|
563
|
+
/**
|
|
564
|
+
* design/178 v2 §2.3 (件①) — the memory VISIBILITY face this leg actually ran under, as an
|
|
565
|
+
* engine-filled OBSERVATION (never a knob: writing it on a spec does nothing). This is the
|
|
566
|
+
* EFFECTIVE face, not the requested one: a refused request never reaches a terminal at all (the
|
|
567
|
+
* whole prepare fails with its governance code), so what this seat answers is "what was actually
|
|
568
|
+
* given" — the delta against the request is directly readable (an org writeScope not explicitly
|
|
569
|
+
* granted collapses to `null` here as it did in the run; a fail-open mount failure reads
|
|
570
|
+
* `memoryless`, never a dressed-up mount).
|
|
571
|
+
*
|
|
572
|
+
* **In-presence condition** (same law as {@link effectiveReadFace}): present on every terminal of
|
|
573
|
+
* a leg that COMPLETED prepare — the memory-less states are answered as their own values
|
|
574
|
+
* (`none` / `memoryless`), so consumers must never read ABSENCE as "no memory"; absence means
|
|
575
|
+
* only "prepare never completed". Delegated children mint their own on their own legs (their
|
|
576
|
+
* request plane can only narrow the parent's frozen org verdict); a resume leg's value is the
|
|
577
|
+
* re-adjudication at resume time (admission runs in every prepare — the current-policy reading,
|
|
578
|
+
* same axis as the read-face seats).
|
|
579
|
+
*
|
|
580
|
+
* Minted AFTER the materialize outcome, not after the admission verdict — the fail-open mount
|
|
581
|
+
* arm sits between the two, and stamping earlier would report a mount that never happened.
|
|
582
|
+
* See {@link EffectiveMemoryScopes} for the per-state field law.
|
|
583
|
+
*/
|
|
584
|
+
effectiveMemoryScopes?: EffectiveMemoryScopes;
|
|
585
|
+
/**
|
|
586
|
+
* `turns`/`tokens`/`costMicroUsd` are this task's OWN model usage. `nested` is the summed usage of any
|
|
587
|
+
* delegated sub-runs (sub-agents) it spawned — present only when it delegated. The true total
|
|
588
|
+
* cost is `tokens + (nested?.tokens ?? 0)`; a large `nested` is the multi-agent "~15×" made visible.
|
|
589
|
+
*/
|
|
590
|
+
stats: {
|
|
591
|
+
turns: number;
|
|
592
|
+
tokens: number;
|
|
593
|
+
/** design/97 CORE-8 (②): total TOOL CALLS executed (one per `tool_start`) — distinct from `turns` (model
|
|
594
|
+
* rounds; a turn may issue several parallel tool calls). For a CC-style "N tool calls" per-agent row.
|
|
595
|
+
* **v1 fidelity (audit MINOR)**: counted in THIS run leg via the harness dispatch path, so on a DURABLE
|
|
596
|
+
* RESUME it under-reports — the resumed gated call (executed by the resume engine, not the harness) and any
|
|
597
|
+
* cross-slice resource-suspend legs are not folded (turns/tokens/cost ARE folded; toolCalls is per-leg, like
|
|
598
|
+
* the compaction subtotal). OBSERVE-ONLY (never gates budget). The workflow display is unaffected (its agents
|
|
599
|
+
* fail-on-suspend, never durably resume). A `spentToolCalls` ledger fold is a follow-on. */
|
|
600
|
+
toolCalls?: number;
|
|
601
|
+
/** Prompt (input) tokens that MISSED the prefix cache, summed across the task's turns and normalized
|
|
602
|
+
* across providers (Anthropic already reports `input_tokens` this way; OpenAI/vLLM include the cache
|
|
603
|
+
* in theirs, so the subsets are removed). Mutually exclusive with `cachedTokens`/`cacheWriteTokens*`;
|
|
604
|
+
* the three of them sum to `totalInputTokens`.
|
|
605
|
+
*
|
|
606
|
+
* **RB-457-a (BREAKING at 3.0.0)**: this used to carry the cache-INCLUSIVE total, which collides with
|
|
607
|
+
* the Anthropic protocol's identically-named quantity — a consumer computing
|
|
608
|
+
* `cachedTokens / (promptTokens + cachedTokens)` double-counted the cache subset and reported
|
|
609
|
+
* `h/(1+h)` (a 98% hit rate surfaced as 49.5%). Use `totalInputTokens` for the old value; use
|
|
610
|
+
* `cacheHitRate` rather than recomputing a ratio. */
|
|
611
|
+
promptTokens?: number;
|
|
612
|
+
/** Prompt tokens served from the model's prefix cache (a cache HIT), summed over the task's
|
|
613
|
+
* turns — when the gateway reports it (vLLM/OpenAI `prompt_tokens_details.cached_tokens`,
|
|
614
|
+
* DeepSeek `prompt_cache_hit_tokens`, Anthropic `cache_read_input_tokens`). */
|
|
615
|
+
cachedTokens?: number;
|
|
616
|
+
/** Prompt tokens written to the cache (Anthropic `cache_creation_input_tokens`); 0 elsewhere. */
|
|
617
|
+
cacheWriteTokens?: number;
|
|
618
|
+
/** `cachedTokens / totalInputTokens` ∈ [0,1] — the prefix-cache hit rate for this task. Cost-critical;
|
|
619
|
+
* a low value on a multi-turn task means the cacheable prefix is unstable (see `design/09`).
|
|
620
|
+
* Track it per task and trend it per session. Undefined when the gateway reports no usage.
|
|
621
|
+
* **Prefer this over recomputing a ratio** — it is the one place the denominator is defined. */
|
|
622
|
+
cacheHitRate?: number;
|
|
623
|
+
/** The cache-INCLUSIVE prompt total across the task's turns — `promptTokens + cachedTokens +
|
|
624
|
+
* cacheWriteTokens + cacheWriteTokensLong`, matching OTel `gen_ai.usage.input_tokens`. This is the
|
|
625
|
+
* quantity cost is computed from, the denominator of `cacheHitRate`, and the figure to use for
|
|
626
|
+
* "how much context did this task present" (window/budget views). RB-457-a: `promptTokens` carried
|
|
627
|
+
* this value up to 2.13.x; it now carries the cache MISS count and this field is its own
|
|
628
|
+
* accumulator rather than an alias. Optional BY DESIGN, like every usage field on this stats
|
|
629
|
+
* face: undefined means the gateway reported no usage (and pre-3.0.0 persisted rows never had
|
|
630
|
+
* it) — the event-face `turn_end.usage.totalInputTokens` is required because there the whole
|
|
631
|
+
* `usage` object is already conditional (intentional asymmetry, not a gap). */
|
|
632
|
+
totalInputTokens?: number;
|
|
633
|
+
/** Completion (output) tokens summed over the task's turns. */
|
|
634
|
+
outputTokens?: number;
|
|
635
|
+
/** Cache-write tokens at the 1-hour TTL (Anthropic); 0 unless 1h caching is in use. */
|
|
636
|
+
cacheWriteTokensLong?: number;
|
|
637
|
+
/** Authoritative (and only) cost figure: **integer micro-USD** (1e-6 USD), computed in core from
|
|
638
|
+
* injected pricing — integer to avoid float-accumulation error in billing. (design/157 B19: the
|
|
639
|
+
* float-USD twin was removed; divide by 1e6 at the display edge if you need USD.)
|
|
640
|
+
* **ABSENT when any spend was unpriced** (RB-368): the serving model had neither a
|
|
641
|
+
* `RunnerDeps.pricing` entry nor a `Model.cost` declaration — an unpriced run reports NO cost
|
|
642
|
+
* rather than a fabricated 0, so "no price table" stays distinguishable from "declared free"
|
|
643
|
+
* (an explicit all-zero `Model.cost` still reports 0). `costBreakdown` is omitted with it. */
|
|
644
|
+
costMicroUsd?: number;
|
|
645
|
+
nested?: NestedUsage;
|
|
646
|
+
/**
|
|
647
|
+
* Post-task memory-consolidation usage (design/41), when consolidation ran. Its own line item —
|
|
648
|
+
* **kept out of `tokens`/`costMicroUsd` and excluded from the budget gate** — so a completed task is
|
|
649
|
+
* never retroactively flipped to a budget terminal by reconcile cost. `tokens` is 0 when every note
|
|
650
|
+
* took a cheap (no-LLM) path; `applied` counts store mutations the pass made (cheap-path
|
|
651
|
+
* UPDATE/DELETE + applied LLM decisions) so callers can observe reconcile efficacy without re-reading
|
|
652
|
+
* the store. Absent when consolidation was disabled / no-op / produced no notes. Populated
|
|
653
|
+
* asynchronously by whatever engine-plane pass produced it (consolidation is fire-and-forget).
|
|
654
|
+
*/
|
|
655
|
+
memory?: {
|
|
656
|
+
tokens: number;
|
|
657
|
+
costMicroUsd: number;
|
|
658
|
+
applied: number;
|
|
659
|
+
};
|
|
660
|
+
/**
|
|
661
|
+
* design/100 §E12 — the prompt-suggestion pass's usage, when it ran. Same budget-excluded side-observable
|
|
662
|
+
* treatment as {@link memory}: **kept out of `tokens`/`costMicroUsd` and excluded from the budget gate**, so
|
|
663
|
+
* the (fire-and-forget, post-completion) suggestion pass can never retroactively flip a done task to
|
|
664
|
+
* a budget terminal. Absent when `suggestNextPrompts` was off / produced nothing. Populated asynchronously —
|
|
665
|
+
* see {@link TaskStream.suggestions}.
|
|
666
|
+
*/
|
|
667
|
+
suggestions?: {
|
|
668
|
+
tokens: number;
|
|
669
|
+
costMicroUsd: number;
|
|
670
|
+
};
|
|
671
|
+
/**
|
|
672
|
+
* TB telemetry B2: counters for the engine's wall-clock/deadline mechanisms, so a
|
|
673
|
+
* bench/monitoring consumer can judge from the RESULT whether they actually engaged (refute-130
|
|
674
|
+
* lesson: "did P1/P2 fire" was unanswerable from artifacts). Present only when at least one
|
|
675
|
+
* mechanism engaged this leg; absent = none did (byte-compatible).
|
|
676
|
+
*/
|
|
677
|
+
mechanisms?: {
|
|
678
|
+
/** design/132: the natural-end final-verification reminder was injected this run. */
|
|
679
|
+
finalVerifyInjected?: true;
|
|
680
|
+
/** oracle-grounding gate (opus LOW 复审, additive): HOW MANY final-verification injections
|
|
681
|
+
* fired this run — 1 = the R9 one-shot only, 2 = R9 + the grounding re-entry (hard cap).
|
|
682
|
+
* Present iff `finalVerifyInjected` is (the boolean stays for compatibility). */
|
|
683
|
+
finalVerifyInjections?: number;
|
|
684
|
+
/** design/133: number of attachment blocks injected this run — turn-boundary bundles
|
|
685
|
+
* (todo/task reminders, changed-files notes, plan-mode re-reminders, listing drift frames;
|
|
686
|
+
* each bundled block counts once) PLUS the [c209-C] first-frame listing deliveries riding the
|
|
687
|
+
* first user message (agent_listing / skills_listing — counted only when the first prompt was
|
|
688
|
+
* actually sent, R2 C5: a hook-blocked / budget-rejected run counts zero). */
|
|
689
|
+
attachmentsInjected?: number;
|
|
690
|
+
/** design/164: how many LIMIT-APPROACH notices were injected this run (0..2 — the
|
|
691
|
+
* "start converging" frame and the "deliver now" frame, each one-shot). Absent when none fired,
|
|
692
|
+
* which is also the answer for a task that armed no limit axis at all. */
|
|
693
|
+
approachNoticesSent?: number;
|
|
694
|
+
/** Repetition telemetry (2026-07-10 observability): brain-side degenerate-repetition
|
|
695
|
+
* stream CUTS this run (each pairs with an `output.degenerate`-classified turn). */
|
|
696
|
+
repetitionCuts?: number;
|
|
697
|
+
/** Repetitions that landed in a detection window but were SPARED by a 2e1c161 structural
|
|
698
|
+
* allowance (code-line shape / divider run) — the raw material for judging the allowance. */
|
|
699
|
+
repetitionSpared?: number;
|
|
700
|
+
/** Per-event details (turn + rule + period/reps + a ≤120-char segment sample), capped at 20
|
|
701
|
+
* entries — `repetitionCuts`/`repetitionSpared` keep the true totals past the cap. */
|
|
702
|
+
repetitionEvents?: Array<{
|
|
703
|
+
turn: number;
|
|
704
|
+
action: "cut" | "spared";
|
|
705
|
+
rule: "char-run" | "unit-loop";
|
|
706
|
+
period: number;
|
|
707
|
+
reps: number;
|
|
708
|
+
segment: string;
|
|
709
|
+
}>;
|
|
710
|
+
/** design/319 (B ticket, G9② observation seat) — reminder-disclosure trigger counts for the
|
|
711
|
+
* leg, keyed `<outlet>.<form>`: outlets `read` / `notebook` / `pdf` / `mcp` / `webFetch` /
|
|
712
|
+
* `webSearch`; forms
|
|
713
|
+
* · `bare` (bare-form trailer appended — reminder-shaped text without this session's mark),
|
|
714
|
+
* · `marked` (marked-form trailer — never throttled),
|
|
715
|
+
* · `bare_throttled` (a bare trailer suppressed by the 60s per-key window),
|
|
716
|
+
* · `defused` (an MCP/web segment's exact-mark bytes were rewritten — the lane's one sanctioned
|
|
717
|
+
* byte change, always paired with a `marked` disclosure),
|
|
718
|
+
* · `envelope` (text shaped like one of the engine's OTHER authority envelopes — the DISCLOSED
|
|
719
|
+
* subset is `task-notification` / `new-diagnostics` / `user_memory` / `skills` /
|
|
720
|
+
* `total_tokens` / `instruction-files`; `scope` is
|
|
721
|
+
* fenced but not disclosed, since `<scope>…</scope>` is also an ordinary build-file element.
|
|
722
|
+
* That family carries no mark, so its sentence is positional rather than byte-testable. It
|
|
723
|
+
* rides ON the reminder copy when both families hit, so `envelope` can be bumped alongside
|
|
724
|
+
* `marked`/`bare` on one result),
|
|
725
|
+
* · `envelope_throttled` (an envelope sentence suppressed by its OWN 60s window — the two
|
|
726
|
+
* families throttle independently, so one may emit while the other reports suppression),
|
|
727
|
+
* · `mark_echo` (observation ONLY, never a disclosure: the session's mark VALUE appeared in
|
|
728
|
+
* external bytes with no reminder-shaped tag around it, so the tag-grammar detector cannot
|
|
729
|
+
* reach it. Nothing is rewritten and no trailer is appended; it is an upper bound on model
|
|
730
|
+
* exposure for the lanes that do not defuse).
|
|
731
|
+
* Present only when ≥1 key is non-zero. The map is OPEN by type — fold by key rather than
|
|
732
|
+
* switching on a closed set. This is the D-4/D-6 re-ruling data (defuse/trailer widening to
|
|
733
|
+
* Read/Bash/Grep): a reading, never a gate. */
|
|
734
|
+
reminderDisclosures?: Record<string, number>;
|
|
735
|
+
};
|
|
736
|
+
/**
|
|
737
|
+
* design/91 — **human-review burden** (design/89 §2.4 C2 axis). The wall-clock time a task spent waiting
|
|
738
|
+
* on a human at an approval gate, plus the count, bucketed by `gate.kind`. Both human-review paths are
|
|
739
|
+
* captured: the **synchronous** `onAsk`/`resolveAsk` await (`t_return − t_call`) and the **durable**
|
|
740
|
+
* suspend→resume wait (`resumedAt − checkpoint.suspendedAt`, including the human's offline time); a
|
|
741
|
+
* multi-leg suspend/resume chain **accumulates across legs**. All timestamps come from {@link RunnerDeps.now}.
|
|
742
|
+
*
|
|
743
|
+
* **NOT an LLM cost (load-bearing, design/91 §1):** human-review time is wall-clock latency, not token
|
|
744
|
+
* spend — it is **NEVER folded into `costMicroUsd`, `costBreakdown`, or the budget gate** (the same
|
|
745
|
+
* budget-excluded side-observable treatment as {@link memory}). Adding human seconds to token µUSD is the
|
|
746
|
+
* design/89 §2.1 "apples + oranges" trap; the supervisor Pareto frontier keeps `(token cost, human seconds,
|
|
747
|
+
* correctness)` as three SEPARATE axes. Absent (`undefined`) when the task hit no approval gate OR no clock
|
|
748
|
+
* is injectable to measure — byte-compatible with a task that never paused for a human.
|
|
749
|
+
*/
|
|
750
|
+
humanReview?: {
|
|
751
|
+
/** Number of human decisions awaited (each synchronous `resolveAsk` return + each durable resume of an
|
|
752
|
+
* approval suspend). Equals `gates.length`. */
|
|
753
|
+
count: number;
|
|
754
|
+
/** Cumulative wall-clock the approval gates were alive (ms), summed across all legs. Equals `Σ gates[].waitMs`. */
|
|
755
|
+
totalWaitMs: number;
|
|
756
|
+
/** One entry per human decision, in order. `kind` = the gate's discriminant (`human` /
|
|
757
|
+
* `irreversible_ask` for durable; `human` for a synchronous ask). `decision` = the adjudication when
|
|
758
|
+
* known (`allow`/`deny`). design/99 MF-24: `toolName` = the gated tool; `toolArg` = a
|
|
759
|
+
* SHORT, SECRET-SCRUBBED one-line summary of its input (the same `primaryActivityArg` boundary as the MF-W
|
|
760
|
+
* activity arg) — the permission-denial ledger's "denied: Bash(rm …)" display. `toolArg` is set on the
|
|
761
|
+
* SYNCHRONOUS-ask path (the input is in scope); a durable-resume gate carries `toolName` only (its input is
|
|
762
|
+
* not threaded onto the persisted gate — a documented follow-on). */
|
|
763
|
+
gates: Array<{
|
|
764
|
+
kind: string;
|
|
765
|
+
waitMs: number;
|
|
766
|
+
decision?: string;
|
|
767
|
+
toolName?: string;
|
|
768
|
+
toolArg?: string;
|
|
769
|
+
}>;
|
|
770
|
+
};
|
|
771
|
+
/**
|
|
772
|
+
* design/80 D-E-core: a thin FINANCE TAXONOMY of the LLM-derived costs the engine actually prices,
|
|
773
|
+
* decomposing the task's spend into report categories (derived from existing cost sources — no new
|
|
774
|
+
* persisted structure). **CORE = LLM-token-derived ONLY** — the deployment SERVICE adds the infra axes it
|
|
775
|
+
* owns (tool-call / sandbox-walltime / egress; it has the k8s cost data) and composes them with this for
|
|
776
|
+
* the supervisor surface. Each category is ≥ 0 and the LLM parts reconcile to the independently-summed
|
|
777
|
+
* LLM total: `llmRootMicroUsd + compactionMicroUsd === costMicroUsd` and the fully-reconciled
|
|
778
|
+
* spend is `costMicroUsd + nested.costMicroUsd` — `costMicroUsd` deliberately EXCLUDES nested
|
|
779
|
+
* subagent cost (it lands in `nested`, see assemble-result.ts), so `nestedSubagentMicroUsd` sits
|
|
780
|
+
* OUTSIDE the costMicroUsd identity, not inside it (measured correction: the previous wording
|
|
781
|
+
* `llmRoot + nested + compaction === costMicroUsd` only held when nested was 0).
|
|
782
|
+
* (`memoryConsolidationMicroUsd` is a separate, budget-excluded line, permanently 0 since the
|
|
783
|
+
* runner-integrated consolidation pass was retired — design/157 B19 — and NOT part of any identity).
|
|
784
|
+
* Present whenever stats are assembled, EXCEPT when `costMicroUsd` is absent for unpriced spend
|
|
785
|
+
* (RB-368) — the taxonomy would partition a fabricated total, so it is omitted together with it.
|
|
786
|
+
*/
|
|
787
|
+
costBreakdown?: {
|
|
788
|
+
/** Root-agent LLM cost (micro-USD) = `costMicroUsd − compactionMicroUsd` (the sub-category folded into
|
|
789
|
+
* it). NOT minus nested — nested cost is never folded into `costMicroUsd` (it lands in `nested`).
|
|
790
|
+
* ⚠️ APPROXIMATE on a multi-slice `resource_limit` RESUME (design/80 D-E-core): the
|
|
791
|
+
* durable resource ledger persists only the AGGREGATE prior spend, not its compaction subtotal, so
|
|
792
|
+
* prior slices' compaction folds in HERE rather than into `compactionMicroUsd`. The TOTAL (`costMicroUsd`)
|
|
793
|
+
* stays exact; only this llmRoot-vs-compaction split is per-leg-approximate across resource slices. */
|
|
794
|
+
llmRootMicroUsd: number;
|
|
795
|
+
/** Delegated sub-agent (nested) LLM cost (micro-USD) = `nested?.costMicroUsd ?? 0`. */
|
|
796
|
+
nestedSubagentMicroUsd: number;
|
|
797
|
+
/** Post-task memory-consolidation LLM cost (micro-USD). Permanently 0 since design/157 B19 retired
|
|
798
|
+
* the runner-integrated consolidation pass (kept: public stats contract; an engine-plane re-mount —
|
|
799
|
+
* design/138 §7 — would fill it). A separate, budget-excluded line. */
|
|
800
|
+
memoryConsolidationMicroUsd: number;
|
|
801
|
+
/** design/100 §E12 — prompt-suggestion pass LLM cost (micro-USD) = `suggestions?.costMicroUsd ?? 0`; filled
|
|
802
|
+
* asynchronously after the (post-completion) pass (0 until then). A separate, budget-excluded line. */
|
|
803
|
+
suggestionsMicroUsd?: number;
|
|
804
|
+
/** Within-task compaction LLM cost (micro-USD) — part of `costMicroUsd`, so subtracted from root.
|
|
805
|
+
* ⚠️ See {@link llmRootMicroUsd}: under-reports prior slices' compaction on a multi-slice resource resume. */
|
|
806
|
+
compactionMicroUsd: number;
|
|
807
|
+
};
|
|
808
|
+
};
|
|
809
|
+
}
|