@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.js CHANGED
@@ -22,7 +22,9 @@ var ToolResultSchema = z.object({
22
22
  toolCallId: z.string(),
23
23
  result: z.any(),
24
24
  error: z.string().optional(),
25
- timestamp: z.number().optional()
25
+ timestamp: z.number().optional(),
26
+ isDuplicate: z.boolean().optional()
27
+ // Flag indicating this was a duplicate tool call
26
28
  });
27
29
  var ScratchpadEntrySchema = z.object({
28
30
  step: z.number(),
@@ -147,13 +149,49 @@ function formatScratchpad(scratchpad) {
147
149
  // src/react/nodes.ts
148
150
  import { HumanMessage, AIMessage, SystemMessage } from "@langchain/core/messages";
149
151
  import { toLangChainTools } from "@agentforge/core";
152
+
153
+ // src/shared/deduplication.ts
154
+ import { createLogger } from "@agentforge/core";
155
+ function generateToolCallCacheKey(toolName, args) {
156
+ const sortedArgs = JSON.stringify(args, Object.keys(args || {}).sort());
157
+ return `${toolName}:${sortedArgs}`;
158
+ }
159
+ function createPatternLogger(name, defaultLevel = "info") {
160
+ const logLevel5 = process.env.LOG_LEVEL?.toLowerCase() || defaultLevel;
161
+ return createLogger(name, { level: logLevel5 });
162
+ }
163
+ function calculateDeduplicationSavings(duplicatesSkipped, toolsExecuted) {
164
+ if (duplicatesSkipped === 0) {
165
+ return "0%";
166
+ }
167
+ const total = toolsExecuted + duplicatesSkipped;
168
+ return `${Math.round(duplicatesSkipped / total * 100)}%`;
169
+ }
170
+ function buildDeduplicationMetrics(toolsExecuted, duplicatesSkipped, totalObservations) {
171
+ return {
172
+ toolsExecuted,
173
+ duplicatesSkipped,
174
+ totalObservations,
175
+ deduplicationSavings: calculateDeduplicationSavings(duplicatesSkipped, toolsExecuted)
176
+ };
177
+ }
178
+
179
+ // src/react/nodes.ts
180
+ var reasoningLogger = createPatternLogger("agentforge:patterns:react:reasoning");
181
+ var actionLogger = createPatternLogger("agentforge:patterns:react:action");
182
+ var observationLogger = createPatternLogger("agentforge:patterns:react:observation");
150
183
  function createReasoningNode(llm, tools, systemPrompt, maxIterations, verbose = false) {
151
184
  const langchainTools = toLangChainTools(tools);
152
185
  const llmWithTools = llm.bindTools ? llm.bindTools(langchainTools) : llm;
153
186
  return async (state) => {
154
- if (verbose) {
155
- console.log(`[reasoning] Iteration ${state.iteration + 1}/${maxIterations}`);
156
- }
187
+ const currentIteration = state.iteration || 0;
188
+ const startTime = Date.now();
189
+ reasoningLogger.debug("Reasoning iteration started", {
190
+ iteration: currentIteration + 1,
191
+ maxIterations,
192
+ observationCount: state.observations?.length || 0,
193
+ hasActions: !!state.actions?.length
194
+ });
157
195
  const stateMessages = state.messages || [];
158
196
  const messages = [
159
197
  new SystemMessage(systemPrompt),
@@ -183,8 +221,15 @@ ${scratchpadText}`));
183
221
  });
184
222
  }
185
223
  }
186
- const currentIteration = state.iteration || 0;
187
224
  const shouldContinue = toolCalls.length > 0 && currentIteration + 1 < maxIterations;
225
+ reasoningLogger.info("Reasoning complete", {
226
+ iteration: currentIteration + 1,
227
+ thoughtGenerated: !!thought,
228
+ actionCount: toolCalls.length,
229
+ shouldContinue,
230
+ isFinalResponse: toolCalls.length === 0,
231
+ duration: Date.now() - startTime
232
+ });
188
233
  return {
189
234
  messages: [{ role: "assistant", content: thought }],
190
235
  thoughts: thought ? [{ content: thought, timestamp: Date.now() }] : [],
@@ -197,16 +242,71 @@ ${scratchpadText}`));
197
242
  };
198
243
  };
199
244
  }
200
- function createActionNode(tools, verbose = false) {
245
+ function createActionNode(tools, verbose = false, enableDeduplication = true) {
201
246
  const toolMap = new Map(tools.map((tool) => [tool.metadata.name, tool]));
202
247
  return async (state) => {
203
248
  const actions = state.actions || [];
204
- if (verbose) {
205
- console.log(`[action] Executing ${actions.length} tool calls`);
206
- }
249
+ const allObservations = state.observations || [];
250
+ const iteration = state.iteration || 0;
251
+ const startTime = Date.now();
252
+ actionLogger.debug("Action node started", {
253
+ actionCount: actions.length,
254
+ iteration,
255
+ cacheEnabled: enableDeduplication
256
+ });
207
257
  const recentActions = actions.slice(-10);
208
258
  const observations = [];
259
+ const executionCache = /* @__PURE__ */ new Map();
260
+ let cacheSize = 0;
261
+ if (enableDeduplication) {
262
+ for (const observation of allObservations) {
263
+ const correspondingAction = actions.find((a) => a.id === observation.toolCallId);
264
+ if (correspondingAction) {
265
+ const cacheKey = generateToolCallCacheKey(correspondingAction.name, correspondingAction.arguments);
266
+ executionCache.set(cacheKey, observation);
267
+ cacheSize++;
268
+ }
269
+ }
270
+ if (cacheSize > 0) {
271
+ actionLogger.debug("Deduplication cache built", {
272
+ cacheSize,
273
+ totalObservations: allObservations.length
274
+ });
275
+ }
276
+ }
277
+ let duplicatesSkipped = 0;
278
+ let toolsExecuted = 0;
209
279
  for (const action of recentActions) {
280
+ const existingObservation = allObservations.find((obs) => obs.toolCallId === action.id);
281
+ if (existingObservation) {
282
+ actionLogger.debug("Skipping already-processed action", {
283
+ toolName: action.name,
284
+ toolCallId: action.id,
285
+ iteration
286
+ });
287
+ continue;
288
+ }
289
+ if (enableDeduplication) {
290
+ const cacheKey = generateToolCallCacheKey(action.name, action.arguments);
291
+ const cachedResult = executionCache.get(cacheKey);
292
+ if (cachedResult) {
293
+ duplicatesSkipped++;
294
+ actionLogger.info("Duplicate tool call prevented", {
295
+ toolName: action.name,
296
+ arguments: action.arguments,
297
+ iteration,
298
+ cacheHit: true
299
+ });
300
+ observations.push({
301
+ toolCallId: action.id,
302
+ result: cachedResult.result,
303
+ error: cachedResult.error,
304
+ timestamp: Date.now(),
305
+ isDuplicate: true
306
+ });
307
+ continue;
308
+ }
309
+ }
210
310
  const tool = toolMap.get(action.name);
211
311
  if (!tool) {
212
312
  observations.push({
@@ -218,31 +318,51 @@ function createActionNode(tools, verbose = false) {
218
318
  continue;
219
319
  }
220
320
  try {
321
+ const startTime2 = Date.now();
221
322
  const result = await tool.execute(action.arguments);
222
- observations.push({
323
+ const executionTime = Date.now() - startTime2;
324
+ toolsExecuted++;
325
+ actionLogger.debug("Tool executed successfully", {
326
+ toolName: action.name,
327
+ executionTime,
328
+ iteration
329
+ });
330
+ const observation = {
223
331
  toolCallId: action.id,
224
332
  result,
225
333
  timestamp: Date.now()
226
- });
227
- if (verbose) {
228
- console.log(`[action] Tool '${action.name}' executed successfully`);
334
+ };
335
+ observations.push(observation);
336
+ if (enableDeduplication) {
337
+ const cacheKey = generateToolCallCacheKey(action.name, action.arguments);
338
+ executionCache.set(cacheKey, observation);
229
339
  }
230
340
  } catch (error) {
231
341
  if (error && typeof error === "object" && "constructor" in error && error.constructor.name === "GraphInterrupt") {
232
342
  throw error;
233
343
  }
234
344
  const errorMessage = error instanceof Error ? error.message : String(error);
345
+ actionLogger.error("Tool execution failed", {
346
+ toolName: action.name,
347
+ error: errorMessage,
348
+ iteration
349
+ });
235
350
  observations.push({
236
351
  toolCallId: action.id,
237
352
  result: null,
238
353
  error: errorMessage,
239
354
  timestamp: Date.now()
240
355
  });
241
- if (verbose) {
242
- console.error(`[action] Tool '${action.name}' failed:`, errorMessage);
243
- }
244
356
  }
245
357
  }
358
+ if (duplicatesSkipped > 0 || toolsExecuted > 0) {
359
+ const metrics = buildDeduplicationMetrics(toolsExecuted, duplicatesSkipped, observations.length);
360
+ actionLogger.info("Action node complete", {
361
+ iteration,
362
+ ...metrics,
363
+ duration: Date.now() - startTime
364
+ });
365
+ }
246
366
  return {
247
367
  observations
248
368
  };
@@ -253,9 +373,11 @@ function createObservationNode(verbose = false) {
253
373
  const observations = state.observations || [];
254
374
  const thoughts = state.thoughts || [];
255
375
  const actions = state.actions || [];
256
- if (verbose) {
257
- console.log(`[observation] Processing ${observations.length} observations`);
258
- }
376
+ const iteration = state.iteration || 0;
377
+ observationLogger.debug("Processing observations", {
378
+ observationCount: observations.length,
379
+ iteration
380
+ });
259
381
  const recentObservations = observations.slice(-10);
260
382
  const currentStep = state.iteration;
261
383
  const latestThought = thoughts[thoughts.length - 1]?.content || "";
@@ -281,6 +403,11 @@ function createObservationNode(verbose = false) {
281
403
  name: latestActions.find((a) => a.id === obs.toolCallId)?.name
282
404
  };
283
405
  });
406
+ observationLogger.debug("Observation node complete", {
407
+ iteration,
408
+ scratchpadUpdated: true,
409
+ messageCount: observationMessages.length
410
+ });
284
411
  return {
285
412
  scratchpad: [scratchpadEntry],
286
413
  messages: observationMessages
@@ -299,7 +426,9 @@ function createReActAgent(config, options) {
299
426
  maxIterations = 10,
300
427
  returnIntermediateSteps = false,
301
428
  stopCondition,
302
- checkpointer
429
+ checkpointer,
430
+ enableDeduplication = true
431
+ // Enable by default
303
432
  } = config;
304
433
  const {
305
434
  verbose = false,
@@ -316,7 +445,7 @@ function createReActAgent(config, options) {
316
445
  maxIterations,
317
446
  verbose
318
447
  );
319
- const actionNode = createActionNode(toolArray, verbose);
448
+ const actionNode = createActionNode(toolArray, verbose, enableDeduplication);
320
449
  const observationNode = createObservationNode(verbose);
321
450
  const shouldContinue = (state) => {
322
451
  if (stopCondition && stopCondition(state)) {
@@ -679,6 +808,9 @@ var REMAINING_STEP_TEMPLATE = `Step {stepNumber}: {description}
679
808
  {dependencies}`;
680
809
 
681
810
  // src/plan-execute/nodes.ts
811
+ var plannerLogger = createPatternLogger("agentforge:patterns:plan-execute:planner");
812
+ var executorLogger = createPatternLogger("agentforge:patterns:plan-execute:executor");
813
+ var replannerLogger = createPatternLogger("agentforge:patterns:plan-execute:replanner");
682
814
  function createPlannerNode(config) {
683
815
  const {
684
816
  model,
@@ -687,7 +819,13 @@ function createPlannerNode(config) {
687
819
  includeToolDescriptions = false
688
820
  } = config;
689
821
  return async (state) => {
822
+ const startTime = Date.now();
690
823
  try {
824
+ plannerLogger.debug("Planning started", {
825
+ input: state.input?.substring(0, 100),
826
+ maxSteps,
827
+ includeToolDescriptions
828
+ });
691
829
  let toolDescriptions = "";
692
830
  if (includeToolDescriptions) {
693
831
  toolDescriptions = "";
@@ -712,6 +850,12 @@ function createPlannerNode(config) {
712
850
  } catch (parseError) {
713
851
  throw new Error(`Failed to parse plan from LLM response: ${parseError}`);
714
852
  }
853
+ plannerLogger.info("Plan created", {
854
+ stepCount: plan.steps.length,
855
+ goal: plan.goal.substring(0, 100),
856
+ confidence: plan.confidence,
857
+ duration: Date.now() - startTime
858
+ });
715
859
  return {
716
860
  plan,
717
861
  status: "executing",
@@ -719,6 +863,10 @@ function createPlannerNode(config) {
719
863
  iteration: 1
720
864
  };
721
865
  } catch (error) {
866
+ plannerLogger.error("Planning failed", {
867
+ error: error instanceof Error ? error.message : String(error),
868
+ duration: Date.now() - startTime
869
+ });
722
870
  return {
723
871
  status: "failed",
724
872
  error: error instanceof Error ? error.message : "Unknown error in planner"
@@ -731,11 +879,18 @@ function createExecutorNode(config) {
731
879
  tools,
732
880
  model,
733
881
  parallel = false,
734
- stepTimeout = 3e4
882
+ stepTimeout = 3e4,
883
+ enableDeduplication = true
735
884
  } = config;
736
885
  return async (state) => {
886
+ const { plan, currentStepIndex = 0, pastSteps = [], iteration = 0 } = state;
737
887
  try {
738
- const { plan, currentStepIndex = 0, pastSteps = [] } = state;
888
+ executorLogger.debug("Executor node executing", {
889
+ currentStepIndex,
890
+ totalSteps: plan?.steps?.length || 0,
891
+ iteration,
892
+ deduplicationEnabled: enableDeduplication
893
+ });
739
894
  if (!plan || !plan.steps || plan.steps.length === 0) {
740
895
  return {
741
896
  status: "completed"
@@ -754,22 +909,67 @@ function createExecutorNode(config) {
754
909
  throw new Error(`Unmet dependencies for step ${currentStep.id}: ${unmetDependencies.join(", ")}`);
755
910
  }
756
911
  }
912
+ const executionCache = /* @__PURE__ */ new Map();
913
+ let cacheSize = 0;
914
+ if (enableDeduplication && currentStep.tool) {
915
+ for (const pastStep of pastSteps) {
916
+ if (pastStep.step.tool) {
917
+ const cacheKey = generateToolCallCacheKey(pastStep.step.tool, pastStep.step.args || {});
918
+ executionCache.set(cacheKey, pastStep);
919
+ cacheSize++;
920
+ }
921
+ }
922
+ if (cacheSize > 0) {
923
+ executorLogger.debug("Deduplication cache built", {
924
+ cacheSize,
925
+ pastStepsCount: pastSteps.length
926
+ });
927
+ }
928
+ }
757
929
  let result;
758
930
  let success = true;
759
931
  let error;
932
+ let isDuplicate = false;
760
933
  try {
761
934
  if (currentStep.tool) {
762
- const tool = tools.find((t) => t.metadata.name === currentStep.tool);
763
- if (!tool) {
764
- throw new Error(`Tool not found: ${currentStep.tool}`);
935
+ if (enableDeduplication) {
936
+ const cacheKey = generateToolCallCacheKey(currentStep.tool, currentStep.args || {});
937
+ const cachedStep = executionCache.get(cacheKey);
938
+ if (cachedStep) {
939
+ isDuplicate = true;
940
+ result = cachedStep.result;
941
+ success = cachedStep.success;
942
+ error = cachedStep.error;
943
+ executorLogger.info("Duplicate step execution prevented", {
944
+ stepId: currentStep.id,
945
+ toolName: currentStep.tool,
946
+ arguments: currentStep.args,
947
+ iteration,
948
+ cacheHit: true
949
+ });
950
+ }
951
+ }
952
+ if (!isDuplicate) {
953
+ const tool = tools.find((t) => t.metadata.name === currentStep.tool);
954
+ if (!tool) {
955
+ throw new Error(`Tool not found: ${currentStep.tool}`);
956
+ }
957
+ const startTime = Date.now();
958
+ const timeoutPromise = new Promise(
959
+ (_, reject) => setTimeout(() => reject(new Error("Step execution timeout")), stepTimeout)
960
+ );
961
+ result = await Promise.race([
962
+ tool.execute(currentStep.args || {}),
963
+ timeoutPromise
964
+ ]);
965
+ const executionTime = Date.now() - startTime;
966
+ executorLogger.debug("Step executed successfully", {
967
+ stepId: currentStep.id,
968
+ toolName: currentStep.tool,
969
+ executionTime,
970
+ iteration
971
+ });
765
972
  }
766
- const timeoutPromise = new Promise(
767
- (_, reject) => setTimeout(() => reject(new Error("Step execution timeout")), stepTimeout)
768
- );
769
- result = await Promise.race([
770
- tool.execute(currentStep.args || {}),
771
- timeoutPromise
772
- ]);
773
973
  } else {
774
974
  result = { message: "Step completed without tool execution" };
775
975
  }
@@ -780,6 +980,12 @@ function createExecutorNode(config) {
780
980
  success = false;
781
981
  error = execError instanceof Error ? execError.message : "Unknown execution error";
782
982
  result = null;
983
+ executorLogger.warn("Step execution failed", {
984
+ stepId: currentStep.id,
985
+ toolName: currentStep.tool,
986
+ error,
987
+ iteration
988
+ });
783
989
  }
784
990
  const completedStep = {
785
991
  step: currentStep,
@@ -788,11 +994,23 @@ function createExecutorNode(config) {
788
994
  error,
789
995
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
790
996
  };
997
+ executorLogger.info("Executor node complete", {
998
+ stepId: currentStep.id,
999
+ stepIndex: currentStepIndex,
1000
+ totalSteps: plan.steps.length,
1001
+ success,
1002
+ isDuplicate,
1003
+ iteration
1004
+ });
791
1005
  return {
792
1006
  pastSteps: [completedStep],
793
1007
  currentStepIndex: currentStepIndex + 1
794
1008
  };
795
1009
  } catch (error) {
1010
+ executorLogger.error("Executor node failed", {
1011
+ error: error instanceof Error ? error.message : "Unknown error",
1012
+ iteration
1013
+ });
796
1014
  return {
797
1015
  status: "failed",
798
1016
  error: error instanceof Error ? error.message : "Unknown error in executor"
@@ -807,11 +1025,18 @@ function createReplannerNode(config) {
807
1025
  systemPrompt = DEFAULT_REPLANNER_SYSTEM_PROMPT
808
1026
  } = config;
809
1027
  return async (state) => {
1028
+ const startTime = Date.now();
810
1029
  try {
811
1030
  const { plan, pastSteps = [], currentStepIndex = 0 } = state;
812
1031
  if (!plan) {
813
1032
  return { status: "failed", error: "No plan available for replanning" };
814
1033
  }
1034
+ replannerLogger.debug("Evaluating replanning", {
1035
+ completedSteps: pastSteps.length,
1036
+ remainingSteps: plan.steps.length - currentStepIndex,
1037
+ successfulSteps: pastSteps.filter((ps) => ps.success).length,
1038
+ failedSteps: pastSteps.filter((ps) => !ps.success).length
1039
+ });
815
1040
  const completedStepsText = pastSteps.map(
816
1041
  (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}`)
817
1042
  ).join("\n\n");
@@ -833,17 +1058,30 @@ function createReplannerNode(config) {
833
1058
  throw new Error(`Failed to parse replan decision from LLM response: ${parseError}`);
834
1059
  }
835
1060
  if (decision.shouldReplan) {
1061
+ replannerLogger.info("Replanning triggered", {
1062
+ reason: decision.reason,
1063
+ newGoal: decision.newGoal?.substring(0, 100),
1064
+ duration: Date.now() - startTime
1065
+ });
836
1066
  return {
837
1067
  status: "planning",
838
1068
  input: decision.newGoal || plan.goal,
839
1069
  iteration: 1
840
1070
  };
841
1071
  } else {
1072
+ replannerLogger.info("Continuing with current plan", {
1073
+ reason: decision.reason,
1074
+ duration: Date.now() - startTime
1075
+ });
842
1076
  return {
843
1077
  status: "executing"
844
1078
  };
845
1079
  }
846
1080
  } catch (error) {
1081
+ replannerLogger.error("Replanning evaluation failed", {
1082
+ error: error instanceof Error ? error.message : String(error),
1083
+ duration: Date.now() - startTime
1084
+ });
847
1085
  return {
848
1086
  status: "failed",
849
1087
  error: error instanceof Error ? error.message : "Unknown error in replanner"
@@ -1221,6 +1459,9 @@ var REVISION_ENTRY_TEMPLATE = `Iteration {iteration}:
1221
1459
 
1222
1460
  // src/reflection/nodes.ts
1223
1461
  import { HumanMessage as HumanMessage3, SystemMessage as SystemMessage3 } from "@langchain/core/messages";
1462
+ var generatorLogger = createPatternLogger("agentforge:patterns:reflection:generator");
1463
+ var reflectorLogger = createPatternLogger("agentforge:patterns:reflection:reflector");
1464
+ var reviserLogger = createPatternLogger("agentforge:patterns:reflection:reviser");
1224
1465
  function createGeneratorNode(config) {
1225
1466
  const {
1226
1467
  model,
@@ -1228,10 +1469,13 @@ function createGeneratorNode(config) {
1228
1469
  verbose = false
1229
1470
  } = config;
1230
1471
  return async (state) => {
1472
+ const startTime = Date.now();
1231
1473
  try {
1232
- if (verbose) {
1233
- console.log("[Generator] Generating initial response...");
1234
- }
1474
+ generatorLogger.debug("Generating response", {
1475
+ attempt: state.iteration + 1,
1476
+ hasFeedback: state.reflections.length > 0,
1477
+ hasExistingResponse: !!state.currentResponse
1478
+ });
1235
1479
  let context = "";
1236
1480
  if (state.iteration > 0 && state.reflections.length > 0) {
1237
1481
  const lastReflection = state.reflections[state.reflections.length - 1];
@@ -1246,16 +1490,23 @@ ${lastReflection.critique}`;
1246
1490
  ];
1247
1491
  const response = await model.invoke(messages);
1248
1492
  const content = typeof response.content === "string" ? response.content : JSON.stringify(response.content);
1249
- if (verbose) {
1250
- console.log("[Generator] Generated response:", content.substring(0, 100) + "...");
1251
- }
1493
+ generatorLogger.info("Response generated", {
1494
+ attempt: state.iteration + 1,
1495
+ responseLength: content.length,
1496
+ isRevision: state.iteration > 0,
1497
+ duration: Date.now() - startTime
1498
+ });
1252
1499
  return {
1253
1500
  currentResponse: content,
1254
1501
  status: "reflecting",
1255
1502
  iteration: 1
1256
1503
  };
1257
1504
  } catch (error) {
1258
- console.error("[Generator] Error:", error);
1505
+ generatorLogger.error("Response generation failed", {
1506
+ attempt: state.iteration + 1,
1507
+ error: error instanceof Error ? error.message : String(error),
1508
+ duration: Date.now() - startTime
1509
+ });
1259
1510
  return {
1260
1511
  status: "failed",
1261
1512
  error: error instanceof Error ? error.message : "Unknown error in generator"
@@ -1271,10 +1522,13 @@ function createReflectorNode(config) {
1271
1522
  verbose = false
1272
1523
  } = config;
1273
1524
  return async (state) => {
1525
+ const startTime = Date.now();
1274
1526
  try {
1275
- if (verbose) {
1276
- console.log("[Reflector] Reflecting on response...");
1277
- }
1527
+ reflectorLogger.debug("Reflecting on response", {
1528
+ attempt: state.iteration,
1529
+ responseLength: state.currentResponse?.length || 0,
1530
+ hasCriteria: !!(qualityCriteria || state.qualityCriteria)
1531
+ });
1278
1532
  if (!state.currentResponse) {
1279
1533
  throw new Error("No current response to reflect on");
1280
1534
  }
@@ -1324,16 +1578,24 @@ function createReflectorNode(config) {
1324
1578
  meetsStandards: false
1325
1579
  };
1326
1580
  }
1327
- if (verbose) {
1328
- console.log("[Reflector] Reflection score:", reflection.score);
1329
- console.log("[Reflector] Meets standards:", reflection.meetsStandards);
1330
- }
1581
+ reflectorLogger.info("Reflection complete", {
1582
+ attempt: state.iteration,
1583
+ score: reflection.score,
1584
+ meetsStandards: reflection.meetsStandards,
1585
+ issueCount: reflection.issues.length,
1586
+ suggestionCount: reflection.suggestions.length,
1587
+ duration: Date.now() - startTime
1588
+ });
1331
1589
  return {
1332
1590
  reflections: [reflection],
1333
1591
  status: reflection.meetsStandards ? "completed" : "revising"
1334
1592
  };
1335
1593
  } catch (error) {
1336
- console.error("[Reflector] Error:", error);
1594
+ reflectorLogger.error("Reflection failed", {
1595
+ attempt: state.iteration,
1596
+ error: error instanceof Error ? error.message : String(error),
1597
+ duration: Date.now() - startTime
1598
+ });
1337
1599
  return {
1338
1600
  status: "failed",
1339
1601
  error: error instanceof Error ? error.message : "Unknown error in reflector"
@@ -1348,10 +1610,8 @@ function createReviserNode(config) {
1348
1610
  verbose = false
1349
1611
  } = config;
1350
1612
  return async (state) => {
1613
+ const startTime = Date.now();
1351
1614
  try {
1352
- if (verbose) {
1353
- console.log("[Reviser] Revising response...");
1354
- }
1355
1615
  if (!state.currentResponse) {
1356
1616
  throw new Error("No current response to revise");
1357
1617
  }
@@ -1359,6 +1619,12 @@ function createReviserNode(config) {
1359
1619
  throw new Error("No reflections to base revision on");
1360
1620
  }
1361
1621
  const lastReflection = state.reflections[state.reflections.length - 1];
1622
+ reviserLogger.debug("Revising response", {
1623
+ attempt: state.iteration,
1624
+ previousScore: lastReflection.score,
1625
+ issueCount: lastReflection.issues.length,
1626
+ suggestionCount: lastReflection.suggestions.length
1627
+ });
1362
1628
  let historySection = "";
1363
1629
  if (state.revisions.length > 0) {
1364
1630
  const revisionsText = state.revisions.map(
@@ -1375,14 +1641,17 @@ ${revisionsText}`;
1375
1641
  ];
1376
1642
  const response = await model.invoke(messages);
1377
1643
  const content = typeof response.content === "string" ? response.content : JSON.stringify(response.content);
1378
- if (verbose) {
1379
- console.log("[Reviser] Created revision:", content.substring(0, 100) + "...");
1380
- }
1381
1644
  const revision = {
1382
1645
  content,
1383
1646
  iteration: state.iteration,
1384
1647
  basedOn: lastReflection
1385
1648
  };
1649
+ reviserLogger.info("Revision complete", {
1650
+ attempt: state.iteration,
1651
+ revisionLength: content.length,
1652
+ basedOnScore: lastReflection.score,
1653
+ duration: Date.now() - startTime
1654
+ });
1386
1655
  return {
1387
1656
  currentResponse: content,
1388
1657
  revisions: [revision],
@@ -1390,7 +1659,11 @@ ${revisionsText}`;
1390
1659
  iteration: 1
1391
1660
  };
1392
1661
  } catch (error) {
1393
- console.error("[Reviser] Error:", error);
1662
+ reviserLogger.error("Revision failed", {
1663
+ attempt: state.iteration,
1664
+ error: error instanceof Error ? error.message : String(error),
1665
+ duration: Date.now() - startTime
1666
+ });
1394
1667
  return {
1395
1668
  status: "failed",
1396
1669
  error: error instanceof Error ? error.message : "Unknown error in reviser"
@@ -1543,26 +1816,35 @@ var RoutingStrategySchema = z7.enum([
1543
1816
  ]);
1544
1817
  var RoutingDecisionSchema = z7.object({
1545
1818
  /**
1546
- * Target agent to route to
1819
+ * Target agent to route to (single agent routing)
1820
+ * @deprecated Use targetAgents for parallel routing support
1821
+ */
1822
+ targetAgent: z7.string().nullable().default(null).describe("Agent to route the task to (single routing)"),
1823
+ /**
1824
+ * Target agents to route to (parallel routing)
1825
+ * When multiple agents are specified, they execute in parallel
1547
1826
  */
1548
- targetAgent: z7.string().describe("Agent to route the task to"),
1827
+ targetAgents: z7.array(z7.string()).nullable().default(null).describe("Agents to route the task to (parallel routing)"),
1549
1828
  /**
1550
1829
  * Reasoning for the routing decision
1551
1830
  */
1552
- reasoning: z7.string().optional().describe("Explanation for routing decision"),
1831
+ reasoning: z7.string().default("").describe("Explanation for routing decision"),
1553
1832
  /**
1554
1833
  * Confidence in the routing decision (0-1)
1555
1834
  */
1556
- confidence: z7.number().min(0).max(1).optional().describe("Confidence score"),
1835
+ confidence: z7.number().min(0).max(1).default(0.8).describe("Confidence score"),
1557
1836
  /**
1558
1837
  * Strategy used for routing
1559
1838
  */
1560
- strategy: RoutingStrategySchema.describe("Strategy used for this decision"),
1839
+ strategy: RoutingStrategySchema.default("llm-based").describe("Strategy used for this decision"),
1561
1840
  /**
1562
1841
  * Timestamp of the routing decision
1563
1842
  */
1564
- timestamp: z7.number().optional().describe("Timestamp of the decision")
1565
- });
1843
+ timestamp: z7.number().default(() => Date.now()).describe("Timestamp of the decision")
1844
+ }).refine(
1845
+ (data) => data.targetAgent || data.targetAgents && data.targetAgents.length > 0,
1846
+ { message: "Either targetAgent or targetAgents must be provided" }
1847
+ );
1566
1848
  var WorkerCapabilitiesSchema = z7.object({
1567
1849
  /**
1568
1850
  * Skills/capabilities the agent has
@@ -1798,11 +2080,22 @@ var MultiAgentState = createStateAnnotation4(MultiAgentStateConfig);
1798
2080
 
1799
2081
  // src/multi-agent/routing.ts
1800
2082
  import { HumanMessage as HumanMessage4, SystemMessage as SystemMessage4, AIMessage as AIMessage2, ToolMessage as ToolMessage2 } from "@langchain/core/messages";
2083
+ import { createLogger as createLogger2, LogLevel } from "@agentforge/core";
2084
+ var logLevel = process.env.LOG_LEVEL?.toLowerCase() || LogLevel.INFO;
2085
+ var logger = createLogger2("multi-agent:routing", { level: logLevel });
1801
2086
  async function executeTools(toolCalls, tools) {
1802
2087
  const results = [];
2088
+ logger.debug("Executing tools", {
2089
+ toolCallCount: toolCalls.length,
2090
+ toolNames: toolCalls.map((tc) => tc.name)
2091
+ });
1803
2092
  for (const toolCall of toolCalls) {
1804
2093
  const tool = tools.find((t) => t.metadata.name === toolCall.name);
1805
2094
  if (!tool) {
2095
+ logger.warn("Tool not found", {
2096
+ toolName: toolCall.name,
2097
+ availableTools: tools.map((t) => t.metadata.name)
2098
+ });
1806
2099
  results.push(new ToolMessage2({
1807
2100
  content: `Error: Tool '${toolCall.name}' not found`,
1808
2101
  tool_call_id: toolCall.id
@@ -1810,19 +2103,41 @@ async function executeTools(toolCalls, tools) {
1810
2103
  continue;
1811
2104
  }
1812
2105
  try {
2106
+ logger.debug("Executing tool", {
2107
+ toolName: toolCall.name,
2108
+ args: toolCall.args
2109
+ });
1813
2110
  const result = await tool.execute(toolCall.args);
1814
2111
  const content = typeof result === "string" ? result : JSON.stringify(result);
2112
+ logger.debug("Tool execution successful", {
2113
+ toolName: toolCall.name,
2114
+ resultLength: content.length
2115
+ });
1815
2116
  results.push(new ToolMessage2({
1816
2117
  content,
1817
2118
  tool_call_id: toolCall.id
1818
2119
  }));
1819
2120
  } catch (error) {
2121
+ logger.error("Tool execution failed", {
2122
+ toolName: toolCall.name,
2123
+ error: error.message
2124
+ });
1820
2125
  results.push(new ToolMessage2({
1821
2126
  content: `Error executing tool: ${error.message}`,
1822
2127
  tool_call_id: toolCall.id
1823
2128
  }));
1824
2129
  }
1825
2130
  }
2131
+ logger.debug("Tool execution complete", {
2132
+ successCount: results.filter((r) => {
2133
+ const content = typeof r.content === "string" ? r.content : JSON.stringify(r.content);
2134
+ return !content.startsWith("Error");
2135
+ }).length,
2136
+ errorCount: results.filter((r) => {
2137
+ const content = typeof r.content === "string" ? r.content : JSON.stringify(r.content);
2138
+ return content.startsWith("Error");
2139
+ }).length
2140
+ });
1826
2141
  return results;
1827
2142
  }
1828
2143
  var DEFAULT_SUPERVISOR_SYSTEM_PROMPT = `You are a supervisor agent responsible for routing tasks to specialized worker agents.
@@ -1830,19 +2145,40 @@ var DEFAULT_SUPERVISOR_SYSTEM_PROMPT = `You are a supervisor agent responsible f
1830
2145
  Your job is to:
1831
2146
  1. Analyze the current task and context
1832
2147
  2. Review available worker capabilities
1833
- 3. Select the most appropriate worker for the task
2148
+ 3. Select the most appropriate worker(s) for the task
1834
2149
  4. Provide clear reasoning for your decision
1835
2150
 
1836
- Respond with a JSON object containing:
2151
+ **IMPORTANT: You can route to MULTIPLE workers for parallel execution when:**
2152
+ - The task requires information from multiple domains (e.g., code + documentation)
2153
+ - Multiple workers have complementary expertise
2154
+ - Parallel execution would provide a more comprehensive answer
2155
+
2156
+ **Response Format:**
2157
+
2158
+ For SINGLE worker routing:
1837
2159
  {
1838
2160
  "targetAgent": "worker_id",
1839
2161
  "reasoning": "explanation of why this worker is best suited",
1840
2162
  "confidence": 0.0-1.0,
1841
2163
  "strategy": "llm-based"
1842
- }`;
2164
+ }
2165
+
2166
+ For PARALLEL multi-worker routing:
2167
+ {
2168
+ "targetAgents": ["worker_id_1", "worker_id_2", ...],
2169
+ "reasoning": "explanation of why these workers should work in parallel",
2170
+ "confidence": 0.0-1.0,
2171
+ "strategy": "llm-based"
2172
+ }
2173
+
2174
+ Choose parallel routing when the task benefits from multiple perspectives or data sources.`;
1843
2175
  var llmBasedRouting = {
1844
2176
  name: "llm-based",
1845
2177
  async route(state, config) {
2178
+ logger.info("Starting LLM-based routing", {
2179
+ iteration: state.iteration,
2180
+ availableWorkers: Object.keys(state.workers).length
2181
+ });
1846
2182
  if (!config.model) {
1847
2183
  throw new Error("LLM-based routing requires a model to be configured");
1848
2184
  }
@@ -1855,17 +2191,34 @@ var llmBasedRouting = {
1855
2191
  const available = caps.available ? "available" : "busy";
1856
2192
  return `- ${id}: Skills: [${skills}], Tools: [${tools2}], Status: ${available}, Workload: ${caps.currentWorkload}`;
1857
2193
  }).join("\n");
2194
+ logger.debug("Worker capabilities", {
2195
+ workers: Object.entries(state.workers).map(([id, caps]) => ({
2196
+ id,
2197
+ skills: caps.skills,
2198
+ available: caps.available,
2199
+ workload: caps.currentWorkload
2200
+ }))
2201
+ });
1858
2202
  const lastMessage = state.messages[state.messages.length - 1];
1859
2203
  const taskContext = lastMessage?.content || state.input;
2204
+ logger.debug("Task context", {
2205
+ taskLength: taskContext.length,
2206
+ taskPreview: taskContext.substring(0, 100)
2207
+ });
1860
2208
  const userPrompt = `Current task: ${taskContext}
1861
2209
 
1862
2210
  Available workers:
1863
2211
  ${workerInfo}
1864
2212
 
1865
- Select the best worker for this task and explain your reasoning.`;
2213
+ Select the best worker(s) for this task and explain your reasoning.`;
1866
2214
  const conversationHistory = [];
1867
2215
  let attempt = 0;
1868
2216
  while (attempt < maxRetries) {
2217
+ logger.debug("LLM routing attempt", {
2218
+ attempt: attempt + 1,
2219
+ maxRetries,
2220
+ conversationHistoryLength: conversationHistory.length
2221
+ });
1869
2222
  const messages = [
1870
2223
  new SystemMessage4(systemPrompt),
1871
2224
  new HumanMessage4(userPrompt),
@@ -1873,6 +2226,10 @@ Select the best worker for this task and explain your reasoning.`;
1873
2226
  ];
1874
2227
  const response = await config.model.invoke(messages);
1875
2228
  if (response.tool_calls && response.tool_calls.length > 0) {
2229
+ logger.info("LLM requested tool calls", {
2230
+ toolCount: response.tool_calls.length,
2231
+ toolNames: response.tool_calls.map((tc) => tc.name)
2232
+ });
1876
2233
  if (tools.length === 0) {
1877
2234
  throw new Error("LLM requested tool calls but no tools are configured");
1878
2235
  }
@@ -1882,36 +2239,90 @@ Select the best worker for this task and explain your reasoning.`;
1882
2239
  ...toolResults
1883
2240
  );
1884
2241
  attempt++;
2242
+ logger.debug("Retrying routing with tool results", { attempt });
1885
2243
  continue;
1886
2244
  }
1887
- const content = typeof response.content === "string" ? response.content : JSON.stringify(response.content);
1888
- try {
1889
- const decision = JSON.parse(content);
1890
- return {
1891
- targetAgent: decision.targetAgent,
1892
- reasoning: decision.reasoning,
1893
- confidence: decision.confidence,
1894
- strategy: "llm-based",
1895
- timestamp: Date.now()
1896
- };
1897
- } catch (error) {
1898
- throw new Error(`Failed to parse routing decision from LLM: ${error}`);
2245
+ logger.debug("Parsing routing decision from LLM response");
2246
+ let decision;
2247
+ if (response && typeof response === "object" && ("targetAgent" in response || "targetAgents" in response)) {
2248
+ logger.debug("Response is structured output", {
2249
+ hasTargetAgent: "targetAgent" in response,
2250
+ hasTargetAgents: "targetAgents" in response
2251
+ });
2252
+ decision = response;
2253
+ } else if (response.content) {
2254
+ if (typeof response.content === "string") {
2255
+ try {
2256
+ decision = JSON.parse(response.content);
2257
+ logger.debug("Parsed JSON from string response");
2258
+ } catch (error) {
2259
+ logger.error("Failed to parse routing decision", {
2260
+ content: response.content,
2261
+ error: error instanceof Error ? error.message : String(error)
2262
+ });
2263
+ throw new Error(`Failed to parse routing decision from LLM. Expected JSON but got: ${response.content}`);
2264
+ }
2265
+ } else if (typeof response.content === "object") {
2266
+ logger.debug("Response content is already an object");
2267
+ decision = response.content;
2268
+ } else {
2269
+ logger.error("Unexpected response content type", {
2270
+ type: typeof response.content
2271
+ });
2272
+ throw new Error(`Unexpected response content type: ${typeof response.content}`);
2273
+ }
2274
+ } else {
2275
+ logger.error("Unexpected response format", {
2276
+ response: JSON.stringify(response)
2277
+ });
2278
+ throw new Error(`Unexpected response format: ${JSON.stringify(response)}`);
1899
2279
  }
2280
+ const result = {
2281
+ targetAgent: decision.targetAgent,
2282
+ targetAgents: decision.targetAgents,
2283
+ reasoning: decision.reasoning,
2284
+ confidence: decision.confidence,
2285
+ strategy: "llm-based",
2286
+ timestamp: Date.now()
2287
+ };
2288
+ logger.info("LLM routing decision made", {
2289
+ targetAgent: result.targetAgent,
2290
+ targetAgents: result.targetAgents,
2291
+ isParallel: result.targetAgents && result.targetAgents.length > 1,
2292
+ confidence: result.confidence,
2293
+ reasoning: result.reasoning
2294
+ });
2295
+ return result;
1900
2296
  }
2297
+ logger.error("Max tool retries exceeded", { maxRetries });
1901
2298
  throw new Error(`Max tool retries (${maxRetries}) exceeded without routing decision`);
1902
2299
  }
1903
2300
  };
1904
2301
  var roundRobinRouting = {
1905
2302
  name: "round-robin",
1906
2303
  async route(state, config) {
2304
+ logger.info("Starting round-robin routing", {
2305
+ iteration: state.iteration
2306
+ });
1907
2307
  const availableWorkers = Object.entries(state.workers).filter(([_, caps]) => caps.available).map(([id]) => id);
2308
+ logger.debug("Available workers for round-robin", {
2309
+ count: availableWorkers.length,
2310
+ workers: availableWorkers
2311
+ });
1908
2312
  if (availableWorkers.length === 0) {
2313
+ logger.error("No available workers for round-robin routing");
1909
2314
  throw new Error("No available workers for round-robin routing");
1910
2315
  }
1911
2316
  const lastRoutingIndex = state.routingHistory.length % availableWorkers.length;
1912
2317
  const targetAgent = availableWorkers[lastRoutingIndex];
2318
+ logger.info("Round-robin routing decision", {
2319
+ targetAgent,
2320
+ index: lastRoutingIndex + 1,
2321
+ totalWorkers: availableWorkers.length
2322
+ });
1913
2323
  return {
1914
2324
  targetAgent,
2325
+ targetAgents: null,
1915
2326
  reasoning: `Round-robin selection: worker ${lastRoutingIndex + 1} of ${availableWorkers.length}`,
1916
2327
  confidence: 1,
1917
2328
  strategy: "round-robin",
@@ -1922,8 +2333,15 @@ var roundRobinRouting = {
1922
2333
  var skillBasedRouting = {
1923
2334
  name: "skill-based",
1924
2335
  async route(state, config) {
2336
+ logger.info("Starting skill-based routing", {
2337
+ iteration: state.iteration
2338
+ });
1925
2339
  const lastMessage = state.messages[state.messages.length - 1];
1926
2340
  const taskContent = (lastMessage?.content || state.input).toLowerCase();
2341
+ logger.debug("Task content for skill matching", {
2342
+ taskLength: taskContent.length,
2343
+ taskPreview: taskContent.substring(0, 100)
2344
+ });
1927
2345
  const workerScores = Object.entries(state.workers).filter(([_, caps]) => caps.available).map(([id, caps]) => {
1928
2346
  const skillMatches = caps.skills.filter(
1929
2347
  (skill) => taskContent.includes(skill.toLowerCase())
@@ -1934,13 +2352,23 @@ var skillBasedRouting = {
1934
2352
  const score = skillMatches * 2 + toolMatches;
1935
2353
  return { id, score, skills: caps.skills, tools: caps.tools };
1936
2354
  }).filter((w) => w.score > 0).sort((a, b) => b.score - a.score);
2355
+ logger.debug("Worker skill scores", {
2356
+ scoredWorkers: workerScores.map((w) => ({ id: w.id, score: w.score }))
2357
+ });
1937
2358
  if (workerScores.length === 0) {
2359
+ logger.warn("No skill matches found, using fallback");
1938
2360
  const firstAvailable = Object.entries(state.workers).find(([_, caps]) => caps.available);
1939
2361
  if (!firstAvailable) {
2362
+ logger.error("No available workers for skill-based routing");
1940
2363
  throw new Error("No available workers for skill-based routing");
1941
2364
  }
2365
+ logger.info("Skill-based routing fallback decision", {
2366
+ targetAgent: firstAvailable[0],
2367
+ confidence: 0.5
2368
+ });
1942
2369
  return {
1943
2370
  targetAgent: firstAvailable[0],
2371
+ targetAgents: null,
1944
2372
  reasoning: "No skill matches found, using first available worker",
1945
2373
  confidence: 0.5,
1946
2374
  strategy: "skill-based",
@@ -1949,8 +2377,15 @@ var skillBasedRouting = {
1949
2377
  }
1950
2378
  const best = workerScores[0];
1951
2379
  const confidence = Math.min(best.score / 5, 1);
2380
+ logger.info("Skill-based routing decision", {
2381
+ targetAgent: best.id,
2382
+ score: best.score,
2383
+ confidence,
2384
+ matchedSkills: best.skills
2385
+ });
1952
2386
  return {
1953
2387
  targetAgent: best.id,
2388
+ targetAgents: null,
1954
2389
  reasoning: `Best skill match with score ${best.score} (skills: ${best.skills.join(", ")})`,
1955
2390
  confidence,
1956
2391
  strategy: "skill-based",
@@ -1961,15 +2396,29 @@ var skillBasedRouting = {
1961
2396
  var loadBalancedRouting = {
1962
2397
  name: "load-balanced",
1963
2398
  async route(state, config) {
2399
+ logger.info("Starting load-balanced routing", {
2400
+ iteration: state.iteration
2401
+ });
1964
2402
  const availableWorkers = Object.entries(state.workers).filter(([_, caps]) => caps.available).map(([id, caps]) => ({ id, workload: caps.currentWorkload })).sort((a, b) => a.workload - b.workload);
2403
+ logger.debug("Worker workloads", {
2404
+ workers: availableWorkers.map((w) => ({ id: w.id, workload: w.workload }))
2405
+ });
1965
2406
  if (availableWorkers.length === 0) {
2407
+ logger.error("No available workers for load-balanced routing");
1966
2408
  throw new Error("No available workers for load-balanced routing");
1967
2409
  }
1968
2410
  const targetWorker = availableWorkers[0];
1969
2411
  const avgWorkload = availableWorkers.reduce((sum, w) => sum + w.workload, 0) / availableWorkers.length;
1970
2412
  const confidence = targetWorker.workload === 0 ? 1 : Math.max(0.5, 1 - targetWorker.workload / (avgWorkload * 2));
2413
+ logger.info("Load-balanced routing decision", {
2414
+ targetAgent: targetWorker.id,
2415
+ workload: targetWorker.workload,
2416
+ avgWorkload: avgWorkload.toFixed(1),
2417
+ confidence
2418
+ });
1971
2419
  return {
1972
2420
  targetAgent: targetWorker.id,
2421
+ targetAgents: null,
1973
2422
  reasoning: `Lowest workload: ${targetWorker.workload} tasks (avg: ${avgWorkload.toFixed(1)})`,
1974
2423
  confidence,
1975
2424
  strategy: "load-balanced",
@@ -1980,13 +2429,24 @@ var loadBalancedRouting = {
1980
2429
  var ruleBasedRouting = {
1981
2430
  name: "rule-based",
1982
2431
  async route(state, config) {
2432
+ logger.info("Starting rule-based routing", {
2433
+ iteration: state.iteration
2434
+ });
1983
2435
  if (!config.routingFn) {
2436
+ logger.error("Rule-based routing requires a custom routing function");
1984
2437
  throw new Error("Rule-based routing requires a custom routing function");
1985
2438
  }
1986
- return await config.routingFn(state);
2439
+ const decision = await config.routingFn(state);
2440
+ logger.info("Rule-based routing decision", {
2441
+ targetAgent: decision.targetAgent,
2442
+ targetAgents: decision.targetAgents,
2443
+ confidence: decision.confidence
2444
+ });
2445
+ return decision;
1987
2446
  }
1988
2447
  };
1989
2448
  function getRoutingStrategy(name) {
2449
+ logger.debug("Getting routing strategy", { name });
1990
2450
  switch (name) {
1991
2451
  case "llm-based":
1992
2452
  return llmBasedRouting;
@@ -1999,14 +2459,15 @@ function getRoutingStrategy(name) {
1999
2459
  case "rule-based":
2000
2460
  return ruleBasedRouting;
2001
2461
  default:
2462
+ logger.error("Unknown routing strategy", { name });
2002
2463
  throw new Error(`Unknown routing strategy: ${name}`);
2003
2464
  }
2004
2465
  }
2005
2466
 
2006
2467
  // src/multi-agent/utils.ts
2007
- import { createLogger, LogLevel } from "@agentforge/core";
2008
- var logLevel = process.env.LOG_LEVEL?.toLowerCase() || LogLevel.INFO;
2009
- var logger = createLogger("multi-agent", { level: logLevel });
2468
+ import { createLogger as createLogger3, LogLevel as LogLevel2 } from "@agentforge/core";
2469
+ var logLevel2 = process.env.LOG_LEVEL?.toLowerCase() || LogLevel2.INFO;
2470
+ var logger2 = createLogger3("multi-agent", { level: logLevel2 });
2010
2471
  function isReActAgent(obj) {
2011
2472
  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
2012
2473
  (obj.constructor?.name === "CompiledGraph" || obj.constructor?.name === "CompiledStateGraph");
@@ -2014,9 +2475,9 @@ function isReActAgent(obj) {
2014
2475
  function wrapReActAgent(workerId, agent, verbose = false) {
2015
2476
  return async (state, config) => {
2016
2477
  try {
2017
- logger.debug("Wrapping ReAct agent execution", { workerId });
2478
+ logger2.debug("Wrapping ReAct agent execution", { workerId });
2018
2479
  const task = state.messages[state.messages.length - 1]?.content || state.input;
2019
- logger.debug("Extracted task", {
2480
+ logger2.debug("Extracted task", {
2020
2481
  workerId,
2021
2482
  taskPreview: task.substring(0, 100) + (task.length > 100 ? "..." : "")
2022
2483
  });
@@ -2024,11 +2485,8 @@ function wrapReActAgent(workerId, agent, verbose = false) {
2024
2485
  (assignment) => assignment.workerId === workerId && !state.completedTasks.some((task2) => task2.assignmentId === assignment.id)
2025
2486
  );
2026
2487
  if (!currentAssignment) {
2027
- logger.debug("No active assignment found", { workerId });
2028
- return {
2029
- currentAgent: "supervisor",
2030
- status: "routing"
2031
- };
2488
+ logger2.debug("No active assignment found", { workerId });
2489
+ return {};
2032
2490
  }
2033
2491
  const result = await agent.invoke(
2034
2492
  {
@@ -2038,14 +2496,14 @@ function wrapReActAgent(workerId, agent, verbose = false) {
2038
2496
  // Pass through the config for checkpointing and interrupt support
2039
2497
  );
2040
2498
  const response = result.messages?.[result.messages.length - 1]?.content || "No response";
2041
- logger.debug("Received response from ReAct agent", {
2499
+ logger2.debug("Received response from ReAct agent", {
2042
2500
  workerId,
2043
2501
  responsePreview: response.substring(0, 100) + (response.length > 100 ? "..." : "")
2044
2502
  });
2045
2503
  const toolsUsed = result.actions?.map((action) => action.name).filter(Boolean) || [];
2046
2504
  const uniqueTools = [...new Set(toolsUsed)];
2047
2505
  if (uniqueTools.length > 0) {
2048
- logger.debug("Tools used by ReAct agent", { workerId, tools: uniqueTools });
2506
+ logger2.debug("Tools used by ReAct agent", { workerId, tools: uniqueTools });
2049
2507
  }
2050
2508
  const taskResult = {
2051
2509
  assignmentId: currentAssignment.id,
@@ -2060,16 +2518,14 @@ function wrapReActAgent(workerId, agent, verbose = false) {
2060
2518
  }
2061
2519
  };
2062
2520
  return {
2063
- completedTasks: [taskResult],
2064
- currentAgent: "supervisor",
2065
- status: "routing"
2521
+ completedTasks: [taskResult]
2066
2522
  };
2067
2523
  } catch (error) {
2068
2524
  if (error && typeof error === "object" && "constructor" in error && error.constructor.name === "GraphInterrupt") {
2069
- logger.debug("GraphInterrupt detected - re-throwing", { workerId });
2525
+ logger2.debug("GraphInterrupt detected - re-throwing", { workerId });
2070
2526
  throw error;
2071
2527
  }
2072
- logger.error("Error in ReAct agent execution", {
2528
+ logger2.error("Error in ReAct agent execution", {
2073
2529
  workerId,
2074
2530
  error: error instanceof Error ? error.message : String(error),
2075
2531
  stack: error instanceof Error ? error.stack : void 0
@@ -2102,7 +2558,9 @@ function wrapReActAgent(workerId, agent, verbose = false) {
2102
2558
 
2103
2559
  // src/multi-agent/nodes.ts
2104
2560
  import { HumanMessage as HumanMessage5, SystemMessage as SystemMessage5 } from "@langchain/core/messages";
2105
- import { toLangChainTools as toLangChainTools2 } from "@agentforge/core";
2561
+ import { toLangChainTools as toLangChainTools2, createLogger as createLogger4, LogLevel as LogLevel3 } from "@agentforge/core";
2562
+ var logLevel3 = process.env.LOG_LEVEL?.toLowerCase() || LogLevel3.INFO;
2563
+ var logger3 = createLogger4("multi-agent:nodes", { level: logLevel3 });
2106
2564
  var DEFAULT_AGGREGATOR_SYSTEM_PROMPT = `You are an aggregator agent responsible for combining results from multiple worker agents.
2107
2565
 
2108
2566
  Your job is to:
@@ -2120,46 +2578,97 @@ function createSupervisorNode(config) {
2120
2578
  } = config;
2121
2579
  return async (state) => {
2122
2580
  try {
2123
- if (verbose) {
2124
- console.log(`[Supervisor] Routing iteration ${state.iteration}/${maxIterations}`);
2125
- }
2581
+ logger3.info("Supervisor node executing", {
2582
+ iteration: state.iteration,
2583
+ maxIterations,
2584
+ activeAssignments: state.activeAssignments.length,
2585
+ completedTasks: state.completedTasks.length
2586
+ });
2587
+ logger3.debug(`Routing iteration ${state.iteration}/${maxIterations}`);
2126
2588
  if (state.iteration >= maxIterations) {
2127
- if (verbose) {
2128
- console.log("[Supervisor] Max iterations reached, moving to aggregation");
2129
- }
2589
+ logger3.warn("Max iterations reached", {
2590
+ iteration: state.iteration,
2591
+ maxIterations
2592
+ });
2593
+ logger3.debug("Max iterations reached, moving to aggregation");
2130
2594
  return {
2131
2595
  status: "aggregating",
2132
2596
  currentAgent: "aggregator"
2133
2597
  };
2134
2598
  }
2135
2599
  const allCompleted = state.activeAssignments.every(
2136
- (assignment2) => state.completedTasks.some((task) => task.assignmentId === assignment2.id)
2600
+ (assignment) => state.completedTasks.some((task2) => task2.assignmentId === assignment.id)
2137
2601
  );
2602
+ logger3.debug("Checking task completion", {
2603
+ activeAssignments: state.activeAssignments.length,
2604
+ completedTasks: state.completedTasks.length,
2605
+ allCompleted
2606
+ });
2138
2607
  if (allCompleted && state.activeAssignments.length > 0) {
2139
- if (verbose) {
2140
- console.log("[Supervisor] All tasks completed, moving to aggregation");
2141
- }
2608
+ logger3.info("All tasks completed, moving to aggregation", {
2609
+ completedCount: state.completedTasks.length
2610
+ });
2611
+ logger3.debug("All tasks completed, moving to aggregation");
2142
2612
  return {
2143
2613
  status: "aggregating",
2144
2614
  currentAgent: "aggregator"
2145
2615
  };
2146
2616
  }
2617
+ logger3.debug("Getting routing strategy", { strategy });
2147
2618
  const routingImpl = getRoutingStrategy(strategy);
2148
2619
  const decision = await routingImpl.route(state, config);
2149
- if (verbose) {
2150
- console.log(`[Supervisor] Routing to ${decision.targetAgent}: ${decision.reasoning}`);
2620
+ const targetAgents = decision.targetAgents && decision.targetAgents.length > 0 ? decision.targetAgents : decision.targetAgent ? [decision.targetAgent] : [];
2621
+ logger3.debug("Target agents determined", {
2622
+ targetAgents,
2623
+ isParallel: targetAgents.length > 1,
2624
+ decision: {
2625
+ reasoning: decision.reasoning,
2626
+ confidence: decision.confidence
2627
+ }
2628
+ });
2629
+ if (targetAgents.length === 0) {
2630
+ logger3.error("No target agents specified in routing decision");
2631
+ throw new Error("Routing decision must specify at least one target agent");
2151
2632
  }
2152
- const assignment = {
2633
+ if (targetAgents.length === 1) {
2634
+ logger3.info("Routing to single agent", {
2635
+ targetAgent: targetAgents[0],
2636
+ reasoning: decision.reasoning,
2637
+ confidence: decision.confidence
2638
+ });
2639
+ } else {
2640
+ logger3.info("Routing to multiple agents in parallel", {
2641
+ targetAgents,
2642
+ count: targetAgents.length,
2643
+ reasoning: decision.reasoning,
2644
+ confidence: decision.confidence
2645
+ });
2646
+ }
2647
+ if (targetAgents.length === 1) {
2648
+ logger3.debug(`Routing to ${targetAgents[0]}: ${decision.reasoning}`);
2649
+ } else {
2650
+ logger3.debug(`Routing to ${targetAgents.length} agents in parallel [${targetAgents.join(", ")}]: ${decision.reasoning}`);
2651
+ }
2652
+ const task = state.messages[state.messages.length - 1]?.content || state.input;
2653
+ const assignments = targetAgents.map((workerId) => ({
2153
2654
  id: `task_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
2154
- workerId: decision.targetAgent,
2155
- task: state.messages[state.messages.length - 1]?.content || state.input,
2655
+ workerId,
2656
+ task,
2156
2657
  priority: 5,
2157
2658
  assignedAt: Date.now()
2158
- };
2159
- const message = {
2659
+ }));
2660
+ logger3.debug("Created task assignments", {
2661
+ assignmentCount: assignments.length,
2662
+ assignments: assignments.map((a) => ({
2663
+ id: a.id,
2664
+ workerId: a.workerId,
2665
+ taskLength: a.task.length
2666
+ }))
2667
+ });
2668
+ const messages = assignments.map((assignment) => ({
2160
2669
  id: `msg-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
2161
2670
  from: "supervisor",
2162
- to: [decision.targetAgent],
2671
+ to: [assignment.workerId],
2163
2672
  type: "task_assignment",
2164
2673
  content: assignment.task,
2165
2674
  timestamp: Date.now(),
@@ -2167,17 +2676,29 @@ function createSupervisorNode(config) {
2167
2676
  assignmentId: assignment.id,
2168
2677
  priority: assignment.priority
2169
2678
  }
2170
- };
2679
+ }));
2680
+ logger3.info("Supervisor routing complete", {
2681
+ currentAgent: targetAgents.join(","),
2682
+ status: "executing",
2683
+ assignmentCount: assignments.length,
2684
+ nextIteration: state.iteration + 1
2685
+ });
2171
2686
  return {
2172
- currentAgent: decision.targetAgent,
2687
+ currentAgent: targetAgents.join(","),
2688
+ // Store all agents (for backward compat)
2173
2689
  status: "executing",
2174
2690
  routingHistory: [decision],
2175
- activeAssignments: [assignment],
2176
- messages: [message],
2691
+ activeAssignments: assignments,
2692
+ // Multiple assignments for parallel execution!
2693
+ messages,
2177
2694
  iteration: state.iteration + 1
2178
2695
  };
2179
2696
  } catch (error) {
2180
- console.error("[Supervisor] Error:", error);
2697
+ logger3.error("Supervisor node error", {
2698
+ error: error instanceof Error ? error.message : String(error),
2699
+ stack: error instanceof Error ? error.stack : void 0,
2700
+ iteration: state.iteration
2701
+ });
2181
2702
  return {
2182
2703
  status: "failed",
2183
2704
  error: error instanceof Error ? error.message : "Unknown error in supervisor"
@@ -2198,43 +2719,52 @@ function createWorkerNode(config) {
2198
2719
  } = config;
2199
2720
  return async (state, runConfig) => {
2200
2721
  try {
2201
- if (verbose) {
2202
- console.log(`[Worker:${id}] Executing task`);
2203
- }
2722
+ logger3.info("Worker node executing", {
2723
+ workerId: id,
2724
+ iteration: state.iteration,
2725
+ activeAssignments: state.activeAssignments.length
2726
+ });
2204
2727
  const currentAssignment = state.activeAssignments.find(
2205
2728
  (assignment) => assignment.workerId === id && !state.completedTasks.some((task) => task.assignmentId === assignment.id)
2206
2729
  );
2207
2730
  if (!currentAssignment) {
2208
- if (verbose) {
2209
- console.log(`[Worker:${id}] No active assignment found`);
2210
- }
2211
- return {
2212
- currentAgent: "supervisor",
2213
- status: "routing"
2214
- };
2731
+ logger3.debug("No active assignment found for worker", {
2732
+ workerId: id,
2733
+ totalActiveAssignments: state.activeAssignments.length,
2734
+ completedTasks: state.completedTasks.length
2735
+ });
2736
+ return {};
2215
2737
  }
2738
+ logger3.info("Worker processing assignment", {
2739
+ workerId: id,
2740
+ assignmentId: currentAssignment.id,
2741
+ taskLength: currentAssignment.task.length,
2742
+ taskPreview: currentAssignment.task.substring(0, 100)
2743
+ });
2216
2744
  if (executeFn) {
2217
- if (verbose) {
2218
- console.log(`[Worker:${id}] Using custom executeFn`);
2219
- }
2745
+ logger3.debug("Using custom execution function", { workerId: id });
2220
2746
  return await executeFn(state, runConfig);
2221
2747
  }
2222
2748
  if (agent) {
2223
2749
  if (isReActAgent(agent)) {
2224
- if (verbose) {
2225
- console.log(`[Worker:${id}] Using ReAct agent (auto-wrapped)`);
2226
- }
2750
+ logger3.debug("Using ReAct agent", { workerId: id });
2227
2751
  const wrappedFn = wrapReActAgent(id, agent, verbose);
2228
2752
  return await wrappedFn(state, runConfig);
2229
2753
  } else {
2230
- console.warn(`[Worker:${id}] Agent provided but does not appear to be a ReAct agent. Falling back to default execution.`);
2754
+ logger3.warn("Agent provided but not a ReAct agent, falling back", { workerId: id });
2231
2755
  }
2232
2756
  }
2233
2757
  if (!model) {
2758
+ logger3.error("Worker missing required configuration", { workerId: id });
2234
2759
  throw new Error(
2235
2760
  `Worker ${id} requires either a model, an agent, or a custom execution function. Provide one of: config.model, config.agent, or config.executeFn`
2236
2761
  );
2237
2762
  }
2763
+ logger3.debug("Using default LLM execution", {
2764
+ workerId: id,
2765
+ hasTools: tools.length > 0,
2766
+ toolCount: tools.length
2767
+ });
2238
2768
  const defaultSystemPrompt = `You are a specialized worker agent with the following capabilities:
2239
2769
  Skills: ${capabilities.skills.join(", ")}
2240
2770
  Tools: ${capabilities.tools.join(", ")}
@@ -2246,14 +2776,23 @@ Execute the assigned task using your skills and tools. Provide a clear, actionab
2246
2776
  ];
2247
2777
  let modelToUse = model;
2248
2778
  if (tools.length > 0 && model.bindTools) {
2779
+ logger3.debug("Binding tools to model", {
2780
+ workerId: id,
2781
+ toolCount: tools.length,
2782
+ toolNames: tools.map((t) => t.metadata.name)
2783
+ });
2249
2784
  const langchainTools = toLangChainTools2(tools);
2250
2785
  modelToUse = model.bindTools(langchainTools);
2251
2786
  }
2787
+ logger3.debug("Invoking LLM", { workerId: id });
2252
2788
  const response = await modelToUse.invoke(messages);
2253
2789
  const result = typeof response.content === "string" ? response.content : JSON.stringify(response.content);
2254
- if (verbose) {
2255
- console.log(`[Worker:${id}] Task completed:`, result.substring(0, 100) + "...");
2256
- }
2790
+ logger3.info("Worker task completed", {
2791
+ workerId: id,
2792
+ assignmentId: currentAssignment.id,
2793
+ resultLength: result.length,
2794
+ resultPreview: result.substring(0, 100)
2795
+ });
2257
2796
  const taskResult = {
2258
2797
  assignmentId: currentAssignment.id,
2259
2798
  workerId: id,
@@ -2283,22 +2822,33 @@ Execute the assigned task using your skills and tools. Provide a clear, actionab
2283
2822
  currentWorkload: Math.max(0, capabilities.currentWorkload - 1)
2284
2823
  }
2285
2824
  };
2825
+ logger3.debug("Worker state update", {
2826
+ workerId: id,
2827
+ newWorkload: updatedWorkers[id].currentWorkload
2828
+ });
2286
2829
  return {
2287
2830
  completedTasks: [taskResult],
2288
2831
  messages: [message],
2289
- workers: updatedWorkers,
2290
- currentAgent: "supervisor",
2291
- status: "routing"
2832
+ workers: updatedWorkers
2292
2833
  };
2293
2834
  } catch (error) {
2294
2835
  if (error && typeof error === "object" && "constructor" in error && error.constructor.name === "GraphInterrupt") {
2836
+ logger3.info("GraphInterrupt detected, re-throwing", { workerId: id });
2295
2837
  throw error;
2296
2838
  }
2297
- console.error(`[Worker:${id}] Error:`, error);
2839
+ logger3.error("Worker node error", {
2840
+ workerId: id,
2841
+ error: error instanceof Error ? error.message : String(error),
2842
+ stack: error instanceof Error ? error.stack : void 0
2843
+ });
2298
2844
  const currentAssignment = state.activeAssignments.find(
2299
2845
  (assignment) => assignment.workerId === id
2300
2846
  );
2301
2847
  if (currentAssignment) {
2848
+ logger3.warn("Creating error result for assignment", {
2849
+ workerId: id,
2850
+ assignmentId: currentAssignment.id
2851
+ });
2302
2852
  const errorResult = {
2303
2853
  assignmentId: currentAssignment.id,
2304
2854
  workerId: id,
@@ -2313,6 +2863,7 @@ Execute the assigned task using your skills and tools. Provide a clear, actionab
2313
2863
  status: "routing"
2314
2864
  };
2315
2865
  }
2866
+ logger3.error("No assignment found for error handling", { workerId: id });
2316
2867
  return {
2317
2868
  status: "failed",
2318
2869
  error: error instanceof Error ? error.message : `Unknown error in worker ${id}`
@@ -2329,29 +2880,44 @@ function createAggregatorNode(config = {}) {
2329
2880
  } = config;
2330
2881
  return async (state) => {
2331
2882
  try {
2332
- if (verbose) {
2333
- console.log("[Aggregator] Combining results from workers");
2334
- }
2883
+ logger3.info("Aggregator node executing", {
2884
+ completedTasks: state.completedTasks.length,
2885
+ successfulTasks: state.completedTasks.filter((t) => t.success).length,
2886
+ failedTasks: state.completedTasks.filter((t) => !t.success).length
2887
+ });
2888
+ logger3.debug("Combining results from workers");
2335
2889
  if (aggregateFn) {
2890
+ logger3.debug("Using custom aggregation function");
2336
2891
  const response2 = await aggregateFn(state);
2892
+ logger3.info("Custom aggregation complete", {
2893
+ responseLength: response2.length
2894
+ });
2337
2895
  return {
2338
2896
  response: response2,
2339
2897
  status: "completed"
2340
2898
  };
2341
2899
  }
2342
2900
  if (state.completedTasks.length === 0) {
2901
+ logger3.warn("No completed tasks to aggregate");
2343
2902
  return {
2344
2903
  response: "No tasks were completed.",
2345
2904
  status: "completed"
2346
2905
  };
2347
2906
  }
2348
2907
  if (!model) {
2908
+ logger3.debug("No model provided, concatenating results");
2349
2909
  const combinedResults = state.completedTasks.filter((task) => task.success).map((task) => task.result).join("\n\n");
2910
+ logger3.info("Simple concatenation complete", {
2911
+ resultLength: combinedResults.length
2912
+ });
2350
2913
  return {
2351
2914
  response: combinedResults || "No successful results to aggregate.",
2352
2915
  status: "completed"
2353
2916
  };
2354
2917
  }
2918
+ logger3.debug("Using LLM for intelligent aggregation", {
2919
+ taskCount: state.completedTasks.length
2920
+ });
2355
2921
  const taskResults = state.completedTasks.map((task, idx) => {
2356
2922
  const status = task.success ? "\u2713" : "\u2717";
2357
2923
  const result = task.success ? task.result : `Error: ${task.error}`;
@@ -2368,17 +2934,24 @@ Please synthesize these results into a comprehensive response that addresses the
2368
2934
  new SystemMessage5(systemPrompt),
2369
2935
  new HumanMessage5(userPrompt)
2370
2936
  ];
2937
+ logger3.debug("Invoking aggregation LLM");
2371
2938
  const response = await model.invoke(messages);
2372
2939
  const aggregatedResponse = typeof response.content === "string" ? response.content : JSON.stringify(response.content);
2373
- if (verbose) {
2374
- console.log("[Aggregator] Aggregation complete");
2375
- }
2940
+ logger3.info("Aggregation complete", {
2941
+ responseLength: aggregatedResponse.length,
2942
+ responsePreview: aggregatedResponse.substring(0, 100)
2943
+ });
2944
+ logger3.debug("Aggregation complete");
2376
2945
  return {
2377
2946
  response: aggregatedResponse,
2378
2947
  status: "completed"
2379
2948
  };
2380
2949
  } catch (error) {
2381
- console.error("[Aggregator] Error:", error);
2950
+ logger3.error("Aggregator node error", {
2951
+ error: error instanceof Error ? error.message : String(error),
2952
+ stack: error instanceof Error ? error.stack : void 0,
2953
+ completedTasks: state.completedTasks.length
2954
+ });
2382
2955
  return {
2383
2956
  status: "failed",
2384
2957
  error: error instanceof Error ? error.message : "Unknown error in aggregator"
@@ -2389,7 +2962,9 @@ Please synthesize these results into a comprehensive response that addresses the
2389
2962
 
2390
2963
  // src/multi-agent/agent.ts
2391
2964
  import { StateGraph as StateGraph4, END as END4 } from "@langchain/langgraph";
2392
- import { toLangChainTools as toLangChainTools3 } from "@agentforge/core";
2965
+ import { toLangChainTools as toLangChainTools3, createLogger as createLogger5, LogLevel as LogLevel4 } from "@agentforge/core";
2966
+ var logLevel4 = process.env.LOG_LEVEL?.toLowerCase() || LogLevel4.INFO;
2967
+ var logger4 = createLogger5("multi-agent:system", { level: logLevel4 });
2393
2968
  function createMultiAgentSystem(config) {
2394
2969
  const {
2395
2970
  supervisor,
@@ -2401,10 +2976,16 @@ function createMultiAgentSystem(config) {
2401
2976
  } = config;
2402
2977
  const workflow = new StateGraph4(MultiAgentState);
2403
2978
  let supervisorConfig = { ...supervisor, maxIterations, verbose };
2404
- if (supervisor.model && supervisor.tools && supervisor.tools.length > 0) {
2405
- const langchainTools = toLangChainTools3(supervisor.tools);
2406
- const modelWithTools = supervisor.model.bindTools(langchainTools);
2407
- supervisorConfig.model = modelWithTools;
2979
+ if (supervisor.model) {
2980
+ let configuredModel = supervisor.model;
2981
+ if (supervisor.strategy === "llm-based") {
2982
+ configuredModel = configuredModel.withStructuredOutput(RoutingDecisionSchema);
2983
+ }
2984
+ if (supervisor.tools && supervisor.tools.length > 0) {
2985
+ const langchainTools = toLangChainTools3(supervisor.tools);
2986
+ configuredModel = configuredModel.bindTools(langchainTools);
2987
+ }
2988
+ supervisorConfig.model = configuredModel;
2408
2989
  }
2409
2990
  const supervisorNode = createSupervisorNode(supervisorConfig);
2410
2991
  workflow.addNode("supervisor", supervisorNode);
@@ -2425,21 +3006,49 @@ function createMultiAgentSystem(config) {
2425
3006
  });
2426
3007
  workflow.addNode("aggregator", aggregatorNode);
2427
3008
  const supervisorRouter = (state) => {
3009
+ logger4.debug("Supervisor router executing", {
3010
+ status: state.status,
3011
+ currentAgent: state.currentAgent,
3012
+ iteration: state.iteration
3013
+ });
2428
3014
  if (state.status === "completed" || state.status === "failed") {
3015
+ logger4.info("Supervisor router: ending workflow", { status: state.status });
2429
3016
  return END4;
2430
3017
  }
2431
3018
  if (state.status === "aggregating") {
3019
+ logger4.info("Supervisor router: routing to aggregator");
2432
3020
  return "aggregator";
2433
3021
  }
2434
3022
  if (state.currentAgent && state.currentAgent !== "supervisor") {
3023
+ if (state.currentAgent.includes(",")) {
3024
+ const agents = state.currentAgent.split(",").map((a) => a.trim());
3025
+ logger4.info("Supervisor router: parallel routing", {
3026
+ agents,
3027
+ count: agents.length
3028
+ });
3029
+ return agents;
3030
+ }
3031
+ logger4.info("Supervisor router: single agent routing", {
3032
+ targetAgent: state.currentAgent
3033
+ });
2435
3034
  return state.currentAgent;
2436
3035
  }
3036
+ logger4.debug("Supervisor router: staying at supervisor");
2437
3037
  return "supervisor";
2438
3038
  };
2439
3039
  const workerRouter = (state) => {
3040
+ logger4.debug("Worker router executing", {
3041
+ iteration: state.iteration,
3042
+ completedTasks: state.completedTasks.length
3043
+ });
3044
+ logger4.debug("Worker router: returning to supervisor");
2440
3045
  return "supervisor";
2441
3046
  };
2442
3047
  const aggregatorRouter = (state) => {
3048
+ logger4.info("Aggregator router: ending workflow", {
3049
+ completedTasks: state.completedTasks.length,
3050
+ status: state.status
3051
+ });
2443
3052
  return END4;
2444
3053
  };
2445
3054
  workflow.setEntryPoint("supervisor");
@@ -2632,11 +3241,14 @@ export {
2632
3241
  ToolCallSchema,
2633
3242
  ToolResultSchema,
2634
3243
  WorkerCapabilitiesSchema,
3244
+ buildDeduplicationMetrics,
3245
+ calculateDeduplicationSavings,
2635
3246
  createAggregatorNode,
2636
3247
  createExecutorNode,
2637
3248
  createFinisherNode,
2638
3249
  createGeneratorNode,
2639
3250
  createMultiAgentSystem,
3251
+ createPatternLogger,
2640
3252
  createPlanExecuteAgent,
2641
3253
  createPlannerNode,
2642
3254
  createReActAgent,
@@ -2648,6 +3260,7 @@ export {
2648
3260
  createReviserNode,
2649
3261
  createSupervisorNode,
2650
3262
  createWorkerNode,
3263
+ generateToolCallCacheKey,
2651
3264
  getRoutingStrategy,
2652
3265
  llmBasedRouting,
2653
3266
  loadBalancedRouting,