@librechat/agents 3.6.2 → 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 (66) hide show
  1. package/dist/cjs/graphs/Graph.cjs +35 -20
  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/llm/openai/index.cjs +36 -0
  7. package/dist/cjs/llm/openai/index.cjs.map +1 -1
  8. package/dist/cjs/main.cjs +4 -1
  9. package/dist/cjs/messages/core.cjs +3 -0
  10. package/dist/cjs/messages/core.cjs.map +1 -1
  11. package/dist/cjs/run.cjs +11 -5
  12. package/dist/cjs/run.cjs.map +1 -1
  13. package/dist/cjs/tools/SubagentTool.cjs +8 -3
  14. package/dist/cjs/tools/SubagentTool.cjs.map +1 -1
  15. package/dist/cjs/tools/ToolNode.cjs +1 -1
  16. package/dist/cjs/tools/subagent/InMemorySubagentTaskStore.cjs +399 -0
  17. package/dist/cjs/tools/subagent/InMemorySubagentTaskStore.cjs.map +1 -0
  18. package/dist/cjs/tools/subagent/SubagentExecutor.cjs +168 -60
  19. package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
  20. package/dist/cjs/tools/subagent/index.cjs +1 -0
  21. package/dist/esm/graphs/Graph.mjs +35 -20
  22. package/dist/esm/graphs/Graph.mjs.map +1 -1
  23. package/dist/esm/hooks/HookRegistry.mjs +7 -1
  24. package/dist/esm/hooks/HookRegistry.mjs.map +1 -1
  25. package/dist/esm/hooks/index.mjs +1 -1
  26. package/dist/esm/llm/openai/index.mjs +37 -1
  27. package/dist/esm/llm/openai/index.mjs.map +1 -1
  28. package/dist/esm/main.mjs +4 -3
  29. package/dist/esm/messages/core.mjs +3 -1
  30. package/dist/esm/messages/core.mjs.map +1 -1
  31. package/dist/esm/run.mjs +11 -5
  32. package/dist/esm/run.mjs.map +1 -1
  33. package/dist/esm/tools/SubagentTool.mjs +8 -3
  34. package/dist/esm/tools/SubagentTool.mjs.map +1 -1
  35. package/dist/esm/tools/ToolNode.mjs +1 -1
  36. package/dist/esm/tools/subagent/InMemorySubagentTaskStore.mjs +399 -0
  37. package/dist/esm/tools/subagent/InMemorySubagentTaskStore.mjs.map +1 -0
  38. package/dist/esm/tools/subagent/SubagentExecutor.mjs +168 -60
  39. package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
  40. package/dist/esm/tools/subagent/index.mjs +1 -0
  41. package/dist/types/graphs/Graph.d.ts +6 -3
  42. package/dist/types/hooks/HookRegistry.d.ts +8 -0
  43. package/dist/types/messages/core.d.ts +2 -0
  44. package/dist/types/run.d.ts +1 -0
  45. package/dist/types/tools/SubagentTool.d.ts +3 -1
  46. package/dist/types/tools/subagent/InMemorySubagentTaskStore.d.ts +45 -0
  47. package/dist/types/tools/subagent/SubagentExecutor.d.ts +26 -3
  48. package/dist/types/tools/subagent/index.d.ts +2 -0
  49. package/dist/types/types/graph.d.ts +11 -4
  50. package/dist/types/types/index.d.ts +1 -0
  51. package/dist/types/types/run.d.ts +6 -0
  52. package/dist/types/types/subagentTasks.d.ts +140 -0
  53. package/package.json +4 -4
  54. package/src/graphs/Graph.ts +84 -36
  55. package/src/hooks/HookRegistry.ts +18 -0
  56. package/src/llm/openai/index.ts +96 -0
  57. package/src/messages/core.ts +5 -0
  58. package/src/run.ts +20 -5
  59. package/src/tools/SubagentTool.ts +20 -2
  60. package/src/tools/subagent/InMemorySubagentTaskStore.ts +624 -0
  61. package/src/tools/subagent/SubagentExecutor.ts +341 -74
  62. package/src/tools/subagent/index.ts +2 -0
  63. package/src/types/graph.ts +11 -4
  64. package/src/types/index.ts +1 -0
  65. package/src/types/run.ts +6 -0
  66. 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.2",
3
+ "version": "3.6.4",
4
4
  "reova": {
5
5
  "enabled": true,
6
6
  "endpoint": "https://telemetry.reo.dev/data"
@@ -218,7 +218,7 @@
218
218
  "label:rescore": "node ./src/scripts/activity-labels/rescore.cjs"
219
219
  },
220
220
  "overrides": {
221
- "@langchain/openai": "1.5.5",
221
+ "@langchain/openai": "$@langchain/openai",
222
222
  "@browserbasehq/stagehand": {
223
223
  "openai": "$openai"
224
224
  },
@@ -234,7 +234,7 @@
234
234
  "@aws-sdk/client-bedrock-runtime": "^3.1075.0",
235
235
  "@langchain/anthropic": "1.5.2",
236
236
  "@langchain/aws": "^1.4.2",
237
- "@langchain/core": "^1.2.3",
237
+ "@langchain/core": "1.2.8",
238
238
  "@langchain/deepseek": "^1.1.3",
239
239
  "@langchain/google-common": "2.2.0",
240
240
  "@langchain/google-gauth": "2.2.0",
@@ -242,7 +242,7 @@
242
242
  "@langchain/google-vertexai": "2.2.0",
243
243
  "@langchain/langgraph": "1.4.8",
244
244
  "@langchain/mistralai": "^1.2.0",
245
- "@langchain/openai": "1.5.5",
245
+ "@langchain/openai": "1.5.8",
246
246
  "@langchain/textsplitters": "^1.0.1",
247
247
  "@langchain/xai": "^1.4.3",
248
248
  "@langfuse/core": "^5.4.1",
@@ -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) &&
@@ -2024,12 +2028,36 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
2024
2028
  GraphEvents.ON_RUN_STEP_CLOSED
2025
2029
  );
2026
2030
  if (handler) {
2027
- await handler.handle(
2028
- GraphEvents.ON_RUN_STEP_CLOSED,
2029
- closedEvent,
2030
- options?.metadata,
2031
- this
2032
- );
2031
+ /**
2032
+ * Isolated, unlike the other dual-dispatch sites, because this one
2033
+ * reports state that is already committed: the step was stamped
2034
+ * terminal and untracked above, and nothing a failed delivery can do
2035
+ * will undo that. Propagating instead costs two things.
2036
+ *
2037
+ * First, it fails an entire run over an observational event —
2038
+ * `closeOpenMessageStep` awaits this inside the stream loop on every
2039
+ * CHAT_MODEL_END, where a rejection sets `streamThrew` and fires the
2040
+ * StopFailure hooks for a response that was fully delivered.
2041
+ *
2042
+ * Second, and worse, it skips the secondary custom-event dispatch
2043
+ * below. That channel exists precisely as the fallback for when the
2044
+ * primary path does not deliver, so letting the primary's failure
2045
+ * suppress it removes the redundancy exactly when it is needed.
2046
+ *
2047
+ * `closeUnfinishedRunSteps` and `dispatchRunStep` already wrap their
2048
+ * own calls for the same reason; this closes the gap inside, which
2049
+ * those wrappers cannot reach.
2050
+ */
2051
+ try {
2052
+ await handler.handle(
2053
+ GraphEvents.ON_RUN_STEP_CLOSED,
2054
+ closedEvent,
2055
+ options?.metadata,
2056
+ this
2057
+ );
2058
+ } catch (_e) {
2059
+ /** Host delivery failure must not fail the run or block the echo */
2060
+ }
2033
2061
  this.handlerDispatchedStepIds.add(stepId);
2034
2062
  }
2035
2063
  const unmarkHandlerDispatchedEvent = handler
@@ -4745,19 +4773,12 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
4745
4773
  }
4746
4774
  const getParentHandlerRegistry = (): HandlerRegistry | undefined =>
4747
4775
  this.handlerRegistry ?? this.parentToolHandlerRegistry;
4748
- const createConfiguredChildGraph: GraphFactory = (request) => {
4749
- const childGraph = this.graphFactory(request);
4750
- if (this.subagentModelOverride != null) {
4751
- childGraph.overrideModel = this.subagentModelOverride;
4752
- childGraph.setSubagentModelOverride(this.subagentModelOverride);
4753
- }
4754
- const childHandlerRegistry = createChildHandlerRegistry(
4755
- getParentHandlerRegistry()
4756
- );
4757
- // Pure execution-ordering hint (unlike `humanInTheLoop`). It only
4758
- // reorders tools already in the child's direct group; it does not
4759
- // force a schema-only event tool onto the direct execution path.
4760
- applyGraphRuntimeConfig(childGraph, {
4776
+ const snapshotChildGraphFactory = (
4777
+ parentHandlerRegistry: HandlerRegistry | undefined
4778
+ ): GraphFactory => {
4779
+ const graphFactory = this.graphFactory;
4780
+ const subagentModelOverride = this.subagentModelOverride;
4781
+ const runtimeConfig = {
4761
4782
  hookRegistry: this.hookRegistry,
4762
4783
  humanInTheLoop: this.humanInTheLoop,
4763
4784
  toolOutputReferences: this.toolOutputReferences,
@@ -4765,18 +4786,33 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
4765
4786
  codeSessionToolNames: this.codeSessionToolNames,
4766
4787
  interruptingToolNames: this.interruptingToolNames,
4767
4788
  toolExecution: this.toolExecution,
4768
- });
4769
- if (this.humanInTheLoop?.enabled === true) {
4770
- childGraph.compileOptions = {
4771
- checkpointer: this.compileOptions?.checkpointer,
4772
- };
4773
- }
4774
- childGraph.parentToolHandlerRegistry = childHandlerRegistry;
4775
- childGraph.eventToolExecutionAvailable =
4776
- childHandlerRegistry?.getHandler(GraphEvents.ON_TOOL_EXECUTE) !=
4777
- null;
4778
- 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
+ };
4779
4813
  };
4814
+ const createConfiguredChildGraph: GraphFactory = (request) =>
4815
+ snapshotChildGraphFactory(getParentHandlerRegistry())(request);
4780
4816
  const executor = new SubagentExecutor({
4781
4817
  configs: new Map(
4782
4818
  executableConfigs.map((config) => [config.type, config])
@@ -4796,6 +4832,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
4796
4832
  langfuse: this.langfuse,
4797
4833
  tokenCounter: agentContext.tokenCounter,
4798
4834
  usageSink: this.subagentUsageSink,
4835
+ taskConfig: this.subagentTasks,
4799
4836
  streamLimits: this.streamLimits,
4800
4837
  humanInTheLoop: this.humanInTheLoop,
4801
4838
  checkpointer: this.compileOptions?.checkpointer,
@@ -4806,6 +4843,10 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
4806
4843
  input,
4807
4844
  }),
4808
4845
  createChildGraphByKind: createConfiguredChildGraph,
4846
+ createDetachedChildGraphFactory: (
4847
+ parentHandlerRegistry
4848
+ ): GraphFactory =>
4849
+ snapshotChildGraphFactory(parentHandlerRegistry),
4809
4850
  });
4810
4851
  this.registerSubagentExecutor(executor);
4811
4852
 
@@ -4813,6 +4854,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
4813
4854
  const input = rawInput as {
4814
4855
  description?: string;
4815
4856
  subagent_type?: string;
4857
+ run_in_background?: boolean;
4816
4858
  };
4817
4859
  const description =
4818
4860
  typeof input.description === 'string' &&
@@ -4845,7 +4887,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
4845
4887
  const batchScope = config.configurable?.[
4846
4888
  RUN_BREAKER_SCOPE_CONFIG_KEY
4847
4889
  ] as RunBreakerScope | undefined;
4848
- const result = await executor.execute({
4890
+ const executeParams = {
4849
4891
  description,
4850
4892
  subagentType,
4851
4893
  threadId,
@@ -4861,9 +4903,15 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
4861
4903
  parentConfigurable: config.configurable as
4862
4904
  | Record<string, unknown>
4863
4905
  | undefined,
4864
- });
4906
+ };
4907
+ if (input.run_in_background === true) {
4908
+ return executor.executeInBackground(executeParams);
4909
+ }
4910
+ const result = await executor.execute(executeParams);
4865
4911
  return result.content;
4866
- }, buildSubagentToolParams(executableConfigs));
4912
+ }, buildSubagentToolParams(executableConfigs, {
4913
+ background: this.subagentTasks != null,
4914
+ }));
4867
4915
  const replayableSubagentTool = subagentTool as typeof subagentTool &
4868
4916
  ReplayableSubagentTool;
4869
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