@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.
- package/dist/cjs/graphs/Graph.cjs +44 -19
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/hooks/HookRegistry.cjs +7 -1
- package/dist/cjs/hooks/HookRegistry.cjs.map +1 -1
- package/dist/cjs/hooks/index.cjs +1 -1
- package/dist/cjs/langchain/index.cjs +12 -0
- package/dist/cjs/langchain/messages.cjs +12 -0
- package/dist/cjs/langfuseConfig.cjs +16 -0
- package/dist/cjs/langfuseConfig.cjs.map +1 -1
- package/dist/cjs/langfuseSpanRegistry.cjs +16 -4
- package/dist/cjs/langfuseSpanRegistry.cjs.map +1 -1
- package/dist/cjs/main.cjs +15 -1
- package/dist/cjs/run.cjs +4 -0
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/tools/SubagentTool.cjs +14 -4
- package/dist/cjs/tools/SubagentTool.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +1 -1
- package/dist/cjs/tools/subagent/InMemorySubagentTaskStore.cjs +403 -0
- package/dist/cjs/tools/subagent/InMemorySubagentTaskStore.cjs.map +1 -0
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs +201 -61
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
- package/dist/cjs/tools/subagent/index.cjs +1 -0
- package/dist/esm/graphs/Graph.mjs +44 -19
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/hooks/HookRegistry.mjs +7 -1
- package/dist/esm/hooks/HookRegistry.mjs.map +1 -1
- package/dist/esm/hooks/index.mjs +1 -1
- package/dist/esm/langchain/index.mjs +2 -2
- package/dist/esm/langchain/messages.mjs +2 -2
- package/dist/esm/langfuseConfig.mjs +16 -0
- package/dist/esm/langfuseConfig.mjs.map +1 -1
- package/dist/esm/langfuseSpanRegistry.mjs +16 -4
- package/dist/esm/langfuseSpanRegistry.mjs.map +1 -1
- package/dist/esm/main.mjs +4 -3
- package/dist/esm/run.mjs +4 -0
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/tools/SubagentTool.mjs +14 -4
- package/dist/esm/tools/SubagentTool.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +1 -1
- package/dist/esm/tools/subagent/InMemorySubagentTaskStore.mjs +403 -0
- package/dist/esm/tools/subagent/InMemorySubagentTaskStore.mjs.map +1 -0
- package/dist/esm/tools/subagent/SubagentExecutor.mjs +201 -61
- package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
- package/dist/esm/tools/subagent/index.mjs +1 -0
- package/dist/types/graphs/Graph.d.ts +6 -3
- package/dist/types/hooks/HookRegistry.d.ts +8 -0
- package/dist/types/langchain/messages.d.ts +2 -2
- package/dist/types/langfuseSpanRegistry.d.ts +9 -4
- package/dist/types/run.d.ts +1 -0
- package/dist/types/tools/SubagentTool.d.ts +5 -2
- package/dist/types/tools/subagent/InMemorySubagentTaskStore.d.ts +45 -0
- package/dist/types/tools/subagent/SubagentExecutor.d.ts +30 -3
- package/dist/types/tools/subagent/index.d.ts +2 -0
- package/dist/types/types/graph.d.ts +22 -4
- package/dist/types/types/index.d.ts +1 -0
- package/dist/types/types/run.d.ts +6 -0
- package/dist/types/types/subagentTasks.d.ts +172 -0
- package/package.json +1 -1
- package/src/graphs/Graph.ts +75 -30
- package/src/hooks/HookRegistry.ts +18 -0
- package/src/langchain/messages.ts +3 -0
- package/src/langfuseConfig.ts +43 -0
- package/src/langfuseSpanRegistry.ts +42 -4
- package/src/run.ts +4 -0
- package/src/tools/SubagentTool.ts +37 -3
- package/src/tools/subagent/InMemorySubagentTaskStore.ts +632 -0
- package/src/tools/subagent/SubagentExecutor.ts +411 -74
- package/src/tools/subagent/index.ts +2 -0
- package/src/types/graph.ts +22 -4
- package/src/types/index.ts +1 -0
- package/src/types/run.ts +6 -0
- 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
|
-
|
|
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
|
-
|
|
1945
|
-
|
|
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
|
-
|
|
1951
|
-
|
|
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
|
-
|
|
1960
|
-
'Error: Human-in-the-loop execution is not yet supported for graph subagents.'
|
|
1961
|
-
|
|
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
|
-
|
|
1970
|
-
'Error: Resumable subagent execution requires a parent tool call ID.'
|
|
1971
|
-
|
|
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
|
-
|
|
1994
|
-
|
|
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
|
-
|
|
2000
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
2171
|
-
|
|
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
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
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
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2875
|
+
return createSubagentFailure(
|
|
2876
|
+
`Subagent error: ${errorMessage}`,
|
|
2877
|
+
errorMessage
|
|
2878
|
+
);
|
|
2542
2879
|
}
|
|
2543
2880
|
|
|
2544
2881
|
/**
|