@sema-agent/core 3.0.0 → 5.0.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 (153) hide show
  1. package/CHANGELOG.md +2382 -0
  2. package/dist/agents/cascade.d.ts +3 -1
  3. package/dist/agents/cascade.js +31 -32
  4. package/dist/agents/cumulative-stats.d.ts +29 -0
  5. package/dist/agents/cumulative-stats.js +29 -0
  6. package/dist/agents/observer.d.ts +4 -0
  7. package/dist/agents/observer.js +11 -10
  8. package/dist/agents/repair-loop.d.ts +2 -0
  9. package/dist/agents/repair-loop.js +48 -24
  10. package/dist/agents/retain-ledger.d.ts +2 -0
  11. package/dist/agents/retain-ledger.js +6 -5
  12. package/dist/agents/roster-store.d.ts +1 -0
  13. package/dist/agents/roster-store.js +3 -5
  14. package/dist/agents/send-message-tool.d.ts +1 -0
  15. package/dist/agents/send-message-tool.js +2 -0
  16. package/dist/agents/subagent.d.ts +3 -1
  17. package/dist/agents/subagent.js +79 -94
  18. package/dist/agents/teacher.d.ts +1 -9
  19. package/dist/agents/teacher.js +21 -24
  20. package/dist/agents/tool-filter.js +6 -7
  21. package/dist/agents/verify.d.ts +1 -0
  22. package/dist/agents/verify.js +15 -14
  23. package/dist/brain/anthropic.js +24 -21
  24. package/dist/brain/openai.js +24 -21
  25. package/dist/brain/stream-engine.js +5 -2
  26. package/dist/brain/stream-shared.d.ts +1 -0
  27. package/dist/brain/stream-shared.js +14 -0
  28. package/dist/brain/terminal-cause.d.ts +4 -0
  29. package/dist/brain/terminal-cause.js +12 -0
  30. package/dist/brain/tool-call-id.d.ts +1 -0
  31. package/dist/brain/tool-call-id.js +3 -0
  32. package/dist/brain/tool-call-repair.js +2 -1
  33. package/dist/core/arg-summary.d.ts +21 -1
  34. package/dist/core/arg-summary.js +61 -14
  35. package/dist/core/auto-compaction.d.ts +1 -0
  36. package/dist/core/auto-compaction.js +16 -25
  37. package/dist/core/background-agent-store.d.ts +22 -22
  38. package/dist/core/background-agent-store.js +19 -23
  39. package/dist/core/checkpoint-store.d.ts +6 -13
  40. package/dist/core/checkpoint-store.js +11 -5
  41. package/dist/core/file-snapshot-store.d.ts +8 -0
  42. package/dist/core/file-snapshot-store.js +4 -3
  43. package/dist/core/fs-write-gate-policy.js +2 -3
  44. package/dist/core/hooks.d.ts +4 -1
  45. package/dist/core/hooks.js +8 -18
  46. package/dist/core/lsp.js +4 -3
  47. package/dist/core/mailbox-store.d.ts +7 -10
  48. package/dist/core/mcp.d.ts +7 -10
  49. package/dist/core/mcp.js +7 -9
  50. package/dist/core/memory-engine/engine.d.ts +0 -1
  51. package/dist/core/memory-engine/engine.js +24 -34
  52. package/dist/core/memory-engine/file-backend.d.ts +1 -2
  53. package/dist/core/memory-engine/file-backend.js +25 -36
  54. package/dist/core/memory-engine/layout.d.ts +8 -0
  55. package/dist/core/memory-engine/layout.js +60 -4
  56. package/dist/core/memory-engine/scope-contract.js +9 -3
  57. package/dist/core/memory-engine/types.d.ts +1 -1
  58. package/dist/core/permission-rules.js +2 -3
  59. package/dist/core/runner/active-skill-scope.d.ts +0 -1
  60. package/dist/core/runner/active-skill-scope.js +2 -17
  61. package/dist/core/runner/assemble-result.d.ts +2 -7
  62. package/dist/core/runner/assemble-result.js +3 -4
  63. package/dist/core/runner/compaction-call-options.d.ts +7 -0
  64. package/dist/core/runner/compaction-call-options.js +30 -0
  65. package/dist/core/runner/prepare-task.d.ts +6 -23
  66. package/dist/core/runner/prepare-task.js +36 -61
  67. package/dist/core/runner/runtask.d.ts +1 -0
  68. package/dist/core/runner/runtask.js +84 -172
  69. package/dist/core/runner/session-rule-policy.js +7 -5
  70. package/dist/core/runner/teardown-bounded.d.ts +7 -0
  71. package/dist/core/runner/teardown-bounded.js +36 -0
  72. package/dist/core/runner/tool-disclosure.d.ts +2 -5
  73. package/dist/core/runner/turn-attachments.d.ts +5 -15
  74. package/dist/core/runner/turn-attachments.js +1 -1
  75. package/dist/core/safe-notify.d.ts +17 -0
  76. package/dist/core/safe-notify.js +59 -0
  77. package/dist/core/sensitive-path-policy.js +4 -4
  78. package/dist/core/session-reconcile.js +1 -2
  79. package/dist/core/skill-tool-specifier.js +2 -3
  80. package/dist/core/skills-directory.js +2 -3
  81. package/dist/core/store-contracts/checkpoint-store-contract.js +1 -1
  82. package/dist/core/stub-env.d.ts +2 -6
  83. package/dist/core/surrogate-safe-slice.d.ts +4 -0
  84. package/dist/core/surrogate-safe-slice.js +18 -0
  85. package/dist/core/task-registry-monitor.js +8 -13
  86. package/dist/core/task-registry-shared.d.ts +23 -1
  87. package/dist/core/task-registry-shared.js +16 -0
  88. package/dist/core/task-registry.d.ts +6 -1
  89. package/dist/core/task-registry.js +52 -87
  90. package/dist/core/task-tool-shape.d.ts +0 -2
  91. package/dist/core/task-tool-shape.js +2 -5
  92. package/dist/core/tool-errors.js +5 -4
  93. package/dist/core/tool-name-aliases.d.ts +1 -2
  94. package/dist/core/tool-name-aliases.js +42 -59
  95. package/dist/core/tool-policy.js +21 -17
  96. package/dist/core/tool-result-store.d.ts +1 -0
  97. package/dist/core/tool-result-store.js +17 -1
  98. package/dist/core/trace.d.ts +9 -9
  99. package/dist/core/types.d.ts +15 -12
  100. package/dist/core/untrusted-egress.d.ts +4 -2
  101. package/dist/core/untrusted-egress.js +28 -14
  102. package/dist/core/workflow-journal-store.d.ts +9 -20
  103. package/dist/core/workflow-run-store.d.ts +11 -10
  104. package/dist/core/workflow-run-store.js +22 -16
  105. package/dist/engine/compaction/utils.js +4 -2
  106. package/dist/engine/execution-env/node-execution-env.d.ts +2 -24
  107. package/dist/engine/execution-env/node-execution-env.js +1 -1
  108. package/dist/engine/harness/agent-harness.d.ts +1 -0
  109. package/dist/engine/harness/agent-harness.js +13 -33
  110. package/dist/engine/harness/types.d.ts +29 -67
  111. package/dist/engine/llm/types.d.ts +1 -1
  112. package/dist/engine/loop/agent-loop.js +12 -14
  113. package/dist/engine/loop/types.d.ts +11 -9
  114. package/dist/engine/session/memory-repo.d.ts +2 -6
  115. package/dist/engine/session/session.d.ts +5 -30
  116. package/dist/index.d.ts +6 -3
  117. package/dist/index.js +4 -3
  118. package/dist/internal/harness-types.d.ts +3 -1
  119. package/dist/orchestration/run-spec.js +6 -4
  120. package/dist/orchestration/run-workflow-tool.js +3 -36
  121. package/dist/orchestration/workflow-governance.js +2 -1
  122. package/dist/orchestration/workflow.d.ts +4 -11
  123. package/dist/orchestration/workflow.js +164 -204
  124. package/dist/prompt-assembly/packs/sema-default.js +1 -3
  125. package/dist/prompt-assembly/tool-catalog.d.ts +10 -9
  126. package/dist/prompt-assembly/turn-snapshot.d.ts +2 -5
  127. package/dist/prompts/default.js +1 -3
  128. package/dist/prompts/simple-sections.d.ts +0 -1
  129. package/dist/prompts/simple-sections.js +0 -1
  130. package/dist/stores/file/background-agent-store.d.ts +3 -12
  131. package/dist/stores/file/background-agent-store.js +3 -41
  132. package/dist/stores/file/checkpoint-store.js +6 -6
  133. package/dist/stores/file/mailbox-store.d.ts +2 -6
  134. package/dist/stores/file/session-policy-store.js +15 -1
  135. package/dist/stores/file/session-store.d.ts +2 -6
  136. package/dist/stores/file/workflow-journal-store.d.ts +3 -11
  137. package/dist/stores/file/workflow-run-store.d.ts +3 -7
  138. package/dist/stores/file/workflow-run-store.js +4 -17
  139. package/dist/tools/fs/bash-readonly-classifier.d.ts +1 -0
  140. package/dist/tools/fs/bash-readonly-classifier.js +60 -25
  141. package/dist/tools/fs/fs-bash.js +330 -104
  142. package/dist/tools/fs/fs-pdf.d.ts +2 -12
  143. package/dist/tools/fs/fs-shared.js +2 -2
  144. package/dist/tools/fs/fs-write.js +0 -1
  145. package/dist/tools/fs/notebook.d.ts +2 -5
  146. package/dist/tools/fs/safety.d.ts +3 -0
  147. package/dist/tools/fs/safety.js +43 -14
  148. package/dist/tools/fs/search.js +5 -5
  149. package/dist/tools/monitor.d.ts +1 -0
  150. package/dist/tools/monitor.js +7 -5
  151. package/dist/tools/worktree.d.ts +2 -7
  152. package/dist/tools/worktree.js +3 -1
  153. package/package.json +6 -2
@@ -5,12 +5,12 @@ import { OUTPUT_TOOL_NAME, REPORT_BLOCKED_TOOL_NAME } from "../core/runner/synth
5
5
  import { TOOL_SEARCH_NAME } from "../core/runner/tool-disclosure.js";
6
6
  import { OFFLOAD_TOOL_NAME } from "../core/tool-result-store.js";
7
7
  import { resolveToolSubset, toolNameAllowed } from "./tool-filter.js";
8
- import { canonicalToolName } from "../core/tool-name-aliases.js";
9
8
  import { builtinAgentDefinitions } from "./builtin-agents.js";
10
9
  import { SUBAGENT_PROMPT } from "../prompts/default.js";
11
10
  import { hasSessionFork } from "../core/session.js";
12
11
  import { isDurablePause } from "./suspend-guard.js";
13
12
  import { delimitUntrusted, inlineUntrusted } from "../core/untrusted-text.js";
13
+ import { createSafeNotifier } from "../core/safe-notify.js";
14
14
  import { boundedRedactedSummary } from "../core/untrusted-egress.js";
15
15
  import { uuidv7 } from "../internal/harness.js";
16
16
  import { addWorktree } from "../core/git-worktree-env.js";
@@ -18,6 +18,17 @@ import { shellQuote } from "../tools/fs/search.js";
18
18
  import { BG_AGENT_REAP_STOP_ERROR } from "../core/task-registry.js";
19
19
  import { extractErrorCode } from "../brain/errors.js";
20
20
  import { FORK_DEFAULT_MAX_TURNS, SESSION_BG_DEFAULT_TIMEOUT_SEC, RETAIN_DEFAULT_TTL_MS, RETAIN_DEFAULT_MAX, RUNNING_AGENT_OBSERVE_EVERY_BEATS } from "../config/defaults.js";
21
+ function rollupDelegatedCost(stats, nested) {
22
+ if (stats.costMicroUsd === undefined)
23
+ return undefined;
24
+ if (nested !== undefined && nested.costMicroUsd === undefined)
25
+ return undefined;
26
+ return stats.costMicroUsd + (nested?.costMicroUsd ?? 0);
27
+ }
28
+ function delegatedCostField(stats) {
29
+ const total = rollupDelegatedCost(stats, stats.nested);
30
+ return total !== undefined ? { costMicroUsd: total } : {};
31
+ }
21
32
  export { FORK_DEFAULT_MAX_TURNS, SESSION_BG_DEFAULT_TIMEOUT_SEC, RETAIN_DEFAULT_TTL_MS, RETAIN_DEFAULT_MAX };
22
33
  import { SUBAGENT_RESUME_CAP, SubagentRetainLedger, getOrCreateSessionRetainLedger, ensureSessionReapHook, createResumePrompt, } from "./retain-ledger.js";
23
34
  import { recordRosterSpawn } from "./roster-store.js";
@@ -63,7 +74,6 @@ export function inheritedManifestScopeFor(snapshot) {
63
74
  return frames;
64
75
  }
65
76
  export const DEFAULT_SUBAGENT_TOOL_NAME = "Agent";
66
- export const LEGACY_SUBAGENT_TOOL_NAME = "Task";
67
77
  export const EXTRA_TOOLS_MAX_FACTORY_CALLS_PER_TREE = 64;
68
78
  export const DEFAULT_SUBAGENT_MAX_DEPTH = 3;
69
79
  export const EXTRA_TOOLS_FAILED_NOTE = "note: extraTools evaluation failed — the injected tool set was skipped for this spawn.";
@@ -73,7 +83,7 @@ function countArgLines(v) {
73
83
  }
74
84
  function createToolStatsCounter(delegationToolName) {
75
85
  const t = { readCount: 0, searchCount: 0, bashCount: 0, editFileCount: 0, linesAdded: 0, linesRemoved: 0, otherToolCount: 0 };
76
- const excluded = new Set([delegationToolName, DEFAULT_SUBAGENT_TOOL_NAME, LEGACY_SUBAGENT_TOOL_NAME]);
86
+ const excluded = new Set([delegationToolName, DEFAULT_SUBAGENT_TOOL_NAME]);
77
87
  const recordEditLines = (args) => {
78
88
  if (args === null || typeof args !== "object")
79
89
  return;
@@ -198,7 +208,7 @@ const ccElapsedTag = (ms) => {
198
208
  };
199
209
  const ccCompletionText = (desc, settled, rawStatus, elapsedMs) => `Agent "${desc}" ${settled === "killed" ? "stopped" : settled === "completed" ? "finished" : rawStatus}${ccElapsedTag(elapsedMs)}`;
200
210
  const BG_AGENT_COLLATERAL_REAP_REASON = "its parent run ended";
201
- const RESERVED_AGENT_NAMES = new Set([OUTPUT_TOOL_NAME, TOOL_SEARCH_NAME, OFFLOAD_TOOL_NAME, REPORT_BLOCKED_TOOL_NAME, DEFAULT_SUBAGENT_TOOL_NAME, LEGACY_SUBAGENT_TOOL_NAME]);
211
+ const RESERVED_AGENT_NAMES = new Set([OUTPUT_TOOL_NAME, TOOL_SEARCH_NAME, OFFLOAD_TOOL_NAME, REPORT_BLOCKED_TOOL_NAME, DEFAULT_SUBAGENT_TOOL_NAME]);
202
212
  export const REPORT_FIELD_MAX = 300;
203
213
  function configError(message, code) {
204
214
  const e = new Error(message);
@@ -264,6 +274,7 @@ function createSteerHandle(stream, parentToolCallId, agentName, settled, retain)
264
274
  };
265
275
  }
266
276
  export function createSubagentResume(deps) {
277
+ const notifier = createSafeNotifier(deps.onNotifyError !== undefined ? { onError: deps.onNotifyError } : undefined);
267
278
  const resume = async (content) => {
268
279
  const ledger = deps.ledger;
269
280
  if (!ledger) {
@@ -360,8 +371,8 @@ export function createSubagentResume(deps) {
360
371
  ...(deps.parentToolCallId !== undefined ? { parentToolCallId: deps.parentToolCallId } : {}),
361
372
  ...(deps.rowParentTaskId !== undefined ? { parentTaskId: deps.rowParentTaskId } : {}),
362
373
  ...(deps.rowParentSessionId !== undefined ? { parentSessionId: deps.rowParentSessionId } : {}),
363
- ...(deps.rowRootSessionId !== undefined ? { rootSessionId: deps.rowRootSessionId } : {}),
364
- startedAt: Date.now(),
374
+ ...((deps.rowRootSessionId ?? deps.rowParentSessionId) !== undefined ? { rootSessionId: deps.rowRootSessionId ?? deps.rowParentSessionId } : {}),
375
+ startedAt: deps.rowSpawnedAt ?? Date.now(),
365
376
  });
366
377
  }
367
378
  }
@@ -403,6 +414,7 @@ export function createSubagentResume(deps) {
403
414
  taskId: deps.taskId,
404
415
  sessionScoped: deps.sessionScoped === true,
405
416
  ...(deps.rowAgentType !== undefined ? { agentType: deps.rowAgentType } : {}),
417
+ sessionId: entry.childSessionId,
406
418
  transcriptId: entry.childSessionId,
407
419
  ...(deps.parentToolCallId !== undefined ? { parentToolCallId: deps.parentToolCallId } : {}),
408
420
  progressTaskId: e.taskId,
@@ -442,11 +454,7 @@ export function createSubagentResume(deps) {
442
454
  settleResolve = r;
443
455
  });
444
456
  if (deps.sink) {
445
- try {
446
- deps.sink(createSteerHandle(stream, deps.parentToolCallId, entry.agentName, settled, { resume, childSessionId: entry.childSessionId }));
447
- }
448
- catch {
449
- }
457
+ notifier.notify(() => deps.sink?.(createSteerHandle(stream, deps.parentToolCallId, entry.agentName, settled, { resume, childSessionId: entry.childSessionId })), "subagent.resume.sink");
450
458
  }
451
459
  void (async () => {
452
460
  try {
@@ -493,8 +501,9 @@ export function createSubagentResume(deps) {
493
501
  seq: entry.cycleSeq,
494
502
  ...(stoppedByRevive !== undefined ? { stoppedBy: stoppedByRevive } : {}),
495
503
  summary: reviveTerminalSummary,
504
+ ...resumeResidual(),
496
505
  resumable: status !== "killed",
497
- usage: { tokens: child.stats.tokens, turns: child.stats.turns, costMicroUsd: child.stats.costMicroUsd },
506
+ usage: { tokens: child.stats.tokens, turns: child.stats.turns, ...delegatedCostField(child.stats) },
498
507
  ...(completionIdRevive !== undefined ? { completionId: completionIdRevive } : {}),
499
508
  });
500
509
  const resumeFrame = {
@@ -512,7 +521,7 @@ export function createSubagentResume(deps) {
512
521
  ...(status === "killed" && child.result ? { partial: true } : {}),
513
522
  ...resumeResidual(),
514
523
  resumable: status !== "killed",
515
- usage: { tokens: child.stats.tokens, turns: child.stats.turns, costMicroUsd: child.stats.costMicroUsd },
524
+ usage: { tokens: child.stats.tokens, turns: child.stats.turns, ...delegatedCostField(child.stats) },
516
525
  ...(completionIdRevive !== undefined ? { completionId: completionIdRevive } : {}),
517
526
  };
518
527
  const gateOwner = ownsTerminalFacesRevive && status === "completed" && deps.notify !== undefined && deps.registry !== undefined && ledger.get(deps.parentToolCallId) === entry
@@ -632,18 +641,6 @@ function parkCompletionNotify(deps) {
632
641
  };
633
642
  entry.deferredNotify = { ...(payload.seq !== undefined ? { seq: payload.seq } : {}), cancel, flush: () => deliver(false) };
634
643
  }
635
- function adaptLegacyForkArgs(args) {
636
- const raw = args;
637
- if (raw !== null && typeof raw === "object" && raw.prompt === undefined && typeof raw.directive === "string") {
638
- return {
639
- ...args,
640
- prompt: raw.directive,
641
- subagent_type: FORK_SUBAGENT_TYPE,
642
- description: typeof raw.description === "string" ? raw.description : "forked agent (legacy Fork call)",
643
- };
644
- }
645
- return args;
646
- }
647
644
  export function normalizeSubagentType(value) {
648
645
  return value.normalize("NFKC").toLowerCase().replace(/[\p{White_Space}\p{Pd}_]+/gu, "");
649
646
  }
@@ -777,6 +774,7 @@ export function agentWhenToUseText(def, lean = true) {
777
774
  function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
778
775
  const maxDepth = opts.maxDepth ?? DEFAULT_SUBAGENT_MAX_DEPTH;
779
776
  const toolName = opts.name ?? DEFAULT_SUBAGENT_TOOL_NAME;
777
+ const notifier = createSafeNotifier(opts.onObserverError !== undefined ? { onError: (f) => opts.onObserverError?.(f.error, { site: f.site }) } : undefined);
780
778
  const deploymentNames = new Set((opts.agents ?? []).map((a) => a.name));
781
779
  const builtins = opts.builtinAgents === false ? [] : builtinAgentDefinitions(toolName).filter((d) => !deploymentNames.has(d.name));
782
780
  const available = [...(opts.agents ?? []), ...builtins].filter((a) => !excluded.has(a.name));
@@ -813,7 +811,6 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
813
811
  agentListing,
814
812
  ...(rosterNames !== undefined ? { agentModels: rosterNames } : {}),
815
813
  executionMode: "parallel",
816
- ...(!opts.name ? { aliases: [LEGACY_SUBAGENT_TOOL_NAME] } : {}),
817
814
  contract: { contractId: "core.agent@1", implementationRevision: "1" },
818
815
  description: `Launch a new agent to handle complex, multi-step tasks. Each agent type has specific capabilities and tools available to it.\n` +
819
816
  (opts.purpose ? `\nThis sub-agent is for: ${opts.purpose}.\n` : "") +
@@ -897,9 +894,9 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
897
894
  }
898
895
  : {}),
899
896
  }),
900
- prepareArguments: (args) => foldGeneralPurposeAlias(adaptLegacyForkArgs(args), generalPurposeShadowed),
897
+ prepareArguments: (args) => foldGeneralPurposeAlias(args, generalPurposeShadowed),
901
898
  execute: async (args, ctx) => {
902
- const a = foldGeneralPurposeAlias(adaptLegacyForkArgs(args), generalPurposeShadowed);
899
+ const a = foldGeneralPurposeAlias(args, generalPurposeShadowed);
903
900
  const wantsBackground = opts.background !== undefined && a.run_in_background !== false;
904
901
  const reviveClaim = ctx.reviveClaim;
905
902
  if (reviveClaim !== undefined && opts.background === undefined) {
@@ -1125,18 +1122,16 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
1125
1122
  if (injectedTools.length > 0) {
1126
1123
  const taken = new Set();
1127
1124
  const reserve = (t) => {
1128
- taken.add(canonicalToolName(t.name));
1125
+ taken.add(t.name);
1129
1126
  for (const alias of t.aliases ?? [])
1130
- taken.add(canonicalToolName(alias));
1127
+ taken.add(alias);
1131
1128
  };
1132
1129
  for (const t of opts.tools ?? [])
1133
1130
  reserve(t);
1134
- taken.add(canonicalToolName(toolName));
1135
- if (!opts.name)
1136
- taken.add(canonicalToolName(LEGACY_SUBAGENT_TOOL_NAME));
1131
+ taken.add(toolName);
1137
1132
  const merged = [...(opts.tools ?? [])];
1138
1133
  for (const t of injectedTools) {
1139
- const names = [canonicalToolName(t.name), ...(t.aliases ?? []).map((alias) => canonicalToolName(alias))];
1134
+ const names = [t.name, ...(t.aliases ?? [])];
1140
1135
  if (names.some((n) => taken.has(n)))
1141
1136
  continue;
1142
1137
  for (const n of names)
@@ -1153,8 +1148,9 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
1153
1148
  : perCallModel ?? (def ? (def.model ?? ctx.model ?? opts.model) : opts.model);
1154
1149
  const childSystemPrompt = def?.systemPrompt ?? opts.systemPrompt;
1155
1150
  const childDefaultPersona = childSystemPrompt === undefined && !wantsFork ? SUBAGENT_PROMPT : undefined;
1156
- const childLimits = def?.maxTurns !== undefined
1157
- ? { ...opts.limits, maxTurns: def.maxTurns }
1151
+ const defMaxTurns = typeof def?.maxTurns === "number" && Number.isFinite(def.maxTurns) && def.maxTurns > 0 ? def.maxTurns : undefined;
1152
+ const childLimits = defMaxTurns !== undefined
1153
+ ? { ...opts.limits, maxTurns: defMaxTurns }
1158
1154
  : wantsFork && opts.limits?.maxTurns === undefined
1159
1155
  ? { ...opts.limits, maxTurns: FORK_DEFAULT_MAX_TURNS }
1160
1156
  : opts.limits;
@@ -1188,14 +1184,10 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
1188
1184
  else if (observerFailure === undefined) {
1189
1185
  observerFailure = msg;
1190
1186
  }
1191
- try {
1192
- opts.onObserverError?.(err, {
1193
- ...(def !== undefined ? { observedAgent: def.name } : {}),
1194
- ...(observerArm ? { observerAgent: observerArm.observerDefinition.name } : {}),
1195
- });
1196
- }
1197
- catch {
1198
- }
1187
+ notifier.notify(() => opts.onObserverError?.(err, {
1188
+ ...(def !== undefined ? { observedAgent: def.name } : {}),
1189
+ ...(observerArm ? { observerAgent: observerArm.observerDefinition.name } : {}),
1190
+ }), "subagent.onObserverError");
1199
1191
  };
1200
1192
  const observedSteerRef = {};
1201
1193
  let pendingTrigger = [];
@@ -1239,7 +1231,10 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
1239
1231
  ...(ctx.alwaysLoadTools !== undefined ? { alwaysLoadTools: [...ctx.alwaysLoadTools] } : {}),
1240
1232
  ...(ctx.promptProfile !== undefined ? { promptProfile: ctx.promptProfile } : {}),
1241
1233
  enableBlockedReport: false,
1242
- limits: { maxTurns: observerDef.maxTurns ?? 8, timeoutSec: 120 },
1234
+ limits: {
1235
+ maxTurns: typeof observerDef.maxTurns === "number" && Number.isFinite(observerDef.maxTurns) && observerDef.maxTurns > 0 ? observerDef.maxTurns : 8,
1236
+ timeoutSec: 120,
1237
+ },
1243
1238
  });
1244
1239
  const runObserverLeg = async (spec) => {
1245
1240
  if (!isObserverTaskId(sid))
@@ -1683,21 +1678,17 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
1683
1678
  resumable: false,
1684
1679
  ...(completionIdReap !== undefined ? { completionId: completionIdReap } : {}),
1685
1680
  });
1686
- try {
1687
- notify?.({
1688
- task_id: taskId,
1689
- task_type: "background_agent",
1690
- toolUseId: ctx.toolCallId,
1691
- status: "killed",
1692
- stoppedBy: stoppedByReap,
1693
- summary: summaryReap,
1694
- sessionId: forkedId,
1695
- resumable: false,
1696
- ...(completionIdReap !== undefined ? { completionId: completionIdReap } : {}),
1697
- }, { priority: "later" });
1698
- }
1699
- catch {
1700
- }
1681
+ notifier.notify(() => notify?.({
1682
+ task_id: taskId,
1683
+ task_type: "background_agent",
1684
+ toolUseId: ctx.toolCallId,
1685
+ status: "killed",
1686
+ stoppedBy: stoppedByReap,
1687
+ summary: summaryReap,
1688
+ sessionId: forkedId,
1689
+ resumable: false,
1690
+ ...(completionIdReap !== undefined ? { completionId: completionIdReap } : {}),
1691
+ }, { priority: "later" }), "subagent.reapTerminalNotify");
1701
1692
  });
1702
1693
  if (agentName !== undefined) {
1703
1694
  recordRosterSpawn(ctx.roster, { name: agentName, agentId: taskId, sessionId: forkedId, toolUseId: ctx.toolCallId, owner: bgOwner, scope: bgScope, ...((typeof childModel === "string" ? resolveModelDisplayLabel(childModel) : childModel?.id) !== undefined ? { model: typeof childModel === "string" ? childModel : childModel?.id } : {}), ...((ctx.rootSessionId ?? ctx.sessionId) !== undefined ? { rootSessionId: ctx.rootSessionId ?? ctx.sessionId } : {}), ...(sessionScopedBg ? { sessionScoped: true } : {}), createdAt: Date.now() });
@@ -1770,6 +1761,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
1770
1761
  kind: "tick",
1771
1762
  taskId,
1772
1763
  sessionScoped: sessionScopedBg === true,
1764
+ sessionId: forkedId,
1773
1765
  transcriptId: forkedId,
1774
1766
  parentToolCallId: ctx.toolCallId,
1775
1767
  progressTaskId: e.taskId,
@@ -1805,7 +1797,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
1805
1797
  tokens: child.stats.tokens + (nsb?.tokens ?? 0),
1806
1798
  turns: child.stats.turns + (nsb?.turns ?? 0),
1807
1799
  tasks: 1 + (nsb?.tasks ?? 0),
1808
- costMicroUsd: (child.stats.costMicroUsd ?? 0) + (nsb?.costMicroUsd ?? 0),
1800
+ costMicroUsd: rollupDelegatedCost(child.stats, nsb),
1809
1801
  });
1810
1802
  }
1811
1803
  catch {
@@ -1857,7 +1849,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
1857
1849
  summary: forkTerminalSummary,
1858
1850
  ...residualFork,
1859
1851
  resumable: resumableFork,
1860
- usage: { tokens: child.stats.tokens, turns: child.stats.turns, costMicroUsd: (child.stats.costMicroUsd ?? 0) + (child.stats.nested?.costMicroUsd ?? 0) },
1852
+ usage: { tokens: child.stats.tokens, turns: child.stats.turns, ...delegatedCostField(child.stats) },
1861
1853
  ...(completionIdFork !== undefined ? { completionId: completionIdFork } : {}),
1862
1854
  });
1863
1855
  try {
@@ -1876,7 +1868,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
1876
1868
  ...(settledBg === "killed" && child.result ? { partial: true } : {}),
1877
1869
  ...residualFork,
1878
1870
  resumable: resumableFork,
1879
- usage: { tokens: child.stats.tokens, turns: child.stats.turns, costMicroUsd: (child.stats.costMicroUsd ?? 0) + (child.stats.nested?.costMicroUsd ?? 0) },
1871
+ usage: { tokens: child.stats.tokens, turns: child.stats.turns, ...delegatedCostField(child.stats) },
1880
1872
  ...(completionIdFork !== undefined ? { completionId: completionIdFork } : {}),
1881
1873
  }, { priority: "later" });
1882
1874
  }
@@ -1980,7 +1972,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
1980
1972
  tokens: forkChild.stats.tokens + (nsf?.tokens ?? 0),
1981
1973
  turns: forkChild.stats.turns + (nsf?.turns ?? 0),
1982
1974
  tasks: 1 + (nsf?.tasks ?? 0),
1983
- costMicroUsd: (forkChild.stats.costMicroUsd ?? 0) + (nsf?.costMicroUsd ?? 0),
1975
+ costMicroUsd: rollupDelegatedCost(forkChild.stats, nsf),
1984
1976
  });
1985
1977
  if (isDurablePause(forkChild.status)) {
1986
1978
  return {
@@ -2100,7 +2092,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2100
2092
  return { isError: true, content: `Sub-agent not started in background: ${e instanceof Error ? e.message : String(e)}${wt ? `\n${wt}` : ""}`, details: { error: "register_failed" } };
2101
2093
  }
2102
2094
  if (agentName !== undefined) {
2103
- recordRosterSpawn(ctx.roster, { name: agentName, agentId: taskId, toolUseId: ctx.toolCallId, owner: bgOwner, scope: bgScope, ...((typeof childModel === "string" ? resolveModelDisplayLabel(childModel) : childModel?.id) !== undefined ? { model: typeof childModel === "string" ? childModel : childModel?.id } : {}), ...(reviveRow !== undefined ? (reviveRow.rootSessionId !== undefined ? { rootSessionId: reviveRow.rootSessionId } : {}) : (ctx.rootSessionId ?? ctx.sessionId) !== undefined ? { rootSessionId: ctx.rootSessionId ?? ctx.sessionId } : {}), ...(sessionScopedBg ? { sessionScoped: true } : {}), createdAt: reviveRow?.spawnedAt ?? Date.now() });
2095
+ recordRosterSpawn(ctx.roster, { name: agentName, agentId: taskId, toolUseId: ctx.toolCallId, owner: bgOwner, scope: bgScope, ...((typeof childModel === "string" ? resolveModelDisplayLabel(childModel) : childModel?.id) !== undefined ? { model: typeof childModel === "string" ? childModel : childModel?.id } : {}), ...(reviveRow !== undefined ? ((reviveRow.rootSessionId ?? reviveRow.parentSessionId) !== undefined ? { rootSessionId: reviveRow.rootSessionId ?? reviveRow.parentSessionId } : {}) : (ctx.rootSessionId ?? ctx.sessionId) !== undefined ? { rootSessionId: ctx.rootSessionId ?? ctx.sessionId } : {}), ...(sessionScopedBg ? { sessionScoped: true } : {}), createdAt: reviveRow?.spawnedAt ?? Date.now() });
2104
2096
  }
2105
2097
  const bgSink = ctx.onBackgroundChildEvent;
2106
2098
  const sinkEmit = (event) => {
@@ -2131,21 +2123,17 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2131
2123
  resumable: false,
2132
2124
  ...(completionIdReap !== undefined ? { completionId: completionIdReap } : {}),
2133
2125
  });
2134
- try {
2135
- notify?.({
2136
- task_id: taskId,
2137
- task_type: "background_agent",
2138
- toolUseId: ctx.toolCallId,
2139
- status: "killed",
2140
- stoppedBy: stoppedByReap,
2141
- summary: summaryReap,
2142
- sessionId: bgChildSessionId,
2143
- resumable: false,
2144
- ...(completionIdReap !== undefined ? { completionId: completionIdReap } : {}),
2145
- }, { priority: "later" });
2146
- }
2147
- catch {
2148
- }
2126
+ notifier.notify(() => notify?.({
2127
+ task_id: taskId,
2128
+ task_type: "background_agent",
2129
+ toolUseId: ctx.toolCallId,
2130
+ status: "killed",
2131
+ stoppedBy: stoppedByReap,
2132
+ summary: summaryReap,
2133
+ sessionId: bgChildSessionId,
2134
+ resumable: false,
2135
+ ...(completionIdReap !== undefined ? { completionId: completionIdReap } : {}),
2136
+ }, { priority: "later" }), "subagent.reapTerminalNotify");
2149
2137
  });
2150
2138
  const bgDurableProbe = bg.registry.durableAgentRowProbe(taskId);
2151
2139
  const bgRowConfirmed = async () => (bgDurableProbe ? await bgDurableProbe().catch(() => false) : false);
@@ -2165,14 +2153,14 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2165
2153
  ? {
2166
2154
  ...(reviveRow.parentTaskId !== undefined ? { parentTaskId: reviveRow.parentTaskId } : {}),
2167
2155
  ...(reviveRow.parentSessionId !== undefined ? { parentSessionId: reviveRow.parentSessionId } : {}),
2168
- ...(reviveRow.rootSessionId !== undefined ? { rootSessionId: reviveRow.rootSessionId } : {}),
2156
+ ...((reviveRow.rootSessionId ?? reviveRow.parentSessionId) !== undefined ? { rootSessionId: reviveRow.rootSessionId ?? reviveRow.parentSessionId } : {}),
2169
2157
  }
2170
2158
  : {
2171
2159
  ...(ctx.taskId !== undefined && ctx.taskId !== ctx.sessionId ? { parentTaskId: ctx.taskId } : {}),
2172
2160
  ...(ctx.sessionId !== undefined ? { parentSessionId: ctx.sessionId } : {}),
2173
2161
  ...((ctx.rootSessionId ?? ctx.sessionId) !== undefined ? { rootSessionId: ctx.rootSessionId ?? ctx.sessionId } : {}),
2174
2162
  }),
2175
- startedAt: Date.now(),
2163
+ startedAt: reviveRow?.spawnedAt ?? Date.now(),
2176
2164
  });
2177
2165
  const bgRetainLedger = reviveRow !== undefined
2178
2166
  ? undefined
@@ -2249,6 +2237,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2249
2237
  kind: "tick",
2250
2238
  taskId,
2251
2239
  sessionScoped: sessionScopedBg === true,
2240
+ sessionId: bgChildSessionId,
2252
2241
  transcriptId: bgChildSessionId,
2253
2242
  parentToolCallId: ctx.toolCallId,
2254
2243
  progressTaskId: e.taskId,
@@ -2405,7 +2394,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2405
2394
  tokens: child.stats.tokens + (ns2?.tokens ?? 0),
2406
2395
  turns: child.stats.turns + (ns2?.turns ?? 0),
2407
2396
  tasks: 1 + (ns2?.tasks ?? 0),
2408
- costMicroUsd: (child.stats.costMicroUsd ?? 0) + (ns2?.costMicroUsd ?? 0),
2397
+ costMicroUsd: rollupDelegatedCost(child.stats, ns2),
2409
2398
  });
2410
2399
  }
2411
2400
  catch {
@@ -2515,7 +2504,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2515
2504
  summary: bgTerminalSummary,
2516
2505
  ...residual,
2517
2506
  resumable: resumableBg,
2518
- usage: { tokens: child.stats.tokens, turns: child.stats.turns, costMicroUsd: (child.stats.costMicroUsd ?? 0) + (child.stats.nested?.costMicroUsd ?? 0) },
2507
+ usage: { tokens: child.stats.tokens, turns: child.stats.turns, ...delegatedCostField(child.stats) },
2519
2508
  ...(completionIdBg !== undefined ? { completionId: completionIdBg } : {}),
2520
2509
  });
2521
2510
  const completionFrame = {
@@ -2533,7 +2522,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2533
2522
  ...(settled === "killed" && child.result ? { partial: true } : {}),
2534
2523
  ...residual,
2535
2524
  resumable: resumableBg,
2536
- usage: { tokens: child.stats.tokens, turns: child.stats.turns, costMicroUsd: (child.stats.costMicroUsd ?? 0) + (child.stats.nested?.costMicroUsd ?? 0) },
2525
+ usage: { tokens: child.stats.tokens, turns: child.stats.turns, ...delegatedCostField(child.stats) },
2537
2526
  ...(completionIdBg !== undefined ? { completionId: completionIdBg } : {}),
2538
2527
  };
2539
2528
  const gateOwner = ownsTerminalFaces && settled === "completed" && notify !== undefined ? bgRetain?.childSessionId : undefined;
@@ -2685,14 +2674,10 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2685
2674
  sink: ctx.onSubagentSpawn,
2686
2675
  ...(opts.background ? { registry: opts.background.registry } : {}),
2687
2676
  });
2688
- try {
2689
- ctx.onSubagentSpawn(createSteerHandle(stream, ctx.toolCallId, childAgentName, settled, {
2690
- resume,
2691
- ...(retainEntry ? { childSessionId: retainEntry.childSessionId } : {}),
2692
- }));
2693
- }
2694
- catch {
2695
- }
2677
+ notifier.notify(() => ctx.onSubagentSpawn?.(createSteerHandle(stream, ctx.toolCallId, childAgentName, settled, {
2678
+ resume,
2679
+ ...(retainEntry ? { childSessionId: retainEntry.childSessionId } : {}),
2680
+ })), "subagent.onSubagentSpawn");
2696
2681
  }
2697
2682
  try {
2698
2683
  for await (const ev of stream)
@@ -2737,7 +2722,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2737
2722
  tokens: child.stats.tokens + (ns?.tokens ?? 0),
2738
2723
  turns: child.stats.turns + (ns?.turns ?? 0),
2739
2724
  tasks: 1 + (ns?.tasks ?? 0),
2740
- costMicroUsd: (child.stats.costMicroUsd ?? 0) + (ns?.costMicroUsd ?? 0),
2725
+ costMicroUsd: rollupDelegatedCost(child.stats, ns),
2741
2726
  });
2742
2727
  if (isDurablePause(child.status)) {
2743
2728
  if (retainEntry)
@@ -53,15 +53,7 @@ export interface TeacherRunResult extends TaskResult {
53
53
  tokens: number;
54
54
  tasks: number;
55
55
  costMicroUsd?: number;
56
- humanReview?: {
57
- count: number;
58
- totalWaitMs: number;
59
- gates: Array<{
60
- kind: string;
61
- waitMs: number;
62
- decision?: string;
63
- }>;
64
- };
56
+ humanReview?: NonNullable<TaskResult["stats"]["humanReview"]>;
65
57
  };
66
58
  }
67
59
  export declare function parseTeacherAdvice(text: string): TeacherAdvice;
@@ -2,6 +2,7 @@ import { randomUUID } from "node:crypto";
2
2
  import { mapNestedSuspend, isDurablePause } from "./suspend-guard.js";
3
3
  import { isDefineToolProduct, stampDefineToolBrand } from "../core/tools.js";
4
4
  import { delimitUntrusted, inlineUntrusted } from "../core/untrusted-text.js";
5
+ import { buildCumulativeStats } from "./cumulative-stats.js";
5
6
  export const TEACHER_PROMPT = `You are an expert advisor to a less-capable "student" agent that got stuck.
6
7
  You receive the task and the student's recent failed attempts. Your job is to help the student RECOVER,
7
8
  not to do the work for it.
@@ -84,6 +85,7 @@ async function runTeacherCore(runner, studentSpec, teacher) {
84
85
  let studentTokens = 0;
85
86
  let studentTurns = 0;
86
87
  let studentCostMicro = 0;
88
+ let anyStudentLegUnpriced = false;
87
89
  let studentPrompt = 0;
88
90
  let studentTotalInput = 0;
89
91
  let studentCached = 0;
@@ -202,6 +204,8 @@ async function runTeacherCore(runner, studentSpec, teacher) {
202
204
  studentTokens += s.tokens;
203
205
  studentTurns += s.turns;
204
206
  studentCostMicro += s.costMicroUsd ?? 0;
207
+ if (s.costMicroUsd === undefined)
208
+ anyStudentLegUnpriced = true;
205
209
  studentPrompt += s.promptTokens ?? 0;
206
210
  studentTotalInput += s.totalInputTokens ?? 0;
207
211
  studentCached += s.cachedTokens ?? 0;
@@ -442,34 +446,27 @@ async function runTeacherCore(runner, studentSpec, teacher) {
442
446
  function finalize(result) {
443
447
  return {
444
448
  ...result,
445
- stats: {
446
- ...result.stats,
449
+ stats: buildCumulativeStats(result.stats, {
447
450
  tokens: studentTokens,
448
451
  turns: studentTurns,
449
452
  costMicroUsd: studentCostMicro || undefined,
450
- promptTokens: studentPrompt || undefined,
451
- totalInputTokens: studentTotalInput || undefined,
452
- cachedTokens: studentCached || undefined,
453
- outputTokens: studentOutput || undefined,
454
- cacheWriteTokens: studentCacheWrite || undefined,
455
- cacheWriteTokensLong: studentCacheWriteLong || undefined,
456
- cacheHitRate: studentTotalInput > 0 ? Math.min(1, studentCached / studentTotalInput) : undefined,
457
- nested: studentNestedTokens > 0 || studentNestedTasks > 0
458
- ? {
459
- tokens: studentNestedTokens,
460
- turns: studentNestedTurns,
461
- tasks: studentNestedTasks,
462
- costMicroUsd: studentNestedCostMicro || undefined,
463
- }
464
- : undefined,
465
- costBreakdown: {
466
- llmRootMicroUsd: studentLlmRootMicro,
467
- nestedSubagentMicroUsd: studentNestedCostMicro,
468
- memoryConsolidationMicroUsd: 0,
469
- compactionMicroUsd: studentCompactionMicro,
453
+ anyLegUnpriced: anyStudentLegUnpriced,
454
+ promptTokens: studentPrompt,
455
+ totalInputTokens: studentTotalInput,
456
+ cachedTokens: studentCached,
457
+ outputTokens: studentOutput,
458
+ cacheWriteTokens: studentCacheWrite,
459
+ cacheWriteTokensLong: studentCacheWriteLong,
460
+ llmRootMicroUsd: studentLlmRootMicro,
461
+ compactionMicroUsd: studentCompactionMicro,
462
+ nested: {
463
+ tokens: studentNestedTokens,
464
+ turns: studentNestedTurns,
465
+ tasks: studentNestedTasks,
466
+ costMicroUsd: studentNestedCostMicro,
470
467
  },
471
- humanReview: hrCount > 0 ? { count: hrCount, totalWaitMs: hrWaitMs, gates: hrGates } : undefined,
472
- },
468
+ humanReview: { count: hrCount, totalWaitMs: hrWaitMs, gates: hrGates },
469
+ }),
473
470
  escalations,
474
471
  teacherStats: {
475
472
  tokens: teacherTokens,
@@ -1,17 +1,16 @@
1
- import { canonicalToolName } from "../core/tool-name-aliases.js";
2
1
  export function toolNameAllowed(name, allowTools, denyTools) {
3
- const allow = allowTools !== undefined && !allowTools.includes("*") ? new Set(allowTools.map(canonicalToolName)) : undefined;
4
- const deny = denyTools && denyTools.length > 0 ? new Set(denyTools.map(canonicalToolName)) : undefined;
5
- const n = canonicalToolName(name);
2
+ const allow = allowTools !== undefined && !allowTools.includes("*") ? new Set(allowTools) : undefined;
3
+ const deny = denyTools && denyTools.length > 0 ? new Set(denyTools) : undefined;
4
+ const n = name;
6
5
  return (!allow || allow.has(n)) && (!deny || !deny.has(n));
7
6
  }
8
7
  export function resolveToolSubset(pool, allowTools, denyTools) {
9
- const allow = allowTools !== undefined && !allowTools.includes("*") ? new Set(allowTools.map(canonicalToolName)) : undefined;
10
- const deny = denyTools && denyTools.length > 0 ? new Set(denyTools.map(canonicalToolName)) : undefined;
8
+ const allow = allowTools !== undefined && !allowTools.includes("*") ? new Set(allowTools) : undefined;
9
+ const deny = denyTools && denyTools.length > 0 ? new Set(denyTools) : undefined;
11
10
  if (!allow && !deny)
12
11
  return [...pool];
13
12
  return pool.filter((t) => {
14
- const matches = (set) => set.has(canonicalToolName(t.name)) || (t.aliases ?? []).some((a) => set.has(canonicalToolName(a)));
13
+ const matches = (set) => set.has(t.name) || (t.aliases ?? []).some((a) => set.has(a));
15
14
  return (!allow || matches(allow)) && (!deny || !matches(deny));
16
15
  });
17
16
  }
@@ -11,6 +11,7 @@ export declare const VerdictSchema: Type.TObject<{
11
11
  }>;
12
12
  export type Verdict = Static<typeof VerdictSchema>;
13
13
  export interface VerifyConfig {
14
+ onNotifyError?: (failure: import("../core/safe-notify.js").SafeNotifyFailure) => void;
14
15
  verifierModel?: ModelRef;
15
16
  verifierTools?: ToolSpec[];
16
17
  maxRounds?: number;
@@ -3,6 +3,7 @@ import { CODE_AGENT_PROMPT } from "../prompts/default.js";
3
3
  import { releaseSession } from "./session-util.js";
4
4
  import { mapNestedSuspend, isDurablePause } from "./suspend-guard.js";
5
5
  import { delimitUntrusted, sanitizeUntrustedText } from "../core/untrusted-text.js";
6
+ import { createSafeNotifier } from "../core/safe-notify.js";
6
7
  export const VERIFICATION_PROMPT = `You are a verification specialist. Your job is NOT to confirm the implementation works — it is to try to BREAK it.
7
8
 
8
9
  You have two documented failure patterns. First, verification avoidance: faced with a check, you find reasons not to run it — you read code, narrate what you would test, declare "PASS," and move on. Second, being seduced by the first 80%: a polished result or a passing test suite makes you inclined to pass it, not noticing the edge that crashes, the state that vanishes, the bad input that is unhandled. The first 80% is the easy part. Your entire value is in finding the last 20%.
@@ -58,6 +59,8 @@ export const VerdictSchema = Type.Object({
58
59
  }),
59
60
  evidence: Type.Optional(Type.String({ description: "Commands run + observed output backing the verdict." })),
60
61
  });
62
+ const VERIFY_ON_ROUND_SITE = "verify.onRound";
63
+ const VERIFY_ON_VERIFIER_PAUSE_SITE = "verify.onVerifierPause";
61
64
  function fixObjective(findings) {
62
65
  const list = findings.length
63
66
  ? findings.map((f) => `- ${sanitizeUntrustedText(f)}`).join("\n")
@@ -82,7 +85,8 @@ export async function verifyCompleted(runner, result, specBase, objective, confi
82
85
  if (result.status !== "completed") {
83
86
  return { ...result, verification: { verdict: "unverified", unverifiedReason: "impl_incomplete", rounds: 0, findings: [] } };
84
87
  }
85
- const maxRounds = Math.max(1, Math.floor(config.maxRounds ?? 2));
88
+ const maxRounds = Number.isFinite(config.maxRounds) ? Math.max(1, Math.floor(config.maxRounds)) : 2;
89
+ const notifier = createSafeNotifier(config.onNotifyError !== undefined ? { onError: config.onNotifyError } : undefined);
86
90
  const verifierTools = config.verifierTools ?? (specBase.tools ?? []).filter((t) => t.effect === "read");
87
91
  const evidenceMode = config.evidence != null && config.evidence.trim() !== "";
88
92
  const verifierPrompt = config.verifierPrompt ?? (evidenceMode ? STATIC_VERIFICATION_PROMPT : VERIFICATION_PROMPT);
@@ -126,6 +130,7 @@ export async function verifyCompleted(runner, result, specBase, objective, confi
126
130
  spend += v.cost;
127
131
  verifierSpend += v.cost;
128
132
  if (v.paused) {
133
+ const paused = v.paused;
129
134
  outcome = {
130
135
  verdict: "unverified",
131
136
  unverifiedReason: "verifier_paused",
@@ -139,18 +144,14 @@ export async function verifyCompleted(runner, result, specBase, objective, confi
139
144
  ...(v.paused.checkpointGate ? { checkpointGate: v.paused.checkpointGate } : {}),
140
145
  },
141
146
  };
142
- try {
143
- config.onVerifierPause?.({
144
- round,
145
- status: v.paused.status,
146
- sessionId: v.paused.sessionId,
147
- ...(v.paused.checkpointToken ? { checkpointToken: v.paused.checkpointToken } : {}),
148
- ...(v.paused.checkpointGate ? { checkpointGate: v.paused.checkpointGate } : {}),
149
- });
150
- }
151
- catch {
152
- }
153
- config.onRound?.({ round, verdict: outcome.verdict, findings: outcome.findings });
147
+ notifier.notify(() => config.onVerifierPause?.({
148
+ round,
149
+ status: paused.status,
150
+ sessionId: paused.sessionId,
151
+ ...(paused.checkpointToken ? { checkpointToken: paused.checkpointToken } : {}),
152
+ ...(paused.checkpointGate ? { checkpointGate: paused.checkpointGate } : {}),
153
+ }), VERIFY_ON_VERIFIER_PAUSE_SITE);
154
+ notifier.notify(() => config.onRound?.({ round, verdict: outcome.verdict, findings: outcome.findings }), VERIFY_ON_ROUND_SITE);
154
155
  break;
155
156
  }
156
157
  const rawVerdict = v.verdict?.verdict ?? "unverified";
@@ -164,7 +165,7 @@ export async function verifyCompleted(runner, result, specBase, objective, confi
164
165
  evidence: v.verdict?.evidence,
165
166
  verifierCost: verifierSpend,
166
167
  };
167
- config.onRound?.({ round, verdict: outcome.verdict, findings: outcome.findings });
168
+ notifier.notify(() => config.onRound?.({ round, verdict: outcome.verdict, findings: outcome.findings }), VERIFY_ON_ROUND_SITE);
168
169
  if (outcome.verdict !== "FAIL")
169
170
  break;
170
171
  if (round >= maxRounds)