@sema-agent/core 3.0.0 → 4.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 (127) hide show
  1. package/CHANGELOG.md +2345 -0
  2. package/dist/agents/cascade.d.ts +2 -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.js +27 -19
  9. package/dist/agents/retain-ledger.d.ts +2 -0
  10. package/dist/agents/retain-ledger.js +6 -5
  11. package/dist/agents/roster-store.js +3 -5
  12. package/dist/agents/subagent.js +66 -64
  13. package/dist/agents/teacher.d.ts +1 -9
  14. package/dist/agents/teacher.js +21 -24
  15. package/dist/agents/verify.js +15 -14
  16. package/dist/brain/anthropic.js +24 -21
  17. package/dist/brain/openai.js +24 -21
  18. package/dist/brain/stream-engine.js +5 -2
  19. package/dist/brain/stream-shared.d.ts +1 -0
  20. package/dist/brain/stream-shared.js +14 -0
  21. package/dist/brain/terminal-cause.d.ts +4 -0
  22. package/dist/brain/terminal-cause.js +12 -0
  23. package/dist/brain/tool-call-id.d.ts +1 -0
  24. package/dist/brain/tool-call-id.js +3 -0
  25. package/dist/brain/tool-call-repair.js +2 -1
  26. package/dist/core/auto-compaction.js +16 -25
  27. package/dist/core/background-agent-store.d.ts +22 -22
  28. package/dist/core/background-agent-store.js +19 -23
  29. package/dist/core/checkpoint-store.d.ts +6 -13
  30. package/dist/core/checkpoint-store.js +11 -5
  31. package/dist/core/file-snapshot-store.d.ts +8 -0
  32. package/dist/core/file-snapshot-store.js +4 -3
  33. package/dist/core/hooks.d.ts +3 -1
  34. package/dist/core/hooks.js +8 -18
  35. package/dist/core/lsp.js +4 -3
  36. package/dist/core/mailbox-store.d.ts +7 -10
  37. package/dist/core/mcp.d.ts +7 -10
  38. package/dist/core/mcp.js +7 -3
  39. package/dist/core/memory-engine/engine.d.ts +0 -1
  40. package/dist/core/memory-engine/engine.js +24 -34
  41. package/dist/core/memory-engine/file-backend.d.ts +1 -2
  42. package/dist/core/memory-engine/file-backend.js +25 -36
  43. package/dist/core/memory-engine/layout.d.ts +8 -0
  44. package/dist/core/memory-engine/layout.js +60 -4
  45. package/dist/core/memory-engine/scope-contract.js +9 -3
  46. package/dist/core/memory-engine/types.d.ts +1 -1
  47. package/dist/core/runner/active-skill-scope.d.ts +0 -1
  48. package/dist/core/runner/active-skill-scope.js +1 -15
  49. package/dist/core/runner/assemble-result.d.ts +2 -7
  50. package/dist/core/runner/assemble-result.js +3 -4
  51. package/dist/core/runner/compaction-call-options.d.ts +7 -0
  52. package/dist/core/runner/compaction-call-options.js +30 -0
  53. package/dist/core/runner/prepare-task.d.ts +6 -23
  54. package/dist/core/runner/prepare-task.js +20 -53
  55. package/dist/core/runner/runtask.d.ts +1 -0
  56. package/dist/core/runner/runtask.js +65 -156
  57. package/dist/core/runner/session-rule-policy.js +4 -1
  58. package/dist/core/runner/teardown-bounded.d.ts +7 -0
  59. package/dist/core/runner/teardown-bounded.js +36 -0
  60. package/dist/core/runner/tool-disclosure.d.ts +2 -5
  61. package/dist/core/runner/turn-attachments.d.ts +5 -15
  62. package/dist/core/runner/turn-attachments.js +1 -1
  63. package/dist/core/safe-notify.d.ts +17 -0
  64. package/dist/core/safe-notify.js +59 -0
  65. package/dist/core/sensitive-path-policy.js +2 -1
  66. package/dist/core/store-contracts/checkpoint-store-contract.js +1 -1
  67. package/dist/core/stub-env.d.ts +2 -6
  68. package/dist/core/surrogate-safe-slice.d.ts +4 -0
  69. package/dist/core/surrogate-safe-slice.js +18 -0
  70. package/dist/core/task-registry-monitor.js +8 -13
  71. package/dist/core/task-registry-shared.d.ts +22 -0
  72. package/dist/core/task-registry-shared.js +16 -0
  73. package/dist/core/task-registry.d.ts +1 -0
  74. package/dist/core/task-registry.js +40 -57
  75. package/dist/core/tool-errors.js +5 -4
  76. package/dist/core/tool-name-aliases.js +2 -1
  77. package/dist/core/tool-policy.js +10 -5
  78. package/dist/core/tool-result-store.d.ts +1 -0
  79. package/dist/core/tool-result-store.js +17 -1
  80. package/dist/core/trace.d.ts +2 -9
  81. package/dist/core/types.d.ts +15 -12
  82. package/dist/core/untrusted-egress.js +7 -5
  83. package/dist/core/workflow-journal-store.d.ts +9 -20
  84. package/dist/core/workflow-run-store.d.ts +11 -10
  85. package/dist/core/workflow-run-store.js +22 -16
  86. package/dist/engine/compaction/utils.js +4 -2
  87. package/dist/engine/execution-env/node-execution-env.d.ts +2 -24
  88. package/dist/engine/harness/agent-harness.d.ts +1 -0
  89. package/dist/engine/harness/agent-harness.js +13 -33
  90. package/dist/engine/harness/types.d.ts +29 -67
  91. package/dist/engine/llm/types.d.ts +1 -1
  92. package/dist/engine/loop/agent-loop.js +11 -4
  93. package/dist/engine/loop/types.d.ts +11 -9
  94. package/dist/engine/session/memory-repo.d.ts +2 -6
  95. package/dist/engine/session/session.d.ts +5 -30
  96. package/dist/index.d.ts +3 -1
  97. package/dist/index.js +2 -1
  98. package/dist/internal/harness-types.d.ts +3 -1
  99. package/dist/orchestration/run-spec.js +7 -4
  100. package/dist/orchestration/run-workflow-tool.js +3 -35
  101. package/dist/orchestration/workflow.d.ts +3 -11
  102. package/dist/orchestration/workflow.js +164 -204
  103. package/dist/prompt-assembly/tool-catalog.d.ts +10 -9
  104. package/dist/prompt-assembly/turn-snapshot.d.ts +2 -5
  105. package/dist/stores/file/background-agent-store.d.ts +3 -12
  106. package/dist/stores/file/background-agent-store.js +3 -41
  107. package/dist/stores/file/checkpoint-store.js +6 -6
  108. package/dist/stores/file/mailbox-store.d.ts +2 -6
  109. package/dist/stores/file/session-policy-store.js +15 -1
  110. package/dist/stores/file/session-store.d.ts +2 -6
  111. package/dist/stores/file/workflow-journal-store.d.ts +3 -11
  112. package/dist/stores/file/workflow-run-store.d.ts +3 -7
  113. package/dist/stores/file/workflow-run-store.js +4 -17
  114. package/dist/tools/fs/bash-readonly-classifier.d.ts +1 -0
  115. package/dist/tools/fs/bash-readonly-classifier.js +60 -25
  116. package/dist/tools/fs/fs-bash.js +324 -96
  117. package/dist/tools/fs/fs-pdf.d.ts +2 -12
  118. package/dist/tools/fs/fs-shared.js +2 -2
  119. package/dist/tools/fs/notebook.d.ts +2 -5
  120. package/dist/tools/fs/safety.d.ts +3 -0
  121. package/dist/tools/fs/safety.js +43 -14
  122. package/dist/tools/fs/search.js +5 -5
  123. package/dist/tools/monitor.d.ts +1 -0
  124. package/dist/tools/monitor.js +7 -5
  125. package/dist/tools/worktree.d.ts +2 -7
  126. package/dist/tools/worktree.js +3 -1
  127. package/package.json +6 -2
@@ -11,6 +11,7 @@ import { SUBAGENT_PROMPT } from "../prompts/default.js";
11
11
  import { hasSessionFork } from "../core/session.js";
12
12
  import { isDurablePause } from "./suspend-guard.js";
13
13
  import { delimitUntrusted, inlineUntrusted } from "../core/untrusted-text.js";
14
+ import { createSafeNotifier } from "../core/safe-notify.js";
14
15
  import { boundedRedactedSummary } from "../core/untrusted-egress.js";
15
16
  import { uuidv7 } from "../internal/harness.js";
16
17
  import { addWorktree } from "../core/git-worktree-env.js";
@@ -18,6 +19,17 @@ import { shellQuote } from "../tools/fs/search.js";
18
19
  import { BG_AGENT_REAP_STOP_ERROR } from "../core/task-registry.js";
19
20
  import { extractErrorCode } from "../brain/errors.js";
20
21
  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";
22
+ function rollupDelegatedCost(stats, nested) {
23
+ if (stats.costMicroUsd === undefined)
24
+ return undefined;
25
+ if (nested !== undefined && nested.costMicroUsd === undefined)
26
+ return undefined;
27
+ return stats.costMicroUsd + (nested?.costMicroUsd ?? 0);
28
+ }
29
+ function delegatedCostField(stats) {
30
+ const total = rollupDelegatedCost(stats, stats.nested);
31
+ return total !== undefined ? { costMicroUsd: total } : {};
32
+ }
21
33
  export { FORK_DEFAULT_MAX_TURNS, SESSION_BG_DEFAULT_TIMEOUT_SEC, RETAIN_DEFAULT_TTL_MS, RETAIN_DEFAULT_MAX };
22
34
  import { SUBAGENT_RESUME_CAP, SubagentRetainLedger, getOrCreateSessionRetainLedger, ensureSessionReapHook, createResumePrompt, } from "./retain-ledger.js";
23
35
  import { recordRosterSpawn } from "./roster-store.js";
@@ -264,6 +276,7 @@ function createSteerHandle(stream, parentToolCallId, agentName, settled, retain)
264
276
  };
265
277
  }
266
278
  export function createSubagentResume(deps) {
279
+ const notifier = createSafeNotifier();
267
280
  const resume = async (content) => {
268
281
  const ledger = deps.ledger;
269
282
  if (!ledger) {
@@ -403,6 +416,7 @@ export function createSubagentResume(deps) {
403
416
  taskId: deps.taskId,
404
417
  sessionScoped: deps.sessionScoped === true,
405
418
  ...(deps.rowAgentType !== undefined ? { agentType: deps.rowAgentType } : {}),
419
+ sessionId: entry.childSessionId,
406
420
  transcriptId: entry.childSessionId,
407
421
  ...(deps.parentToolCallId !== undefined ? { parentToolCallId: deps.parentToolCallId } : {}),
408
422
  progressTaskId: e.taskId,
@@ -442,11 +456,7 @@ export function createSubagentResume(deps) {
442
456
  settleResolve = r;
443
457
  });
444
458
  if (deps.sink) {
445
- try {
446
- deps.sink(createSteerHandle(stream, deps.parentToolCallId, entry.agentName, settled, { resume, childSessionId: entry.childSessionId }));
447
- }
448
- catch {
449
- }
459
+ notifier.notify(() => deps.sink?.(createSteerHandle(stream, deps.parentToolCallId, entry.agentName, settled, { resume, childSessionId: entry.childSessionId })), "subagent.resume.sink");
450
460
  }
451
461
  void (async () => {
452
462
  try {
@@ -493,8 +503,9 @@ export function createSubagentResume(deps) {
493
503
  seq: entry.cycleSeq,
494
504
  ...(stoppedByRevive !== undefined ? { stoppedBy: stoppedByRevive } : {}),
495
505
  summary: reviveTerminalSummary,
506
+ ...resumeResidual(),
496
507
  resumable: status !== "killed",
497
- usage: { tokens: child.stats.tokens, turns: child.stats.turns, costMicroUsd: child.stats.costMicroUsd },
508
+ usage: { tokens: child.stats.tokens, turns: child.stats.turns, ...delegatedCostField(child.stats) },
498
509
  ...(completionIdRevive !== undefined ? { completionId: completionIdRevive } : {}),
499
510
  });
500
511
  const resumeFrame = {
@@ -512,7 +523,7 @@ export function createSubagentResume(deps) {
512
523
  ...(status === "killed" && child.result ? { partial: true } : {}),
513
524
  ...resumeResidual(),
514
525
  resumable: status !== "killed",
515
- usage: { tokens: child.stats.tokens, turns: child.stats.turns, costMicroUsd: child.stats.costMicroUsd },
526
+ usage: { tokens: child.stats.tokens, turns: child.stats.turns, ...delegatedCostField(child.stats) },
516
527
  ...(completionIdRevive !== undefined ? { completionId: completionIdRevive } : {}),
517
528
  };
518
529
  const gateOwner = ownsTerminalFacesRevive && status === "completed" && deps.notify !== undefined && deps.registry !== undefined && ledger.get(deps.parentToolCallId) === entry
@@ -777,6 +788,7 @@ export function agentWhenToUseText(def, lean = true) {
777
788
  function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
778
789
  const maxDepth = opts.maxDepth ?? DEFAULT_SUBAGENT_MAX_DEPTH;
779
790
  const toolName = opts.name ?? DEFAULT_SUBAGENT_TOOL_NAME;
791
+ const notifier = createSafeNotifier();
780
792
  const deploymentNames = new Set((opts.agents ?? []).map((a) => a.name));
781
793
  const builtins = opts.builtinAgents === false ? [] : builtinAgentDefinitions(toolName).filter((d) => !deploymentNames.has(d.name));
782
794
  const available = [...(opts.agents ?? []), ...builtins].filter((a) => !excluded.has(a.name));
@@ -1153,8 +1165,9 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
1153
1165
  : perCallModel ?? (def ? (def.model ?? ctx.model ?? opts.model) : opts.model);
1154
1166
  const childSystemPrompt = def?.systemPrompt ?? opts.systemPrompt;
1155
1167
  const childDefaultPersona = childSystemPrompt === undefined && !wantsFork ? SUBAGENT_PROMPT : undefined;
1156
- const childLimits = def?.maxTurns !== undefined
1157
- ? { ...opts.limits, maxTurns: def.maxTurns }
1168
+ const defMaxTurns = typeof def?.maxTurns === "number" && Number.isFinite(def.maxTurns) && def.maxTurns > 0 ? def.maxTurns : undefined;
1169
+ const childLimits = defMaxTurns !== undefined
1170
+ ? { ...opts.limits, maxTurns: defMaxTurns }
1158
1171
  : wantsFork && opts.limits?.maxTurns === undefined
1159
1172
  ? { ...opts.limits, maxTurns: FORK_DEFAULT_MAX_TURNS }
1160
1173
  : opts.limits;
@@ -1188,14 +1201,10 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
1188
1201
  else if (observerFailure === undefined) {
1189
1202
  observerFailure = msg;
1190
1203
  }
1191
- try {
1192
- opts.onObserverError?.(err, {
1193
- ...(def !== undefined ? { observedAgent: def.name } : {}),
1194
- ...(observerArm ? { observerAgent: observerArm.observerDefinition.name } : {}),
1195
- });
1196
- }
1197
- catch {
1198
- }
1204
+ notifier.notify(() => opts.onObserverError?.(err, {
1205
+ ...(def !== undefined ? { observedAgent: def.name } : {}),
1206
+ ...(observerArm ? { observerAgent: observerArm.observerDefinition.name } : {}),
1207
+ }), "subagent.onObserverError");
1199
1208
  };
1200
1209
  const observedSteerRef = {};
1201
1210
  let pendingTrigger = [];
@@ -1239,7 +1248,10 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
1239
1248
  ...(ctx.alwaysLoadTools !== undefined ? { alwaysLoadTools: [...ctx.alwaysLoadTools] } : {}),
1240
1249
  ...(ctx.promptProfile !== undefined ? { promptProfile: ctx.promptProfile } : {}),
1241
1250
  enableBlockedReport: false,
1242
- limits: { maxTurns: observerDef.maxTurns ?? 8, timeoutSec: 120 },
1251
+ limits: {
1252
+ maxTurns: typeof observerDef.maxTurns === "number" && Number.isFinite(observerDef.maxTurns) && observerDef.maxTurns > 0 ? observerDef.maxTurns : 8,
1253
+ timeoutSec: 120,
1254
+ },
1243
1255
  });
1244
1256
  const runObserverLeg = async (spec) => {
1245
1257
  if (!isObserverTaskId(sid))
@@ -1683,21 +1695,17 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
1683
1695
  resumable: false,
1684
1696
  ...(completionIdReap !== undefined ? { completionId: completionIdReap } : {}),
1685
1697
  });
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
- }
1698
+ notifier.notify(() => notify?.({
1699
+ task_id: taskId,
1700
+ task_type: "background_agent",
1701
+ toolUseId: ctx.toolCallId,
1702
+ status: "killed",
1703
+ stoppedBy: stoppedByReap,
1704
+ summary: summaryReap,
1705
+ sessionId: forkedId,
1706
+ resumable: false,
1707
+ ...(completionIdReap !== undefined ? { completionId: completionIdReap } : {}),
1708
+ }, { priority: "later" }), "subagent.reapTerminalNotify");
1701
1709
  });
1702
1710
  if (agentName !== undefined) {
1703
1711
  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 +1778,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
1770
1778
  kind: "tick",
1771
1779
  taskId,
1772
1780
  sessionScoped: sessionScopedBg === true,
1781
+ sessionId: forkedId,
1773
1782
  transcriptId: forkedId,
1774
1783
  parentToolCallId: ctx.toolCallId,
1775
1784
  progressTaskId: e.taskId,
@@ -1805,7 +1814,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
1805
1814
  tokens: child.stats.tokens + (nsb?.tokens ?? 0),
1806
1815
  turns: child.stats.turns + (nsb?.turns ?? 0),
1807
1816
  tasks: 1 + (nsb?.tasks ?? 0),
1808
- costMicroUsd: (child.stats.costMicroUsd ?? 0) + (nsb?.costMicroUsd ?? 0),
1817
+ costMicroUsd: rollupDelegatedCost(child.stats, nsb),
1809
1818
  });
1810
1819
  }
1811
1820
  catch {
@@ -1857,7 +1866,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
1857
1866
  summary: forkTerminalSummary,
1858
1867
  ...residualFork,
1859
1868
  resumable: resumableFork,
1860
- usage: { tokens: child.stats.tokens, turns: child.stats.turns, costMicroUsd: (child.stats.costMicroUsd ?? 0) + (child.stats.nested?.costMicroUsd ?? 0) },
1869
+ usage: { tokens: child.stats.tokens, turns: child.stats.turns, ...delegatedCostField(child.stats) },
1861
1870
  ...(completionIdFork !== undefined ? { completionId: completionIdFork } : {}),
1862
1871
  });
1863
1872
  try {
@@ -1876,7 +1885,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
1876
1885
  ...(settledBg === "killed" && child.result ? { partial: true } : {}),
1877
1886
  ...residualFork,
1878
1887
  resumable: resumableFork,
1879
- usage: { tokens: child.stats.tokens, turns: child.stats.turns, costMicroUsd: (child.stats.costMicroUsd ?? 0) + (child.stats.nested?.costMicroUsd ?? 0) },
1888
+ usage: { tokens: child.stats.tokens, turns: child.stats.turns, ...delegatedCostField(child.stats) },
1880
1889
  ...(completionIdFork !== undefined ? { completionId: completionIdFork } : {}),
1881
1890
  }, { priority: "later" });
1882
1891
  }
@@ -1980,7 +1989,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
1980
1989
  tokens: forkChild.stats.tokens + (nsf?.tokens ?? 0),
1981
1990
  turns: forkChild.stats.turns + (nsf?.turns ?? 0),
1982
1991
  tasks: 1 + (nsf?.tasks ?? 0),
1983
- costMicroUsd: (forkChild.stats.costMicroUsd ?? 0) + (nsf?.costMicroUsd ?? 0),
1992
+ costMicroUsd: rollupDelegatedCost(forkChild.stats, nsf),
1984
1993
  });
1985
1994
  if (isDurablePause(forkChild.status)) {
1986
1995
  return {
@@ -2131,21 +2140,17 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2131
2140
  resumable: false,
2132
2141
  ...(completionIdReap !== undefined ? { completionId: completionIdReap } : {}),
2133
2142
  });
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
- }
2143
+ notifier.notify(() => notify?.({
2144
+ task_id: taskId,
2145
+ task_type: "background_agent",
2146
+ toolUseId: ctx.toolCallId,
2147
+ status: "killed",
2148
+ stoppedBy: stoppedByReap,
2149
+ summary: summaryReap,
2150
+ sessionId: bgChildSessionId,
2151
+ resumable: false,
2152
+ ...(completionIdReap !== undefined ? { completionId: completionIdReap } : {}),
2153
+ }, { priority: "later" }), "subagent.reapTerminalNotify");
2149
2154
  });
2150
2155
  const bgDurableProbe = bg.registry.durableAgentRowProbe(taskId);
2151
2156
  const bgRowConfirmed = async () => (bgDurableProbe ? await bgDurableProbe().catch(() => false) : false);
@@ -2249,6 +2254,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2249
2254
  kind: "tick",
2250
2255
  taskId,
2251
2256
  sessionScoped: sessionScopedBg === true,
2257
+ sessionId: bgChildSessionId,
2252
2258
  transcriptId: bgChildSessionId,
2253
2259
  parentToolCallId: ctx.toolCallId,
2254
2260
  progressTaskId: e.taskId,
@@ -2405,7 +2411,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2405
2411
  tokens: child.stats.tokens + (ns2?.tokens ?? 0),
2406
2412
  turns: child.stats.turns + (ns2?.turns ?? 0),
2407
2413
  tasks: 1 + (ns2?.tasks ?? 0),
2408
- costMicroUsd: (child.stats.costMicroUsd ?? 0) + (ns2?.costMicroUsd ?? 0),
2414
+ costMicroUsd: rollupDelegatedCost(child.stats, ns2),
2409
2415
  });
2410
2416
  }
2411
2417
  catch {
@@ -2515,7 +2521,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2515
2521
  summary: bgTerminalSummary,
2516
2522
  ...residual,
2517
2523
  resumable: resumableBg,
2518
- usage: { tokens: child.stats.tokens, turns: child.stats.turns, costMicroUsd: (child.stats.costMicroUsd ?? 0) + (child.stats.nested?.costMicroUsd ?? 0) },
2524
+ usage: { tokens: child.stats.tokens, turns: child.stats.turns, ...delegatedCostField(child.stats) },
2519
2525
  ...(completionIdBg !== undefined ? { completionId: completionIdBg } : {}),
2520
2526
  });
2521
2527
  const completionFrame = {
@@ -2533,7 +2539,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2533
2539
  ...(settled === "killed" && child.result ? { partial: true } : {}),
2534
2540
  ...residual,
2535
2541
  resumable: resumableBg,
2536
- usage: { tokens: child.stats.tokens, turns: child.stats.turns, costMicroUsd: (child.stats.costMicroUsd ?? 0) + (child.stats.nested?.costMicroUsd ?? 0) },
2542
+ usage: { tokens: child.stats.tokens, turns: child.stats.turns, ...delegatedCostField(child.stats) },
2537
2543
  ...(completionIdBg !== undefined ? { completionId: completionIdBg } : {}),
2538
2544
  };
2539
2545
  const gateOwner = ownsTerminalFaces && settled === "completed" && notify !== undefined ? bgRetain?.childSessionId : undefined;
@@ -2685,14 +2691,10 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2685
2691
  sink: ctx.onSubagentSpawn,
2686
2692
  ...(opts.background ? { registry: opts.background.registry } : {}),
2687
2693
  });
2688
- try {
2689
- ctx.onSubagentSpawn(createSteerHandle(stream, ctx.toolCallId, childAgentName, settled, {
2690
- resume,
2691
- ...(retainEntry ? { childSessionId: retainEntry.childSessionId } : {}),
2692
- }));
2693
- }
2694
- catch {
2695
- }
2694
+ notifier.notify(() => ctx.onSubagentSpawn?.(createSteerHandle(stream, ctx.toolCallId, childAgentName, settled, {
2695
+ resume,
2696
+ ...(retainEntry ? { childSessionId: retainEntry.childSessionId } : {}),
2697
+ })), "subagent.onSubagentSpawn");
2696
2698
  }
2697
2699
  try {
2698
2700
  for await (const ev of stream)
@@ -2737,7 +2739,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2737
2739
  tokens: child.stats.tokens + (ns?.tokens ?? 0),
2738
2740
  turns: child.stats.turns + (ns?.turns ?? 0),
2739
2741
  tasks: 1 + (ns?.tasks ?? 0),
2740
- costMicroUsd: (child.stats.costMicroUsd ?? 0) + (ns?.costMicroUsd ?? 0),
2742
+ costMicroUsd: rollupDelegatedCost(child.stats, ns),
2741
2743
  });
2742
2744
  if (isDurablePause(child.status)) {
2743
2745
  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,
@@ -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();
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)
@@ -1,7 +1,8 @@
1
1
  import {} from "../internal/llm.js";
2
2
  import { BrainError } from "./errors.js";
3
3
  import { DEGENERATE_MESSAGE, trimDegenerateTail } from "./repetition.js";
4
- import { createRepetitionPoll, createWalltimeGate } from "./stream-shared.js";
4
+ import { createRepetitionPoll, createWalltimeGate, parseStreamedToolArgs } from "./stream-shared.js";
5
+ import { mintFallbackToolCallId } from "./tool-call-id.js";
5
6
  import { WALLTIME_CUTOFF_MESSAGE } from "./walltime.js";
6
7
  import { emitBrainTelemetry } from "./status-sink.js";
7
8
  import { ANTHROPIC_RESERVED, applyExtraBody, stripAuthHeaders } from "./request-params.js";
@@ -201,23 +202,11 @@ function closeToolUseBlock(acc) {
201
202
  return acc.closedTc;
202
203
  if (!acc.toolName)
203
204
  return undefined;
204
- let args;
205
- if (acc.toolJson) {
206
- try {
207
- const parsed = JSON.parse(acc.toolJson);
208
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
209
- return undefined;
210
- args = parsed;
211
- }
212
- catch {
213
- return undefined;
214
- }
215
- }
216
- else {
217
- args = {};
218
- }
205
+ const args = parseStreamedToolArgs(acc.toolJson);
206
+ if (args === undefined)
207
+ return undefined;
219
208
  if (!acc.toolId)
220
- acc.toolId = `toolu_${Math.random().toString(36).slice(2)}`;
209
+ acc.toolId = mintFallbackToolCallId("toolu_");
221
210
  acc.closedTc = { type: "toolCall", id: acc.toolId, name: acc.toolName, arguments: args };
222
211
  return acc.closedTc;
223
212
  }
@@ -339,6 +328,8 @@ export function createAnthropicBrain(config = {}) {
339
328
  let stopReason = null;
340
329
  let stopDetails;
341
330
  let nextContentIndex = 0;
331
+ let malformedFrames = 0;
332
+ let malformedSample;
342
333
  let degenerate = false;
343
334
  let degenTextBlock;
344
335
  const rep = createRepetitionPoll();
@@ -512,6 +503,8 @@ export function createAnthropicBrain(config = {}) {
512
503
  evt = JSON.parse(data);
513
504
  }
514
505
  catch {
506
+ malformedFrames++;
507
+ malformedSample ??= data.slice(0, 160);
515
508
  return;
516
509
  }
517
510
  handleEvent(evt);
@@ -563,6 +556,12 @@ export function createAnthropicBrain(config = {}) {
563
556
  text: `\n[note: ${malformed.length} tool call(s) were truncated (stop_reason="${stopReason ?? "?"}") and dropped — re-issue them next turn: ${malformed.join("; ")}]`,
564
557
  });
565
558
  }
559
+ if (malformedFrames > 0) {
560
+ finalContent.push({
561
+ type: "text",
562
+ text: `\n[note: ${malformedFrames} provider stream frame(s) were not valid JSON and were dropped — the streamed output may be missing content (first sample: ${JSON.stringify(malformedSample)})]`,
563
+ });
564
+ }
566
565
  const truncatedEmpty = stopReason === "max_tokens" && noUsableContent && !toolError;
567
566
  const dynamicCut = truncatedEmpty && options?.maxTokensDynamic === true;
568
567
  const staticReasoningCut = truncatedEmpty && !dynamicCut && reasoningSeen && options?.staticReasoningCutDowngrade === true;
@@ -586,7 +585,7 @@ export function createAnthropicBrain(config = {}) {
586
585
  ? `[length_empty] response truncated at max_tokens with no answer text (${maxTokensNote}) — the model emitted reasoning although no thinking was requested (a budget-ignoring gateway?) and it consumed the entire output budget; raise max_tokens`
587
586
  : `[length_empty] response truncated at max_tokens with no answer text (${maxTokensNote}); raise max_tokens`
588
587
  : emptyNoFinish
589
- ? `[stream_torn] model stream ended with no content and no stop_reason — the response was lost (a torn stream or an in-band provider error frame)`
588
+ ? `[stream_torn] model stream ended with no content and no stop_reason — the response was lost (a torn stream or an in-band provider error frame)${malformedFrames > 0 ? ` — ${malformedFrames} unparseable data frame(s) were dropped on this stream (first sample: ${JSON.stringify(malformedSample)})` : ""}`
590
589
  : undefined));
591
590
  const errored = walltime.cut ||
592
591
  degenerate ||
@@ -595,9 +594,13 @@ export function createAnthropicBrain(config = {}) {
595
594
  emptyNoFinish ||
596
595
  (toolError !== undefined && noUsableContent) ||
597
596
  (truncatedEmpty && !dynamicCut && !staticReasoningCut);
598
- const errorKind = !walltime.cut && !degenerate && !safetyCut && toolError === undefined && truncatedEmpty && !dynamicCut && !staticReasoningCut
599
- ? "length_empty"
600
- : undefined;
597
+ const errorKind = walltime.cut
598
+ ? "walltime_cutoff"
599
+ : degenerate
600
+ ? "degenerate"
601
+ : !safetyCut && toolError === undefined && truncatedEmpty && !dynamicCut && !staticReasoningCut
602
+ ? "length_empty"
603
+ : undefined;
601
604
  const finalMessage = {
602
605
  ...partial,
603
606
  content: finalContent.length > 0 ? finalContent : [{ type: "text", text: "" }],