@agentforge/patterns 0.5.4 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -327,6 +327,9 @@ function createActionNode(tools, verbose = false) {
327
327
  console.log(`[action] Tool '${action.name}' executed successfully`);
328
328
  }
329
329
  } catch (error) {
330
+ if (error && typeof error === "object" && "constructor" in error && error.constructor.name === "GraphInterrupt") {
331
+ throw error;
332
+ }
330
333
  const errorMessage = error instanceof Error ? error.message : String(error);
331
334
  observations.push({
332
335
  toolCallId: action.id,
@@ -1891,6 +1894,33 @@ var MultiAgentState = (0, import_core6.createStateAnnotation)(MultiAgentStateCon
1891
1894
 
1892
1895
  // src/multi-agent/routing.ts
1893
1896
  var import_messages4 = require("@langchain/core/messages");
1897
+ async function executeTools(toolCalls, tools) {
1898
+ const results = [];
1899
+ for (const toolCall of toolCalls) {
1900
+ const tool = tools.find((t) => t.metadata.name === toolCall.name);
1901
+ if (!tool) {
1902
+ results.push(new import_messages4.ToolMessage({
1903
+ content: `Error: Tool '${toolCall.name}' not found`,
1904
+ tool_call_id: toolCall.id
1905
+ }));
1906
+ continue;
1907
+ }
1908
+ try {
1909
+ const result = await tool.execute(toolCall.args);
1910
+ const content = typeof result === "string" ? result : JSON.stringify(result);
1911
+ results.push(new import_messages4.ToolMessage({
1912
+ content,
1913
+ tool_call_id: toolCall.id
1914
+ }));
1915
+ } catch (error) {
1916
+ results.push(new import_messages4.ToolMessage({
1917
+ content: `Error executing tool: ${error.message}`,
1918
+ tool_call_id: toolCall.id
1919
+ }));
1920
+ }
1921
+ }
1922
+ return results;
1923
+ }
1894
1924
  var DEFAULT_SUPERVISOR_SYSTEM_PROMPT = `You are a supervisor agent responsible for routing tasks to specialized worker agents.
1895
1925
 
1896
1926
  Your job is to:
@@ -1913,11 +1943,13 @@ var llmBasedRouting = {
1913
1943
  throw new Error("LLM-based routing requires a model to be configured");
1914
1944
  }
1915
1945
  const systemPrompt = config.systemPrompt || DEFAULT_SUPERVISOR_SYSTEM_PROMPT;
1946
+ const maxRetries = config.maxToolRetries || 3;
1947
+ const tools = config.tools || [];
1916
1948
  const workerInfo = Object.entries(state.workers).map(([id, caps]) => {
1917
1949
  const skills = caps.skills.join(", ");
1918
- const tools = caps.tools.join(", ");
1950
+ const tools2 = caps.tools.join(", ");
1919
1951
  const available = caps.available ? "available" : "busy";
1920
- return `- ${id}: Skills: [${skills}], Tools: [${tools}], Status: ${available}, Workload: ${caps.currentWorkload}`;
1952
+ return `- ${id}: Skills: [${skills}], Tools: [${tools2}], Status: ${available}, Workload: ${caps.currentWorkload}`;
1921
1953
  }).join("\n");
1922
1954
  const lastMessage = state.messages[state.messages.length - 1];
1923
1955
  const taskContext = lastMessage?.content || state.input;
@@ -1927,24 +1959,42 @@ Available workers:
1927
1959
  ${workerInfo}
1928
1960
 
1929
1961
  Select the best worker for this task and explain your reasoning.`;
1930
- const messages = [
1931
- new import_messages4.SystemMessage(systemPrompt),
1932
- new import_messages4.HumanMessage(userPrompt)
1933
- ];
1934
- const response = await config.model.invoke(messages);
1935
- const content = typeof response.content === "string" ? response.content : JSON.stringify(response.content);
1936
- try {
1937
- const decision = JSON.parse(content);
1938
- return {
1939
- targetAgent: decision.targetAgent,
1940
- reasoning: decision.reasoning,
1941
- confidence: decision.confidence,
1942
- strategy: "llm-based",
1943
- timestamp: Date.now()
1944
- };
1945
- } catch (error) {
1946
- throw new Error(`Failed to parse routing decision from LLM: ${error}`);
1962
+ const conversationHistory = [];
1963
+ let attempt = 0;
1964
+ while (attempt < maxRetries) {
1965
+ const messages = [
1966
+ new import_messages4.SystemMessage(systemPrompt),
1967
+ new import_messages4.HumanMessage(userPrompt),
1968
+ ...conversationHistory
1969
+ ];
1970
+ const response = await config.model.invoke(messages);
1971
+ if (response.tool_calls && response.tool_calls.length > 0) {
1972
+ if (tools.length === 0) {
1973
+ throw new Error("LLM requested tool calls but no tools are configured");
1974
+ }
1975
+ const toolResults = await executeTools(response.tool_calls, tools);
1976
+ conversationHistory.push(
1977
+ new import_messages4.AIMessage({ content: response.content || "", tool_calls: response.tool_calls }),
1978
+ ...toolResults
1979
+ );
1980
+ attempt++;
1981
+ continue;
1982
+ }
1983
+ const content = typeof response.content === "string" ? response.content : JSON.stringify(response.content);
1984
+ try {
1985
+ const decision = JSON.parse(content);
1986
+ return {
1987
+ targetAgent: decision.targetAgent,
1988
+ reasoning: decision.reasoning,
1989
+ confidence: decision.confidence,
1990
+ strategy: "llm-based",
1991
+ timestamp: Date.now()
1992
+ };
1993
+ } catch (error) {
1994
+ throw new Error(`Failed to parse routing decision from LLM: ${error}`);
1995
+ }
1947
1996
  }
1997
+ throw new Error(`Max tool retries (${maxRetries}) exceeded without routing decision`);
1948
1998
  }
1949
1999
  };
1950
2000
  var roundRobinRouting = {
@@ -2050,43 +2100,48 @@ function getRoutingStrategy(name) {
2050
2100
  }
2051
2101
 
2052
2102
  // src/multi-agent/utils.ts
2103
+ var import_core7 = require("@agentforge/core");
2104
+ var logLevel = process.env.LOG_LEVEL?.toLowerCase() || import_core7.LogLevel.INFO;
2105
+ var logger = (0, import_core7.createLogger)("multi-agent", { level: logLevel });
2053
2106
  function isReActAgent(obj) {
2054
2107
  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
2055
2108
  (obj.constructor?.name === "CompiledGraph" || obj.constructor?.name === "CompiledStateGraph");
2056
2109
  }
2057
2110
  function wrapReActAgent(workerId, agent, verbose = false) {
2058
- return async (state) => {
2111
+ return async (state, config) => {
2059
2112
  try {
2060
- if (verbose) {
2061
- console.log(`[ReActWrapper:${workerId}] Wrapping ReAct agent execution`);
2062
- }
2113
+ logger.debug("Wrapping ReAct agent execution", { workerId });
2063
2114
  const task = state.messages[state.messages.length - 1]?.content || state.input;
2064
- if (verbose) {
2065
- console.log(`[ReActWrapper:${workerId}] Task:`, task.substring(0, 100) + "...");
2066
- }
2115
+ logger.debug("Extracted task", {
2116
+ workerId,
2117
+ taskPreview: task.substring(0, 100) + (task.length > 100 ? "..." : "")
2118
+ });
2067
2119
  const currentAssignment = state.activeAssignments.find(
2068
2120
  (assignment) => assignment.workerId === workerId && !state.completedTasks.some((task2) => task2.assignmentId === assignment.id)
2069
2121
  );
2070
2122
  if (!currentAssignment) {
2071
- if (verbose) {
2072
- console.log(`[ReActWrapper:${workerId}] No active assignment found`);
2073
- }
2123
+ logger.debug("No active assignment found", { workerId });
2074
2124
  return {
2075
2125
  currentAgent: "supervisor",
2076
2126
  status: "routing"
2077
2127
  };
2078
2128
  }
2079
- const result = await agent.invoke({
2080
- messages: [{ role: "user", content: task }]
2081
- });
2129
+ const result = await agent.invoke(
2130
+ {
2131
+ messages: [{ role: "user", content: task }]
2132
+ },
2133
+ config
2134
+ // Pass through the config for checkpointing and interrupt support
2135
+ );
2082
2136
  const response = result.messages?.[result.messages.length - 1]?.content || "No response";
2083
- if (verbose) {
2084
- console.log(`[ReActWrapper:${workerId}] Response:`, response.substring(0, 100) + "...");
2085
- }
2137
+ logger.debug("Received response from ReAct agent", {
2138
+ workerId,
2139
+ responsePreview: response.substring(0, 100) + (response.length > 100 ? "..." : "")
2140
+ });
2086
2141
  const toolsUsed = result.actions?.map((action) => action.name).filter(Boolean) || [];
2087
2142
  const uniqueTools = [...new Set(toolsUsed)];
2088
- if (verbose && uniqueTools.length > 0) {
2089
- console.log(`[ReActWrapper:${workerId}] Tools used:`, uniqueTools.join(", "));
2143
+ if (uniqueTools.length > 0) {
2144
+ logger.debug("Tools used by ReAct agent", { workerId, tools: uniqueTools });
2090
2145
  }
2091
2146
  const taskResult = {
2092
2147
  assignmentId: currentAssignment.id,
@@ -2106,7 +2161,15 @@ function wrapReActAgent(workerId, agent, verbose = false) {
2106
2161
  status: "routing"
2107
2162
  };
2108
2163
  } catch (error) {
2109
- console.error(`[ReActWrapper:${workerId}] Error:`, error);
2164
+ if (error && typeof error === "object" && "constructor" in error && error.constructor.name === "GraphInterrupt") {
2165
+ logger.debug("GraphInterrupt detected - re-throwing", { workerId });
2166
+ throw error;
2167
+ }
2168
+ logger.error("Error in ReAct agent execution", {
2169
+ workerId,
2170
+ error: error instanceof Error ? error.message : String(error),
2171
+ stack: error instanceof Error ? error.stack : void 0
2172
+ });
2110
2173
  const currentAssignment = state.activeAssignments.find(
2111
2174
  (assignment) => assignment.workerId === workerId
2112
2175
  );
@@ -2135,7 +2198,7 @@ function wrapReActAgent(workerId, agent, verbose = false) {
2135
2198
 
2136
2199
  // src/multi-agent/nodes.ts
2137
2200
  var import_messages5 = require("@langchain/core/messages");
2138
- var import_core7 = require("@agentforge/core");
2201
+ var import_core8 = require("@agentforge/core");
2139
2202
  var DEFAULT_AGGREGATOR_SYSTEM_PROMPT = `You are an aggregator agent responsible for combining results from multiple worker agents.
2140
2203
 
2141
2204
  Your job is to:
@@ -2229,7 +2292,7 @@ function createWorkerNode(config) {
2229
2292
  executeFn,
2230
2293
  agent
2231
2294
  } = config;
2232
- return async (state) => {
2295
+ return async (state, runConfig) => {
2233
2296
  try {
2234
2297
  if (verbose) {
2235
2298
  console.log(`[Worker:${id}] Executing task`);
@@ -2250,7 +2313,7 @@ function createWorkerNode(config) {
2250
2313
  if (verbose) {
2251
2314
  console.log(`[Worker:${id}] Using custom executeFn`);
2252
2315
  }
2253
- return await executeFn(state);
2316
+ return await executeFn(state, runConfig);
2254
2317
  }
2255
2318
  if (agent) {
2256
2319
  if (isReActAgent(agent)) {
@@ -2258,7 +2321,7 @@ function createWorkerNode(config) {
2258
2321
  console.log(`[Worker:${id}] Using ReAct agent (auto-wrapped)`);
2259
2322
  }
2260
2323
  const wrappedFn = wrapReActAgent(id, agent, verbose);
2261
- return await wrappedFn(state);
2324
+ return await wrappedFn(state, runConfig);
2262
2325
  } else {
2263
2326
  console.warn(`[Worker:${id}] Agent provided but does not appear to be a ReAct agent. Falling back to default execution.`);
2264
2327
  }
@@ -2279,7 +2342,7 @@ Execute the assigned task using your skills and tools. Provide a clear, actionab
2279
2342
  ];
2280
2343
  let modelToUse = model;
2281
2344
  if (tools.length > 0 && model.bindTools) {
2282
- const langchainTools = (0, import_core7.toLangChainTools)(tools);
2345
+ const langchainTools = (0, import_core8.toLangChainTools)(tools);
2283
2346
  modelToUse = model.bindTools(langchainTools);
2284
2347
  }
2285
2348
  const response = await modelToUse.invoke(messages);
@@ -2324,6 +2387,9 @@ Execute the assigned task using your skills and tools. Provide a clear, actionab
2324
2387
  status: "routing"
2325
2388
  };
2326
2389
  } catch (error) {
2390
+ if (error && typeof error === "object" && "constructor" in error && error.constructor.name === "GraphInterrupt") {
2391
+ throw error;
2392
+ }
2327
2393
  console.error(`[Worker:${id}] Error:`, error);
2328
2394
  const currentAssignment = state.activeAssignments.find(
2329
2395
  (assignment) => assignment.workerId === id
@@ -2419,6 +2485,7 @@ Please synthesize these results into a comprehensive response that addresses the
2419
2485
 
2420
2486
  // src/multi-agent/agent.ts
2421
2487
  var import_langgraph4 = require("@langchain/langgraph");
2488
+ var import_core9 = require("@agentforge/core");
2422
2489
  function createMultiAgentSystem(config) {
2423
2490
  const {
2424
2491
  supervisor,
@@ -2429,11 +2496,13 @@ function createMultiAgentSystem(config) {
2429
2496
  checkpointer
2430
2497
  } = config;
2431
2498
  const workflow = new import_langgraph4.StateGraph(MultiAgentState);
2432
- const supervisorNode = createSupervisorNode({
2433
- ...supervisor,
2434
- maxIterations,
2435
- verbose
2436
- });
2499
+ let supervisorConfig = { ...supervisor, maxIterations, verbose };
2500
+ if (supervisor.model && supervisor.tools && supervisor.tools.length > 0) {
2501
+ const langchainTools = (0, import_core9.toLangChainTools)(supervisor.tools);
2502
+ const modelWithTools = supervisor.model.bindTools(langchainTools);
2503
+ supervisorConfig.model = modelWithTools;
2504
+ }
2505
+ const supervisorNode = createSupervisorNode(supervisorConfig);
2437
2506
  workflow.addNode("supervisor", supervisorNode);
2438
2507
  const workerIds = [];
2439
2508
  const workerCapabilities = {};
package/dist/index.d.cts CHANGED
@@ -2411,6 +2411,37 @@ interface SupervisorConfig {
2411
2411
  * Maximum number of routing iterations
2412
2412
  */
2413
2413
  maxIterations?: number;
2414
+ /**
2415
+ * Optional tools the supervisor can use during routing
2416
+ *
2417
+ * Enables the supervisor to gather additional information before making routing decisions.
2418
+ * Common use case: askHuman tool for clarifying ambiguous queries.
2419
+ *
2420
+ * Note: Only works with LLM-based routing strategy.
2421
+ *
2422
+ * @example
2423
+ * ```typescript
2424
+ * import { createAskHumanTool } from '@agentforge/tools';
2425
+ *
2426
+ * const system = createMultiAgentSystem({
2427
+ * supervisor: {
2428
+ * strategy: 'llm-based',
2429
+ * model: chatModel,
2430
+ * tools: [createAskHumanTool()],
2431
+ * },
2432
+ * // ...
2433
+ * });
2434
+ * ```
2435
+ */
2436
+ tools?: Tool<any, any>[];
2437
+ /**
2438
+ * Maximum number of tool call retries before requiring routing decision
2439
+ *
2440
+ * Prevents infinite loops where the supervisor keeps calling tools without making a routing decision.
2441
+ *
2442
+ * @default 3
2443
+ */
2444
+ maxToolRetries?: number;
2414
2445
  }
2415
2446
  /**
2416
2447
  * Configuration for a worker agent node
@@ -2445,8 +2476,11 @@ interface WorkerConfig {
2445
2476
  *
2446
2477
  * If provided, this function will be used to execute tasks for this worker.
2447
2478
  * Takes precedence over the `agent` property.
2479
+ *
2480
+ * The config parameter contains LangGraph runtime configuration including
2481
+ * thread_id for checkpointing, which is required for interrupt functionality.
2448
2482
  */
2449
- executeFn?: (state: MultiAgentStateType) => Promise<Partial<MultiAgentStateType>>;
2483
+ executeFn?: (state: MultiAgentStateType, config?: any) => Promise<Partial<MultiAgentStateType>>;
2450
2484
  /**
2451
2485
  * ReAct agent instance
2452
2486
  *
@@ -2576,6 +2610,8 @@ declare const DEFAULT_SUPERVISOR_SYSTEM_PROMPT = "You are a supervisor agent res
2576
2610
  /**
2577
2611
  * LLM-based routing strategy
2578
2612
  * Uses an LLM to intelligently route tasks based on worker capabilities
2613
+ *
2614
+ * Supports tool calls (e.g., askHuman) for gathering additional information before routing.
2579
2615
  */
2580
2616
  declare const llmBasedRouting: RoutingStrategyImpl;
2581
2617
  /**
@@ -2622,7 +2658,7 @@ declare function createSupervisorNode(config: SupervisorConfig): (state: MultiAg
2622
2658
  /**
2623
2659
  * Create a worker agent node
2624
2660
  */
2625
- declare function createWorkerNode(config: WorkerConfig): (state: MultiAgentStateType) => Promise<Partial<MultiAgentStateType>>;
2661
+ declare function createWorkerNode(config: WorkerConfig): (state: MultiAgentStateType, runConfig?: any) => Promise<Partial<MultiAgentStateType>>;
2626
2662
  /**
2627
2663
  * Create an aggregator node that combines worker results
2628
2664
  */
package/dist/index.d.ts CHANGED
@@ -2411,6 +2411,37 @@ interface SupervisorConfig {
2411
2411
  * Maximum number of routing iterations
2412
2412
  */
2413
2413
  maxIterations?: number;
2414
+ /**
2415
+ * Optional tools the supervisor can use during routing
2416
+ *
2417
+ * Enables the supervisor to gather additional information before making routing decisions.
2418
+ * Common use case: askHuman tool for clarifying ambiguous queries.
2419
+ *
2420
+ * Note: Only works with LLM-based routing strategy.
2421
+ *
2422
+ * @example
2423
+ * ```typescript
2424
+ * import { createAskHumanTool } from '@agentforge/tools';
2425
+ *
2426
+ * const system = createMultiAgentSystem({
2427
+ * supervisor: {
2428
+ * strategy: 'llm-based',
2429
+ * model: chatModel,
2430
+ * tools: [createAskHumanTool()],
2431
+ * },
2432
+ * // ...
2433
+ * });
2434
+ * ```
2435
+ */
2436
+ tools?: Tool<any, any>[];
2437
+ /**
2438
+ * Maximum number of tool call retries before requiring routing decision
2439
+ *
2440
+ * Prevents infinite loops where the supervisor keeps calling tools without making a routing decision.
2441
+ *
2442
+ * @default 3
2443
+ */
2444
+ maxToolRetries?: number;
2414
2445
  }
2415
2446
  /**
2416
2447
  * Configuration for a worker agent node
@@ -2445,8 +2476,11 @@ interface WorkerConfig {
2445
2476
  *
2446
2477
  * If provided, this function will be used to execute tasks for this worker.
2447
2478
  * Takes precedence over the `agent` property.
2479
+ *
2480
+ * The config parameter contains LangGraph runtime configuration including
2481
+ * thread_id for checkpointing, which is required for interrupt functionality.
2448
2482
  */
2449
- executeFn?: (state: MultiAgentStateType) => Promise<Partial<MultiAgentStateType>>;
2483
+ executeFn?: (state: MultiAgentStateType, config?: any) => Promise<Partial<MultiAgentStateType>>;
2450
2484
  /**
2451
2485
  * ReAct agent instance
2452
2486
  *
@@ -2576,6 +2610,8 @@ declare const DEFAULT_SUPERVISOR_SYSTEM_PROMPT = "You are a supervisor agent res
2576
2610
  /**
2577
2611
  * LLM-based routing strategy
2578
2612
  * Uses an LLM to intelligently route tasks based on worker capabilities
2613
+ *
2614
+ * Supports tool calls (e.g., askHuman) for gathering additional information before routing.
2579
2615
  */
2580
2616
  declare const llmBasedRouting: RoutingStrategyImpl;
2581
2617
  /**
@@ -2622,7 +2658,7 @@ declare function createSupervisorNode(config: SupervisorConfig): (state: MultiAg
2622
2658
  /**
2623
2659
  * Create a worker agent node
2624
2660
  */
2625
- declare function createWorkerNode(config: WorkerConfig): (state: MultiAgentStateType) => Promise<Partial<MultiAgentStateType>>;
2661
+ declare function createWorkerNode(config: WorkerConfig): (state: MultiAgentStateType, runConfig?: any) => Promise<Partial<MultiAgentStateType>>;
2626
2662
  /**
2627
2663
  * Create an aggregator node that combines worker results
2628
2664
  */
package/dist/index.js CHANGED
@@ -228,6 +228,9 @@ function createActionNode(tools, verbose = false) {
228
228
  console.log(`[action] Tool '${action.name}' executed successfully`);
229
229
  }
230
230
  } catch (error) {
231
+ if (error && typeof error === "object" && "constructor" in error && error.constructor.name === "GraphInterrupt") {
232
+ throw error;
233
+ }
231
234
  const errorMessage = error instanceof Error ? error.message : String(error);
232
235
  observations.push({
233
236
  toolCallId: action.id,
@@ -1791,7 +1794,34 @@ var MultiAgentStateConfig = {
1791
1794
  var MultiAgentState = createStateAnnotation4(MultiAgentStateConfig);
1792
1795
 
1793
1796
  // src/multi-agent/routing.ts
1794
- import { HumanMessage as HumanMessage4, SystemMessage as SystemMessage4 } from "@langchain/core/messages";
1797
+ import { HumanMessage as HumanMessage4, SystemMessage as SystemMessage4, AIMessage as AIMessage2, ToolMessage as ToolMessage2 } from "@langchain/core/messages";
1798
+ async function executeTools(toolCalls, tools) {
1799
+ const results = [];
1800
+ for (const toolCall of toolCalls) {
1801
+ const tool = tools.find((t) => t.metadata.name === toolCall.name);
1802
+ if (!tool) {
1803
+ results.push(new ToolMessage2({
1804
+ content: `Error: Tool '${toolCall.name}' not found`,
1805
+ tool_call_id: toolCall.id
1806
+ }));
1807
+ continue;
1808
+ }
1809
+ try {
1810
+ const result = await tool.execute(toolCall.args);
1811
+ const content = typeof result === "string" ? result : JSON.stringify(result);
1812
+ results.push(new ToolMessage2({
1813
+ content,
1814
+ tool_call_id: toolCall.id
1815
+ }));
1816
+ } catch (error) {
1817
+ results.push(new ToolMessage2({
1818
+ content: `Error executing tool: ${error.message}`,
1819
+ tool_call_id: toolCall.id
1820
+ }));
1821
+ }
1822
+ }
1823
+ return results;
1824
+ }
1795
1825
  var DEFAULT_SUPERVISOR_SYSTEM_PROMPT = `You are a supervisor agent responsible for routing tasks to specialized worker agents.
1796
1826
 
1797
1827
  Your job is to:
@@ -1814,11 +1844,13 @@ var llmBasedRouting = {
1814
1844
  throw new Error("LLM-based routing requires a model to be configured");
1815
1845
  }
1816
1846
  const systemPrompt = config.systemPrompt || DEFAULT_SUPERVISOR_SYSTEM_PROMPT;
1847
+ const maxRetries = config.maxToolRetries || 3;
1848
+ const tools = config.tools || [];
1817
1849
  const workerInfo = Object.entries(state.workers).map(([id, caps]) => {
1818
1850
  const skills = caps.skills.join(", ");
1819
- const tools = caps.tools.join(", ");
1851
+ const tools2 = caps.tools.join(", ");
1820
1852
  const available = caps.available ? "available" : "busy";
1821
- return `- ${id}: Skills: [${skills}], Tools: [${tools}], Status: ${available}, Workload: ${caps.currentWorkload}`;
1853
+ return `- ${id}: Skills: [${skills}], Tools: [${tools2}], Status: ${available}, Workload: ${caps.currentWorkload}`;
1822
1854
  }).join("\n");
1823
1855
  const lastMessage = state.messages[state.messages.length - 1];
1824
1856
  const taskContext = lastMessage?.content || state.input;
@@ -1828,24 +1860,42 @@ Available workers:
1828
1860
  ${workerInfo}
1829
1861
 
1830
1862
  Select the best worker for this task and explain your reasoning.`;
1831
- const messages = [
1832
- new SystemMessage4(systemPrompt),
1833
- new HumanMessage4(userPrompt)
1834
- ];
1835
- const response = await config.model.invoke(messages);
1836
- const content = typeof response.content === "string" ? response.content : JSON.stringify(response.content);
1837
- try {
1838
- const decision = JSON.parse(content);
1839
- return {
1840
- targetAgent: decision.targetAgent,
1841
- reasoning: decision.reasoning,
1842
- confidence: decision.confidence,
1843
- strategy: "llm-based",
1844
- timestamp: Date.now()
1845
- };
1846
- } catch (error) {
1847
- throw new Error(`Failed to parse routing decision from LLM: ${error}`);
1863
+ const conversationHistory = [];
1864
+ let attempt = 0;
1865
+ while (attempt < maxRetries) {
1866
+ const messages = [
1867
+ new SystemMessage4(systemPrompt),
1868
+ new HumanMessage4(userPrompt),
1869
+ ...conversationHistory
1870
+ ];
1871
+ const response = await config.model.invoke(messages);
1872
+ if (response.tool_calls && response.tool_calls.length > 0) {
1873
+ if (tools.length === 0) {
1874
+ throw new Error("LLM requested tool calls but no tools are configured");
1875
+ }
1876
+ const toolResults = await executeTools(response.tool_calls, tools);
1877
+ conversationHistory.push(
1878
+ new AIMessage2({ content: response.content || "", tool_calls: response.tool_calls }),
1879
+ ...toolResults
1880
+ );
1881
+ attempt++;
1882
+ continue;
1883
+ }
1884
+ const content = typeof response.content === "string" ? response.content : JSON.stringify(response.content);
1885
+ try {
1886
+ const decision = JSON.parse(content);
1887
+ return {
1888
+ targetAgent: decision.targetAgent,
1889
+ reasoning: decision.reasoning,
1890
+ confidence: decision.confidence,
1891
+ strategy: "llm-based",
1892
+ timestamp: Date.now()
1893
+ };
1894
+ } catch (error) {
1895
+ throw new Error(`Failed to parse routing decision from LLM: ${error}`);
1896
+ }
1848
1897
  }
1898
+ throw new Error(`Max tool retries (${maxRetries}) exceeded without routing decision`);
1849
1899
  }
1850
1900
  };
1851
1901
  var roundRobinRouting = {
@@ -1951,43 +2001,48 @@ function getRoutingStrategy(name) {
1951
2001
  }
1952
2002
 
1953
2003
  // src/multi-agent/utils.ts
2004
+ import { createLogger, LogLevel } from "@agentforge/core";
2005
+ var logLevel = process.env.LOG_LEVEL?.toLowerCase() || LogLevel.INFO;
2006
+ var logger = createLogger("multi-agent", { level: logLevel });
1954
2007
  function isReActAgent(obj) {
1955
2008
  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
1956
2009
  (obj.constructor?.name === "CompiledGraph" || obj.constructor?.name === "CompiledStateGraph");
1957
2010
  }
1958
2011
  function wrapReActAgent(workerId, agent, verbose = false) {
1959
- return async (state) => {
2012
+ return async (state, config) => {
1960
2013
  try {
1961
- if (verbose) {
1962
- console.log(`[ReActWrapper:${workerId}] Wrapping ReAct agent execution`);
1963
- }
2014
+ logger.debug("Wrapping ReAct agent execution", { workerId });
1964
2015
  const task = state.messages[state.messages.length - 1]?.content || state.input;
1965
- if (verbose) {
1966
- console.log(`[ReActWrapper:${workerId}] Task:`, task.substring(0, 100) + "...");
1967
- }
2016
+ logger.debug("Extracted task", {
2017
+ workerId,
2018
+ taskPreview: task.substring(0, 100) + (task.length > 100 ? "..." : "")
2019
+ });
1968
2020
  const currentAssignment = state.activeAssignments.find(
1969
2021
  (assignment) => assignment.workerId === workerId && !state.completedTasks.some((task2) => task2.assignmentId === assignment.id)
1970
2022
  );
1971
2023
  if (!currentAssignment) {
1972
- if (verbose) {
1973
- console.log(`[ReActWrapper:${workerId}] No active assignment found`);
1974
- }
2024
+ logger.debug("No active assignment found", { workerId });
1975
2025
  return {
1976
2026
  currentAgent: "supervisor",
1977
2027
  status: "routing"
1978
2028
  };
1979
2029
  }
1980
- const result = await agent.invoke({
1981
- messages: [{ role: "user", content: task }]
1982
- });
2030
+ const result = await agent.invoke(
2031
+ {
2032
+ messages: [{ role: "user", content: task }]
2033
+ },
2034
+ config
2035
+ // Pass through the config for checkpointing and interrupt support
2036
+ );
1983
2037
  const response = result.messages?.[result.messages.length - 1]?.content || "No response";
1984
- if (verbose) {
1985
- console.log(`[ReActWrapper:${workerId}] Response:`, response.substring(0, 100) + "...");
1986
- }
2038
+ logger.debug("Received response from ReAct agent", {
2039
+ workerId,
2040
+ responsePreview: response.substring(0, 100) + (response.length > 100 ? "..." : "")
2041
+ });
1987
2042
  const toolsUsed = result.actions?.map((action) => action.name).filter(Boolean) || [];
1988
2043
  const uniqueTools = [...new Set(toolsUsed)];
1989
- if (verbose && uniqueTools.length > 0) {
1990
- console.log(`[ReActWrapper:${workerId}] Tools used:`, uniqueTools.join(", "));
2044
+ if (uniqueTools.length > 0) {
2045
+ logger.debug("Tools used by ReAct agent", { workerId, tools: uniqueTools });
1991
2046
  }
1992
2047
  const taskResult = {
1993
2048
  assignmentId: currentAssignment.id,
@@ -2007,7 +2062,15 @@ function wrapReActAgent(workerId, agent, verbose = false) {
2007
2062
  status: "routing"
2008
2063
  };
2009
2064
  } catch (error) {
2010
- console.error(`[ReActWrapper:${workerId}] Error:`, error);
2065
+ if (error && typeof error === "object" && "constructor" in error && error.constructor.name === "GraphInterrupt") {
2066
+ logger.debug("GraphInterrupt detected - re-throwing", { workerId });
2067
+ throw error;
2068
+ }
2069
+ logger.error("Error in ReAct agent execution", {
2070
+ workerId,
2071
+ error: error instanceof Error ? error.message : String(error),
2072
+ stack: error instanceof Error ? error.stack : void 0
2073
+ });
2011
2074
  const currentAssignment = state.activeAssignments.find(
2012
2075
  (assignment) => assignment.workerId === workerId
2013
2076
  );
@@ -2130,7 +2193,7 @@ function createWorkerNode(config) {
2130
2193
  executeFn,
2131
2194
  agent
2132
2195
  } = config;
2133
- return async (state) => {
2196
+ return async (state, runConfig) => {
2134
2197
  try {
2135
2198
  if (verbose) {
2136
2199
  console.log(`[Worker:${id}] Executing task`);
@@ -2151,7 +2214,7 @@ function createWorkerNode(config) {
2151
2214
  if (verbose) {
2152
2215
  console.log(`[Worker:${id}] Using custom executeFn`);
2153
2216
  }
2154
- return await executeFn(state);
2217
+ return await executeFn(state, runConfig);
2155
2218
  }
2156
2219
  if (agent) {
2157
2220
  if (isReActAgent(agent)) {
@@ -2159,7 +2222,7 @@ function createWorkerNode(config) {
2159
2222
  console.log(`[Worker:${id}] Using ReAct agent (auto-wrapped)`);
2160
2223
  }
2161
2224
  const wrappedFn = wrapReActAgent(id, agent, verbose);
2162
- return await wrappedFn(state);
2225
+ return await wrappedFn(state, runConfig);
2163
2226
  } else {
2164
2227
  console.warn(`[Worker:${id}] Agent provided but does not appear to be a ReAct agent. Falling back to default execution.`);
2165
2228
  }
@@ -2225,6 +2288,9 @@ Execute the assigned task using your skills and tools. Provide a clear, actionab
2225
2288
  status: "routing"
2226
2289
  };
2227
2290
  } catch (error) {
2291
+ if (error && typeof error === "object" && "constructor" in error && error.constructor.name === "GraphInterrupt") {
2292
+ throw error;
2293
+ }
2228
2294
  console.error(`[Worker:${id}] Error:`, error);
2229
2295
  const currentAssignment = state.activeAssignments.find(
2230
2296
  (assignment) => assignment.workerId === id
@@ -2320,6 +2386,7 @@ Please synthesize these results into a comprehensive response that addresses the
2320
2386
 
2321
2387
  // src/multi-agent/agent.ts
2322
2388
  import { StateGraph as StateGraph4, END as END4 } from "@langchain/langgraph";
2389
+ import { toLangChainTools as toLangChainTools3 } from "@agentforge/core";
2323
2390
  function createMultiAgentSystem(config) {
2324
2391
  const {
2325
2392
  supervisor,
@@ -2330,11 +2397,13 @@ function createMultiAgentSystem(config) {
2330
2397
  checkpointer
2331
2398
  } = config;
2332
2399
  const workflow = new StateGraph4(MultiAgentState);
2333
- const supervisorNode = createSupervisorNode({
2334
- ...supervisor,
2335
- maxIterations,
2336
- verbose
2337
- });
2400
+ let supervisorConfig = { ...supervisor, maxIterations, verbose };
2401
+ if (supervisor.model && supervisor.tools && supervisor.tools.length > 0) {
2402
+ const langchainTools = toLangChainTools3(supervisor.tools);
2403
+ const modelWithTools = supervisor.model.bindTools(langchainTools);
2404
+ supervisorConfig.model = modelWithTools;
2405
+ }
2406
+ const supervisorNode = createSupervisorNode(supervisorConfig);
2338
2407
  workflow.addNode("supervisor", supervisorNode);
2339
2408
  const workerIds = [];
2340
2409
  const workerCapabilities = {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentforge/patterns",
3
- "version": "0.5.4",
3
+ "version": "0.6.1",
4
4
  "description": "Agent patterns (ReAct, Planner-Executor) for AgentForge framework",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",