@agentforge/patterns 0.6.3 → 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"
@@ -1807,11 +2080,22 @@ var MultiAgentState = createStateAnnotation4(MultiAgentStateConfig);
1807
2080
 
1808
2081
  // src/multi-agent/routing.ts
1809
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 });
1810
2086
  async function executeTools(toolCalls, tools) {
1811
2087
  const results = [];
2088
+ logger.debug("Executing tools", {
2089
+ toolCallCount: toolCalls.length,
2090
+ toolNames: toolCalls.map((tc) => tc.name)
2091
+ });
1812
2092
  for (const toolCall of toolCalls) {
1813
2093
  const tool = tools.find((t) => t.metadata.name === toolCall.name);
1814
2094
  if (!tool) {
2095
+ logger.warn("Tool not found", {
2096
+ toolName: toolCall.name,
2097
+ availableTools: tools.map((t) => t.metadata.name)
2098
+ });
1815
2099
  results.push(new ToolMessage2({
1816
2100
  content: `Error: Tool '${toolCall.name}' not found`,
1817
2101
  tool_call_id: toolCall.id
@@ -1819,19 +2103,41 @@ async function executeTools(toolCalls, tools) {
1819
2103
  continue;
1820
2104
  }
1821
2105
  try {
2106
+ logger.debug("Executing tool", {
2107
+ toolName: toolCall.name,
2108
+ args: toolCall.args
2109
+ });
1822
2110
  const result = await tool.execute(toolCall.args);
1823
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
+ });
1824
2116
  results.push(new ToolMessage2({
1825
2117
  content,
1826
2118
  tool_call_id: toolCall.id
1827
2119
  }));
1828
2120
  } catch (error) {
2121
+ logger.error("Tool execution failed", {
2122
+ toolName: toolCall.name,
2123
+ error: error.message
2124
+ });
1829
2125
  results.push(new ToolMessage2({
1830
2126
  content: `Error executing tool: ${error.message}`,
1831
2127
  tool_call_id: toolCall.id
1832
2128
  }));
1833
2129
  }
1834
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
+ });
1835
2141
  return results;
1836
2142
  }
1837
2143
  var DEFAULT_SUPERVISOR_SYSTEM_PROMPT = `You are a supervisor agent responsible for routing tasks to specialized worker agents.
@@ -1869,6 +2175,10 @@ Choose parallel routing when the task benefits from multiple perspectives or dat
1869
2175
  var llmBasedRouting = {
1870
2176
  name: "llm-based",
1871
2177
  async route(state, config) {
2178
+ logger.info("Starting LLM-based routing", {
2179
+ iteration: state.iteration,
2180
+ availableWorkers: Object.keys(state.workers).length
2181
+ });
1872
2182
  if (!config.model) {
1873
2183
  throw new Error("LLM-based routing requires a model to be configured");
1874
2184
  }
@@ -1881,8 +2191,20 @@ var llmBasedRouting = {
1881
2191
  const available = caps.available ? "available" : "busy";
1882
2192
  return `- ${id}: Skills: [${skills}], Tools: [${tools2}], Status: ${available}, Workload: ${caps.currentWorkload}`;
1883
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
+ });
1884
2202
  const lastMessage = state.messages[state.messages.length - 1];
1885
2203
  const taskContext = lastMessage?.content || state.input;
2204
+ logger.debug("Task context", {
2205
+ taskLength: taskContext.length,
2206
+ taskPreview: taskContext.substring(0, 100)
2207
+ });
1886
2208
  const userPrompt = `Current task: ${taskContext}
1887
2209
 
1888
2210
  Available workers:
@@ -1892,6 +2214,11 @@ Select the best worker(s) for this task and explain your reasoning.`;
1892
2214
  const conversationHistory = [];
1893
2215
  let attempt = 0;
1894
2216
  while (attempt < maxRetries) {
2217
+ logger.debug("LLM routing attempt", {
2218
+ attempt: attempt + 1,
2219
+ maxRetries,
2220
+ conversationHistoryLength: conversationHistory.length
2221
+ });
1895
2222
  const messages = [
1896
2223
  new SystemMessage4(systemPrompt),
1897
2224
  new HumanMessage4(userPrompt),
@@ -1899,6 +2226,10 @@ Select the best worker(s) for this task and explain your reasoning.`;
1899
2226
  ];
1900
2227
  const response = await config.model.invoke(messages);
1901
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
+ });
1902
2233
  if (tools.length === 0) {
1903
2234
  throw new Error("LLM requested tool calls but no tools are configured");
1904
2235
  }
@@ -1908,24 +2239,42 @@ Select the best worker(s) for this task and explain your reasoning.`;
1908
2239
  ...toolResults
1909
2240
  );
1910
2241
  attempt++;
2242
+ logger.debug("Retrying routing with tool results", { attempt });
1911
2243
  continue;
1912
2244
  }
2245
+ logger.debug("Parsing routing decision from LLM response");
1913
2246
  let decision;
1914
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
+ });
1915
2252
  decision = response;
1916
2253
  } else if (response.content) {
1917
2254
  if (typeof response.content === "string") {
1918
2255
  try {
1919
2256
  decision = JSON.parse(response.content);
2257
+ logger.debug("Parsed JSON from string response");
1920
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
+ });
1921
2263
  throw new Error(`Failed to parse routing decision from LLM. Expected JSON but got: ${response.content}`);
1922
2264
  }
1923
2265
  } else if (typeof response.content === "object") {
2266
+ logger.debug("Response content is already an object");
1924
2267
  decision = response.content;
1925
2268
  } else {
2269
+ logger.error("Unexpected response content type", {
2270
+ type: typeof response.content
2271
+ });
1926
2272
  throw new Error(`Unexpected response content type: ${typeof response.content}`);
1927
2273
  }
1928
2274
  } else {
2275
+ logger.error("Unexpected response format", {
2276
+ response: JSON.stringify(response)
2277
+ });
1929
2278
  throw new Error(`Unexpected response format: ${JSON.stringify(response)}`);
1930
2279
  }
1931
2280
  const result = {
@@ -1936,20 +2285,41 @@ Select the best worker(s) for this task and explain your reasoning.`;
1936
2285
  strategy: "llm-based",
1937
2286
  timestamp: Date.now()
1938
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
+ });
1939
2295
  return result;
1940
2296
  }
2297
+ logger.error("Max tool retries exceeded", { maxRetries });
1941
2298
  throw new Error(`Max tool retries (${maxRetries}) exceeded without routing decision`);
1942
2299
  }
1943
2300
  };
1944
2301
  var roundRobinRouting = {
1945
2302
  name: "round-robin",
1946
2303
  async route(state, config) {
2304
+ logger.info("Starting round-robin routing", {
2305
+ iteration: state.iteration
2306
+ });
1947
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
+ });
1948
2312
  if (availableWorkers.length === 0) {
2313
+ logger.error("No available workers for round-robin routing");
1949
2314
  throw new Error("No available workers for round-robin routing");
1950
2315
  }
1951
2316
  const lastRoutingIndex = state.routingHistory.length % availableWorkers.length;
1952
2317
  const targetAgent = availableWorkers[lastRoutingIndex];
2318
+ logger.info("Round-robin routing decision", {
2319
+ targetAgent,
2320
+ index: lastRoutingIndex + 1,
2321
+ totalWorkers: availableWorkers.length
2322
+ });
1953
2323
  return {
1954
2324
  targetAgent,
1955
2325
  targetAgents: null,
@@ -1963,8 +2333,15 @@ var roundRobinRouting = {
1963
2333
  var skillBasedRouting = {
1964
2334
  name: "skill-based",
1965
2335
  async route(state, config) {
2336
+ logger.info("Starting skill-based routing", {
2337
+ iteration: state.iteration
2338
+ });
1966
2339
  const lastMessage = state.messages[state.messages.length - 1];
1967
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
+ });
1968
2345
  const workerScores = Object.entries(state.workers).filter(([_, caps]) => caps.available).map(([id, caps]) => {
1969
2346
  const skillMatches = caps.skills.filter(
1970
2347
  (skill) => taskContent.includes(skill.toLowerCase())
@@ -1975,11 +2352,20 @@ var skillBasedRouting = {
1975
2352
  const score = skillMatches * 2 + toolMatches;
1976
2353
  return { id, score, skills: caps.skills, tools: caps.tools };
1977
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
+ });
1978
2358
  if (workerScores.length === 0) {
2359
+ logger.warn("No skill matches found, using fallback");
1979
2360
  const firstAvailable = Object.entries(state.workers).find(([_, caps]) => caps.available);
1980
2361
  if (!firstAvailable) {
2362
+ logger.error("No available workers for skill-based routing");
1981
2363
  throw new Error("No available workers for skill-based routing");
1982
2364
  }
2365
+ logger.info("Skill-based routing fallback decision", {
2366
+ targetAgent: firstAvailable[0],
2367
+ confidence: 0.5
2368
+ });
1983
2369
  return {
1984
2370
  targetAgent: firstAvailable[0],
1985
2371
  targetAgents: null,
@@ -1991,6 +2377,12 @@ var skillBasedRouting = {
1991
2377
  }
1992
2378
  const best = workerScores[0];
1993
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
+ });
1994
2386
  return {
1995
2387
  targetAgent: best.id,
1996
2388
  targetAgents: null,
@@ -2004,13 +2396,26 @@ var skillBasedRouting = {
2004
2396
  var loadBalancedRouting = {
2005
2397
  name: "load-balanced",
2006
2398
  async route(state, config) {
2399
+ logger.info("Starting load-balanced routing", {
2400
+ iteration: state.iteration
2401
+ });
2007
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
+ });
2008
2406
  if (availableWorkers.length === 0) {
2407
+ logger.error("No available workers for load-balanced routing");
2009
2408
  throw new Error("No available workers for load-balanced routing");
2010
2409
  }
2011
2410
  const targetWorker = availableWorkers[0];
2012
2411
  const avgWorkload = availableWorkers.reduce((sum, w) => sum + w.workload, 0) / availableWorkers.length;
2013
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
+ });
2014
2419
  return {
2015
2420
  targetAgent: targetWorker.id,
2016
2421
  targetAgents: null,
@@ -2024,13 +2429,24 @@ var loadBalancedRouting = {
2024
2429
  var ruleBasedRouting = {
2025
2430
  name: "rule-based",
2026
2431
  async route(state, config) {
2432
+ logger.info("Starting rule-based routing", {
2433
+ iteration: state.iteration
2434
+ });
2027
2435
  if (!config.routingFn) {
2436
+ logger.error("Rule-based routing requires a custom routing function");
2028
2437
  throw new Error("Rule-based routing requires a custom routing function");
2029
2438
  }
2030
- 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;
2031
2446
  }
2032
2447
  };
2033
2448
  function getRoutingStrategy(name) {
2449
+ logger.debug("Getting routing strategy", { name });
2034
2450
  switch (name) {
2035
2451
  case "llm-based":
2036
2452
  return llmBasedRouting;
@@ -2043,14 +2459,15 @@ function getRoutingStrategy(name) {
2043
2459
  case "rule-based":
2044
2460
  return ruleBasedRouting;
2045
2461
  default:
2462
+ logger.error("Unknown routing strategy", { name });
2046
2463
  throw new Error(`Unknown routing strategy: ${name}`);
2047
2464
  }
2048
2465
  }
2049
2466
 
2050
2467
  // src/multi-agent/utils.ts
2051
- import { createLogger, LogLevel } from "@agentforge/core";
2052
- var logLevel = process.env.LOG_LEVEL?.toLowerCase() || LogLevel.INFO;
2053
- 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 });
2054
2471
  function isReActAgent(obj) {
2055
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
2056
2473
  (obj.constructor?.name === "CompiledGraph" || obj.constructor?.name === "CompiledStateGraph");
@@ -2058,9 +2475,9 @@ function isReActAgent(obj) {
2058
2475
  function wrapReActAgent(workerId, agent, verbose = false) {
2059
2476
  return async (state, config) => {
2060
2477
  try {
2061
- logger.debug("Wrapping ReAct agent execution", { workerId });
2478
+ logger2.debug("Wrapping ReAct agent execution", { workerId });
2062
2479
  const task = state.messages[state.messages.length - 1]?.content || state.input;
2063
- logger.debug("Extracted task", {
2480
+ logger2.debug("Extracted task", {
2064
2481
  workerId,
2065
2482
  taskPreview: task.substring(0, 100) + (task.length > 100 ? "..." : "")
2066
2483
  });
@@ -2068,7 +2485,7 @@ function wrapReActAgent(workerId, agent, verbose = false) {
2068
2485
  (assignment) => assignment.workerId === workerId && !state.completedTasks.some((task2) => task2.assignmentId === assignment.id)
2069
2486
  );
2070
2487
  if (!currentAssignment) {
2071
- logger.debug("No active assignment found", { workerId });
2488
+ logger2.debug("No active assignment found", { workerId });
2072
2489
  return {};
2073
2490
  }
2074
2491
  const result = await agent.invoke(
@@ -2079,14 +2496,14 @@ function wrapReActAgent(workerId, agent, verbose = false) {
2079
2496
  // Pass through the config for checkpointing and interrupt support
2080
2497
  );
2081
2498
  const response = result.messages?.[result.messages.length - 1]?.content || "No response";
2082
- logger.debug("Received response from ReAct agent", {
2499
+ logger2.debug("Received response from ReAct agent", {
2083
2500
  workerId,
2084
2501
  responsePreview: response.substring(0, 100) + (response.length > 100 ? "..." : "")
2085
2502
  });
2086
2503
  const toolsUsed = result.actions?.map((action) => action.name).filter(Boolean) || [];
2087
2504
  const uniqueTools = [...new Set(toolsUsed)];
2088
2505
  if (uniqueTools.length > 0) {
2089
- logger.debug("Tools used by ReAct agent", { workerId, tools: uniqueTools });
2506
+ logger2.debug("Tools used by ReAct agent", { workerId, tools: uniqueTools });
2090
2507
  }
2091
2508
  const taskResult = {
2092
2509
  assignmentId: currentAssignment.id,
@@ -2105,10 +2522,10 @@ function wrapReActAgent(workerId, agent, verbose = false) {
2105
2522
  };
2106
2523
  } catch (error) {
2107
2524
  if (error && typeof error === "object" && "constructor" in error && error.constructor.name === "GraphInterrupt") {
2108
- logger.debug("GraphInterrupt detected - re-throwing", { workerId });
2525
+ logger2.debug("GraphInterrupt detected - re-throwing", { workerId });
2109
2526
  throw error;
2110
2527
  }
2111
- logger.error("Error in ReAct agent execution", {
2528
+ logger2.error("Error in ReAct agent execution", {
2112
2529
  workerId,
2113
2530
  error: error instanceof Error ? error.message : String(error),
2114
2531
  stack: error instanceof Error ? error.stack : void 0
@@ -2141,7 +2558,9 @@ function wrapReActAgent(workerId, agent, verbose = false) {
2141
2558
 
2142
2559
  // src/multi-agent/nodes.ts
2143
2560
  import { HumanMessage as HumanMessage5, SystemMessage as SystemMessage5 } from "@langchain/core/messages";
2144
- 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 });
2145
2564
  var DEFAULT_AGGREGATOR_SYSTEM_PROMPT = `You are an aggregator agent responsible for combining results from multiple worker agents.
2146
2565
 
2147
2566
  Your job is to:
@@ -2159,13 +2578,19 @@ function createSupervisorNode(config) {
2159
2578
  } = config;
2160
2579
  return async (state) => {
2161
2580
  try {
2162
- if (verbose) {
2163
- console.log(`[Supervisor] Routing iteration ${state.iteration}/${maxIterations}`);
2164
- }
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}`);
2165
2588
  if (state.iteration >= maxIterations) {
2166
- if (verbose) {
2167
- console.log("[Supervisor] Max iterations reached, moving to aggregation");
2168
- }
2589
+ logger3.warn("Max iterations reached", {
2590
+ iteration: state.iteration,
2591
+ maxIterations
2592
+ });
2593
+ logger3.debug("Max iterations reached, moving to aggregation");
2169
2594
  return {
2170
2595
  status: "aggregating",
2171
2596
  currentAgent: "aggregator"
@@ -2174,27 +2599,55 @@ function createSupervisorNode(config) {
2174
2599
  const allCompleted = state.activeAssignments.every(
2175
2600
  (assignment) => state.completedTasks.some((task2) => task2.assignmentId === assignment.id)
2176
2601
  );
2602
+ logger3.debug("Checking task completion", {
2603
+ activeAssignments: state.activeAssignments.length,
2604
+ completedTasks: state.completedTasks.length,
2605
+ allCompleted
2606
+ });
2177
2607
  if (allCompleted && state.activeAssignments.length > 0) {
2178
- if (verbose) {
2179
- console.log("[Supervisor] All tasks completed, moving to aggregation");
2180
- }
2608
+ logger3.info("All tasks completed, moving to aggregation", {
2609
+ completedCount: state.completedTasks.length
2610
+ });
2611
+ logger3.debug("All tasks completed, moving to aggregation");
2181
2612
  return {
2182
2613
  status: "aggregating",
2183
2614
  currentAgent: "aggregator"
2184
2615
  };
2185
2616
  }
2617
+ logger3.debug("Getting routing strategy", { strategy });
2186
2618
  const routingImpl = getRoutingStrategy(strategy);
2187
2619
  const decision = await routingImpl.route(state, config);
2188
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
+ });
2189
2629
  if (targetAgents.length === 0) {
2630
+ logger3.error("No target agents specified in routing decision");
2190
2631
  throw new Error("Routing decision must specify at least one target agent");
2191
2632
  }
2192
- if (verbose) {
2193
- if (targetAgents.length === 1) {
2194
- console.log(`[Supervisor] Routing to ${targetAgents[0]}: ${decision.reasoning}`);
2195
- } else {
2196
- console.log(`[Supervisor] Routing to ${targetAgents.length} agents in parallel [${targetAgents.join(", ")}]: ${decision.reasoning}`);
2197
- }
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}`);
2198
2651
  }
2199
2652
  const task = state.messages[state.messages.length - 1]?.content || state.input;
2200
2653
  const assignments = targetAgents.map((workerId) => ({
@@ -2204,6 +2657,14 @@ function createSupervisorNode(config) {
2204
2657
  priority: 5,
2205
2658
  assignedAt: Date.now()
2206
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
+ });
2207
2668
  const messages = assignments.map((assignment) => ({
2208
2669
  id: `msg-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
2209
2670
  from: "supervisor",
@@ -2216,6 +2677,12 @@ function createSupervisorNode(config) {
2216
2677
  priority: assignment.priority
2217
2678
  }
2218
2679
  }));
2680
+ logger3.info("Supervisor routing complete", {
2681
+ currentAgent: targetAgents.join(","),
2682
+ status: "executing",
2683
+ assignmentCount: assignments.length,
2684
+ nextIteration: state.iteration + 1
2685
+ });
2219
2686
  return {
2220
2687
  currentAgent: targetAgents.join(","),
2221
2688
  // Store all agents (for backward compat)
@@ -2227,7 +2694,11 @@ function createSupervisorNode(config) {
2227
2694
  iteration: state.iteration + 1
2228
2695
  };
2229
2696
  } catch (error) {
2230
- 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
+ });
2231
2702
  return {
2232
2703
  status: "failed",
2233
2704
  error: error instanceof Error ? error.message : "Unknown error in supervisor"
@@ -2248,40 +2719,52 @@ function createWorkerNode(config) {
2248
2719
  } = config;
2249
2720
  return async (state, runConfig) => {
2250
2721
  try {
2251
- if (verbose) {
2252
- console.log(`[Worker:${id}] Executing task`);
2253
- }
2722
+ logger3.info("Worker node executing", {
2723
+ workerId: id,
2724
+ iteration: state.iteration,
2725
+ activeAssignments: state.activeAssignments.length
2726
+ });
2254
2727
  const currentAssignment = state.activeAssignments.find(
2255
2728
  (assignment) => assignment.workerId === id && !state.completedTasks.some((task) => task.assignmentId === assignment.id)
2256
2729
  );
2257
2730
  if (!currentAssignment) {
2258
- if (verbose) {
2259
- console.log(`[Worker:${id}] No active assignment found`);
2260
- }
2731
+ logger3.debug("No active assignment found for worker", {
2732
+ workerId: id,
2733
+ totalActiveAssignments: state.activeAssignments.length,
2734
+ completedTasks: state.completedTasks.length
2735
+ });
2261
2736
  return {};
2262
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
+ });
2263
2744
  if (executeFn) {
2264
- if (verbose) {
2265
- console.log(`[Worker:${id}] Using custom executeFn`);
2266
- }
2745
+ logger3.debug("Using custom execution function", { workerId: id });
2267
2746
  return await executeFn(state, runConfig);
2268
2747
  }
2269
2748
  if (agent) {
2270
2749
  if (isReActAgent(agent)) {
2271
- if (verbose) {
2272
- console.log(`[Worker:${id}] Using ReAct agent (auto-wrapped)`);
2273
- }
2750
+ logger3.debug("Using ReAct agent", { workerId: id });
2274
2751
  const wrappedFn = wrapReActAgent(id, agent, verbose);
2275
2752
  return await wrappedFn(state, runConfig);
2276
2753
  } else {
2277
- 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 });
2278
2755
  }
2279
2756
  }
2280
2757
  if (!model) {
2758
+ logger3.error("Worker missing required configuration", { workerId: id });
2281
2759
  throw new Error(
2282
2760
  `Worker ${id} requires either a model, an agent, or a custom execution function. Provide one of: config.model, config.agent, or config.executeFn`
2283
2761
  );
2284
2762
  }
2763
+ logger3.debug("Using default LLM execution", {
2764
+ workerId: id,
2765
+ hasTools: tools.length > 0,
2766
+ toolCount: tools.length
2767
+ });
2285
2768
  const defaultSystemPrompt = `You are a specialized worker agent with the following capabilities:
2286
2769
  Skills: ${capabilities.skills.join(", ")}
2287
2770
  Tools: ${capabilities.tools.join(", ")}
@@ -2293,14 +2776,23 @@ Execute the assigned task using your skills and tools. Provide a clear, actionab
2293
2776
  ];
2294
2777
  let modelToUse = model;
2295
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
+ });
2296
2784
  const langchainTools = toLangChainTools2(tools);
2297
2785
  modelToUse = model.bindTools(langchainTools);
2298
2786
  }
2787
+ logger3.debug("Invoking LLM", { workerId: id });
2299
2788
  const response = await modelToUse.invoke(messages);
2300
2789
  const result = typeof response.content === "string" ? response.content : JSON.stringify(response.content);
2301
- if (verbose) {
2302
- console.log(`[Worker:${id}] Task completed:`, result.substring(0, 100) + "...");
2303
- }
2790
+ logger3.info("Worker task completed", {
2791
+ workerId: id,
2792
+ assignmentId: currentAssignment.id,
2793
+ resultLength: result.length,
2794
+ resultPreview: result.substring(0, 100)
2795
+ });
2304
2796
  const taskResult = {
2305
2797
  assignmentId: currentAssignment.id,
2306
2798
  workerId: id,
@@ -2330,6 +2822,10 @@ Execute the assigned task using your skills and tools. Provide a clear, actionab
2330
2822
  currentWorkload: Math.max(0, capabilities.currentWorkload - 1)
2331
2823
  }
2332
2824
  };
2825
+ logger3.debug("Worker state update", {
2826
+ workerId: id,
2827
+ newWorkload: updatedWorkers[id].currentWorkload
2828
+ });
2333
2829
  return {
2334
2830
  completedTasks: [taskResult],
2335
2831
  messages: [message],
@@ -2337,13 +2833,22 @@ Execute the assigned task using your skills and tools. Provide a clear, actionab
2337
2833
  };
2338
2834
  } catch (error) {
2339
2835
  if (error && typeof error === "object" && "constructor" in error && error.constructor.name === "GraphInterrupt") {
2836
+ logger3.info("GraphInterrupt detected, re-throwing", { workerId: id });
2340
2837
  throw error;
2341
2838
  }
2342
- 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
+ });
2343
2844
  const currentAssignment = state.activeAssignments.find(
2344
2845
  (assignment) => assignment.workerId === id
2345
2846
  );
2346
2847
  if (currentAssignment) {
2848
+ logger3.warn("Creating error result for assignment", {
2849
+ workerId: id,
2850
+ assignmentId: currentAssignment.id
2851
+ });
2347
2852
  const errorResult = {
2348
2853
  assignmentId: currentAssignment.id,
2349
2854
  workerId: id,
@@ -2358,6 +2863,7 @@ Execute the assigned task using your skills and tools. Provide a clear, actionab
2358
2863
  status: "routing"
2359
2864
  };
2360
2865
  }
2866
+ logger3.error("No assignment found for error handling", { workerId: id });
2361
2867
  return {
2362
2868
  status: "failed",
2363
2869
  error: error instanceof Error ? error.message : `Unknown error in worker ${id}`
@@ -2374,29 +2880,44 @@ function createAggregatorNode(config = {}) {
2374
2880
  } = config;
2375
2881
  return async (state) => {
2376
2882
  try {
2377
- if (verbose) {
2378
- console.log("[Aggregator] Combining results from workers");
2379
- }
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");
2380
2889
  if (aggregateFn) {
2890
+ logger3.debug("Using custom aggregation function");
2381
2891
  const response2 = await aggregateFn(state);
2892
+ logger3.info("Custom aggregation complete", {
2893
+ responseLength: response2.length
2894
+ });
2382
2895
  return {
2383
2896
  response: response2,
2384
2897
  status: "completed"
2385
2898
  };
2386
2899
  }
2387
2900
  if (state.completedTasks.length === 0) {
2901
+ logger3.warn("No completed tasks to aggregate");
2388
2902
  return {
2389
2903
  response: "No tasks were completed.",
2390
2904
  status: "completed"
2391
2905
  };
2392
2906
  }
2393
2907
  if (!model) {
2908
+ logger3.debug("No model provided, concatenating results");
2394
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
+ });
2395
2913
  return {
2396
2914
  response: combinedResults || "No successful results to aggregate.",
2397
2915
  status: "completed"
2398
2916
  };
2399
2917
  }
2918
+ logger3.debug("Using LLM for intelligent aggregation", {
2919
+ taskCount: state.completedTasks.length
2920
+ });
2400
2921
  const taskResults = state.completedTasks.map((task, idx) => {
2401
2922
  const status = task.success ? "\u2713" : "\u2717";
2402
2923
  const result = task.success ? task.result : `Error: ${task.error}`;
@@ -2413,17 +2934,24 @@ Please synthesize these results into a comprehensive response that addresses the
2413
2934
  new SystemMessage5(systemPrompt),
2414
2935
  new HumanMessage5(userPrompt)
2415
2936
  ];
2937
+ logger3.debug("Invoking aggregation LLM");
2416
2938
  const response = await model.invoke(messages);
2417
2939
  const aggregatedResponse = typeof response.content === "string" ? response.content : JSON.stringify(response.content);
2418
- if (verbose) {
2419
- console.log("[Aggregator] Aggregation complete");
2420
- }
2940
+ logger3.info("Aggregation complete", {
2941
+ responseLength: aggregatedResponse.length,
2942
+ responsePreview: aggregatedResponse.substring(0, 100)
2943
+ });
2944
+ logger3.debug("Aggregation complete");
2421
2945
  return {
2422
2946
  response: aggregatedResponse,
2423
2947
  status: "completed"
2424
2948
  };
2425
2949
  } catch (error) {
2426
- 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
+ });
2427
2955
  return {
2428
2956
  status: "failed",
2429
2957
  error: error instanceof Error ? error.message : "Unknown error in aggregator"
@@ -2434,7 +2962,9 @@ Please synthesize these results into a comprehensive response that addresses the
2434
2962
 
2435
2963
  // src/multi-agent/agent.ts
2436
2964
  import { StateGraph as StateGraph4, END as END4 } from "@langchain/langgraph";
2437
- 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 });
2438
2968
  function createMultiAgentSystem(config) {
2439
2969
  const {
2440
2970
  supervisor,
@@ -2476,25 +3006,49 @@ function createMultiAgentSystem(config) {
2476
3006
  });
2477
3007
  workflow.addNode("aggregator", aggregatorNode);
2478
3008
  const supervisorRouter = (state) => {
3009
+ logger4.debug("Supervisor router executing", {
3010
+ status: state.status,
3011
+ currentAgent: state.currentAgent,
3012
+ iteration: state.iteration
3013
+ });
2479
3014
  if (state.status === "completed" || state.status === "failed") {
3015
+ logger4.info("Supervisor router: ending workflow", { status: state.status });
2480
3016
  return END4;
2481
3017
  }
2482
3018
  if (state.status === "aggregating") {
3019
+ logger4.info("Supervisor router: routing to aggregator");
2483
3020
  return "aggregator";
2484
3021
  }
2485
3022
  if (state.currentAgent && state.currentAgent !== "supervisor") {
2486
3023
  if (state.currentAgent.includes(",")) {
2487
3024
  const agents = state.currentAgent.split(",").map((a) => a.trim());
3025
+ logger4.info("Supervisor router: parallel routing", {
3026
+ agents,
3027
+ count: agents.length
3028
+ });
2488
3029
  return agents;
2489
3030
  }
3031
+ logger4.info("Supervisor router: single agent routing", {
3032
+ targetAgent: state.currentAgent
3033
+ });
2490
3034
  return state.currentAgent;
2491
3035
  }
3036
+ logger4.debug("Supervisor router: staying at supervisor");
2492
3037
  return "supervisor";
2493
3038
  };
2494
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");
2495
3045
  return "supervisor";
2496
3046
  };
2497
3047
  const aggregatorRouter = (state) => {
3048
+ logger4.info("Aggregator router: ending workflow", {
3049
+ completedTasks: state.completedTasks.length,
3050
+ status: state.status
3051
+ });
2498
3052
  return END4;
2499
3053
  };
2500
3054
  workflow.setEntryPoint("supervisor");
@@ -2687,11 +3241,14 @@ export {
2687
3241
  ToolCallSchema,
2688
3242
  ToolResultSchema,
2689
3243
  WorkerCapabilitiesSchema,
3244
+ buildDeduplicationMetrics,
3245
+ calculateDeduplicationSavings,
2690
3246
  createAggregatorNode,
2691
3247
  createExecutorNode,
2692
3248
  createFinisherNode,
2693
3249
  createGeneratorNode,
2694
3250
  createMultiAgentSystem,
3251
+ createPatternLogger,
2695
3252
  createPlanExecuteAgent,
2696
3253
  createPlannerNode,
2697
3254
  createReActAgent,
@@ -2703,6 +3260,7 @@ export {
2703
3260
  createReviserNode,
2704
3261
  createSupervisorNode,
2705
3262
  createWorkerNode,
3263
+ generateToolCallCacheKey,
2706
3264
  getRoutingStrategy,
2707
3265
  llmBasedRouting,
2708
3266
  loadBalancedRouting,