@illuma-ai/agents 1.1.15 → 1.1.17

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 (64) hide show
  1. package/dist/cjs/common/enum.cjs +15 -13
  2. package/dist/cjs/common/enum.cjs.map +1 -1
  3. package/dist/cjs/graphs/MultiAgentGraph.cjs +173 -150
  4. package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
  5. package/dist/cjs/main.cjs +2 -2
  6. package/dist/cjs/types/graph.cjs.map +1 -1
  7. package/dist/esm/common/enum.mjs +14 -12
  8. package/dist/esm/common/enum.mjs.map +1 -1
  9. package/dist/esm/graphs/MultiAgentGraph.mjs +174 -151
  10. package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
  11. package/dist/esm/main.mjs +1 -1
  12. package/dist/esm/types/graph.mjs.map +1 -1
  13. package/dist/types/common/enum.d.ts +13 -11
  14. package/dist/types/graphs/MultiAgentGraph.d.ts +38 -36
  15. package/dist/types/types/graph.d.ts +22 -7
  16. package/package.json +1 -1
  17. package/src/common/__tests__/enum.test.ts +14 -6
  18. package/src/common/enum.ts +13 -11
  19. package/src/graphs/MultiAgentGraph.ts +190 -152
  20. package/src/graphs/__tests__/multi-agent-delegate.test.ts +44 -44
  21. package/src/graphs/__tests__/multi-agent-edges.test.ts +83 -85
  22. package/src/scripts/multi-agent-chain.js +1 -1
  23. package/src/scripts/multi-agent-chain.ts +1 -1
  24. package/src/scripts/multi-agent-document-review-chain.js +1 -1
  25. package/src/scripts/multi-agent-document-review-chain.ts +1 -1
  26. package/src/scripts/multi-agent-hybrid-flow.js +3 -3
  27. package/src/scripts/multi-agent-hybrid-flow.ts +3 -3
  28. package/src/scripts/multi-agent-parallel.js +2 -2
  29. package/src/scripts/multi-agent-parallel.ts +2 -2
  30. package/src/scripts/multi-agent-sequence.js +2 -2
  31. package/src/scripts/multi-agent-sequence.ts +2 -2
  32. package/src/scripts/multi-agent-supervisor.js +5 -5
  33. package/src/scripts/multi-agent-supervisor.ts +5 -5
  34. package/src/scripts/poc-multi-agent-comprehensive.ts +7 -7
  35. package/src/scripts/sequential-full-metadata-test.js +1 -1
  36. package/src/scripts/sequential-full-metadata-test.ts +1 -1
  37. package/src/scripts/test-custom-prompt-key.js +3 -3
  38. package/src/scripts/test-custom-prompt-key.ts +3 -3
  39. package/src/scripts/test-handoff-input.js +1 -1
  40. package/src/scripts/test-handoff-input.ts +1 -1
  41. package/src/scripts/test-handoff-preamble.js +1 -1
  42. package/src/scripts/test-handoff-preamble.ts +1 -1
  43. package/src/scripts/test-handoff-steering.js +3 -3
  44. package/src/scripts/test-handoff-steering.ts +3 -3
  45. package/src/scripts/test-multi-agent-list-handoff.js +1 -1
  46. package/src/scripts/test-multi-agent-list-handoff.ts +1 -1
  47. package/src/scripts/test-parallel-agent-labeling.js +2 -2
  48. package/src/scripts/test-parallel-agent-labeling.ts +2 -2
  49. package/src/scripts/test-parallel-handoffs.js +2 -2
  50. package/src/scripts/test-parallel-handoffs.ts +2 -2
  51. package/src/scripts/test-thinking-handoff-bedrock.js +1 -1
  52. package/src/scripts/test-thinking-handoff-bedrock.ts +1 -1
  53. package/src/scripts/test-thinking-handoff.js +1 -1
  54. package/src/scripts/test-thinking-handoff.ts +1 -1
  55. package/src/scripts/test-thinking-to-thinking-handoff-bedrock.js +1 -1
  56. package/src/scripts/test-thinking-to-thinking-handoff-bedrock.ts +1 -1
  57. package/src/scripts/test-tool-before-handoff-role-order.js +1 -1
  58. package/src/scripts/test-tool-before-handoff-role-order.ts +1 -1
  59. package/src/scripts/test-tools-before-handoff.js +1 -1
  60. package/src/scripts/test-tools-before-handoff.ts +1 -1
  61. package/src/specs/agent-handoffs-bedrock.integration.test.ts +6 -6
  62. package/src/specs/agent-handoffs.test.ts +35 -35
  63. package/src/specs/thinking-handoff.test.ts +9 -9
  64. package/src/types/graph.ts +23 -7
@@ -11,32 +11,33 @@ require('../tools/approval/constants.cjs');
11
11
  require('../utils/toonFormat.cjs');
12
12
  var summarize = require('../messages/summarize.cjs');
13
13
  var Graph = require('./Graph.cjs');
14
+ var events = require('../utils/events.cjs');
14
15
 
15
16
  /** Pattern to extract instructions from transfer ToolMessage content */
16
- const HANDOFF_INSTRUCTIONS_PATTERN = /(?:Instructions?|Context):\s*(.+)/is;
17
+ const TRANSFER_INSTRUCTIONS_PATTERN = /(?:Instructions?|Context):\s*(.+)/is;
17
18
  /**
18
19
  * MultiAgentGraph extends StandardGraph to support dynamic multi-agent workflows
19
20
  * with handoffs, fan-in/fan-out, and other composable patterns.
20
21
  *
21
22
  * Key behavior:
22
- * - Agents with ONLY handoff edges: Can dynamically route to any handoff destination
23
- * - Agents with ONLY direct edges: Always follow their direct edges
24
- * - Agents with BOTH: Use Command for exclusive routing (handoff OR direct, not both)
25
- * - If handoff occurs: Only the handoff destination executes
26
- * - If no handoff: Direct edges execute (potentially in parallel)
23
+ * - Agents with ONLY transfer edges: Can dynamically route to any transfer destination
24
+ * - Agents with ONLY sequence edges: Always follow their sequence edges
25
+ * - Agents with BOTH: Use Command for exclusive routing (transfer OR sequence, not both)
26
+ * - If transfer occurs: Only the transfer destination executes
27
+ * - If no transfer: Sequence edges execute (potentially in parallel)
27
28
  *
28
- * This enables the common pattern where an agent either delegates (handoff)
29
- * OR continues its workflow (direct edges), but not both simultaneously.
29
+ * This enables the common pattern where an agent either transfers (one-way)
30
+ * OR continues its workflow (sequence edges), but not both simultaneously.
30
31
  */
31
32
  class MultiAgentGraph extends Graph.StandardGraph {
32
33
  edges;
33
34
  startingNodes = new Set();
34
- directEdges = [];
35
+ sequenceEdges = [];
36
+ transferEdges = [];
35
37
  handoffEdges = [];
36
- delegateEdges = [];
37
38
  /**
38
39
  * Lazily populated registry of compiled subgraphs, keyed by agentId.
39
- * Delegate tools are created in the constructor but reference subgraphs
40
+ * Handoff tools are created in the constructor but reference subgraphs
40
41
  * that are only created in createWorkflow(). This Map bridges that gap —
41
42
  * tools capture the Map reference in their closure, and createWorkflow()
42
43
  * populates it before any tool invocation occurs.
@@ -63,41 +64,39 @@ class MultiAgentGraph extends Graph.StandardGraph {
63
64
  this.edges = input.edges;
64
65
  this.categorizeEdges();
65
66
  this.analyzeGraph();
67
+ this.createTransferTools();
66
68
  this.createHandoffTools();
67
- this.createDelegateTools();
68
69
  console.debug(`[MultiAgentGraph] Constructor complete: ${this.agentContexts.size} agents, ${this.edges.length} edges`);
69
70
  }
70
71
  /**
71
- * Categorize edges into handoff and direct types
72
+ * Categorize edges into handoff, transfer, and sequence types
72
73
  */
73
74
  categorizeEdges() {
74
75
  for (const edge of this.edges) {
75
- // Default behavior: edges with conditions or explicit 'handoff' type are handoff edges
76
- // Edges with explicit 'direct' type or multi-destination without conditions are direct edges
77
- if (edge.edgeType === _enum.EdgeType.DELEGATE) {
78
- this.delegateEdges.push(edge);
76
+ if (edge.edgeType === _enum.EdgeType.HANDOFF) {
77
+ this.handoffEdges.push(edge);
79
78
  }
80
- else if (edge.edgeType === _enum.EdgeType.DIRECT) {
81
- this.directEdges.push(edge);
79
+ else if (edge.edgeType === _enum.EdgeType.SEQUENCE) {
80
+ this.sequenceEdges.push(edge);
82
81
  }
83
- else if (edge.edgeType === _enum.EdgeType.HANDOFF || edge.condition != null) {
84
- this.handoffEdges.push(edge);
82
+ else if (edge.edgeType === _enum.EdgeType.TRANSFER || edge.condition != null) {
83
+ this.transferEdges.push(edge);
85
84
  }
86
85
  else {
87
- // Default: single-to-single edges are handoff, single-to-multiple are direct
86
+ // Default: single-to-single edges are transfer, single-to-multiple are sequence
88
87
  const destinations = Array.isArray(edge.to) ? edge.to : [edge.to];
89
88
  const sources = Array.isArray(edge.from) ? edge.from : [edge.from];
90
89
  if (sources.length === 1 && destinations.length > 1) {
91
- // Fan-out pattern defaults to direct
92
- this.directEdges.push(edge);
90
+ // Fan-out pattern defaults to sequence
91
+ this.sequenceEdges.push(edge);
93
92
  }
94
93
  else {
95
- // Everything else defaults to handoff
96
- this.handoffEdges.push(edge);
94
+ // Everything else defaults to transfer
95
+ this.transferEdges.push(edge);
97
96
  }
98
97
  }
99
98
  }
100
- console.debug(`[MultiAgentGraph] Edge categorization: ${this.handoffEdges.length} handoff, ${this.directEdges.length} direct, ${this.delegateEdges.length} delegate (of ${this.edges.length} total)`);
99
+ console.debug(`[MultiAgentGraph] Edge categorization: ${this.handoffEdges.length} handoff, ${this.transferEdges.length} transfer, ${this.sequenceEdges.length} sequence (of ${this.edges.length} total)`);
101
100
  }
102
101
  /**
103
102
  * Analyze graph structure to determine starting nodes and connections
@@ -155,8 +154,8 @@ class MultiAgentGraph extends Graph.StandardGraph {
155
154
  if (visited.has(current))
156
155
  continue;
157
156
  visited.add(current);
158
- // Find direct edges from this node
159
- for (const edge of this.directEdges) {
157
+ // Find sequence edges from this node
158
+ for (const edge of this.sequenceEdges) {
160
159
  const sources = Array.isArray(edge.from) ? edge.from : [edge.from];
161
160
  if (!sources.includes(current))
162
161
  continue;
@@ -183,8 +182,8 @@ class MultiAgentGraph extends Graph.StandardGraph {
183
182
  }
184
183
  }
185
184
  }
186
- // Also follow handoff and delegate edges for traversal (they don't create parallel groups)
187
- for (const edge of [...this.handoffEdges, ...this.delegateEdges]) {
185
+ // Also follow transfer and handoff edges for traversal (they don't create parallel groups)
186
+ for (const edge of [...this.transferEdges, ...this.handoffEdges]) {
188
187
  const sources = Array.isArray(edge.from) ? edge.from : [edge.from];
189
188
  if (!sources.includes(current))
190
189
  continue;
@@ -227,46 +226,44 @@ class MultiAgentGraph extends Graph.StandardGraph {
227
226
  return this.agentParallelGroups.get(agentId);
228
227
  }
229
228
  /**
230
- * Create handoff tools for agents based on handoff edges only
229
+ * Create transfer tools for agents based on transfer edges only.
230
+ * Transfer tools return Command for one-way routing — parent exits, child takes over.
231
231
  */
232
- createHandoffTools() {
233
- // Group handoff edges by source agent(s)
234
- const handoffsByAgent = new Map();
235
- // Only process handoff edges for tool creation
236
- for (const edge of this.handoffEdges) {
232
+ createTransferTools() {
233
+ const transfersByAgent = new Map();
234
+ for (const edge of this.transferEdges) {
237
235
  const sources = Array.isArray(edge.from) ? edge.from : [edge.from];
238
236
  sources.forEach((source) => {
239
- if (!handoffsByAgent.has(source)) {
240
- handoffsByAgent.set(source, []);
237
+ if (!transfersByAgent.has(source)) {
238
+ transfersByAgent.set(source, []);
241
239
  }
242
- handoffsByAgent.get(source).push(edge);
240
+ transfersByAgent.get(source).push(edge);
243
241
  });
244
242
  }
245
- // Create handoff tools for each agent
246
- for (const [agentId, edges] of handoffsByAgent) {
243
+ for (const [agentId, edges] of transfersByAgent) {
247
244
  const agentContext = this.agentContexts.get(agentId);
248
245
  if (!agentContext)
249
246
  continue;
250
- // Create handoff tools for this agent's outgoing edges
251
- const handoffTools = [];
247
+ const transferTools = [];
252
248
  const sourceAgentName = agentContext.name ?? agentId;
253
249
  for (const edge of edges) {
254
- handoffTools.push(...this.createHandoffToolsForEdge(edge, agentId, sourceAgentName));
250
+ transferTools.push(...this.createTransferToolsForEdge(edge, agentId, sourceAgentName));
255
251
  }
256
252
  if (!agentContext.graphTools) {
257
253
  agentContext.graphTools = [];
258
254
  }
259
- agentContext.graphTools.push(...handoffTools);
260
- console.debug(`[MultiAgentGraph] Handoff tools for "${agentId}": [${handoffTools.map((t) => t.name).join(', ')}]`);
255
+ agentContext.graphTools.push(...transferTools);
256
+ console.debug(`[MultiAgentGraph] Transfer tools for "${agentId}": [${transferTools.map((t) => t.name).join(', ')}]`);
261
257
  }
262
258
  }
263
259
  /**
264
- * Create handoff tools for an edge (handles multiple destinations)
265
- * @param edge - The graph edge defining the handoff
266
- * @param sourceAgentId - The ID of the agent that will perform the handoff
260
+ * Create transfer tools for an edge (handles multiple destinations).
261
+ * Transfer tools return Command for one-way routing parent exits, child takes over.
262
+ * @param edge - The graph edge defining the transfer
263
+ * @param sourceAgentId - The ID of the agent that will perform the transfer
267
264
  * @param sourceAgentName - The human-readable name of the source agent
268
265
  */
269
- createHandoffToolsForEdge(edge, sourceAgentId, sourceAgentName) {
266
+ createTransferToolsForEdge(edge, sourceAgentId, sourceAgentName) {
270
267
  const tools$1 = [];
271
268
  const destinations = Array.isArray(edge.to) ? edge.to : [edge.to];
272
269
  /** If there's a condition, create a single conditional handoff tool */
@@ -274,8 +271,8 @@ class MultiAgentGraph extends Graph.StandardGraph {
274
271
  const toolName = 'conditional_transfer';
275
272
  const toolDescription = edge.description ?? 'Conditionally transfer control based on state';
276
273
  /** Check if we have a prompt for handoff input */
277
- const hasHandoffInput = edge.prompt != null && typeof edge.prompt === 'string';
278
- const handoffInputDescription = hasHandoffInput ? edge.prompt : undefined;
274
+ const hasTransferInput = edge.prompt != null && typeof edge.prompt === 'string';
275
+ const transferInputDescription = hasTransferInput ? edge.prompt : undefined;
279
276
  const promptKey = edge.promptKey ?? 'instructions';
280
277
  tools$1.push(tools.tool(async (rawInput, config) => {
281
278
  const input = rawInput;
@@ -299,7 +296,7 @@ class MultiAgentGraph extends Graph.StandardGraph {
299
296
  destination = Array.isArray(result) ? result[0] : destinations[0];
300
297
  }
301
298
  let content = `Conditionally transferred to ${destination}`;
302
- if (hasHandoffInput &&
299
+ if (hasTransferInput &&
303
300
  promptKey in input &&
304
301
  input[promptKey] != null) {
305
302
  content += `\n\n${promptKey.charAt(0).toUpperCase() + promptKey.slice(1)}: ${input[promptKey]}`;
@@ -322,13 +319,13 @@ class MultiAgentGraph extends Graph.StandardGraph {
322
319
  });
323
320
  }, {
324
321
  name: toolName,
325
- schema: hasHandoffInput
322
+ schema: hasTransferInput
326
323
  ? {
327
324
  type: 'object',
328
325
  properties: {
329
326
  [promptKey]: {
330
327
  type: 'string',
331
- description: handoffInputDescription,
328
+ description: transferInputDescription,
332
329
  },
333
330
  },
334
331
  required: [],
@@ -343,10 +340,10 @@ class MultiAgentGraph extends Graph.StandardGraph {
343
340
  const toolName = `${_enum.Constants.LC_TRANSFER_TO_}${destination}`;
344
341
  const destContext = this.agentContexts.get(destination);
345
342
  const toolDescription = edge.description ??
346
- this.buildDefaultHandoffDescription(destContext, destination);
343
+ this.buildDefaultTransferDescription(destContext, destination);
347
344
  /** Check if we have a prompt for handoff input */
348
- const hasHandoffInput = edge.prompt != null && typeof edge.prompt === 'string';
349
- const handoffInputDescription = hasHandoffInput
345
+ const hasTransferInput = edge.prompt != null && typeof edge.prompt === 'string';
346
+ const transferInputDescription = hasTransferInput
350
347
  ? edge.prompt
351
348
  : undefined;
352
349
  const promptKey = edge.promptKey ?? 'instructions';
@@ -355,7 +352,7 @@ class MultiAgentGraph extends Graph.StandardGraph {
355
352
  const toolCallId = config?.toolCall?.id ??
356
353
  'unknown';
357
354
  let content = `Successfully transferred to ${destination}`;
358
- if (hasHandoffInput &&
355
+ if (hasTransferInput &&
359
356
  promptKey in input &&
360
357
  input[promptKey] != null) {
361
358
  content += `\n\n${promptKey.charAt(0).toUpperCase() + promptKey.slice(1)}: ${input[promptKey]}`;
@@ -432,13 +429,13 @@ class MultiAgentGraph extends Graph.StandardGraph {
432
429
  });
433
430
  }, {
434
431
  name: toolName,
435
- schema: hasHandoffInput
432
+ schema: hasTransferInput
436
433
  ? {
437
434
  type: 'object',
438
435
  properties: {
439
436
  [promptKey]: {
440
437
  type: 'string',
441
- description: handoffInputDescription,
438
+ description: transferInputDescription,
442
439
  },
443
440
  },
444
441
  required: [],
@@ -451,13 +448,13 @@ class MultiAgentGraph extends Graph.StandardGraph {
451
448
  return tools$1;
452
449
  }
453
450
  /**
454
- * Builds a meaningful default description for a handoff tool when no explicit
451
+ * Builds a meaningful default description for a transfer tool when no explicit
455
452
  * edge.description is provided. Uses the destination agent's name and description
456
453
  * so the LLM can make informed routing decisions.
457
454
  * @param destContext - AgentContext of the destination agent (may be undefined)
458
455
  * @param destinationId - Raw agent ID (fallback when context unavailable)
459
456
  */
460
- buildDefaultHandoffDescription(destContext, destinationId) {
457
+ buildDefaultTransferDescription(destContext, destinationId) {
461
458
  const displayName = destContext?.name ?? destinationId;
462
459
  const agentDescription = destContext?.description;
463
460
  if (agentDescription != null && agentDescription !== '') {
@@ -466,58 +463,58 @@ class MultiAgentGraph extends Graph.StandardGraph {
466
463
  return `Transfer control to "${displayName}"`;
467
464
  }
468
465
  /**
469
- * Create delegate tools for agents based on delegate edges.
470
- * Delegate tools invoke child agent subgraphs inline and return the result
471
- * as a string to the parent agent's context. Unlike handoff tools (which
472
- * return Command for fire-and-forget routing), delegate tools execute the
473
- * child, extract the final text, and return it within the parent's agent loop.
466
+ * Create handoff tools for agents based on handoff edges.
467
+ * Handoff tools invoke child agent subgraphs inline and return the result
468
+ * as a string to the parent agent's context. Unlike transfer tools (which
469
+ * return Command for one-way routing), handoff tools execute the child,
470
+ * extract the final text, and return it within the parent's agent loop.
474
471
  *
475
472
  * This enables the supervisor pattern: parent calls child → gets result → thinks → calls another.
476
473
  */
477
- createDelegateTools() {
478
- const delegatesByAgent = new Map();
479
- for (const edge of this.delegateEdges) {
474
+ createHandoffTools() {
475
+ const handoffsByAgent = new Map();
476
+ for (const edge of this.handoffEdges) {
480
477
  const sources = Array.isArray(edge.from) ? edge.from : [edge.from];
481
478
  sources.forEach((source) => {
482
- if (!delegatesByAgent.has(source)) {
483
- delegatesByAgent.set(source, []);
479
+ if (!handoffsByAgent.has(source)) {
480
+ handoffsByAgent.set(source, []);
484
481
  }
485
- delegatesByAgent.get(source).push(edge);
482
+ handoffsByAgent.get(source).push(edge);
486
483
  });
487
484
  }
488
- for (const [agentId, edges] of delegatesByAgent) {
485
+ for (const [agentId, edges] of handoffsByAgent) {
489
486
  const agentContext = this.agentContexts.get(agentId);
490
487
  if (!agentContext)
491
488
  continue;
492
- const delegateTools = [];
489
+ const handoffTools = [];
493
490
  for (const edge of edges) {
494
- delegateTools.push(...this.createDelegateToolsForEdge(edge, agentId));
491
+ handoffTools.push(...this.createHandoffToolsForEdge(edge, agentId));
495
492
  }
496
493
  if (!agentContext.graphTools) {
497
494
  agentContext.graphTools = [];
498
495
  }
499
- agentContext.graphTools.push(...delegateTools);
500
- console.debug(`[MultiAgentGraph] Delegate tools for "${agentId}": [${delegateTools.map((t) => t.name).join(', ')}]`);
496
+ agentContext.graphTools.push(...handoffTools);
497
+ console.debug(`[MultiAgentGraph] Handoff tools for "${agentId}": [${handoffTools.map((t) => t.name).join(', ')}]`);
501
498
  }
502
499
  }
503
500
  /**
504
- * Create delegate tools for an edge (handles multiple destinations).
505
- * Each delegate tool invokes the child agent's compiled subgraph inline,
501
+ * Create handoff tools for an edge (handles multiple destinations).
502
+ * Each handoff tool invokes the child agent's compiled subgraph inline,
506
503
  * extracts the final AI message text, truncates it, and returns it as
507
504
  * a string (which becomes a ToolMessage in the parent's context).
508
505
  *
509
- * @param edge - The graph edge defining the delegation
506
+ * @param edge - The graph edge defining the handoff
510
507
  * @param sourceAgentId - The ID of the parent/supervisor agent
511
508
  */
512
- createDelegateToolsForEdge(edge, sourceAgentId) {
509
+ createHandoffToolsForEdge(edge, sourceAgentId) {
513
510
  const tools$1 = [];
514
511
  const destinations = Array.isArray(edge.to) ? edge.to : [edge.to];
515
- const maxResultChars = edge.maxResultChars ?? _enum.DEFAULT_DELEGATE_MAX_RESULT_CHARS;
512
+ const maxResultChars = edge.maxResultChars ?? _enum.DEFAULT_HANDOFF_MAX_RESULT_CHARS;
516
513
  for (const destination of destinations) {
517
- const toolName = `${_enum.Constants.LC_DELEGATE_TO_}${destination}`;
514
+ const toolName = `${_enum.Constants.LC_HANDOFF_TO_}${destination}`;
518
515
  const destContext = this.agentContexts.get(destination);
519
516
  const toolDescription = edge.description ??
520
- this.buildDefaultDelegateDescription(destContext, destination);
517
+ this.buildDefaultHandoffDescription(destContext, destination);
521
518
  const hasPromptInput = edge.prompt != null && typeof edge.prompt === 'string';
522
519
  const promptInputDescription = hasPromptInput ? edge.prompt : undefined;
523
520
  const promptKey = edge.promptKey ?? 'instructions';
@@ -527,7 +524,7 @@ class MultiAgentGraph extends Graph.StandardGraph {
527
524
  const input = rawInput;
528
525
  const subgraph = registry.get(destination);
529
526
  if (!subgraph) {
530
- throw new Error(`Delegate target "${destination}" subgraph not found in registry. ` +
527
+ throw new Error(`Handoff target "${destination}" subgraph not found in registry. ` +
531
528
  'This is a bug: createWorkflow() should have populated the subgraph registry.');
532
529
  }
533
530
  const state = langgraph.getCurrentTaskInput();
@@ -544,7 +541,7 @@ class MultiAgentGraph extends Graph.StandardGraph {
544
541
  const childState = {
545
542
  messages: childMessages,
546
543
  };
547
- console.debug(`[MultiAgentGraph] Delegate "${sourceAgentId}" -> "${destination}" START ` +
544
+ console.debug(`[MultiAgentGraph] Handoff "${sourceAgentId}" -> "${destination}" START ` +
548
545
  `(messages: ${childMessages.length})`);
549
546
  try {
550
547
  /**
@@ -553,17 +550,25 @@ class MultiAgentGraph extends Graph.StandardGraph {
553
550
  * and configurable data (thread_id, user_id) to the child.
554
551
  */
555
552
  const result = await subgraph.invoke(childState, config);
556
- const resultText = MultiAgentGraph.extractDelegateResult(result.messages, destination);
557
- const truncatedResult = MultiAgentGraph.truncateDelegateResult(resultText, maxResultChars);
558
- console.debug(`[MultiAgentGraph] Delegate "${sourceAgentId}" -> "${destination}" DONE ` +
553
+ const resultText = MultiAgentGraph.extractHandoffResult(result.messages, destination);
554
+ const truncatedResult = MultiAgentGraph.truncateHandoffResult(resultText, maxResultChars);
555
+ console.debug(`[MultiAgentGraph] Handoff "${sourceAgentId}" -> "${destination}" DONE ` +
559
556
  `(result: ${resultText.length} chars` +
560
557
  `${truncatedResult.length < resultText.length ? `, truncated to ${truncatedResult.length}` : ''})`);
558
+ await events.safeDispatchCustomEvent(_enum.GraphEvents.ON_AGENT_TRANSITION, {
559
+ sourceAgentId: sourceAgentId,
560
+ sourceAgentName: this.agentContexts.get(sourceAgentId)?.name ?? sourceAgentId,
561
+ destinationAgentId: destination,
562
+ destinationAgentName: destContext?.name ?? destination,
563
+ edgeType: _enum.EdgeType.HANDOFF,
564
+ timestamp: Date.now(),
565
+ }, config);
561
566
  return truncatedResult;
562
567
  }
563
568
  catch (err) {
564
569
  const errorMessage = err instanceof Error ? err.message : String(err);
565
- console.error(`[MultiAgentGraph] Delegate "${sourceAgentId}" -> "${destination}" ERROR:`, errorMessage);
566
- return `[Delegate to "${destination}" failed: ${errorMessage}]`;
570
+ console.error(`[MultiAgentGraph] Handoff "${sourceAgentId}" -> "${destination}" ERROR:`, errorMessage);
571
+ return `[Handoff to "${destination}" failed: ${errorMessage}]`;
567
572
  }
568
573
  }, {
569
574
  name: toolName,
@@ -591,7 +596,7 @@ class MultiAgentGraph extends Graph.StandardGraph {
591
596
  * @param messages - The child agent's output messages
592
597
  * @param agentId - The child agent ID (for fallback message)
593
598
  */
594
- static extractDelegateResult(messages, agentId) {
599
+ static extractHandoffResult(messages, agentId) {
595
600
  for (let i = messages.length - 1; i >= 0; i--) {
596
601
  const msg = messages[i];
597
602
  if (msg.getType() !== 'ai')
@@ -618,17 +623,17 @@ class MultiAgentGraph extends Graph.StandardGraph {
618
623
  return `[Agent "${agentId}" completed but produced no text output]`;
619
624
  }
620
625
  /**
621
- * Truncate delegate result using head/tail strategy (60/40 split).
626
+ * Truncate handoff result using head/tail strategy (60/40 split).
622
627
  * Preserves the beginning (key findings) and end (conclusions).
623
628
  * Matches the TaskTool.truncateResult pattern from Ranger.
624
629
  * @param result - The full result text
625
630
  * @param maxChars - Maximum allowed characters
626
631
  */
627
- static truncateDelegateResult(result, maxChars) {
632
+ static truncateHandoffResult(result, maxChars) {
628
633
  if (!result || result.length <= maxChars) {
629
634
  return result;
630
635
  }
631
- const truncationNotice = '\n\n[... delegate output truncated — middle section omitted to fit parent context ...]\n\n';
636
+ const truncationNotice = '\n\n[... handoff output truncated — middle section omitted to fit parent context ...]\n\n';
632
637
  const available = maxChars - truncationNotice.length;
633
638
  if (available <= 0) {
634
639
  return result.substring(0, maxChars);
@@ -640,17 +645,17 @@ class MultiAgentGraph extends Graph.StandardGraph {
640
645
  result.substring(result.length - tailSize));
641
646
  }
642
647
  /**
643
- * Build a meaningful default description for a delegate tool.
648
+ * Build a meaningful default description for a handoff tool.
644
649
  * @param destContext - AgentContext of the destination agent
645
650
  * @param destinationId - Raw agent ID (fallback)
646
651
  */
647
- buildDefaultDelegateDescription(destContext, destinationId) {
652
+ buildDefaultHandoffDescription(destContext, destinationId) {
648
653
  const displayName = destContext?.name ?? destinationId;
649
654
  const agentDescription = destContext?.description;
650
655
  if (agentDescription != null && agentDescription !== '') {
651
- return `Delegate task to "${displayName}": ${agentDescription}. The agent will execute and return its result.`;
656
+ return `Hand off task to "${displayName}": ${agentDescription}. The agent will execute and return its result.`;
652
657
  }
653
- return `Delegate task to "${displayName}" and receive its result.`;
658
+ return `Hand off task to "${displayName}" and receive its result.`;
654
659
  }
655
660
  /**
656
661
  * Create a complete agent subgraph (similar to createReactAgent)
@@ -671,7 +676,7 @@ class MultiAgentGraph extends Graph.StandardGraph {
671
676
  * @param agentId - The agent ID to check for handoff reception
672
677
  * @returns Object with filtered messages, extracted instructions, source agent, and parallel siblings
673
678
  */
674
- processHandoffReception(messages$1, agentId) {
679
+ processTransferReception(messages$1, agentId) {
675
680
  if (messages$1.length === 0)
676
681
  return null;
677
682
  /**
@@ -700,8 +705,8 @@ class MultiAgentGraph extends Graph.StandardGraph {
700
705
  destinationAgent = toolName.replace(_enum.Constants.LC_TRANSFER_TO_, '');
701
706
  }
702
707
  else if (isConditionalTransfer) {
703
- const handoffDest = candidateMsg.additional_kwargs.handoff_destination;
704
- destinationAgent = typeof handoffDest === 'string' ? handoffDest : null;
708
+ const transferDest = candidateMsg.additional_kwargs.handoff_destination;
709
+ destinationAgent = typeof transferDest === 'string' ? transferDest : null;
705
710
  }
706
711
  /** Check if this transfer targets our agent */
707
712
  if (destinationAgent === agentId) {
@@ -717,7 +722,7 @@ class MultiAgentGraph extends Graph.StandardGraph {
717
722
  const contentStr = typeof toolMessage.content === 'string'
718
723
  ? toolMessage.content
719
724
  : JSON.stringify(toolMessage.content);
720
- const instructionsMatch = contentStr.match(HANDOFF_INSTRUCTIONS_PATTERN);
725
+ const instructionsMatch = contentStr.match(TRANSFER_INSTRUCTIONS_PATTERN);
721
726
  const instructions = instructionsMatch?.[1]?.trim() ?? null;
722
727
  /** Extract source agent name from additional_kwargs */
723
728
  const handoffSourceName = toolMessage.additional_kwargs.handoff_source_name;
@@ -895,7 +900,7 @@ class MultiAgentGraph extends Graph.StandardGraph {
895
900
  };
896
901
  }
897
902
  /**
898
- * Create the multi-agent workflow with dynamic handoffs
903
+ * Create the multi-agent workflow with handoffs, transfers, and sequences
899
904
  */
900
905
  createWorkflow() {
901
906
  const StateAnnotation = langgraph.Annotation.Root({
@@ -921,54 +926,54 @@ class MultiAgentGraph extends Graph.StandardGraph {
921
926
  // Add all agents as complete subgraphs
922
927
  for (const [agentId] of this.agentContexts) {
923
928
  // Get all possible destinations for this agent
924
- const handoffDestinations = new Set();
925
- const directDestinations = new Set();
926
- // Check handoff edges for destinations
927
- for (const edge of this.handoffEdges) {
929
+ const transferDestinations = new Set();
930
+ const sequenceDestinations = new Set();
931
+ // Check transfer edges for destinations
932
+ for (const edge of this.transferEdges) {
928
933
  const sources = Array.isArray(edge.from) ? edge.from : [edge.from];
929
934
  if (sources.includes(agentId) === true) {
930
935
  const dests = Array.isArray(edge.to) ? edge.to : [edge.to];
931
- dests.forEach((dest) => handoffDestinations.add(dest));
936
+ dests.forEach((dest) => transferDestinations.add(dest));
932
937
  }
933
938
  }
934
- // Check direct edges for destinations
935
- for (const edge of this.directEdges) {
939
+ // Check sequence edges for destinations
940
+ for (const edge of this.sequenceEdges) {
936
941
  const sources = Array.isArray(edge.from) ? edge.from : [edge.from];
937
942
  if (sources.includes(agentId) === true) {
938
943
  const dests = Array.isArray(edge.to) ? edge.to : [edge.to];
939
- dests.forEach((dest) => directDestinations.add(dest));
944
+ dests.forEach((dest) => sequenceDestinations.add(dest));
940
945
  }
941
946
  }
942
- /** Check if this agent has BOTH handoff and direct edges */
943
- const hasHandoffEdges = handoffDestinations.size > 0;
944
- const hasDirectEdges = directDestinations.size > 0;
945
- const needsCommandRouting = hasHandoffEdges && hasDirectEdges;
947
+ /** Check if this agent has BOTH transfer and sequence edges */
948
+ const hasTransferEdges = transferDestinations.size > 0;
949
+ const hasSequenceEdges = sequenceDestinations.size > 0;
950
+ const needsCommandRouting = hasTransferEdges && hasSequenceEdges;
946
951
  /** Collect all possible destinations for this agent */
947
952
  const allDestinations = new Set([
948
- ...handoffDestinations,
949
- ...directDestinations,
953
+ ...transferDestinations,
954
+ ...sequenceDestinations,
950
955
  ]);
951
- if (handoffDestinations.size > 0 || directDestinations.size === 0) {
956
+ if (transferDestinations.size > 0 || sequenceDestinations.size === 0) {
952
957
  allDestinations.add(langgraph.END);
953
958
  }
954
959
  /** Agent subgraph (includes agent + tools) */
955
960
  const agentSubgraph = this.createAgentSubgraph(agentId);
956
- /** Register subgraph for delegate tools (lazy reference resolution) */
961
+ /** Register subgraph for handoff tools (lazy reference resolution) */
957
962
  this.subgraphRegistry.set(agentId, agentSubgraph);
958
963
  /** Wrapper function that handles agentMessages channel, handoff reception, and conditional routing */
959
964
  const agentWrapper = async (state, config) => {
960
965
  console.debug(`[MultiAgentGraph] Agent "${agentId}" wrapper ENTRY (messages: ${state.messages.length}, needsCommandRouting: ${needsCommandRouting})`);
961
966
  let result;
962
967
  /**
963
- * Check if this agent is receiving a handoff.
968
+ * Check if this agent is receiving a transfer.
964
969
  * If so, filter out the transfer messages and inject instructions as preamble.
965
970
  * This prevents the receiving agent from seeing the transfer as "completed work"
966
971
  * and prematurely producing an end token.
967
972
  */
968
- const handoffContext = this.processHandoffReception(state.messages, agentId);
969
- if (handoffContext !== null) {
970
- const { filteredMessages, instructions, sourceAgentName, parallelSiblings, } = handoffContext;
971
- console.debug(`[MultiAgentGraph] Agent "${agentId}" receiving handoff from "${sourceAgentName}" (instructions: ${instructions != null}, parallelSiblings: ${parallelSiblings.length})`);
973
+ const transferContext = this.processTransferReception(state.messages, agentId);
974
+ if (transferContext !== null) {
975
+ const { filteredMessages, instructions, sourceAgentName, parallelSiblings, } = transferContext;
976
+ console.debug(`[MultiAgentGraph] Agent "${agentId}" receiving transfer from "${sourceAgentName}" (instructions: ${instructions != null}, parallelSiblings: ${parallelSiblings.length})`);
972
977
  /**
973
978
  * Set handoff context on the receiving agent.
974
979
  * Uses pre-computed graph position for depth and parallel info.
@@ -1085,24 +1090,24 @@ class MultiAgentGraph extends Graph.StandardGraph {
1085
1090
  /** Track the last agent that produced output for continuation support */
1086
1091
  this.lastActiveAgentId = agentId;
1087
1092
  console.debug(`[MultiAgentGraph] Agent "${agentId}" wrapper EXIT (result messages: ${result.messages.length})`);
1088
- /** If agent has both handoff and direct edges, use Command for exclusive routing */
1093
+ /** If agent has both transfer and sequence edges, use Command for exclusive routing */
1089
1094
  if (needsCommandRouting) {
1090
- /** Check if a handoff occurred */
1095
+ /** Check if a transfer occurred */
1091
1096
  const lastMessage = result.messages[result.messages.length - 1];
1092
1097
  if (lastMessage != null &&
1093
1098
  lastMessage.getType() === 'tool' &&
1094
1099
  typeof lastMessage.name === 'string' &&
1095
1100
  lastMessage.name.startsWith(_enum.Constants.LC_TRANSFER_TO_)) {
1096
- /** Handoff occurred - extract destination and navigate there exclusively */
1097
- const handoffDest = lastMessage.name.replace(_enum.Constants.LC_TRANSFER_TO_, '');
1098
- console.debug(`[MultiAgentGraph] Command routing: "${agentId}" -> handoff to "${handoffDest}" (direct edges skipped: [${Array.from(directDestinations).join(', ')}])`);
1101
+ /** Transfer occurred - extract destination and navigate there exclusively */
1102
+ const transferDest = lastMessage.name.replace(_enum.Constants.LC_TRANSFER_TO_, '');
1103
+ console.debug(`[MultiAgentGraph] Command routing: "${agentId}" -> transfer to "${transferDest}" (sequence edges skipped: [${Array.from(sequenceDestinations).join(', ')}])`);
1099
1104
  /** Validate destination agent exists */
1100
- if (!this.agentContexts.has(handoffDest)) {
1105
+ if (!this.agentContexts.has(transferDest)) {
1101
1106
  const availableAgents = Array.from(this.agentContexts.keys()).join(', ');
1102
- console.error(`[MultiAgentGraph] Handoff to non-existent agent "${handoffDest}". Available: ${availableAgents}`);
1107
+ console.error(`[MultiAgentGraph] Transfer to non-existent agent "${transferDest}". Available: ${availableAgents}`);
1103
1108
  /** Return error to model so it can self-correct */
1104
1109
  const errorMsg = new messages.ToolMessage({
1105
- content: `Transfer failed: agent "${handoffDest}" does not exist. Available agents: ${availableAgents}. Please choose a valid agent to transfer to.`,
1110
+ content: `Transfer failed: agent "${transferDest}" does not exist. Available agents: ${availableAgents}. Please choose a valid agent to transfer to.`,
1106
1111
  tool_call_id: lastMessage.tool_call_id,
1107
1112
  name: lastMessage.name,
1108
1113
  });
@@ -1112,7 +1117,7 @@ class MultiAgentGraph extends Graph.StandardGraph {
1112
1117
  };
1113
1118
  }
1114
1119
  /** Pre-handoff context compaction: if receiving agent has smaller budget */
1115
- const receiverContext = this.agentContexts.get(handoffDest);
1120
+ const receiverContext = this.agentContexts.get(transferDest);
1116
1121
  const senderContext = this.agentContexts.get(agentId);
1117
1122
  if (receiverContext?.maxContextTokens != null &&
1118
1123
  senderContext?.tokenCounter != null &&
@@ -1124,7 +1129,7 @@ class MultiAgentGraph extends Graph.StandardGraph {
1124
1129
  const receiverBudget = receiverContext.maxContextTokens;
1125
1130
  if (currentSize > receiverBudget * 0.7) {
1126
1131
  console.warn(`[MultiAgentGraph] Pre-handoff compaction: context (${currentSize} tokens) exceeds ` +
1127
- `70% of receiver "${handoffDest}" budget (${receiverBudget} tokens)`);
1132
+ `70% of receiver "${transferDest}" budget (${receiverBudget} tokens)`);
1128
1133
  /** Generate handoff briefing */
1129
1134
  const senderName = senderContext.name ?? agentId;
1130
1135
  if (senderContext.summarizeCallback) {
@@ -1136,8 +1141,8 @@ class MultiAgentGraph extends Graph.StandardGraph {
1136
1141
  summaryBudget: Math.floor(receiverBudget * 0.2),
1137
1142
  isMultiAgent: true,
1138
1143
  agentWorkflowState: {
1139
- currentAgentId: handoffDest,
1140
- agentChain: [agentId, handoffDest],
1144
+ currentAgentId: transferDest,
1145
+ agentChain: [agentId, transferDest],
1141
1146
  pendingAgents: [],
1142
1147
  },
1143
1148
  });
@@ -1173,15 +1178,33 @@ class MultiAgentGraph extends Graph.StandardGraph {
1173
1178
  }
1174
1179
  }
1175
1180
  }
1181
+ await events.safeDispatchCustomEvent(_enum.GraphEvents.ON_AGENT_TRANSITION, {
1182
+ sourceAgentId: agentId,
1183
+ sourceAgentName: this.agentContexts.get(agentId)?.name ?? agentId,
1184
+ destinationAgentId: transferDest,
1185
+ destinationAgentName: this.agentContexts.get(transferDest)?.name ?? transferDest,
1186
+ edgeType: _enum.EdgeType.TRANSFER,
1187
+ timestamp: Date.now(),
1188
+ }, config);
1176
1189
  return new langgraph.Command({
1177
1190
  update: result,
1178
- goto: handoffDest,
1191
+ goto: transferDest,
1179
1192
  });
1180
1193
  }
1181
1194
  else {
1182
- /** No handoff - proceed with direct edges */
1183
- console.debug(`[MultiAgentGraph] Command routing: "${agentId}" -> no handoff, following direct edges: [${Array.from(directDestinations).join(', ')}]`);
1184
- const directDests = Array.from(directDestinations);
1195
+ /** No transfer - proceed with sequence edges */
1196
+ console.debug(`[MultiAgentGraph] Command routing: "${agentId}" -> no transfer, following sequence edges: [${Array.from(sequenceDestinations).join(', ')}]`);
1197
+ const directDests = Array.from(sequenceDestinations);
1198
+ for (const dest of directDests) {
1199
+ await events.safeDispatchCustomEvent(_enum.GraphEvents.ON_AGENT_TRANSITION, {
1200
+ sourceAgentId: agentId,
1201
+ sourceAgentName: this.agentContexts.get(agentId)?.name ?? agentId,
1202
+ destinationAgentId: dest,
1203
+ destinationAgentName: this.agentContexts.get(dest)?.name ?? dest,
1204
+ edgeType: _enum.EdgeType.SEQUENCE,
1205
+ timestamp: Date.now(),
1206
+ }, config);
1207
+ }
1185
1208
  if (directDests.length === 1) {
1186
1209
  return new langgraph.Command({
1187
1210
  update: result,
@@ -1212,11 +1235,11 @@ class MultiAgentGraph extends Graph.StandardGraph {
1212
1235
  builder.addEdge(langgraph.START, startNode);
1213
1236
  }
1214
1237
  /**
1215
- * Add direct edges for automatic transitions
1238
+ * Add sequence edges for automatic transitions
1216
1239
  * Group edges by destination to handle fan-in scenarios
1217
1240
  */
1218
1241
  const edgesByDestination = new Map();
1219
- for (const edge of this.directEdges) {
1242
+ for (const edge of this.sequenceEdges) {
1220
1243
  const destinations = Array.isArray(edge.to) ? edge.to : [edge.to];
1221
1244
  for (const destination of destinations) {
1222
1245
  if (!edgesByDestination.has(destination)) {
@@ -1305,17 +1328,17 @@ class MultiAgentGraph extends Graph.StandardGraph {
1305
1328
  for (const edge of edges) {
1306
1329
  const sources = Array.isArray(edge.from) ? edge.from : [edge.from];
1307
1330
  for (const source of sources) {
1308
- /** Check if this source node has both handoff and direct edges */
1309
- const sourceHandoffEdges = this.handoffEdges.filter((e) => {
1331
+ /** Check if this source node has both transfer and sequence edges */
1332
+ const sourceTransferEdges = this.transferEdges.filter((e) => {
1310
1333
  const eSources = Array.isArray(e.from) ? e.from : [e.from];
1311
1334
  return eSources.includes(source);
1312
1335
  });
1313
- const sourceDirectEdges = this.directEdges.filter((e) => {
1336
+ const sourceSequenceEdges = this.sequenceEdges.filter((e) => {
1314
1337
  const eSources = Array.isArray(e.from) ? e.from : [e.from];
1315
1338
  return eSources.includes(source);
1316
1339
  });
1317
1340
  /** Skip adding edge if source uses Command routing (has both types) */
1318
- if (sourceHandoffEdges.length > 0 && sourceDirectEdges.length > 0) {
1341
+ if (sourceTransferEdges.length > 0 && sourceSequenceEdges.length > 0) {
1319
1342
  continue;
1320
1343
  }
1321
1344
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment