@librechat/agents 3.4.1 → 3.4.2

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 (128) hide show
  1. package/dist/cjs/agents/AgentContext.cjs.map +1 -1
  2. package/dist/cjs/graphs/Graph.cjs +57 -33
  3. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  4. package/dist/cjs/graphs/MultiAgentGraph.cjs +81 -33
  5. package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
  6. package/dist/cjs/graphs/applyGraphRuntimeConfig.cjs +14 -0
  7. package/dist/cjs/graphs/applyGraphRuntimeConfig.cjs.map +1 -0
  8. package/dist/cjs/graphs/createGraph.cjs +15 -0
  9. package/dist/cjs/graphs/createGraph.cjs.map +1 -0
  10. package/dist/cjs/graphs/index.cjs +1 -0
  11. package/dist/cjs/hooks/createWorkspacePolicyHook.cjs +1 -1
  12. package/dist/cjs/main.cjs +20 -11
  13. package/dist/cjs/run.cjs +48 -38
  14. package/dist/cjs/run.cjs.map +1 -1
  15. package/dist/cjs/session/JsonlSessionStore.cjs +2 -2
  16. package/dist/cjs/tools/SubagentTool.cjs +4 -4
  17. package/dist/cjs/tools/SubagentTool.cjs.map +1 -1
  18. package/dist/cjs/tools/ToolNode.cjs +13 -9
  19. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  20. package/dist/cjs/tools/eagerEventExecution.cjs +1 -0
  21. package/dist/cjs/tools/local/LocalExecutionEngine.cjs +3 -3
  22. package/dist/cjs/tools/local/LocalProgrammaticToolCalling.cjs +2 -2
  23. package/dist/cjs/tools/search/rerankers.cjs +154 -1
  24. package/dist/cjs/tools/search/rerankers.cjs.map +1 -1
  25. package/dist/cjs/tools/search/tool.cjs +4 -1
  26. package/dist/cjs/tools/search/tool.cjs.map +1 -1
  27. package/dist/cjs/tools/subagent/SubagentExecutionRegistry.cjs +601 -0
  28. package/dist/cjs/tools/subagent/SubagentExecutionRegistry.cjs.map +1 -0
  29. package/dist/cjs/tools/subagent/SubagentExecutor.cjs +672 -343
  30. package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
  31. package/dist/cjs/tools/subagent/SubagentReplay.cjs +3 -1
  32. package/dist/cjs/tools/subagent/SubagentReplay.cjs.map +1 -1
  33. package/dist/cjs/tools/subagent/childGraphConfig.cjs +278 -0
  34. package/dist/cjs/tools/subagent/childGraphConfig.cjs.map +1 -0
  35. package/dist/cjs/tools/subagent/index.cjs +1 -0
  36. package/dist/cjs/tools/subagent/runtimeLimits.cjs +5 -0
  37. package/dist/cjs/tools/subagent/runtimeLimits.cjs.map +1 -0
  38. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  39. package/dist/esm/graphs/Graph.mjs +57 -33
  40. package/dist/esm/graphs/Graph.mjs.map +1 -1
  41. package/dist/esm/graphs/MultiAgentGraph.mjs +81 -33
  42. package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
  43. package/dist/esm/graphs/applyGraphRuntimeConfig.mjs +14 -0
  44. package/dist/esm/graphs/applyGraphRuntimeConfig.mjs.map +1 -0
  45. package/dist/esm/graphs/createGraph.mjs +15 -0
  46. package/dist/esm/graphs/createGraph.mjs.map +1 -0
  47. package/dist/esm/graphs/index.mjs +1 -0
  48. package/dist/esm/hooks/createWorkspacePolicyHook.mjs +1 -1
  49. package/dist/esm/main.mjs +12 -10
  50. package/dist/esm/run.mjs +48 -38
  51. package/dist/esm/run.mjs.map +1 -1
  52. package/dist/esm/session/JsonlSessionStore.mjs +2 -2
  53. package/dist/esm/tools/SubagentTool.mjs +4 -4
  54. package/dist/esm/tools/SubagentTool.mjs.map +1 -1
  55. package/dist/esm/tools/ToolNode.mjs +13 -9
  56. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  57. package/dist/esm/tools/eagerEventExecution.mjs +1 -1
  58. package/dist/esm/tools/local/LocalExecutionEngine.mjs +3 -3
  59. package/dist/esm/tools/local/LocalProgrammaticToolCalling.mjs +2 -2
  60. package/dist/esm/tools/search/rerankers.mjs +154 -1
  61. package/dist/esm/tools/search/rerankers.mjs.map +1 -1
  62. package/dist/esm/tools/search/tool.mjs +4 -1
  63. package/dist/esm/tools/search/tool.mjs.map +1 -1
  64. package/dist/esm/tools/subagent/SubagentExecutionRegistry.mjs +597 -0
  65. package/dist/esm/tools/subagent/SubagentExecutionRegistry.mjs.map +1 -0
  66. package/dist/esm/tools/subagent/SubagentExecutor.mjs +672 -343
  67. package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
  68. package/dist/esm/tools/subagent/SubagentReplay.mjs +3 -1
  69. package/dist/esm/tools/subagent/SubagentReplay.mjs.map +1 -1
  70. package/dist/esm/tools/subagent/childGraphConfig.mjs +272 -0
  71. package/dist/esm/tools/subagent/childGraphConfig.mjs.map +1 -0
  72. package/dist/esm/tools/subagent/index.mjs +1 -0
  73. package/dist/esm/tools/subagent/runtimeLimits.mjs +5 -0
  74. package/dist/esm/tools/subagent/runtimeLimits.mjs.map +1 -0
  75. package/dist/types/agents/AgentContext.d.ts +1 -1
  76. package/dist/types/graphs/Graph.d.ts +7 -2
  77. package/dist/types/graphs/MultiAgentGraph.d.ts +7 -1
  78. package/dist/types/graphs/applyGraphRuntimeConfig.d.ts +3 -0
  79. package/dist/types/graphs/createGraph.d.ts +13 -0
  80. package/dist/types/graphs/graphFactory.d.ts +13 -0
  81. package/dist/types/graphs/index.d.ts +2 -0
  82. package/dist/types/run.d.ts +2 -2
  83. package/dist/types/tools/SubagentTool.d.ts +1 -1
  84. package/dist/types/tools/search/rerankers.d.ts +28 -0
  85. package/dist/types/tools/search/types.d.ts +38 -1
  86. package/dist/types/tools/subagent/SubagentExecutionRegistry.d.ts +171 -0
  87. package/dist/types/tools/subagent/SubagentExecutor.d.ts +32 -37
  88. package/dist/types/tools/subagent/SubagentReplay.d.ts +5 -1
  89. package/dist/types/tools/subagent/childGraphConfig.d.ts +37 -0
  90. package/dist/types/tools/subagent/index.d.ts +1 -1
  91. package/dist/types/tools/subagent/runtimeLimits.d.ts +4 -0
  92. package/dist/types/types/graph.d.ts +184 -19
  93. package/package.json +3 -1
  94. package/src/agents/AgentContext.ts +1 -1
  95. package/src/graphs/Graph.ts +111 -60
  96. package/src/graphs/MultiAgentGraph.ts +149 -47
  97. package/src/graphs/__tests__/MultiAgentGraph.test.ts +86 -0
  98. package/src/graphs/__tests__/composition.smoke.test.ts +152 -0
  99. package/src/graphs/__tests__/createGraph.test.ts +206 -0
  100. package/src/graphs/applyGraphRuntimeConfig.ts +25 -0
  101. package/src/graphs/createGraph.ts +32 -0
  102. package/src/graphs/graphFactory.ts +12 -0
  103. package/src/graphs/index.ts +2 -0
  104. package/src/run.ts +50 -38
  105. package/src/specs/graph-subagent.live.test.ts +242 -0
  106. package/src/specs/graph-subagent.test.ts +322 -0
  107. package/src/specs/langfuse-routing.integration.test.ts +120 -1
  108. package/src/specs/subagent.test.ts +130 -4
  109. package/src/tools/SubagentTool.ts +4 -4
  110. package/src/tools/ToolNode.ts +22 -10
  111. package/src/tools/__tests__/SubagentExecutor.test.ts +198 -9
  112. package/src/tools/__tests__/SubagentReplay.test.ts +29 -0
  113. package/src/tools/__tests__/directToolHITLResumeScope.test.ts +28 -2
  114. package/src/tools/__tests__/subagentHooks.test.ts +90 -3
  115. package/src/tools/search/rag-api-reranker.test.ts +891 -0
  116. package/src/tools/search/rerankers.ts +260 -0
  117. package/src/tools/search/tool.ts +6 -0
  118. package/src/tools/search/types.ts +45 -1
  119. package/src/tools/subagent/SubagentExecutionRegistry.ts +1159 -0
  120. package/src/tools/subagent/SubagentExecutor.ts +1245 -563
  121. package/src/tools/subagent/SubagentReplay.ts +10 -1
  122. package/src/tools/subagent/__tests__/SubagentExecutionRegistry.test.ts +637 -0
  123. package/src/tools/subagent/__tests__/SubagentExecutor.lazy.test.ts +3054 -0
  124. package/src/tools/subagent/__tests__/childGraphConfig.test.ts +521 -0
  125. package/src/tools/subagent/childGraphConfig.ts +694 -0
  126. package/src/tools/subagent/index.ts +6 -0
  127. package/src/tools/subagent/runtimeLimits.ts +7 -0
  128. package/src/types/graph.ts +217 -19
@@ -32,8 +32,13 @@ export type BaseGraphState = {
32
32
  export type AgentSubgraphState = BaseGraphState & {
33
33
  summarizationRequest?: SummarizationNodeInput;
34
34
  };
35
+ export type SubagentGraphResult = {
36
+ agentId: string;
37
+ message?: BaseMessage;
38
+ };
35
39
  export type MultiAgentGraphState = BaseGraphState & {
36
40
  agentMessages?: BaseMessage[];
41
+ subagentResult?: SubagentGraphResult;
37
42
  };
38
43
  export type IState = BaseGraphState;
39
44
  export interface AgentLogEvent {
@@ -223,9 +228,9 @@ export type StandardGraphInput = {
223
228
  indexTokenCountMap?: Record<string, number>;
224
229
  calibrationRatio?: number;
225
230
  /**
226
- * Receives a {@link SubagentUsageEvent} for every model call made inside
227
- * a subagent child run spawned from this graph (including nested
228
- * subagents and child-side summarization calls). Child graphs run via
231
+ * Receives a {@link SubagentUsageEvent} for every model call that reports
232
+ * usage metadata inside a subagent child run spawned from this graph
233
+ * (including nested subagents and child-side summarization calls). Child graphs run via
229
234
  * `invoke()` outside the host's `streamEvents` loop, so their
230
235
  * `on_chat_model_end` events never reach the run's handler registry —
231
236
  * this sink is the only way hosts can observe child token usage for
@@ -256,9 +261,11 @@ export type StandardGraphInput = {
256
261
  * every streamed chunk event. See {@link StreamLimits}.
257
262
  */
258
263
  streamLimits?: StreamLimits;
264
+ /** Structured lineage for a graph executing as a subagent child. */
265
+ subagentExecutionContext?: SubagentExecutionContext;
259
266
  };
260
267
  export type GraphEdge = {
261
- /** Agent ID, use a list for multiple sources */
268
+ /** Agent ID; direct edges use an array as an all-of waiting source group. */
262
269
  from: string | string[];
263
270
  /** Agent ID, use a list for multiple destinations */
264
271
  to: string | string[];
@@ -288,40 +295,180 @@ export type GraphEdge = {
288
295
  */
289
296
  promptKey?: string;
290
297
  };
298
+ export type GraphSubagentEdge = Omit<GraphEdge, 'edgeType' | 'condition' | 'promptKey'> & {
299
+ edgeType: 'direct';
300
+ condition?: never;
301
+ promptKey?: never;
302
+ };
291
303
  export type MultiAgentGraphInput = StandardGraphInput & {
292
304
  edges: GraphEdge[];
305
+ /** Captures the designated member's final AI turn in graph state. */
306
+ resultAgentId?: string;
307
+ /** Optional per-member Pregel budget when the outer graph has its own topology budget. */
308
+ memberRecursionLimit?: number;
293
309
  };
294
- /** Configuration for a subagent type that can be spawned by a parent agent. */
295
- export type SubagentConfig = {
296
- /** Identifier used in the tool's `subagent_type` enum (e.g. 'researcher', 'coder'). */
310
+ /** Lightweight identity advertised to the model for a spawnable subagent. */
311
+ export interface SubagentDescriptor {
312
+ /** Stable identifier used in the tool's `subagent_type` enum (e.g. 'researcher', 'coder'). */
297
313
  type: string;
298
314
  /** Human-readable display name. */
299
315
  name: string;
300
316
  /** What this subagent specializes in — shown to the LLM. */
301
317
  description: string;
302
- /** Full agent config for the child graph. Omit when `self` is true. */
303
- agentInputs?: AgentInputs;
304
- /** When true, reuse the parent's AgentInputs (context isolation without separate config). */
305
- self?: boolean;
318
+ /**
319
+ * Opaque, versioned identity of the child configuration. Required when
320
+ * `resolveAgentInputs` is used and changed whenever its resolved inputs
321
+ * change incompatibly.
322
+ */
323
+ configId?: string;
324
+ }
325
+ /** Lazy descriptor with the durable configuration identity required to resolve it. */
326
+ export interface LazySubagentDescriptor extends SubagentDescriptor {
327
+ configId: string;
328
+ }
329
+ /** Stable request identifiers exposed to a selected subagent resolver. */
330
+ export interface SubagentResolveRequestContext {
331
+ conversationId?: string;
332
+ messageId?: string;
333
+ parentMessageId?: string;
334
+ }
335
+ /** Stable user identifiers exposed to a selected subagent resolver. */
336
+ export interface SubagentResolveUserContext {
337
+ id?: string;
338
+ role?: string;
339
+ tenantId?: string;
340
+ }
341
+ /** Sanitized host runtime context safe for lazy subagent resolution. */
342
+ export interface SubagentResolveConfigurable {
343
+ requestBody?: Readonly<SubagentResolveRequestContext>;
344
+ user?: Readonly<SubagentResolveUserContext>;
345
+ user_id?: string;
346
+ }
347
+ /** Runtime context supplied when a host lazily resolves a selected subagent. */
348
+ export interface SubagentResolveContext {
349
+ /** Stable subagent identity selected by the model. */
350
+ descriptor: Readonly<LazySubagentDescriptor>;
351
+ /** Stable child execution identity, including across HITL reconstruction. */
352
+ executionId: string;
353
+ /** Parent run that dispatched this execution. */
354
+ parentRunId: string;
355
+ /** Parent agent that dispatched this execution. */
356
+ parentAgentId?: string;
357
+ /** Parent-side tool call that selected the subagent. */
358
+ parentToolCallId?: string;
359
+ /** Durable parent conversation thread, when supplied by the host. */
360
+ threadId?: string;
361
+ /** Parent/breaker cancellation composed for this child execution. */
362
+ signal: AbortSignal;
363
+ /** Stable, sanitized host context from the parent tool invocation. */
364
+ configurable?: Readonly<SubagentResolveConfigurable>;
365
+ }
366
+ /** Host contract for resolving a selected subagent's full graph inputs. */
367
+ export type SubagentAgentInputsResolver = (context: SubagentResolveContext) => Promise<AgentInputs>;
368
+ interface SubagentConfigBase extends SubagentDescriptor {
306
369
  /** Max AGENT→TOOLS cycles before forced stop (default: 25). */
307
370
  maxTurns?: number;
308
371
  /** Allow this subagent to spawn its own subagents (default: false). */
309
372
  allowNested?: boolean;
310
- };
311
- /** SubagentConfig with agentInputs guaranteed present (self-spawn resolved). */
312
- export type ResolvedSubagentConfig = SubagentConfig & {
373
+ }
374
+ export interface SubagentConfig extends SubagentConfigBase {
375
+ /** Full agent config for the child graph. Omit when `self` is true. */
376
+ agentInputs?: AgentInputs;
377
+ /**
378
+ * Resolve the full child config only after this descriptor is selected.
379
+ * Eager `agentInputs` take precedence when also supplied. `self` and a
380
+ * resolver are mutually exclusive and normalization rejects that pairing.
381
+ *
382
+ * Resolvers should rebuild inputs from the stable execution context instead
383
+ * of retaining request-owned state. The SDK keeps resolved inputs in memory
384
+ * only for the lifetime of the selected execution and supplies cancellation
385
+ * through `context.signal`. A reconstructed execution may invoke the
386
+ * resolver again, so the same `context.executionId` must produce equivalent
387
+ * child inputs. Durable resolver work should be idempotent on
388
+ * `(context.executionId, context.descriptor.configId)`.
389
+ */
390
+ resolveAgentInputs?: SubagentAgentInputsResolver;
391
+ /** When true, reuse the parent's AgentInputs (context isolation without separate config). */
392
+ self?: boolean;
393
+ }
394
+ export interface SingleAgentSubagentConfig extends SubagentConfig {
395
+ kind?: 'agent';
396
+ agents?: never;
397
+ edges?: never;
398
+ entryAgentId?: never;
399
+ resultAgentId?: never;
400
+ }
401
+ export interface GraphSubagentConfig extends SubagentConfigBase {
402
+ kind: 'graph';
403
+ configId?: never;
404
+ resolveAgentInputs?: never;
405
+ allowNested?: false;
406
+ agents: AgentInputs[];
407
+ /**
408
+ * Explicit direct DAG edges. Array-valued sources are all-of waiting edges.
409
+ * Relative message order between parallel branches is implementation-dependent.
410
+ * Prompted edges are supported in chains or on the final converged result transition.
411
+ */
412
+ edges: GraphSubagentEdge[];
413
+ entryAgentId: string;
414
+ resultAgentId: string;
415
+ agentInputs?: never;
416
+ self?: never;
417
+ }
418
+ /** Any configuration that can be spawned through the subagent tool. */
419
+ export type SubagentConfigEntry = SubagentConfig | GraphSubagentConfig;
420
+ /** Legacy single-agent config with self-spawn resolution completed. */
421
+ export interface ResolvedSubagentConfig extends SubagentConfig {
313
422
  agentInputs: AgentInputs;
314
- };
423
+ }
424
+ /** Explicit alias for graph-aware code that needs to name the legacy variant. */
425
+ export interface ResolvedSingleAgentSubagentConfig extends ResolvedSubagentConfig {
426
+ kind?: 'agent';
427
+ agents?: never;
428
+ edges?: never;
429
+ entryAgentId?: never;
430
+ resultAgentId?: never;
431
+ }
432
+ export type ResolvedSubagentConfigEntry = ResolvedSubagentConfig | GraphSubagentConfig;
433
+ /** Lazy single-agent entry accepted after descriptor validation. */
434
+ export interface LazySingleAgentSubagentConfig extends SingleAgentSubagentConfig {
435
+ configId: string;
436
+ resolveAgentInputs: SubagentAgentInputsResolver;
437
+ agentInputs?: never;
438
+ self?: never;
439
+ }
440
+ /** Single-agent config accepted after eager/self/lazy eligibility checks. */
441
+ export type ExecutableSubagentConfig = ResolvedSubagentConfig | LazySingleAgentSubagentConfig;
442
+ /** Graph-aware config accepted by the executor. Graph configs stay eager. */
443
+ export type ExecutableSubagentConfigEntry = ExecutableSubagentConfig | GraphSubagentConfig;
315
444
  /** Lifecycle phase carried on {@link SubagentUpdateEvent}. */
316
445
  export type SubagentUpdatePhase = 'start' | 'run_step' | 'run_step_delta' | 'run_step_completed' | 'message_delta' | 'reasoning_delta' | 'stop' | 'error';
446
+ export interface SubagentAncestryEntry {
447
+ readonly subagentRunId: string;
448
+ readonly subagentType: string;
449
+ readonly subagentKind: 'agent' | 'graph';
450
+ /** Execution subject ID; synthetic for graph subagents. */
451
+ readonly subagentAgentId: string;
452
+ readonly parentRunId: string;
453
+ readonly parentAgentId?: string;
454
+ readonly parentToolCallId?: string;
455
+ }
456
+ export interface SubagentExecutionContext {
457
+ readonly rootRunId: string;
458
+ readonly hookSessionId: string;
459
+ readonly depth: number;
460
+ readonly ancestry: readonly SubagentAncestryEntry[];
461
+ }
317
462
  /**
318
463
  * Wrapper event emitted when a subagent's child graph dispatches activity.
319
464
  * Lets hosts show subagent progress in a UI surface separate from the parent
320
465
  * conversation without having to untangle events by agent ID.
321
466
  */
322
467
  export interface SubagentUpdateEvent {
323
- /** Parent run ID. */
468
+ /** Root run ID that owns this execution tree. */
324
469
  runId: string;
470
+ /** Immediate parent run that spawned this child. */
471
+ parentRunId?: string;
325
472
  /** Child run ID (unique per subagent execution). */
326
473
  subagentRunId: string;
327
474
  /**
@@ -333,8 +480,16 @@ export interface SubagentUpdateEvent {
333
480
  parentToolCallId?: string;
334
481
  /** Subagent `type` identifier from the SubagentConfig. */
335
482
  subagentType: string;
336
- /** Child agent ID assigned to this subagent execution. */
483
+ /** Execution shape. Omitted by older emitters. */
484
+ subagentKind?: 'agent' | 'graph';
485
+ /** Execution subject ID; synthetic for graph subagents. */
337
486
  subagentAgentId: string;
487
+ /** Graph member that produced this update, when attributable. */
488
+ memberAgentId?: string;
489
+ /** One-based nesting depth beneath the root graph. */
490
+ depth?: number;
491
+ /** Root-to-leaf execution lineage, without parsing composed run IDs. */
492
+ ancestry?: readonly SubagentAncestryEntry[];
338
493
  /** Parent agent ID that spawned this subagent. */
339
494
  parentAgentId?: string;
340
495
  /** Lifecycle phase carried by this update. */
@@ -376,10 +531,20 @@ export interface SubagentUsageEvent {
376
531
  provider?: string;
377
532
  /** Subagent `type` identifier from the SubagentConfig. */
378
533
  subagentType: string;
534
+ /** Execution shape. Omitted by older emitters. */
535
+ subagentKind?: 'agent' | 'graph';
379
536
  /** Child run ID (unique per subagent execution). */
380
537
  subagentRunId: string;
381
- /** Child agent ID assigned to this subagent execution. */
538
+ /** Execution subject ID; synthetic for graph subagents. */
382
539
  subagentAgentId: string;
540
+ /** Graph member whose model call produced this usage. */
541
+ memberAgentId?: string;
542
+ /** Immediate parent run that spawned this child. */
543
+ parentRunId?: string;
544
+ /** One-based nesting depth beneath the root graph. */
545
+ depth?: number;
546
+ /** Root-to-leaf execution lineage, without parsing composed run IDs. */
547
+ ancestry?: readonly SubagentAncestryEntry[];
383
548
  /**
384
549
  * ROOT run ID of the host run that owns billing. For nested subagents
385
550
  * each forwarding layer rewrites this upward, so events from any depth
@@ -512,7 +677,7 @@ export interface AgentInputs {
512
677
  /** Pre-computed tool schema token count (from cache). Skips recalculation when provided. */
513
678
  toolSchemaTokens?: number;
514
679
  /** Subagent configurations for hierarchical delegation. Each defines a child agent type. */
515
- subagentConfigs?: SubagentConfig[];
680
+ subagentConfigs?: SubagentConfigEntry[];
516
681
  /** Maximum subagent nesting depth. Default 1 means top-level agents can spawn subagents but subagents cannot nest further. */
517
682
  maxSubagentDepth?: number;
518
683
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@librechat/agents",
3
- "version": "3.4.1",
3
+ "version": "3.4.2",
4
4
  "reova": {
5
5
  "enabled": true,
6
6
  "endpoint": "https://telemetry.reo.dev/data"
@@ -120,6 +120,8 @@
120
120
  "prepublishOnly": "npm run build",
121
121
  "build": "tsdown && tsc -p tsconfig.build.json",
122
122
  "build:dev": "tsdown",
123
+ "check:circular-deps": "node config/circular-deps.mjs",
124
+ "test:circular-deps": "node --test config/circular-deps.test.mjs",
123
125
  "sort-imports": "node scripts/sort-imports.ts",
124
126
  "sort-imports:check": "node scripts/sort-imports.ts --check",
125
127
  "start": "node dist/esm/main.js",
@@ -275,7 +275,7 @@ export class AgentContext {
275
275
  /** Original AgentInputs used to create this context — used for self-spawn subagent resolution. */
276
276
  _sourceInputs?: t.AgentInputs;
277
277
  /** Subagent configurations for hierarchical delegation. */
278
- subagentConfigs?: t.SubagentConfig[];
278
+ subagentConfigs?: t.SubagentConfigEntry[];
279
279
  /** Maximum subagent nesting depth. */
280
280
  maxSubagentDepth?: number;
281
281
  /** Instructions for this agent */
@@ -30,6 +30,10 @@ import type {
30
30
  StreamedToolCallArgTally,
31
31
  StreamDeltaEventTally,
32
32
  } from '@/llm/streamLimits';
33
+ import type {
34
+ GraphFactory,
35
+ GraphFactoryDependencies,
36
+ } from '@/graphs/graphFactory';
33
37
  import type { OverflowRecoveryPlan } from '@/llm/contextOverflowRecovery';
34
38
  import type { FallbackErrorContext } from '@/llm/invoke';
35
39
  import type { HookRegistry } from '@/hooks';
@@ -93,6 +97,12 @@ import {
93
97
  STREAM_LIMIT_EPOCH_KEY,
94
98
  RUN_BREAKER_SCOPE_CONFIG_KEY,
95
99
  } from '@/llm/streamLimits';
100
+ import {
101
+ DEFAULT_SUBAGENT_DESCRIPTION,
102
+ SubagentExecutor,
103
+ isGraphSubagentConfig,
104
+ normalizeSubagentConfigEntries,
105
+ } from '@/tools/subagent';
96
106
  import {
97
107
  Constants,
98
108
  GraphNodeKeys,
@@ -141,7 +151,7 @@ import { ToolNode as CustomToolNode, toolsCondition } from '@/tools/ToolNode';
141
151
  import { shouldTraceToolNodeForLangfuse } from '@/langfuseToolOutputTracing';
142
152
  import { createLocalCodingToolBundle } from '@/tools/local/LocalCodingTools';
143
153
  import { SUBAGENT_REPLAY_CONTROLLER } from '@/tools/subagent/SubagentReplay';
144
- import { SubagentExecutor, resolveSubagentConfigs } from '@/tools/subagent';
154
+ import { applyGraphRuntimeConfig } from '@/graphs/applyGraphRuntimeConfig';
145
155
  import { partitionAndMarkBedrockToolCache } from '@/llm/bedrock/toolCache';
146
156
  import { safeDispatchCustomEvent, emitAgentLog } from '@/utils/events';
147
157
  import { createCloudflareCodingToolBundle } from '@/tools/cloudflare';
@@ -179,15 +189,21 @@ const EMPTY_PREEMPT_BOUNDARY: PreemptBoundaryResult = {
179
189
  /** Minimum relative variance before calibrated toolSchemaTokens overrides current value. */
180
190
  const CALIBRATION_VARIANCE_THRESHOLD = 0.15;
181
191
 
182
- function createToolHandlerRegistry(
192
+ function createChildHandlerRegistry(
183
193
  source: HandlerRegistry | undefined
184
194
  ): HandlerRegistry | undefined {
185
195
  const toolHandler = source?.getHandler(GraphEvents.ON_TOOL_EXECUTE);
186
- if (toolHandler == null) {
196
+ const updateHandler = source?.getHandler(GraphEvents.ON_SUBAGENT_UPDATE);
197
+ if (toolHandler == null && updateHandler == null) {
187
198
  return undefined;
188
199
  }
189
200
  const registry = new HandlerRegistry();
190
- registry.register(GraphEvents.ON_TOOL_EXECUTE, toolHandler);
201
+ if (toolHandler != null) {
202
+ registry.register(GraphEvents.ON_TOOL_EXECUTE, toolHandler);
203
+ }
204
+ if (updateHandler != null) {
205
+ registry.register(GraphEvents.ON_SUBAGENT_UPDATE, updateHandler);
206
+ }
191
207
  return registry;
192
208
  }
193
209
 
@@ -1148,6 +1164,8 @@ export abstract class Graph<
1148
1164
  export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1149
1165
  overrideModel?: t.ChatModel;
1150
1166
  private subagentModelOverride?: t.ChatModel;
1167
+ private readonly graphFactory: GraphFactory;
1168
+ private readonly supportsMultiAgentChildren: boolean;
1151
1169
  /** Optional compile options passed into workflow.compile() */
1152
1170
  compileOptions?: t.CompileOptions | undefined;
1153
1171
  /** Whether the workflow was actually compiled with a checkpointer. */
@@ -1196,6 +1214,8 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1196
1214
  subagentUsageSink?: t.SubagentUsageSink;
1197
1215
  /** See {@link t.StandardGraphInput.subagentScope}. */
1198
1216
  subagentScope: boolean;
1217
+ /** See {@link t.StandardGraphInput.subagentExecutionContext}. */
1218
+ private readonly subagentExecutionContext?: t.SubagentExecutionContext;
1199
1219
  /** See {@link t.StandardGraphInput.preemption}. */
1200
1220
  preemption?: t.StreamPreemption;
1201
1221
  /**
@@ -1317,26 +1337,42 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1317
1337
  */
1318
1338
  pendingPreemptReturn = new Set<string>();
1319
1339
 
1320
- constructor({
1321
- runId,
1322
- signal,
1323
- agents,
1324
- langfuse,
1325
- tokenCounter,
1326
- indexTokenCountMap,
1327
- calibrationRatio,
1328
- subagentUsageSink,
1329
- subagentScope,
1330
- preemption,
1331
- streamLimits,
1332
- }: t.StandardGraphInput) {
1340
+ constructor(
1341
+ {
1342
+ runId,
1343
+ signal,
1344
+ agents,
1345
+ langfuse,
1346
+ tokenCounter,
1347
+ indexTokenCountMap,
1348
+ calibrationRatio,
1349
+ subagentUsageSink,
1350
+ subagentScope,
1351
+ subagentExecutionContext,
1352
+ preemption,
1353
+ streamLimits,
1354
+ }: t.StandardGraphInput,
1355
+ dependencies?: GraphFactoryDependencies
1356
+ ) {
1333
1357
  super();
1358
+ this.supportsMultiAgentChildren = dependencies != null;
1359
+ this.graphFactory =
1360
+ dependencies?.graphFactory ??
1361
+ ((request): StandardGraph => {
1362
+ if (request.kind !== 'standard') {
1363
+ throw new Error(
1364
+ 'A polymorphic graph factory is required for multi-agent graph construction.'
1365
+ );
1366
+ }
1367
+ return new StandardGraph(request.input);
1368
+ });
1334
1369
  this.runId = runId;
1335
1370
  this.langfuseScopeRunId = `${runId ?? 'graph'}:${nanoid()}`;
1336
1371
  this.signal = signal;
1337
1372
  this.langfuse = langfuse;
1338
1373
  this.subagentUsageSink = subagentUsageSink;
1339
1374
  this.subagentScope = subagentScope === true;
1375
+ this.subagentExecutionContext = subagentExecutionContext;
1340
1376
  this.preemption = preemption;
1341
1377
  this.streamLimits = resolveStreamLimits(streamLimits);
1342
1378
 
@@ -4211,15 +4247,57 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
4211
4247
  agentContext.subagentConfigs.length > 0 &&
4212
4248
  effectiveSubagentDepth > 0
4213
4249
  ) {
4214
- const resolvedConfigs = resolveSubagentConfigs(
4250
+ const executableConfigs = normalizeSubagentConfigEntries(
4215
4251
  agentContext.subagentConfigs,
4216
4252
  agentContext
4217
4253
  );
4218
- if (resolvedConfigs.length > 0) {
4254
+ if (executableConfigs.length > 0) {
4255
+ if (
4256
+ !this.supportsMultiAgentChildren &&
4257
+ executableConfigs.some(isGraphSubagentConfig)
4258
+ ) {
4259
+ throw new Error(
4260
+ 'Graph subagents require constructing the parent with createGraph() or an injected GraphFactory dependency.'
4261
+ );
4262
+ }
4219
4263
  const getParentHandlerRegistry = (): HandlerRegistry | undefined =>
4220
4264
  this.handlerRegistry ?? this.parentToolHandlerRegistry;
4265
+ const createConfiguredChildGraph: GraphFactory = (request) => {
4266
+ const childGraph = this.graphFactory(request);
4267
+ if (this.subagentModelOverride != null) {
4268
+ childGraph.overrideModel = this.subagentModelOverride;
4269
+ childGraph.setSubagentModelOverride(this.subagentModelOverride);
4270
+ }
4271
+ const childHandlerRegistry = createChildHandlerRegistry(
4272
+ getParentHandlerRegistry()
4273
+ );
4274
+ // Pure execution-ordering hint (unlike `humanInTheLoop`). It only
4275
+ // reorders tools already in the child's direct group; it does not
4276
+ // force a schema-only event tool onto the direct execution path.
4277
+ applyGraphRuntimeConfig(childGraph, {
4278
+ hookRegistry: this.hookRegistry,
4279
+ humanInTheLoop: this.humanInTheLoop,
4280
+ toolOutputReferences: this.toolOutputReferences,
4281
+ eagerEventToolExecution: this.eagerEventToolExecution,
4282
+ codeSessionToolNames: this.codeSessionToolNames,
4283
+ interruptingToolNames: this.interruptingToolNames,
4284
+ toolExecution: this.toolExecution,
4285
+ });
4286
+ if (this.humanInTheLoop?.enabled === true) {
4287
+ childGraph.compileOptions = {
4288
+ checkpointer: this.compileOptions?.checkpointer,
4289
+ };
4290
+ }
4291
+ childGraph.parentToolHandlerRegistry = childHandlerRegistry;
4292
+ childGraph.eventToolExecutionAvailable =
4293
+ childHandlerRegistry?.getHandler(GraphEvents.ON_TOOL_EXECUTE) !=
4294
+ null;
4295
+ return childGraph;
4296
+ };
4221
4297
  const executor = new SubagentExecutor({
4222
- configs: new Map(resolvedConfigs.map((c) => [c.type, c])),
4298
+ configs: new Map(
4299
+ executableConfigs.map((config) => [config.type, config])
4300
+ ),
4223
4301
  parentSignal: this.signal,
4224
4302
  breakerScope: {
4225
4303
  controller: (): AbortController => this.breakerAbort,
@@ -4231,6 +4309,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
4231
4309
  parentHandlerRegistry: getParentHandlerRegistry,
4232
4310
  parentRunId: this.runId ?? '',
4233
4311
  parentAgentId: agentContext.agentId,
4312
+ executionContext: this.subagentExecutionContext,
4234
4313
  langfuse: this.langfuse,
4235
4314
  tokenCounter: agentContext.tokenCounter,
4236
4315
  usageSink: this.subagentUsageSink,
@@ -4238,42 +4317,12 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
4238
4317
  humanInTheLoop: this.humanInTheLoop,
4239
4318
  checkpointer: this.compileOptions?.checkpointer,
4240
4319
  maxDepth: effectiveSubagentDepth,
4241
- createChildGraph: (input): StandardGraph => {
4242
- const childGraph = new StandardGraph(input);
4243
- if (this.subagentModelOverride != null) {
4244
- childGraph.overrideModel = this.subagentModelOverride;
4245
- childGraph.setSubagentModelOverride(this.subagentModelOverride);
4246
- }
4247
- const toolHandlerRegistry = createToolHandlerRegistry(
4248
- getParentHandlerRegistry()
4249
- );
4250
- childGraph.hookRegistry = this.hookRegistry;
4251
- childGraph.humanInTheLoop = this.humanInTheLoop;
4252
- if (this.humanInTheLoop?.enabled === true) {
4253
- childGraph.compileOptions = {
4254
- checkpointer: this.compileOptions?.checkpointer,
4255
- };
4256
- }
4257
- childGraph.toolOutputReferences = this.toolOutputReferences;
4258
- childGraph.eagerEventToolExecution = this.eagerEventToolExecution;
4259
- childGraph.codeSessionToolNames = this.codeSessionToolNames;
4260
- // Pure execution-ordering hint (unlike `humanInTheLoop` above).
4261
- // It ONLY reorders tools already in the child's direct group;
4262
- // it does not force a name onto the direct path (that fold-in
4263
- // was removed — Codex review of #294). So for a self-spawned
4264
- // child that scrubs inherited `graphTools` (keeping only the
4265
- // event `toolDefinition` / schema-only stub for a name like
4266
- // `ask_user_question`), the name isn't in the child's direct
4267
- // group and this is a no-op — the stub is still dispatched via
4268
- // ON_TOOL_EXECUTE, never invoked directly. Where the child DOES
4269
- // have the executable graphTool, the guard correctly applies.
4270
- childGraph.interruptingToolNames = this.interruptingToolNames;
4271
- childGraph.toolExecution = this.toolExecution;
4272
- childGraph.parentToolHandlerRegistry = toolHandlerRegistry;
4273
- childGraph.eventToolExecutionAvailable =
4274
- toolHandlerRegistry != null;
4275
- return childGraph;
4276
- },
4320
+ createChildGraph: (input): StandardGraph =>
4321
+ createConfiguredChildGraph({
4322
+ kind: 'standard',
4323
+ input,
4324
+ }),
4325
+ createChildGraphByKind: createConfiguredChildGraph,
4277
4326
  });
4278
4327
  this.registerSubagentExecutor(executor);
4279
4328
 
@@ -4286,7 +4335,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
4286
4335
  typeof input.description === 'string' &&
4287
4336
  input.description.trim().length > 0
4288
4337
  ? input.description
4289
- : 'No task description provided';
4338
+ : DEFAULT_SUBAGENT_DESCRIPTION;
4290
4339
  const subagentType =
4291
4340
  typeof input.subagent_type === 'string' ? input.subagent_type : '';
4292
4341
  const threadId = config.configurable?.thread_id as string | undefined;
@@ -4317,6 +4366,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
4317
4366
  description,
4318
4367
  subagentType,
4319
4368
  threadId,
4369
+ signal: config.signal,
4320
4370
  parentToolCallId,
4321
4371
  breaker: batchScope?.controller,
4322
4372
  /**
@@ -4330,14 +4380,15 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
4330
4380
  | undefined,
4331
4381
  });
4332
4382
  return result.content;
4333
- }, buildSubagentToolParams(resolvedConfigs));
4383
+ }, buildSubagentToolParams(executableConfigs));
4334
4384
  const replayableSubagentTool = subagentTool as typeof subagentTool &
4335
4385
  ReplayableSubagentTool;
4336
4386
  replayableSubagentTool[SUBAGENT_REPLAY_CONTROLLER] = {
4337
4387
  getResumeManifest: (
4338
- parentToolCallIds
4388
+ parentToolCallIds,
4389
+ config
4339
4390
  ): Promise<SubagentResumeManifest | undefined> =>
4340
- executor.getResumeManifest(parentToolCallIds),
4391
+ executor.getResumeManifest(parentToolCallIds, config),
4341
4392
  getSettledOutput: (
4342
4393
  call,
4343
4394
  config