@sema-agent/core 5.6.0 → 5.8.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 +55 -0
- package/dist/agents/cascade.d.ts +1 -1
- package/dist/agents/subagent.d.ts +4 -7
- package/dist/agents/subagent.js +10 -19
- package/dist/agents/teacher.js +3 -3
- package/dist/agents/team.d.ts +1 -1
- package/dist/agents/team.js +1 -1
- package/dist/agents/verify.js +1 -1
- package/dist/brain/anthropic.js +24 -35
- package/dist/brain/openai.js +22 -33
- package/dist/brain/stream-engine.d.ts +0 -2
- package/dist/brain/stream-engine.js +5 -47
- package/dist/brain/stream-shared.d.ts +0 -10
- package/dist/brain/stream-shared.js +0 -23
- package/dist/brain/terminal-cause.d.ts +0 -1
- package/dist/brain/terminal-cause.js +0 -3
- package/dist/brain/timeout.d.ts +4 -2
- package/dist/brain/timeout.js +4 -14
- package/dist/config/catalog.d.ts +3 -11
- package/dist/config/catalog.js +30 -69
- package/dist/config/defaults.d.ts +0 -3
- package/dist/config/defaults.js +0 -3
- package/dist/core/auto-compaction.d.ts +2 -11
- package/dist/core/auto-compaction.js +3 -67
- package/dist/core/checkpoint-store.d.ts +15 -10
- package/dist/core/checkpoint-store.js +13 -10
- package/dist/core/memory-engine/file-backend.js +20 -17
- package/dist/core/memory-engine/layout.d.ts +5 -0
- package/dist/core/memory-engine/layout.js +72 -47
- package/dist/core/runner/assemble-result.d.ts +2 -8
- package/dist/core/runner/assemble-result.js +15 -16
- package/dist/core/runner/prepare-task.d.ts +26 -6
- package/dist/core/runner/prepare-task.js +203 -141
- package/dist/core/runner/runtask.d.ts +0 -2
- package/dist/core/runner/runtask.js +267 -253
- package/dist/core/secret-env.d.ts +2 -0
- package/dist/core/secret-env.js +16 -4
- package/dist/core/session-policy-store.d.ts +5 -0
- package/dist/core/session-policy-store.js +3 -1
- package/dist/core/store-contracts/checkpoint-store-contract.js +2 -2
- package/dist/core/task-notification.d.ts +1 -0
- package/dist/core/task-notification.js +3 -0
- package/dist/core/task-registry-agent.d.ts +2 -0
- package/dist/core/task-registry-agent.js +33 -1
- package/dist/core/task-registry.d.ts +2 -0
- package/dist/core/task-registry.js +9 -1
- package/dist/core/tool-errors.js +10 -2
- package/dist/core/trace.d.ts +1 -8
- package/dist/core/types.d.ts +24 -30
- package/dist/core/usage-window-store.d.ts +39 -0
- package/dist/core/usage-window-store.js +115 -0
- package/dist/engine/execution-env/node-execution-env.js +0 -22
- package/dist/engine/harness/agent-harness.d.ts +0 -7
- package/dist/engine/harness/agent-harness.js +13 -53
- package/dist/engine/harness/types.d.ts +2 -15
- package/dist/engine/llm/types.d.ts +1 -2
- package/dist/engine/loop/agent-loop.d.ts +1 -1
- package/dist/engine/loop/agent-loop.js +5 -171
- package/dist/engine/loop/types.d.ts +0 -18
- package/dist/index.d.ts +10 -5
- package/dist/index.js +8 -4
- package/dist/orchestration/goal.js +1 -5
- package/dist/orchestration/run-workflow-tool.d.ts +1 -1
- package/dist/orchestration/run-workflow-tool.js +1 -2
- package/dist/orchestration/workflow-governance.d.ts +6 -6
- package/dist/orchestration/workflow-governance.js +33 -47
- package/dist/prompt-assembly/event-registry.js +2 -3
- package/dist/stores/cc/lockfile.js +9 -11
- package/dist/stores/cc/mailbox-store.js +49 -38
- package/dist/stores/file/index.d.ts +3 -0
- package/dist/stores/file/index.js +4 -0
- package/dist/stores/file/usage-window-store.d.ts +9 -0
- package/dist/stores/file/usage-window-store.js +86 -0
- package/dist/tools/fs/fs-bash.d.ts +1 -10
- package/dist/tools/fs/fs-bash.js +20 -60
- package/dist/tools/fs/index.d.ts +0 -2
- package/dist/tools/fs/index.js +1 -2
- package/package.json +1 -4
- package/dist/bin/sema-tb.d.ts +0 -31
- package/dist/bin/sema-tb.js +0 -448
- package/dist/bin/tb-env.d.ts +0 -2
- package/dist/bin/tb-env.js +0 -17
- package/dist/brain/walltime.d.ts +0 -1
- package/dist/brain/walltime.js +0 -1
- package/dist/core/runner/call-cap.d.ts +0 -67
- package/dist/core/runner/call-cap.js +0 -145
- package/dist/core/runner/cut-kill.d.ts +0 -10
- package/dist/core/runner/cut-kill.js +0 -37
|
@@ -3,7 +3,6 @@ import { BrainError, classifyHttp, describeNetworkError } from "./errors.js";
|
|
|
3
3
|
import { retryBackoffMs } from "./retry.js";
|
|
4
4
|
import { emitBrainStatus, emitBrainTelemetry } from "./status-sink.js";
|
|
5
5
|
import { createConnectController, resolveStallTimeoutMs } from "./timeout.js";
|
|
6
|
-
import { WALLTIME_CUTOFF_MESSAGE } from "./walltime.js";
|
|
7
6
|
const DEFAULT_MAX_RETRIES = 10;
|
|
8
7
|
const MAX_RETRIES_ENV_CEILING = 15;
|
|
9
8
|
const MAX_RETRIES_ENV = "SEMA_MAX_RETRIES";
|
|
@@ -43,9 +42,6 @@ function emptyAssistant(model) {
|
|
|
43
42
|
function isAbortError(err) {
|
|
44
43
|
return err instanceof Error && (err.name === "AbortError" || err.name === "TimeoutError");
|
|
45
44
|
}
|
|
46
|
-
function isWalltimeCutoff(err) {
|
|
47
|
-
return err instanceof Error && err.message === WALLTIME_CUTOFF_MESSAGE;
|
|
48
|
-
}
|
|
49
45
|
function sleep(ms, signal) {
|
|
50
46
|
return new Promise((resolve) => {
|
|
51
47
|
if (signal?.aborted)
|
|
@@ -78,7 +74,7 @@ async function sleepAnnouncingRetry(totalMs, signal, frame) {
|
|
|
78
74
|
}
|
|
79
75
|
}
|
|
80
76
|
export function runStreamingBrain(args) {
|
|
81
|
-
const { model, doFetch, signal, config, httpLabel, buildRequest, makeParser,
|
|
77
|
+
const { model, doFetch, signal, config, httpLabel, buildRequest, makeParser, stallTimeouts } = args;
|
|
82
78
|
const out = createAssistantMessageEventStream();
|
|
83
79
|
let cleanup;
|
|
84
80
|
let announcedRetry = false;
|
|
@@ -88,13 +84,10 @@ export function runStreamingBrain(args) {
|
|
|
88
84
|
.catch((err) => {
|
|
89
85
|
const aborted = signal?.aborted === true || isAbortError(err);
|
|
90
86
|
terminalRetryPhase = "gave_up";
|
|
91
|
-
|
|
92
|
-
terminalRetryDetail = aborted ? "cancelled while retrying" : walltimeCut ? "wall-clock deadline reached while retrying" : "retries exhausted";
|
|
87
|
+
terminalRetryDetail = aborted ? "cancelled while retrying" : "retries exhausted";
|
|
93
88
|
const errorMsg = emptyAssistant(model);
|
|
94
89
|
errorMsg.stopReason = aborted ? "aborted" : "error";
|
|
95
90
|
errorMsg.errorMessage = err instanceof Error ? err.message : String(err);
|
|
96
|
-
if (walltimeCut)
|
|
97
|
-
errorMsg.errorKind = "walltime_cutoff";
|
|
98
91
|
errorMsg.usageMissing = true;
|
|
99
92
|
out.push({ type: "error", reason: aborted ? "aborted" : "error", error: errorMsg });
|
|
100
93
|
})
|
|
@@ -128,20 +121,13 @@ export function runStreamingBrain(args) {
|
|
|
128
121
|
e.name = "AbortError";
|
|
129
122
|
throw e;
|
|
130
123
|
}
|
|
131
|
-
|
|
132
|
-
throw new Error(WALLTIME_CUTOFF_MESSAGE);
|
|
133
|
-
}
|
|
134
|
-
const cc = createConnectController(connectTimeoutMs, signal, callDeadlineMs);
|
|
124
|
+
const cc = createConnectController(connectTimeoutMs, signal);
|
|
135
125
|
let r;
|
|
136
126
|
let netErr;
|
|
137
127
|
try {
|
|
138
128
|
r = await doFetch(req.url, { method: "POST", headers: req.headers, body: req.body, signal: cc.signal });
|
|
139
129
|
}
|
|
140
130
|
catch (e) {
|
|
141
|
-
if (cc.deadlineHit()) {
|
|
142
|
-
cc.dispose();
|
|
143
|
-
throw new Error(WALLTIME_CUTOFF_MESSAGE);
|
|
144
|
-
}
|
|
145
131
|
if (cc.timedOut()) {
|
|
146
132
|
netErr = new Error(`connect timeout after ${connectTimeoutMs}ms`);
|
|
147
133
|
}
|
|
@@ -153,10 +139,6 @@ export function runStreamingBrain(args) {
|
|
|
153
139
|
netErr = e;
|
|
154
140
|
}
|
|
155
141
|
}
|
|
156
|
-
if (r && cc.deadlineHit()) {
|
|
157
|
-
cc.dispose();
|
|
158
|
-
throw new Error(WALLTIME_CUTOFF_MESSAGE);
|
|
159
|
-
}
|
|
160
142
|
if (r && r.ok && r.body) {
|
|
161
143
|
cc.clearTimer();
|
|
162
144
|
connect = cc;
|
|
@@ -168,13 +150,7 @@ export function runStreamingBrain(args) {
|
|
|
168
150
|
const retryable = netErr !== undefined ||
|
|
169
151
|
(r !== undefined && (r.status >= 500 || r.status === 429 || r.status === 408 || r.status === 409));
|
|
170
152
|
if (retryable && attempt < maxRetries) {
|
|
171
|
-
if (callDeadlineMs !== undefined && Date.now() >= callDeadlineMs) {
|
|
172
|
-
throw new Error(WALLTIME_CUTOFF_MESSAGE);
|
|
173
|
-
}
|
|
174
153
|
const delayMs = retryBackoffMs(baseDelay, attempt, r);
|
|
175
|
-
if (callDeadlineMs !== undefined && Date.now() + delayMs >= callDeadlineMs) {
|
|
176
|
-
throw new Error(WALLTIME_CUTOFF_MESSAGE);
|
|
177
|
-
}
|
|
178
154
|
const statusPhase = r?.status === 429 ? "rate_limited" : netErr !== undefined ? "reconnecting" : "retrying";
|
|
179
155
|
emitBrainTelemetry({ kind: "retry", attempt: attempt + 1, phase: "connect" });
|
|
180
156
|
announcedRetry = true;
|
|
@@ -228,8 +204,6 @@ export function runStreamingBrain(args) {
|
|
|
228
204
|
void reader.cancel().catch(() => undefined);
|
|
229
205
|
}, idleTimeoutMs);
|
|
230
206
|
};
|
|
231
|
-
let dlTimer;
|
|
232
|
-
let deadlineCut = false;
|
|
233
207
|
const sawContentToken = () => {
|
|
234
208
|
bumpIdle();
|
|
235
209
|
if (firstTokenSeen)
|
|
@@ -250,10 +224,6 @@ export function runStreamingBrain(args) {
|
|
|
250
224
|
clearTimeout(idleTimer);
|
|
251
225
|
idleTimer = undefined;
|
|
252
226
|
}
|
|
253
|
-
if (dlTimer) {
|
|
254
|
-
clearTimeout(dlTimer);
|
|
255
|
-
dlTimer = undefined;
|
|
256
|
-
}
|
|
257
227
|
connect?.dispose();
|
|
258
228
|
void reader.cancel().catch(() => undefined);
|
|
259
229
|
};
|
|
@@ -273,14 +243,6 @@ export function runStreamingBrain(args) {
|
|
|
273
243
|
sawContentToken,
|
|
274
244
|
cancel: () => void reader.cancel().catch(() => undefined),
|
|
275
245
|
});
|
|
276
|
-
if (callDeadlineMs !== undefined && parser.onDeadline) {
|
|
277
|
-
dlTimer = setTimeout(() => {
|
|
278
|
-
dlTimer = undefined;
|
|
279
|
-
deadlineCut = parser.onDeadline();
|
|
280
|
-
void reader.cancel().catch(() => undefined);
|
|
281
|
-
}, Math.max(0, callDeadlineMs - Date.now()));
|
|
282
|
-
dlTimer.unref?.();
|
|
283
|
-
}
|
|
284
246
|
const INBAND_OVERLOAD_WARNING = "API overloaded mid-response. The response above may be incomplete.";
|
|
285
247
|
const onLineOverloadTierA = (line) => {
|
|
286
248
|
try {
|
|
@@ -334,14 +296,10 @@ export function runStreamingBrain(args) {
|
|
|
334
296
|
clearTimeout(idleTimer);
|
|
335
297
|
idleTimer = undefined;
|
|
336
298
|
}
|
|
337
|
-
if (
|
|
338
|
-
clearTimeout(dlTimer);
|
|
339
|
-
dlTimer = undefined;
|
|
340
|
-
}
|
|
341
|
-
if (!failure && firstTokenTimedOut && !deadlineCut) {
|
|
299
|
+
if (!failure && firstTokenTimedOut) {
|
|
342
300
|
failure = { kind: "stalled", err: new BrainError("network", `first-token timeout after ${firstTokenTimeoutMs}ms`) };
|
|
343
301
|
}
|
|
344
|
-
if (!failure && idleTimedOut
|
|
302
|
+
if (!failure && idleTimedOut) {
|
|
345
303
|
failure = { kind: "stalled", err: new BrainError("network", `stream idle timeout after ${idleTimeoutMs}ms`) };
|
|
346
304
|
}
|
|
347
305
|
if (!failure) {
|
|
@@ -6,13 +6,3 @@ export interface RepetitionPoll {
|
|
|
6
6
|
readonly spared: RepetitionEvent[];
|
|
7
7
|
}
|
|
8
8
|
export declare function createRepetitionPoll(): RepetitionPoll;
|
|
9
|
-
export interface WalltimeGate {
|
|
10
|
-
onDeadline(): boolean;
|
|
11
|
-
shouldDropLine(): boolean;
|
|
12
|
-
readonly cut: boolean;
|
|
13
|
-
}
|
|
14
|
-
export declare function createWalltimeGate(deps: {
|
|
15
|
-
callDeadlineMs: number | undefined;
|
|
16
|
-
isDegenerate: () => boolean;
|
|
17
|
-
cancel: () => void;
|
|
18
|
-
}): WalltimeGate;
|
|
@@ -39,26 +39,3 @@ export function createRepetitionPoll() {
|
|
|
39
39
|
},
|
|
40
40
|
};
|
|
41
41
|
}
|
|
42
|
-
export function createWalltimeGate(deps) {
|
|
43
|
-
const { callDeadlineMs, isDegenerate, cancel } = deps;
|
|
44
|
-
let walltimeCut = false;
|
|
45
|
-
return {
|
|
46
|
-
get cut() {
|
|
47
|
-
return walltimeCut;
|
|
48
|
-
},
|
|
49
|
-
onDeadline() {
|
|
50
|
-
if (isDegenerate())
|
|
51
|
-
return false;
|
|
52
|
-
walltimeCut = true;
|
|
53
|
-
return true;
|
|
54
|
-
},
|
|
55
|
-
shouldDropLine() {
|
|
56
|
-
if (callDeadlineMs !== undefined && !walltimeCut && !isDegenerate() && Date.now() >= callDeadlineMs) {
|
|
57
|
-
walltimeCut = true;
|
|
58
|
-
cancel();
|
|
59
|
-
return true;
|
|
60
|
-
}
|
|
61
|
-
return walltimeCut;
|
|
62
|
-
},
|
|
63
|
-
};
|
|
64
|
-
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
1
|
import type { AssistantMessage } from "../internal/llm.js";
|
|
2
2
|
export type TerminalCauseCarrier = Pick<AssistantMessage, "errorKind">;
|
|
3
3
|
export declare function isDegenerateCutMessage(message: TerminalCauseCarrier): boolean;
|
|
4
|
-
export declare function isWalltimeCutMessage(message: TerminalCauseCarrier): boolean;
|
package/dist/brain/timeout.d.ts
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import type { StreamFn } from "../internal/llm.js";
|
|
2
2
|
export declare function resolveStallTimeoutMs(value: number | undefined, knob: string): number | undefined;
|
|
3
|
+
export declare const STALL_CONNECT_MS = 30000;
|
|
4
|
+
export declare const STALL_FIRST_TOKEN_MS = 120000;
|
|
5
|
+
export declare const STALL_IDLE_MS = 90000;
|
|
3
6
|
export interface BrainTimeoutConfig {
|
|
4
7
|
connectTimeoutMs?: number;
|
|
5
8
|
firstTokenTimeoutMs?: number;
|
|
6
9
|
idleTimeoutMs?: number;
|
|
7
10
|
}
|
|
8
|
-
export declare function createConnectController(connectTimeoutMs: number | undefined, outerSignal: AbortSignal | undefined
|
|
11
|
+
export declare function createConnectController(connectTimeoutMs: number | undefined, outerSignal: AbortSignal | undefined): {
|
|
9
12
|
signal: AbortSignal;
|
|
10
13
|
timedOut: () => boolean;
|
|
11
|
-
deadlineHit: () => boolean;
|
|
12
14
|
clearTimer: () => void;
|
|
13
15
|
dispose: () => void;
|
|
14
16
|
};
|
package/dist/brain/timeout.js
CHANGED
|
@@ -8,10 +8,12 @@ export function resolveStallTimeoutMs(value, knob) {
|
|
|
8
8
|
}
|
|
9
9
|
return value;
|
|
10
10
|
}
|
|
11
|
-
export
|
|
11
|
+
export const STALL_CONNECT_MS = 30_000;
|
|
12
|
+
export const STALL_FIRST_TOKEN_MS = 120_000;
|
|
13
|
+
export const STALL_IDLE_MS = 90_000;
|
|
14
|
+
export function createConnectController(connectTimeoutMs, outerSignal) {
|
|
12
15
|
const ac = new AbortController();
|
|
13
16
|
let timedOut = false;
|
|
14
|
-
let deadlineHit = false;
|
|
15
17
|
const onAbort = () => ac.abort(outerSignal?.reason);
|
|
16
18
|
if (outerSignal) {
|
|
17
19
|
if (outerSignal.aborted)
|
|
@@ -26,26 +28,14 @@ export function createConnectController(connectTimeoutMs, outerSignal, deadlineA
|
|
|
26
28
|
ac.abort();
|
|
27
29
|
}, connectTimeoutMs);
|
|
28
30
|
}
|
|
29
|
-
let deadlineTimer;
|
|
30
|
-
if (deadlineAtMs !== undefined) {
|
|
31
|
-
deadlineTimer = setTimeout(() => {
|
|
32
|
-
deadlineHit = true;
|
|
33
|
-
ac.abort();
|
|
34
|
-
}, Math.max(0, deadlineAtMs - Date.now()));
|
|
35
|
-
deadlineTimer.unref?.();
|
|
36
|
-
}
|
|
37
31
|
const clearTimer = () => {
|
|
38
32
|
if (timer)
|
|
39
33
|
clearTimeout(timer);
|
|
40
34
|
timer = undefined;
|
|
41
|
-
if (deadlineTimer)
|
|
42
|
-
clearTimeout(deadlineTimer);
|
|
43
|
-
deadlineTimer = undefined;
|
|
44
35
|
};
|
|
45
36
|
return {
|
|
46
37
|
signal: ac.signal,
|
|
47
38
|
timedOut: () => timedOut,
|
|
48
|
-
deadlineHit: () => deadlineHit,
|
|
49
39
|
clearTimer,
|
|
50
40
|
dispose: () => {
|
|
51
41
|
clearTimer();
|
package/dist/config/catalog.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { TaskLimits } from "../core/types.js";
|
|
1
2
|
export declare const CONFIG_CATALOG_VERSION = "sema-config@1";
|
|
2
3
|
export interface ConfigKnob {
|
|
3
4
|
key: string;
|
|
@@ -24,18 +25,9 @@ export interface EffectiveConfigField {
|
|
|
24
25
|
}
|
|
25
26
|
export declare const MAX_OVERRIDE_DECLARATIONS = 32;
|
|
26
27
|
export declare function resolveEffectiveConfig(spec: {
|
|
27
|
-
limits?:
|
|
28
|
-
maxTurns?: number;
|
|
29
|
-
timeoutSec?: number;
|
|
30
|
-
maxOutputTokens?: number;
|
|
31
|
-
deadlineNudge?: false | object;
|
|
32
|
-
callCapByDeadline?: false;
|
|
33
|
-
gracefulFinalize?: false;
|
|
34
|
-
};
|
|
35
|
-
maxCostUsd?: number;
|
|
36
|
-
maxTokens?: number;
|
|
28
|
+
limits?: Pick<TaskLimits, "maxTurns" | "maxWalltimeMs" | "maxOutputTokens" | "maxCostUsd" | "maxTokens">;
|
|
37
29
|
resourceSuspend?: {
|
|
38
|
-
|
|
30
|
+
totalTokens?: number;
|
|
39
31
|
totalBudgetUsd?: number;
|
|
40
32
|
};
|
|
41
33
|
configOverrides?: readonly ConfigOverrideDeclaration[];
|
package/dist/config/catalog.js
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { RETAIN_DEFAULT_TTL_MS, RETAIN_DEFAULT_MAX, SESSION_DEFAULT_TTL_DAYS } from "./defaults.js";
|
|
2
2
|
export const CONFIG_CATALOG_VERSION = "sema-config@1";
|
|
3
3
|
export function describeConfigCatalog() {
|
|
4
4
|
const knobs = [
|
|
5
5
|
{
|
|
6
6
|
key: "limits.maxTurns",
|
|
7
|
-
recommended:
|
|
7
|
+
recommended: null,
|
|
8
8
|
unit: "turns",
|
|
9
|
-
kind: "
|
|
10
|
-
summary: "
|
|
11
|
-
precedence: "spec.limits.maxTurns >
|
|
9
|
+
kind: "budget",
|
|
10
|
+
summary: "Per-slice turn ceiling. Unset = unbounded (design/164 retired the engine-side net): a task that needs a turn bound states one. 0 = the explicit unbounded sentinel.",
|
|
11
|
+
precedence: "spec.limits.maxTurns > unset",
|
|
12
12
|
},
|
|
13
13
|
{
|
|
14
|
-
key: "limits.
|
|
14
|
+
key: "limits.maxWalltimeMs",
|
|
15
15
|
recommended: null,
|
|
16
|
-
unit: "
|
|
16
|
+
unit: "ms",
|
|
17
17
|
kind: "budget",
|
|
18
|
-
summary: "
|
|
19
|
-
precedence: "spec.limits.
|
|
18
|
+
summary: "Per-slice ACTIVE wall-clock window for the task (suspended time does not count; a resume opens a fresh window). Unset = unbounded, which is the recommendation — bound work by tokens, not by time.",
|
|
19
|
+
precedence: "spec.limits.maxWalltimeMs > unset",
|
|
20
20
|
},
|
|
21
21
|
{
|
|
22
22
|
key: "limits.maxOutputTokens",
|
|
@@ -27,65 +27,35 @@ export function describeConfigCatalog() {
|
|
|
27
27
|
precedence: "spec.limits.maxOutputTokens > model.maxTokens > provider default",
|
|
28
28
|
},
|
|
29
29
|
{
|
|
30
|
-
key: "limits.
|
|
31
|
-
recommended: true,
|
|
32
|
-
kind: "behavior",
|
|
33
|
-
summary: "Two one-shot convergence reminders (~80%/~95% of timeoutSec). Auto-ON when timeoutSec is set; no effect without it.",
|
|
34
|
-
precedence: "spec.limits.deadlineNudge > derived(timeoutSec set)",
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
key: "limits.callCapByDeadline",
|
|
38
|
-
recommended: true,
|
|
39
|
-
kind: "behavior",
|
|
40
|
-
summary: "Shrink each call's max_tokens to what remaining wall-clock can absorb (shrink-only). Auto-ON when timeoutSec is set.",
|
|
41
|
-
precedence: "spec.limits.callCapByDeadline > derived(timeoutSec set)",
|
|
42
|
-
},
|
|
43
|
-
{
|
|
44
|
-
key: "limits.gracefulFinalize",
|
|
45
|
-
recommended: true,
|
|
46
|
-
kind: "behavior",
|
|
47
|
-
summary: "One-shot finalize turn + write-out cushion + soft execution deadline when remaining time no longer fits a full cycle. Auto-ON when timeoutSec is set.",
|
|
48
|
-
precedence: "spec.limits.gracefulFinalize > derived(timeoutSec set)",
|
|
49
|
-
},
|
|
50
|
-
{
|
|
51
|
-
key: "budget.maxCostUsd",
|
|
30
|
+
key: "limits.maxCostUsd",
|
|
52
31
|
recommended: null,
|
|
53
32
|
unit: "usd",
|
|
54
33
|
kind: "budget",
|
|
55
|
-
summary: "
|
|
56
|
-
precedence: "spec.maxCostUsd > unset",
|
|
34
|
+
summary: "Per-slice spend ceiling (hard-cancels the in-flight stream by default when crossed). Unset = unbounded.",
|
|
35
|
+
precedence: "spec.limits.maxCostUsd > unset",
|
|
57
36
|
},
|
|
58
37
|
{
|
|
59
|
-
key: "
|
|
38
|
+
key: "limits.maxTokens",
|
|
60
39
|
recommended: null,
|
|
61
40
|
unit: "tokens",
|
|
62
41
|
kind: "budget",
|
|
63
|
-
summary: "
|
|
64
|
-
precedence: "spec.maxTokens > unset",
|
|
42
|
+
summary: "Per-slice token budget — the PRIMARY axis. Unset = unbounded.",
|
|
43
|
+
precedence: "spec.limits.maxTokens > unset",
|
|
65
44
|
},
|
|
66
45
|
{
|
|
67
|
-
key: "
|
|
46
|
+
key: "limits.approachNotice",
|
|
68
47
|
recommended: null,
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
precedence: "spec.resources.totalWalltimeSec (first slice) > unset",
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
key: "delegation.forkMaxTurns",
|
|
76
|
-
recommended: FORK_DEFAULT_MAX_TURNS,
|
|
77
|
-
unit: "turns",
|
|
78
|
-
kind: "safety-net",
|
|
79
|
-
summary: "Default turn cap for the Agent fork arm (child inherits the parent's world; tighter net than the global default).",
|
|
80
|
-
precedence: "agent definition limits > fork default",
|
|
48
|
+
kind: "behavior",
|
|
49
|
+
summary: "Advisory limit-approach reminders (one at ~80% of the tightest armed axis, one at ~95%). Unset = ON whenever an axis is armed; false = off; { at } overrides the thresholds.",
|
|
50
|
+
precedence: "spec.limits.approachNotice > on-when-any-axis-armed",
|
|
81
51
|
},
|
|
82
52
|
{
|
|
83
|
-
key: "
|
|
84
|
-
recommended:
|
|
85
|
-
unit: "
|
|
86
|
-
kind: "
|
|
87
|
-
summary: "
|
|
88
|
-
precedence: "
|
|
53
|
+
key: "budget.totalTokens",
|
|
54
|
+
recommended: null,
|
|
55
|
+
unit: "tokens",
|
|
56
|
+
kind: "budget",
|
|
57
|
+
summary: "Cross-slice token allocation for resource-suspend tasks. First slice only; later slices read the frozen ledger. Unset = per-slice windows only.",
|
|
58
|
+
precedence: "spec.resourceSuspend.totalTokens (first slice) > unset",
|
|
89
59
|
},
|
|
90
60
|
{
|
|
91
61
|
key: "delegation.retainTtlMs",
|
|
@@ -119,28 +89,19 @@ const MAX_DECLARATION_CHARS = 200;
|
|
|
119
89
|
const sanitizeDeclarationText = (s) => s.replace(/[\u0000-\u0008\u000b-\u001f\u007f]/g, "").slice(0, MAX_DECLARATION_CHARS);
|
|
120
90
|
export function resolveEffectiveConfig(spec, ctx) {
|
|
121
91
|
const l = spec.limits;
|
|
122
|
-
const hasDeadline = l?.timeoutSec !== undefined;
|
|
123
92
|
const pick = (key, specVal, dflt) => specVal !== undefined ? { key, value: specVal, provenance: "spec" } : { key, value: dflt, provenance: "default" };
|
|
124
|
-
const deadlineSwitch = (key, v) => v === false
|
|
125
|
-
? { key, value: false, provenance: "spec" }
|
|
126
|
-
: v !== undefined
|
|
127
|
-
? { key, value: hasDeadline, provenance: "spec" }
|
|
128
|
-
: { key, value: hasDeadline, provenance: "derived" };
|
|
129
93
|
const maxOutputTokens = l?.maxOutputTokens !== undefined
|
|
130
94
|
? { key: "limits.maxOutputTokens", value: l.maxOutputTokens, provenance: "spec" }
|
|
131
95
|
: ctx?.modelMaxTokens !== undefined
|
|
132
96
|
? { key: "limits.maxOutputTokens", value: ctx.modelMaxTokens, provenance: "derived" }
|
|
133
97
|
: { key: "limits.maxOutputTokens", value: null, provenance: "default" };
|
|
134
98
|
const fields = [
|
|
135
|
-
pick("limits.maxTurns", l?.maxTurns,
|
|
136
|
-
pick("limits.
|
|
99
|
+
pick("limits.maxTurns", l?.maxTurns, null),
|
|
100
|
+
pick("limits.maxWalltimeMs", l?.maxWalltimeMs, null),
|
|
137
101
|
maxOutputTokens,
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
pick("budget.maxCostUsd", spec.maxCostUsd, null),
|
|
142
|
-
pick("budget.maxTokens", spec.maxTokens, null),
|
|
143
|
-
pick("budget.totalWalltimeSec", spec.resourceSuspend?.totalWalltimeSec, null),
|
|
102
|
+
pick("limits.maxCostUsd", l?.maxCostUsd, null),
|
|
103
|
+
pick("limits.maxTokens", l?.maxTokens, null),
|
|
104
|
+
pick("budget.totalTokens", spec.resourceSuspend?.totalTokens, null),
|
|
144
105
|
pick("budget.totalBudgetUsd", spec.resourceSuspend?.totalBudgetUsd, null),
|
|
145
106
|
];
|
|
146
107
|
for (const d of (spec.configOverrides ?? []).slice(0, MAX_OVERRIDE_DECLARATIONS)) {
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
export declare const DEFAULT_MAX_TURNS = 1000;
|
|
2
|
-
export declare const FORK_DEFAULT_MAX_TURNS = 200;
|
|
3
|
-
export declare const SESSION_BG_DEFAULT_TIMEOUT_SEC: number;
|
|
4
1
|
export declare const RETAIN_DEFAULT_TTL_MS: number;
|
|
5
2
|
export declare const RETAIN_DEFAULT_MAX = 16;
|
|
6
3
|
export declare const SESSION_DEFAULT_TTL_DAYS = 7;
|
package/dist/config/defaults.js
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
export const DEFAULT_MAX_TURNS = 1000;
|
|
2
|
-
export const FORK_DEFAULT_MAX_TURNS = 200;
|
|
3
|
-
export const SESSION_BG_DEFAULT_TIMEOUT_SEC = 30 * 60;
|
|
4
1
|
export const RETAIN_DEFAULT_TTL_MS = 30 * 60 * 1000;
|
|
5
2
|
export const RETAIN_DEFAULT_MAX = 16;
|
|
6
3
|
export const SESSION_DEFAULT_TTL_DAYS = 7;
|
|
@@ -6,16 +6,14 @@ import type { Brain } from "./types.js";
|
|
|
6
6
|
export declare const DEFAULT_COMPACTION_INSTRUCTIONS: string;
|
|
7
7
|
export declare const COMPACTION_INSTRUCTIONS_MAX_CHARS = 2048;
|
|
8
8
|
export declare function sanitizeCompactionInstructions(text: string): string;
|
|
9
|
-
export declare const COMPACTION_THROUGHPUT_PRIOR_TOKENS_PER_MS = 3;
|
|
10
9
|
export type CompactionPhaseDurations = Omit<Extract<TraceEvent, {
|
|
11
10
|
kind: "compaction.phase_timings";
|
|
12
11
|
}>, "kind" | "version" | "taskId" | "ts" | "durationMs">;
|
|
13
|
-
export declare function isCompactionWalltimeAbort(err: unknown): boolean;
|
|
14
12
|
export declare function isCompactionManualCancel(err: unknown): boolean;
|
|
15
13
|
export interface CompactionWindowSafetyInfo {
|
|
16
14
|
kind: "fallback" | "clamp_disclosure";
|
|
17
15
|
truncationRatio: number;
|
|
18
|
-
reason?: "budget" | "
|
|
16
|
+
reason?: "budget" | "tolerance";
|
|
19
17
|
contentTokens: number;
|
|
20
18
|
headroomTokens?: number;
|
|
21
19
|
estCostMicroUsd?: number;
|
|
@@ -51,13 +49,6 @@ export interface MaybeCompactOptions {
|
|
|
51
49
|
capMicroUsd: number;
|
|
52
50
|
mainInputPer1M?: number;
|
|
53
51
|
};
|
|
54
|
-
walltime?: {
|
|
55
|
-
deadlineMs: number;
|
|
56
|
-
cushionMs: () => number;
|
|
57
|
-
throughputRef: {
|
|
58
|
-
ewmaTokensPerMs?: number;
|
|
59
|
-
};
|
|
60
|
-
};
|
|
61
52
|
onWindowSafety?: (info: CompactionWindowSafetyInfo) => void;
|
|
62
53
|
minTokens?: number;
|
|
63
54
|
force?: boolean;
|
|
@@ -131,7 +122,7 @@ export declare function maybeCompact(opts: MaybeCompactOptions): Promise<{
|
|
|
131
122
|
modelFallback?: true;
|
|
132
123
|
fallbackReason?: "window";
|
|
133
124
|
clampedRatio?: number;
|
|
134
|
-
clampReason?: "budget" | "
|
|
125
|
+
clampReason?: "budget" | "tolerance";
|
|
135
126
|
}>;
|
|
136
127
|
export declare function nextTrimForceBackoff(prev: boolean, comp: {
|
|
137
128
|
compacted: boolean;
|
|
@@ -22,13 +22,7 @@ const WORKING_FILE_BREAKOUT_TAGS = ["working-file", "summary"];
|
|
|
22
22
|
function escapeWorkingFilePathAttr(path) {
|
|
23
23
|
return path.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/[\r\n]+/g, " ");
|
|
24
24
|
}
|
|
25
|
-
export const COMPACTION_THROUGHPUT_PRIOR_TOKENS_PER_MS = 3;
|
|
26
|
-
const COMPACTION_THROUGHPUT_EWMA_ALPHA = 0.3;
|
|
27
|
-
const COMPACTION_WALLTIME_ABORT_FLAG = "semaCompactionWalltimeAbort";
|
|
28
25
|
const COMPACTION_MANUAL_CANCEL_FLAG = "semaCompactionManualCancel";
|
|
29
|
-
export function isCompactionWalltimeAbort(err) {
|
|
30
|
-
return (typeof err === "object" && err !== null && err[COMPACTION_WALLTIME_ABORT_FLAG] === true);
|
|
31
|
-
}
|
|
32
26
|
export function isCompactionManualCancel(err) {
|
|
33
27
|
return (typeof err === "object" && err !== null && err[COMPACTION_MANUAL_CANCEL_FLAG] === true);
|
|
34
28
|
}
|
|
@@ -180,7 +174,7 @@ export async function maybeCompact(opts) {
|
|
|
180
174
|
const wantsWindowSafety = opts.compactionModel !== undefined &&
|
|
181
175
|
summarizationCharsCapacity(opts.compactionModel) < summarizationCharsCapacity(opts.model);
|
|
182
176
|
let dry;
|
|
183
|
-
if (wantsWindowSafety
|
|
177
|
+
if (wantsWindowSafety) {
|
|
184
178
|
dry = dryRunSummarizationClamp(prep.value, summaryModel, effectiveInstructions);
|
|
185
179
|
}
|
|
186
180
|
const contentTokens = dry !== undefined ? Math.ceil((dry.conversationChars + (prep.value.previousSummary?.length ?? 0)) / cpt) : 0;
|
|
@@ -201,13 +195,6 @@ export async function maybeCompact(opts) {
|
|
|
201
195
|
gate = "budget";
|
|
202
196
|
}
|
|
203
197
|
}
|
|
204
|
-
if (gate === undefined && opts.walltime !== undefined) {
|
|
205
|
-
const remainingMs = opts.walltime.deadlineMs - opts.walltime.cushionMs() - Date.now();
|
|
206
|
-
const throughput = opts.walltime.throughputRef.ewmaTokensPerMs ?? COMPACTION_THROUGHPUT_PRIOR_TOKENS_PER_MS;
|
|
207
|
-
if (remainingMs < contentTokens / Math.max(throughput, 1e-6)) {
|
|
208
|
-
gate = "walltime";
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
198
|
if (gate === undefined) {
|
|
212
199
|
summaryModel = opts.model;
|
|
213
200
|
modelFallback = true;
|
|
@@ -245,35 +232,7 @@ export async function maybeCompact(opts) {
|
|
|
245
232
|
}
|
|
246
233
|
}
|
|
247
234
|
const auth = await opts.getApiKeyAndHeaders?.(summaryModel);
|
|
248
|
-
|
|
249
|
-
let softTimer;
|
|
250
|
-
let softAbort;
|
|
251
|
-
let onOuterAbort;
|
|
252
|
-
let firstAbortCause;
|
|
253
|
-
const latchCallerCause = () => {
|
|
254
|
-
firstAbortCause ??= opts.manualCancelSignal?.aborted === true ? "manual" : "caller";
|
|
255
|
-
};
|
|
256
|
-
if (opts.walltime !== undefined) {
|
|
257
|
-
softAbort = new AbortController();
|
|
258
|
-
onOuterAbort = () => {
|
|
259
|
-
latchCallerCause();
|
|
260
|
-
softAbort?.abort();
|
|
261
|
-
};
|
|
262
|
-
if (opts.signal?.aborted) {
|
|
263
|
-
latchCallerCause();
|
|
264
|
-
softAbort.abort();
|
|
265
|
-
}
|
|
266
|
-
else if (opts.signal) {
|
|
267
|
-
opts.signal.addEventListener("abort", onOuterAbort, { once: true });
|
|
268
|
-
}
|
|
269
|
-
const softMs = opts.walltime.deadlineMs - opts.walltime.cushionMs() - Date.now();
|
|
270
|
-
softTimer = setTimeout(() => {
|
|
271
|
-
firstAbortCause ??= "walltime";
|
|
272
|
-
softAbort?.abort();
|
|
273
|
-
}, Math.max(0, softMs));
|
|
274
|
-
softTimer.unref?.();
|
|
275
|
-
summarySignal = softAbort.signal;
|
|
276
|
-
}
|
|
235
|
+
const summarySignal = opts.signal;
|
|
277
236
|
const runtime = brainToRuntime(opts.brain);
|
|
278
237
|
let res;
|
|
279
238
|
summaryStartAt = Date.now();
|
|
@@ -284,23 +243,9 @@ export async function maybeCompact(opts) {
|
|
|
284
243
|
}
|
|
285
244
|
finally {
|
|
286
245
|
summaryEndAt = Date.now();
|
|
287
|
-
if (softTimer !== undefined)
|
|
288
|
-
clearTimeout(softTimer);
|
|
289
|
-
if (onOuterAbort !== undefined)
|
|
290
|
-
opts.signal?.removeEventListener("abort", onOuterAbort);
|
|
291
246
|
}
|
|
292
247
|
if (!res.ok) {
|
|
293
|
-
|
|
294
|
-
softAbort.signal.aborted &&
|
|
295
|
-
(firstAbortCause !== undefined ? firstAbortCause === "walltime" : opts.signal?.aborted !== true);
|
|
296
|
-
if (walltimeWon) {
|
|
297
|
-
if (typeof res.error !== "object" || res.error === null) {
|
|
298
|
-
throw Object.assign(new Error(String(res.error)), { [COMPACTION_WALLTIME_ABORT_FLAG]: true });
|
|
299
|
-
}
|
|
300
|
-
res.error[COMPACTION_WALLTIME_ABORT_FLAG] = true;
|
|
301
|
-
}
|
|
302
|
-
if (!walltimeWon &&
|
|
303
|
-
opts.manualCancelSignal?.aborted === true &&
|
|
248
|
+
if (opts.manualCancelSignal?.aborted === true &&
|
|
304
249
|
res.error instanceof CompactionError &&
|
|
305
250
|
res.error.code === "aborted") {
|
|
306
251
|
res.error[COMPACTION_MANUAL_CANCEL_FLAG] = true;
|
|
@@ -315,15 +260,6 @@ export async function maybeCompact(opts) {
|
|
|
315
260
|
throw new Error(`compaction produced an oversized summary (≈${Math.ceil(summary.length / cpt)} tokens vs ` +
|
|
316
261
|
`${res.value.tokensBefore} before compaction; history left untouched)`);
|
|
317
262
|
}
|
|
318
|
-
if (opts.walltime !== undefined && contentTokens > 0) {
|
|
319
|
-
const passMs = Math.max(1, Date.now() - compactStartAt);
|
|
320
|
-
const sample = contentTokens / passMs;
|
|
321
|
-
const prev = opts.walltime.throughputRef.ewmaTokensPerMs;
|
|
322
|
-
opts.walltime.throughputRef.ewmaTokensPerMs =
|
|
323
|
-
prev === undefined
|
|
324
|
-
? sample
|
|
325
|
-
: COMPACTION_THROUGHPUT_EWMA_ALPHA * sample + (1 - COMPACTION_THROUGHPUT_EWMA_ALPHA) * prev;
|
|
326
|
-
}
|
|
327
263
|
firstKeptEntryId = res.value.firstKeptEntryId;
|
|
328
264
|
summaryTokensBefore = res.value.tokensBefore;
|
|
329
265
|
details = res.value.details;
|