@namzu/sdk 36.0.0 → 37.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 (308) hide show
  1. package/CHANGELOG.md +180 -0
  2. package/README.md +68 -41
  3. package/dist/bridge/a2a/mapper.d.ts.map +1 -1
  4. package/dist/bridge/a2a/mapper.js +2 -0
  5. package/dist/bridge/a2a/mapper.js.map +1 -1
  6. package/dist/bridge/sse/mapper.d.ts.map +1 -1
  7. package/dist/bridge/sse/mapper.js +2 -0
  8. package/dist/bridge/sse/mapper.js.map +1 -1
  9. package/dist/compaction/extractor.d.ts +1 -1
  10. package/dist/compaction/extractor.d.ts.map +1 -1
  11. package/dist/compaction/extractor.js +15 -7
  12. package/dist/compaction/extractor.js.map +1 -1
  13. package/dist/compaction/manager.d.ts +2 -0
  14. package/dist/compaction/manager.d.ts.map +1 -1
  15. package/dist/compaction/manager.js +12 -1
  16. package/dist/compaction/manager.js.map +1 -1
  17. package/dist/compaction/verifier.d.ts.map +1 -1
  18. package/dist/compaction/verifier.js +82 -15
  19. package/dist/compaction/verifier.js.map +1 -1
  20. package/dist/contracts/schemas.d.ts +10 -10
  21. package/dist/eval/harness-verification.d.ts +63 -0
  22. package/dist/eval/harness-verification.d.ts.map +1 -0
  23. package/dist/eval/harness-verification.js +165 -0
  24. package/dist/eval/harness-verification.js.map +1 -0
  25. package/dist/eval/index.d.ts +2 -0
  26. package/dist/eval/index.d.ts.map +1 -1
  27. package/dist/eval/index.js +1 -0
  28. package/dist/eval/index.js.map +1 -1
  29. package/dist/execution/code-runtime/json-value.d.ts +10 -0
  30. package/dist/execution/code-runtime/json-value.d.ts.map +1 -0
  31. package/dist/execution/code-runtime/json-value.js +89 -0
  32. package/dist/execution/code-runtime/json-value.js.map +1 -0
  33. package/dist/execution/code-runtime/worker-program.d.ts +8 -0
  34. package/dist/execution/code-runtime/worker-program.d.ts.map +1 -0
  35. package/dist/execution/code-runtime/worker-program.js +240 -0
  36. package/dist/execution/code-runtime/worker-program.js.map +1 -0
  37. package/dist/execution/code-runtime/worker.d.ts +22 -0
  38. package/dist/execution/code-runtime/worker.d.ts.map +1 -1
  39. package/dist/execution/code-runtime/worker.js +148 -111
  40. package/dist/execution/code-runtime/worker.js.map +1 -1
  41. package/dist/manager/agent/lifecycle.d.ts +12 -0
  42. package/dist/manager/agent/lifecycle.d.ts.map +1 -1
  43. package/dist/manager/agent/lifecycle.js +306 -37
  44. package/dist/manager/agent/lifecycle.js.map +1 -1
  45. package/dist/manager/run/persistence.d.ts +2 -0
  46. package/dist/manager/run/persistence.d.ts.map +1 -1
  47. package/dist/manager/run/persistence.js +6 -0
  48. package/dist/manager/run/persistence.js.map +1 -1
  49. package/dist/provider/capabilities.d.ts +3 -1
  50. package/dist/provider/capabilities.d.ts.map +1 -1
  51. package/dist/provider/capabilities.js +13 -0
  52. package/dist/provider/capabilities.js.map +1 -1
  53. package/dist/provider/fallback.d.ts.map +1 -1
  54. package/dist/provider/fallback.js +2 -0
  55. package/dist/provider/fallback.js.map +1 -1
  56. package/dist/public-runtime.d.ts +3 -1
  57. package/dist/public-runtime.d.ts.map +1 -1
  58. package/dist/public-runtime.js +2 -1
  59. package/dist/public-runtime.js.map +1 -1
  60. package/dist/public-tools.d.ts +1 -1
  61. package/dist/public-tools.d.ts.map +1 -1
  62. package/dist/public-tools.js +1 -1
  63. package/dist/public-tools.js.map +1 -1
  64. package/dist/public-types.d.ts +1 -0
  65. package/dist/public-types.d.ts.map +1 -1
  66. package/dist/registry/tool/execute.d.ts +3 -0
  67. package/dist/registry/tool/execute.d.ts.map +1 -1
  68. package/dist/registry/tool/execute.js +11 -80
  69. package/dist/registry/tool/execute.js.map +1 -1
  70. package/dist/run/command-gate.d.ts.map +1 -1
  71. package/dist/run/command-gate.js +65 -12
  72. package/dist/run/command-gate.js.map +1 -1
  73. package/dist/run/index.d.ts +2 -0
  74. package/dist/run/index.d.ts.map +1 -1
  75. package/dist/run/index.js +1 -0
  76. package/dist/run/index.js.map +1 -1
  77. package/dist/run/memory-promoter.d.ts +3 -35
  78. package/dist/run/memory-promoter.d.ts.map +1 -1
  79. package/dist/run/memory-promoter.js +32 -40
  80. package/dist/run/memory-promoter.js.map +1 -1
  81. package/dist/run/memory-recall.d.ts +25 -0
  82. package/dist/run/memory-recall.d.ts.map +1 -0
  83. package/dist/run/memory-recall.js +228 -0
  84. package/dist/run/memory-recall.js.map +1 -0
  85. package/dist/run/reporter.d.ts.map +1 -1
  86. package/dist/run/reporter.js +1 -0
  87. package/dist/run/reporter.js.map +1 -1
  88. package/dist/run/token-budget.d.ts +2 -0
  89. package/dist/run/token-budget.d.ts.map +1 -1
  90. package/dist/run/token-budget.js +4 -0
  91. package/dist/run/token-budget.js.map +1 -1
  92. package/dist/run/workspace-fingerprint.d.ts +10 -3
  93. package/dist/run/workspace-fingerprint.d.ts.map +1 -1
  94. package/dist/run/workspace-fingerprint.js +17 -8
  95. package/dist/run/workspace-fingerprint.js.map +1 -1
  96. package/dist/runtime/query/checkpoint.d.ts +14 -0
  97. package/dist/runtime/query/checkpoint.d.ts.map +1 -1
  98. package/dist/runtime/query/checkpoint.js +82 -1
  99. package/dist/runtime/query/checkpoint.js.map +1 -1
  100. package/dist/runtime/query/executor.d.ts +5 -0
  101. package/dist/runtime/query/executor.d.ts.map +1 -1
  102. package/dist/runtime/query/executor.js +70 -14
  103. package/dist/runtime/query/executor.js.map +1 -1
  104. package/dist/runtime/query/index.d.ts +5 -8
  105. package/dist/runtime/query/index.d.ts.map +1 -1
  106. package/dist/runtime/query/index.js +54 -7
  107. package/dist/runtime/query/index.js.map +1 -1
  108. package/dist/runtime/query/iteration/index.d.ts +10 -0
  109. package/dist/runtime/query/iteration/index.d.ts.map +1 -1
  110. package/dist/runtime/query/iteration/index.js +325 -36
  111. package/dist/runtime/query/iteration/index.js.map +1 -1
  112. package/dist/runtime/query/iteration/native-output.d.ts +10 -0
  113. package/dist/runtime/query/iteration/native-output.d.ts.map +1 -0
  114. package/dist/runtime/query/iteration/native-output.js +32 -0
  115. package/dist/runtime/query/iteration/native-output.js.map +1 -0
  116. package/dist/runtime/query/iteration/phases/advisory.d.ts.map +1 -1
  117. package/dist/runtime/query/iteration/phases/advisory.js +18 -13
  118. package/dist/runtime/query/iteration/phases/advisory.js.map +1 -1
  119. package/dist/runtime/query/iteration/phases/compaction.d.ts +2 -0
  120. package/dist/runtime/query/iteration/phases/compaction.d.ts.map +1 -1
  121. package/dist/runtime/query/iteration/phases/compaction.js +32 -11
  122. package/dist/runtime/query/iteration/phases/compaction.js.map +1 -1
  123. package/dist/runtime/query/iteration/phases/context.d.ts +9 -0
  124. package/dist/runtime/query/iteration/phases/context.d.ts.map +1 -1
  125. package/dist/runtime/query/iteration/phases/context.js.map +1 -1
  126. package/dist/runtime/query/iteration/phases/tool-review.d.ts.map +1 -1
  127. package/dist/runtime/query/iteration/phases/tool-review.js +1 -1
  128. package/dist/runtime/query/iteration/phases/tool-review.js.map +1 -1
  129. package/dist/runtime/query/iteration/phases/working-memory.d.ts +1 -1
  130. package/dist/runtime/query/iteration/phases/working-memory.d.ts.map +1 -1
  131. package/dist/runtime/query/iteration/phases/working-memory.js +23 -6
  132. package/dist/runtime/query/iteration/phases/working-memory.js.map +1 -1
  133. package/dist/runtime/query/iteration/stream-turn.d.ts.map +1 -1
  134. package/dist/runtime/query/iteration/stream-turn.js +2 -0
  135. package/dist/runtime/query/iteration/stream-turn.js.map +1 -1
  136. package/dist/runtime/query/steering.d.ts +6 -2
  137. package/dist/runtime/query/steering.d.ts.map +1 -1
  138. package/dist/runtime/query/steering.js +17 -2
  139. package/dist/runtime/query/steering.js.map +1 -1
  140. package/dist/runtime/query/tool-call-budget.d.ts +19 -0
  141. package/dist/runtime/query/tool-call-budget.d.ts.map +1 -0
  142. package/dist/runtime/query/tool-call-budget.js +110 -0
  143. package/dist/runtime/query/tool-call-budget.js.map +1 -0
  144. package/dist/runtime/query/tooling.d.ts +2 -0
  145. package/dist/runtime/query/tooling.d.ts.map +1 -1
  146. package/dist/runtime/query/tooling.js +4 -0
  147. package/dist/runtime/query/tooling.js.map +1 -1
  148. package/dist/scheduler/local.d.ts.map +1 -1
  149. package/dist/scheduler/local.js +1 -0
  150. package/dist/scheduler/local.js.map +1 -1
  151. package/dist/session/handoff/capacity.d.ts +2 -2
  152. package/dist/session/handoff/capacity.d.ts.map +1 -1
  153. package/dist/session/handoff/capacity.js +3 -2
  154. package/dist/session/handoff/capacity.js.map +1 -1
  155. package/dist/store/memory/disk.d.ts +6 -2
  156. package/dist/store/memory/disk.d.ts.map +1 -1
  157. package/dist/store/memory/disk.js +42 -25
  158. package/dist/store/memory/disk.js.map +1 -1
  159. package/dist/store/memory/index.d.ts +4 -0
  160. package/dist/store/memory/index.d.ts.map +1 -1
  161. package/dist/store/memory/index.js +55 -19
  162. package/dist/store/memory/index.js.map +1 -1
  163. package/dist/store/memory/memory.d.ts +3 -2
  164. package/dist/store/memory/memory.d.ts.map +1 -1
  165. package/dist/store/memory/memory.js +11 -2
  166. package/dist/store/memory/memory.js.map +1 -1
  167. package/dist/store/memory/operation-lock.d.ts +11 -0
  168. package/dist/store/memory/operation-lock.d.ts.map +1 -0
  169. package/dist/store/memory/operation-lock.js +94 -0
  170. package/dist/store/memory/operation-lock.js.map +1 -0
  171. package/dist/tools/builtins/computer-use.d.ts.map +1 -1
  172. package/dist/tools/builtins/computer-use.js +60 -10
  173. package/dist/tools/builtins/computer-use.js.map +1 -1
  174. package/dist/tools/builtins/run-code.d.ts +6 -0
  175. package/dist/tools/builtins/run-code.d.ts.map +1 -1
  176. package/dist/tools/builtins/run-code.js +15 -4
  177. package/dist/tools/builtins/run-code.js.map +1 -1
  178. package/dist/tools/builtins/search-tools.d.ts.map +1 -1
  179. package/dist/tools/builtins/search-tools.js +14 -2
  180. package/dist/tools/builtins/search-tools.js.map +1 -1
  181. package/dist/tools/defineTool.d.ts +2 -0
  182. package/dist/tools/defineTool.d.ts.map +1 -1
  183. package/dist/tools/defineTool.js +3 -0
  184. package/dist/tools/defineTool.js.map +1 -1
  185. package/dist/tools/memory/delete.d.ts +4 -0
  186. package/dist/tools/memory/delete.d.ts.map +1 -0
  187. package/dist/tools/memory/delete.js +26 -0
  188. package/dist/tools/memory/delete.js.map +1 -0
  189. package/dist/tools/memory/index.d.ts +2 -0
  190. package/dist/tools/memory/index.d.ts.map +1 -1
  191. package/dist/tools/memory/index.js +6 -0
  192. package/dist/tools/memory/index.js.map +1 -1
  193. package/dist/tools/memory/save.d.ts.map +1 -1
  194. package/dist/tools/memory/save.js +3 -2
  195. package/dist/tools/memory/save.js.map +1 -1
  196. package/dist/tools/memory/search.d.ts.map +1 -1
  197. package/dist/tools/memory/search.js +12 -5
  198. package/dist/tools/memory/search.js.map +1 -1
  199. package/dist/tools/memory/update.d.ts +4 -0
  200. package/dist/tools/memory/update.d.ts.map +1 -0
  201. package/dist/tools/memory/update.js +47 -0
  202. package/dist/tools/memory/update.js.map +1 -0
  203. package/dist/types/agent/scheduler.d.ts +5 -0
  204. package/dist/types/agent/scheduler.d.ts.map +1 -1
  205. package/dist/types/agent/task.d.ts +10 -0
  206. package/dist/types/agent/task.d.ts.map +1 -1
  207. package/dist/types/computer-use/index.d.ts +6 -0
  208. package/dist/types/computer-use/index.d.ts.map +1 -1
  209. package/dist/types/computer-use/index.js.map +1 -1
  210. package/dist/types/errors/catalog.js +1 -1
  211. package/dist/types/errors/catalog.js.map +1 -1
  212. package/dist/types/hitl/index.d.ts +11 -1
  213. package/dist/types/hitl/index.d.ts.map +1 -1
  214. package/dist/types/hitl/index.js.map +1 -1
  215. package/dist/types/memory/index.d.ts +14 -1
  216. package/dist/types/memory/index.d.ts.map +1 -1
  217. package/dist/types/project/entity.d.ts +1 -0
  218. package/dist/types/project/entity.d.ts.map +1 -1
  219. package/dist/types/provider/config.d.ts +2 -0
  220. package/dist/types/provider/config.d.ts.map +1 -1
  221. package/dist/types/provider/model.d.ts +4 -0
  222. package/dist/types/provider/model.d.ts.map +1 -1
  223. package/dist/types/run/answer-review.d.ts +2 -0
  224. package/dist/types/run/answer-review.d.ts.map +1 -1
  225. package/dist/types/run/events.d.ts +9 -0
  226. package/dist/types/run/events.d.ts.map +1 -1
  227. package/dist/types/run/events.js.map +1 -1
  228. package/dist/types/run/prepare-step.d.ts +19 -1
  229. package/dist/types/run/prepare-step.d.ts.map +1 -1
  230. package/dist/types/structured-output/index.d.ts +8 -2
  231. package/dist/types/structured-output/index.d.ts.map +1 -1
  232. package/dist/types/tool/index.d.ts +15 -0
  233. package/dist/types/tool/index.d.ts.map +1 -1
  234. package/dist/types/tool/index.js.map +1 -1
  235. package/dist/utils/json-snapshot.d.ts +3 -0
  236. package/dist/utils/json-snapshot.d.ts.map +1 -0
  237. package/dist/utils/json-snapshot.js +79 -0
  238. package/dist/utils/json-snapshot.js.map +1 -0
  239. package/package.json +4 -2
  240. package/src/bridge/a2a/mapper.ts +2 -0
  241. package/src/bridge/sse/mapper.ts +2 -0
  242. package/src/compaction/extractor.ts +21 -8
  243. package/src/compaction/manager.ts +11 -1
  244. package/src/compaction/verifier.ts +80 -18
  245. package/src/eval/harness-verification.ts +270 -0
  246. package/src/eval/index.ts +10 -0
  247. package/src/execution/code-runtime/json-value.ts +82 -0
  248. package/src/execution/code-runtime/worker-program.ts +239 -0
  249. package/src/execution/code-runtime/worker.ts +164 -111
  250. package/src/manager/agent/lifecycle.ts +340 -45
  251. package/src/manager/run/persistence.ts +7 -0
  252. package/src/provider/capabilities.ts +25 -1
  253. package/src/provider/fallback.ts +2 -0
  254. package/src/public-runtime.ts +4 -0
  255. package/src/public-tools.ts +5 -1
  256. package/src/public-types.ts +1 -0
  257. package/src/registry/tool/execute.ts +17 -79
  258. package/src/run/command-gate.ts +61 -13
  259. package/src/run/index.ts +2 -0
  260. package/src/run/memory-promoter.ts +32 -40
  261. package/src/run/memory-recall.ts +254 -0
  262. package/src/run/reporter.ts +1 -0
  263. package/src/run/token-budget.ts +4 -0
  264. package/src/run/workspace-fingerprint.ts +17 -8
  265. package/src/runtime/query/checkpoint.ts +96 -2
  266. package/src/runtime/query/executor.ts +77 -14
  267. package/src/runtime/query/index.ts +73 -16
  268. package/src/runtime/query/iteration/index.ts +377 -44
  269. package/src/runtime/query/iteration/native-output.ts +35 -0
  270. package/src/runtime/query/iteration/phases/advisory.ts +21 -11
  271. package/src/runtime/query/iteration/phases/compaction.ts +36 -14
  272. package/src/runtime/query/iteration/phases/context.ts +9 -0
  273. package/src/runtime/query/iteration/phases/tool-review.ts +5 -1
  274. package/src/runtime/query/iteration/phases/working-memory.ts +23 -5
  275. package/src/runtime/query/iteration/stream-turn.ts +2 -0
  276. package/src/runtime/query/steering.ts +19 -2
  277. package/src/runtime/query/tool-call-budget.ts +115 -0
  278. package/src/runtime/query/tooling.ts +6 -0
  279. package/src/scheduler/local.ts +1 -0
  280. package/src/session/handoff/capacity.ts +7 -3
  281. package/src/store/memory/disk.ts +52 -30
  282. package/src/store/memory/index.ts +75 -27
  283. package/src/store/memory/memory.ts +18 -6
  284. package/src/store/memory/operation-lock.ts +99 -0
  285. package/src/tools/builtins/computer-use.ts +61 -7
  286. package/src/tools/builtins/run-code.ts +24 -5
  287. package/src/tools/builtins/search-tools.ts +18 -3
  288. package/src/tools/defineTool.ts +5 -0
  289. package/src/tools/memory/delete.ts +29 -0
  290. package/src/tools/memory/index.ts +6 -0
  291. package/src/tools/memory/save.ts +4 -2
  292. package/src/tools/memory/search.ts +12 -5
  293. package/src/tools/memory/update.ts +50 -0
  294. package/src/types/agent/scheduler.ts +6 -0
  295. package/src/types/agent/task.ts +13 -0
  296. package/src/types/computer-use/index.ts +6 -0
  297. package/src/types/errors/catalog.ts +1 -1
  298. package/src/types/hitl/index.ts +11 -1
  299. package/src/types/memory/index.ts +16 -1
  300. package/src/types/project/entity.ts +1 -0
  301. package/src/types/provider/config.ts +2 -0
  302. package/src/types/provider/model.ts +4 -0
  303. package/src/types/run/answer-review.ts +2 -0
  304. package/src/types/run/events.ts +10 -0
  305. package/src/types/run/prepare-step.ts +19 -1
  306. package/src/types/structured-output/index.ts +15 -2
  307. package/src/types/tool/index.ts +16 -0
  308. package/src/utils/json-snapshot.ts +76 -0
@@ -1,4 +1,5 @@
1
1
  import { AGENT_MANAGER_DEFAULTS } from '../../constants/agent/index.js';
2
+ import { EMPTY_TOKEN_USAGE } from '../../constants/limits.js';
2
3
  import { GENAI } from '../../constants/telemetry/index.js';
3
4
  import { DelegationCapacityExceeded, } from '../../session/handoff/capacity.js';
4
5
  import { isTerminalAgentTaskState } from '../../types/agent/task.js';
@@ -13,6 +14,9 @@ import { SCOPE_ATTRIBUTE } from '../../utils/log/types.js';
13
14
  import { resolveLogger } from '../../utils/logger.js';
14
15
  import { requireOpenProject } from '../project/lifecycle.js';
15
16
  import { resolveTopicManager } from '../topic/dependency.js';
17
+ /** Internal backpressure: the parent's unmeasured request owns its budget until its receipt. */
18
+ class ParentBudgetRequestPending extends Error {
19
+ }
16
20
  /**
17
21
  * Combine a child's own environment with what its parent passed down.
18
22
  *
@@ -42,9 +46,18 @@ export class AgentManager {
42
46
  spawnLocks = new Map();
43
47
  deps;
44
48
  topicManager;
49
+ pendingSpawns = new Map();
50
+ drainingParents = new Set();
51
+ executingTasks = new Set();
52
+ cancelingTasks = new Set();
53
+ admissionTimer;
54
+ disposed = false;
45
55
  constructor(registry, config, deps) {
46
56
  this.registry = registry;
47
57
  this.config = { ...AGENT_MANAGER_DEFAULTS, ...config };
58
+ const maxPending = this.config.maxPendingTasks ?? 128;
59
+ if (!Number.isSafeInteger(maxPending) || maxPending < 1)
60
+ throw new Error('maxPendingTasks must be a positive safe integer');
48
61
  this.log = resolveLogger(deps.log).child({
49
62
  [SCOPE_ATTRIBUTE]: 'manager/agent/lifecycle',
50
63
  });
@@ -52,6 +65,170 @@ export class AgentManager {
52
65
  this.topicManager = resolveTopicManager(deps);
53
66
  }
54
67
  async sendMessage(options, context, listener) {
68
+ if (this.disposed)
69
+ throw new Error('Agent manager is disposed');
70
+ if (this.config.capacityBehavior === 'queue')
71
+ return this.enqueueMessage(options, context, listener);
72
+ return this.startMessage(options, context, listener);
73
+ }
74
+ async enqueueMessage(options, context, listener) {
75
+ context.parentAbortController.signal.throwIfAborted();
76
+ if (context.depth >= this.config.maxDepth)
77
+ throw new Error(`Max task depth ${this.config.maxDepth} exceeded`);
78
+ if (options.tenantId !== context.tenantId)
79
+ throw new Error('Tenant mismatch: cross-tenant spawn rejected');
80
+ const definition = this.registry.getOrThrow(options.agentId);
81
+ const count = [...this.pendingSpawns.values()].reduce((sum, queue) => sum + queue.length, 0);
82
+ if (count >= (this.config.maxPendingTasks ?? 128))
83
+ throw new Error(`Delegation queue is full (${this.config.maxPendingTasks ?? 128} pending tasks). Wait for existing tasks before submitting more work.`);
84
+ const task = {
85
+ taskId: generateTaskId(),
86
+ agentId: options.agentId,
87
+ agent: definition.typedAgent,
88
+ childAbortController: createChildAbortController(context.parentAbortController),
89
+ context,
90
+ state: 'pending',
91
+ pendingMessages: [],
92
+ createdAt: Date.now(),
93
+ runEventListener: listener,
94
+ };
95
+ this.instances.set(task.taskId, task);
96
+ const onAbort = () => {
97
+ // Before admission this observer owns cancellation. Once child scope
98
+ // exists, startup rollback may abort its controller for a configuration
99
+ // error; that is a failure, not a user cancellation.
100
+ if (task.context === context || context.parentAbortController.signal.aborted)
101
+ this.cancel(task.taskId, 'parent');
102
+ };
103
+ task.childAbortController.signal.addEventListener('abort', onAbort, { once: true });
104
+ const entry = {
105
+ ready: false,
106
+ task,
107
+ options,
108
+ context,
109
+ listener,
110
+ removeAbortListener: () => task.childAbortController.signal.removeEventListener('abort', onAbort),
111
+ };
112
+ const queue = this.pendingSpawns.get(options.parentSessionId) ?? [];
113
+ queue.push(entry);
114
+ this.pendingSpawns.set(options.parentSessionId, queue);
115
+ this.emit({
116
+ type: 'pending',
117
+ taskId: task.taskId,
118
+ agentId: options.agentId,
119
+ parentAgentId: context.parentAgentId,
120
+ depth: context.depth,
121
+ });
122
+ try {
123
+ await listener?.({
124
+ type: 'agent_pending',
125
+ runId: context.parentRunId,
126
+ taskId: task.taskId,
127
+ parentAgentId: context.parentAgentId,
128
+ childAgentId: options.agentId,
129
+ depth: context.depth,
130
+ });
131
+ entry.ready = true;
132
+ }
133
+ catch (error) {
134
+ this.failAdmission(entry, error);
135
+ }
136
+ this.pumpAdmissions(options.parentSessionId);
137
+ return task;
138
+ }
139
+ pumpAdmissions(parentSessionId) {
140
+ if (this.disposed || this.drainingParents.has(parentSessionId))
141
+ return;
142
+ this.drainingParents.add(parentSessionId);
143
+ void this.drainAdmissions(parentSessionId)
144
+ .finally(() => {
145
+ this.drainingParents.delete(parentSessionId);
146
+ // External handoffs and metadata changes have no manager event. A
147
+ // single slow, unref'ed timer observes those without spinning or
148
+ // keeping a host alive. Actual completions wake admission directly.
149
+ if (!this.disposed && this.pendingSpawns.size > 0 && !this.admissionTimer) {
150
+ this.admissionTimer = setTimeout(() => {
151
+ this.admissionTimer = undefined;
152
+ for (const parent of this.pendingSpawns.keys())
153
+ this.pumpAdmissions(parent);
154
+ }, 250);
155
+ this.admissionTimer.unref?.();
156
+ }
157
+ })
158
+ .catch((error) => this.log.error('Delegation admission failed', {
159
+ 'exception.message': toErrorMessage(error),
160
+ }));
161
+ }
162
+ async drainAdmissions(parentSessionId) {
163
+ const queue = this.pendingSpawns.get(parentSessionId);
164
+ if (!queue)
165
+ return;
166
+ while (!this.disposed && queue.length > 0) {
167
+ const entry = queue[0];
168
+ if (!entry)
169
+ break;
170
+ if (isTerminalAgentTaskState(entry.task.state)) {
171
+ entry.removeAbortListener();
172
+ queue.shift();
173
+ continue;
174
+ }
175
+ // A concurrent enqueue can pump this same FIFO while the head's
176
+ // pending listener still owns its acknowledgement. Do not provision
177
+ // or spend until that acknowledgement succeeds.
178
+ if (!entry.ready)
179
+ return;
180
+ try {
181
+ await entry.options.beforeStart?.();
182
+ entry.task.childAbortController.signal.throwIfAborted();
183
+ await this.validateSpawn(entry.options, entry.context);
184
+ entry.task.childAbortController.signal.throwIfAborted();
185
+ await this.startMessage(entry.options, entry.context, entry.listener, entry.task);
186
+ entry.removeAbortListener();
187
+ }
188
+ catch (error) {
189
+ if ((error instanceof ParentBudgetRequestPending ||
190
+ (error instanceof DelegationCapacityExceeded && error.details.dimension === 'width')) &&
191
+ !isTerminalAgentTaskState(entry.task.state))
192
+ return;
193
+ this.failAdmission(entry, error);
194
+ }
195
+ queue.shift();
196
+ }
197
+ if (queue.length === 0 && this.pendingSpawns.get(parentSessionId) === queue)
198
+ this.pendingSpawns.delete(parentSessionId);
199
+ }
200
+ failAdmission(entry, reason) {
201
+ entry.removeAbortListener();
202
+ const task = entry.task;
203
+ if (isTerminalAgentTaskState(task.state))
204
+ return;
205
+ task.childAbortController.abort(reason);
206
+ const error = toErrorMessage(reason);
207
+ task.result = {
208
+ runId: entry.context.parentRunId,
209
+ status: 'failed',
210
+ usage: { ...EMPTY_TOKEN_USAGE },
211
+ cost: { ...ZERO_COST },
212
+ iterations: 0,
213
+ durationMs: Date.now() - task.createdAt,
214
+ messages: [],
215
+ lastError: error,
216
+ };
217
+ task.state = 'failed';
218
+ task.completedAt = Date.now();
219
+ this.emit({ type: 'failed', taskId: task.taskId, error });
220
+ this.emitRunEvent(task, {
221
+ type: 'agent_failed',
222
+ runId: entry.context.parentRunId,
223
+ taskId: task.taskId,
224
+ error,
225
+ });
226
+ this.scheduleEviction(task.taskId);
227
+ this.resolveCompletionCallbacks(task.taskId);
228
+ }
229
+ async startMessage(options, context, listener, queuedTask) {
230
+ await options.beforeStart?.();
231
+ queuedTask?.childAbortController.signal.throwIfAborted();
55
232
  if (context.depth >= this.config.maxDepth) {
56
233
  throw new Error(`Max task depth ${this.config.maxDepth} exceeded (current: ${context.depth}). Recursive agent delegation is limited to prevent resource exhaustion.`);
57
234
  }
@@ -83,9 +260,21 @@ export class AgentManager {
83
260
  // Reserve synchronously before provisioning yields. The authority is shared
84
261
  // across managers and nested sessions, while the session lock below only
85
262
  // serializes filesystem capacity checks.
263
+ // Queued siblings share the available budget with the parent. The first
264
+ // of eight children receives at most one ninth; later siblings receive
265
+ // comparable grants instead of a geometric 1/2, 1/4, ... starvation tail.
266
+ let budgetShares = 1;
267
+ if (queuedTask) {
268
+ const project = await requireOpenProject(this.deps.sessionStore, context.projectId, context.tenantId, 'spawn');
269
+ const children = await this.deps.sessionStore.getChildren(options.parentSessionId, context.tenantId);
270
+ const active = children.filter((child) => child.status !== 'idle' && child.status !== 'failed' && child.status !== 'archived').length;
271
+ budgetShares = Math.max(2, project.config.maxDelegationWidth - active + 1);
272
+ }
273
+ context.parentAbortController.signal.throwIfAborted();
274
+ queuedTask?.childAbortController.signal.throwIfAborted();
86
275
  const remaining = context.budget.remaining;
87
276
  const maxAllocation = Number.isFinite(remaining)
88
- ? Math.floor(remaining * this.config.maxBudgetFraction)
277
+ ? Math.floor(Math.min(remaining * this.config.maxBudgetFraction, remaining / budgetShares))
89
278
  : (options.budgetAllocation?.tokenBudget ??
90
279
  (options.configOverrides?.tokenBudget === 0
91
280
  ? 200_000
@@ -97,6 +286,12 @@ export class AgentManager {
97
286
  message: `Cannot spawn "${options.agentId}": the parent has ${remaining} tokens remaining; a child allocation must be a finite positive integer.`,
98
287
  });
99
288
  }
289
+ // No await between this check and reserve: a parent's response may start
290
+ // during the scope reads above. Its own in-flight request is temporary
291
+ // backpressure; descendant requests are expected during a fan-out.
292
+ // Invalid/zero allocations above still fail rather than waiting forever.
293
+ if (queuedTask && context.budget.hasInFlightRequest)
294
+ throw new ParentBudgetRequestPending('The parent provider request is still in flight');
100
295
  const childBudget = context.budget.reserve(allocatedTokens);
101
296
  let spawnRecord;
102
297
  try {
@@ -111,8 +306,11 @@ export class AgentManager {
111
306
  let childAbortController;
112
307
  let agentTask;
113
308
  try {
114
- childAbortController = createChildAbortController(context.parentAbortController);
115
- const taskId = generateTaskId();
309
+ childAbortController =
310
+ queuedTask?.childAbortController ??
311
+ createChildAbortController(context.parentAbortController);
312
+ childAbortController.signal.throwIfAborted();
313
+ const taskId = queuedTask?.taskId ?? generateTaskId();
116
314
  const childParentActor = {
117
315
  kind: 'agent',
118
316
  agentId: context.parentAgentId,
@@ -155,38 +353,40 @@ export class AgentManager {
155
353
  parentActor: childParentActor,
156
354
  ...(resolvedDenies.length > 0 ? { toolDenies: resolvedDenies } : {}),
157
355
  };
158
- agentTask = {
356
+ agentTask = Object.assign(queuedTask ?? {}, {
159
357
  taskId,
160
358
  agentId: options.agentId,
161
359
  agent,
162
360
  childAbortController,
163
361
  context: childContext,
164
362
  state: 'pending',
165
- pendingMessages: [],
166
- createdAt: Date.now(),
363
+ pendingMessages: queuedTask?.pendingMessages ?? [],
364
+ createdAt: queuedTask?.createdAt ?? Date.now(),
167
365
  runEventListener: listener,
168
- };
366
+ });
169
367
  childAbortController.signal.throwIfAborted();
170
368
  this.instances.set(taskId, agentTask);
171
369
  if (resolvedDenies.length > 0)
172
370
  spawnRecord.resolvedToolDenies = resolvedDenies;
173
371
  this.spawnRecords.set(taskId, spawnRecord);
174
- this.emit({
175
- type: 'pending',
176
- taskId,
177
- agentId: options.agentId,
178
- parentAgentId: context.parentAgentId,
179
- depth: context.depth,
180
- });
181
- if (listener) {
182
- await listener({
183
- type: 'agent_pending',
184
- runId: context.parentRunId,
372
+ if (!queuedTask)
373
+ this.emit({
374
+ type: 'pending',
185
375
  taskId,
376
+ agentId: options.agentId,
186
377
  parentAgentId: context.parentAgentId,
187
- childAgentId: options.agentId,
188
378
  depth: context.depth,
189
379
  });
380
+ if (listener) {
381
+ if (!queuedTask)
382
+ await listener({
383
+ type: 'agent_pending',
384
+ runId: context.parentRunId,
385
+ taskId,
386
+ parentAgentId: context.parentAgentId,
387
+ childAgentId: options.agentId,
388
+ depth: context.depth,
389
+ });
190
390
  const lineage = {
191
391
  parentSessionId: spawnRecord.parentSessionId,
192
392
  rootSessionId: spawnRecord.rootSessionId,
@@ -373,7 +573,10 @@ export class AgentManager {
373
573
  childAbortController.signal.throwIfAborted();
374
574
  await childBudget.flush();
375
575
  childAbortController.signal.throwIfAborted();
376
- this.runChild(agentTask, options, childConfig, listener).catch(async (err) => {
576
+ this.executingTasks.add(taskId);
577
+ const runningTask = agentTask;
578
+ this.runChild(runningTask, options, childConfig, listener)
579
+ .catch(async (err) => {
377
580
  // A thrown invocation supplied no final usage receipt. Keep its
378
581
  // reservation, including when its task handle was canceled or evicted.
379
582
  let failure = err;
@@ -383,8 +586,24 @@ export class AgentManager {
383
586
  catch (writeError) {
384
587
  failure = writeError;
385
588
  }
386
- this.markFailed(taskId, toErrorMessage(failure));
387
- });
589
+ // Cancellation may already have published a terminal handle. Its
590
+ // invocation has only NOW stopped; release the persisted edge here.
591
+ if (!this.instances.has(taskId) || isTerminalAgentTaskState(runningTask.state)) {
592
+ await this.failSubSession(spawnRecord);
593
+ }
594
+ else
595
+ this.markFailed(taskId, toErrorMessage(failure));
596
+ })
597
+ .finally(() => {
598
+ this.executingTasks.delete(taskId);
599
+ if (!this.instances.has(taskId))
600
+ this.spawnRecords.delete(taskId);
601
+ this.pumpAdmissions(options.parentSessionId);
602
+ })
603
+ .catch((error) => this.log.warn('Child cleanup failed', {
604
+ 'namzu.task.id': taskId,
605
+ 'exception.message': toErrorMessage(error),
606
+ }));
388
607
  return agentTask;
389
608
  }
390
609
  catch (err) {
@@ -392,7 +611,7 @@ export class AgentManager {
392
611
  // provisioned resources are still ours to return. A builder/listener
393
612
  // failure must not strand a pending task the caller never received.
394
613
  if (agentTask) {
395
- await this.rollbackUnstartedSpawn(agentTask, spawnRecord, err);
614
+ await this.rollbackUnstartedSpawn(agentTask, spawnRecord, err, queuedTask !== undefined);
396
615
  }
397
616
  else {
398
617
  childAbortController?.abort();
@@ -403,7 +622,7 @@ export class AgentManager {
403
622
  throw err;
404
623
  }
405
624
  }
406
- async rollbackUnstartedSpawn(agentTask, spawnRecord, reason) {
625
+ async rollbackUnstartedSpawn(agentTask, spawnRecord, reason, retainHandle = false) {
407
626
  agentTask.childAbortController.abort();
408
627
  agentTask.context.budget.settle(0);
409
628
  await agentTask.context.budget.flush();
@@ -427,7 +646,22 @@ export class AgentManager {
427
646
  }
428
647
  }
429
648
  this.clearEvictionTimer(agentTask.taskId);
430
- this.instances.delete(agentTask.taskId);
649
+ if (retainHandle) {
650
+ agentTask.result ??= {
651
+ runId: agentTask.context.parentRunId,
652
+ status: 'failed',
653
+ usage: { ...EMPTY_TOKEN_USAGE },
654
+ cost: { ...ZERO_COST },
655
+ iterations: 0,
656
+ durationMs: Date.now() - agentTask.createdAt,
657
+ messages: [],
658
+ lastError: toErrorMessage(reason),
659
+ };
660
+ agentTask.completedAt = Date.now();
661
+ this.scheduleEviction(agentTask.taskId);
662
+ }
663
+ else
664
+ this.instances.delete(agentTask.taskId);
431
665
  this.spawnRecords.delete(agentTask.taskId);
432
666
  this.resolveCompletionCallbacks(agentTask.taskId);
433
667
  await this.rollbackSpawnResources(spawnRecord);
@@ -448,14 +682,24 @@ export class AgentManager {
448
682
  }
449
683
  }
450
684
  cancel(taskId, cause) {
685
+ if (this.cancelingTasks.has(taskId))
686
+ return;
451
687
  const agentTask = this.instances.get(taskId);
452
688
  if (!agentTask || isTerminalAgentTaskState(agentTask.state))
453
689
  return;
454
690
  // Was the bare string `'canceled'`, which `abortReasonText` suppresses
455
691
  // by name — its docblock cites this exact call site — so the child's
456
692
  // run saw a cancellation with no attributable origin at all.
457
- agentTask.childAbortController.abort(cause ? new RunCancelled(cause) : undefined);
458
- this.markCanceled(taskId, cause);
693
+ // Abort listeners run synchronously. A pending task's parent observer
694
+ // must not reenter this method and replace an explicit user's cause.
695
+ this.cancelingTasks.add(taskId);
696
+ try {
697
+ agentTask.childAbortController.abort(cause ? new RunCancelled(cause) : undefined);
698
+ this.markCanceled(taskId, cause);
699
+ }
700
+ finally {
701
+ this.cancelingTasks.delete(taskId);
702
+ }
459
703
  }
460
704
  cancelAll(parentRunId, cause = 'parent') {
461
705
  // `'parent'` by default, because this call site IS a parent
@@ -539,11 +783,20 @@ export class AgentManager {
539
783
  if (isTerminalAgentTaskState(agentTask.state)) {
540
784
  this.clearEvictionTimer(taskId);
541
785
  this.instances.delete(taskId);
542
- this.spawnRecords.delete(taskId);
786
+ if (!this.executingTasks.has(taskId))
787
+ this.spawnRecords.delete(taskId);
543
788
  }
544
789
  }
545
790
  }
546
791
  dispose() {
792
+ this.disposed = true;
793
+ if (this.admissionTimer)
794
+ clearTimeout(this.admissionTimer);
795
+ this.admissionTimer = undefined;
796
+ for (const queue of this.pendingSpawns.values())
797
+ for (const entry of queue)
798
+ entry.removeAbortListener();
799
+ this.pendingSpawns.clear();
547
800
  for (const taskId of this.instances.keys()) {
548
801
  this.clearEvictionTimer(taskId);
549
802
  }
@@ -555,7 +808,9 @@ export class AgentManager {
555
808
  this.cancel(taskId);
556
809
  }
557
810
  this.instances.clear();
558
- this.spawnRecords.clear();
811
+ for (const taskId of this.spawnRecords.keys())
812
+ if (!this.executingTasks.has(taskId))
813
+ this.spawnRecords.delete(taskId);
559
814
  this.listeners.length = 0;
560
815
  }
561
816
  /**
@@ -591,7 +846,7 @@ export class AgentManager {
591
846
  this.spawnLocks.delete(key);
592
847
  }
593
848
  }
594
- async provisionSpawnUnlocked(options, context) {
849
+ async validateSpawn(options, context) {
595
850
  // Phase 9: deps are unconditional required. Every spawn produces a
596
851
  // SubSession + Session + WorkspaceRef triple (Convention #0: no
597
852
  // partial/legacy path).
@@ -618,6 +873,8 @@ export class AgentManager {
618
873
  if (!parentSession) {
619
874
  throw new Error(`Parent session ${options.parentSessionId} not found for tenant ${context.tenantId} — spawn rejected`);
620
875
  }
876
+ if (parentSession.status === 'archived')
877
+ throw new Error('Delegation parent session is archived');
621
878
  if (parentSession.topicId !== context.topicId) {
622
879
  throw new Error(`Topic mismatch on spawn: parent session ${parentSession.id} is on topic ${parentSession.topicId}, but context.topicId=${context.topicId}. Cross-topic spawn is forbidden (session-hierarchy.md §6.3).`);
623
880
  }
@@ -633,6 +890,11 @@ export class AgentManager {
633
890
  // chain; width counts existing direct children of the parent.
634
891
  await this.deps.capacity.validateDepth(options.parentSessionId, project.config.maxDelegationDepth, context.tenantId);
635
892
  await this.deps.capacity.validateWidth(options.parentSessionId, 1, project.config.maxDelegationWidth, context.tenantId);
893
+ }
894
+ async provisionSpawnUnlocked(options, context) {
895
+ await this.validateSpawn(options, context);
896
+ context.parentAbortController.signal.throwIfAborted();
897
+ const store = this.deps.sessionStore;
636
898
  // Ancestry walk gives both the child depth and the root session id
637
899
  // attached to every sub-session event from here down.
638
900
  const parentAncestry = await store.getAncestry(options.parentSessionId, context.tenantId);
@@ -995,13 +1257,16 @@ export class AgentManager {
995
1257
  return agentTask;
996
1258
  }
997
1259
  scheduleEviction(taskId) {
1260
+ if (this.disposed)
1261
+ return;
998
1262
  const agentTask = this.instances.get(taskId);
999
1263
  if (!agentTask)
1000
1264
  return;
1001
1265
  agentTask.evictAfter = Date.now() + this.config.evictionMs;
1002
1266
  const timer = setTimeout(() => {
1003
1267
  this.instances.delete(taskId);
1004
- this.spawnRecords.delete(taskId);
1268
+ if (!this.executingTasks.has(taskId))
1269
+ this.spawnRecords.delete(taskId);
1005
1270
  this.evictionTimers.delete(taskId);
1006
1271
  this.log.info('Agent task evicted', { 'namzu.agent.task_id': taskId });
1007
1272
  }, this.config.evictionMs);
@@ -1038,15 +1303,19 @@ export class AgentManager {
1038
1303
  emitRunEvent(agentTask, event) {
1039
1304
  if (!agentTask.runEventListener)
1040
1305
  return;
1041
- try {
1042
- agentTask.runEventListener(event);
1043
- }
1044
- catch (err) {
1306
+ const reportFailure = (error) => {
1045
1307
  this.log.error('RunEvent emission error', {
1046
1308
  'namzu.event.type': event.type,
1047
- 'namzu.task.id': agentTask.taskId,
1048
- 'exception.message': toErrorMessage(err),
1309
+ 'exception.message': toErrorMessage(error),
1049
1310
  });
1311
+ };
1312
+ try {
1313
+ // Terminal observation must not delay completion or leak a rejected
1314
+ // listener promise into the host as an unhandled rejection.
1315
+ void Promise.resolve(agentTask.runEventListener(event)).catch(reportFailure);
1316
+ }
1317
+ catch (error) {
1318
+ reportFailure(error);
1050
1319
  }
1051
1320
  }
1052
1321
  }