@sema-agent/core 5.7.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.
Files changed (84) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/dist/agents/cascade.d.ts +1 -1
  3. package/dist/agents/subagent.d.ts +4 -7
  4. package/dist/agents/subagent.js +10 -19
  5. package/dist/agents/teacher.js +3 -3
  6. package/dist/agents/team.d.ts +1 -1
  7. package/dist/agents/team.js +1 -1
  8. package/dist/agents/verify.js +1 -1
  9. package/dist/brain/anthropic.js +24 -35
  10. package/dist/brain/openai.js +22 -33
  11. package/dist/brain/stream-engine.d.ts +0 -2
  12. package/dist/brain/stream-engine.js +5 -47
  13. package/dist/brain/stream-shared.d.ts +0 -10
  14. package/dist/brain/stream-shared.js +0 -23
  15. package/dist/brain/terminal-cause.d.ts +0 -1
  16. package/dist/brain/terminal-cause.js +0 -3
  17. package/dist/brain/timeout.d.ts +4 -2
  18. package/dist/brain/timeout.js +4 -14
  19. package/dist/config/catalog.d.ts +3 -11
  20. package/dist/config/catalog.js +30 -69
  21. package/dist/config/defaults.d.ts +0 -3
  22. package/dist/config/defaults.js +0 -3
  23. package/dist/core/auto-compaction.d.ts +2 -11
  24. package/dist/core/auto-compaction.js +3 -67
  25. package/dist/core/checkpoint-store.d.ts +10 -8
  26. package/dist/core/checkpoint-store.js +7 -7
  27. package/dist/core/memory-engine/file-backend.js +20 -17
  28. package/dist/core/memory-engine/layout.d.ts +5 -0
  29. package/dist/core/memory-engine/layout.js +72 -47
  30. package/dist/core/runner/assemble-result.d.ts +2 -8
  31. package/dist/core/runner/assemble-result.js +15 -16
  32. package/dist/core/runner/prepare-task.d.ts +26 -6
  33. package/dist/core/runner/prepare-task.js +203 -141
  34. package/dist/core/runner/runtask.d.ts +0 -2
  35. package/dist/core/runner/runtask.js +200 -244
  36. package/dist/core/session-policy-store.d.ts +5 -0
  37. package/dist/core/session-policy-store.js +3 -1
  38. package/dist/core/store-contracts/checkpoint-store-contract.js +2 -2
  39. package/dist/core/task-registry-agent.d.ts +2 -0
  40. package/dist/core/task-registry-agent.js +33 -1
  41. package/dist/core/task-registry.d.ts +2 -0
  42. package/dist/core/task-registry.js +9 -1
  43. package/dist/core/tool-errors.js +10 -2
  44. package/dist/core/trace.d.ts +1 -8
  45. package/dist/core/types.d.ts +24 -30
  46. package/dist/core/usage-window-store.d.ts +39 -0
  47. package/dist/core/usage-window-store.js +115 -0
  48. package/dist/engine/execution-env/node-execution-env.js +0 -22
  49. package/dist/engine/harness/agent-harness.d.ts +0 -7
  50. package/dist/engine/harness/agent-harness.js +13 -53
  51. package/dist/engine/harness/types.d.ts +2 -15
  52. package/dist/engine/llm/types.d.ts +1 -2
  53. package/dist/engine/loop/agent-loop.d.ts +1 -1
  54. package/dist/engine/loop/agent-loop.js +5 -171
  55. package/dist/engine/loop/types.d.ts +0 -18
  56. package/dist/index.d.ts +10 -5
  57. package/dist/index.js +8 -4
  58. package/dist/orchestration/goal.js +1 -5
  59. package/dist/orchestration/run-workflow-tool.d.ts +1 -1
  60. package/dist/orchestration/run-workflow-tool.js +1 -2
  61. package/dist/orchestration/workflow-governance.d.ts +6 -6
  62. package/dist/orchestration/workflow-governance.js +33 -47
  63. package/dist/prompt-assembly/event-registry.js +2 -3
  64. package/dist/stores/cc/lockfile.js +9 -11
  65. package/dist/stores/cc/mailbox-store.js +49 -38
  66. package/dist/stores/file/index.d.ts +3 -0
  67. package/dist/stores/file/index.js +4 -0
  68. package/dist/stores/file/usage-window-store.d.ts +9 -0
  69. package/dist/stores/file/usage-window-store.js +86 -0
  70. package/dist/tools/fs/fs-bash.d.ts +1 -10
  71. package/dist/tools/fs/fs-bash.js +20 -60
  72. package/dist/tools/fs/index.d.ts +0 -2
  73. package/dist/tools/fs/index.js +1 -2
  74. package/package.json +1 -4
  75. package/dist/bin/sema-tb.d.ts +0 -31
  76. package/dist/bin/sema-tb.js +0 -448
  77. package/dist/bin/tb-env.d.ts +0 -2
  78. package/dist/bin/tb-env.js +0 -17
  79. package/dist/brain/walltime.d.ts +0 -1
  80. package/dist/brain/walltime.js +0 -1
  81. package/dist/core/runner/call-cap.d.ts +0 -67
  82. package/dist/core/runner/call-cap.js +0 -145
  83. package/dist/core/runner/cut-kill.d.ts +0 -10
  84. package/dist/core/runner/cut-kill.js +0 -37
@@ -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;
@@ -1,6 +1,3 @@
1
1
  export function isDegenerateCutMessage(message) {
2
2
  return message.errorKind === "degenerate";
3
3
  }
4
- export function isWalltimeCutMessage(message) {
5
- return message.errorKind === "walltime_cutoff";
6
- }
@@ -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, deadlineAtMs?: number): {
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
  };
@@ -8,10 +8,12 @@ export function resolveStallTimeoutMs(value, knob) {
8
8
  }
9
9
  return value;
10
10
  }
11
- export function createConnectController(connectTimeoutMs, outerSignal, deadlineAtMs) {
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();
@@ -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
- totalWalltimeSec?: number;
30
+ totalTokens?: number;
39
31
  totalBudgetUsd?: number;
40
32
  };
41
33
  configOverrides?: readonly ConfigOverrideDeclaration[];
@@ -1,22 +1,22 @@
1
- import { DEFAULT_MAX_TURNS, FORK_DEFAULT_MAX_TURNS, SESSION_BG_DEFAULT_TIMEOUT_SEC, RETAIN_DEFAULT_TTL_MS, RETAIN_DEFAULT_MAX, SESSION_DEFAULT_TTL_DAYS } from "./defaults.js";
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: DEFAULT_MAX_TURNS,
7
+ recommended: null,
8
8
  unit: "turns",
9
- kind: "safety-net",
10
- summary: "Runaway net, NOT the long-run knob: catches a wedged tool-call loop before it burns budget. A genuinely long task sets its own explicit bound. 0 = unbounded.",
11
- precedence: "spec.limits.maxTurns > engine default",
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.timeoutSec",
14
+ key: "limits.maxWalltimeMs",
15
15
  recommended: null,
16
- unit: "seconds",
16
+ unit: "ms",
17
17
  kind: "budget",
18
- summary: "Hard wall-clock budget for the task. Unset = unbounded. Setting it arms the deadline family (nudges, call caps, graceful finalize) by default.",
19
- precedence: "spec.limits.timeoutSec > unset",
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.deadlineNudge",
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: "Cumulative spend cap for the task (hard-cancels the in-flight stream by default when crossed). Unset = unbounded.",
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: "budget.maxTokens",
38
+ key: "limits.maxTokens",
60
39
  recommended: null,
61
40
  unit: "tokens",
62
41
  kind: "budget",
63
- summary: "Cumulative token budget for the task. Unset = unbounded.",
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: "budget.totalWalltimeSec",
46
+ key: "limits.approachNotice",
68
47
  recommended: null,
69
- unit: "seconds",
70
- kind: "budget",
71
- summary: "Cross-slice wall-clock allocation for resource-suspend tasks (RB-21). First slice only; later slices read the frozen ledger. Unset = per-slice budgets only.",
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: "delegation.sessionBgTimeoutSec",
84
- recommended: SESSION_BG_DEFAULT_TIMEOUT_SEC,
85
- unit: "seconds",
86
- kind: "safety-net",
87
- summary: "Default wall-clock budget for a session-scoped background child.",
88
- precedence: "spawn args > default",
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, DEFAULT_MAX_TURNS),
136
- pick("limits.timeoutSec", l?.timeoutSec, null),
99
+ pick("limits.maxTurns", l?.maxTurns, null),
100
+ pick("limits.maxWalltimeMs", l?.maxWalltimeMs, null),
137
101
  maxOutputTokens,
138
- deadlineSwitch("limits.deadlineNudge", l?.deadlineNudge),
139
- deadlineSwitch("limits.callCapByDeadline", l?.callCapByDeadline),
140
- deadlineSwitch("limits.gracefulFinalize", l?.gracefulFinalize),
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;
@@ -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" | "walltime" | "tolerance";
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" | "walltime" | "tolerance";
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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").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 || opts.walltime !== undefined) {
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
- let summarySignal = opts.signal;
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
- const walltimeWon = softAbort !== undefined &&
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;
@@ -10,7 +10,8 @@ export type CheckpointToken = string & {
10
10
  readonly __brand: "CheckpointToken";
11
11
  };
12
12
  export declare function mintCheckpointToken(): CheckpointToken;
13
- export type ResourceLimitReason = "budget" | "walltime" | "turns" | "preempt";
13
+ export type ResourceLimitReason = "budget_tokens" | "budget_cost" | "walltime" | "turns" | "preempt" | "env_lifetime" | "usage_window";
14
+ export type PlatformLimitReason = Extract<ResourceLimitReason, "env_lifetime" | "usage_window">;
14
15
  export interface SafetyAxis {
15
16
  egress?: boolean;
16
17
  irreversible?: boolean;
@@ -53,6 +54,7 @@ export type CheckpointGate = {
53
54
  } | {
54
55
  kind: "resource_limit";
55
56
  reason: ResourceLimitReason;
57
+ resumeAfterMs?: number;
56
58
  } | {
57
59
  kind: "needs_review";
58
60
  reason: string;
@@ -153,12 +155,12 @@ export type SerializedCheckpointState = {
153
155
  export declare const CURRENT_CHECKPOINT_VERSION = 1;
154
156
  export declare const RESOURCE_CHECKPOINT_VERSION = 2;
155
157
  export declare const BINDING_CHECKPOINT_VERSION = 3;
156
- export declare const WALLTIME_CHECKPOINT_VERSION = 4;
157
- export declare const MAX_SUPPORTED_CHECKPOINT_VERSION = 4;
158
+ export declare const TOKEN_CHECKPOINT_VERSION = 5;
159
+ export declare const MAX_SUPPORTED_CHECKPOINT_VERSION = 5;
158
160
  export declare function checkpointVersionOf(cp: Pick<Checkpoint, "version">): number;
159
161
  export interface ResourceLedger {
160
162
  totalBudgetMicroUsd?: number;
161
- totalWalltimeSec?: number;
163
+ totalTokens?: number;
162
164
  spentMicroUsd: number;
163
165
  spentTokens: number;
164
166
  spentTurns: number;
@@ -172,12 +174,12 @@ export declare function debitLedger(prior: ResourceLedger | undefined, slice: {
172
174
  walltimeMs?: number;
173
175
  }, total?: {
174
176
  totalBudgetMicroUsd?: number;
175
- totalWalltimeSec?: number;
177
+ totalTokens?: number;
176
178
  }, opts?: {
177
179
  countSlice?: boolean;
178
180
  }): ResourceLedger;
179
181
  export declare function remainingBudgetMicroUsd(ledger: ResourceLedger | undefined): number | undefined;
180
- export declare function remainingWalltimeMs(ledger: ResourceLedger | undefined): number | undefined;
182
+ export declare function remainingTokens(ledger: ResourceLedger | undefined): number | undefined;
181
183
  export interface ResolvedOutcome {
182
184
  boundCallId: string;
183
185
  decision: "allow" | "deny" | "approve" | "reject" | "edit";
@@ -242,11 +244,11 @@ export interface CheckpointSummary {
242
244
  }
243
245
  export declare function summarizeCheckpoint(cp: Checkpoint): CheckpointSummary;
244
246
  export declare class CheckpointError extends Error {
245
- readonly code: "checkpoint.already_exists" | "checkpoint.already_resolved" | "checkpoint.not_found" | "checkpoint.gate_mismatch" | "checkpoint.resume_aborted" | "checkpoint.invalid_outcome" | "checkpoint.unsupported_version" | "checkpoint.reopen_revote" | "checkpoint.reopened_concurrently" | "checkpoint.reopen_failed" | "steering.invalid_content" | "wake.gate_pending" | "wake.nothing_to_deliver" | "resume.parent_constraint_missing" | "resume.parent_constraint_mismatch";
247
+ readonly code: "checkpoint.already_exists" | "checkpoint.already_resolved" | "checkpoint.not_found" | "checkpoint.gate_mismatch" | "checkpoint.resume_aborted" | "checkpoint.invalid_outcome" | "checkpoint.unsupported_version" | "checkpoint.walltime_axis_retired" | "checkpoint.reopen_revote" | "checkpoint.reopened_concurrently" | "checkpoint.reopen_failed" | "steering.invalid_content" | "wake.gate_pending" | "wake.nothing_to_deliver" | "resume.parent_constraint_missing" | "resume.parent_constraint_mismatch";
246
248
  readonly detail?: {
247
249
  field?: "boundCallId" | "boundInputHash" | "answer";
248
250
  } | undefined;
249
- constructor(code: "checkpoint.already_exists" | "checkpoint.already_resolved" | "checkpoint.not_found" | "checkpoint.gate_mismatch" | "checkpoint.resume_aborted" | "checkpoint.invalid_outcome" | "checkpoint.unsupported_version" | "checkpoint.reopen_revote" | "checkpoint.reopened_concurrently" | "checkpoint.reopen_failed" | "steering.invalid_content" | "wake.gate_pending" | "wake.nothing_to_deliver" | "resume.parent_constraint_missing" | "resume.parent_constraint_mismatch", message: string, detail?: {
251
+ constructor(code: "checkpoint.already_exists" | "checkpoint.already_resolved" | "checkpoint.not_found" | "checkpoint.gate_mismatch" | "checkpoint.resume_aborted" | "checkpoint.invalid_outcome" | "checkpoint.unsupported_version" | "checkpoint.walltime_axis_retired" | "checkpoint.reopen_revote" | "checkpoint.reopened_concurrently" | "checkpoint.reopen_failed" | "steering.invalid_content" | "wake.gate_pending" | "wake.nothing_to_deliver" | "resume.parent_constraint_missing" | "resume.parent_constraint_mismatch", message: string, detail?: {
250
252
  field?: "boundCallId" | "boundInputHash" | "answer";
251
253
  } | undefined);
252
254
  }
@@ -126,8 +126,8 @@ export function buildRiskDescriptor(input) {
126
126
  export const CURRENT_CHECKPOINT_VERSION = 1;
127
127
  export const RESOURCE_CHECKPOINT_VERSION = 2;
128
128
  export const BINDING_CHECKPOINT_VERSION = 3;
129
- export const WALLTIME_CHECKPOINT_VERSION = 4;
130
- export const MAX_SUPPORTED_CHECKPOINT_VERSION = 4;
129
+ export const TOKEN_CHECKPOINT_VERSION = 5;
130
+ export const MAX_SUPPORTED_CHECKPOINT_VERSION = 5;
131
131
  export function checkpointVersionOf(cp) {
132
132
  return cp.version ?? 0;
133
133
  }
@@ -140,7 +140,7 @@ function finiteSpend(v) {
140
140
  export function debitLedger(prior, slice, total, opts) {
141
141
  return {
142
142
  totalBudgetMicroUsd: finiteBound(prior?.totalBudgetMicroUsd) ?? finiteBound(total?.totalBudgetMicroUsd),
143
- totalWalltimeSec: finiteBound(prior?.totalWalltimeSec) ?? finiteBound(total?.totalWalltimeSec),
143
+ totalTokens: finiteBound(prior?.totalTokens) ?? finiteBound(total?.totalTokens),
144
144
  spentMicroUsd: (finiteSpend(prior?.spentMicroUsd) ?? 0) + Math.max(0, finiteSpend(slice.costMicroUsd) ?? 0),
145
145
  spentTokens: (finiteSpend(prior?.spentTokens) ?? 0) + Math.max(0, finiteSpend(slice.tokens) ?? 0),
146
146
  spentTurns: (finiteSpend(prior?.spentTurns) ?? 0) + Math.max(0, finiteSpend(slice.turns) ?? 0),
@@ -157,14 +157,14 @@ export function remainingBudgetMicroUsd(ledger) {
157
157
  return 0;
158
158
  return Math.max(0, total - (spent ?? 0));
159
159
  }
160
- export function remainingWalltimeMs(ledger) {
161
- const total = ledger?.totalWalltimeSec;
160
+ export function remainingTokens(ledger) {
161
+ const total = ledger?.totalTokens;
162
162
  if (total === undefined || !Number.isFinite(total))
163
163
  return undefined;
164
- const spent = ledger?.spentWalltimeMs;
164
+ const spent = ledger?.spentTokens;
165
165
  if (spent !== undefined && !Number.isFinite(spent))
166
166
  return 0;
167
- return Math.max(0, total * 1000 - (spent ?? 0));
167
+ return Math.max(0, total - (spent ?? 0));
168
168
  }
169
169
  export function summarizeCheckpoint(cp) {
170
170
  const riskDescriptor = cp.gate.kind === "human" || cp.gate.kind === "irreversible_ask" ? cp.gate.riskDescriptor : undefined;
@@ -487,28 +487,31 @@ export class FileMemoryEngineBackend {
487
487
  mtimeMs = statSync(lockDir).mtimeMs;
488
488
  }
489
489
  catch {
490
- continue;
490
+ mtimeMs = undefined;
491
491
  }
492
- const staleAt = mtimeMs + staleMs;
493
- deadline = Math.max(deadline, staleAt + stealGraceMs);
494
- if (Date.now() > staleAt) {
495
- let tokenPresent = false;
496
- try {
497
- statSync(ownerPath);
498
- tokenPresent = true;
499
- }
500
- catch {
501
- }
502
- if (tokenPresent) {
503
- const tomb = join(this.controlPlaneRoot, `txn.lock.stale-${randomUUID()}`);
492
+ if (mtimeMs !== undefined) {
493
+ const staleAt = mtimeMs + staleMs;
494
+ deadline = Math.max(deadline, staleAt + stealGraceMs);
495
+ if (Date.now() > staleAt) {
496
+ let tokenPresent = false;
504
497
  try {
505
- renameSync(lockDir, tomb);
498
+ statSync(ownerPath);
499
+ tokenPresent = true;
506
500
  }
507
501
  catch {
508
- continue;
509
502
  }
510
- rmSync(tomb, { recursive: true, force: true });
511
- continue;
503
+ if (tokenPresent) {
504
+ const tomb = join(this.controlPlaneRoot, `txn.lock.stale-${randomUUID()}`);
505
+ let stolen = false;
506
+ try {
507
+ renameSync(lockDir, tomb);
508
+ stolen = true;
509
+ }
510
+ catch {
511
+ }
512
+ if (stolen)
513
+ rmSync(tomb, { recursive: true, force: true });
514
+ }
512
515
  }
513
516
  }
514
517
  if (Date.now() > deadline) {
@@ -41,6 +41,11 @@ export declare const ANNOUNCEMENTS_FILE = "announcements.json";
41
41
  export declare const MEMORY_ANNOUNCEMENTS_MAX = 20;
42
42
  export declare const SCAN_FUSE_FILE = "scan-fuse.json";
43
43
  export declare const SCAN_FUSE_THRESHOLD = 3;
44
+ export interface SidecarLockOptions {
45
+ onDeadline?: "proceed" | "throw";
46
+ waitCapMs?: number;
47
+ }
48
+ export declare function acquireSidecarLock(lockDir: string, opts?: SidecarLockOptions): string;
44
49
  interface AnnouncementsRecord {
45
50
  folded: number;
46
51
  queue: MemoryAnnouncement[];