@librechat/agents 3.6.3 → 3.6.5

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 (72) hide show
  1. package/dist/cjs/graphs/Graph.cjs +44 -19
  2. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  3. package/dist/cjs/hooks/HookRegistry.cjs +7 -1
  4. package/dist/cjs/hooks/HookRegistry.cjs.map +1 -1
  5. package/dist/cjs/hooks/index.cjs +1 -1
  6. package/dist/cjs/langchain/index.cjs +12 -0
  7. package/dist/cjs/langchain/messages.cjs +12 -0
  8. package/dist/cjs/langfuseConfig.cjs +16 -0
  9. package/dist/cjs/langfuseConfig.cjs.map +1 -1
  10. package/dist/cjs/langfuseSpanRegistry.cjs +16 -4
  11. package/dist/cjs/langfuseSpanRegistry.cjs.map +1 -1
  12. package/dist/cjs/main.cjs +15 -1
  13. package/dist/cjs/run.cjs +4 -0
  14. package/dist/cjs/run.cjs.map +1 -1
  15. package/dist/cjs/tools/SubagentTool.cjs +14 -4
  16. package/dist/cjs/tools/SubagentTool.cjs.map +1 -1
  17. package/dist/cjs/tools/ToolNode.cjs +1 -1
  18. package/dist/cjs/tools/subagent/InMemorySubagentTaskStore.cjs +403 -0
  19. package/dist/cjs/tools/subagent/InMemorySubagentTaskStore.cjs.map +1 -0
  20. package/dist/cjs/tools/subagent/SubagentExecutor.cjs +201 -61
  21. package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
  22. package/dist/cjs/tools/subagent/index.cjs +1 -0
  23. package/dist/esm/graphs/Graph.mjs +44 -19
  24. package/dist/esm/graphs/Graph.mjs.map +1 -1
  25. package/dist/esm/hooks/HookRegistry.mjs +7 -1
  26. package/dist/esm/hooks/HookRegistry.mjs.map +1 -1
  27. package/dist/esm/hooks/index.mjs +1 -1
  28. package/dist/esm/langchain/index.mjs +2 -2
  29. package/dist/esm/langchain/messages.mjs +2 -2
  30. package/dist/esm/langfuseConfig.mjs +16 -0
  31. package/dist/esm/langfuseConfig.mjs.map +1 -1
  32. package/dist/esm/langfuseSpanRegistry.mjs +16 -4
  33. package/dist/esm/langfuseSpanRegistry.mjs.map +1 -1
  34. package/dist/esm/main.mjs +4 -3
  35. package/dist/esm/run.mjs +4 -0
  36. package/dist/esm/run.mjs.map +1 -1
  37. package/dist/esm/tools/SubagentTool.mjs +14 -4
  38. package/dist/esm/tools/SubagentTool.mjs.map +1 -1
  39. package/dist/esm/tools/ToolNode.mjs +1 -1
  40. package/dist/esm/tools/subagent/InMemorySubagentTaskStore.mjs +403 -0
  41. package/dist/esm/tools/subagent/InMemorySubagentTaskStore.mjs.map +1 -0
  42. package/dist/esm/tools/subagent/SubagentExecutor.mjs +201 -61
  43. package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
  44. package/dist/esm/tools/subagent/index.mjs +1 -0
  45. package/dist/types/graphs/Graph.d.ts +6 -3
  46. package/dist/types/hooks/HookRegistry.d.ts +8 -0
  47. package/dist/types/langchain/messages.d.ts +2 -2
  48. package/dist/types/langfuseSpanRegistry.d.ts +9 -4
  49. package/dist/types/run.d.ts +1 -0
  50. package/dist/types/tools/SubagentTool.d.ts +5 -2
  51. package/dist/types/tools/subagent/InMemorySubagentTaskStore.d.ts +45 -0
  52. package/dist/types/tools/subagent/SubagentExecutor.d.ts +30 -3
  53. package/dist/types/tools/subagent/index.d.ts +2 -0
  54. package/dist/types/types/graph.d.ts +22 -4
  55. package/dist/types/types/index.d.ts +1 -0
  56. package/dist/types/types/run.d.ts +6 -0
  57. package/dist/types/types/subagentTasks.d.ts +172 -0
  58. package/package.json +1 -1
  59. package/src/graphs/Graph.ts +75 -30
  60. package/src/hooks/HookRegistry.ts +18 -0
  61. package/src/langchain/messages.ts +3 -0
  62. package/src/langfuseConfig.ts +43 -0
  63. package/src/langfuseSpanRegistry.ts +42 -4
  64. package/src/run.ts +4 -0
  65. package/src/tools/SubagentTool.ts +37 -3
  66. package/src/tools/subagent/InMemorySubagentTaskStore.ts +632 -0
  67. package/src/tools/subagent/SubagentExecutor.ts +411 -74
  68. package/src/tools/subagent/index.ts +2 -0
  69. package/src/types/graph.ts +22 -4
  70. package/src/types/index.ts +1 -0
  71. package/src/types/run.ts +6 -0
  72. package/src/types/subagentTasks.ts +162 -0
@@ -52,6 +52,8 @@ import type {
52
52
  ResolvedSubagentConfig,
53
53
  ResolvedSubagentConfigEntry,
54
54
  SubagentExecutionContext,
55
+ SubagentTaskConfig,
56
+ SubagentTaskRuntime,
55
57
  SubagentResolveConfigurable,
56
58
  SubagentResolveRequestContext,
57
59
  SubagentResolveUserContext,
@@ -81,12 +83,12 @@ import type {
81
83
  } from './SubagentReplay';
82
84
  import type {
83
85
  AggregatedHookResult,
84
- HookRegistry,
86
+ PostToolBatchHookOutput,
87
+ PreemptBoundaryHookOutput,
85
88
  ToolApprovalReplaySnapshot,
86
89
  } from '@/hooks';
87
90
  import type { GraphFactory } from '@/graphs/graphFactory';
88
91
  import type { StandardGraph } from '@/graphs/Graph';
89
- import type { HandlerRegistry } from '@/events';
90
92
  import {
91
93
  getSubagentApprovalExecutionScope,
92
94
  SubagentDefinitionBindingError,
@@ -101,6 +103,11 @@ import {
101
103
  SUBAGENT_RESUME_ATTEMPT_CONFIG_KEY,
102
104
  SUBAGENT_RESUME_MANIFEST_CONFIG_KEY,
103
105
  } from './SubagentReplay';
106
+ import {
107
+ executeHooks,
108
+ HookRegistry,
109
+ TOOL_APPROVAL_EXECUTION_SCOPE_CONFIG_KEY,
110
+ } from '@/hooks';
104
111
  import {
105
112
  StreamLimitExceededError,
106
113
  RUN_BREAKER_SCOPE_CONFIG_KEY,
@@ -116,10 +123,6 @@ import {
116
123
  Callback,
117
124
  StepTypes,
118
125
  } from '@/common';
119
- import {
120
- executeHooks,
121
- TOOL_APPROVAL_EXECUTION_SCOPE_CONFIG_KEY,
122
- } from '@/hooks';
123
126
  import {
124
127
  createChildGraphPlan,
125
128
  isGraphSubagentConfig,
@@ -127,7 +130,9 @@ import {
127
130
  import { stripRunStepResumeState } from '@/tools/runStepResume';
128
131
  import { seedAgentInitialSessions } from '@/utils/toolSessions';
129
132
  import { stableStringify } from '@/tools/eagerEventExecution';
133
+ import { convertInjectedMessages } from '@/messages/injected';
130
134
  import { composeAbortSignals } from '@/utils/misc';
135
+ import { HandlerRegistry } from '@/events';
131
136
 
132
137
  export {
133
138
  buildChildInputs,
@@ -140,6 +145,7 @@ export {
140
145
 
141
146
  const ERROR_MESSAGE_MAX_CHARS = 200;
142
147
  const MAX_QUEUED_SUBAGENT_UPDATES = 64;
148
+ const MAX_BACKGROUND_SUBAGENT_SEALS = 32;
143
149
  const SUBAGENT_UPDATE_HANDLER_TIMEOUT_MS = 5_000;
144
150
  const TEXT_DELTA_CONTENT_TYPE = `${ContentTypes.TEXT}_delta`;
145
151
  const SUBAGENT_RESOLUTION_ERROR_MESSAGE =
@@ -615,6 +621,29 @@ function getSettlementFingerprint(output: PersistedToolOutput): string {
615
621
  return createHash('sha256').update(stableStringify(output)).digest('hex');
616
622
  }
617
623
 
624
+ function getBackgroundTaskFingerprint(
625
+ description: string,
626
+ subagentType: string,
627
+ threadId?: string
628
+ ): string {
629
+ return createHash('sha256')
630
+ .update(
631
+ stableStringify({
632
+ description,
633
+ subagentType,
634
+ ...(threadId == null || threadId === '' ? {} : { threadId }),
635
+ })
636
+ )
637
+ .digest('hex');
638
+ }
639
+
640
+ function getBackgroundTaskHookSessionId(
641
+ sourceHookSessionId: string,
642
+ taskId: string
643
+ ): string {
644
+ return `${sourceHookSessionId}:subagent-task:${taskId}`;
645
+ }
646
+
618
647
  function deserializeToolOutput(
619
648
  output: PersistedToolOutput
620
649
  ): SettledSubagentToolOutput {
@@ -736,6 +765,8 @@ function createReplayCheckpointWorkflow(
736
765
  export type SubagentExecuteParams = {
737
766
  description: string;
738
767
  subagentType: string;
768
+ /** Saved logical child thread selected by the parent model. */
769
+ subagentThreadId?: string;
739
770
  threadId?: string;
740
771
  /** Signal attached to this specific parent tool invocation. */
741
772
  signal?: AbortSignal;
@@ -786,13 +817,28 @@ export type SubagentExecuteParams = {
786
817
  * rather than sharing parent's host context.
787
818
  */
788
819
  parentConfigurable?: Record<string, unknown>;
820
+ /** Dedicated hook session used by a detached task. @internal */
821
+ hookSessionId?: string;
822
+ /** Process-local task controls consumed by the child graph. @internal */
823
+ taskRuntime?: SubagentTaskRuntime;
824
+ /** Host-restored child transcript for a fresh continuation run. @internal */
825
+ initialMessages?: BaseMessage[];
789
826
  };
790
827
 
791
828
  export type SubagentExecuteResult = {
792
829
  content: string;
793
830
  messages: BaseMessage[];
831
+ /** Tagged internal failure; foreground callers retain the legacy content. */
832
+ error?: string;
794
833
  };
795
834
 
835
+ function createSubagentFailure(
836
+ content: string,
837
+ error = content
838
+ ): SubagentExecuteResult {
839
+ return { content, messages: [], error };
840
+ }
841
+
796
842
  /**
797
843
  * Factory that constructs a child graph for subagent execution. Injected
798
844
  * rather than imported so that `SubagentExecutor` does not have a runtime
@@ -848,6 +894,13 @@ export type SubagentExecutorOptions = {
848
894
  /** Preferred polymorphic child constructor. The legacy standard-only
849
895
  * factory remains required for source compatibility. */
850
896
  createChildGraphByKind?: GraphFactory;
897
+ /**
898
+ * Captures a child-graph factory and its run-scoped host dependencies
899
+ * synchronously, before a detached task can outlive parent cleanup.
900
+ */
901
+ createDetachedChildGraphFactory?: (
902
+ parentHandlerRegistry: HandlerRegistry
903
+ ) => GraphFactory;
851
904
  /**
852
905
  * Parent's event handler registry. When provided, child-graph events are
853
906
  * forwarded through this registry so hosts can:
@@ -870,6 +923,8 @@ export type SubagentExecutorOptions = {
870
923
  * nested subagents report through the same sink.
871
924
  */
872
925
  usageSink?: SubagentUsageSink;
926
+ /** Host-owned process-local task namespace for detached execution. */
927
+ taskConfig?: SubagentTaskConfig;
873
928
  };
874
929
 
875
930
  type DurableExecutionRecord = SubagentExecutionRecord<
@@ -902,7 +957,11 @@ export class SubagentExecutor {
902
957
  private readonly maxDepth: number;
903
958
  private readonly createChildGraph: ChildGraphFactory;
904
959
  private readonly createChildGraphByKind?: GraphFactory;
960
+ private readonly createDetachedChildGraphFactory?: (
961
+ parentHandlerRegistry: HandlerRegistry
962
+ ) => GraphFactory;
905
963
  private readonly usageSink?: SubagentUsageSink;
964
+ private readonly taskConfig?: SubagentTaskConfig;
906
965
  private readonly executions: SubagentExecutionRegistry<
907
966
  SubagentExecuteResult,
908
967
  ResolvedSubagentConfig,
@@ -943,7 +1002,10 @@ export class SubagentExecutor {
943
1002
  this.maxDepth = options.maxDepth ?? 1;
944
1003
  this.createChildGraph = options.createChildGraph;
945
1004
  this.createChildGraphByKind = options.createChildGraphByKind;
1005
+ this.createDetachedChildGraphFactory =
1006
+ options.createDetachedChildGraphFactory;
946
1007
  this.usageSink = options.usageSink;
1008
+ this.taskConfig = options.taskConfig;
947
1009
  const rawRegistry = options.parentHandlerRegistry;
948
1010
  if (typeof rawRegistry === 'function') {
949
1011
  this.resolveParentHandlerRegistry = rawRegistry;
@@ -979,6 +1041,257 @@ export class SubagentExecutor {
979
1041
  return this.resolveParentHandlerRegistry?.();
980
1042
  }
981
1043
 
1044
+ /**
1045
+ * Starts one independently-owned executor behind the configured task store.
1046
+ * The parent ToolNode receives the handle synchronously; the detached clone
1047
+ * is not registered on the parent graph, so end-of-turn cleanup cannot
1048
+ * invalidate or clear a child that intentionally outlives that turn.
1049
+ */
1050
+ executeInBackground(params: SubagentExecuteParams): string {
1051
+ if (this.taskConfig == null) {
1052
+ return JSON.stringify({
1053
+ status: 'rejected',
1054
+ message: 'Background subagent execution is not enabled for this run.',
1055
+ });
1056
+ }
1057
+ const executableConfig = this.configs.get(params.subagentType);
1058
+ if (executableConfig == null) {
1059
+ return JSON.stringify({
1060
+ status: 'rejected',
1061
+ message: `Unknown subagent type "${params.subagentType}".`,
1062
+ });
1063
+ }
1064
+ if (this.maxDepth <= 0) {
1065
+ return JSON.stringify({
1066
+ status: 'rejected',
1067
+ message: 'Maximum subagent nesting depth exceeded.',
1068
+ });
1069
+ }
1070
+ if (this.humanInTheLoop?.enabled === true) {
1071
+ return JSON.stringify({
1072
+ status: 'rejected',
1073
+ message:
1074
+ 'Background subagent execution does not support human-in-the-loop pauses.',
1075
+ });
1076
+ }
1077
+ const parentToolCallId = params.parentToolCallId?.trim();
1078
+ if (parentToolCallId == null || parentToolCallId === '') {
1079
+ return JSON.stringify({
1080
+ status: 'rejected',
1081
+ message:
1082
+ 'Background subagent execution requires a parent tool call ID.',
1083
+ });
1084
+ }
1085
+ const subagentThreadId = params.subagentThreadId?.trim();
1086
+ if (
1087
+ subagentThreadId != null &&
1088
+ subagentThreadId !== '' &&
1089
+ this.taskConfig.store.supportsThreadContinuation !== true
1090
+ ) {
1091
+ return JSON.stringify({
1092
+ status: 'rejected',
1093
+ tool: Constants.SUBAGENT,
1094
+ message:
1095
+ 'Child-thread continuation is not enabled by this host.',
1096
+ });
1097
+ }
1098
+ const detachedHandlers = new HandlerRegistry();
1099
+ const sourceHookSessionId =
1100
+ asNonEmptyString(params.parentConfigurable?.run_id) ??
1101
+ this.executionContext.hookSessionId;
1102
+ const taskHookRegistry =
1103
+ this.hookRegistry?.forkSession(sourceHookSessionId) ?? new HookRegistry();
1104
+ const toolHandler = this.getParentHandlerRegistry()?.getHandler(
1105
+ GraphEvents.ON_TOOL_EXECUTE
1106
+ );
1107
+ if (toolHandler != null) {
1108
+ detachedHandlers.register(GraphEvents.ON_TOOL_EXECUTE, toolHandler);
1109
+ }
1110
+ const detachedGraphFactory =
1111
+ this.createDetachedChildGraphFactory?.(detachedHandlers);
1112
+ const started = this.taskConfig.store.start({
1113
+ scopeId: this.taskConfig.scopeId,
1114
+ idempotencyKey: JSON.stringify([
1115
+ this.parentRunId,
1116
+ this.parentAgentId ?? '',
1117
+ parentToolCallId,
1118
+ ]),
1119
+ parentRunId: this.parentRunId,
1120
+ ...(this.parentAgentId == null
1121
+ ? {}
1122
+ : { parentAgentId: this.parentAgentId }),
1123
+ parentToolCallId,
1124
+ requestFingerprint: getBackgroundTaskFingerprint(
1125
+ params.description,
1126
+ params.subagentType,
1127
+ subagentThreadId
1128
+ ),
1129
+ ...(subagentThreadId == null || subagentThreadId === ''
1130
+ ? {}
1131
+ : { threadId: subagentThreadId }),
1132
+ input: params.description,
1133
+ subagentKind:
1134
+ 'kind' in executableConfig && executableConfig.kind === 'graph'
1135
+ ? 'graph'
1136
+ : 'agent',
1137
+ subagentType: params.subagentType,
1138
+ run: (runtime, initialMessages) =>
1139
+ this.executeDetached(
1140
+ {
1141
+ ...params,
1142
+ ...(initialMessages == null ? {} : { initialMessages }),
1143
+ },
1144
+ runtime,
1145
+ detachedHandlers,
1146
+ taskHookRegistry,
1147
+ detachedGraphFactory
1148
+ ),
1149
+ });
1150
+ if (!started.accepted) {
1151
+ if (started.reason === 'thread_unavailable') {
1152
+ return JSON.stringify({
1153
+ status: 'rejected',
1154
+ tool: Constants.SUBAGENT,
1155
+ message:
1156
+ 'The requested subagent thread is unavailable in this parent scope. Start a new subagent thread or choose one created by this parent for the same subagent type.',
1157
+ });
1158
+ }
1159
+ if (started.reason === 'conflict') {
1160
+ return JSON.stringify({
1161
+ status: 'rejected',
1162
+ tool: Constants.SUBAGENT,
1163
+ message:
1164
+ 'The same parent tool call ID was already used with different background subagent arguments.',
1165
+ });
1166
+ }
1167
+ return JSON.stringify({
1168
+ status: 'rejected',
1169
+ tool: Constants.SUBAGENT,
1170
+ message:
1171
+ 'Too many background subagent tasks are already running in this scope or process. Poll or cancel an existing task, or run this call in the foreground.',
1172
+ });
1173
+ }
1174
+ const startedThreadId = started.task.threadId?.trim();
1175
+ if (
1176
+ this.taskConfig.store.supportsThreadContinuation === true &&
1177
+ (startedThreadId == null || startedThreadId === '')
1178
+ ) {
1179
+ this.taskConfig.store.control(
1180
+ this.taskConfig.scopeId,
1181
+ started.task.taskId,
1182
+ { action: 'cancel' }
1183
+ );
1184
+ return JSON.stringify({
1185
+ status: 'rejected',
1186
+ tool: Constants.SUBAGENT,
1187
+ message:
1188
+ 'The host accepted the subagent task without assigning its required thread ID.',
1189
+ });
1190
+ }
1191
+ return JSON.stringify({
1192
+ background_task_id: started.task.taskId,
1193
+ ...(this.taskConfig.store.supportsThreadContinuation === true
1194
+ ? { subagent_thread_id: startedThreadId }
1195
+ : {}),
1196
+ tool: Constants.SUBAGENT,
1197
+ subagent_type: params.subagentType,
1198
+ status: started.task.status,
1199
+ message: `${started.isNew ? 'Started' : 'Reused'} subagent "${params.subagentType}" background task. Poll the host background-task tool with background_task_id "${started.task.taskId}" to check progress or collect its result.`,
1200
+ });
1201
+ }
1202
+
1203
+ private async executeDetached(
1204
+ params: SubagentExecuteParams,
1205
+ runtime: SubagentTaskRuntime,
1206
+ detachedHandlers: HandlerRegistry,
1207
+ taskHookRegistry: HookRegistry,
1208
+ detachedGraphFactory?: GraphFactory
1209
+ ): Promise<SubagentExecuteResult> {
1210
+ const sourceHookSessionId =
1211
+ asNonEmptyString(params.parentConfigurable?.run_id) ??
1212
+ this.executionContext.hookSessionId;
1213
+ const taskHookSessionId = getBackgroundTaskHookSessionId(
1214
+ sourceHookSessionId,
1215
+ runtime.taskId
1216
+ );
1217
+ const unregisterHooks: Array<() => void> = [];
1218
+ unregisterHooks.push(
1219
+ taskHookRegistry.registerSession(taskHookSessionId, 'PostToolBatch', {
1220
+ hooks: [
1221
+ (): PostToolBatchHookOutput => ({
1222
+ injectedMessages: runtime.drain('tool'),
1223
+ }),
1224
+ ],
1225
+ }),
1226
+ taskHookRegistry.registerSession(taskHookSessionId, 'PreemptBoundary', {
1227
+ hooks: [
1228
+ (): PreemptBoundaryHookOutput => ({
1229
+ injectedMessages: runtime.drain('preempt'),
1230
+ }),
1231
+ ],
1232
+ })
1233
+ );
1234
+
1235
+ detachedHandlers.register(GraphEvents.ON_SUBAGENT_UPDATE, {
1236
+ handle: (_event, data): void => {
1237
+ runtime.reportProgress(data as SubagentUpdateEvent);
1238
+ },
1239
+ });
1240
+
1241
+ const detached = new SubagentExecutor({
1242
+ configs: this.configs,
1243
+ parentSignal: runtime.signal,
1244
+ hookRegistry: taskHookRegistry,
1245
+ parentHandlerRegistry: detachedHandlers,
1246
+ parentRunId: this.parentRunId,
1247
+ parentAgentId: this.parentAgentId,
1248
+ executionContext: {
1249
+ ...this.executionContext,
1250
+ hookSessionId: taskHookSessionId,
1251
+ },
1252
+ langfuse: this.langfuse,
1253
+ tokenCounter: this.tokenCounter,
1254
+ usageSink: this.usageSink,
1255
+ streamLimits: this.streamLimits,
1256
+ maxDepth: this.maxDepth,
1257
+ createChildGraph:
1258
+ detachedGraphFactory == null
1259
+ ? this.createChildGraph
1260
+ : (input): StandardGraph =>
1261
+ detachedGraphFactory({ kind: 'standard', input }),
1262
+ createChildGraphByKind:
1263
+ detachedGraphFactory ?? this.createChildGraphByKind,
1264
+ });
1265
+ try {
1266
+ const result = await detached.execute({
1267
+ ...params,
1268
+ signal: undefined,
1269
+ breaker: undefined,
1270
+ hookSessionId: taskHookSessionId,
1271
+ taskRuntime: runtime,
1272
+ parentConfigurable: {
1273
+ ...params.parentConfigurable,
1274
+ run_id: taskHookSessionId,
1275
+ },
1276
+ });
1277
+ if (runtime.signal.aborted) {
1278
+ throw runtime.signal.reason instanceof Error
1279
+ ? runtime.signal.reason
1280
+ : new Error('Detached subagent task cancelled.');
1281
+ }
1282
+ if (result.error != null) {
1283
+ throw new Error(result.error);
1284
+ }
1285
+ return result;
1286
+ } finally {
1287
+ detached.clearHeavyState();
1288
+ for (const unregister of unregisterHooks) {
1289
+ unregister();
1290
+ }
1291
+ taskHookRegistry.clearSession(taskHookSessionId);
1292
+ }
1293
+ }
1294
+
982
1295
  private bindExecutionDefinition(
983
1296
  execution: DurableExecutionRecord,
984
1297
  binding: SubagentDefinitionBinding,
@@ -1940,36 +2253,38 @@ export class SubagentExecutor {
1940
2253
  const executableConfig = this.configs.get(params.subagentType);
1941
2254
  if (executableConfig == null) {
1942
2255
  const available = [...this.configs.keys()].join(', ');
1943
- return Promise.resolve({
1944
- content: `Error: Unknown subagent type "${params.subagentType}". Available types: ${available}`,
1945
- messages: [],
1946
- });
2256
+ return Promise.resolve(
2257
+ createSubagentFailure(
2258
+ `Error: Unknown subagent type "${params.subagentType}". Available types: ${available}`
2259
+ )
2260
+ );
1947
2261
  }
1948
2262
  if (this.maxDepth <= 0) {
1949
- return Promise.resolve({
1950
- content: 'Error: Maximum subagent nesting depth exceeded.',
1951
- messages: [],
1952
- });
2263
+ return Promise.resolve(
2264
+ createSubagentFailure(
2265
+ 'Error: Maximum subagent nesting depth exceeded.'
2266
+ )
2267
+ );
1953
2268
  }
1954
2269
  if (
1955
2270
  isGraphSubagentConfig(executableConfig) &&
1956
2271
  this.humanInTheLoop?.enabled === true
1957
2272
  ) {
1958
- return Promise.resolve({
1959
- content:
1960
- 'Error: Human-in-the-loop execution is not yet supported for graph subagents.',
1961
- messages: [],
1962
- });
2273
+ return Promise.resolve(
2274
+ createSubagentFailure(
2275
+ 'Error: Human-in-the-loop execution is not yet supported for graph subagents.'
2276
+ )
2277
+ );
1963
2278
  }
1964
2279
  if (
1965
2280
  this.humanInTheLoop?.enabled === true &&
1966
2281
  (params.parentToolCallId == null || params.parentToolCallId === '')
1967
2282
  ) {
1968
- return Promise.resolve({
1969
- content:
1970
- 'Error: Resumable subagent execution requires a parent tool call ID.',
1971
- messages: [],
1972
- });
2283
+ return Promise.resolve(
2284
+ createSubagentFailure(
2285
+ 'Error: Resumable subagent execution requires a parent tool call ID.'
2286
+ )
2287
+ );
1973
2288
  }
1974
2289
  const execution = this.executions.open({
1975
2290
  threadId: params.threadId,
@@ -1989,16 +2304,14 @@ export class SubagentExecutor {
1989
2304
  );
1990
2305
  } catch (error) {
1991
2306
  if (error instanceof SubagentDefinitionBindingError) {
1992
- return Promise.resolve({
1993
- content: SUBAGENT_CONFIG_CHANGED_MESSAGE,
1994
- messages: [],
1995
- });
2307
+ return Promise.resolve(
2308
+ createSubagentFailure(SUBAGENT_CONFIG_CHANGED_MESSAGE)
2309
+ );
1996
2310
  }
1997
2311
  if (error instanceof SubagentInvocationBindingError) {
1998
- return Promise.resolve({
1999
- content: SUBAGENT_INVOCATION_CHANGED_MESSAGE,
2000
- messages: [],
2001
- });
2312
+ return Promise.resolve(
2313
+ createSubagentFailure(SUBAGENT_INVOCATION_CHANGED_MESSAGE)
2314
+ );
2002
2315
  }
2003
2316
  throw error;
2004
2317
  }
@@ -2030,10 +2343,7 @@ export class SubagentExecutor {
2030
2343
  )
2031
2344
  ) {
2032
2345
  this.executions.remove(execution);
2033
- return {
2034
- content: SUBAGENT_CONFIG_CHANGED_MESSAGE,
2035
- messages: [],
2036
- };
2346
+ return createSubagentFailure(SUBAGENT_CONFIG_CHANGED_MESSAGE);
2037
2347
  }
2038
2348
  let identity: SubagentExecutionIdentity;
2039
2349
  try {
@@ -2043,10 +2353,7 @@ export class SubagentExecutor {
2043
2353
  if (error instanceof StreamLimitExceededError) {
2044
2354
  throw error;
2045
2355
  }
2046
- return {
2047
- content: SUBAGENT_RESOLUTION_ERROR_MESSAGE,
2048
- messages: [],
2049
- };
2356
+ return createSubagentFailure(SUBAGENT_RESOLUTION_ERROR_MESSAGE);
2050
2357
  }
2051
2358
  const { childRunId, childThreadId, approvalExecutionScope } = identity;
2052
2359
  const bound = this.bindExecutionDefinition(
@@ -2060,7 +2367,7 @@ export class SubagentExecutor {
2060
2367
  'effective'
2061
2368
  );
2062
2369
  if (!bound) {
2063
- return { content: SUBAGENT_CONFIG_CHANGED_MESSAGE, messages: [] };
2370
+ return createSubagentFailure(SUBAGENT_CONFIG_CHANGED_MESSAGE);
2064
2371
  }
2065
2372
  const completedChildResult = execution.completedResult;
2066
2373
  if (completedChildResult != null) {
@@ -2081,10 +2388,7 @@ export class SubagentExecutor {
2081
2388
  if (error instanceof StreamLimitExceededError) {
2082
2389
  throw error;
2083
2390
  }
2084
- return {
2085
- content: SUBAGENT_RESOLUTION_ERROR_MESSAGE,
2086
- messages: [],
2087
- };
2391
+ return createSubagentFailure(SUBAGENT_RESOLUTION_ERROR_MESSAGE);
2088
2392
  }
2089
2393
 
2090
2394
  const parentRegistry = this.getParentHandlerRegistry();
@@ -2109,6 +2413,7 @@ export class SubagentExecutor {
2109
2413
  });
2110
2414
  const childAgentId = childPlan.subjectAgentId;
2111
2415
  const currentHookSessionId =
2416
+ asNonEmptyString(params.hookSessionId) ??
2112
2417
  asNonEmptyString(params.parentConfigurable?.run_id) ??
2113
2418
  this.executionContext.hookSessionId;
2114
2419
  const childExecutionContext: SubagentExecutionContext = {
@@ -2132,7 +2437,8 @@ export class SubagentExecutor {
2132
2437
  const memberRecursionLimit = maxTurns * SUBAGENT_RECURSION_MULTIPLIER;
2133
2438
  const recursionLimit =
2134
2439
  memberRecursionLimit *
2135
- (childPlan.kind === 'graph' ? childPlan.agents.length : 1);
2440
+ (childPlan.kind === 'graph' ? childPlan.agents.length : 1) +
2441
+ (params.taskRuntime == null ? 0 : MAX_BACKGROUND_SUBAGENT_SEALS);
2136
2442
 
2137
2443
  const hostUsageSink = this.usageSink;
2138
2444
  let subagentUsageSink: SubagentUsageSink | undefined;
@@ -2162,15 +2468,22 @@ export class SubagentExecutor {
2162
2468
  * never see grandchild model calls.
2163
2469
  */
2164
2470
  subagentUsageSink,
2471
+ ...(params.taskRuntime == null
2472
+ ? {}
2473
+ : {
2474
+ preemption: {
2475
+ shouldPreempt: (): boolean =>
2476
+ params.taskRuntime?.shouldPreempt() === true,
2477
+ maxSeals: MAX_BACKGROUND_SUBAGENT_SEALS,
2478
+ },
2479
+ }),
2165
2480
  };
2166
2481
  let childGraph = cachedChildRun?.graph;
2167
2482
  if (childGraph == null && childPlan.kind === 'graph') {
2168
2483
  if (this.createChildGraphByKind == null) {
2169
- return {
2170
- content:
2171
- 'Error: Graph subagent execution requires a polymorphic child graph factory.',
2172
- messages: [],
2173
- };
2484
+ return createSubagentFailure(
2485
+ 'Error: Graph subagent execution requires a polymorphic child graph factory.'
2486
+ );
2174
2487
  }
2175
2488
  childGraph = this.createChildGraphByKind({
2176
2489
  kind: 'multi-agent',
@@ -2183,6 +2496,9 @@ export class SubagentExecutor {
2183
2496
  });
2184
2497
  }
2185
2498
  childGraph ??= this.createChildGraph(childGraphInput);
2499
+ if (params.taskRuntime != null) {
2500
+ childGraph.hookRegistry = this.hookRegistry;
2501
+ }
2186
2502
  if (cachedChildRun == null) {
2187
2503
  seedChildGraphSessions(childGraph, childPlan.agents);
2188
2504
  }
@@ -2368,6 +2684,7 @@ export class SubagentExecutor {
2368
2684
  } else {
2369
2685
  childInput = {
2370
2686
  messages: [
2687
+ ...(params.initialMessages ?? []),
2371
2688
  new HumanMessage({
2372
2689
  content: description,
2373
2690
  additional_kwargs: {
@@ -2426,26 +2743,46 @@ export class SubagentExecutor {
2426
2743
  });
2427
2744
  }
2428
2745
 
2429
- let childResult: MultiAgentGraphState;
2430
- if (this.humanInTheLoop?.enabled === true) {
2431
- /** Execute as an independently checkpointed root instead of inheriting
2432
- * the parent's Pregel namespace. Parent decisions are routed explicitly
2433
- * by interrupt id, so concurrent children keep isolated resume state. */
2434
- childResult = await AsyncLocalStorageProviderSingleton.runWithConfig(
2435
- childInvokeConfig,
2436
- (): Promise<MultiAgentGraphState> =>
2437
- workflow.invoke(childInput, childInvokeConfig)
2438
- );
2439
- } else {
2440
- childResult = await workflow.invoke(childInput, childInvokeConfig);
2441
- }
2442
- const childInterrupts = isInterrupted(childResult)
2443
- ? childResult[INTERRUPT]
2444
- : undefined;
2445
- if (childInterrupts != null && childInterrupts.length > 0) {
2446
- throw new GraphInterrupt(childInterrupts);
2746
+ for (;;) {
2747
+ let childResult: MultiAgentGraphState;
2748
+ if (this.humanInTheLoop?.enabled === true) {
2749
+ /** Execute as an independently checkpointed root instead of inheriting
2750
+ * the parent's Pregel namespace. Parent decisions are routed explicitly
2751
+ * by interrupt id, so concurrent children keep isolated resume state. */
2752
+ childResult =
2753
+ await AsyncLocalStorageProviderSingleton.runWithConfig(
2754
+ childInvokeConfig,
2755
+ (): Promise<MultiAgentGraphState> =>
2756
+ workflow.invoke(childInput, childInvokeConfig)
2757
+ );
2758
+ } else {
2759
+ childResult = await workflow.invoke(childInput, childInvokeConfig);
2760
+ }
2761
+ const childInterrupts = isInterrupted(childResult)
2762
+ ? childResult[INTERRUPT]
2763
+ : undefined;
2764
+ if (childInterrupts != null && childInterrupts.length > 0) {
2765
+ throw new GraphInterrupt(childInterrupts);
2766
+ }
2767
+ result = childResult;
2768
+ const continuation = params.taskRuntime?.closeTurn();
2769
+ if (continuation == null || continuation.closed) {
2770
+ break;
2771
+ }
2772
+ /**
2773
+ * A queued control becomes the next user turn inside the same
2774
+ * detached task. Reset graph sidecars while carrying the complete
2775
+ * child transcript forward, preserving one task/trace identity and
2776
+ * avoiding a second subagent lifecycle or duplicate billing path.
2777
+ */
2778
+ childGraph.resetValues(true);
2779
+ childInput = {
2780
+ messages: [
2781
+ ...childResult.messages,
2782
+ ...convertInjectedMessages(continuation.messages),
2783
+ ],
2784
+ };
2447
2785
  }
2448
- result = childResult;
2449
2786
  }
2450
2787
  } catch (error) {
2451
2788
  /** Stamped at failure, not after the error-envelope work below. */
@@ -2535,10 +2872,10 @@ export class SubagentExecutor {
2535
2872
  if (error instanceof StreamLimitExceededError) {
2536
2873
  throw error;
2537
2874
  }
2538
- return {
2539
- content: `Subagent error: ${errorMessage}`,
2540
- messages: [],
2541
- };
2875
+ return createSubagentFailure(
2876
+ `Subagent error: ${errorMessage}`,
2877
+ errorMessage
2878
+ );
2542
2879
  }
2543
2880
 
2544
2881
  /**