@agentforge/patterns 0.16.32 → 0.16.34

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.
package/dist/index.cjs CHANGED
@@ -2391,9 +2391,41 @@ var MultiAgentStateConfig = {
2391
2391
  };
2392
2392
  var MultiAgentState = (0, import_core7.createStateAnnotation)(MultiAgentStateConfig);
2393
2393
 
2394
- // src/multi-agent/routing.ts
2394
+ // src/multi-agent/routing-internal/llm-routing.ts
2395
2395
  var import_messages5 = require("@langchain/core/messages");
2396
2396
  var logger = createPatternLogger("agentforge:patterns:multi-agent:routing");
2397
+ var DEFAULT_SUPERVISOR_SYSTEM_PROMPT = `You are a supervisor agent responsible for routing tasks to specialized worker agents.
2398
+
2399
+ Your job is to:
2400
+ 1. Analyze the current task and context
2401
+ 2. Review available worker capabilities
2402
+ 3. Select the most appropriate worker(s) for the task
2403
+ 4. Provide clear reasoning for your decision
2404
+
2405
+ **IMPORTANT: You can route to MULTIPLE workers for parallel execution when:**
2406
+ - The task requires information from multiple domains (e.g., code + documentation)
2407
+ - Multiple workers have complementary expertise
2408
+ - Parallel execution would provide a more comprehensive answer
2409
+
2410
+ **Response Format:**
2411
+
2412
+ For SINGLE worker routing:
2413
+ {
2414
+ "targetAgent": "worker_id",
2415
+ "reasoning": "explanation of why this worker is best suited",
2416
+ "confidence": 0.0-1.0,
2417
+ "strategy": "llm-based"
2418
+ }
2419
+
2420
+ For PARALLEL multi-worker routing:
2421
+ {
2422
+ "targetAgents": ["worker_id_1", "worker_id_2", ...],
2423
+ "reasoning": "explanation of why these workers should work in parallel",
2424
+ "confidence": 0.0-1.0,
2425
+ "strategy": "llm-based"
2426
+ }
2427
+
2428
+ Choose parallel routing when the task benefits from multiple perspectives or data sources.`;
2397
2429
  function hasStructuredOutput(model) {
2398
2430
  return typeof model.withStructuredOutput === "function";
2399
2431
  }
@@ -2449,58 +2481,29 @@ function finalizeLlmRoutingDecision(decision) {
2449
2481
  timestamp: Date.now()
2450
2482
  };
2451
2483
  }
2484
+ async function invokeStructuredRoutingDecision(model, messages) {
2485
+ let structuredModel;
2486
+ try {
2487
+ structuredModel = model.withStructuredOutput(RoutingDecisionSchema);
2488
+ } catch (error) {
2489
+ logger.warn("Structured output unavailable, using direct routing fallback", {
2490
+ strategy: "llm-based",
2491
+ fallback: "direct-model-invoke",
2492
+ error: error instanceof Error ? error.message : String(error)
2493
+ });
2494
+ const decision2 = await model.invoke(messages);
2495
+ return finalizeLlmRoutingDecision(decision2);
2496
+ }
2497
+ const decision = await structuredModel.invoke(messages);
2498
+ return finalizeLlmRoutingDecision(decision);
2499
+ }
2452
2500
  async function invokeRoutingDecision(model, messages) {
2453
2501
  if (hasStructuredOutput(model)) {
2454
- let structuredModel;
2455
- try {
2456
- structuredModel = model.withStructuredOutput(RoutingDecisionSchema);
2457
- } catch (error) {
2458
- logger.warn("Structured output unavailable, using direct routing fallback", {
2459
- strategy: "llm-based",
2460
- fallback: "direct-model-invoke",
2461
- error: error instanceof Error ? error.message : String(error)
2462
- });
2463
- const decision3 = await model.invoke(messages);
2464
- return finalizeLlmRoutingDecision(decision3);
2465
- }
2466
- const decision2 = await structuredModel.invoke(messages);
2467
- return finalizeLlmRoutingDecision(decision2);
2502
+ return invokeStructuredRoutingDecision(model, messages);
2468
2503
  }
2469
2504
  const decision = await model.invoke(messages);
2470
2505
  return finalizeLlmRoutingDecision(decision);
2471
2506
  }
2472
- var DEFAULT_SUPERVISOR_SYSTEM_PROMPT = `You are a supervisor agent responsible for routing tasks to specialized worker agents.
2473
-
2474
- Your job is to:
2475
- 1. Analyze the current task and context
2476
- 2. Review available worker capabilities
2477
- 3. Select the most appropriate worker(s) for the task
2478
- 4. Provide clear reasoning for your decision
2479
-
2480
- **IMPORTANT: You can route to MULTIPLE workers for parallel execution when:**
2481
- - The task requires information from multiple domains (e.g., code + documentation)
2482
- - Multiple workers have complementary expertise
2483
- - Parallel execution would provide a more comprehensive answer
2484
-
2485
- **Response Format:**
2486
-
2487
- For SINGLE worker routing:
2488
- {
2489
- "targetAgent": "worker_id",
2490
- "reasoning": "explanation of why this worker is best suited",
2491
- "confidence": 0.0-1.0,
2492
- "strategy": "llm-based"
2493
- }
2494
-
2495
- For PARALLEL multi-worker routing:
2496
- {
2497
- "targetAgents": ["worker_id_1", "worker_id_2", ...],
2498
- "reasoning": "explanation of why these workers should work in parallel",
2499
- "confidence": 0.0-1.0,
2500
- "strategy": "llm-based"
2501
- }
2502
-
2503
- Choose parallel routing when the task benefits from multiple perspectives or data sources.`;
2504
2507
  var llmBasedRouting = {
2505
2508
  name: "llm-based",
2506
2509
  async route(state, config) {
@@ -2526,56 +2529,119 @@ Select the best worker(s) for this task and explain your reasoning.`;
2526
2529
  new import_messages5.SystemMessage(systemPrompt),
2527
2530
  new import_messages5.HumanMessage(userPrompt)
2528
2531
  ];
2529
- return await invokeRoutingDecision(config.model, messages);
2532
+ return invokeRoutingDecision(config.model, messages);
2530
2533
  }
2531
2534
  };
2535
+
2536
+ // src/multi-agent/routing-internal/load-balanced-routing.ts
2537
+ function getAvailableWorkerLoads(state) {
2538
+ return Object.entries(state.workers).filter(([_, caps]) => caps.available).map(([id, caps]) => ({ id, workload: caps.currentWorkload })).sort((a, b) => a.workload - b.workload);
2539
+ }
2540
+ var loadBalancedRouting = {
2541
+ name: "load-balanced",
2542
+ async route(state, _config) {
2543
+ const availableWorkers = getAvailableWorkerLoads(state);
2544
+ if (availableWorkers.length === 0) {
2545
+ throw new Error("No available workers for load-balanced routing");
2546
+ }
2547
+ const targetWorker = availableWorkers[0];
2548
+ const avgWorkload = availableWorkers.reduce((sum, worker) => sum + worker.workload, 0) / availableWorkers.length;
2549
+ const confidence = targetWorker.workload === 0 ? 1 : Math.max(0.5, 1 - targetWorker.workload / (avgWorkload * 2));
2550
+ return {
2551
+ targetAgent: targetWorker.id,
2552
+ targetAgents: null,
2553
+ reasoning: `Lowest workload: ${targetWorker.workload} tasks (avg: ${avgWorkload.toFixed(1)})`,
2554
+ confidence,
2555
+ strategy: "load-balanced",
2556
+ timestamp: Date.now()
2557
+ };
2558
+ }
2559
+ };
2560
+
2561
+ // src/multi-agent/routing-internal/worker-selection.ts
2562
+ function getAvailableWorkerIds(state) {
2563
+ return Object.entries(state.workers).filter(([_, caps]) => caps.available).map(([id]) => id);
2564
+ }
2565
+ function getTaskContent(state) {
2566
+ const lastMessage = state.messages[state.messages.length - 1];
2567
+ return String(lastMessage?.content || state.input);
2568
+ }
2569
+
2570
+ // src/multi-agent/routing-internal/round-robin-routing.ts
2571
+ function createRoundRobinDecision(targetAgent, position, workerCount) {
2572
+ return {
2573
+ targetAgent,
2574
+ targetAgents: null,
2575
+ reasoning: `Round-robin selection: worker ${position} of ${workerCount}`,
2576
+ confidence: 1,
2577
+ strategy: "round-robin",
2578
+ timestamp: Date.now()
2579
+ };
2580
+ }
2532
2581
  var roundRobinRouting = {
2533
2582
  name: "round-robin",
2534
2583
  async route(state, _config) {
2535
- const availableWorkers = Object.entries(state.workers).filter(([_, caps]) => caps.available).map(([id]) => id);
2584
+ const availableWorkers = getAvailableWorkerIds(state);
2536
2585
  if (availableWorkers.length === 0) {
2537
2586
  throw new Error("No available workers for round-robin routing");
2538
2587
  }
2539
2588
  const lastRoutingIndex = state.routingHistory.length % availableWorkers.length;
2540
- const targetAgent = availableWorkers[lastRoutingIndex];
2589
+ return createRoundRobinDecision(
2590
+ availableWorkers[lastRoutingIndex],
2591
+ lastRoutingIndex + 1,
2592
+ availableWorkers.length
2593
+ );
2594
+ }
2595
+ };
2596
+
2597
+ // src/multi-agent/routing-internal/rule-based-routing.ts
2598
+ var ruleBasedRouting = {
2599
+ name: "rule-based",
2600
+ async route(state, config) {
2601
+ if (!config.routingFn) {
2602
+ throw new Error("Rule-based routing requires a custom routing function");
2603
+ }
2604
+ return config.routingFn(state);
2605
+ }
2606
+ };
2607
+
2608
+ // src/multi-agent/routing-internal/skill-based-routing.ts
2609
+ function scoreWorkers(state, taskContent) {
2610
+ return Object.entries(state.workers).filter(([_, caps]) => caps.available).map(([id, caps]) => {
2611
+ const skillMatches = caps.skills.filter(
2612
+ (skill) => taskContent.includes(skill.toLowerCase())
2613
+ ).length;
2614
+ const toolMatches = caps.tools.filter(
2615
+ (tool) => taskContent.includes(tool.toLowerCase())
2616
+ ).length;
2541
2617
  return {
2542
- targetAgent,
2543
- targetAgents: null,
2544
- reasoning: `Round-robin selection: worker ${lastRoutingIndex + 1} of ${availableWorkers.length}`,
2545
- confidence: 1,
2546
- strategy: "round-robin",
2547
- timestamp: Date.now()
2618
+ id,
2619
+ score: skillMatches * 2 + toolMatches,
2620
+ skills: caps.skills
2548
2621
  };
2622
+ }).filter((worker) => worker.score > 0).sort((a, b) => b.score - a.score);
2623
+ }
2624
+ function createFallbackDecision(state) {
2625
+ const firstAvailable = Object.entries(state.workers).find(([_, caps]) => caps.available);
2626
+ if (!firstAvailable) {
2627
+ throw new Error("No available workers for skill-based routing");
2549
2628
  }
2550
- };
2629
+ return {
2630
+ targetAgent: firstAvailable[0],
2631
+ targetAgents: null,
2632
+ reasoning: "No skill matches found, using first available worker",
2633
+ confidence: 0.5,
2634
+ strategy: "skill-based",
2635
+ timestamp: Date.now()
2636
+ };
2637
+ }
2551
2638
  var skillBasedRouting = {
2552
2639
  name: "skill-based",
2553
2640
  async route(state, _config) {
2554
- const lastMessage = state.messages[state.messages.length - 1];
2555
- const taskContent = (lastMessage?.content || state.input).toLowerCase();
2556
- const workerScores = Object.entries(state.workers).filter(([_, caps]) => caps.available).map(([id, caps]) => {
2557
- const skillMatches = caps.skills.filter(
2558
- (skill) => taskContent.includes(skill.toLowerCase())
2559
- ).length;
2560
- const toolMatches = caps.tools.filter(
2561
- (tool) => taskContent.includes(tool.toLowerCase())
2562
- ).length;
2563
- const score = skillMatches * 2 + toolMatches;
2564
- return { id, score, skills: caps.skills, tools: caps.tools };
2565
- }).filter((w) => w.score > 0).sort((a, b) => b.score - a.score);
2641
+ const taskContent = getTaskContent(state).toLowerCase();
2642
+ const workerScores = scoreWorkers(state, taskContent);
2566
2643
  if (workerScores.length === 0) {
2567
- const firstAvailable = Object.entries(state.workers).find(([_, caps]) => caps.available);
2568
- if (!firstAvailable) {
2569
- throw new Error("No available workers for skill-based routing");
2570
- }
2571
- return {
2572
- targetAgent: firstAvailable[0],
2573
- targetAgents: null,
2574
- reasoning: "No skill matches found, using first available worker",
2575
- confidence: 0.5,
2576
- strategy: "skill-based",
2577
- timestamp: Date.now()
2578
- };
2644
+ return createFallbackDecision(state);
2579
2645
  }
2580
2646
  const best = workerScores[0];
2581
2647
  const confidence = Math.min(best.score / 5, 1);
@@ -2589,50 +2655,20 @@ var skillBasedRouting = {
2589
2655
  };
2590
2656
  }
2591
2657
  };
2592
- var loadBalancedRouting = {
2593
- name: "load-balanced",
2594
- async route(state, _config) {
2595
- const availableWorkers = Object.entries(state.workers).filter(([_, caps]) => caps.available).map(([id, caps]) => ({ id, workload: caps.currentWorkload })).sort((a, b) => a.workload - b.workload);
2596
- if (availableWorkers.length === 0) {
2597
- throw new Error("No available workers for load-balanced routing");
2598
- }
2599
- const targetWorker = availableWorkers[0];
2600
- const avgWorkload = availableWorkers.reduce((sum, w) => sum + w.workload, 0) / availableWorkers.length;
2601
- const confidence = targetWorker.workload === 0 ? 1 : Math.max(0.5, 1 - targetWorker.workload / (avgWorkload * 2));
2602
- return {
2603
- targetAgent: targetWorker.id,
2604
- targetAgents: null,
2605
- reasoning: `Lowest workload: ${targetWorker.workload} tasks (avg: ${avgWorkload.toFixed(1)})`,
2606
- confidence,
2607
- strategy: "load-balanced",
2608
- timestamp: Date.now()
2609
- };
2610
- }
2611
- };
2612
- var ruleBasedRouting = {
2613
- name: "rule-based",
2614
- async route(state, config) {
2615
- if (!config.routingFn) {
2616
- throw new Error("Rule-based routing requires a custom routing function");
2617
- }
2618
- return await config.routingFn(state);
2619
- }
2658
+
2659
+ // src/multi-agent/routing.ts
2660
+ var routingStrategies = {
2661
+ "llm-based": llmBasedRouting,
2662
+ "round-robin": roundRobinRouting,
2663
+ "skill-based": skillBasedRouting,
2664
+ "load-balanced": loadBalancedRouting,
2665
+ "rule-based": ruleBasedRouting
2620
2666
  };
2621
2667
  function getRoutingStrategy(name) {
2622
- switch (name) {
2623
- case "llm-based":
2624
- return llmBasedRouting;
2625
- case "round-robin":
2626
- return roundRobinRouting;
2627
- case "skill-based":
2628
- return skillBasedRouting;
2629
- case "load-balanced":
2630
- return loadBalancedRouting;
2631
- case "rule-based":
2632
- return ruleBasedRouting;
2633
- default:
2634
- throw new Error(`Unknown routing strategy: ${name}`);
2668
+ if (!Object.hasOwn(routingStrategies, name)) {
2669
+ throw new Error(`Unknown routing strategy: ${name}`);
2635
2670
  }
2671
+ return routingStrategies[name];
2636
2672
  }
2637
2673
 
2638
2674
  // src/multi-agent/utils.ts
package/dist/index.d.cts CHANGED
@@ -3387,37 +3387,29 @@ interface RoutingStrategyImpl {
3387
3387
  route: (state: MultiAgentStateType, config: SupervisorConfig) => Promise<RoutingDecision>;
3388
3388
  }
3389
3389
 
3390
- /**
3391
- * Default system prompt for LLM-based routing
3392
- */
3393
3390
  declare const DEFAULT_SUPERVISOR_SYSTEM_PROMPT = "You are a supervisor agent responsible for routing tasks to specialized worker agents.\n\nYour job is to:\n1. Analyze the current task and context\n2. Review available worker capabilities\n3. Select the most appropriate worker(s) for the task\n4. Provide clear reasoning for your decision\n\n**IMPORTANT: You can route to MULTIPLE workers for parallel execution when:**\n- The task requires information from multiple domains (e.g., code + documentation)\n- Multiple workers have complementary expertise\n- Parallel execution would provide a more comprehensive answer\n\n**Response Format:**\n\nFor SINGLE worker routing:\n{\n \"targetAgent\": \"worker_id\",\n \"reasoning\": \"explanation of why this worker is best suited\",\n \"confidence\": 0.0-1.0,\n \"strategy\": \"llm-based\"\n}\n\nFor PARALLEL multi-worker routing:\n{\n \"targetAgents\": [\"worker_id_1\", \"worker_id_2\", ...],\n \"reasoning\": \"explanation of why these workers should work in parallel\",\n \"confidence\": 0.0-1.0,\n \"strategy\": \"llm-based\"\n}\n\nChoose parallel routing when the task benefits from multiple perspectives or data sources.";
3394
- /**
3395
- * LLM-based routing strategy
3396
- * Uses an LLM to intelligently route tasks based on worker capabilities
3397
- */
3398
3391
  declare const llmBasedRouting: RoutingStrategyImpl;
3399
- /**
3400
- * Round-robin routing strategy
3401
- * Distributes tasks evenly across all available workers
3402
- */
3392
+
3393
+ declare const loadBalancedRouting: RoutingStrategyImpl;
3394
+
3403
3395
  declare const roundRobinRouting: RoutingStrategyImpl;
3404
- /**
3405
- * Skill-based routing strategy
3406
- * Routes tasks to workers based on matching skills
3407
- */
3396
+
3397
+ declare const ruleBasedRouting: RoutingStrategyImpl;
3398
+
3408
3399
  declare const skillBasedRouting: RoutingStrategyImpl;
3400
+
3409
3401
  /**
3410
- * Load-balanced routing strategy
3411
- * Routes tasks to workers with the lowest current workload
3412
- */
3413
- declare const loadBalancedRouting: RoutingStrategyImpl;
3414
- /**
3415
- * Rule-based routing strategy
3416
- * Uses custom routing function provided in config
3402
+ * Routing Strategy Implementations for Multi-Agent Pattern
3403
+ *
3404
+ * This module is the thin public facade for multi-agent routing strategies.
3405
+ * Concrete strategy logic lives in focused internal modules so behavior can be
3406
+ * reviewed and extended without one oversized implementation file.
3407
+ *
3408
+ * @module patterns/multi-agent/routing
3417
3409
  */
3418
- declare const ruleBasedRouting: RoutingStrategyImpl;
3410
+
3419
3411
  /**
3420
- * Get routing strategy implementation by name
3412
+ * Get routing strategy implementation by name.
3421
3413
  */
3422
3414
  declare function getRoutingStrategy(name: string): RoutingStrategyImpl;
3423
3415
 
package/dist/index.d.ts CHANGED
@@ -3387,37 +3387,29 @@ interface RoutingStrategyImpl {
3387
3387
  route: (state: MultiAgentStateType, config: SupervisorConfig) => Promise<RoutingDecision>;
3388
3388
  }
3389
3389
 
3390
- /**
3391
- * Default system prompt for LLM-based routing
3392
- */
3393
3390
  declare const DEFAULT_SUPERVISOR_SYSTEM_PROMPT = "You are a supervisor agent responsible for routing tasks to specialized worker agents.\n\nYour job is to:\n1. Analyze the current task and context\n2. Review available worker capabilities\n3. Select the most appropriate worker(s) for the task\n4. Provide clear reasoning for your decision\n\n**IMPORTANT: You can route to MULTIPLE workers for parallel execution when:**\n- The task requires information from multiple domains (e.g., code + documentation)\n- Multiple workers have complementary expertise\n- Parallel execution would provide a more comprehensive answer\n\n**Response Format:**\n\nFor SINGLE worker routing:\n{\n \"targetAgent\": \"worker_id\",\n \"reasoning\": \"explanation of why this worker is best suited\",\n \"confidence\": 0.0-1.0,\n \"strategy\": \"llm-based\"\n}\n\nFor PARALLEL multi-worker routing:\n{\n \"targetAgents\": [\"worker_id_1\", \"worker_id_2\", ...],\n \"reasoning\": \"explanation of why these workers should work in parallel\",\n \"confidence\": 0.0-1.0,\n \"strategy\": \"llm-based\"\n}\n\nChoose parallel routing when the task benefits from multiple perspectives or data sources.";
3394
- /**
3395
- * LLM-based routing strategy
3396
- * Uses an LLM to intelligently route tasks based on worker capabilities
3397
- */
3398
3391
  declare const llmBasedRouting: RoutingStrategyImpl;
3399
- /**
3400
- * Round-robin routing strategy
3401
- * Distributes tasks evenly across all available workers
3402
- */
3392
+
3393
+ declare const loadBalancedRouting: RoutingStrategyImpl;
3394
+
3403
3395
  declare const roundRobinRouting: RoutingStrategyImpl;
3404
- /**
3405
- * Skill-based routing strategy
3406
- * Routes tasks to workers based on matching skills
3407
- */
3396
+
3397
+ declare const ruleBasedRouting: RoutingStrategyImpl;
3398
+
3408
3399
  declare const skillBasedRouting: RoutingStrategyImpl;
3400
+
3409
3401
  /**
3410
- * Load-balanced routing strategy
3411
- * Routes tasks to workers with the lowest current workload
3412
- */
3413
- declare const loadBalancedRouting: RoutingStrategyImpl;
3414
- /**
3415
- * Rule-based routing strategy
3416
- * Uses custom routing function provided in config
3402
+ * Routing Strategy Implementations for Multi-Agent Pattern
3403
+ *
3404
+ * This module is the thin public facade for multi-agent routing strategies.
3405
+ * Concrete strategy logic lives in focused internal modules so behavior can be
3406
+ * reviewed and extended without one oversized implementation file.
3407
+ *
3408
+ * @module patterns/multi-agent/routing
3417
3409
  */
3418
- declare const ruleBasedRouting: RoutingStrategyImpl;
3410
+
3419
3411
  /**
3420
- * Get routing strategy implementation by name
3412
+ * Get routing strategy implementation by name.
3421
3413
  */
3422
3414
  declare function getRoutingStrategy(name: string): RoutingStrategyImpl;
3423
3415
 
package/dist/index.js CHANGED
@@ -2293,9 +2293,41 @@ var MultiAgentStateConfig = {
2293
2293
  };
2294
2294
  var MultiAgentState = createStateAnnotation4(MultiAgentStateConfig);
2295
2295
 
2296
- // src/multi-agent/routing.ts
2296
+ // src/multi-agent/routing-internal/llm-routing.ts
2297
2297
  import { HumanMessage as HumanMessage5, SystemMessage as SystemMessage5 } from "@langchain/core/messages";
2298
2298
  var logger = createPatternLogger("agentforge:patterns:multi-agent:routing");
2299
+ var DEFAULT_SUPERVISOR_SYSTEM_PROMPT = `You are a supervisor agent responsible for routing tasks to specialized worker agents.
2300
+
2301
+ Your job is to:
2302
+ 1. Analyze the current task and context
2303
+ 2. Review available worker capabilities
2304
+ 3. Select the most appropriate worker(s) for the task
2305
+ 4. Provide clear reasoning for your decision
2306
+
2307
+ **IMPORTANT: You can route to MULTIPLE workers for parallel execution when:**
2308
+ - The task requires information from multiple domains (e.g., code + documentation)
2309
+ - Multiple workers have complementary expertise
2310
+ - Parallel execution would provide a more comprehensive answer
2311
+
2312
+ **Response Format:**
2313
+
2314
+ For SINGLE worker routing:
2315
+ {
2316
+ "targetAgent": "worker_id",
2317
+ "reasoning": "explanation of why this worker is best suited",
2318
+ "confidence": 0.0-1.0,
2319
+ "strategy": "llm-based"
2320
+ }
2321
+
2322
+ For PARALLEL multi-worker routing:
2323
+ {
2324
+ "targetAgents": ["worker_id_1", "worker_id_2", ...],
2325
+ "reasoning": "explanation of why these workers should work in parallel",
2326
+ "confidence": 0.0-1.0,
2327
+ "strategy": "llm-based"
2328
+ }
2329
+
2330
+ Choose parallel routing when the task benefits from multiple perspectives or data sources.`;
2299
2331
  function hasStructuredOutput(model) {
2300
2332
  return typeof model.withStructuredOutput === "function";
2301
2333
  }
@@ -2351,58 +2383,29 @@ function finalizeLlmRoutingDecision(decision) {
2351
2383
  timestamp: Date.now()
2352
2384
  };
2353
2385
  }
2386
+ async function invokeStructuredRoutingDecision(model, messages) {
2387
+ let structuredModel;
2388
+ try {
2389
+ structuredModel = model.withStructuredOutput(RoutingDecisionSchema);
2390
+ } catch (error) {
2391
+ logger.warn("Structured output unavailable, using direct routing fallback", {
2392
+ strategy: "llm-based",
2393
+ fallback: "direct-model-invoke",
2394
+ error: error instanceof Error ? error.message : String(error)
2395
+ });
2396
+ const decision2 = await model.invoke(messages);
2397
+ return finalizeLlmRoutingDecision(decision2);
2398
+ }
2399
+ const decision = await structuredModel.invoke(messages);
2400
+ return finalizeLlmRoutingDecision(decision);
2401
+ }
2354
2402
  async function invokeRoutingDecision(model, messages) {
2355
2403
  if (hasStructuredOutput(model)) {
2356
- let structuredModel;
2357
- try {
2358
- structuredModel = model.withStructuredOutput(RoutingDecisionSchema);
2359
- } catch (error) {
2360
- logger.warn("Structured output unavailable, using direct routing fallback", {
2361
- strategy: "llm-based",
2362
- fallback: "direct-model-invoke",
2363
- error: error instanceof Error ? error.message : String(error)
2364
- });
2365
- const decision3 = await model.invoke(messages);
2366
- return finalizeLlmRoutingDecision(decision3);
2367
- }
2368
- const decision2 = await structuredModel.invoke(messages);
2369
- return finalizeLlmRoutingDecision(decision2);
2404
+ return invokeStructuredRoutingDecision(model, messages);
2370
2405
  }
2371
2406
  const decision = await model.invoke(messages);
2372
2407
  return finalizeLlmRoutingDecision(decision);
2373
2408
  }
2374
- var DEFAULT_SUPERVISOR_SYSTEM_PROMPT = `You are a supervisor agent responsible for routing tasks to specialized worker agents.
2375
-
2376
- Your job is to:
2377
- 1. Analyze the current task and context
2378
- 2. Review available worker capabilities
2379
- 3. Select the most appropriate worker(s) for the task
2380
- 4. Provide clear reasoning for your decision
2381
-
2382
- **IMPORTANT: You can route to MULTIPLE workers for parallel execution when:**
2383
- - The task requires information from multiple domains (e.g., code + documentation)
2384
- - Multiple workers have complementary expertise
2385
- - Parallel execution would provide a more comprehensive answer
2386
-
2387
- **Response Format:**
2388
-
2389
- For SINGLE worker routing:
2390
- {
2391
- "targetAgent": "worker_id",
2392
- "reasoning": "explanation of why this worker is best suited",
2393
- "confidence": 0.0-1.0,
2394
- "strategy": "llm-based"
2395
- }
2396
-
2397
- For PARALLEL multi-worker routing:
2398
- {
2399
- "targetAgents": ["worker_id_1", "worker_id_2", ...],
2400
- "reasoning": "explanation of why these workers should work in parallel",
2401
- "confidence": 0.0-1.0,
2402
- "strategy": "llm-based"
2403
- }
2404
-
2405
- Choose parallel routing when the task benefits from multiple perspectives or data sources.`;
2406
2409
  var llmBasedRouting = {
2407
2410
  name: "llm-based",
2408
2411
  async route(state, config) {
@@ -2428,56 +2431,119 @@ Select the best worker(s) for this task and explain your reasoning.`;
2428
2431
  new SystemMessage5(systemPrompt),
2429
2432
  new HumanMessage5(userPrompt)
2430
2433
  ];
2431
- return await invokeRoutingDecision(config.model, messages);
2434
+ return invokeRoutingDecision(config.model, messages);
2432
2435
  }
2433
2436
  };
2437
+
2438
+ // src/multi-agent/routing-internal/load-balanced-routing.ts
2439
+ function getAvailableWorkerLoads(state) {
2440
+ return Object.entries(state.workers).filter(([_, caps]) => caps.available).map(([id, caps]) => ({ id, workload: caps.currentWorkload })).sort((a, b) => a.workload - b.workload);
2441
+ }
2442
+ var loadBalancedRouting = {
2443
+ name: "load-balanced",
2444
+ async route(state, _config) {
2445
+ const availableWorkers = getAvailableWorkerLoads(state);
2446
+ if (availableWorkers.length === 0) {
2447
+ throw new Error("No available workers for load-balanced routing");
2448
+ }
2449
+ const targetWorker = availableWorkers[0];
2450
+ const avgWorkload = availableWorkers.reduce((sum, worker) => sum + worker.workload, 0) / availableWorkers.length;
2451
+ const confidence = targetWorker.workload === 0 ? 1 : Math.max(0.5, 1 - targetWorker.workload / (avgWorkload * 2));
2452
+ return {
2453
+ targetAgent: targetWorker.id,
2454
+ targetAgents: null,
2455
+ reasoning: `Lowest workload: ${targetWorker.workload} tasks (avg: ${avgWorkload.toFixed(1)})`,
2456
+ confidence,
2457
+ strategy: "load-balanced",
2458
+ timestamp: Date.now()
2459
+ };
2460
+ }
2461
+ };
2462
+
2463
+ // src/multi-agent/routing-internal/worker-selection.ts
2464
+ function getAvailableWorkerIds(state) {
2465
+ return Object.entries(state.workers).filter(([_, caps]) => caps.available).map(([id]) => id);
2466
+ }
2467
+ function getTaskContent(state) {
2468
+ const lastMessage = state.messages[state.messages.length - 1];
2469
+ return String(lastMessage?.content || state.input);
2470
+ }
2471
+
2472
+ // src/multi-agent/routing-internal/round-robin-routing.ts
2473
+ function createRoundRobinDecision(targetAgent, position, workerCount) {
2474
+ return {
2475
+ targetAgent,
2476
+ targetAgents: null,
2477
+ reasoning: `Round-robin selection: worker ${position} of ${workerCount}`,
2478
+ confidence: 1,
2479
+ strategy: "round-robin",
2480
+ timestamp: Date.now()
2481
+ };
2482
+ }
2434
2483
  var roundRobinRouting = {
2435
2484
  name: "round-robin",
2436
2485
  async route(state, _config) {
2437
- const availableWorkers = Object.entries(state.workers).filter(([_, caps]) => caps.available).map(([id]) => id);
2486
+ const availableWorkers = getAvailableWorkerIds(state);
2438
2487
  if (availableWorkers.length === 0) {
2439
2488
  throw new Error("No available workers for round-robin routing");
2440
2489
  }
2441
2490
  const lastRoutingIndex = state.routingHistory.length % availableWorkers.length;
2442
- const targetAgent = availableWorkers[lastRoutingIndex];
2491
+ return createRoundRobinDecision(
2492
+ availableWorkers[lastRoutingIndex],
2493
+ lastRoutingIndex + 1,
2494
+ availableWorkers.length
2495
+ );
2496
+ }
2497
+ };
2498
+
2499
+ // src/multi-agent/routing-internal/rule-based-routing.ts
2500
+ var ruleBasedRouting = {
2501
+ name: "rule-based",
2502
+ async route(state, config) {
2503
+ if (!config.routingFn) {
2504
+ throw new Error("Rule-based routing requires a custom routing function");
2505
+ }
2506
+ return config.routingFn(state);
2507
+ }
2508
+ };
2509
+
2510
+ // src/multi-agent/routing-internal/skill-based-routing.ts
2511
+ function scoreWorkers(state, taskContent) {
2512
+ return Object.entries(state.workers).filter(([_, caps]) => caps.available).map(([id, caps]) => {
2513
+ const skillMatches = caps.skills.filter(
2514
+ (skill) => taskContent.includes(skill.toLowerCase())
2515
+ ).length;
2516
+ const toolMatches = caps.tools.filter(
2517
+ (tool) => taskContent.includes(tool.toLowerCase())
2518
+ ).length;
2443
2519
  return {
2444
- targetAgent,
2445
- targetAgents: null,
2446
- reasoning: `Round-robin selection: worker ${lastRoutingIndex + 1} of ${availableWorkers.length}`,
2447
- confidence: 1,
2448
- strategy: "round-robin",
2449
- timestamp: Date.now()
2520
+ id,
2521
+ score: skillMatches * 2 + toolMatches,
2522
+ skills: caps.skills
2450
2523
  };
2524
+ }).filter((worker) => worker.score > 0).sort((a, b) => b.score - a.score);
2525
+ }
2526
+ function createFallbackDecision(state) {
2527
+ const firstAvailable = Object.entries(state.workers).find(([_, caps]) => caps.available);
2528
+ if (!firstAvailable) {
2529
+ throw new Error("No available workers for skill-based routing");
2451
2530
  }
2452
- };
2531
+ return {
2532
+ targetAgent: firstAvailable[0],
2533
+ targetAgents: null,
2534
+ reasoning: "No skill matches found, using first available worker",
2535
+ confidence: 0.5,
2536
+ strategy: "skill-based",
2537
+ timestamp: Date.now()
2538
+ };
2539
+ }
2453
2540
  var skillBasedRouting = {
2454
2541
  name: "skill-based",
2455
2542
  async route(state, _config) {
2456
- const lastMessage = state.messages[state.messages.length - 1];
2457
- const taskContent = (lastMessage?.content || state.input).toLowerCase();
2458
- const workerScores = Object.entries(state.workers).filter(([_, caps]) => caps.available).map(([id, caps]) => {
2459
- const skillMatches = caps.skills.filter(
2460
- (skill) => taskContent.includes(skill.toLowerCase())
2461
- ).length;
2462
- const toolMatches = caps.tools.filter(
2463
- (tool) => taskContent.includes(tool.toLowerCase())
2464
- ).length;
2465
- const score = skillMatches * 2 + toolMatches;
2466
- return { id, score, skills: caps.skills, tools: caps.tools };
2467
- }).filter((w) => w.score > 0).sort((a, b) => b.score - a.score);
2543
+ const taskContent = getTaskContent(state).toLowerCase();
2544
+ const workerScores = scoreWorkers(state, taskContent);
2468
2545
  if (workerScores.length === 0) {
2469
- const firstAvailable = Object.entries(state.workers).find(([_, caps]) => caps.available);
2470
- if (!firstAvailable) {
2471
- throw new Error("No available workers for skill-based routing");
2472
- }
2473
- return {
2474
- targetAgent: firstAvailable[0],
2475
- targetAgents: null,
2476
- reasoning: "No skill matches found, using first available worker",
2477
- confidence: 0.5,
2478
- strategy: "skill-based",
2479
- timestamp: Date.now()
2480
- };
2546
+ return createFallbackDecision(state);
2481
2547
  }
2482
2548
  const best = workerScores[0];
2483
2549
  const confidence = Math.min(best.score / 5, 1);
@@ -2491,50 +2557,20 @@ var skillBasedRouting = {
2491
2557
  };
2492
2558
  }
2493
2559
  };
2494
- var loadBalancedRouting = {
2495
- name: "load-balanced",
2496
- async route(state, _config) {
2497
- const availableWorkers = Object.entries(state.workers).filter(([_, caps]) => caps.available).map(([id, caps]) => ({ id, workload: caps.currentWorkload })).sort((a, b) => a.workload - b.workload);
2498
- if (availableWorkers.length === 0) {
2499
- throw new Error("No available workers for load-balanced routing");
2500
- }
2501
- const targetWorker = availableWorkers[0];
2502
- const avgWorkload = availableWorkers.reduce((sum, w) => sum + w.workload, 0) / availableWorkers.length;
2503
- const confidence = targetWorker.workload === 0 ? 1 : Math.max(0.5, 1 - targetWorker.workload / (avgWorkload * 2));
2504
- return {
2505
- targetAgent: targetWorker.id,
2506
- targetAgents: null,
2507
- reasoning: `Lowest workload: ${targetWorker.workload} tasks (avg: ${avgWorkload.toFixed(1)})`,
2508
- confidence,
2509
- strategy: "load-balanced",
2510
- timestamp: Date.now()
2511
- };
2512
- }
2513
- };
2514
- var ruleBasedRouting = {
2515
- name: "rule-based",
2516
- async route(state, config) {
2517
- if (!config.routingFn) {
2518
- throw new Error("Rule-based routing requires a custom routing function");
2519
- }
2520
- return await config.routingFn(state);
2521
- }
2560
+
2561
+ // src/multi-agent/routing.ts
2562
+ var routingStrategies = {
2563
+ "llm-based": llmBasedRouting,
2564
+ "round-robin": roundRobinRouting,
2565
+ "skill-based": skillBasedRouting,
2566
+ "load-balanced": loadBalancedRouting,
2567
+ "rule-based": ruleBasedRouting
2522
2568
  };
2523
2569
  function getRoutingStrategy(name) {
2524
- switch (name) {
2525
- case "llm-based":
2526
- return llmBasedRouting;
2527
- case "round-robin":
2528
- return roundRobinRouting;
2529
- case "skill-based":
2530
- return skillBasedRouting;
2531
- case "load-balanced":
2532
- return loadBalancedRouting;
2533
- case "rule-based":
2534
- return ruleBasedRouting;
2535
- default:
2536
- throw new Error(`Unknown routing strategy: ${name}`);
2570
+ if (!Object.hasOwn(routingStrategies, name)) {
2571
+ throw new Error(`Unknown routing strategy: ${name}`);
2537
2572
  }
2573
+ return routingStrategies[name];
2538
2574
  }
2539
2575
 
2540
2576
  // src/multi-agent/utils.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentforge/patterns",
3
- "version": "0.16.32",
3
+ "version": "0.16.34",
4
4
  "description": "Production-ready agent workflow patterns for TypeScript including ReAct and Planner-Executor, built on LangGraph.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -41,13 +41,13 @@
41
41
  "url": "https://github.com/TVScoundrel/agentforge/issues"
42
42
  },
43
43
  "dependencies": {
44
- "@agentforge/core": "0.16.32",
44
+ "@agentforge/core": "0.16.34",
45
45
  "@langchain/core": "^1.1.17",
46
46
  "@langchain/langgraph": "^1.1.2",
47
47
  "zod": "^3.23.8"
48
48
  },
49
49
  "devDependencies": {
50
- "@agentforge/testing": "0.16.32",
50
+ "@agentforge/testing": "0.16.34",
51
51
  "@eslint/js": "^9.17.0",
52
52
  "@types/node": "^22.10.2",
53
53
  "eslint": "^9.17.0",