@librechat/agents 3.4.0 → 3.4.2

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.
Files changed (204) hide show
  1. package/dist/cjs/agents/AgentContext.cjs.map +1 -1
  2. package/dist/cjs/graphs/Graph.cjs +165 -57
  3. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  4. package/dist/cjs/graphs/MultiAgentGraph.cjs +81 -33
  5. package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
  6. package/dist/cjs/graphs/applyGraphRuntimeConfig.cjs +14 -0
  7. package/dist/cjs/graphs/applyGraphRuntimeConfig.cjs.map +1 -0
  8. package/dist/cjs/graphs/createGraph.cjs +15 -0
  9. package/dist/cjs/graphs/createGraph.cjs.map +1 -0
  10. package/dist/cjs/graphs/index.cjs +1 -0
  11. package/dist/cjs/hooks/HookRegistry.cjs +83 -0
  12. package/dist/cjs/hooks/HookRegistry.cjs.map +1 -1
  13. package/dist/cjs/hooks/createWorkspacePolicyHook.cjs +1 -1
  14. package/dist/cjs/hooks/executeHooks.cjs +60 -14
  15. package/dist/cjs/hooks/executeHooks.cjs.map +1 -1
  16. package/dist/cjs/hooks/index.cjs.map +1 -1
  17. package/dist/cjs/hooks/types.cjs +2 -0
  18. package/dist/cjs/hooks/types.cjs.map +1 -1
  19. package/dist/cjs/llm/init.cjs +3 -3
  20. package/dist/cjs/llm/invoke.cjs +2 -2
  21. package/dist/cjs/main.cjs +34 -24
  22. package/dist/cjs/messages/format.cjs +40 -23
  23. package/dist/cjs/messages/format.cjs.map +1 -1
  24. package/dist/cjs/run.cjs +130 -53
  25. package/dist/cjs/run.cjs.map +1 -1
  26. package/dist/cjs/session/AgentSession.cjs +37 -2
  27. package/dist/cjs/session/AgentSession.cjs.map +1 -1
  28. package/dist/cjs/session/JsonlSessionStore.cjs +2 -2
  29. package/dist/cjs/stream.cjs +1 -1
  30. package/dist/cjs/summarization/node.cjs +6 -3
  31. package/dist/cjs/summarization/node.cjs.map +1 -1
  32. package/dist/cjs/tools/BashExecutor.cjs +1 -1
  33. package/dist/cjs/tools/CodeExecutor.cjs +1 -1
  34. package/dist/cjs/tools/ProgrammaticToolCalling.cjs +1 -1
  35. package/dist/cjs/tools/SubagentTool.cjs +4 -4
  36. package/dist/cjs/tools/SubagentTool.cjs.map +1 -1
  37. package/dist/cjs/tools/ToolNode.cjs +321 -95
  38. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  39. package/dist/cjs/tools/ToolSearch.cjs +1 -1
  40. package/dist/cjs/tools/eagerEventExecution.cjs +1 -0
  41. package/dist/cjs/tools/local/LocalExecutionEngine.cjs +3 -3
  42. package/dist/cjs/tools/local/LocalProgrammaticToolCalling.cjs +2 -2
  43. package/dist/cjs/tools/search/rerankers.cjs +154 -1
  44. package/dist/cjs/tools/search/rerankers.cjs.map +1 -1
  45. package/dist/cjs/tools/search/tool.cjs +4 -1
  46. package/dist/cjs/tools/search/tool.cjs.map +1 -1
  47. package/dist/cjs/tools/subagent/SubagentExecutionRegistry.cjs +601 -0
  48. package/dist/cjs/tools/subagent/SubagentExecutionRegistry.cjs.map +1 -0
  49. package/dist/cjs/tools/subagent/SubagentExecutor.cjs +1114 -233
  50. package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
  51. package/dist/cjs/tools/subagent/SubagentReplay.cjs +177 -0
  52. package/dist/cjs/tools/subagent/SubagentReplay.cjs.map +1 -0
  53. package/dist/cjs/tools/subagent/childGraphConfig.cjs +278 -0
  54. package/dist/cjs/tools/subagent/childGraphConfig.cjs.map +1 -0
  55. package/dist/cjs/tools/subagent/index.cjs +1 -0
  56. package/dist/cjs/tools/subagent/runtimeLimits.cjs +5 -0
  57. package/dist/cjs/tools/subagent/runtimeLimits.cjs.map +1 -0
  58. package/dist/cjs/tools/toolOutputReferences.cjs +20 -0
  59. package/dist/cjs/tools/toolOutputReferences.cjs.map +1 -1
  60. package/dist/cjs/utils/index.cjs +2 -2
  61. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  62. package/dist/esm/graphs/Graph.mjs +165 -57
  63. package/dist/esm/graphs/Graph.mjs.map +1 -1
  64. package/dist/esm/graphs/MultiAgentGraph.mjs +81 -33
  65. package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
  66. package/dist/esm/graphs/applyGraphRuntimeConfig.mjs +14 -0
  67. package/dist/esm/graphs/applyGraphRuntimeConfig.mjs.map +1 -0
  68. package/dist/esm/graphs/createGraph.mjs +15 -0
  69. package/dist/esm/graphs/createGraph.mjs.map +1 -0
  70. package/dist/esm/graphs/index.mjs +1 -0
  71. package/dist/esm/hooks/HookRegistry.mjs +83 -0
  72. package/dist/esm/hooks/HookRegistry.mjs.map +1 -1
  73. package/dist/esm/hooks/createWorkspacePolicyHook.mjs +1 -1
  74. package/dist/esm/hooks/executeHooks.mjs +60 -14
  75. package/dist/esm/hooks/executeHooks.mjs.map +1 -1
  76. package/dist/esm/hooks/index.mjs.map +1 -1
  77. package/dist/esm/hooks/types.mjs +2 -1
  78. package/dist/esm/hooks/types.mjs.map +1 -1
  79. package/dist/esm/llm/init.mjs +1 -1
  80. package/dist/esm/llm/invoke.mjs +2 -2
  81. package/dist/esm/main.mjs +21 -19
  82. package/dist/esm/messages/format.mjs +40 -23
  83. package/dist/esm/messages/format.mjs.map +1 -1
  84. package/dist/esm/run.mjs +131 -54
  85. package/dist/esm/run.mjs.map +1 -1
  86. package/dist/esm/session/AgentSession.mjs +37 -2
  87. package/dist/esm/session/AgentSession.mjs.map +1 -1
  88. package/dist/esm/session/JsonlSessionStore.mjs +2 -2
  89. package/dist/esm/stream.mjs +1 -1
  90. package/dist/esm/summarization/node.mjs +6 -3
  91. package/dist/esm/summarization/node.mjs.map +1 -1
  92. package/dist/esm/tools/BashExecutor.mjs +1 -1
  93. package/dist/esm/tools/CodeExecutor.mjs +1 -1
  94. package/dist/esm/tools/ProgrammaticToolCalling.mjs +1 -1
  95. package/dist/esm/tools/SubagentTool.mjs +4 -4
  96. package/dist/esm/tools/SubagentTool.mjs.map +1 -1
  97. package/dist/esm/tools/ToolNode.mjs +322 -96
  98. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  99. package/dist/esm/tools/ToolSearch.mjs +1 -1
  100. package/dist/esm/tools/eagerEventExecution.mjs +1 -1
  101. package/dist/esm/tools/local/LocalExecutionEngine.mjs +3 -3
  102. package/dist/esm/tools/local/LocalProgrammaticToolCalling.mjs +2 -2
  103. package/dist/esm/tools/search/rerankers.mjs +154 -1
  104. package/dist/esm/tools/search/rerankers.mjs.map +1 -1
  105. package/dist/esm/tools/search/tool.mjs +4 -1
  106. package/dist/esm/tools/search/tool.mjs.map +1 -1
  107. package/dist/esm/tools/subagent/SubagentExecutionRegistry.mjs +597 -0
  108. package/dist/esm/tools/subagent/SubagentExecutionRegistry.mjs.map +1 -0
  109. package/dist/esm/tools/subagent/SubagentExecutor.mjs +1114 -233
  110. package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
  111. package/dist/esm/tools/subagent/SubagentReplay.mjs +170 -0
  112. package/dist/esm/tools/subagent/SubagentReplay.mjs.map +1 -0
  113. package/dist/esm/tools/subagent/childGraphConfig.mjs +272 -0
  114. package/dist/esm/tools/subagent/childGraphConfig.mjs.map +1 -0
  115. package/dist/esm/tools/subagent/index.mjs +1 -0
  116. package/dist/esm/tools/subagent/runtimeLimits.mjs +5 -0
  117. package/dist/esm/tools/subagent/runtimeLimits.mjs.map +1 -0
  118. package/dist/esm/tools/toolOutputReferences.mjs +20 -0
  119. package/dist/esm/tools/toolOutputReferences.mjs.map +1 -1
  120. package/dist/esm/utils/index.mjs +2 -2
  121. package/dist/types/agents/AgentContext.d.ts +1 -1
  122. package/dist/types/graphs/Graph.d.ts +22 -2
  123. package/dist/types/graphs/MultiAgentGraph.d.ts +7 -1
  124. package/dist/types/graphs/applyGraphRuntimeConfig.d.ts +3 -0
  125. package/dist/types/graphs/createGraph.d.ts +13 -0
  126. package/dist/types/graphs/graphFactory.d.ts +13 -0
  127. package/dist/types/graphs/index.d.ts +2 -0
  128. package/dist/types/hooks/HookRegistry.d.ts +10 -1
  129. package/dist/types/hooks/executeHooks.d.ts +5 -1
  130. package/dist/types/hooks/index.d.ts +2 -2
  131. package/dist/types/hooks/types.d.ts +12 -0
  132. package/dist/types/run.d.ts +6 -3
  133. package/dist/types/session/AgentSession.d.ts +1 -0
  134. package/dist/types/tools/SubagentTool.d.ts +1 -1
  135. package/dist/types/tools/ToolNode.d.ts +23 -15
  136. package/dist/types/tools/search/rerankers.d.ts +28 -0
  137. package/dist/types/tools/search/types.d.ts +38 -1
  138. package/dist/types/tools/subagent/SubagentExecutionRegistry.d.ts +171 -0
  139. package/dist/types/tools/subagent/SubagentExecutor.d.ts +70 -36
  140. package/dist/types/tools/subagent/SubagentReplay.d.ts +85 -0
  141. package/dist/types/tools/subagent/childGraphConfig.d.ts +37 -0
  142. package/dist/types/tools/subagent/index.d.ts +1 -1
  143. package/dist/types/tools/subagent/runtimeLimits.d.ts +4 -0
  144. package/dist/types/tools/toolOutputReferences.d.ts +12 -0
  145. package/dist/types/types/graph.d.ts +189 -24
  146. package/dist/types/types/hitl.d.ts +15 -0
  147. package/package.json +8 -2
  148. package/src/agents/AgentContext.ts +1 -1
  149. package/src/graphs/Graph.ts +349 -104
  150. package/src/graphs/MultiAgentGraph.ts +149 -47
  151. package/src/graphs/__tests__/Graph.breakerLifecycle.test.ts +48 -9
  152. package/src/graphs/__tests__/Graph.contextOverflow.test.ts +1 -1
  153. package/src/graphs/__tests__/Graph.subagentResumeState.test.ts +80 -0
  154. package/src/graphs/__tests__/MultiAgentGraph.test.ts +86 -0
  155. package/src/graphs/__tests__/composition.smoke.test.ts +152 -0
  156. package/src/graphs/__tests__/createGraph.test.ts +206 -0
  157. package/src/graphs/applyGraphRuntimeConfig.ts +25 -0
  158. package/src/graphs/createGraph.ts +32 -0
  159. package/src/graphs/graphFactory.ts +12 -0
  160. package/src/graphs/index.ts +2 -0
  161. package/src/hooks/HookRegistry.ts +151 -1
  162. package/src/hooks/__tests__/HookRegistry.test.ts +96 -0
  163. package/src/hooks/__tests__/executeHooks.test.ts +160 -0
  164. package/src/hooks/executeHooks.ts +133 -15
  165. package/src/hooks/index.ts +3 -1
  166. package/src/hooks/types.ts +16 -0
  167. package/src/messages/format.ts +54 -26
  168. package/src/messages/formatAgentMessages.reducer.test.ts +162 -0
  169. package/src/messages/formatAgentMessages.steer.test.ts +16 -11
  170. package/src/messages/formatAgentMessages.test.ts +7 -2
  171. package/src/run.ts +214 -56
  172. package/src/session/AgentSession.ts +52 -2
  173. package/src/session/__tests__/JsonlSessionStore.test.ts +53 -0
  174. package/src/specs/graph-subagent.live.test.ts +242 -0
  175. package/src/specs/graph-subagent.test.ts +322 -0
  176. package/src/specs/langfuse-routing.integration.test.ts +120 -1
  177. package/src/specs/subagent.test.ts +130 -4
  178. package/src/summarization/__tests__/node.test.ts +32 -14
  179. package/src/summarization/node.ts +15 -11
  180. package/src/tools/SubagentTool.ts +4 -4
  181. package/src/tools/ToolNode.ts +643 -157
  182. package/src/tools/__tests__/SubagentExecutor.test.ts +740 -15
  183. package/src/tools/__tests__/SubagentReplay.test.ts +329 -0
  184. package/src/tools/__tests__/ToolNode.breakerSignal.test.ts +208 -2
  185. package/src/tools/__tests__/directToolHITLResumeScope.test.ts +512 -2
  186. package/src/tools/__tests__/hitl.test.ts +234 -5
  187. package/src/tools/__tests__/subagentHooks.test.ts +1126 -61
  188. package/src/tools/__tests__/toolOutputReferences.test.ts +19 -2
  189. package/src/tools/search/rag-api-reranker.test.ts +891 -0
  190. package/src/tools/search/rerankers.ts +260 -0
  191. package/src/tools/search/tool.ts +6 -0
  192. package/src/tools/search/types.ts +45 -1
  193. package/src/tools/subagent/SubagentExecutionRegistry.ts +1159 -0
  194. package/src/tools/subagent/SubagentExecutor.ts +2232 -312
  195. package/src/tools/subagent/SubagentReplay.ts +584 -0
  196. package/src/tools/subagent/__tests__/SubagentExecutionRegistry.test.ts +637 -0
  197. package/src/tools/subagent/__tests__/SubagentExecutor.lazy.test.ts +3054 -0
  198. package/src/tools/subagent/__tests__/childGraphConfig.test.ts +521 -0
  199. package/src/tools/subagent/childGraphConfig.ts +694 -0
  200. package/src/tools/subagent/index.ts +6 -0
  201. package/src/tools/subagent/runtimeLimits.ts +7 -0
  202. package/src/tools/toolOutputReferences.ts +40 -1
  203. package/src/types/graph.ts +222 -24
  204. package/src/types/hitl.ts +16 -0
package/src/run.ts CHANGED
@@ -4,21 +4,30 @@ import { PromptTemplate } from '@langchain/core/prompts';
4
4
  import { RunnableLambda } from '@langchain/core/runnables';
5
5
  import { AzureChatOpenAI, ChatOpenAI } from '@langchain/openai';
6
6
  import { BaseCallbackHandler } from '@langchain/core/callbacks/base';
7
- import { HumanMessage, SystemMessage } from '@langchain/core/messages';
7
+ import {
8
+ BaseMessage,
9
+ HumanMessage,
10
+ SystemMessage,
11
+ } from '@langchain/core/messages';
8
12
  import {
9
13
  Command,
10
14
  INTERRUPT,
11
15
  MemorySaver,
12
16
  isInterrupted,
13
17
  } from '@langchain/langgraph';
14
- import type {
15
- MessageContentComplex,
16
- BaseMessage,
17
- } from '@langchain/core/messages';
18
18
  import type { StringPromptValue } from '@langchain/core/prompt_values';
19
+ import type { MessageContentComplex } from '@langchain/core/messages';
19
20
  import type { RunnableConfig } from '@langchain/core/runnables';
21
+ import type { MultiAgentGraph } from '@/graphs/MultiAgentGraph';
22
+ import type { StandardGraph } from '@/graphs/Graph';
20
23
  import type { HookRegistry } from '@/hooks';
21
24
  import type * as t from '@/types';
25
+ import {
26
+ requireValidSubagentResumeManifest,
27
+ stripSubagentResumeManifest,
28
+ SUBAGENT_RESUME_ATTEMPT_CONFIG_KEY,
29
+ SUBAGENT_RESUME_MANIFEST_CONFIG_KEY,
30
+ } from '@/tools/subagent/SubagentReplay';
22
31
  import {
23
32
  createLangfuseTraceMetadata,
24
33
  createLangfuseHandler,
@@ -55,12 +64,12 @@ import {
55
64
  createCompletionTitleRunnable,
56
65
  createTitleRunnable,
57
66
  } from '@/utils/title';
67
+ import { applyGraphRuntimeConfig } from '@/graphs/applyGraphRuntimeConfig';
58
68
  import { createTokenCounter, encodingForModel } from '@/utils/tokens';
59
69
  import { initializeLangfuseTracing } from './instrumentation';
60
- import { MultiAgentGraph } from '@/graphs/MultiAgentGraph';
61
70
  import { getTraceIdSeed } from '@/langfuseRuntimeContext';
71
+ import { createGraph } from '@/graphs/createGraph';
62
72
  import { resolveMaxSeals } from '@/llm/preempt';
63
- import { StandardGraph } from '@/graphs/Graph';
64
73
  import { initializeModel } from '@/llm/init';
65
74
  import { HandlerRegistry } from '@/events';
66
75
  import { isOpenAILike } from '@/utils/llm';
@@ -121,19 +130,83 @@ function isLangGraphResumeMapForInterrupt(
121
130
  return Object.prototype.hasOwnProperty.call(value, interruptId);
122
131
  }
123
132
 
133
+ function getInterruptHookSessionId(payload: unknown): string | undefined {
134
+ const publicPayload = stripSubagentResumeManifest(payload);
135
+ if (
136
+ publicPayload == null ||
137
+ typeof publicPayload !== 'object' ||
138
+ (publicPayload as { type?: unknown }).type !== 'tool_approval'
139
+ ) {
140
+ return undefined;
141
+ }
142
+ const sessionId = (publicPayload as { hook_session_id?: unknown })
143
+ .hook_session_id;
144
+ return typeof sessionId === 'string' && sessionId.length > 0
145
+ ? sessionId
146
+ : undefined;
147
+ }
148
+
124
149
  type InterruptStateSnapshot = {
125
150
  config?: RunnableConfig;
151
+ values?: { messages?: BaseMessage[] };
126
152
  tasks?: Array<{
127
- interrupts?: Array<{ id?: string }>;
153
+ interrupts?: Array<{ id?: string; value?: unknown }>;
128
154
  }>;
129
155
  };
130
156
 
131
157
  type WorkflowWithStateHistory = {
158
+ getState?(config: RunnableConfig): Promise<InterruptStateSnapshot>;
132
159
  getStateHistory?(
133
160
  config: RunnableConfig
134
161
  ): AsyncIterableIterator<InterruptStateSnapshot>;
135
162
  };
136
163
 
164
+ function getFirstPersistedInterrupt(
165
+ snapshot: InterruptStateSnapshot
166
+ ): { id: string; value: unknown } | undefined {
167
+ for (const task of snapshot.tasks ?? []) {
168
+ for (const pendingInterrupt of task.interrupts ?? []) {
169
+ if (
170
+ typeof pendingInterrupt.id === 'string' &&
171
+ pendingInterrupt.id.length > 0
172
+ ) {
173
+ return { id: pendingInterrupt.id, value: pendingInterrupt.value };
174
+ }
175
+ }
176
+ }
177
+ return undefined;
178
+ }
179
+
180
+ function getPersistedMessages(
181
+ snapshot: InterruptStateSnapshot
182
+ ): BaseMessage[] | undefined {
183
+ const messages = snapshot.values?.messages;
184
+ if (!Array.isArray(messages) || !messages.every(BaseMessage.isInstance)) {
185
+ return undefined;
186
+ }
187
+ return messages;
188
+ }
189
+
190
+ type ResumeCommandUpdate = ConstructorParameters<typeof Command>[0]['update'];
191
+
192
+ function getResumeUpdateMessages(
193
+ update: ResumeCommandUpdate
194
+ ): BaseMessage[] | undefined {
195
+ if (update == null) {
196
+ return undefined;
197
+ }
198
+ const messages = Array.isArray(update)
199
+ ? update.find(([key]) => key === 'messages')?.[1]
200
+ : update.messages;
201
+ if (BaseMessage.isInstance(messages)) {
202
+ return [messages];
203
+ }
204
+ if (!Array.isArray(messages) || !messages.every(BaseMessage.isInstance)) {
205
+ return undefined;
206
+ }
207
+ return messages;
208
+ }
209
+
137
210
  export class Run<_T extends t.BaseGraphState> {
138
211
  id: string;
139
212
  private tokenCounter?: t.TokenCounter;
@@ -227,8 +300,6 @@ export class Run<_T extends t.BaseGraphState> {
227
300
  /** Default to legacy graph for 'standard' or undefined type */
228
301
  this.graphRunnable = this.createLegacyGraph(config.graphConfig);
229
302
  if (this.Graph) {
230
- this.Graph.compileOptions =
231
- config.graphConfig.compileOptions ?? this.Graph.compileOptions;
232
303
  this.Graph.handlerRegistry = handlerRegistry;
233
304
  }
234
305
  }
@@ -277,30 +348,35 @@ export class Run<_T extends t.BaseGraphState> {
277
348
  signal = legacySignal;
278
349
  }
279
350
 
280
- const standardGraph = new StandardGraph({
281
- signal,
282
- runId: this.id,
283
- agents: [agentConfig],
284
- langfuse: this.langfuse,
285
- tokenCounter: this.tokenCounter,
286
- indexTokenCountMap: this.indexTokenCountMap,
287
- calibrationRatio: this.calibrationRatio,
288
- subagentUsageSink: this.subagentUsageSink,
289
- preemption: this.preemption,
290
- streamLimits: this.streamLimits,
351
+ const standardGraph = createGraph({
352
+ kind: 'standard',
353
+ input: {
354
+ signal,
355
+ runId: this.id,
356
+ agents: [agentConfig],
357
+ langfuse: this.langfuse,
358
+ tokenCounter: this.tokenCounter,
359
+ indexTokenCountMap: this.indexTokenCountMap,
360
+ calibrationRatio: this.calibrationRatio,
361
+ subagentUsageSink: this.subagentUsageSink,
362
+ preemption: this.preemption,
363
+ streamLimits: this.streamLimits,
364
+ },
291
365
  });
292
366
  /** Propagate compile options from graph config */
293
367
  standardGraph.compileOptions = this.applyHITLCheckpointerFallback(
294
368
  config.compileOptions
295
369
  );
296
370
  this.hasCheckpointer = standardGraph.compileOptions?.checkpointer != null;
297
- standardGraph.hookRegistry = this.hookRegistry;
298
- standardGraph.humanInTheLoop = this.humanInTheLoop;
299
- standardGraph.toolOutputReferences = this.toolOutputReferences;
300
- standardGraph.eagerEventToolExecution = this.eagerEventToolExecution;
301
- standardGraph.codeSessionToolNames = this.codeSessionToolNames;
302
- standardGraph.interruptingToolNames = this.interruptingToolNames;
303
- standardGraph.toolExecution = this.toolExecution;
371
+ applyGraphRuntimeConfig(standardGraph, {
372
+ hookRegistry: this.hookRegistry,
373
+ humanInTheLoop: this.humanInTheLoop,
374
+ toolOutputReferences: this.toolOutputReferences,
375
+ eagerEventToolExecution: this.eagerEventToolExecution,
376
+ codeSessionToolNames: this.codeSessionToolNames,
377
+ interruptingToolNames: this.interruptingToolNames,
378
+ toolExecution: this.toolExecution,
379
+ });
304
380
  this.Graph = standardGraph;
305
381
  return standardGraph.createWorkflow();
306
382
  }
@@ -310,30 +386,35 @@ export class Run<_T extends t.BaseGraphState> {
310
386
  ): t.CompiledStateWorkflow {
311
387
  const { agents, edges, compileOptions } = config;
312
388
 
313
- const multiAgentGraph = new MultiAgentGraph({
314
- runId: this.id,
315
- agents,
316
- edges,
317
- langfuse: this.langfuse,
318
- tokenCounter: this.tokenCounter,
319
- indexTokenCountMap: this.indexTokenCountMap,
320
- calibrationRatio: this.calibrationRatio,
321
- subagentUsageSink: this.subagentUsageSink,
322
- preemption: this.preemption,
323
- streamLimits: this.streamLimits,
389
+ const multiAgentGraph = createGraph({
390
+ kind: 'multi-agent',
391
+ input: {
392
+ runId: this.id,
393
+ agents,
394
+ edges,
395
+ langfuse: this.langfuse,
396
+ tokenCounter: this.tokenCounter,
397
+ indexTokenCountMap: this.indexTokenCountMap,
398
+ calibrationRatio: this.calibrationRatio,
399
+ subagentUsageSink: this.subagentUsageSink,
400
+ preemption: this.preemption,
401
+ streamLimits: this.streamLimits,
402
+ },
324
403
  });
325
404
 
326
405
  multiAgentGraph.compileOptions =
327
406
  this.applyHITLCheckpointerFallback(compileOptions);
328
407
  this.hasCheckpointer = multiAgentGraph.compileOptions?.checkpointer != null;
329
408
 
330
- multiAgentGraph.hookRegistry = this.hookRegistry;
331
- multiAgentGraph.humanInTheLoop = this.humanInTheLoop;
332
- multiAgentGraph.toolOutputReferences = this.toolOutputReferences;
333
- multiAgentGraph.eagerEventToolExecution = this.eagerEventToolExecution;
334
- multiAgentGraph.codeSessionToolNames = this.codeSessionToolNames;
335
- multiAgentGraph.interruptingToolNames = this.interruptingToolNames;
336
- multiAgentGraph.toolExecution = this.toolExecution;
409
+ applyGraphRuntimeConfig(multiAgentGraph, {
410
+ hookRegistry: this.hookRegistry,
411
+ humanInTheLoop: this.humanInTheLoop,
412
+ toolOutputReferences: this.toolOutputReferences,
413
+ eagerEventToolExecution: this.eagerEventToolExecution,
414
+ codeSessionToolNames: this.codeSessionToolNames,
415
+ interruptingToolNames: this.interruptingToolNames,
416
+ toolExecution: this.toolExecution,
417
+ });
337
418
  this.Graph = multiAgentGraph;
338
419
  return multiAgentGraph.createWorkflow();
339
420
  }
@@ -542,6 +623,10 @@ export class Run<_T extends t.BaseGraphState> {
542
623
  return this.Graph.getRunMessages();
543
624
  }
544
625
 
626
+ getChildCheckpointThreadIds(): string[] {
627
+ return this.Graph?.getChildCheckpointThreadIds() ?? [];
628
+ }
629
+
545
630
  /**
546
631
  * Returns a defensive snapshot of tools discovered by the current run.
547
632
  * Pass an agent id for that context, or omit it for the ordered union across
@@ -759,6 +844,10 @@ export class Run<_T extends t.BaseGraphState> {
759
844
  recursionLimit,
760
845
  configurable: { ...callerConfig.configurable },
761
846
  };
847
+ if (!isResume) {
848
+ delete config.configurable?.[SUBAGENT_RESUME_ATTEMPT_CONFIG_KEY];
849
+ delete config.configurable?.[SUBAGENT_RESUME_MANIFEST_CONFIG_KEY];
850
+ }
762
851
 
763
852
  /**
764
853
  * Cancellation can arrive either at graph construction or per-call through
@@ -1248,7 +1337,13 @@ export class Run<_T extends t.BaseGraphState> {
1248
1337
  getInterrupt<TPayload = t.HumanInterruptPayload>():
1249
1338
  | t.RunInterruptResult<TPayload>
1250
1339
  | undefined {
1251
- return this._interrupt as t.RunInterruptResult<TPayload> | undefined;
1340
+ if (this._interrupt == null) {
1341
+ return undefined;
1342
+ }
1343
+ return {
1344
+ ...this._interrupt,
1345
+ payload: stripSubagentResumeManifest(this._interrupt.payload),
1346
+ } as t.RunInterruptResult<TPayload>;
1252
1347
  }
1253
1348
 
1254
1349
  /**
@@ -1331,6 +1426,10 @@ export class Run<_T extends t.BaseGraphState> {
1331
1426
  'update' | 'goto'
1332
1427
  >
1333
1428
  ): Promise<MessageContentComplex[] | undefined> {
1429
+ const resumeConfig = await this.resolveInterruptResumeConfig(
1430
+ callerConfig,
1431
+ commandOptions?.update
1432
+ );
1334
1433
  const interruptId = this._interrupt?.interruptId;
1335
1434
  const scopedResume =
1336
1435
  typeof interruptId === 'string' &&
@@ -1338,7 +1437,6 @@ export class Run<_T extends t.BaseGraphState> {
1338
1437
  !isLangGraphResumeMapForInterrupt(resumeValue, interruptId)
1339
1438
  ? { [interruptId]: resumeValue }
1340
1439
  : resumeValue;
1341
- const resumeConfig = await this.resolveInterruptResumeConfig(callerConfig);
1342
1440
  // langgraph 1.4.5 applies resume + state update + reroute in one superstep
1343
1441
  // (single checkpoint). `update`/`goto` are omitted unless the caller sets them.
1344
1442
  return this.processStream(
@@ -1357,9 +1455,29 @@ export class Run<_T extends t.BaseGraphState> {
1357
1455
  }
1358
1456
 
1359
1457
  private async resolveInterruptResumeConfig(
1360
- callerConfig: t.RunStreamConfig
1458
+ callerConfig: t.RunStreamConfig,
1459
+ resumeUpdate?: ResumeCommandUpdate
1361
1460
  ): Promise<t.RunStreamConfig> {
1461
+ await this.restoreInterruptFromCheckpoint(callerConfig, resumeUpdate);
1362
1462
  const interrupt = this._interrupt;
1463
+ const resumeManifest = requireValidSubagentResumeManifest(
1464
+ interrupt?.payload
1465
+ );
1466
+ const resumeConfigurable = { ...callerConfig.configurable };
1467
+ delete resumeConfigurable[SUBAGENT_RESUME_ATTEMPT_CONFIG_KEY];
1468
+ delete resumeConfigurable[SUBAGENT_RESUME_MANIFEST_CONFIG_KEY];
1469
+ resumeConfigurable[SUBAGENT_RESUME_ATTEMPT_CONFIG_KEY] = nanoid();
1470
+ if (resumeManifest != null) {
1471
+ resumeConfigurable[SUBAGENT_RESUME_MANIFEST_CONFIG_KEY] = resumeManifest;
1472
+ }
1473
+ const manifestConfig = {
1474
+ ...callerConfig,
1475
+ configurable: resumeConfigurable,
1476
+ };
1477
+ const hookSessionId = getInterruptHookSessionId(interrupt?.payload);
1478
+ if (hookSessionId != null) {
1479
+ this.hookRegistry?.copySession(hookSessionId, this.id);
1480
+ }
1363
1481
  const interruptId = interrupt?.interruptId;
1364
1482
  const workflow = this.graphRunnable as
1365
1483
  | (t.CompiledStateWorkflow & WorkflowWithStateHistory)
@@ -1367,9 +1485,9 @@ export class Run<_T extends t.BaseGraphState> {
1367
1485
  const stateHistory = workflow?.getStateHistory;
1368
1486
  if (interrupt?.checkpointId != null && interrupt.checkpointId.length > 0) {
1369
1487
  return {
1370
- ...callerConfig,
1488
+ ...manifestConfig,
1371
1489
  configurable: {
1372
- ...callerConfig.configurable,
1490
+ ...manifestConfig.configurable,
1373
1491
  checkpoint_id: interrupt.checkpointId,
1374
1492
  ...(typeof interrupt.checkpointNs === 'string'
1375
1493
  ? { checkpoint_ns: interrupt.checkpointNs }
@@ -1383,12 +1501,12 @@ export class Run<_T extends t.BaseGraphState> {
1383
1501
  interruptId.length === 0 ||
1384
1502
  typeof stateHistory !== 'function'
1385
1503
  ) {
1386
- return callerConfig;
1504
+ return manifestConfig;
1387
1505
  }
1388
1506
 
1389
1507
  for await (const snapshot of stateHistory.call(
1390
1508
  this.graphRunnable,
1391
- callerConfig as RunnableConfig
1509
+ manifestConfig as RunnableConfig
1392
1510
  )) {
1393
1511
  const hasMatchingInterrupt =
1394
1512
  snapshot.tasks?.some(
@@ -1411,9 +1529,9 @@ export class Run<_T extends t.BaseGraphState> {
1411
1529
  ...(typeof checkpointNs === 'string' ? { checkpointNs } : {}),
1412
1530
  };
1413
1531
  return {
1414
- ...callerConfig,
1532
+ ...manifestConfig,
1415
1533
  configurable: {
1416
- ...callerConfig.configurable,
1534
+ ...manifestConfig.configurable,
1417
1535
  checkpoint_id: checkpointId,
1418
1536
  ...(typeof checkpointNs === 'string'
1419
1537
  ? { checkpoint_ns: checkpointNs }
@@ -1423,7 +1541,47 @@ export class Run<_T extends t.BaseGraphState> {
1423
1541
  }
1424
1542
  }
1425
1543
 
1426
- return callerConfig;
1544
+ return manifestConfig;
1545
+ }
1546
+
1547
+ private async restoreInterruptFromCheckpoint(
1548
+ callerConfig: t.RunStreamConfig,
1549
+ resumeUpdate?: ResumeCommandUpdate
1550
+ ): Promise<void> {
1551
+ if (this._interrupt != null || this.humanInTheLoop?.enabled !== true) {
1552
+ return;
1553
+ }
1554
+ const workflow = this.graphRunnable as
1555
+ | (t.CompiledStateWorkflow & WorkflowWithStateHistory)
1556
+ | undefined;
1557
+ if (typeof workflow?.getState !== 'function') {
1558
+ return;
1559
+ }
1560
+
1561
+ const snapshot = await workflow.getState(callerConfig as RunnableConfig);
1562
+ const persistedInterrupt = getFirstPersistedInterrupt(snapshot);
1563
+ if (persistedInterrupt == null) {
1564
+ return;
1565
+ }
1566
+ const persistedMessages = getPersistedMessages(snapshot);
1567
+ if (persistedMessages != null) {
1568
+ this.Graph?.restoreCheckpointMessages(
1569
+ persistedMessages,
1570
+ getResumeUpdateMessages(resumeUpdate)
1571
+ );
1572
+ }
1573
+
1574
+ const checkpointConfigurable = snapshot.config?.configurable;
1575
+ const checkpointId = checkpointConfigurable?.checkpoint_id;
1576
+ const checkpointNs = checkpointConfigurable?.checkpoint_ns;
1577
+ const threadId = callerConfig.configurable?.thread_id;
1578
+ this._interrupt = {
1579
+ interruptId: persistedInterrupt.id,
1580
+ payload: persistedInterrupt.value,
1581
+ ...(typeof threadId === 'string' ? { threadId } : {}),
1582
+ ...(typeof checkpointId === 'string' ? { checkpointId } : {}),
1583
+ ...(typeof checkpointNs === 'string' ? { checkpointNs } : {}),
1584
+ };
1427
1585
  }
1428
1586
 
1429
1587
  private createSystemCallback<K extends keyof t.ClientCallbacks>(
@@ -934,6 +934,30 @@ export class AgentSession {
934
934
  });
935
935
  }
936
936
 
937
+ private async recordChildCheckpointThreads(params: {
938
+ source: 'run' | 'resume';
939
+ runId: string;
940
+ run: Run<t.IState>;
941
+ }): Promise<void> {
942
+ if (!this.checkpointing.enabled || this.store == null) {
943
+ return;
944
+ }
945
+ const recordedThreadIds = new Set(
946
+ this.store.getCheckpoints().map((checkpoint) => checkpoint.data.threadId)
947
+ );
948
+ for (const threadId of params.run.getChildCheckpointThreadIds()) {
949
+ if (recordedThreadIds.has(threadId)) {
950
+ continue;
951
+ }
952
+ recordedThreadIds.add(threadId);
953
+ await this.store.appendCheckpoint({
954
+ source: params.source,
955
+ runId: params.runId,
956
+ threadId,
957
+ });
958
+ }
959
+ }
960
+
937
961
  private getCheckpointThreadIds(): string[] {
938
962
  const threadIds = new Set<string>([this.threadId]);
939
963
  for (const checkpoint of this.store?.getCheckpoints() ?? []) {
@@ -1006,6 +1030,7 @@ export class AgentSession {
1006
1030
  const sessionState = createSessionRunState(
1007
1031
  isSessionThread ? (this.store?.getPath() ?? []) : []
1008
1032
  );
1033
+ let run: Run<t.IState> | undefined;
1009
1034
  try {
1010
1035
  const runConfig: t.RunConfig = {
1011
1036
  ...this.runConfig,
@@ -1024,7 +1049,7 @@ export class AgentSession {
1024
1049
  ...handlerResult.handlers,
1025
1050
  },
1026
1051
  };
1027
- const run = await Run.create<t.IState>(runConfig);
1052
+ run = await Run.create<t.IState>(runConfig);
1028
1053
  let messages = inputMessages;
1029
1054
  if (!useCheckpointState && sessionState.messages.length > 0) {
1030
1055
  messages = sessionState.messages;
@@ -1070,6 +1095,11 @@ export class AgentSession {
1070
1095
  checkpointId: interrupt?.checkpointId,
1071
1096
  checkpointNs: interrupt?.checkpointNs,
1072
1097
  });
1098
+ await this.recordChildCheckpointThreads({
1099
+ source: 'run',
1100
+ runId,
1101
+ run,
1102
+ });
1073
1103
  const contentParts = (content ?? handlerResult.contentParts).filter(
1074
1104
  (part): part is t.MessageContentComplex => part != null
1075
1105
  );
@@ -1099,6 +1129,13 @@ export class AgentSession {
1099
1129
  threadId,
1100
1130
  config: callerConfig,
1101
1131
  });
1132
+ if (run != null) {
1133
+ await this.recordChildCheckpointThreads({
1134
+ source: 'run',
1135
+ runId,
1136
+ run,
1137
+ });
1138
+ }
1102
1139
  throw error;
1103
1140
  }
1104
1141
  }
@@ -1348,8 +1385,9 @@ export class AgentSession {
1348
1385
  const sessionState = createSessionRunState(
1349
1386
  isSessionThread ? (this.store?.getPath() ?? []) : []
1350
1387
  );
1388
+ let run: Run<t.IState> | undefined;
1351
1389
  try {
1352
- const run = await Run.create<t.IState>({
1390
+ run = await Run.create<t.IState>({
1353
1391
  ...this.runConfig,
1354
1392
  runId,
1355
1393
  graphConfig: applyCheckpointingToGraphConfig(
@@ -1400,6 +1438,11 @@ export class AgentSession {
1400
1438
  checkpointId: interrupt?.checkpointId,
1401
1439
  checkpointNs: interrupt?.checkpointNs,
1402
1440
  });
1441
+ await this.recordChildCheckpointThreads({
1442
+ source: 'resume',
1443
+ runId,
1444
+ run,
1445
+ });
1403
1446
  const contentParts = (content ?? handlerResult.contentParts).filter(
1404
1447
  (part): part is t.MessageContentComplex => part != null
1405
1448
  );
@@ -1425,6 +1468,13 @@ export class AgentSession {
1425
1468
  threadId,
1426
1469
  config: callerConfig,
1427
1470
  });
1471
+ if (run != null) {
1472
+ await this.recordChildCheckpointThreads({
1473
+ source: 'resume',
1474
+ runId,
1475
+ run,
1476
+ });
1477
+ }
1428
1478
  throw error;
1429
1479
  }
1430
1480
  }
@@ -25,6 +25,9 @@ type MockRun = {
25
25
  >;
26
26
  getInterrupt: jest.MockedFunction<Run<t.IState>['getInterrupt']>;
27
27
  getHaltReason: jest.MockedFunction<Run<t.IState>['getHaltReason']>;
28
+ getChildCheckpointThreadIds: jest.MockedFunction<
29
+ Run<t.IState>['getChildCheckpointThreadIds']
30
+ >;
28
31
  };
29
32
 
30
33
  function createMockRun(outputText = 'ok'): MockRun {
@@ -45,6 +48,7 @@ function createMockRun(outputText = 'ok'): MockRun {
45
48
  getCalibrationRatio: jest.fn(() => 1),
46
49
  getInterrupt: jest.fn(() => undefined),
47
50
  getHaltReason: jest.fn(() => undefined),
51
+ getChildCheckpointThreadIds: jest.fn(() => []),
48
52
  };
49
53
  }
50
54
 
@@ -1119,6 +1123,55 @@ describe('JsonlSessionStore', () => {
1119
1123
  });
1120
1124
  });
1121
1125
 
1126
+ it('records and resets child checkpoint threads owned by a run', async () => {
1127
+ const checkpointer = new MemorySaver();
1128
+ const childThreadId = 'subagent:owned-child';
1129
+ const mockRun = createMockRun('child result');
1130
+ mockRun.getChildCheckpointThreadIds.mockReturnValue([childThreadId]);
1131
+ mockRunCreate(mockRun);
1132
+ const session = await createAgentSession({
1133
+ cwd: dir,
1134
+ runId: 'template-run',
1135
+ checkpointing: { checkpointer },
1136
+ graphConfig: {
1137
+ type: 'standard',
1138
+ llmConfig: {
1139
+ provider: 'openAI' as never,
1140
+ model: 'test-model',
1141
+ },
1142
+ instructions: 'test',
1143
+ },
1144
+ });
1145
+ await putCheckpoint({
1146
+ checkpointer,
1147
+ threadId: childThreadId,
1148
+ id: 'checkpoint_child',
1149
+ });
1150
+
1151
+ await session.run('fresh turn', { runId: 'run_with_child' });
1152
+
1153
+ expect(
1154
+ session.getSessionStore()?.getCheckpoints(childThreadId).at(-1)?.data
1155
+ ).toMatchObject({
1156
+ source: 'run',
1157
+ runId: 'run_with_child',
1158
+ threadId: childThreadId,
1159
+ });
1160
+ const firstMessage = session
1161
+ .getSessionStore()
1162
+ ?.getPath()
1163
+ .find((entry) => entry.type === 'message');
1164
+ await session.branch(firstMessage?.id ?? '', { position: 'at' });
1165
+
1166
+ const tuple = await checkpointer.getTuple({
1167
+ configurable: { thread_id: childThreadId },
1168
+ });
1169
+ expect(tuple).toBeUndefined();
1170
+ expect(
1171
+ session.getSessionStore()?.getCheckpoints(childThreadId).at(-1)?.data
1172
+ ).toMatchObject({ source: 'reset', reason: 'branch' });
1173
+ });
1174
+
1122
1175
  it('keeps checkpoint state when branching to the active JSONL leaf', async () => {
1123
1176
  const checkpointer = new MemorySaver();
1124
1177
  const session = await createAgentSession({