@librechat/agents 3.6.3 → 3.6.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/dist/cjs/graphs/Graph.cjs +32 -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/main.cjs +3 -1
  7. package/dist/cjs/run.cjs +4 -0
  8. package/dist/cjs/run.cjs.map +1 -1
  9. package/dist/cjs/tools/SubagentTool.cjs +8 -3
  10. package/dist/cjs/tools/SubagentTool.cjs.map +1 -1
  11. package/dist/cjs/tools/ToolNode.cjs +1 -1
  12. package/dist/cjs/tools/subagent/InMemorySubagentTaskStore.cjs +399 -0
  13. package/dist/cjs/tools/subagent/InMemorySubagentTaskStore.cjs.map +1 -0
  14. package/dist/cjs/tools/subagent/SubagentExecutor.cjs +168 -60
  15. package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
  16. package/dist/cjs/tools/subagent/index.cjs +1 -0
  17. package/dist/esm/graphs/Graph.mjs +32 -19
  18. package/dist/esm/graphs/Graph.mjs.map +1 -1
  19. package/dist/esm/hooks/HookRegistry.mjs +7 -1
  20. package/dist/esm/hooks/HookRegistry.mjs.map +1 -1
  21. package/dist/esm/hooks/index.mjs +1 -1
  22. package/dist/esm/main.mjs +3 -2
  23. package/dist/esm/run.mjs +4 -0
  24. package/dist/esm/run.mjs.map +1 -1
  25. package/dist/esm/tools/SubagentTool.mjs +8 -3
  26. package/dist/esm/tools/SubagentTool.mjs.map +1 -1
  27. package/dist/esm/tools/ToolNode.mjs +1 -1
  28. package/dist/esm/tools/subagent/InMemorySubagentTaskStore.mjs +399 -0
  29. package/dist/esm/tools/subagent/InMemorySubagentTaskStore.mjs.map +1 -0
  30. package/dist/esm/tools/subagent/SubagentExecutor.mjs +168 -60
  31. package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
  32. package/dist/esm/tools/subagent/index.mjs +1 -0
  33. package/dist/types/graphs/Graph.d.ts +6 -3
  34. package/dist/types/hooks/HookRegistry.d.ts +8 -0
  35. package/dist/types/run.d.ts +1 -0
  36. package/dist/types/tools/SubagentTool.d.ts +3 -1
  37. package/dist/types/tools/subagent/InMemorySubagentTaskStore.d.ts +45 -0
  38. package/dist/types/tools/subagent/SubagentExecutor.d.ts +26 -3
  39. package/dist/types/tools/subagent/index.d.ts +2 -0
  40. package/dist/types/types/graph.d.ts +11 -4
  41. package/dist/types/types/index.d.ts +1 -0
  42. package/dist/types/types/run.d.ts +6 -0
  43. package/dist/types/types/subagentTasks.d.ts +140 -0
  44. package/package.json +1 -1
  45. package/src/graphs/Graph.ts +54 -30
  46. package/src/hooks/HookRegistry.ts +18 -0
  47. package/src/run.ts +4 -0
  48. package/src/tools/SubagentTool.ts +20 -2
  49. package/src/tools/subagent/InMemorySubagentTaskStore.ts +624 -0
  50. package/src/tools/subagent/SubagentExecutor.ts +341 -74
  51. package/src/tools/subagent/index.ts +2 -0
  52. package/src/types/graph.ts +11 -4
  53. package/src/types/index.ts +1 -0
  54. package/src/types/run.ts +6 -0
  55. package/src/types/subagentTasks.ts +129 -0
@@ -0,0 +1,45 @@
1
+ import type { SubagentTaskClaim, SubagentTaskControlCommand, SubagentTaskControlResult, SubagentTaskSnapshot, SubagentTaskStartRequest, SubagentTaskStartResult, SubagentTaskStore } from '@/types';
2
+ export interface InMemorySubagentTaskStoreOptions {
3
+ completedTtlMs?: number;
4
+ maxControlMessageChars?: number;
5
+ maxControlsPerTask?: number;
6
+ maxErrorChars?: number;
7
+ maxResultChars?: number;
8
+ maxRunningPerScope?: number;
9
+ maxRunningTotal?: number;
10
+ maxTasksPerScope?: number;
11
+ maxTasksTotal?: number;
12
+ taskTimeoutMs?: number;
13
+ }
14
+ /**
15
+ * Bounded process-local task ownership for detached subagents. Terminal tasks
16
+ * keep only a bounded claimable result: the child graph, checkpoint, and full
17
+ * transcript are released. Hosts that need later child-chat continuation may
18
+ * replace this store and persist the canonical messages returned by `run`.
19
+ * This default deliberately makes no restart or cross-replica durability
20
+ * claim.
21
+ */
22
+ export declare class InMemorySubagentTaskStore implements SubagentTaskStore {
23
+ private readonly buckets;
24
+ private readonly options;
25
+ private runningTasks;
26
+ private totalTasks;
27
+ constructor(options?: InMemorySubagentTaskStoreOptions);
28
+ start(request: SubagentTaskStartRequest): SubagentTaskStartResult;
29
+ get(scopeId: string, taskId: string): SubagentTaskSnapshot | undefined;
30
+ list(scopeId: string): SubagentTaskSnapshot[];
31
+ claim(scopeId: string, taskId: string): SubagentTaskClaim;
32
+ control(scopeId: string, taskId: string, command: SubagentTaskControlCommand): SubagentTaskControlResult;
33
+ private getBucket;
34
+ private find;
35
+ private makeRoom;
36
+ private makeGlobalRoom;
37
+ private sweepBucket;
38
+ private removeTask;
39
+ private dropEmptyBucket;
40
+ private scheduleExpiry;
41
+ private clearTaskExpiry;
42
+ private clearTaskTimeout;
43
+ private finishWithError;
44
+ private createRuntime;
45
+ }
@@ -1,12 +1,12 @@
1
1
  import { BaseMessage } from '@langchain/core/messages';
2
2
  import type { RunnableConfig } from '@langchain/core/runnables';
3
3
  import type { ToolCall } from '@langchain/core/messages/tool';
4
- import type { MultiAgentGraphState, HumanInTheLoopConfig, StandardGraphInput, ExecutableSubagentConfigEntry, SubagentExecutionContext, SubagentUsageSink, TokenCounter } from '@/types';
4
+ import type { MultiAgentGraphState, HumanInTheLoopConfig, StandardGraphInput, ExecutableSubagentConfigEntry, SubagentExecutionContext, SubagentTaskConfig, SubagentTaskRuntime, SubagentUsageSink, TokenCounter } from '@/types';
5
5
  import type { SubagentResumeManifest, SettledSubagentToolOutput } from './SubagentReplay';
6
- import type { HookRegistry } from '@/hooks';
7
6
  import type { GraphFactory } from '@/graphs/graphFactory';
8
7
  import type { StandardGraph } from '@/graphs/Graph';
9
- import type { HandlerRegistry } from '@/events';
8
+ import { HookRegistry } from '@/hooks';
9
+ import { HandlerRegistry } from '@/events';
10
10
  export { buildChildInputs, isGraphSubagentConfig, normalizeSubagentConfigs, normalizeSubagentConfigEntries, resolveSubagentConfigs, resolveSubagentConfigEntries, } from './childGraphConfig';
11
11
  export declare const DEFAULT_SUBAGENT_DESCRIPTION = "No task description provided";
12
12
  export type SubagentExecuteParams = {
@@ -62,10 +62,16 @@ export type SubagentExecuteParams = {
62
62
  * rather than sharing parent's host context.
63
63
  */
64
64
  parentConfigurable?: Record<string, unknown>;
65
+ /** Dedicated hook session used by a detached task. @internal */
66
+ hookSessionId?: string;
67
+ /** Process-local task controls consumed by the child graph. @internal */
68
+ taskRuntime?: SubagentTaskRuntime;
65
69
  };
66
70
  export type SubagentExecuteResult = {
67
71
  content: string;
68
72
  messages: BaseMessage[];
73
+ /** Tagged internal failure; foreground callers retain the legacy content. */
74
+ error?: string;
69
75
  };
70
76
  /**
71
77
  * Factory that constructs a child graph for subagent execution. Injected
@@ -121,6 +127,11 @@ export type SubagentExecutorOptions = {
121
127
  /** Preferred polymorphic child constructor. The legacy standard-only
122
128
  * factory remains required for source compatibility. */
123
129
  createChildGraphByKind?: GraphFactory;
130
+ /**
131
+ * Captures a child-graph factory and its run-scoped host dependencies
132
+ * synchronously, before a detached task can outlive parent cleanup.
133
+ */
134
+ createDetachedChildGraphFactory?: (parentHandlerRegistry: HandlerRegistry) => GraphFactory;
124
135
  /**
125
136
  * Parent's event handler registry. When provided, child-graph events are
126
137
  * forwarded through this registry so hosts can:
@@ -143,6 +154,8 @@ export type SubagentExecutorOptions = {
143
154
  * nested subagents report through the same sink.
144
155
  */
145
156
  usageSink?: SubagentUsageSink;
157
+ /** Host-owned process-local task namespace for detached execution. */
158
+ taskConfig?: SubagentTaskConfig;
146
159
  };
147
160
  export declare class SubagentExecutor {
148
161
  private readonly configs;
@@ -167,7 +180,9 @@ export declare class SubagentExecutor {
167
180
  private readonly maxDepth;
168
181
  private readonly createChildGraph;
169
182
  private readonly createChildGraphByKind?;
183
+ private readonly createDetachedChildGraphFactory?;
170
184
  private readonly usageSink?;
185
+ private readonly taskConfig?;
171
186
  private readonly executions;
172
187
  private replayCheckpointWorkflow?;
173
188
  private readonly resolveParentHandlerRegistry?;
@@ -181,6 +196,14 @@ export declare class SubagentExecutor {
181
196
  private composeChildSignal;
182
197
  /** Snapshot of the parent's registry at the moment a subagent is dispatched. */
183
198
  private getParentHandlerRegistry;
199
+ /**
200
+ * Starts one independently-owned executor behind the configured task store.
201
+ * The parent ToolNode receives the handle synchronously; the detached clone
202
+ * is not registered on the parent graph, so end-of-turn cleanup cannot
203
+ * invalidate or clear a child that intentionally outlives that turn.
204
+ */
205
+ executeInBackground(params: SubagentExecuteParams): string;
206
+ private executeDetached;
184
207
  private bindExecutionDefinition;
185
208
  /** Resolve one lazy descriptor per stable child execution. Concurrent
186
209
  * duplicate dispatches share the same in-flight resolution; HITL re-entry
@@ -1,2 +1,4 @@
1
1
  export { DEFAULT_SUBAGENT_DESCRIPTION, SubagentExecutor, filterSubagentResult, filterGraphSubagentResult, isGraphSubagentConfig, normalizeSubagentConfigs, normalizeSubagentConfigEntries, resolveSubagentConfigs, resolveSubagentConfigEntries, buildChildInputs, summarizeEvent, } from './SubagentExecutor';
2
2
  export type { SubagentExecuteParams, SubagentExecuteResult, SubagentExecutorOptions, ChildGraphFactory, } from './SubagentExecutor';
3
+ export { InMemorySubagentTaskStore } from './InMemorySubagentTaskStore';
4
+ export type { InMemorySubagentTaskStoreOptions } from './InMemorySubagentTaskStore';
@@ -9,6 +9,7 @@ import type { RunStep, RunStepDeltaEvent, RunStepResumeState, RunStepClosedEvent
9
9
  import type { ToolMap, ToolSessionMap, ToolEndEvent, GenericTool, LCTool, ToolExecuteBatchRequest } from '@/types/tools';
10
10
  import type { TokenCounter, StreamLimits, StreamPreemption, TokenBudgetBreakdown } from '@/types/run';
11
11
  import type { Providers, Callback, GraphNodeKeys } from '@/common';
12
+ import type { SubagentTaskConfig } from '@/types/subagentTasks';
12
13
  import type { StandardGraph, MultiAgentGraph } from '@/graphs';
13
14
  import type { ClientOptions } from '@/types/llm';
14
15
  /** Interface for bound model with stream and invoke methods */
@@ -239,6 +240,12 @@ export type StandardGraphInput = {
239
240
  * they already flow through the registry's `CHAT_MODEL_END` handler.
240
241
  */
241
242
  subagentUsageSink?: SubagentUsageSink;
243
+ /**
244
+ * Optional host-owned process-local task namespace for detached subagents.
245
+ * Presence enables `run_in_background` on the subagent tool. Child graphs
246
+ * do not inherit it, keeping background nesting disabled for the MVP.
247
+ */
248
+ subagentTasks?: SubagentTaskConfig;
242
249
  /**
243
250
  * True when this graph IS a subagent child run (set by `SubagentExecutor`
244
251
  * when it constructs the child graph). Drives the hook-input `agentId`
@@ -250,10 +257,10 @@ export type StandardGraphInput = {
250
257
  */
251
258
  subagentScope?: boolean;
252
259
  /**
253
- * Cooperative preemption, forwarded from `RunConfig.preemption`. Only ever
254
- * set on the top-level graph: a steer targets the conversation, so subagent
255
- * children must run to completion and `buildChildInputs` does not propagate
256
- * this field.
260
+ * Cooperative preemption, forwarded from `RunConfig.preemption`. Ordinary
261
+ * child graphs do not inherit it. Detached subagent tasks may receive their
262
+ * own internal parent-control source so an interrupt can reuse the same
263
+ * provider-safe sealing path without targeting the top-level conversation.
257
264
  */
258
265
  preemption?: StreamPreemption;
259
266
  /**
@@ -5,6 +5,7 @@ export * from './messages';
5
5
  export * from './run';
6
6
  export * from './skill';
7
7
  export * from './stream';
8
+ export * from './subagentTasks';
8
9
  export * from './tools';
9
10
  export * from './summarize';
10
11
  export * from './activityLabel';
@@ -4,6 +4,7 @@ import type { BaseMessage } from '@langchain/core/messages';
4
4
  import type { StructuredTool } from '@langchain/core/tools';
5
5
  import type * as z from 'zod';
6
6
  import type { ToolSessionMap, ToolExecutionConfig, ToolOutputReferencesConfig, EagerEventToolExecutionConfig } from '@/types/tools';
7
+ import type { SubagentTaskConfig } from '@/types/subagentTasks';
7
8
  import type { HumanInTheLoopConfig } from '@/types/hitl';
8
9
  import type { HookRegistry } from '@/hooks';
9
10
  import type * as s from '@/types/stream';
@@ -215,6 +216,11 @@ export type RunConfig = {
215
216
  * the registered `CHAT_MODEL_END` handler as usual.
216
217
  */
217
218
  subagentUsageSink?: g.SubagentUsageSink;
219
+ /**
220
+ * Trusted process-local task namespace for detached subagent execution.
221
+ * Omit to preserve foreground-only subagent behavior.
222
+ */
223
+ subagentTasks?: SubagentTaskConfig;
218
224
  /**
219
225
  * Pre-constructed hook registry for this run. Hooks fire at lifecycle
220
226
  * points in `processStream` (RunStart, UserPromptSubmit, Stop,
@@ -0,0 +1,140 @@
1
+ import type { BaseMessage } from '@langchain/core/messages';
2
+ import type { SubagentUpdateEvent } from './graph';
3
+ import type { InjectedMessage } from './tools';
4
+ /** Terminal and in-flight states for a detached subagent task. */
5
+ export type SubagentTaskStatus = 'running' | 'completed' | 'error' | 'cancelled';
6
+ /** Where a pending parent message may enter the child run. */
7
+ export type SubagentTaskBoundary = 'preempt' | 'tool' | 'turn';
8
+ /** Parent-to-child control operations accepted while a task is running. */
9
+ export type SubagentTaskControlCommand = {
10
+ action: 'steer' | 'queue' | 'interrupt';
11
+ message: string;
12
+ } | {
13
+ action: 'cancel';
14
+ } | {
15
+ action: 'cancel_message';
16
+ controlId: string;
17
+ };
18
+ /** Small, payload-free progress view safe to retain between parent turns. */
19
+ export interface SubagentTaskProgress {
20
+ phase: SubagentUpdateEvent['phase'];
21
+ at: number;
22
+ eventCount: number;
23
+ label?: string;
24
+ }
25
+ /** Read-only task metadata. Results are exposed only through `claim`. */
26
+ export interface SubagentTaskSnapshot {
27
+ /** Handle for this child-conversation execution within its trusted scope. */
28
+ taskId: string;
29
+ subagentType: string;
30
+ status: SubagentTaskStatus;
31
+ createdAt: number;
32
+ updatedAt: number;
33
+ resultAvailable: boolean;
34
+ resultClaimed: boolean;
35
+ pendingControls: number;
36
+ progress?: SubagentTaskProgress;
37
+ error?: string;
38
+ }
39
+ export type SubagentTaskClaim = {
40
+ status: 'running';
41
+ task: SubagentTaskSnapshot;
42
+ } | {
43
+ status: 'completed';
44
+ task: SubagentTaskSnapshot;
45
+ result: string;
46
+ } | {
47
+ status: 'error';
48
+ task: SubagentTaskSnapshot;
49
+ error: string;
50
+ } | {
51
+ status: 'cancelled';
52
+ task: SubagentTaskSnapshot;
53
+ error: string;
54
+ } | {
55
+ status: 'claimed';
56
+ task: SubagentTaskSnapshot;
57
+ } | {
58
+ status: 'not_found';
59
+ };
60
+ export type SubagentTaskControlResult = {
61
+ status: 'accepted';
62
+ task: SubagentTaskSnapshot;
63
+ controlId?: string;
64
+ } | {
65
+ status: 'cancelled';
66
+ task: SubagentTaskSnapshot;
67
+ } | {
68
+ status: 'not_running';
69
+ task: SubagentTaskSnapshot;
70
+ } | {
71
+ status: 'not_found';
72
+ } | {
73
+ status: 'control_not_found';
74
+ task: SubagentTaskSnapshot;
75
+ } | {
76
+ status: 'invalid';
77
+ message: string;
78
+ };
79
+ /**
80
+ * Child-side view supplied to one detached execution. It intentionally owns
81
+ * only cancellation, bounded message drains, and payload-free progress — no
82
+ * request/response object or host transport can leak into retained task state.
83
+ */
84
+ export interface SubagentTaskRuntime {
85
+ readonly taskId: string;
86
+ readonly signal: AbortSignal;
87
+ shouldPreempt(): boolean;
88
+ drain(boundary: SubagentTaskBoundary): InjectedMessage[];
89
+ closeTurn(): {
90
+ closed: boolean;
91
+ messages: InjectedMessage[];
92
+ };
93
+ reportProgress(event: SubagentUpdateEvent): void;
94
+ }
95
+ export interface SubagentTaskStartRequest {
96
+ scopeId: string;
97
+ idempotencyKey: string;
98
+ /** Stable hash of model-writable inputs used to reject conflicting replays. */
99
+ requestFingerprint?: string;
100
+ subagentType: string;
101
+ /**
102
+ * Starts one ephemeral execution lease. The canonical child transcript is
103
+ * returned so a host-owned store may persist it for a later fresh run;
104
+ * retaining a graph/checkpoint after terminal completion is unnecessary.
105
+ */
106
+ run(runtime: SubagentTaskRuntime): Promise<{
107
+ content: string;
108
+ messages?: BaseMessage[];
109
+ }>;
110
+ }
111
+ export type SubagentTaskStartResult = {
112
+ accepted: true;
113
+ isNew: boolean;
114
+ task: SubagentTaskSnapshot;
115
+ } | {
116
+ accepted: false;
117
+ reason: 'capacity';
118
+ } | {
119
+ accepted: false;
120
+ reason: 'conflict';
121
+ task: SubagentTaskSnapshot;
122
+ };
123
+ /**
124
+ * Host-replaceable store contract used by the SDK's subagent tool. The store
125
+ * should normally outlive individual `Run` instances. Durable hosts may
126
+ * persist the transcript returned by `run` under the task/conversation
127
+ * lineage and start a fresh execution for a later turn.
128
+ */
129
+ export interface SubagentTaskStore {
130
+ start(request: SubagentTaskStartRequest): SubagentTaskStartResult;
131
+ get(scopeId: string, taskId: string): SubagentTaskSnapshot | undefined;
132
+ list(scopeId: string): SubagentTaskSnapshot[];
133
+ claim(scopeId: string, taskId: string): SubagentTaskClaim;
134
+ control(scopeId: string, taskId: string, command: SubagentTaskControlCommand): SubagentTaskControlResult;
135
+ }
136
+ /** Trusted, host-selected task namespace. It is never model-writable. */
137
+ export interface SubagentTaskConfig {
138
+ store: SubagentTaskStore;
139
+ scopeId: string;
140
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@librechat/agents",
3
- "version": "3.6.3",
3
+ "version": "3.6.4",
4
4
  "reova": {
5
5
  "enabled": true,
6
6
  "endpoint": "https://telemetry.reo.dev/data"
@@ -1312,6 +1312,8 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1312
1312
  subagentUsageSink?: t.SubagentUsageSink;
1313
1313
  /** See {@link t.StandardGraphInput.subagentScope}. */
1314
1314
  subagentScope: boolean;
1315
+ /** See {@link t.StandardGraphInput.subagentTasks}. */
1316
+ subagentTasks: t.SubagentTaskConfig | undefined;
1315
1317
  /** See {@link t.StandardGraphInput.subagentExecutionContext}. */
1316
1318
  private readonly subagentExecutionContext?: t.SubagentExecutionContext;
1317
1319
  /** See {@link t.StandardGraphInput.preemption}. */
@@ -1445,6 +1447,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1445
1447
  indexTokenCountMap,
1446
1448
  calibrationRatio,
1447
1449
  subagentUsageSink,
1450
+ subagentTasks,
1448
1451
  subagentScope,
1449
1452
  subagentExecutionContext,
1450
1453
  preemption,
@@ -1469,6 +1472,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1469
1472
  this.signal = signal;
1470
1473
  this.langfuse = langfuse;
1471
1474
  this.subagentUsageSink = subagentUsageSink;
1475
+ this.subagentTasks = subagentTasks;
1472
1476
  this.subagentScope = subagentScope === true;
1473
1477
  this.subagentExecutionContext = subagentExecutionContext;
1474
1478
  this.preemption = preemption;
@@ -1695,8 +1699,9 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1695
1699
  * budget is taken by {@link claimPreemptSeal} once the accumulated chunk is
1696
1700
  * known to be safe, so a chunk that cannot seal never spends budget.
1697
1701
  *
1698
- * Subagent scopes never seal: a steer targets the top-level conversation,
1699
- * and a child run must finish so its parent sees a complete result.
1702
+ * Ordinary subagent scopes never receive `preemption`. A detached child may
1703
+ * receive a dedicated parent-control preemption source, in which case the
1704
+ * same provider-safe seal path is intentionally reused inside that child.
1700
1705
  */
1701
1706
  /** Internal seal preconditions only — no host callback, no side effects. */
1702
1707
  private canClaimPreemptSeal(): boolean {
@@ -1710,7 +1715,6 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1710
1715
  const runId =
1711
1716
  (this.config?.configurable?.run_id as string | undefined) ?? this.runId;
1712
1717
  return (
1713
- !this.subagentScope &&
1714
1718
  this.preemption != null &&
1715
1719
  !this.preemptSealInFlight &&
1716
1720
  this.preemptSealBudgetUsed < resolveMaxSeals(this.preemption.maxSeals) &&
@@ -4769,19 +4773,12 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
4769
4773
  }
4770
4774
  const getParentHandlerRegistry = (): HandlerRegistry | undefined =>
4771
4775
  this.handlerRegistry ?? this.parentToolHandlerRegistry;
4772
- const createConfiguredChildGraph: GraphFactory = (request) => {
4773
- const childGraph = this.graphFactory(request);
4774
- if (this.subagentModelOverride != null) {
4775
- childGraph.overrideModel = this.subagentModelOverride;
4776
- childGraph.setSubagentModelOverride(this.subagentModelOverride);
4777
- }
4778
- const childHandlerRegistry = createChildHandlerRegistry(
4779
- getParentHandlerRegistry()
4780
- );
4781
- // Pure execution-ordering hint (unlike `humanInTheLoop`). It only
4782
- // reorders tools already in the child's direct group; it does not
4783
- // force a schema-only event tool onto the direct execution path.
4784
- applyGraphRuntimeConfig(childGraph, {
4776
+ const snapshotChildGraphFactory = (
4777
+ parentHandlerRegistry: HandlerRegistry | undefined
4778
+ ): GraphFactory => {
4779
+ const graphFactory = this.graphFactory;
4780
+ const subagentModelOverride = this.subagentModelOverride;
4781
+ const runtimeConfig = {
4785
4782
  hookRegistry: this.hookRegistry,
4786
4783
  humanInTheLoop: this.humanInTheLoop,
4787
4784
  toolOutputReferences: this.toolOutputReferences,
@@ -4789,18 +4786,33 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
4789
4786
  codeSessionToolNames: this.codeSessionToolNames,
4790
4787
  interruptingToolNames: this.interruptingToolNames,
4791
4788
  toolExecution: this.toolExecution,
4792
- });
4793
- if (this.humanInTheLoop?.enabled === true) {
4794
- childGraph.compileOptions = {
4795
- checkpointer: this.compileOptions?.checkpointer,
4796
- };
4797
- }
4798
- childGraph.parentToolHandlerRegistry = childHandlerRegistry;
4799
- childGraph.eventToolExecutionAvailable =
4800
- childHandlerRegistry?.getHandler(GraphEvents.ON_TOOL_EXECUTE) !=
4801
- null;
4802
- return childGraph;
4789
+ };
4790
+ const checkpointer = this.compileOptions?.checkpointer;
4791
+ return (request): StandardGraph => {
4792
+ const childGraph = graphFactory(request);
4793
+ if (subagentModelOverride != null) {
4794
+ childGraph.overrideModel = subagentModelOverride;
4795
+ childGraph.setSubagentModelOverride(subagentModelOverride);
4796
+ }
4797
+ const childHandlerRegistry = createChildHandlerRegistry(
4798
+ parentHandlerRegistry
4799
+ );
4800
+ // Pure execution-ordering hint (unlike `humanInTheLoop`). It only
4801
+ // reorders tools already in the child's direct group; it does not
4802
+ // force a schema-only event tool onto the direct execution path.
4803
+ applyGraphRuntimeConfig(childGraph, runtimeConfig);
4804
+ if (runtimeConfig.humanInTheLoop?.enabled === true) {
4805
+ childGraph.compileOptions = { checkpointer };
4806
+ }
4807
+ childGraph.parentToolHandlerRegistry = childHandlerRegistry;
4808
+ childGraph.eventToolExecutionAvailable =
4809
+ childHandlerRegistry?.getHandler(GraphEvents.ON_TOOL_EXECUTE) !=
4810
+ null;
4811
+ return childGraph;
4812
+ };
4803
4813
  };
4814
+ const createConfiguredChildGraph: GraphFactory = (request) =>
4815
+ snapshotChildGraphFactory(getParentHandlerRegistry())(request);
4804
4816
  const executor = new SubagentExecutor({
4805
4817
  configs: new Map(
4806
4818
  executableConfigs.map((config) => [config.type, config])
@@ -4820,6 +4832,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
4820
4832
  langfuse: this.langfuse,
4821
4833
  tokenCounter: agentContext.tokenCounter,
4822
4834
  usageSink: this.subagentUsageSink,
4835
+ taskConfig: this.subagentTasks,
4823
4836
  streamLimits: this.streamLimits,
4824
4837
  humanInTheLoop: this.humanInTheLoop,
4825
4838
  checkpointer: this.compileOptions?.checkpointer,
@@ -4830,6 +4843,10 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
4830
4843
  input,
4831
4844
  }),
4832
4845
  createChildGraphByKind: createConfiguredChildGraph,
4846
+ createDetachedChildGraphFactory: (
4847
+ parentHandlerRegistry
4848
+ ): GraphFactory =>
4849
+ snapshotChildGraphFactory(parentHandlerRegistry),
4833
4850
  });
4834
4851
  this.registerSubagentExecutor(executor);
4835
4852
 
@@ -4837,6 +4854,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
4837
4854
  const input = rawInput as {
4838
4855
  description?: string;
4839
4856
  subagent_type?: string;
4857
+ run_in_background?: boolean;
4840
4858
  };
4841
4859
  const description =
4842
4860
  typeof input.description === 'string' &&
@@ -4869,7 +4887,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
4869
4887
  const batchScope = config.configurable?.[
4870
4888
  RUN_BREAKER_SCOPE_CONFIG_KEY
4871
4889
  ] as RunBreakerScope | undefined;
4872
- const result = await executor.execute({
4890
+ const executeParams = {
4873
4891
  description,
4874
4892
  subagentType,
4875
4893
  threadId,
@@ -4885,9 +4903,15 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
4885
4903
  parentConfigurable: config.configurable as
4886
4904
  | Record<string, unknown>
4887
4905
  | undefined,
4888
- });
4906
+ };
4907
+ if (input.run_in_background === true) {
4908
+ return executor.executeInBackground(executeParams);
4909
+ }
4910
+ const result = await executor.execute(executeParams);
4889
4911
  return result.content;
4890
- }, buildSubagentToolParams(executableConfigs));
4912
+ }, buildSubagentToolParams(executableConfigs, {
4913
+ background: this.subagentTasks != null,
4914
+ }));
4891
4915
  const replayableSubagentTool = subagentTool as typeof subagentTool &
4892
4916
  ReplayableSubagentTool;
4893
4917
  replayableSubagentTool[SUBAGENT_REPLAY_CONTROLLER] = {
@@ -6,6 +6,7 @@ import type {
6
6
  ToolApprovalReplaySnapshot,
7
7
  AggregatedHookResult,
8
8
  } from './types';
9
+ import { HOOK_EVENTS } from './types';
9
10
 
10
11
  function serializeApprovalKey(key: ToolApprovalReplayKey): string {
11
12
  return JSON.stringify([key.executionScope, key.agentId, key.toolUseId]);
@@ -227,6 +228,23 @@ export class HookRegistry {
227
228
  }
228
229
  }
229
230
 
231
+ /**
232
+ * Takes an isolated policy snapshot for work that may outlive the source
233
+ * run. Global and source-session matchers become global to the returned
234
+ * task-local registry, so parent cleanup and one-shot hook consumption
235
+ * cannot mutate the detached child (or vice versa). Runtime halt signals
236
+ * and pending approvals are intentionally not copied.
237
+ */
238
+ forkSession(sourceSessionId: string): HookRegistry {
239
+ const fork = new HookRegistry();
240
+ for (const event of HOOK_EVENTS) {
241
+ for (const matcher of this.getMatchers(event, sourceSessionId)) {
242
+ fork.register(event, matcher);
243
+ }
244
+ }
245
+ return fork;
246
+ }
247
+
230
248
  getPendingToolApproval(
231
249
  sessionId: string,
232
250
  key: ToolApprovalReplayKey
package/src/run.ts CHANGED
@@ -296,6 +296,7 @@ export class Run<_T extends t.BaseGraphState> {
296
296
  private subagentUsageSink?: t.SubagentUsageSink;
297
297
  private preemption?: t.StreamPreemption;
298
298
  private streamLimits?: t.StreamLimits;
299
+ private subagentTasks?: t.SubagentTaskConfig;
299
300
  private indexTokenCountMap?: Record<string, number>;
300
301
  calibrationRatio: number = 1;
301
302
  graphRunnable?: t.CompiledStateWorkflow;
@@ -363,6 +364,7 @@ export class Run<_T extends t.BaseGraphState> {
363
364
  this.interruptingToolNames = config.interruptingToolNames;
364
365
  this.toolExecution = config.toolExecution;
365
366
  this.subagentUsageSink = config.subagentUsageSink;
367
+ this.subagentTasks = config.subagentTasks;
366
368
  this.preemption = config.preemption;
367
369
  this.streamLimits = config.streamLimits;
368
370
 
@@ -455,6 +457,7 @@ export class Run<_T extends t.BaseGraphState> {
455
457
  indexTokenCountMap: this.indexTokenCountMap,
456
458
  calibrationRatio: this.calibrationRatio,
457
459
  subagentUsageSink: this.subagentUsageSink,
460
+ subagentTasks: this.subagentTasks,
458
461
  preemption: this.preemption,
459
462
  streamLimits: this.streamLimits,
460
463
  },
@@ -493,6 +496,7 @@ export class Run<_T extends t.BaseGraphState> {
493
496
  indexTokenCountMap: this.indexTokenCountMap,
494
497
  calibrationRatio: this.calibrationRatio,
495
498
  subagentUsageSink: this.subagentUsageSink,
499
+ subagentTasks: this.subagentTasks,
496
500
  preemption: this.preemption,
497
501
  streamLimits: this.streamLimits,
498
502
  },
@@ -27,6 +27,9 @@ const DESCRIPTION_PROP_DESCRIPTION =
27
27
  const SUBAGENT_TYPE_PROP_DESCRIPTION =
28
28
  'Which subagent type to delegate to. Must be one of the available types.';
29
29
 
30
+ const RUN_IN_BACKGROUND_PROP_DESCRIPTION =
31
+ 'Set true to start the subagent as a detached process-local task and return a background_task_id immediately. Poll the host background-task tool to collect its result. The task can outlive this turn but does not survive a process restart.';
32
+
30
33
  export const SubagentToolSchema = {
31
34
  type: 'object',
32
35
  properties: {
@@ -54,7 +57,10 @@ export const SubagentToolDefinition: LCTool = {
54
57
  * Used by `Graph.createAgentNode()` when constructing the runtime tool instance.
55
58
  * Extends `SubagentToolSchema` by populating `subagent_type.enum` dynamically.
56
59
  */
57
- export function buildSubagentToolParams(configs: SubagentConfig[]): {
60
+ export function buildSubagentToolParams(
61
+ configs: SubagentConfig[],
62
+ options: { background?: boolean } = {}
63
+ ): {
58
64
  name: string;
59
65
  schema: JsonSchemaType;
60
66
  description: string;
@@ -79,10 +85,22 @@ export function buildSubagentToolParams(configs: SubagentConfig[]): {
79
85
  enum: types,
80
86
  description: `${SUBAGENT_TYPE_PROP_DESCRIPTION} Available: ${types.join(', ')}.`,
81
87
  },
88
+ ...(options.background === true
89
+ ? {
90
+ run_in_background: {
91
+ type: 'boolean',
92
+ description: RUN_IN_BACKGROUND_PROP_DESCRIPTION,
93
+ },
94
+ }
95
+ : {}),
82
96
  },
83
97
  required: ['description', 'subagent_type'],
84
98
  },
85
- description: `${SubagentToolDescription}\n\nAvailable types:\n${typeDescriptions}`,
99
+ description: `${SubagentToolDescription}${
100
+ options.background === true
101
+ ? '\n\nBACKGROUND EXECUTION:\n- Set run_in_background to true when you do not need the result immediately. The call returns a background_task_id; use the host background-task tools to poll, steer, queue, interrupt, or cancel it.'
102
+ : ''
103
+ }\n\nAvailable types:\n${typeDescriptions}`,
86
104
  };
87
105
  }
88
106