@sema-agent/core 5.61.0 → 5.63.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +107 -0
- package/dist/agents/subagent.d.ts +12 -2
- package/dist/agents/subagent.js +3 -2
- package/dist/brain/open-responses.js +8 -3
- package/dist/brain/openai.js +4 -4
- package/dist/brain/stream-engine.d.ts +13 -2
- package/dist/brain/stream-engine.js +3 -3
- package/dist/core/auto-compaction.d.ts +6 -4
- package/dist/core/auto-compaction.js +3 -0
- package/dist/core/auto-mode-prompt-assets.js +1 -1
- package/dist/core/checkpoint-store.d.ts +36 -4
- package/dist/core/checkpoint-store.js +1 -0
- package/dist/core/context-edit.d.ts +36 -29
- package/dist/core/context-edit.js +3 -3
- package/dist/core/governance-codes.d.ts +1 -1
- package/dist/core/governance-codes.js +2 -0
- package/dist/core/hooks.d.ts +86 -4
- package/dist/core/hooks.js +3 -3
- package/dist/core/memory-engine/engine.d.ts +11 -0
- package/dist/core/memory-engine/engine.js +29 -3
- package/dist/core/memory-engine/index.d.ts +1 -1
- package/dist/core/memory-engine/origin-clearance.d.ts +28 -0
- package/dist/core/park-selfcheck.js +2 -0
- package/dist/core/pricing.d.ts +24 -0
- package/dist/core/pricing.js +18 -0
- package/dist/core/runner/prepare-config-doors.d.ts +36 -2
- package/dist/core/runner/prepare-config-doors.js +66 -8
- package/dist/core/runner/prepare-task.d.ts +113 -12
- package/dist/core/runner/prepare-task.js +239 -131
- package/dist/core/runner/runtask.d.ts +7 -0
- package/dist/core/runner/runtask.js +325 -95
- package/dist/core/runner/turn-attachments.d.ts +137 -5
- package/dist/core/runner/turn-attachments.js +25 -2
- package/dist/core/store-contracts/checkpoint-store-contract.js +19 -0
- package/dist/core/tool-errors.d.ts +2 -1
- package/dist/core/tool-policy.d.ts +27 -0
- package/dist/core/trace.d.ts +5 -4
- package/dist/core/types.d.ts +163 -29
- package/dist/core/untrusted-text.d.ts +5 -4
- package/dist/core/untrusted-text.js +8 -0
- package/dist/core/usage-window-store.d.ts +109 -8
- package/dist/core/usage-window-store.js +79 -12
- package/dist/engine/harness/agent-harness.js +20 -5
- package/dist/engine/harness/types.d.ts +38 -0
- package/dist/engine/loop/agent-loop.js +20 -1
- package/dist/engine/loop/types.d.ts +41 -1
- package/dist/index.d.ts +1 -1
- package/dist/orchestration/run-workflow-tool.d.ts +2 -2
- package/dist/orchestration/workflow-types.d.ts +48 -1
- package/dist/orchestration/workflow-types.js +12 -4
- package/dist/orchestration/workflow.d.ts +14 -3
- package/dist/orchestration/workflow.js +44 -19
- package/dist/prompt-assembly/event-registry.js +2 -0
- package/dist/prompts/default.js +1 -1
- package/dist/server/http.d.ts +1 -1
- package/dist/stores/file/usage-window-store.d.ts +1 -1
- package/dist/stores/file/usage-window-store.js +27 -6
- package/dist/tools/loop-tick.js +1 -1
- package/dist/tools/scheduler-tools.js +9 -1
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +3 -1
|
@@ -24,6 +24,9 @@ export function resolveUsageWindows(windows) {
|
|
|
24
24
|
if (typeof w.maxTokens !== "number" || !Number.isFinite(w.maxTokens) || w.maxTokens < 0) {
|
|
25
25
|
throw usageConfigError("config.usage_window_invalid", `RunnerDeps.usageWindows[].maxTokens must be a finite, non-negative number (got ${String(w.maxTokens)})`);
|
|
26
26
|
}
|
|
27
|
+
if (w.maxCostUsd !== undefined && (typeof w.maxCostUsd !== "number" || !Number.isFinite(w.maxCostUsd) || w.maxCostUsd < 0)) {
|
|
28
|
+
throw usageConfigError("config.usage_window_invalid", `RunnerDeps.usageWindows[].maxCostUsd must be a finite, non-negative number of USD when present (got ${String(w.maxCostUsd)})`);
|
|
29
|
+
}
|
|
27
30
|
if (w.anchor !== "first-use" && w.anchor !== "rolling") {
|
|
28
31
|
throw usageConfigError("config.usage_window_invalid", `RunnerDeps.usageWindows[].anchor must be "first-use" or "rolling" (got ${String(w.anchor)})`);
|
|
29
32
|
}
|
|
@@ -40,11 +43,34 @@ function rollingHorizonMs(windows) {
|
|
|
40
43
|
}
|
|
41
44
|
return widest;
|
|
42
45
|
}
|
|
43
|
-
export function
|
|
46
|
+
export function windowsGovernCost(windows) {
|
|
47
|
+
return windows.some((w) => w.maxCostUsd !== undefined);
|
|
48
|
+
}
|
|
49
|
+
function maxCostMicroUsdOf(w) {
|
|
50
|
+
return w.maxCostUsd === undefined ? undefined : Math.ceil(Number((w.maxCostUsd * 1e6).toPrecision(12)));
|
|
51
|
+
}
|
|
52
|
+
export function chargeUsageRecord(record, tokens, at, windows, costMicroUsd) {
|
|
44
53
|
const horizon = rollingHorizonMs(windows);
|
|
45
|
-
const
|
|
46
|
-
if (
|
|
47
|
-
|
|
54
|
+
const governsCost = windowsGovernCost(windows);
|
|
55
|
+
if (governsCost && costMicroUsd === undefined) {
|
|
56
|
+
throw usageConfigError("usage_window.store_cost_unanswered", "a usage-window CHARGE for a deployment that governs maxCostUsd arrived with no cost figure at all. Core supplies one (a number, or an explicit null for spend nothing could price) on every such charge, so an absent one means a store in the chain does not carry the cost argument — folding it to 0 would leave the money ceiling open forever with nothing able to notice. Forward `costMicroUsd` from `UsageWindowStore.charge` into `chargeUsageRecord`.");
|
|
57
|
+
}
|
|
58
|
+
const cost = governsCost && costMicroUsd !== null ? costMicroUsd : undefined;
|
|
59
|
+
const unknown = governsCost && costMicroUsd === null;
|
|
60
|
+
const moves = tokens > 0 || (cost !== undefined && cost > 0) || unknown;
|
|
61
|
+
const keptSlots = horizon === undefined
|
|
62
|
+
? []
|
|
63
|
+
: record.slots
|
|
64
|
+
.filter((s) => s.at > at - horizon)
|
|
65
|
+
.map((s) => ({
|
|
66
|
+
at: s.at,
|
|
67
|
+
tokens: s.tokens,
|
|
68
|
+
...(s.costMicroUsd === undefined ? {} : { costMicroUsd: s.costMicroUsd }),
|
|
69
|
+
...(s.costUnknown === true ? { costUnknown: true } : {}),
|
|
70
|
+
}));
|
|
71
|
+
if (horizon !== undefined && moves) {
|
|
72
|
+
keptSlots.push({ at, tokens, ...(cost === undefined ? {} : { costMicroUsd: cost }), ...(unknown ? { costUnknown: true } : {}) });
|
|
73
|
+
}
|
|
48
74
|
const buckets = [];
|
|
49
75
|
for (const w of windows) {
|
|
50
76
|
if (w.anchor !== "first-use")
|
|
@@ -54,11 +80,21 @@ export function chargeUsageRecord(record, tokens, at, windows) {
|
|
|
54
80
|
const prior = record.buckets.find((b) => b.windowMs === w.windowMs);
|
|
55
81
|
const lapsed = prior === undefined || at >= prior.openedAt + w.windowMs;
|
|
56
82
|
if (lapsed) {
|
|
57
|
-
if (
|
|
58
|
-
buckets.push({ windowMs: w.windowMs, openedAt: at, tokens });
|
|
83
|
+
if (moves) {
|
|
84
|
+
buckets.push({ windowMs: w.windowMs, openedAt: at, tokens, ...(cost === undefined ? {} : { costMicroUsd: cost }), ...(unknown ? { costUnknown: true } : {}) });
|
|
85
|
+
}
|
|
59
86
|
continue;
|
|
60
87
|
}
|
|
61
|
-
|
|
88
|
+
const priorCost = prior.costMicroUsd;
|
|
89
|
+
const nextCost = cost === undefined ? priorCost : (priorCost ?? 0) + cost;
|
|
90
|
+
const nextUnknown = unknown || prior.costUnknown === true;
|
|
91
|
+
buckets.push({
|
|
92
|
+
windowMs: w.windowMs,
|
|
93
|
+
openedAt: prior.openedAt,
|
|
94
|
+
tokens: prior.tokens + tokens,
|
|
95
|
+
...(nextCost === undefined ? {} : { costMicroUsd: nextCost }),
|
|
96
|
+
...(nextUnknown ? { costUnknown: true } : {}),
|
|
97
|
+
});
|
|
62
98
|
}
|
|
63
99
|
return { slots: keptSlots, buckets };
|
|
64
100
|
}
|
|
@@ -66,6 +102,8 @@ export function readUsageRecord(record, windows, now) {
|
|
|
66
102
|
const readings = [];
|
|
67
103
|
for (const w of windows) {
|
|
68
104
|
let tokens = 0;
|
|
105
|
+
let costMicroUsd = 0;
|
|
106
|
+
let costUnknown = false;
|
|
69
107
|
let freesAt;
|
|
70
108
|
if (w.anchor === "rolling") {
|
|
71
109
|
const floor = now - w.windowMs;
|
|
@@ -74,6 +112,9 @@ export function readUsageRecord(record, windows, now) {
|
|
|
74
112
|
if (s.at <= floor)
|
|
75
113
|
continue;
|
|
76
114
|
tokens += s.tokens;
|
|
115
|
+
costMicroUsd += s.costMicroUsd ?? 0;
|
|
116
|
+
if (s.costUnknown === true)
|
|
117
|
+
costUnknown = true;
|
|
77
118
|
if (oldest === undefined || s.at < oldest)
|
|
78
119
|
oldest = s.at;
|
|
79
120
|
}
|
|
@@ -84,18 +125,44 @@ export function readUsageRecord(record, windows, now) {
|
|
|
84
125
|
const row = record.buckets.find((b) => b.windowMs === w.windowMs);
|
|
85
126
|
if (row !== undefined && now < row.openedAt + w.windowMs) {
|
|
86
127
|
tokens = row.tokens;
|
|
128
|
+
costMicroUsd = row.costMicroUsd ?? 0;
|
|
129
|
+
if (row.costUnknown === true)
|
|
130
|
+
costUnknown = true;
|
|
87
131
|
freesAt = row.openedAt + w.windowMs;
|
|
88
132
|
}
|
|
89
133
|
}
|
|
90
|
-
const
|
|
134
|
+
const maxCostMicroUsd = maxCostMicroUsdOf(w);
|
|
135
|
+
const exhausted = tokens >= w.maxTokens || (maxCostMicroUsd !== undefined && costMicroUsd >= maxCostMicroUsd);
|
|
91
136
|
const retryAfterMs = exhausted ? Math.max(1, freesAt === undefined ? w.windowMs : freesAt - now) : 0;
|
|
92
|
-
readings.push({
|
|
137
|
+
readings.push({
|
|
138
|
+
window: w,
|
|
139
|
+
tokens,
|
|
140
|
+
...(maxCostMicroUsd !== undefined ? { costMicroUsd } : {}),
|
|
141
|
+
...(maxCostMicroUsd !== undefined && costUnknown ? { costUnknown: true } : {}),
|
|
142
|
+
exhausted,
|
|
143
|
+
retryAfterMs,
|
|
144
|
+
});
|
|
93
145
|
}
|
|
94
146
|
return readings;
|
|
95
147
|
}
|
|
96
|
-
export function usageRetryAfterMs(readings) {
|
|
148
|
+
export function usageRetryAfterMs(readings, windows) {
|
|
149
|
+
if (windows !== undefined) {
|
|
150
|
+
if (readings.length !== windows.length) {
|
|
151
|
+
throw usageConfigError("usage_window.store_cost_unanswered", `the usage-window ledger answered ${String(readings.length)} reading(s) for ${String(windows.length)} declared window(s) — the read contract is one reading per window, in the order given. A window with no reading was never evaluated: refused rather than treated as open.`);
|
|
152
|
+
}
|
|
153
|
+
for (let i = 0; i < windows.length; i++) {
|
|
154
|
+
const w = windows[i];
|
|
155
|
+
const echoed = readings[i].window;
|
|
156
|
+
if (echoed === undefined || echoed.windowMs !== w.windowMs || echoed.anchor !== w.anchor || echoed.maxTokens !== w.maxTokens || echoed.maxCostUsd !== w.maxCostUsd) {
|
|
157
|
+
throw usageConfigError("usage_window.store_cost_unanswered", `the usage-window ledger's reading #${String(i)} does not echo declared window #${String(i)} verbatim (declared: windowMs ${String(w.windowMs)}, ${String(w.anchor)}, maxTokens ${String(w.maxTokens)}, maxCostUsd ${String(w.maxCostUsd)}) — the read contract requires the window back verbatim, in order, so its ceilings are provably the ones evaluated. Refused rather than re-attributed.`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
97
161
|
let worst;
|
|
98
162
|
for (const r of readings) {
|
|
163
|
+
if (r.window?.maxCostUsd !== undefined && r.costMicroUsd === undefined) {
|
|
164
|
+
throw usageConfigError("usage_window.store_cost_unanswered", `the usage-window ledger answered a window that declares maxCostUsd ${String(r.window.maxCostUsd)} (windowMs ${String(r.window.windowMs)}, ${String(r.window.anchor)}) with NO costMicroUsd — the money ceiling was never evaluated. Refused rather than treated as open: the store must persist the cost charged to it and report what the window holds.`);
|
|
165
|
+
}
|
|
99
166
|
if (!r.exhausted)
|
|
100
167
|
continue;
|
|
101
168
|
if (worst === undefined || r.retryAfterMs > worst)
|
|
@@ -105,9 +172,9 @@ export function usageRetryAfterMs(readings) {
|
|
|
105
172
|
}
|
|
106
173
|
export class InMemoryUsageWindowStore {
|
|
107
174
|
records = new Map();
|
|
108
|
-
async charge(key, tokens, at, windows) {
|
|
175
|
+
async charge(key, tokens, at, windows, costMicroUsd) {
|
|
109
176
|
const prior = this.records.get(key) ?? EMPTY_USAGE_WINDOW_RECORD;
|
|
110
|
-
this.records.set(key, chargeUsageRecord(prior, tokens, at, windows));
|
|
177
|
+
this.records.set(key, chargeUsageRecord(prior, tokens, at, windows, costMicroUsd));
|
|
111
178
|
}
|
|
112
179
|
async read(key, windows, now) {
|
|
113
180
|
return readUsageRecord(this.records.get(key) ?? EMPTY_USAGE_WINDOW_RECORD, windows, now);
|
|
@@ -547,7 +547,7 @@ export class AgentHarness {
|
|
|
547
547
|
userInputParkRecords.delete(message);
|
|
548
548
|
engineNotePayloads.delete(message);
|
|
549
549
|
}
|
|
550
|
-
createLoopConfig(getTurnState, setTurnState) {
|
|
550
|
+
createLoopConfig(getTurnState, setTurnState, runPromptOverride) {
|
|
551
551
|
const turnState = getTurnState();
|
|
552
552
|
const self = this;
|
|
553
553
|
return {
|
|
@@ -564,9 +564,24 @@ export class AgentHarness {
|
|
|
564
564
|
: {}),
|
|
565
565
|
convertToLlm: (messages) => stripEngineMetadata(convertToLlm(messages)),
|
|
566
566
|
shouldStopAfterTurn: () => this._stopAfterTurn,
|
|
567
|
-
transformContext: async (messages) => {
|
|
568
|
-
const result = await this.emitHook({ type: "context", messages: [...messages] });
|
|
569
|
-
|
|
567
|
+
transformContext: async (messages, signal) => {
|
|
568
|
+
const result = await this.emitHook({ type: "context", messages: [...messages], ...(signal !== undefined ? { signal } : {}) });
|
|
569
|
+
if (result?.adoptSessionRebuild !== true) {
|
|
570
|
+
return result?.messages ?? messages;
|
|
571
|
+
}
|
|
572
|
+
await this.flushPendingSessionWrites();
|
|
573
|
+
const nextTurnState = await this.createTurnState();
|
|
574
|
+
setTurnState(nextTurnState);
|
|
575
|
+
const rebuiltContext = this.createContext(nextTurnState, runPromptOverride);
|
|
576
|
+
const recheck = await this.emitHook({ type: "context", messages: [...rebuiltContext.messages], ...(signal !== undefined ? { signal } : {}), recheck: true });
|
|
577
|
+
return {
|
|
578
|
+
messages: recheck?.messages ?? rebuiltContext.messages,
|
|
579
|
+
adoptedContext: {
|
|
580
|
+
messages: rebuiltContext.messages,
|
|
581
|
+
systemPrompt: rebuiltContext.systemPrompt,
|
|
582
|
+
...(rebuiltContext.systemBlocks !== undefined ? { systemBlocks: rebuiltContext.systemBlocks } : {}),
|
|
583
|
+
},
|
|
584
|
+
};
|
|
570
585
|
},
|
|
571
586
|
beforeToolCall: async ({ toolCall, args }) => {
|
|
572
587
|
const result = await this.emitHook({
|
|
@@ -780,7 +795,7 @@ export class AgentHarness {
|
|
|
780
795
|
this.runAbortController = abortController;
|
|
781
796
|
const runResultPromise = (async () => {
|
|
782
797
|
try {
|
|
783
|
-
return await runAgentLoop(messages, this.createContext(turnState, beforeResult?.systemPrompt), this.createLoopConfig(getTurnState, setTurnState), (event) => this.handleAgentEvent(event, abortController.signal), abortController.signal, this.createStreamFn(getTurnState), undefined, this.loopTrace);
|
|
798
|
+
return await runAgentLoop(messages, this.createContext(turnState, beforeResult?.systemPrompt), this.createLoopConfig(getTurnState, setTurnState, beforeResult?.systemPrompt), (event) => this.handleAgentEvent(event, abortController.signal), abortController.signal, this.createStreamFn(getTurnState), undefined, this.loopTrace);
|
|
784
799
|
}
|
|
785
800
|
catch (error) {
|
|
786
801
|
try {
|
|
@@ -919,6 +919,25 @@ export interface BeforeAgentStartEvent<TSkill extends Skill = Skill, TPromptTemp
|
|
|
919
919
|
export interface ContextEvent {
|
|
920
920
|
type: "context";
|
|
921
921
|
messages: AgentMessage[];
|
|
922
|
+
/**
|
|
923
|
+
* The TURN-scoped abort signal of the request build being transformed (present when the loop
|
|
924
|
+
* handed one — it always does in production; absent only for bare-harness callers). A handler
|
|
925
|
+
* that runs an EXPENSIVE reduction (the slice-3 arm-B in-turn compaction is the standing case)
|
|
926
|
+
* must honor it: a turn interrupt (design/373) fires this signal, and a reduction that only
|
|
927
|
+
* watches the RUN-level signal would make the interrupt wait out a long summary call and commit
|
|
928
|
+
* a session mutation the interrupted turn no longer needs (adversarial review r3).
|
|
929
|
+
*/
|
|
930
|
+
signal?: AbortSignal;
|
|
931
|
+
/**
|
|
932
|
+
* design/374 slice 3 — set on the ONE bounded re-dispatch of the adopt seam
|
|
933
|
+
* ({@link ContextResult.adoptSessionRebuild}): `messages` are the just-adopted session rebuild,
|
|
934
|
+
* re-presented so the handler's request pipeline applies to what the provider will actually
|
|
935
|
+
* receive (the "recheck" step of build→reduce→adopt→recheck). A handler must not request a
|
|
936
|
+
* second adoption on this dispatch — the harness does not honor it (see the result member's
|
|
937
|
+
* doc) — so a reduction that is still over budget takes the handler's own next arm (the trim
|
|
938
|
+
* last resort) instead of looping. Absent on every ordinary dispatch.
|
|
939
|
+
*/
|
|
940
|
+
recheck?: boolean;
|
|
922
941
|
}
|
|
923
942
|
export interface TurnBoundaryEvent {
|
|
924
943
|
/** Fires between model-request boundaries, after session flush, before the context rebuild. */
|
|
@@ -997,6 +1016,25 @@ export interface BeforeAgentStartResult {
|
|
|
997
1016
|
/** Hook result for replacing the full context message list before provider conversion. */
|
|
998
1017
|
export interface ContextResult {
|
|
999
1018
|
messages: AgentMessage[];
|
|
1019
|
+
/**
|
|
1020
|
+
* design/374 slice 3 (arm B, the adopt seam) — the handler PERSISTED a session-level reduction
|
|
1021
|
+
* during this dispatch (e.g. an in-turn forced compaction appended to the session) and asks the
|
|
1022
|
+
* harness to adopt it MID-BUILD, the same three-step form as the two existing adoption flows
|
|
1023
|
+
* (turn_boundary / prompt-too-long recovery): flush pending session writes, rebuild the turn
|
|
1024
|
+
* context from the session (`createTurnState`), adopt it (`setTurnState` + the loop-context
|
|
1025
|
+
* adoption via {@link import("../loop/types.js").TransformedContext}), then re-dispatch the
|
|
1026
|
+
* context hook ONCE on the rebuilt view (`ContextEvent.recheck`) so the request pipeline applies
|
|
1027
|
+
* to it. Result: the provider request, every subsequent hook dispatch, and the active turn state
|
|
1028
|
+
* all see the SAME reduced transcript — the three-party co-view the seam exists for. `messages`
|
|
1029
|
+
* on this arm are the handler's best unadopted view and are superseded by the rebuild.
|
|
1030
|
+
*
|
|
1031
|
+
* Honored AT MOST ONCE per request build: on the recheck dispatch this member is ignored (its
|
|
1032
|
+
* `messages` are used verbatim) — the bound that keeps a reduction that cannot get under budget
|
|
1033
|
+
* from re-entering forever. Typed on the hook RESULT deliberately (not a closure side channel):
|
|
1034
|
+
* the adoption is part of the hook's answer, and the one prior closure-ref transport in this
|
|
1035
|
+
* area (`requestLossyRef`) is a recorded bypass shape, not a precedent to grow.
|
|
1036
|
+
*/
|
|
1037
|
+
adoptSessionRebuild?: boolean;
|
|
1000
1038
|
}
|
|
1001
1039
|
/** Hook result for patching provider request options before payload construction. */
|
|
1002
1040
|
export interface BeforeProviderRequestResult {
|
|
@@ -524,7 +524,26 @@ async function settleInterruptedTurn(state, message, executor, committedResults,
|
|
|
524
524
|
async function streamAssistantResponse(context, config, signal, emit, streamFn, runtime, executor, staticReasoningCutDowngrade) {
|
|
525
525
|
let messages = context.messages;
|
|
526
526
|
if (config.transformContext) {
|
|
527
|
-
|
|
527
|
+
const transformed = await config.transformContext(messages, signal);
|
|
528
|
+
if (Array.isArray(transformed)) {
|
|
529
|
+
messages = transformed;
|
|
530
|
+
}
|
|
531
|
+
else {
|
|
532
|
+
const adopted = transformed.adoptedContext;
|
|
533
|
+
if (adopted !== undefined) {
|
|
534
|
+
context.messages = adopted.messages;
|
|
535
|
+
if (adopted.systemPrompt !== undefined) {
|
|
536
|
+
context.systemPrompt = adopted.systemPrompt;
|
|
537
|
+
if (adopted.systemBlocks !== undefined) {
|
|
538
|
+
context.systemBlocks = adopted.systemBlocks;
|
|
539
|
+
}
|
|
540
|
+
else {
|
|
541
|
+
delete context.systemBlocks;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
messages = transformed.messages;
|
|
546
|
+
}
|
|
528
547
|
}
|
|
529
548
|
const llmMessages = await config.convertToLlm(messages);
|
|
530
549
|
const llmContext = {
|
|
@@ -223,6 +223,40 @@ export interface LoopThinkingOnlyRecovery {
|
|
|
223
223
|
* side is `recover(messages, attempt)` and also carries `withholdErrorEvents`, so the two can never share
|
|
224
224
|
* one type. (`agent-harness.ts` is outside this file's edit scope — its side of this mutual-reference
|
|
225
225
|
* note is pending.) */
|
|
226
|
+
/**
|
|
227
|
+
* design/374 slice 3 — the transform's ADOPTING return shape (see
|
|
228
|
+
* {@link AgentLoopConfig.transformContext}). Two arrays with different standings, on purpose:
|
|
229
|
+
* `messages` is the WIRE view for this one request (a request-only projection — cleared markers,
|
|
230
|
+
* caps, trim — which must NEVER become loop state, the D-4 non-destructive posture), while
|
|
231
|
+
* `adoptedContext` is a session-rebuilt TRANSCRIPT the loop adopts as its live context
|
|
232
|
+
* (`state.context.messages`) so the rest of the turn — later requests, the interrupt reconcile,
|
|
233
|
+
* the recovery pops — continues from the reduced transcript. This is the transform-seam sibling of
|
|
234
|
+
* the ④b recover adoption (`state.context.messages = replaced`); a transform that reduced the
|
|
235
|
+
* SESSION but only re-projected the wire would leave the loop replaying the pre-reduction
|
|
236
|
+
* transcript into every later request of the turn.
|
|
237
|
+
*/
|
|
238
|
+
export interface TransformedContext {
|
|
239
|
+
/** The wire view for THIS request (what `convertToLlm` receives). */
|
|
240
|
+
messages: AgentMessage[];
|
|
241
|
+
/** Present ⇒ adopt this rebuilt context as the loop's live state before streaming. */
|
|
242
|
+
adoptedContext?: {
|
|
243
|
+
/** The rebuilt transcript the loop adopts as `state.context.messages`. */
|
|
244
|
+
messages: AgentMessage[];
|
|
245
|
+
/** When present, the rebuilt SYSTEM PROMPT is adopted too — and it applies to THIS very
|
|
246
|
+
* request (the loop reads `context.systemPrompt` after the transform): an in-turn compaction
|
|
247
|
+
* can commit a prompt-epoch change (center-prompt adoption/rollback rides the compaction
|
|
248
|
+
* pass), and a request built from the rebuilt transcript under the PRE-epoch prompt would
|
|
249
|
+
* disagree with the epoch the session just recorded (adversarial review r1). `systemBlocks`
|
|
250
|
+
* must only ever accompany the prompt they byte-correspond to; when the prompt is adopted
|
|
251
|
+
* WITHOUT blocks, stale blocks are dropped (the string face is the truth source — same
|
|
252
|
+
* degrade-to-string posture as the harness's atomic-face guard). */
|
|
253
|
+
systemPrompt?: string;
|
|
254
|
+
systemBlocks?: Array<{
|
|
255
|
+
text: string;
|
|
256
|
+
cacheControlBoundary: boolean;
|
|
257
|
+
}>;
|
|
258
|
+
};
|
|
259
|
+
}
|
|
226
260
|
export interface LoopRecoveryOptions {
|
|
227
261
|
promptTooLong?: LoopPromptTooLongRecovery;
|
|
228
262
|
truncatedOutput?: LoopTruncatedOutputRecovery;
|
|
@@ -328,8 +362,14 @@ export interface AgentLoopConfig extends SimpleStreamOptions {
|
|
|
328
362
|
* return messages;
|
|
329
363
|
* }
|
|
330
364
|
* ```
|
|
365
|
+
*
|
|
366
|
+
* Return shape (design/374 slice 3): a bare array is the historic contract — a REQUEST-ONLY
|
|
367
|
+
* projection, never adopted into the loop's live context. Returning a
|
|
368
|
+
* {@link TransformedContext} additionally lets the transform ADOPT a session-rebuilt transcript
|
|
369
|
+
* mid-turn (the in-turn forced-compaction seam) — the loop half of the same two-half adoption
|
|
370
|
+
* form the prompt-too-long recovery already has (`state.context.messages = replaced`).
|
|
331
371
|
*/
|
|
332
|
-
transformContext?: (messages: AgentMessage[], signal?: AbortSignal) => Promise<AgentMessage[]>;
|
|
372
|
+
transformContext?: (messages: AgentMessage[], signal?: AbortSignal) => Promise<AgentMessage[] | TransformedContext>;
|
|
333
373
|
/**
|
|
334
374
|
* Resolves an API key dynamically for each LLM call.
|
|
335
375
|
*
|
package/dist/index.d.ts
CHANGED
|
@@ -168,7 +168,7 @@ export { AdoptionError, assertAdoptionBootGate, readRootAdoptionFile, writeRootA
|
|
|
168
168
|
export { adoptLocalDataRoot, ackAdoptionConfig, witnessAdoptionConfig, listAdoptionQuarantine, readAdoptionStatus, type AdoptionStatus, type AdoptLocalDataRootOptions, type AdoptLocalDataRootResult, type AdoptionCarriageLeg, type AdoptionCarriageLegContext, type AdoptionConfigWitnessReceipt, } from "./stores/file/adoption/adopt.js";
|
|
169
169
|
export { formatHookFeedback, runToolGate, createHookEnvCapabilities, createPreToolUseConstraintPolicy, type PersistedRuleHit, type PersistedRuleHitRule, type PersistedRuleUnreadable, type PersistedRuleCoverage, type PersistedRuleAnswer, normalizePersistedRuleHit, type Hooks, type HookToolContext, type HookInvocationIdentity, type UserPromptSubmitContext, type PostToolBatchContext, type HookEnvCapabilities, type HookToolOutput, type PreToolUseResult, type PostToolUseResult, type UserPromptSubmitResult, type HookToolFailure, type PostToolUseFailureResult, type PostToolBatchCall, type PostToolBatchResult, type PreCompactContext, type PreCompactResult, type PostCompactContext, type StopFailureContext, type PermissionDeniedPayload, type PermissionDeniedSource, } from "./core/hooks.js";
|
|
170
170
|
export { InMemoryMemoryStore, composeMemoryBlock, composeLayeredMemoryBlock, normalizeMemorySpec, type NormalizedMemorySpec, type MemorySpecInput, supportsConsolidation, supportsPeriodicConsolidation, firstSentence, expandLexicalTerms, lexicalSearchMatch, type Embedder, classifyPromotable, enforcePromotableWriteGate, guardedMemoryStore, MemoryGateError, detectSecret, enforceSecretWriteGate, enforceStructuredNoteSecretGate, type UtilityGate, type MemoryStore, type MemoryVectorMode, type ScoredMemory, type MemoryNoteHeader, type MemoryNoteRecord, type MemoryNoteType, type StructuredNoteInput, } from "./core/memory.js";
|
|
171
|
-
export { MemoryEngine, FileMemoryEngineBackend, memoryBackendContract, assertMemoryBackendSearchEquivalence, type MemoryBackendContractHooks, buildMemoryInstruction, truncateIndex, scanEntryFiles, deriveRepoKey, deriveRepoMemoryDir, deriveControlPlaneDir, deriveRepoControlPlaneDir, resolveMemoryEngineRoot, scopeDirName, ControlPlaneCorruptError, parseEntryFile, serializeEntryFile, computeEntryRev, entryFromFile, MEMORY_ORIGIN_CAUSES, committedOriginOf, originEquals, ambiguousOriginRepresentation, type OriginClearanceRow, type OriginClearanceEvent, committedDistilledOf, distilledEquals, type MemoryEntryDistilled, type MemoryEntryDistilledInput, CONSOLIDATION_DEFAULTS, ConsolidationRefusedError, MEMORY_SEARCH_SUPERSEDED_TAG, consolidationTypeEligible, deriveSupersededSet, memorySupersededNote, readIntentCredentials, supersessionFuseCeiling, memoryConsolidationRecommendedNotice, memoryConsolidationCommittedNotice, memoryConsolidationConflictNotice, memoryConsolidationIncompleteNotice, memoryConsolidationRefusedNotice, type ConsolidationGateRead, type ConsolidationGateRow, type ConsolidationIntent, type ConsolidationIntentCredentialRow, type ConsolidationLeaseSeat, type ConsolidationProductProposal, type ConsolidationProposal, type MemoryConsolidationOptions, type ConsolidationCommitReceipt, type ConsolidationReconcileReport, type ConsolidationResolveReceipt, type ConsolidationPlanSummary, type ConsolidationPlanFoldEvidence, DISTILLER_DEFAULT_MAX_INPUTS_PER_PRODUCT, LLM_DISTILLER_CONTRACT, LLM_DISTILLER_CONTRACT_DL2, LLM_DISTILLER_CONTRACT_DL3, LLM_DISTILLER_CONTRACTS, MEMORY_DISTILLER_CONTRACT_V1, contractGroupingDiff, driveConsolidationToFixpoint, isAliasModelId, llmPlanDistiller, mintExposurePartitionedPlan, mintLlmConsolidationPlan, openAiCompatChatSeat, parseJsonAnswer, planParseRepairs, sanitizeLlmGroups, scheduleUnderFuse, type ConsolidationDistillFn, type ConsolidationDriveCycleRow, type ConsolidationDriveEngine, type ConsolidationDriveResult, type ConsolidationFoldState, type DistillerCandidate, type DistillerChatAnswer, type DistillerChatFn, type DistillerChatRequest, type FuseSchedule, type LlmConsolidationPlan, type LlmConsolidationPlanArm, type LlmConsolidationPlanProduct, type LlmDistillerContract, type MintLlmConsolidationPlanResult, type PlanParseRepairs, type SanitizedLlmGroups, CONSOLIDATION_DRIVER_PLANS_DIR, CONSOLIDATION_DRIVER_RUNS_FILE, CONSOLIDATION_RUN_STOP_REASONS, archiveDistillerPlan, readConsolidationDriverRun, runMemoryConsolidationDriver, type ConsolidationDriverEngine, type ConsolidationDriverRunRow, type ConsolidationRunReceipt, type ConsolidationRunStopReason, type RunMemoryConsolidationOptions, isInstructionEntry, type MemoryEntryOrigin, type MemoryOriginCause, MEMORY_INSTRUCTION_TEMPLATE, MEMORY_RECALL_DISCIPLINE, MEMORY_PREFERENCE_DISCIPLINE, MEMORY_READONLY_NOTICE, readV2HeaderHints, type V2HeaderHints, LINEAGE_FILE, CHALLENGES_FILE, CHALLENGED_HISTORY_FILE, CHALLENGE_LEDGER_MAX_EVENTS, rebuildStrictControlPlaneLedger, type ControlPlaneRebuildReceipt, type StrictControlPlaneLedger, type ChallengeAssignment, type ChallengeEvent, type ChallengedHistoryRow, type LineagePendingTxn, type LineagePromotion, MEMORY_INDEX_FILENAME, MEMORY_INDEX_MAX_LINES, MEMORY_INDEX_MAX_BYTES, MEMORY_SEARCH_TOOL_NAME, MEMORY_GET_TOOL_NAME, MEMORY_ENGINE_TOOL_NAMES, type MemorySearchDetails, type MemorySearchHit, type CleanMemorySearchHit, type ExposedMemorySearchHit, MEMORY_EXPOSURE_BANNER, MEMORY_EXPOSURE_HANDLE_TAG, memoryExposureIndexRow, type MemoryGetDetails, STUB_ARCHIVED_LINE, DEFAULT_MAX_MEMORY_FILES, DEFAULT_MAX_ENTRY_DEPTH, DEFAULT_HARVEST_DEADLINE_MS, DEFAULT_HARVEST_FILE_BUDGET, MASS_DELETION_FUSE_RATIO, scanMemoryWrite, scanMemoryFileName, scanRemediation, MEMORY_FILENAME_SEGMENT_RE, renderAnnouncements, enqueueMemoryAnnouncement, drainMemoryAnnouncements, peekMemoryAnnouncements, bumpScanFuse, scanFuseCount, clearScanFuse, ANNOUNCEMENTS_FILE, MEMORY_ANNOUNCEMENTS_MAX, SCAN_FUSE_FILE, SCAN_FUSE_THRESHOLD, type MemoryAnnouncement, type ScanFinding, type MemoryEngineOptions, type MemoryInjection, type EntryProvenanceAccount, type TransferEvidence, type CommittedBinding, type CommittedEntrySnapshot, type CommittedScopeSnapshots, type EntryCustodyReport, erasureSelectHash, type EraseMemoryEntriesInput, type ErasureSelect, type ErasedBinding, type MemoryErasureAttestation, computeMemoryBundleHash, type MemoryExportBundle, type MemoryImportReport, type MemoryExportSnapshot, type MemoryBundleImportPlan, type BundleChallengeRow, type BundleLineageRow, type BundlePollutedSession, type MemoryBackend, type MemoryEntry, type MemoryEntryFrontmatter, type MemoryEntryHeader, type MemoryScopeEnumeration, type ScoredMemoryEntry, type NotePatch, type PatchReport, type MaterializedFile, type MemorySessionHandle, type HarvestReport, type HarvestRejection, type HarvestRejectionCode, type ParsedEntryFile, type ScannedEntryFile, SCOPE_SEGMENT_MAX_ENCODED, PROJECT_MARKER_PATH, encodeScopeSegment, decodeScopeSegment, parseScopeKey, formatUserScope, formatOrgScope, formatProjScope, formatUserProjScope, isPersonalScope, assertScopeContractPlacement, formatProjectMarker, parseProjectMarker, resolveProjectId, PROJECT_ID_REGEX, type ParsedScopeKey, migrateScope, type MigrateScopeReport, materializeEntriesToFiles, harvestFilesToPatches, projectionsToWriteBack, screenInboundEntries, REMOTE_HARVEST_PER_FILE_BYTES, REMOTE_MASS_DELETION_FUSE_RATIO, type RemoteMemoryFile, type RemoteMemoryBaseline, type RemoteMaterialization, type RemoteHarvestResult, type InboundEntryFinding, deriveProjectMemoryDir, deriveProjectControlDir, recordProjectIdHint, lookupProjectIdHint, PROJECT_ID_HINTS_FILE, reconcileMemoryEntries, nextSyncBaseline, mergeRecallHits, type MemorySyncCursor, type MemorySyncConflict, type MemorySyncPlan, type RecallSource, syncMemoryScope, type MemorySyncTransport, type MemorySyncRequestBody, type MemorySyncResponseBody, type MemorySyncClientConflict, type SyncMemoryScopeOptions, type MemorySyncClientResult, } from "./core/memory-engine/index.js";
|
|
171
|
+
export { MemoryEngine, FileMemoryEngineBackend, memoryBackendContract, assertMemoryBackendSearchEquivalence, type MemoryBackendContractHooks, buildMemoryInstruction, truncateIndex, scanEntryFiles, deriveRepoKey, deriveRepoMemoryDir, deriveControlPlaneDir, deriveRepoControlPlaneDir, resolveMemoryEngineRoot, scopeDirName, ControlPlaneCorruptError, parseEntryFile, serializeEntryFile, computeEntryRev, entryFromFile, MEMORY_ORIGIN_CAUSES, committedOriginOf, originEquals, ambiguousOriginRepresentation, type OriginClearanceRow, type OriginClearanceEvent, type OriginClearanceShadow, committedDistilledOf, distilledEquals, type MemoryEntryDistilled, type MemoryEntryDistilledInput, CONSOLIDATION_DEFAULTS, ConsolidationRefusedError, MEMORY_SEARCH_SUPERSEDED_TAG, consolidationTypeEligible, deriveSupersededSet, memorySupersededNote, readIntentCredentials, supersessionFuseCeiling, memoryConsolidationRecommendedNotice, memoryConsolidationCommittedNotice, memoryConsolidationConflictNotice, memoryConsolidationIncompleteNotice, memoryConsolidationRefusedNotice, type ConsolidationGateRead, type ConsolidationGateRow, type ConsolidationIntent, type ConsolidationIntentCredentialRow, type ConsolidationLeaseSeat, type ConsolidationProductProposal, type ConsolidationProposal, type MemoryConsolidationOptions, type ConsolidationCommitReceipt, type ConsolidationReconcileReport, type ConsolidationResolveReceipt, type ConsolidationPlanSummary, type ConsolidationPlanFoldEvidence, DISTILLER_DEFAULT_MAX_INPUTS_PER_PRODUCT, LLM_DISTILLER_CONTRACT, LLM_DISTILLER_CONTRACT_DL2, LLM_DISTILLER_CONTRACT_DL3, LLM_DISTILLER_CONTRACTS, MEMORY_DISTILLER_CONTRACT_V1, contractGroupingDiff, driveConsolidationToFixpoint, isAliasModelId, llmPlanDistiller, mintExposurePartitionedPlan, mintLlmConsolidationPlan, openAiCompatChatSeat, parseJsonAnswer, planParseRepairs, sanitizeLlmGroups, scheduleUnderFuse, type ConsolidationDistillFn, type ConsolidationDriveCycleRow, type ConsolidationDriveEngine, type ConsolidationDriveResult, type ConsolidationFoldState, type DistillerCandidate, type DistillerChatAnswer, type DistillerChatFn, type DistillerChatRequest, type FuseSchedule, type LlmConsolidationPlan, type LlmConsolidationPlanArm, type LlmConsolidationPlanProduct, type LlmDistillerContract, type MintLlmConsolidationPlanResult, type PlanParseRepairs, type SanitizedLlmGroups, CONSOLIDATION_DRIVER_PLANS_DIR, CONSOLIDATION_DRIVER_RUNS_FILE, CONSOLIDATION_RUN_STOP_REASONS, archiveDistillerPlan, readConsolidationDriverRun, runMemoryConsolidationDriver, type ConsolidationDriverEngine, type ConsolidationDriverRunRow, type ConsolidationRunReceipt, type ConsolidationRunStopReason, type RunMemoryConsolidationOptions, isInstructionEntry, type MemoryEntryOrigin, type MemoryOriginCause, MEMORY_INSTRUCTION_TEMPLATE, MEMORY_RECALL_DISCIPLINE, MEMORY_PREFERENCE_DISCIPLINE, MEMORY_READONLY_NOTICE, readV2HeaderHints, type V2HeaderHints, LINEAGE_FILE, CHALLENGES_FILE, CHALLENGED_HISTORY_FILE, CHALLENGE_LEDGER_MAX_EVENTS, rebuildStrictControlPlaneLedger, type ControlPlaneRebuildReceipt, type StrictControlPlaneLedger, type ChallengeAssignment, type ChallengeEvent, type ChallengedHistoryRow, type LineagePendingTxn, type LineagePromotion, MEMORY_INDEX_FILENAME, MEMORY_INDEX_MAX_LINES, MEMORY_INDEX_MAX_BYTES, MEMORY_SEARCH_TOOL_NAME, MEMORY_GET_TOOL_NAME, MEMORY_ENGINE_TOOL_NAMES, type MemorySearchDetails, type MemorySearchHit, type CleanMemorySearchHit, type ExposedMemorySearchHit, MEMORY_EXPOSURE_BANNER, MEMORY_EXPOSURE_HANDLE_TAG, memoryExposureIndexRow, type MemoryGetDetails, STUB_ARCHIVED_LINE, DEFAULT_MAX_MEMORY_FILES, DEFAULT_MAX_ENTRY_DEPTH, DEFAULT_HARVEST_DEADLINE_MS, DEFAULT_HARVEST_FILE_BUDGET, MASS_DELETION_FUSE_RATIO, scanMemoryWrite, scanMemoryFileName, scanRemediation, MEMORY_FILENAME_SEGMENT_RE, renderAnnouncements, enqueueMemoryAnnouncement, drainMemoryAnnouncements, peekMemoryAnnouncements, bumpScanFuse, scanFuseCount, clearScanFuse, ANNOUNCEMENTS_FILE, MEMORY_ANNOUNCEMENTS_MAX, SCAN_FUSE_FILE, SCAN_FUSE_THRESHOLD, type MemoryAnnouncement, type ScanFinding, type MemoryEngineOptions, type MemoryInjection, type EntryProvenanceAccount, type TransferEvidence, type CommittedBinding, type CommittedEntrySnapshot, type CommittedScopeSnapshots, type EntryCustodyReport, erasureSelectHash, type EraseMemoryEntriesInput, type ErasureSelect, type ErasedBinding, type MemoryErasureAttestation, computeMemoryBundleHash, type MemoryExportBundle, type MemoryImportReport, type MemoryExportSnapshot, type MemoryBundleImportPlan, type BundleChallengeRow, type BundleLineageRow, type BundlePollutedSession, type MemoryBackend, type MemoryEntry, type MemoryEntryFrontmatter, type MemoryEntryHeader, type MemoryScopeEnumeration, type ScoredMemoryEntry, type NotePatch, type PatchReport, type MaterializedFile, type MemorySessionHandle, type HarvestReport, type HarvestRejection, type HarvestRejectionCode, type ParsedEntryFile, type ScannedEntryFile, SCOPE_SEGMENT_MAX_ENCODED, PROJECT_MARKER_PATH, encodeScopeSegment, decodeScopeSegment, parseScopeKey, formatUserScope, formatOrgScope, formatProjScope, formatUserProjScope, isPersonalScope, assertScopeContractPlacement, formatProjectMarker, parseProjectMarker, resolveProjectId, PROJECT_ID_REGEX, type ParsedScopeKey, migrateScope, type MigrateScopeReport, materializeEntriesToFiles, harvestFilesToPatches, projectionsToWriteBack, screenInboundEntries, REMOTE_HARVEST_PER_FILE_BYTES, REMOTE_MASS_DELETION_FUSE_RATIO, type RemoteMemoryFile, type RemoteMemoryBaseline, type RemoteMaterialization, type RemoteHarvestResult, type InboundEntryFinding, deriveProjectMemoryDir, deriveProjectControlDir, recordProjectIdHint, lookupProjectIdHint, PROJECT_ID_HINTS_FILE, reconcileMemoryEntries, nextSyncBaseline, mergeRecallHits, type MemorySyncCursor, type MemorySyncConflict, type MemorySyncPlan, type RecallSource, syncMemoryScope, type MemorySyncTransport, type MemorySyncRequestBody, type MemorySyncResponseBody, type MemorySyncClientConflict, type SyncMemoryScopeOptions, type MemorySyncClientResult, } from "./core/memory-engine/index.js";
|
|
172
172
|
export { SHARED_MEMORY_READ_CAP_BYTES, SHARED_MEMORY_LIST_PAGE_SIZE, SharedMemoryStoreError, type SharedMemoryStoreProvider, type SharedMemoryStoreReader, type SharedMemoryPagedList, type SharedMemoryStoreInfo, type SharedMemoryDocumentEntry, type SharedMemorySnapshot, type SharedMemoryRequestContext, type MemoryListDetails, type MemoryReadDetails, } from "./core/shared-memory/types.js";
|
|
173
173
|
export { sharedMemoryStoreContract, type SharedMemoryFixture, type SharedMemoryStoreContractHooks, } from "./core/shared-memory/contract.js";
|
|
174
174
|
export { termSet, jaccardDistance, cosineDistance } from "./core/memory-vector.js";
|
|
@@ -278,8 +278,8 @@ export interface RunWorkflowToolDeps {
|
|
|
278
278
|
* (`RunnerDeps.onAsk`, else the fail-closed headless auto-deny). */
|
|
279
279
|
parentOnAsk?: import("../core/tool-policy.js").OnAsk;
|
|
280
280
|
/** The HOST run's display sink (its `RunInternals.onForwardEvent` behind the runner's ctx wrapper:
|
|
281
|
-
* `task_progress` always, plus the children's content events — `text_delta`/`
|
|
282
|
-
* `tool_start`/`tool_end`, UNTRUSTED-RAW: the consumer must redact — when the HOST spec set
|
|
281
|
+
* `task_progress` always, plus the children's content events — `text_delta`/`text_end`/
|
|
282
|
+
* `reasoning_delta`/`tool_start`/`tool_end`, UNTRUSTED-RAW: the consumer must redact — when the HOST spec set
|
|
283
283
|
* `forwardSubagentEvents: true`) — threaded via `startWorkflow` into every spawned agent's trusted
|
|
284
284
|
* internals so a workflow child's events bubble to the deployment's one sink, the same
|
|
285
285
|
* channel a `createSubagentTool` delegation threads. Display-only; absent ⇒ ticks stay in each
|
|
@@ -153,6 +153,45 @@ export interface WorkflowRun {
|
|
|
153
153
|
* contract (store status filters, terminal checks like `isTerminalWorkflowStatus`, monitor rows all
|
|
154
154
|
* switch on the closed enum — a new enum value would break them; an optional field cannot). */
|
|
155
155
|
agentFailures?: number;
|
|
156
|
+
/** The run's TERMINAL token-budget overshoot — present ONLY when the run actually spent MORE than its
|
|
157
|
+
* ceiling (`spentTokens + unsettledTokens > budgetTokens`), absent on every run that stayed within it and
|
|
158
|
+
* on every run that set no budget at all. Same ADDITIVE-observation contract as {@link agentFailures}
|
|
159
|
+
* (never a gate input, never re-read by the engine, and the status enums stay untouched).
|
|
160
|
+
*
|
|
161
|
+
* WHY IT CAN EXIST AT ALL: the budget is a per-CALL ADMISSION gate — it refuses NEW `ctx.agent` calls
|
|
162
|
+
* once settled live spend reaches the ceiling, and agents already IN FLIGHT at that moment are not bound
|
|
163
|
+
* by it (the {@link WorkflowBudgetExceededError} message promises exactly that: "In-flight agents will
|
|
164
|
+
* complete"). So a wide concurrency window can land far past the ceiling while the script's body still
|
|
165
|
+
* returns normally — a run that read as an unqualified `completed` with no trace of the overrun anywhere
|
|
166
|
+
* on its record. This seat is that trace; the same fact is also narrated on the run's log lane.
|
|
167
|
+
*
|
|
168
|
+
* `budgetTokens` is the configured ceiling and `spentTokens` is the gate's OWN input — LIVE settled spend
|
|
169
|
+
* (`ctx.budget.spent()`) at the terminal, which is NOT the same figure as `stats.tokens +
|
|
170
|
+
* stats.nested.tokens` (run stats also count REPLAYED work, which the budget deliberately never charges).
|
|
171
|
+
*
|
|
172
|
+
* `unsettledTokens` (present only when non-zero) is spend OBSERVED on agents still IN FLIGHT at the
|
|
173
|
+
* terminal — a fire-and-forget `agentStream`, or one a deadline abandoned. Nothing downstream ever
|
|
174
|
+
* accounts for it (a settle landing after the run finalized is dropped by design), so it is reported
|
|
175
|
+
* here rather than silently lost: without it this seat would answer "no overshoot" for a run that in
|
|
176
|
+
* fact burned several times its ceiling. It is kept SEPARATE from `spentTokens` — the own/nested
|
|
177
|
+
* discipline {@link WorkflowRunStats} uses — because the two have different standing: settled spend the
|
|
178
|
+
* gate itself read, versus a best-known observation the gate never charged. **A consumer measuring the
|
|
179
|
+
* overshoot adds them** (`spentTokens + unsettledTokens - budgetTokens`).
|
|
180
|
+
*
|
|
181
|
+
* ⚠️ `unsettledTokens` is an ESTIMATE, stated rather than implied — the SAME live per-turn figure the
|
|
182
|
+
* running-agent observation surfaces already show (`stats` while an agent runs), with the same standing:
|
|
183
|
+
* · it omits work those agents DELEGATED (that reaches this engine only through `TaskResult.stats.nested`
|
|
184
|
+
* at their settle, which for an agent still in flight at the terminal never arrives), and
|
|
185
|
+
* · it is the beat's own per-turn arithmetic (cache-inclusive input + output), which APPROXIMATES the
|
|
186
|
+
* authoritative per-call figure a settle would have written rather than reproducing it.
|
|
187
|
+
* So a run whose overshoot rests ENTIRELY on this member is a best-effort disclosure, not a measurement:
|
|
188
|
+
* read it as "this run appears to have overrun, and here is what was seen". `spentTokens` carries no such
|
|
189
|
+
* caveat — it is the gate's own settled figure. */
|
|
190
|
+
budgetOvershoot?: {
|
|
191
|
+
budgetTokens: number;
|
|
192
|
+
spentTokens: number;
|
|
193
|
+
unsettledTokens?: number;
|
|
194
|
+
};
|
|
156
195
|
phases: WorkflowPhase[];
|
|
157
196
|
agents: WorkflowAgentRun[];
|
|
158
197
|
/** design/97 CORE-3: nested `ctx.workflow` sub-groups (the persisted group tree). Empty when the script
|
|
@@ -372,9 +411,17 @@ export declare class WorkflowMaxAgentsError extends Error {
|
|
|
372
411
|
/** The run's token ceiling AT THE MOMENT THE CAP FIRED, or `null` when the run set no budget. It selects
|
|
373
412
|
* which cause the message may name — the two are mutually exclusive facts, not one fixed label. */
|
|
374
413
|
readonly budgetTotal: number | null;
|
|
414
|
+
/** LIVE spend at the moment the cap fired, when the caller knows it. It selects the THIRD arm below:
|
|
415
|
+
* a cap that fires while the token ceiling is ALREADY overshot must not send the reader off to raise
|
|
416
|
+
* `maxAgents` (raising it buys more overshoot, not less). Absent ⇒ the two historic arms only. */
|
|
417
|
+
readonly spentTokens?: number | undefined;
|
|
375
418
|
readonly code = "workflow.max_agents";
|
|
376
419
|
constructor(max: number,
|
|
377
420
|
/** The run's token ceiling AT THE MOMENT THE CAP FIRED, or `null` when the run set no budget. It selects
|
|
378
421
|
* which cause the message may name — the two are mutually exclusive facts, not one fixed label. */
|
|
379
|
-
budgetTotal?: number | null
|
|
422
|
+
budgetTotal?: number | null,
|
|
423
|
+
/** LIVE spend at the moment the cap fired, when the caller knows it. It selects the THIRD arm below:
|
|
424
|
+
* a cap that fires while the token ceiling is ALREADY overshot must not send the reader off to raise
|
|
425
|
+
* `maxAgents` (raising it buys more overshoot, not less). Absent ⇒ the two historic arms only. */
|
|
426
|
+
spentTokens?: number | undefined);
|
|
380
427
|
}
|
|
@@ -58,17 +58,25 @@ export class WorkflowAgentBlockedError extends Error {
|
|
|
58
58
|
export class WorkflowMaxAgentsError extends Error {
|
|
59
59
|
max;
|
|
60
60
|
budgetTotal;
|
|
61
|
+
spentTokens;
|
|
61
62
|
code = "workflow.max_agents";
|
|
62
|
-
constructor(max, budgetTotal = null) {
|
|
63
|
+
constructor(max, budgetTotal = null, spentTokens) {
|
|
63
64
|
super(budgetTotal === null
|
|
64
65
|
? `Workflow agent() call cap reached (${max}). This usually means a loop using budget.remaining() never ` +
|
|
65
66
|
`terminates because no token budget was set — remaining() returns Infinity when budget.total is null. ` +
|
|
66
67
|
`Add a hard iteration cap to the loop, or pass a token budget.`
|
|
67
|
-
:
|
|
68
|
-
`
|
|
69
|
-
|
|
68
|
+
: spentTokens !== undefined && spentTokens > budgetTotal
|
|
69
|
+
? `Workflow agent() call cap reached (${max}), and the token budget is ALREADY EXCEEDED ` +
|
|
70
|
+
`(${spentTokens.toLocaleString()} spent / ${budgetTotal.toLocaleString()} output tokens): agents already in flight when ` +
|
|
71
|
+
`the ceiling was reached are not bound by the per-call gate, so their spend landed on top of it. BOTH bounds are ` +
|
|
72
|
+
`binding — raising maxAgents alone would only buy more overshoot. Fan out over fewer items, or lower concurrency ` +
|
|
73
|
+
`(it bounds the overshoot) and raise the token budget deliberately.`
|
|
74
|
+
: `Workflow agent() call cap reached (${max}). A token budget IS set (${budgetTotal.toLocaleString()} output tokens), ` +
|
|
75
|
+
`so this is the CALL-COUNT cap, not the token ceiling: the script asked for more than ${max} agent() calls. ` +
|
|
76
|
+
`Fan out over fewer items, or raise maxAgents.`);
|
|
70
77
|
this.max = max;
|
|
71
78
|
this.budgetTotal = budgetTotal;
|
|
79
|
+
this.spentTokens = spentTokens;
|
|
72
80
|
this.name = "WorkflowMaxAgentsError";
|
|
73
81
|
}
|
|
74
82
|
}
|
|
@@ -201,8 +201,19 @@ export interface WorkflowAgentHandle {
|
|
|
201
201
|
* live is parked and delivered into the NEXT turn's context (birth-window delivery, bounded — ledger item 36); steers are
|
|
202
202
|
* delivered in call order, the birth window included. Rejects with `steering.not_running` once the task has
|
|
203
203
|
* finished (teardown included).
|
|
204
|
+
*
|
|
205
|
+
* `opts.inputId` is a PASS-THROUGH of the underlying `TaskStream.steer` correlation/idempotency key
|
|
206
|
+
* (design/171 §6.3 — its whole contract, value domain and typed refusals are that verb's; absent ⇒ byte-identical
|
|
207
|
+
* to every pre-existing call). A launcher whose own ingress already minted a message id passes it here so the two
|
|
208
|
+
* legs of one steering ingress are equally replay-safe. ⚠️ ONE arm of that contract is NOT reachable through this
|
|
209
|
+
* handle, deliberately: the idempotent-REPLAY arm ("same id, same payload ⇒ injects nothing"). Each call mints a
|
|
210
|
+
* FRESH unguessable correlation marker into the framing it delivers, so a retry under the same id is a
|
|
211
|
+
* same-id-DIFFERENT-instruction call and refuses typed `steering.duplicate_input_id`. What the key buys on this
|
|
212
|
+
* lane is therefore AT-MOST-ONCE: a retried ingress is refused LOUDLY instead of injecting a second copy.
|
|
204
213
|
*/
|
|
205
|
-
steer(content: string
|
|
214
|
+
steer(content: string, opts?: {
|
|
215
|
+
inputId?: string;
|
|
216
|
+
}): Promise<string>;
|
|
206
217
|
/** Await the agent's {@link TaskResult} (the same value the eager recording used; idempotent). */
|
|
207
218
|
result(): Promise<TaskResult>;
|
|
208
219
|
}
|
|
@@ -384,8 +395,8 @@ export interface RunWorkflowOptions {
|
|
|
384
395
|
parentCenterArtifactDigest?: string;
|
|
385
396
|
parentCenterSourceRevision?: string;
|
|
386
397
|
/** The launching run's display sink (`RunInternals.onForwardEvent` behind the runner's ctx wrapper:
|
|
387
|
-
* `task_progress` always, PLUS the children's content events — `text_delta`/`
|
|
388
|
-
* `tool_start`/`tool_end`, UNTRUSTED-RAW: the consumer must redact — when the HOST spec set
|
|
398
|
+
* `task_progress` always, PLUS the children's content events — `text_delta`/`text_end`/
|
|
399
|
+
* `reasoning_delta`/`tool_start`/`tool_end`, UNTRUSTED-RAW: the consumer must redact — when the HOST spec set
|
|
389
400
|
* `forwardSubagentEvents: true`) — threaded into every spawned agent's trusted internals so the
|
|
390
401
|
* children's events bubble out of their isolated streams to the deployment's one sink (fleet
|
|
391
402
|
* footer/monitor rows). Display-only; absent ⇒ ticks stay in each child's own stream. */
|