@agentforge/patterns 0.6.2 → 0.6.4

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
@@ -71,11 +71,14 @@ __export(index_exports, {
71
71
  ToolCallSchema: () => ToolCallSchema,
72
72
  ToolResultSchema: () => ToolResultSchema,
73
73
  WorkerCapabilitiesSchema: () => WorkerCapabilitiesSchema,
74
+ buildDeduplicationMetrics: () => buildDeduplicationMetrics,
75
+ calculateDeduplicationSavings: () => calculateDeduplicationSavings,
74
76
  createAggregatorNode: () => createAggregatorNode,
75
77
  createExecutorNode: () => createExecutorNode,
76
78
  createFinisherNode: () => createFinisherNode,
77
79
  createGeneratorNode: () => createGeneratorNode,
78
80
  createMultiAgentSystem: () => createMultiAgentSystem,
81
+ createPatternLogger: () => createPatternLogger,
79
82
  createPlanExecuteAgent: () => createPlanExecuteAgent,
80
83
  createPlannerNode: () => createPlannerNode,
81
84
  createReActAgent: () => createReActAgent,
@@ -87,6 +90,7 @@ __export(index_exports, {
87
90
  createReviserNode: () => createReviserNode,
88
91
  createSupervisorNode: () => createSupervisorNode,
89
92
  createWorkerNode: () => createWorkerNode,
93
+ generateToolCallCacheKey: () => generateToolCallCacheKey,
90
94
  getRoutingStrategy: () => getRoutingStrategy,
91
95
  llmBasedRouting: () => llmBasedRouting,
92
96
  loadBalancedRouting: () => loadBalancedRouting,
@@ -121,7 +125,9 @@ var ToolResultSchema = import_zod.z.object({
121
125
  toolCallId: import_zod.z.string(),
122
126
  result: import_zod.z.any(),
123
127
  error: import_zod.z.string().optional(),
124
- timestamp: import_zod.z.number().optional()
128
+ timestamp: import_zod.z.number().optional(),
129
+ isDuplicate: import_zod.z.boolean().optional()
130
+ // Flag indicating this was a duplicate tool call
125
131
  });
126
132
  var ScratchpadEntrySchema = import_zod.z.object({
127
133
  step: import_zod.z.number(),
@@ -245,14 +251,50 @@ function formatScratchpad(scratchpad) {
245
251
 
246
252
  // src/react/nodes.ts
247
253
  var import_messages = require("@langchain/core/messages");
254
+ var import_core3 = require("@agentforge/core");
255
+
256
+ // src/shared/deduplication.ts
248
257
  var import_core2 = require("@agentforge/core");
258
+ function generateToolCallCacheKey(toolName, args) {
259
+ const sortedArgs = JSON.stringify(args, Object.keys(args || {}).sort());
260
+ return `${toolName}:${sortedArgs}`;
261
+ }
262
+ function createPatternLogger(name, defaultLevel = "info") {
263
+ const logLevel5 = process.env.LOG_LEVEL?.toLowerCase() || defaultLevel;
264
+ return (0, import_core2.createLogger)(name, { level: logLevel5 });
265
+ }
266
+ function calculateDeduplicationSavings(duplicatesSkipped, toolsExecuted) {
267
+ if (duplicatesSkipped === 0) {
268
+ return "0%";
269
+ }
270
+ const total = toolsExecuted + duplicatesSkipped;
271
+ return `${Math.round(duplicatesSkipped / total * 100)}%`;
272
+ }
273
+ function buildDeduplicationMetrics(toolsExecuted, duplicatesSkipped, totalObservations) {
274
+ return {
275
+ toolsExecuted,
276
+ duplicatesSkipped,
277
+ totalObservations,
278
+ deduplicationSavings: calculateDeduplicationSavings(duplicatesSkipped, toolsExecuted)
279
+ };
280
+ }
281
+
282
+ // src/react/nodes.ts
283
+ var reasoningLogger = createPatternLogger("agentforge:patterns:react:reasoning");
284
+ var actionLogger = createPatternLogger("agentforge:patterns:react:action");
285
+ var observationLogger = createPatternLogger("agentforge:patterns:react:observation");
249
286
  function createReasoningNode(llm, tools, systemPrompt, maxIterations, verbose = false) {
250
- const langchainTools = (0, import_core2.toLangChainTools)(tools);
287
+ const langchainTools = (0, import_core3.toLangChainTools)(tools);
251
288
  const llmWithTools = llm.bindTools ? llm.bindTools(langchainTools) : llm;
252
289
  return async (state) => {
253
- if (verbose) {
254
- console.log(`[reasoning] Iteration ${state.iteration + 1}/${maxIterations}`);
255
- }
290
+ const currentIteration = state.iteration || 0;
291
+ const startTime = Date.now();
292
+ reasoningLogger.debug("Reasoning iteration started", {
293
+ iteration: currentIteration + 1,
294
+ maxIterations,
295
+ observationCount: state.observations?.length || 0,
296
+ hasActions: !!state.actions?.length
297
+ });
256
298
  const stateMessages = state.messages || [];
257
299
  const messages = [
258
300
  new import_messages.SystemMessage(systemPrompt),
@@ -282,8 +324,15 @@ ${scratchpadText}`));
282
324
  });
283
325
  }
284
326
  }
285
- const currentIteration = state.iteration || 0;
286
327
  const shouldContinue = toolCalls.length > 0 && currentIteration + 1 < maxIterations;
328
+ reasoningLogger.info("Reasoning complete", {
329
+ iteration: currentIteration + 1,
330
+ thoughtGenerated: !!thought,
331
+ actionCount: toolCalls.length,
332
+ shouldContinue,
333
+ isFinalResponse: toolCalls.length === 0,
334
+ duration: Date.now() - startTime
335
+ });
287
336
  return {
288
337
  messages: [{ role: "assistant", content: thought }],
289
338
  thoughts: thought ? [{ content: thought, timestamp: Date.now() }] : [],
@@ -296,16 +345,71 @@ ${scratchpadText}`));
296
345
  };
297
346
  };
298
347
  }
299
- function createActionNode(tools, verbose = false) {
348
+ function createActionNode(tools, verbose = false, enableDeduplication = true) {
300
349
  const toolMap = new Map(tools.map((tool) => [tool.metadata.name, tool]));
301
350
  return async (state) => {
302
351
  const actions = state.actions || [];
303
- if (verbose) {
304
- console.log(`[action] Executing ${actions.length} tool calls`);
305
- }
352
+ const allObservations = state.observations || [];
353
+ const iteration = state.iteration || 0;
354
+ const startTime = Date.now();
355
+ actionLogger.debug("Action node started", {
356
+ actionCount: actions.length,
357
+ iteration,
358
+ cacheEnabled: enableDeduplication
359
+ });
306
360
  const recentActions = actions.slice(-10);
307
361
  const observations = [];
362
+ const executionCache = /* @__PURE__ */ new Map();
363
+ let cacheSize = 0;
364
+ if (enableDeduplication) {
365
+ for (const observation of allObservations) {
366
+ const correspondingAction = actions.find((a) => a.id === observation.toolCallId);
367
+ if (correspondingAction) {
368
+ const cacheKey = generateToolCallCacheKey(correspondingAction.name, correspondingAction.arguments);
369
+ executionCache.set(cacheKey, observation);
370
+ cacheSize++;
371
+ }
372
+ }
373
+ if (cacheSize > 0) {
374
+ actionLogger.debug("Deduplication cache built", {
375
+ cacheSize,
376
+ totalObservations: allObservations.length
377
+ });
378
+ }
379
+ }
380
+ let duplicatesSkipped = 0;
381
+ let toolsExecuted = 0;
308
382
  for (const action of recentActions) {
383
+ const existingObservation = allObservations.find((obs) => obs.toolCallId === action.id);
384
+ if (existingObservation) {
385
+ actionLogger.debug("Skipping already-processed action", {
386
+ toolName: action.name,
387
+ toolCallId: action.id,
388
+ iteration
389
+ });
390
+ continue;
391
+ }
392
+ if (enableDeduplication) {
393
+ const cacheKey = generateToolCallCacheKey(action.name, action.arguments);
394
+ const cachedResult = executionCache.get(cacheKey);
395
+ if (cachedResult) {
396
+ duplicatesSkipped++;
397
+ actionLogger.info("Duplicate tool call prevented", {
398
+ toolName: action.name,
399
+ arguments: action.arguments,
400
+ iteration,
401
+ cacheHit: true
402
+ });
403
+ observations.push({
404
+ toolCallId: action.id,
405
+ result: cachedResult.result,
406
+ error: cachedResult.error,
407
+ timestamp: Date.now(),
408
+ isDuplicate: true
409
+ });
410
+ continue;
411
+ }
412
+ }
309
413
  const tool = toolMap.get(action.name);
310
414
  if (!tool) {
311
415
  observations.push({
@@ -317,31 +421,51 @@ function createActionNode(tools, verbose = false) {
317
421
  continue;
318
422
  }
319
423
  try {
424
+ const startTime2 = Date.now();
320
425
  const result = await tool.execute(action.arguments);
321
- observations.push({
426
+ const executionTime = Date.now() - startTime2;
427
+ toolsExecuted++;
428
+ actionLogger.debug("Tool executed successfully", {
429
+ toolName: action.name,
430
+ executionTime,
431
+ iteration
432
+ });
433
+ const observation = {
322
434
  toolCallId: action.id,
323
435
  result,
324
436
  timestamp: Date.now()
325
- });
326
- if (verbose) {
327
- console.log(`[action] Tool '${action.name}' executed successfully`);
437
+ };
438
+ observations.push(observation);
439
+ if (enableDeduplication) {
440
+ const cacheKey = generateToolCallCacheKey(action.name, action.arguments);
441
+ executionCache.set(cacheKey, observation);
328
442
  }
329
443
  } catch (error) {
330
444
  if (error && typeof error === "object" && "constructor" in error && error.constructor.name === "GraphInterrupt") {
331
445
  throw error;
332
446
  }
333
447
  const errorMessage = error instanceof Error ? error.message : String(error);
448
+ actionLogger.error("Tool execution failed", {
449
+ toolName: action.name,
450
+ error: errorMessage,
451
+ iteration
452
+ });
334
453
  observations.push({
335
454
  toolCallId: action.id,
336
455
  result: null,
337
456
  error: errorMessage,
338
457
  timestamp: Date.now()
339
458
  });
340
- if (verbose) {
341
- console.error(`[action] Tool '${action.name}' failed:`, errorMessage);
342
- }
343
459
  }
344
460
  }
461
+ if (duplicatesSkipped > 0 || toolsExecuted > 0) {
462
+ const metrics = buildDeduplicationMetrics(toolsExecuted, duplicatesSkipped, observations.length);
463
+ actionLogger.info("Action node complete", {
464
+ iteration,
465
+ ...metrics,
466
+ duration: Date.now() - startTime
467
+ });
468
+ }
345
469
  return {
346
470
  observations
347
471
  };
@@ -352,9 +476,11 @@ function createObservationNode(verbose = false) {
352
476
  const observations = state.observations || [];
353
477
  const thoughts = state.thoughts || [];
354
478
  const actions = state.actions || [];
355
- if (verbose) {
356
- console.log(`[observation] Processing ${observations.length} observations`);
357
- }
479
+ const iteration = state.iteration || 0;
480
+ observationLogger.debug("Processing observations", {
481
+ observationCount: observations.length,
482
+ iteration
483
+ });
358
484
  const recentObservations = observations.slice(-10);
359
485
  const currentStep = state.iteration;
360
486
  const latestThought = thoughts[thoughts.length - 1]?.content || "";
@@ -380,6 +506,11 @@ function createObservationNode(verbose = false) {
380
506
  name: latestActions.find((a) => a.id === obs.toolCallId)?.name
381
507
  };
382
508
  });
509
+ observationLogger.debug("Observation node complete", {
510
+ iteration,
511
+ scratchpadUpdated: true,
512
+ messageCount: observationMessages.length
513
+ });
383
514
  return {
384
515
  scratchpad: [scratchpadEntry],
385
516
  messages: observationMessages
@@ -389,7 +520,7 @@ function createObservationNode(verbose = false) {
389
520
 
390
521
  // src/react/agent.ts
391
522
  var import_langgraph = require("@langchain/langgraph");
392
- var import_core3 = require("@agentforge/core");
523
+ var import_core4 = require("@agentforge/core");
393
524
  function createReActAgent(config, options) {
394
525
  const {
395
526
  model,
@@ -398,13 +529,15 @@ function createReActAgent(config, options) {
398
529
  maxIterations = 10,
399
530
  returnIntermediateSteps = false,
400
531
  stopCondition,
401
- checkpointer
532
+ checkpointer,
533
+ enableDeduplication = true
534
+ // Enable by default
402
535
  } = config;
403
536
  const {
404
537
  verbose = false,
405
538
  nodeNames = {}
406
539
  } = options || {};
407
- const toolArray = tools instanceof import_core3.ToolRegistry ? tools.getAll() : tools;
540
+ const toolArray = tools instanceof import_core4.ToolRegistry ? tools.getAll() : tools;
408
541
  const REASONING_NODE = nodeNames.reasoning || "reasoning";
409
542
  const ACTION_NODE = nodeNames.action || "action";
410
543
  const OBSERVATION_NODE = nodeNames.observation || "observation";
@@ -415,7 +548,7 @@ function createReActAgent(config, options) {
415
548
  maxIterations,
416
549
  verbose
417
550
  );
418
- const actionNode = createActionNode(toolArray, verbose);
551
+ const actionNode = createActionNode(toolArray, verbose, enableDeduplication);
419
552
  const observationNode = createObservationNode(verbose);
420
553
  const shouldContinue = (state) => {
421
554
  if (stopCondition && stopCondition(state)) {
@@ -551,7 +684,7 @@ var import_langgraph2 = require("@langchain/langgraph");
551
684
 
552
685
  // src/plan-execute/state.ts
553
686
  var import_zod4 = require("zod");
554
- var import_core4 = require("@agentforge/core");
687
+ var import_core5 = require("@agentforge/core");
555
688
 
556
689
  // src/plan-execute/schemas.ts
557
690
  var import_zod3 = require("zod");
@@ -713,7 +846,7 @@ var PlanExecuteStateConfig = {
713
846
  description: "Maximum number of planning iterations allowed"
714
847
  }
715
848
  };
716
- var PlanExecuteState = (0, import_core4.createStateAnnotation)(PlanExecuteStateConfig);
849
+ var PlanExecuteState = (0, import_core5.createStateAnnotation)(PlanExecuteStateConfig);
717
850
 
718
851
  // src/plan-execute/nodes.ts
719
852
  var import_messages2 = require("@langchain/core/messages");
@@ -778,6 +911,9 @@ var REMAINING_STEP_TEMPLATE = `Step {stepNumber}: {description}
778
911
  {dependencies}`;
779
912
 
780
913
  // src/plan-execute/nodes.ts
914
+ var plannerLogger = createPatternLogger("agentforge:patterns:plan-execute:planner");
915
+ var executorLogger = createPatternLogger("agentforge:patterns:plan-execute:executor");
916
+ var replannerLogger = createPatternLogger("agentforge:patterns:plan-execute:replanner");
781
917
  function createPlannerNode(config) {
782
918
  const {
783
919
  model,
@@ -786,7 +922,13 @@ function createPlannerNode(config) {
786
922
  includeToolDescriptions = false
787
923
  } = config;
788
924
  return async (state) => {
925
+ const startTime = Date.now();
789
926
  try {
927
+ plannerLogger.debug("Planning started", {
928
+ input: state.input?.substring(0, 100),
929
+ maxSteps,
930
+ includeToolDescriptions
931
+ });
790
932
  let toolDescriptions = "";
791
933
  if (includeToolDescriptions) {
792
934
  toolDescriptions = "";
@@ -811,6 +953,12 @@ function createPlannerNode(config) {
811
953
  } catch (parseError) {
812
954
  throw new Error(`Failed to parse plan from LLM response: ${parseError}`);
813
955
  }
956
+ plannerLogger.info("Plan created", {
957
+ stepCount: plan.steps.length,
958
+ goal: plan.goal.substring(0, 100),
959
+ confidence: plan.confidence,
960
+ duration: Date.now() - startTime
961
+ });
814
962
  return {
815
963
  plan,
816
964
  status: "executing",
@@ -818,6 +966,10 @@ function createPlannerNode(config) {
818
966
  iteration: 1
819
967
  };
820
968
  } catch (error) {
969
+ plannerLogger.error("Planning failed", {
970
+ error: error instanceof Error ? error.message : String(error),
971
+ duration: Date.now() - startTime
972
+ });
821
973
  return {
822
974
  status: "failed",
823
975
  error: error instanceof Error ? error.message : "Unknown error in planner"
@@ -830,11 +982,18 @@ function createExecutorNode(config) {
830
982
  tools,
831
983
  model,
832
984
  parallel = false,
833
- stepTimeout = 3e4
985
+ stepTimeout = 3e4,
986
+ enableDeduplication = true
834
987
  } = config;
835
988
  return async (state) => {
989
+ const { plan, currentStepIndex = 0, pastSteps = [], iteration = 0 } = state;
836
990
  try {
837
- const { plan, currentStepIndex = 0, pastSteps = [] } = state;
991
+ executorLogger.debug("Executor node executing", {
992
+ currentStepIndex,
993
+ totalSteps: plan?.steps?.length || 0,
994
+ iteration,
995
+ deduplicationEnabled: enableDeduplication
996
+ });
838
997
  if (!plan || !plan.steps || plan.steps.length === 0) {
839
998
  return {
840
999
  status: "completed"
@@ -853,22 +1012,67 @@ function createExecutorNode(config) {
853
1012
  throw new Error(`Unmet dependencies for step ${currentStep.id}: ${unmetDependencies.join(", ")}`);
854
1013
  }
855
1014
  }
1015
+ const executionCache = /* @__PURE__ */ new Map();
1016
+ let cacheSize = 0;
1017
+ if (enableDeduplication && currentStep.tool) {
1018
+ for (const pastStep of pastSteps) {
1019
+ if (pastStep.step.tool) {
1020
+ const cacheKey = generateToolCallCacheKey(pastStep.step.tool, pastStep.step.args || {});
1021
+ executionCache.set(cacheKey, pastStep);
1022
+ cacheSize++;
1023
+ }
1024
+ }
1025
+ if (cacheSize > 0) {
1026
+ executorLogger.debug("Deduplication cache built", {
1027
+ cacheSize,
1028
+ pastStepsCount: pastSteps.length
1029
+ });
1030
+ }
1031
+ }
856
1032
  let result;
857
1033
  let success = true;
858
1034
  let error;
1035
+ let isDuplicate = false;
859
1036
  try {
860
1037
  if (currentStep.tool) {
861
- const tool = tools.find((t) => t.metadata.name === currentStep.tool);
862
- if (!tool) {
863
- throw new Error(`Tool not found: ${currentStep.tool}`);
1038
+ if (enableDeduplication) {
1039
+ const cacheKey = generateToolCallCacheKey(currentStep.tool, currentStep.args || {});
1040
+ const cachedStep = executionCache.get(cacheKey);
1041
+ if (cachedStep) {
1042
+ isDuplicate = true;
1043
+ result = cachedStep.result;
1044
+ success = cachedStep.success;
1045
+ error = cachedStep.error;
1046
+ executorLogger.info("Duplicate step execution prevented", {
1047
+ stepId: currentStep.id,
1048
+ toolName: currentStep.tool,
1049
+ arguments: currentStep.args,
1050
+ iteration,
1051
+ cacheHit: true
1052
+ });
1053
+ }
1054
+ }
1055
+ if (!isDuplicate) {
1056
+ const tool = tools.find((t) => t.metadata.name === currentStep.tool);
1057
+ if (!tool) {
1058
+ throw new Error(`Tool not found: ${currentStep.tool}`);
1059
+ }
1060
+ const startTime = Date.now();
1061
+ const timeoutPromise = new Promise(
1062
+ (_, reject) => setTimeout(() => reject(new Error("Step execution timeout")), stepTimeout)
1063
+ );
1064
+ result = await Promise.race([
1065
+ tool.execute(currentStep.args || {}),
1066
+ timeoutPromise
1067
+ ]);
1068
+ const executionTime = Date.now() - startTime;
1069
+ executorLogger.debug("Step executed successfully", {
1070
+ stepId: currentStep.id,
1071
+ toolName: currentStep.tool,
1072
+ executionTime,
1073
+ iteration
1074
+ });
864
1075
  }
865
- const timeoutPromise = new Promise(
866
- (_, reject) => setTimeout(() => reject(new Error("Step execution timeout")), stepTimeout)
867
- );
868
- result = await Promise.race([
869
- tool.execute(currentStep.args || {}),
870
- timeoutPromise
871
- ]);
872
1076
  } else {
873
1077
  result = { message: "Step completed without tool execution" };
874
1078
  }
@@ -879,6 +1083,12 @@ function createExecutorNode(config) {
879
1083
  success = false;
880
1084
  error = execError instanceof Error ? execError.message : "Unknown execution error";
881
1085
  result = null;
1086
+ executorLogger.warn("Step execution failed", {
1087
+ stepId: currentStep.id,
1088
+ toolName: currentStep.tool,
1089
+ error,
1090
+ iteration
1091
+ });
882
1092
  }
883
1093
  const completedStep = {
884
1094
  step: currentStep,
@@ -887,11 +1097,23 @@ function createExecutorNode(config) {
887
1097
  error,
888
1098
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
889
1099
  };
1100
+ executorLogger.info("Executor node complete", {
1101
+ stepId: currentStep.id,
1102
+ stepIndex: currentStepIndex,
1103
+ totalSteps: plan.steps.length,
1104
+ success,
1105
+ isDuplicate,
1106
+ iteration
1107
+ });
890
1108
  return {
891
1109
  pastSteps: [completedStep],
892
1110
  currentStepIndex: currentStepIndex + 1
893
1111
  };
894
1112
  } catch (error) {
1113
+ executorLogger.error("Executor node failed", {
1114
+ error: error instanceof Error ? error.message : "Unknown error",
1115
+ iteration
1116
+ });
895
1117
  return {
896
1118
  status: "failed",
897
1119
  error: error instanceof Error ? error.message : "Unknown error in executor"
@@ -906,11 +1128,18 @@ function createReplannerNode(config) {
906
1128
  systemPrompt = DEFAULT_REPLANNER_SYSTEM_PROMPT
907
1129
  } = config;
908
1130
  return async (state) => {
1131
+ const startTime = Date.now();
909
1132
  try {
910
1133
  const { plan, pastSteps = [], currentStepIndex = 0 } = state;
911
1134
  if (!plan) {
912
1135
  return { status: "failed", error: "No plan available for replanning" };
913
1136
  }
1137
+ replannerLogger.debug("Evaluating replanning", {
1138
+ completedSteps: pastSteps.length,
1139
+ remainingSteps: plan.steps.length - currentStepIndex,
1140
+ successfulSteps: pastSteps.filter((ps) => ps.success).length,
1141
+ failedSteps: pastSteps.filter((ps) => !ps.success).length
1142
+ });
914
1143
  const completedStepsText = pastSteps.map(
915
1144
  (ps, idx) => COMPLETED_STEP_TEMPLATE.replace("{stepNumber}", String(idx + 1)).replace("{description}", ps.step.description).replace("{result}", JSON.stringify(ps.result)).replace("{status}", ps.success ? "Success" : `Failed: ${ps.error}`)
916
1145
  ).join("\n\n");
@@ -932,17 +1161,30 @@ function createReplannerNode(config) {
932
1161
  throw new Error(`Failed to parse replan decision from LLM response: ${parseError}`);
933
1162
  }
934
1163
  if (decision.shouldReplan) {
1164
+ replannerLogger.info("Replanning triggered", {
1165
+ reason: decision.reason,
1166
+ newGoal: decision.newGoal?.substring(0, 100),
1167
+ duration: Date.now() - startTime
1168
+ });
935
1169
  return {
936
1170
  status: "planning",
937
1171
  input: decision.newGoal || plan.goal,
938
1172
  iteration: 1
939
1173
  };
940
1174
  } else {
1175
+ replannerLogger.info("Continuing with current plan", {
1176
+ reason: decision.reason,
1177
+ duration: Date.now() - startTime
1178
+ });
941
1179
  return {
942
1180
  status: "executing"
943
1181
  };
944
1182
  }
945
1183
  } catch (error) {
1184
+ replannerLogger.error("Replanning evaluation failed", {
1185
+ error: error instanceof Error ? error.message : String(error),
1186
+ duration: Date.now() - startTime
1187
+ });
946
1188
  return {
947
1189
  status: "failed",
948
1190
  error: error instanceof Error ? error.message : "Unknown error in replanner"
@@ -1043,7 +1285,7 @@ function createPlanExecuteAgent(config) {
1043
1285
 
1044
1286
  // src/reflection/state.ts
1045
1287
  var import_zod6 = require("zod");
1046
- var import_core5 = require("@agentforge/core");
1288
+ var import_core6 = require("@agentforge/core");
1047
1289
 
1048
1290
  // src/reflection/schemas.ts
1049
1291
  var import_zod5 = require("zod");
@@ -1216,7 +1458,7 @@ var ReflectionStateConfig = {
1216
1458
  description: "Error message if reflection failed"
1217
1459
  }
1218
1460
  };
1219
- var ReflectionState = (0, import_core5.createStateAnnotation)(ReflectionStateConfig);
1461
+ var ReflectionState = (0, import_core6.createStateAnnotation)(ReflectionStateConfig);
1220
1462
 
1221
1463
  // src/reflection/prompts.ts
1222
1464
  var DEFAULT_GENERATOR_SYSTEM_PROMPT = `You are an expert content generator. Your task is to create high-quality responses to user requests.
@@ -1320,6 +1562,9 @@ var REVISION_ENTRY_TEMPLATE = `Iteration {iteration}:
1320
1562
 
1321
1563
  // src/reflection/nodes.ts
1322
1564
  var import_messages3 = require("@langchain/core/messages");
1565
+ var generatorLogger = createPatternLogger("agentforge:patterns:reflection:generator");
1566
+ var reflectorLogger = createPatternLogger("agentforge:patterns:reflection:reflector");
1567
+ var reviserLogger = createPatternLogger("agentforge:patterns:reflection:reviser");
1323
1568
  function createGeneratorNode(config) {
1324
1569
  const {
1325
1570
  model,
@@ -1327,10 +1572,13 @@ function createGeneratorNode(config) {
1327
1572
  verbose = false
1328
1573
  } = config;
1329
1574
  return async (state) => {
1575
+ const startTime = Date.now();
1330
1576
  try {
1331
- if (verbose) {
1332
- console.log("[Generator] Generating initial response...");
1333
- }
1577
+ generatorLogger.debug("Generating response", {
1578
+ attempt: state.iteration + 1,
1579
+ hasFeedback: state.reflections.length > 0,
1580
+ hasExistingResponse: !!state.currentResponse
1581
+ });
1334
1582
  let context = "";
1335
1583
  if (state.iteration > 0 && state.reflections.length > 0) {
1336
1584
  const lastReflection = state.reflections[state.reflections.length - 1];
@@ -1345,16 +1593,23 @@ ${lastReflection.critique}`;
1345
1593
  ];
1346
1594
  const response = await model.invoke(messages);
1347
1595
  const content = typeof response.content === "string" ? response.content : JSON.stringify(response.content);
1348
- if (verbose) {
1349
- console.log("[Generator] Generated response:", content.substring(0, 100) + "...");
1350
- }
1596
+ generatorLogger.info("Response generated", {
1597
+ attempt: state.iteration + 1,
1598
+ responseLength: content.length,
1599
+ isRevision: state.iteration > 0,
1600
+ duration: Date.now() - startTime
1601
+ });
1351
1602
  return {
1352
1603
  currentResponse: content,
1353
1604
  status: "reflecting",
1354
1605
  iteration: 1
1355
1606
  };
1356
1607
  } catch (error) {
1357
- console.error("[Generator] Error:", error);
1608
+ generatorLogger.error("Response generation failed", {
1609
+ attempt: state.iteration + 1,
1610
+ error: error instanceof Error ? error.message : String(error),
1611
+ duration: Date.now() - startTime
1612
+ });
1358
1613
  return {
1359
1614
  status: "failed",
1360
1615
  error: error instanceof Error ? error.message : "Unknown error in generator"
@@ -1370,10 +1625,13 @@ function createReflectorNode(config) {
1370
1625
  verbose = false
1371
1626
  } = config;
1372
1627
  return async (state) => {
1628
+ const startTime = Date.now();
1373
1629
  try {
1374
- if (verbose) {
1375
- console.log("[Reflector] Reflecting on response...");
1376
- }
1630
+ reflectorLogger.debug("Reflecting on response", {
1631
+ attempt: state.iteration,
1632
+ responseLength: state.currentResponse?.length || 0,
1633
+ hasCriteria: !!(qualityCriteria || state.qualityCriteria)
1634
+ });
1377
1635
  if (!state.currentResponse) {
1378
1636
  throw new Error("No current response to reflect on");
1379
1637
  }
@@ -1423,16 +1681,24 @@ function createReflectorNode(config) {
1423
1681
  meetsStandards: false
1424
1682
  };
1425
1683
  }
1426
- if (verbose) {
1427
- console.log("[Reflector] Reflection score:", reflection.score);
1428
- console.log("[Reflector] Meets standards:", reflection.meetsStandards);
1429
- }
1684
+ reflectorLogger.info("Reflection complete", {
1685
+ attempt: state.iteration,
1686
+ score: reflection.score,
1687
+ meetsStandards: reflection.meetsStandards,
1688
+ issueCount: reflection.issues.length,
1689
+ suggestionCount: reflection.suggestions.length,
1690
+ duration: Date.now() - startTime
1691
+ });
1430
1692
  return {
1431
1693
  reflections: [reflection],
1432
1694
  status: reflection.meetsStandards ? "completed" : "revising"
1433
1695
  };
1434
1696
  } catch (error) {
1435
- console.error("[Reflector] Error:", error);
1697
+ reflectorLogger.error("Reflection failed", {
1698
+ attempt: state.iteration,
1699
+ error: error instanceof Error ? error.message : String(error),
1700
+ duration: Date.now() - startTime
1701
+ });
1436
1702
  return {
1437
1703
  status: "failed",
1438
1704
  error: error instanceof Error ? error.message : "Unknown error in reflector"
@@ -1447,10 +1713,8 @@ function createReviserNode(config) {
1447
1713
  verbose = false
1448
1714
  } = config;
1449
1715
  return async (state) => {
1716
+ const startTime = Date.now();
1450
1717
  try {
1451
- if (verbose) {
1452
- console.log("[Reviser] Revising response...");
1453
- }
1454
1718
  if (!state.currentResponse) {
1455
1719
  throw new Error("No current response to revise");
1456
1720
  }
@@ -1458,6 +1722,12 @@ function createReviserNode(config) {
1458
1722
  throw new Error("No reflections to base revision on");
1459
1723
  }
1460
1724
  const lastReflection = state.reflections[state.reflections.length - 1];
1725
+ reviserLogger.debug("Revising response", {
1726
+ attempt: state.iteration,
1727
+ previousScore: lastReflection.score,
1728
+ issueCount: lastReflection.issues.length,
1729
+ suggestionCount: lastReflection.suggestions.length
1730
+ });
1461
1731
  let historySection = "";
1462
1732
  if (state.revisions.length > 0) {
1463
1733
  const revisionsText = state.revisions.map(
@@ -1474,14 +1744,17 @@ ${revisionsText}`;
1474
1744
  ];
1475
1745
  const response = await model.invoke(messages);
1476
1746
  const content = typeof response.content === "string" ? response.content : JSON.stringify(response.content);
1477
- if (verbose) {
1478
- console.log("[Reviser] Created revision:", content.substring(0, 100) + "...");
1479
- }
1480
1747
  const revision = {
1481
1748
  content,
1482
1749
  iteration: state.iteration,
1483
1750
  basedOn: lastReflection
1484
1751
  };
1752
+ reviserLogger.info("Revision complete", {
1753
+ attempt: state.iteration,
1754
+ revisionLength: content.length,
1755
+ basedOnScore: lastReflection.score,
1756
+ duration: Date.now() - startTime
1757
+ });
1485
1758
  return {
1486
1759
  currentResponse: content,
1487
1760
  revisions: [revision],
@@ -1489,7 +1762,11 @@ ${revisionsText}`;
1489
1762
  iteration: 1
1490
1763
  };
1491
1764
  } catch (error) {
1492
- console.error("[Reviser] Error:", error);
1765
+ reviserLogger.error("Revision failed", {
1766
+ attempt: state.iteration,
1767
+ error: error instanceof Error ? error.message : String(error),
1768
+ duration: Date.now() - startTime
1769
+ });
1493
1770
  return {
1494
1771
  status: "failed",
1495
1772
  error: error instanceof Error ? error.message : "Unknown error in reviser"
@@ -1579,7 +1856,7 @@ function createReflectionAgent(config) {
1579
1856
 
1580
1857
  // src/multi-agent/state.ts
1581
1858
  var import_zod8 = require("zod");
1582
- var import_core6 = require("@agentforge/core");
1859
+ var import_core7 = require("@agentforge/core");
1583
1860
 
1584
1861
  // src/multi-agent/schemas.ts
1585
1862
  var import_zod7 = require("zod");
@@ -1642,26 +1919,35 @@ var RoutingStrategySchema = import_zod7.z.enum([
1642
1919
  ]);
1643
1920
  var RoutingDecisionSchema = import_zod7.z.object({
1644
1921
  /**
1645
- * Target agent to route to
1922
+ * Target agent to route to (single agent routing)
1923
+ * @deprecated Use targetAgents for parallel routing support
1924
+ */
1925
+ targetAgent: import_zod7.z.string().nullable().default(null).describe("Agent to route the task to (single routing)"),
1926
+ /**
1927
+ * Target agents to route to (parallel routing)
1928
+ * When multiple agents are specified, they execute in parallel
1646
1929
  */
1647
- targetAgent: import_zod7.z.string().describe("Agent to route the task to"),
1930
+ targetAgents: import_zod7.z.array(import_zod7.z.string()).nullable().default(null).describe("Agents to route the task to (parallel routing)"),
1648
1931
  /**
1649
1932
  * Reasoning for the routing decision
1650
1933
  */
1651
- reasoning: import_zod7.z.string().optional().describe("Explanation for routing decision"),
1934
+ reasoning: import_zod7.z.string().default("").describe("Explanation for routing decision"),
1652
1935
  /**
1653
1936
  * Confidence in the routing decision (0-1)
1654
1937
  */
1655
- confidence: import_zod7.z.number().min(0).max(1).optional().describe("Confidence score"),
1938
+ confidence: import_zod7.z.number().min(0).max(1).default(0.8).describe("Confidence score"),
1656
1939
  /**
1657
1940
  * Strategy used for routing
1658
1941
  */
1659
- strategy: RoutingStrategySchema.describe("Strategy used for this decision"),
1942
+ strategy: RoutingStrategySchema.default("llm-based").describe("Strategy used for this decision"),
1660
1943
  /**
1661
1944
  * Timestamp of the routing decision
1662
1945
  */
1663
- timestamp: import_zod7.z.number().optional().describe("Timestamp of the decision")
1664
- });
1946
+ timestamp: import_zod7.z.number().default(() => Date.now()).describe("Timestamp of the decision")
1947
+ }).refine(
1948
+ (data) => data.targetAgent || data.targetAgents && data.targetAgents.length > 0,
1949
+ { message: "Either targetAgent or targetAgents must be provided" }
1950
+ );
1665
1951
  var WorkerCapabilitiesSchema = import_zod7.z.object({
1666
1952
  /**
1667
1953
  * Skills/capabilities the agent has
@@ -1893,15 +2179,26 @@ var MultiAgentStateConfig = {
1893
2179
  description: "Error message if execution failed"
1894
2180
  }
1895
2181
  };
1896
- var MultiAgentState = (0, import_core6.createStateAnnotation)(MultiAgentStateConfig);
2182
+ var MultiAgentState = (0, import_core7.createStateAnnotation)(MultiAgentStateConfig);
1897
2183
 
1898
2184
  // src/multi-agent/routing.ts
1899
2185
  var import_messages4 = require("@langchain/core/messages");
2186
+ var import_core8 = require("@agentforge/core");
2187
+ var logLevel = process.env.LOG_LEVEL?.toLowerCase() || import_core8.LogLevel.INFO;
2188
+ var logger = (0, import_core8.createLogger)("multi-agent:routing", { level: logLevel });
1900
2189
  async function executeTools(toolCalls, tools) {
1901
2190
  const results = [];
2191
+ logger.debug("Executing tools", {
2192
+ toolCallCount: toolCalls.length,
2193
+ toolNames: toolCalls.map((tc) => tc.name)
2194
+ });
1902
2195
  for (const toolCall of toolCalls) {
1903
2196
  const tool = tools.find((t) => t.metadata.name === toolCall.name);
1904
2197
  if (!tool) {
2198
+ logger.warn("Tool not found", {
2199
+ toolName: toolCall.name,
2200
+ availableTools: tools.map((t) => t.metadata.name)
2201
+ });
1905
2202
  results.push(new import_messages4.ToolMessage({
1906
2203
  content: `Error: Tool '${toolCall.name}' not found`,
1907
2204
  tool_call_id: toolCall.id
@@ -1909,19 +2206,41 @@ async function executeTools(toolCalls, tools) {
1909
2206
  continue;
1910
2207
  }
1911
2208
  try {
2209
+ logger.debug("Executing tool", {
2210
+ toolName: toolCall.name,
2211
+ args: toolCall.args
2212
+ });
1912
2213
  const result = await tool.execute(toolCall.args);
1913
2214
  const content = typeof result === "string" ? result : JSON.stringify(result);
2215
+ logger.debug("Tool execution successful", {
2216
+ toolName: toolCall.name,
2217
+ resultLength: content.length
2218
+ });
1914
2219
  results.push(new import_messages4.ToolMessage({
1915
2220
  content,
1916
2221
  tool_call_id: toolCall.id
1917
2222
  }));
1918
2223
  } catch (error) {
2224
+ logger.error("Tool execution failed", {
2225
+ toolName: toolCall.name,
2226
+ error: error.message
2227
+ });
1919
2228
  results.push(new import_messages4.ToolMessage({
1920
2229
  content: `Error executing tool: ${error.message}`,
1921
2230
  tool_call_id: toolCall.id
1922
2231
  }));
1923
2232
  }
1924
2233
  }
2234
+ logger.debug("Tool execution complete", {
2235
+ successCount: results.filter((r) => {
2236
+ const content = typeof r.content === "string" ? r.content : JSON.stringify(r.content);
2237
+ return !content.startsWith("Error");
2238
+ }).length,
2239
+ errorCount: results.filter((r) => {
2240
+ const content = typeof r.content === "string" ? r.content : JSON.stringify(r.content);
2241
+ return content.startsWith("Error");
2242
+ }).length
2243
+ });
1925
2244
  return results;
1926
2245
  }
1927
2246
  var DEFAULT_SUPERVISOR_SYSTEM_PROMPT = `You are a supervisor agent responsible for routing tasks to specialized worker agents.
@@ -1929,19 +2248,40 @@ var DEFAULT_SUPERVISOR_SYSTEM_PROMPT = `You are a supervisor agent responsible f
1929
2248
  Your job is to:
1930
2249
  1. Analyze the current task and context
1931
2250
  2. Review available worker capabilities
1932
- 3. Select the most appropriate worker for the task
2251
+ 3. Select the most appropriate worker(s) for the task
1933
2252
  4. Provide clear reasoning for your decision
1934
2253
 
1935
- Respond with a JSON object containing:
2254
+ **IMPORTANT: You can route to MULTIPLE workers for parallel execution when:**
2255
+ - The task requires information from multiple domains (e.g., code + documentation)
2256
+ - Multiple workers have complementary expertise
2257
+ - Parallel execution would provide a more comprehensive answer
2258
+
2259
+ **Response Format:**
2260
+
2261
+ For SINGLE worker routing:
1936
2262
  {
1937
2263
  "targetAgent": "worker_id",
1938
2264
  "reasoning": "explanation of why this worker is best suited",
1939
2265
  "confidence": 0.0-1.0,
1940
2266
  "strategy": "llm-based"
1941
- }`;
2267
+ }
2268
+
2269
+ For PARALLEL multi-worker routing:
2270
+ {
2271
+ "targetAgents": ["worker_id_1", "worker_id_2", ...],
2272
+ "reasoning": "explanation of why these workers should work in parallel",
2273
+ "confidence": 0.0-1.0,
2274
+ "strategy": "llm-based"
2275
+ }
2276
+
2277
+ Choose parallel routing when the task benefits from multiple perspectives or data sources.`;
1942
2278
  var llmBasedRouting = {
1943
2279
  name: "llm-based",
1944
2280
  async route(state, config) {
2281
+ logger.info("Starting LLM-based routing", {
2282
+ iteration: state.iteration,
2283
+ availableWorkers: Object.keys(state.workers).length
2284
+ });
1945
2285
  if (!config.model) {
1946
2286
  throw new Error("LLM-based routing requires a model to be configured");
1947
2287
  }
@@ -1954,17 +2294,34 @@ var llmBasedRouting = {
1954
2294
  const available = caps.available ? "available" : "busy";
1955
2295
  return `- ${id}: Skills: [${skills}], Tools: [${tools2}], Status: ${available}, Workload: ${caps.currentWorkload}`;
1956
2296
  }).join("\n");
2297
+ logger.debug("Worker capabilities", {
2298
+ workers: Object.entries(state.workers).map(([id, caps]) => ({
2299
+ id,
2300
+ skills: caps.skills,
2301
+ available: caps.available,
2302
+ workload: caps.currentWorkload
2303
+ }))
2304
+ });
1957
2305
  const lastMessage = state.messages[state.messages.length - 1];
1958
2306
  const taskContext = lastMessage?.content || state.input;
2307
+ logger.debug("Task context", {
2308
+ taskLength: taskContext.length,
2309
+ taskPreview: taskContext.substring(0, 100)
2310
+ });
1959
2311
  const userPrompt = `Current task: ${taskContext}
1960
2312
 
1961
2313
  Available workers:
1962
2314
  ${workerInfo}
1963
2315
 
1964
- Select the best worker for this task and explain your reasoning.`;
2316
+ Select the best worker(s) for this task and explain your reasoning.`;
1965
2317
  const conversationHistory = [];
1966
2318
  let attempt = 0;
1967
2319
  while (attempt < maxRetries) {
2320
+ logger.debug("LLM routing attempt", {
2321
+ attempt: attempt + 1,
2322
+ maxRetries,
2323
+ conversationHistoryLength: conversationHistory.length
2324
+ });
1968
2325
  const messages = [
1969
2326
  new import_messages4.SystemMessage(systemPrompt),
1970
2327
  new import_messages4.HumanMessage(userPrompt),
@@ -1972,6 +2329,10 @@ Select the best worker for this task and explain your reasoning.`;
1972
2329
  ];
1973
2330
  const response = await config.model.invoke(messages);
1974
2331
  if (response.tool_calls && response.tool_calls.length > 0) {
2332
+ logger.info("LLM requested tool calls", {
2333
+ toolCount: response.tool_calls.length,
2334
+ toolNames: response.tool_calls.map((tc) => tc.name)
2335
+ });
1975
2336
  if (tools.length === 0) {
1976
2337
  throw new Error("LLM requested tool calls but no tools are configured");
1977
2338
  }
@@ -1981,36 +2342,90 @@ Select the best worker for this task and explain your reasoning.`;
1981
2342
  ...toolResults
1982
2343
  );
1983
2344
  attempt++;
2345
+ logger.debug("Retrying routing with tool results", { attempt });
1984
2346
  continue;
1985
2347
  }
1986
- const content = typeof response.content === "string" ? response.content : JSON.stringify(response.content);
1987
- try {
1988
- const decision = JSON.parse(content);
1989
- return {
1990
- targetAgent: decision.targetAgent,
1991
- reasoning: decision.reasoning,
1992
- confidence: decision.confidence,
1993
- strategy: "llm-based",
1994
- timestamp: Date.now()
1995
- };
1996
- } catch (error) {
1997
- throw new Error(`Failed to parse routing decision from LLM: ${error}`);
2348
+ logger.debug("Parsing routing decision from LLM response");
2349
+ let decision;
2350
+ if (response && typeof response === "object" && ("targetAgent" in response || "targetAgents" in response)) {
2351
+ logger.debug("Response is structured output", {
2352
+ hasTargetAgent: "targetAgent" in response,
2353
+ hasTargetAgents: "targetAgents" in response
2354
+ });
2355
+ decision = response;
2356
+ } else if (response.content) {
2357
+ if (typeof response.content === "string") {
2358
+ try {
2359
+ decision = JSON.parse(response.content);
2360
+ logger.debug("Parsed JSON from string response");
2361
+ } catch (error) {
2362
+ logger.error("Failed to parse routing decision", {
2363
+ content: response.content,
2364
+ error: error instanceof Error ? error.message : String(error)
2365
+ });
2366
+ throw new Error(`Failed to parse routing decision from LLM. Expected JSON but got: ${response.content}`);
2367
+ }
2368
+ } else if (typeof response.content === "object") {
2369
+ logger.debug("Response content is already an object");
2370
+ decision = response.content;
2371
+ } else {
2372
+ logger.error("Unexpected response content type", {
2373
+ type: typeof response.content
2374
+ });
2375
+ throw new Error(`Unexpected response content type: ${typeof response.content}`);
2376
+ }
2377
+ } else {
2378
+ logger.error("Unexpected response format", {
2379
+ response: JSON.stringify(response)
2380
+ });
2381
+ throw new Error(`Unexpected response format: ${JSON.stringify(response)}`);
1998
2382
  }
2383
+ const result = {
2384
+ targetAgent: decision.targetAgent,
2385
+ targetAgents: decision.targetAgents,
2386
+ reasoning: decision.reasoning,
2387
+ confidence: decision.confidence,
2388
+ strategy: "llm-based",
2389
+ timestamp: Date.now()
2390
+ };
2391
+ logger.info("LLM routing decision made", {
2392
+ targetAgent: result.targetAgent,
2393
+ targetAgents: result.targetAgents,
2394
+ isParallel: result.targetAgents && result.targetAgents.length > 1,
2395
+ confidence: result.confidence,
2396
+ reasoning: result.reasoning
2397
+ });
2398
+ return result;
1999
2399
  }
2400
+ logger.error("Max tool retries exceeded", { maxRetries });
2000
2401
  throw new Error(`Max tool retries (${maxRetries}) exceeded without routing decision`);
2001
2402
  }
2002
2403
  };
2003
2404
  var roundRobinRouting = {
2004
2405
  name: "round-robin",
2005
2406
  async route(state, config) {
2407
+ logger.info("Starting round-robin routing", {
2408
+ iteration: state.iteration
2409
+ });
2006
2410
  const availableWorkers = Object.entries(state.workers).filter(([_, caps]) => caps.available).map(([id]) => id);
2411
+ logger.debug("Available workers for round-robin", {
2412
+ count: availableWorkers.length,
2413
+ workers: availableWorkers
2414
+ });
2007
2415
  if (availableWorkers.length === 0) {
2416
+ logger.error("No available workers for round-robin routing");
2008
2417
  throw new Error("No available workers for round-robin routing");
2009
2418
  }
2010
2419
  const lastRoutingIndex = state.routingHistory.length % availableWorkers.length;
2011
2420
  const targetAgent = availableWorkers[lastRoutingIndex];
2421
+ logger.info("Round-robin routing decision", {
2422
+ targetAgent,
2423
+ index: lastRoutingIndex + 1,
2424
+ totalWorkers: availableWorkers.length
2425
+ });
2012
2426
  return {
2013
2427
  targetAgent,
2428
+ targetAgents: null,
2014
2429
  reasoning: `Round-robin selection: worker ${lastRoutingIndex + 1} of ${availableWorkers.length}`,
2015
2430
  confidence: 1,
2016
2431
  strategy: "round-robin",
@@ -2021,8 +2436,15 @@ var roundRobinRouting = {
2021
2436
  var skillBasedRouting = {
2022
2437
  name: "skill-based",
2023
2438
  async route(state, config) {
2439
+ logger.info("Starting skill-based routing", {
2440
+ iteration: state.iteration
2441
+ });
2024
2442
  const lastMessage = state.messages[state.messages.length - 1];
2025
2443
  const taskContent = (lastMessage?.content || state.input).toLowerCase();
2444
+ logger.debug("Task content for skill matching", {
2445
+ taskLength: taskContent.length,
2446
+ taskPreview: taskContent.substring(0, 100)
2447
+ });
2026
2448
  const workerScores = Object.entries(state.workers).filter(([_, caps]) => caps.available).map(([id, caps]) => {
2027
2449
  const skillMatches = caps.skills.filter(
2028
2450
  (skill) => taskContent.includes(skill.toLowerCase())
@@ -2033,13 +2455,23 @@ var skillBasedRouting = {
2033
2455
  const score = skillMatches * 2 + toolMatches;
2034
2456
  return { id, score, skills: caps.skills, tools: caps.tools };
2035
2457
  }).filter((w) => w.score > 0).sort((a, b) => b.score - a.score);
2458
+ logger.debug("Worker skill scores", {
2459
+ scoredWorkers: workerScores.map((w) => ({ id: w.id, score: w.score }))
2460
+ });
2036
2461
  if (workerScores.length === 0) {
2462
+ logger.warn("No skill matches found, using fallback");
2037
2463
  const firstAvailable = Object.entries(state.workers).find(([_, caps]) => caps.available);
2038
2464
  if (!firstAvailable) {
2465
+ logger.error("No available workers for skill-based routing");
2039
2466
  throw new Error("No available workers for skill-based routing");
2040
2467
  }
2468
+ logger.info("Skill-based routing fallback decision", {
2469
+ targetAgent: firstAvailable[0],
2470
+ confidence: 0.5
2471
+ });
2041
2472
  return {
2042
2473
  targetAgent: firstAvailable[0],
2474
+ targetAgents: null,
2043
2475
  reasoning: "No skill matches found, using first available worker",
2044
2476
  confidence: 0.5,
2045
2477
  strategy: "skill-based",
@@ -2048,8 +2480,15 @@ var skillBasedRouting = {
2048
2480
  }
2049
2481
  const best = workerScores[0];
2050
2482
  const confidence = Math.min(best.score / 5, 1);
2483
+ logger.info("Skill-based routing decision", {
2484
+ targetAgent: best.id,
2485
+ score: best.score,
2486
+ confidence,
2487
+ matchedSkills: best.skills
2488
+ });
2051
2489
  return {
2052
2490
  targetAgent: best.id,
2491
+ targetAgents: null,
2053
2492
  reasoning: `Best skill match with score ${best.score} (skills: ${best.skills.join(", ")})`,
2054
2493
  confidence,
2055
2494
  strategy: "skill-based",
@@ -2060,15 +2499,29 @@ var skillBasedRouting = {
2060
2499
  var loadBalancedRouting = {
2061
2500
  name: "load-balanced",
2062
2501
  async route(state, config) {
2502
+ logger.info("Starting load-balanced routing", {
2503
+ iteration: state.iteration
2504
+ });
2063
2505
  const availableWorkers = Object.entries(state.workers).filter(([_, caps]) => caps.available).map(([id, caps]) => ({ id, workload: caps.currentWorkload })).sort((a, b) => a.workload - b.workload);
2506
+ logger.debug("Worker workloads", {
2507
+ workers: availableWorkers.map((w) => ({ id: w.id, workload: w.workload }))
2508
+ });
2064
2509
  if (availableWorkers.length === 0) {
2510
+ logger.error("No available workers for load-balanced routing");
2065
2511
  throw new Error("No available workers for load-balanced routing");
2066
2512
  }
2067
2513
  const targetWorker = availableWorkers[0];
2068
2514
  const avgWorkload = availableWorkers.reduce((sum, w) => sum + w.workload, 0) / availableWorkers.length;
2069
2515
  const confidence = targetWorker.workload === 0 ? 1 : Math.max(0.5, 1 - targetWorker.workload / (avgWorkload * 2));
2516
+ logger.info("Load-balanced routing decision", {
2517
+ targetAgent: targetWorker.id,
2518
+ workload: targetWorker.workload,
2519
+ avgWorkload: avgWorkload.toFixed(1),
2520
+ confidence
2521
+ });
2070
2522
  return {
2071
2523
  targetAgent: targetWorker.id,
2524
+ targetAgents: null,
2072
2525
  reasoning: `Lowest workload: ${targetWorker.workload} tasks (avg: ${avgWorkload.toFixed(1)})`,
2073
2526
  confidence,
2074
2527
  strategy: "load-balanced",
@@ -2079,13 +2532,24 @@ var loadBalancedRouting = {
2079
2532
  var ruleBasedRouting = {
2080
2533
  name: "rule-based",
2081
2534
  async route(state, config) {
2535
+ logger.info("Starting rule-based routing", {
2536
+ iteration: state.iteration
2537
+ });
2082
2538
  if (!config.routingFn) {
2539
+ logger.error("Rule-based routing requires a custom routing function");
2083
2540
  throw new Error("Rule-based routing requires a custom routing function");
2084
2541
  }
2085
- return await config.routingFn(state);
2542
+ const decision = await config.routingFn(state);
2543
+ logger.info("Rule-based routing decision", {
2544
+ targetAgent: decision.targetAgent,
2545
+ targetAgents: decision.targetAgents,
2546
+ confidence: decision.confidence
2547
+ });
2548
+ return decision;
2086
2549
  }
2087
2550
  };
2088
2551
  function getRoutingStrategy(name) {
2552
+ logger.debug("Getting routing strategy", { name });
2089
2553
  switch (name) {
2090
2554
  case "llm-based":
2091
2555
  return llmBasedRouting;
@@ -2098,14 +2562,15 @@ function getRoutingStrategy(name) {
2098
2562
  case "rule-based":
2099
2563
  return ruleBasedRouting;
2100
2564
  default:
2565
+ logger.error("Unknown routing strategy", { name });
2101
2566
  throw new Error(`Unknown routing strategy: ${name}`);
2102
2567
  }
2103
2568
  }
2104
2569
 
2105
2570
  // src/multi-agent/utils.ts
2106
- var import_core7 = require("@agentforge/core");
2107
- var logLevel = process.env.LOG_LEVEL?.toLowerCase() || import_core7.LogLevel.INFO;
2108
- var logger = (0, import_core7.createLogger)("multi-agent", { level: logLevel });
2571
+ var import_core9 = require("@agentforge/core");
2572
+ var logLevel2 = process.env.LOG_LEVEL?.toLowerCase() || import_core9.LogLevel.INFO;
2573
+ var logger2 = (0, import_core9.createLogger)("multi-agent", { level: logLevel2 });
2109
2574
  function isReActAgent(obj) {
2110
2575
  return obj && typeof obj === "object" && typeof obj.invoke === "function" && typeof obj.stream === "function" && // Additional check to ensure it's not just any object with invoke/stream
2111
2576
  (obj.constructor?.name === "CompiledGraph" || obj.constructor?.name === "CompiledStateGraph");
@@ -2113,9 +2578,9 @@ function isReActAgent(obj) {
2113
2578
  function wrapReActAgent(workerId, agent, verbose = false) {
2114
2579
  return async (state, config) => {
2115
2580
  try {
2116
- logger.debug("Wrapping ReAct agent execution", { workerId });
2581
+ logger2.debug("Wrapping ReAct agent execution", { workerId });
2117
2582
  const task = state.messages[state.messages.length - 1]?.content || state.input;
2118
- logger.debug("Extracted task", {
2583
+ logger2.debug("Extracted task", {
2119
2584
  workerId,
2120
2585
  taskPreview: task.substring(0, 100) + (task.length > 100 ? "..." : "")
2121
2586
  });
@@ -2123,11 +2588,8 @@ function wrapReActAgent(workerId, agent, verbose = false) {
2123
2588
  (assignment) => assignment.workerId === workerId && !state.completedTasks.some((task2) => task2.assignmentId === assignment.id)
2124
2589
  );
2125
2590
  if (!currentAssignment) {
2126
- logger.debug("No active assignment found", { workerId });
2127
- return {
2128
- currentAgent: "supervisor",
2129
- status: "routing"
2130
- };
2591
+ logger2.debug("No active assignment found", { workerId });
2592
+ return {};
2131
2593
  }
2132
2594
  const result = await agent.invoke(
2133
2595
  {
@@ -2137,14 +2599,14 @@ function wrapReActAgent(workerId, agent, verbose = false) {
2137
2599
  // Pass through the config for checkpointing and interrupt support
2138
2600
  );
2139
2601
  const response = result.messages?.[result.messages.length - 1]?.content || "No response";
2140
- logger.debug("Received response from ReAct agent", {
2602
+ logger2.debug("Received response from ReAct agent", {
2141
2603
  workerId,
2142
2604
  responsePreview: response.substring(0, 100) + (response.length > 100 ? "..." : "")
2143
2605
  });
2144
2606
  const toolsUsed = result.actions?.map((action) => action.name).filter(Boolean) || [];
2145
2607
  const uniqueTools = [...new Set(toolsUsed)];
2146
2608
  if (uniqueTools.length > 0) {
2147
- logger.debug("Tools used by ReAct agent", { workerId, tools: uniqueTools });
2609
+ logger2.debug("Tools used by ReAct agent", { workerId, tools: uniqueTools });
2148
2610
  }
2149
2611
  const taskResult = {
2150
2612
  assignmentId: currentAssignment.id,
@@ -2159,16 +2621,14 @@ function wrapReActAgent(workerId, agent, verbose = false) {
2159
2621
  }
2160
2622
  };
2161
2623
  return {
2162
- completedTasks: [taskResult],
2163
- currentAgent: "supervisor",
2164
- status: "routing"
2624
+ completedTasks: [taskResult]
2165
2625
  };
2166
2626
  } catch (error) {
2167
2627
  if (error && typeof error === "object" && "constructor" in error && error.constructor.name === "GraphInterrupt") {
2168
- logger.debug("GraphInterrupt detected - re-throwing", { workerId });
2628
+ logger2.debug("GraphInterrupt detected - re-throwing", { workerId });
2169
2629
  throw error;
2170
2630
  }
2171
- logger.error("Error in ReAct agent execution", {
2631
+ logger2.error("Error in ReAct agent execution", {
2172
2632
  workerId,
2173
2633
  error: error instanceof Error ? error.message : String(error),
2174
2634
  stack: error instanceof Error ? error.stack : void 0
@@ -2201,7 +2661,9 @@ function wrapReActAgent(workerId, agent, verbose = false) {
2201
2661
 
2202
2662
  // src/multi-agent/nodes.ts
2203
2663
  var import_messages5 = require("@langchain/core/messages");
2204
- var import_core8 = require("@agentforge/core");
2664
+ var import_core10 = require("@agentforge/core");
2665
+ var logLevel3 = process.env.LOG_LEVEL?.toLowerCase() || import_core10.LogLevel.INFO;
2666
+ var logger3 = (0, import_core10.createLogger)("multi-agent:nodes", { level: logLevel3 });
2205
2667
  var DEFAULT_AGGREGATOR_SYSTEM_PROMPT = `You are an aggregator agent responsible for combining results from multiple worker agents.
2206
2668
 
2207
2669
  Your job is to:
@@ -2219,46 +2681,97 @@ function createSupervisorNode(config) {
2219
2681
  } = config;
2220
2682
  return async (state) => {
2221
2683
  try {
2222
- if (verbose) {
2223
- console.log(`[Supervisor] Routing iteration ${state.iteration}/${maxIterations}`);
2224
- }
2684
+ logger3.info("Supervisor node executing", {
2685
+ iteration: state.iteration,
2686
+ maxIterations,
2687
+ activeAssignments: state.activeAssignments.length,
2688
+ completedTasks: state.completedTasks.length
2689
+ });
2690
+ logger3.debug(`Routing iteration ${state.iteration}/${maxIterations}`);
2225
2691
  if (state.iteration >= maxIterations) {
2226
- if (verbose) {
2227
- console.log("[Supervisor] Max iterations reached, moving to aggregation");
2228
- }
2692
+ logger3.warn("Max iterations reached", {
2693
+ iteration: state.iteration,
2694
+ maxIterations
2695
+ });
2696
+ logger3.debug("Max iterations reached, moving to aggregation");
2229
2697
  return {
2230
2698
  status: "aggregating",
2231
2699
  currentAgent: "aggregator"
2232
2700
  };
2233
2701
  }
2234
2702
  const allCompleted = state.activeAssignments.every(
2235
- (assignment2) => state.completedTasks.some((task) => task.assignmentId === assignment2.id)
2703
+ (assignment) => state.completedTasks.some((task2) => task2.assignmentId === assignment.id)
2236
2704
  );
2705
+ logger3.debug("Checking task completion", {
2706
+ activeAssignments: state.activeAssignments.length,
2707
+ completedTasks: state.completedTasks.length,
2708
+ allCompleted
2709
+ });
2237
2710
  if (allCompleted && state.activeAssignments.length > 0) {
2238
- if (verbose) {
2239
- console.log("[Supervisor] All tasks completed, moving to aggregation");
2240
- }
2711
+ logger3.info("All tasks completed, moving to aggregation", {
2712
+ completedCount: state.completedTasks.length
2713
+ });
2714
+ logger3.debug("All tasks completed, moving to aggregation");
2241
2715
  return {
2242
2716
  status: "aggregating",
2243
2717
  currentAgent: "aggregator"
2244
2718
  };
2245
2719
  }
2720
+ logger3.debug("Getting routing strategy", { strategy });
2246
2721
  const routingImpl = getRoutingStrategy(strategy);
2247
2722
  const decision = await routingImpl.route(state, config);
2248
- if (verbose) {
2249
- console.log(`[Supervisor] Routing to ${decision.targetAgent}: ${decision.reasoning}`);
2723
+ const targetAgents = decision.targetAgents && decision.targetAgents.length > 0 ? decision.targetAgents : decision.targetAgent ? [decision.targetAgent] : [];
2724
+ logger3.debug("Target agents determined", {
2725
+ targetAgents,
2726
+ isParallel: targetAgents.length > 1,
2727
+ decision: {
2728
+ reasoning: decision.reasoning,
2729
+ confidence: decision.confidence
2730
+ }
2731
+ });
2732
+ if (targetAgents.length === 0) {
2733
+ logger3.error("No target agents specified in routing decision");
2734
+ throw new Error("Routing decision must specify at least one target agent");
2250
2735
  }
2251
- const assignment = {
2736
+ if (targetAgents.length === 1) {
2737
+ logger3.info("Routing to single agent", {
2738
+ targetAgent: targetAgents[0],
2739
+ reasoning: decision.reasoning,
2740
+ confidence: decision.confidence
2741
+ });
2742
+ } else {
2743
+ logger3.info("Routing to multiple agents in parallel", {
2744
+ targetAgents,
2745
+ count: targetAgents.length,
2746
+ reasoning: decision.reasoning,
2747
+ confidence: decision.confidence
2748
+ });
2749
+ }
2750
+ if (targetAgents.length === 1) {
2751
+ logger3.debug(`Routing to ${targetAgents[0]}: ${decision.reasoning}`);
2752
+ } else {
2753
+ logger3.debug(`Routing to ${targetAgents.length} agents in parallel [${targetAgents.join(", ")}]: ${decision.reasoning}`);
2754
+ }
2755
+ const task = state.messages[state.messages.length - 1]?.content || state.input;
2756
+ const assignments = targetAgents.map((workerId) => ({
2252
2757
  id: `task_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
2253
- workerId: decision.targetAgent,
2254
- task: state.messages[state.messages.length - 1]?.content || state.input,
2758
+ workerId,
2759
+ task,
2255
2760
  priority: 5,
2256
2761
  assignedAt: Date.now()
2257
- };
2258
- const message = {
2762
+ }));
2763
+ logger3.debug("Created task assignments", {
2764
+ assignmentCount: assignments.length,
2765
+ assignments: assignments.map((a) => ({
2766
+ id: a.id,
2767
+ workerId: a.workerId,
2768
+ taskLength: a.task.length
2769
+ }))
2770
+ });
2771
+ const messages = assignments.map((assignment) => ({
2259
2772
  id: `msg-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
2260
2773
  from: "supervisor",
2261
- to: [decision.targetAgent],
2774
+ to: [assignment.workerId],
2262
2775
  type: "task_assignment",
2263
2776
  content: assignment.task,
2264
2777
  timestamp: Date.now(),
@@ -2266,17 +2779,29 @@ function createSupervisorNode(config) {
2266
2779
  assignmentId: assignment.id,
2267
2780
  priority: assignment.priority
2268
2781
  }
2269
- };
2782
+ }));
2783
+ logger3.info("Supervisor routing complete", {
2784
+ currentAgent: targetAgents.join(","),
2785
+ status: "executing",
2786
+ assignmentCount: assignments.length,
2787
+ nextIteration: state.iteration + 1
2788
+ });
2270
2789
  return {
2271
- currentAgent: decision.targetAgent,
2790
+ currentAgent: targetAgents.join(","),
2791
+ // Store all agents (for backward compat)
2272
2792
  status: "executing",
2273
2793
  routingHistory: [decision],
2274
- activeAssignments: [assignment],
2275
- messages: [message],
2794
+ activeAssignments: assignments,
2795
+ // Multiple assignments for parallel execution!
2796
+ messages,
2276
2797
  iteration: state.iteration + 1
2277
2798
  };
2278
2799
  } catch (error) {
2279
- console.error("[Supervisor] Error:", error);
2800
+ logger3.error("Supervisor node error", {
2801
+ error: error instanceof Error ? error.message : String(error),
2802
+ stack: error instanceof Error ? error.stack : void 0,
2803
+ iteration: state.iteration
2804
+ });
2280
2805
  return {
2281
2806
  status: "failed",
2282
2807
  error: error instanceof Error ? error.message : "Unknown error in supervisor"
@@ -2297,43 +2822,52 @@ function createWorkerNode(config) {
2297
2822
  } = config;
2298
2823
  return async (state, runConfig) => {
2299
2824
  try {
2300
- if (verbose) {
2301
- console.log(`[Worker:${id}] Executing task`);
2302
- }
2825
+ logger3.info("Worker node executing", {
2826
+ workerId: id,
2827
+ iteration: state.iteration,
2828
+ activeAssignments: state.activeAssignments.length
2829
+ });
2303
2830
  const currentAssignment = state.activeAssignments.find(
2304
2831
  (assignment) => assignment.workerId === id && !state.completedTasks.some((task) => task.assignmentId === assignment.id)
2305
2832
  );
2306
2833
  if (!currentAssignment) {
2307
- if (verbose) {
2308
- console.log(`[Worker:${id}] No active assignment found`);
2309
- }
2310
- return {
2311
- currentAgent: "supervisor",
2312
- status: "routing"
2313
- };
2834
+ logger3.debug("No active assignment found for worker", {
2835
+ workerId: id,
2836
+ totalActiveAssignments: state.activeAssignments.length,
2837
+ completedTasks: state.completedTasks.length
2838
+ });
2839
+ return {};
2314
2840
  }
2841
+ logger3.info("Worker processing assignment", {
2842
+ workerId: id,
2843
+ assignmentId: currentAssignment.id,
2844
+ taskLength: currentAssignment.task.length,
2845
+ taskPreview: currentAssignment.task.substring(0, 100)
2846
+ });
2315
2847
  if (executeFn) {
2316
- if (verbose) {
2317
- console.log(`[Worker:${id}] Using custom executeFn`);
2318
- }
2848
+ logger3.debug("Using custom execution function", { workerId: id });
2319
2849
  return await executeFn(state, runConfig);
2320
2850
  }
2321
2851
  if (agent) {
2322
2852
  if (isReActAgent(agent)) {
2323
- if (verbose) {
2324
- console.log(`[Worker:${id}] Using ReAct agent (auto-wrapped)`);
2325
- }
2853
+ logger3.debug("Using ReAct agent", { workerId: id });
2326
2854
  const wrappedFn = wrapReActAgent(id, agent, verbose);
2327
2855
  return await wrappedFn(state, runConfig);
2328
2856
  } else {
2329
- console.warn(`[Worker:${id}] Agent provided but does not appear to be a ReAct agent. Falling back to default execution.`);
2857
+ logger3.warn("Agent provided but not a ReAct agent, falling back", { workerId: id });
2330
2858
  }
2331
2859
  }
2332
2860
  if (!model) {
2861
+ logger3.error("Worker missing required configuration", { workerId: id });
2333
2862
  throw new Error(
2334
2863
  `Worker ${id} requires either a model, an agent, or a custom execution function. Provide one of: config.model, config.agent, or config.executeFn`
2335
2864
  );
2336
2865
  }
2866
+ logger3.debug("Using default LLM execution", {
2867
+ workerId: id,
2868
+ hasTools: tools.length > 0,
2869
+ toolCount: tools.length
2870
+ });
2337
2871
  const defaultSystemPrompt = `You are a specialized worker agent with the following capabilities:
2338
2872
  Skills: ${capabilities.skills.join(", ")}
2339
2873
  Tools: ${capabilities.tools.join(", ")}
@@ -2345,14 +2879,23 @@ Execute the assigned task using your skills and tools. Provide a clear, actionab
2345
2879
  ];
2346
2880
  let modelToUse = model;
2347
2881
  if (tools.length > 0 && model.bindTools) {
2348
- const langchainTools = (0, import_core8.toLangChainTools)(tools);
2882
+ logger3.debug("Binding tools to model", {
2883
+ workerId: id,
2884
+ toolCount: tools.length,
2885
+ toolNames: tools.map((t) => t.metadata.name)
2886
+ });
2887
+ const langchainTools = (0, import_core10.toLangChainTools)(tools);
2349
2888
  modelToUse = model.bindTools(langchainTools);
2350
2889
  }
2890
+ logger3.debug("Invoking LLM", { workerId: id });
2351
2891
  const response = await modelToUse.invoke(messages);
2352
2892
  const result = typeof response.content === "string" ? response.content : JSON.stringify(response.content);
2353
- if (verbose) {
2354
- console.log(`[Worker:${id}] Task completed:`, result.substring(0, 100) + "...");
2355
- }
2893
+ logger3.info("Worker task completed", {
2894
+ workerId: id,
2895
+ assignmentId: currentAssignment.id,
2896
+ resultLength: result.length,
2897
+ resultPreview: result.substring(0, 100)
2898
+ });
2356
2899
  const taskResult = {
2357
2900
  assignmentId: currentAssignment.id,
2358
2901
  workerId: id,
@@ -2382,22 +2925,33 @@ Execute the assigned task using your skills and tools. Provide a clear, actionab
2382
2925
  currentWorkload: Math.max(0, capabilities.currentWorkload - 1)
2383
2926
  }
2384
2927
  };
2928
+ logger3.debug("Worker state update", {
2929
+ workerId: id,
2930
+ newWorkload: updatedWorkers[id].currentWorkload
2931
+ });
2385
2932
  return {
2386
2933
  completedTasks: [taskResult],
2387
2934
  messages: [message],
2388
- workers: updatedWorkers,
2389
- currentAgent: "supervisor",
2390
- status: "routing"
2935
+ workers: updatedWorkers
2391
2936
  };
2392
2937
  } catch (error) {
2393
2938
  if (error && typeof error === "object" && "constructor" in error && error.constructor.name === "GraphInterrupt") {
2939
+ logger3.info("GraphInterrupt detected, re-throwing", { workerId: id });
2394
2940
  throw error;
2395
2941
  }
2396
- console.error(`[Worker:${id}] Error:`, error);
2942
+ logger3.error("Worker node error", {
2943
+ workerId: id,
2944
+ error: error instanceof Error ? error.message : String(error),
2945
+ stack: error instanceof Error ? error.stack : void 0
2946
+ });
2397
2947
  const currentAssignment = state.activeAssignments.find(
2398
2948
  (assignment) => assignment.workerId === id
2399
2949
  );
2400
2950
  if (currentAssignment) {
2951
+ logger3.warn("Creating error result for assignment", {
2952
+ workerId: id,
2953
+ assignmentId: currentAssignment.id
2954
+ });
2401
2955
  const errorResult = {
2402
2956
  assignmentId: currentAssignment.id,
2403
2957
  workerId: id,
@@ -2412,6 +2966,7 @@ Execute the assigned task using your skills and tools. Provide a clear, actionab
2412
2966
  status: "routing"
2413
2967
  };
2414
2968
  }
2969
+ logger3.error("No assignment found for error handling", { workerId: id });
2415
2970
  return {
2416
2971
  status: "failed",
2417
2972
  error: error instanceof Error ? error.message : `Unknown error in worker ${id}`
@@ -2428,29 +2983,44 @@ function createAggregatorNode(config = {}) {
2428
2983
  } = config;
2429
2984
  return async (state) => {
2430
2985
  try {
2431
- if (verbose) {
2432
- console.log("[Aggregator] Combining results from workers");
2433
- }
2986
+ logger3.info("Aggregator node executing", {
2987
+ completedTasks: state.completedTasks.length,
2988
+ successfulTasks: state.completedTasks.filter((t) => t.success).length,
2989
+ failedTasks: state.completedTasks.filter((t) => !t.success).length
2990
+ });
2991
+ logger3.debug("Combining results from workers");
2434
2992
  if (aggregateFn) {
2993
+ logger3.debug("Using custom aggregation function");
2435
2994
  const response2 = await aggregateFn(state);
2995
+ logger3.info("Custom aggregation complete", {
2996
+ responseLength: response2.length
2997
+ });
2436
2998
  return {
2437
2999
  response: response2,
2438
3000
  status: "completed"
2439
3001
  };
2440
3002
  }
2441
3003
  if (state.completedTasks.length === 0) {
3004
+ logger3.warn("No completed tasks to aggregate");
2442
3005
  return {
2443
3006
  response: "No tasks were completed.",
2444
3007
  status: "completed"
2445
3008
  };
2446
3009
  }
2447
3010
  if (!model) {
3011
+ logger3.debug("No model provided, concatenating results");
2448
3012
  const combinedResults = state.completedTasks.filter((task) => task.success).map((task) => task.result).join("\n\n");
3013
+ logger3.info("Simple concatenation complete", {
3014
+ resultLength: combinedResults.length
3015
+ });
2449
3016
  return {
2450
3017
  response: combinedResults || "No successful results to aggregate.",
2451
3018
  status: "completed"
2452
3019
  };
2453
3020
  }
3021
+ logger3.debug("Using LLM for intelligent aggregation", {
3022
+ taskCount: state.completedTasks.length
3023
+ });
2454
3024
  const taskResults = state.completedTasks.map((task, idx) => {
2455
3025
  const status = task.success ? "\u2713" : "\u2717";
2456
3026
  const result = task.success ? task.result : `Error: ${task.error}`;
@@ -2467,17 +3037,24 @@ Please synthesize these results into a comprehensive response that addresses the
2467
3037
  new import_messages5.SystemMessage(systemPrompt),
2468
3038
  new import_messages5.HumanMessage(userPrompt)
2469
3039
  ];
3040
+ logger3.debug("Invoking aggregation LLM");
2470
3041
  const response = await model.invoke(messages);
2471
3042
  const aggregatedResponse = typeof response.content === "string" ? response.content : JSON.stringify(response.content);
2472
- if (verbose) {
2473
- console.log("[Aggregator] Aggregation complete");
2474
- }
3043
+ logger3.info("Aggregation complete", {
3044
+ responseLength: aggregatedResponse.length,
3045
+ responsePreview: aggregatedResponse.substring(0, 100)
3046
+ });
3047
+ logger3.debug("Aggregation complete");
2475
3048
  return {
2476
3049
  response: aggregatedResponse,
2477
3050
  status: "completed"
2478
3051
  };
2479
3052
  } catch (error) {
2480
- console.error("[Aggregator] Error:", error);
3053
+ logger3.error("Aggregator node error", {
3054
+ error: error instanceof Error ? error.message : String(error),
3055
+ stack: error instanceof Error ? error.stack : void 0,
3056
+ completedTasks: state.completedTasks.length
3057
+ });
2481
3058
  return {
2482
3059
  status: "failed",
2483
3060
  error: error instanceof Error ? error.message : "Unknown error in aggregator"
@@ -2488,7 +3065,9 @@ Please synthesize these results into a comprehensive response that addresses the
2488
3065
 
2489
3066
  // src/multi-agent/agent.ts
2490
3067
  var import_langgraph4 = require("@langchain/langgraph");
2491
- var import_core9 = require("@agentforge/core");
3068
+ var import_core11 = require("@agentforge/core");
3069
+ var logLevel4 = process.env.LOG_LEVEL?.toLowerCase() || import_core11.LogLevel.INFO;
3070
+ var logger4 = (0, import_core11.createLogger)("multi-agent:system", { level: logLevel4 });
2492
3071
  function createMultiAgentSystem(config) {
2493
3072
  const {
2494
3073
  supervisor,
@@ -2500,10 +3079,16 @@ function createMultiAgentSystem(config) {
2500
3079
  } = config;
2501
3080
  const workflow = new import_langgraph4.StateGraph(MultiAgentState);
2502
3081
  let supervisorConfig = { ...supervisor, maxIterations, verbose };
2503
- if (supervisor.model && supervisor.tools && supervisor.tools.length > 0) {
2504
- const langchainTools = (0, import_core9.toLangChainTools)(supervisor.tools);
2505
- const modelWithTools = supervisor.model.bindTools(langchainTools);
2506
- supervisorConfig.model = modelWithTools;
3082
+ if (supervisor.model) {
3083
+ let configuredModel = supervisor.model;
3084
+ if (supervisor.strategy === "llm-based") {
3085
+ configuredModel = configuredModel.withStructuredOutput(RoutingDecisionSchema);
3086
+ }
3087
+ if (supervisor.tools && supervisor.tools.length > 0) {
3088
+ const langchainTools = (0, import_core11.toLangChainTools)(supervisor.tools);
3089
+ configuredModel = configuredModel.bindTools(langchainTools);
3090
+ }
3091
+ supervisorConfig.model = configuredModel;
2507
3092
  }
2508
3093
  const supervisorNode = createSupervisorNode(supervisorConfig);
2509
3094
  workflow.addNode("supervisor", supervisorNode);
@@ -2524,21 +3109,49 @@ function createMultiAgentSystem(config) {
2524
3109
  });
2525
3110
  workflow.addNode("aggregator", aggregatorNode);
2526
3111
  const supervisorRouter = (state) => {
3112
+ logger4.debug("Supervisor router executing", {
3113
+ status: state.status,
3114
+ currentAgent: state.currentAgent,
3115
+ iteration: state.iteration
3116
+ });
2527
3117
  if (state.status === "completed" || state.status === "failed") {
3118
+ logger4.info("Supervisor router: ending workflow", { status: state.status });
2528
3119
  return import_langgraph4.END;
2529
3120
  }
2530
3121
  if (state.status === "aggregating") {
3122
+ logger4.info("Supervisor router: routing to aggregator");
2531
3123
  return "aggregator";
2532
3124
  }
2533
3125
  if (state.currentAgent && state.currentAgent !== "supervisor") {
3126
+ if (state.currentAgent.includes(",")) {
3127
+ const agents = state.currentAgent.split(",").map((a) => a.trim());
3128
+ logger4.info("Supervisor router: parallel routing", {
3129
+ agents,
3130
+ count: agents.length
3131
+ });
3132
+ return agents;
3133
+ }
3134
+ logger4.info("Supervisor router: single agent routing", {
3135
+ targetAgent: state.currentAgent
3136
+ });
2534
3137
  return state.currentAgent;
2535
3138
  }
3139
+ logger4.debug("Supervisor router: staying at supervisor");
2536
3140
  return "supervisor";
2537
3141
  };
2538
3142
  const workerRouter = (state) => {
3143
+ logger4.debug("Worker router executing", {
3144
+ iteration: state.iteration,
3145
+ completedTasks: state.completedTasks.length
3146
+ });
3147
+ logger4.debug("Worker router: returning to supervisor");
2539
3148
  return "supervisor";
2540
3149
  };
2541
3150
  const aggregatorRouter = (state) => {
3151
+ logger4.info("Aggregator router: ending workflow", {
3152
+ completedTasks: state.completedTasks.length,
3153
+ status: state.status
3154
+ });
2542
3155
  return import_langgraph4.END;
2543
3156
  };
2544
3157
  workflow.setEntryPoint("supervisor");
@@ -2732,11 +3345,14 @@ function registerWorkers(system, workers) {
2732
3345
  ToolCallSchema,
2733
3346
  ToolResultSchema,
2734
3347
  WorkerCapabilitiesSchema,
3348
+ buildDeduplicationMetrics,
3349
+ calculateDeduplicationSavings,
2735
3350
  createAggregatorNode,
2736
3351
  createExecutorNode,
2737
3352
  createFinisherNode,
2738
3353
  createGeneratorNode,
2739
3354
  createMultiAgentSystem,
3355
+ createPatternLogger,
2740
3356
  createPlanExecuteAgent,
2741
3357
  createPlannerNode,
2742
3358
  createReActAgent,
@@ -2748,6 +3364,7 @@ function registerWorkers(system, workers) {
2748
3364
  createReviserNode,
2749
3365
  createSupervisorNode,
2750
3366
  createWorkerNode,
3367
+ generateToolCallCacheKey,
2751
3368
  getRoutingStrategy,
2752
3369
  llmBasedRouting,
2753
3370
  loadBalancedRouting,