@librechat/agents 3.4.1 → 3.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/dist/cjs/agents/AgentContext.cjs.map +1 -1
  2. package/dist/cjs/graphs/Graph.cjs +57 -33
  3. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  4. package/dist/cjs/graphs/MultiAgentGraph.cjs +81 -33
  5. package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
  6. package/dist/cjs/graphs/applyGraphRuntimeConfig.cjs +14 -0
  7. package/dist/cjs/graphs/applyGraphRuntimeConfig.cjs.map +1 -0
  8. package/dist/cjs/graphs/createGraph.cjs +15 -0
  9. package/dist/cjs/graphs/createGraph.cjs.map +1 -0
  10. package/dist/cjs/graphs/index.cjs +1 -0
  11. package/dist/cjs/hooks/createWorkspacePolicyHook.cjs +1 -1
  12. package/dist/cjs/main.cjs +20 -11
  13. package/dist/cjs/run.cjs +48 -38
  14. package/dist/cjs/run.cjs.map +1 -1
  15. package/dist/cjs/session/JsonlSessionStore.cjs +2 -2
  16. package/dist/cjs/tools/SubagentTool.cjs +4 -4
  17. package/dist/cjs/tools/SubagentTool.cjs.map +1 -1
  18. package/dist/cjs/tools/ToolNode.cjs +13 -9
  19. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  20. package/dist/cjs/tools/eagerEventExecution.cjs +1 -0
  21. package/dist/cjs/tools/local/LocalExecutionEngine.cjs +3 -3
  22. package/dist/cjs/tools/local/LocalProgrammaticToolCalling.cjs +2 -2
  23. package/dist/cjs/tools/search/rerankers.cjs +154 -1
  24. package/dist/cjs/tools/search/rerankers.cjs.map +1 -1
  25. package/dist/cjs/tools/search/tool.cjs +4 -1
  26. package/dist/cjs/tools/search/tool.cjs.map +1 -1
  27. package/dist/cjs/tools/subagent/SubagentExecutionRegistry.cjs +601 -0
  28. package/dist/cjs/tools/subagent/SubagentExecutionRegistry.cjs.map +1 -0
  29. package/dist/cjs/tools/subagent/SubagentExecutor.cjs +672 -343
  30. package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
  31. package/dist/cjs/tools/subagent/SubagentReplay.cjs +3 -1
  32. package/dist/cjs/tools/subagent/SubagentReplay.cjs.map +1 -1
  33. package/dist/cjs/tools/subagent/childGraphConfig.cjs +278 -0
  34. package/dist/cjs/tools/subagent/childGraphConfig.cjs.map +1 -0
  35. package/dist/cjs/tools/subagent/index.cjs +1 -0
  36. package/dist/cjs/tools/subagent/runtimeLimits.cjs +5 -0
  37. package/dist/cjs/tools/subagent/runtimeLimits.cjs.map +1 -0
  38. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  39. package/dist/esm/graphs/Graph.mjs +57 -33
  40. package/dist/esm/graphs/Graph.mjs.map +1 -1
  41. package/dist/esm/graphs/MultiAgentGraph.mjs +81 -33
  42. package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
  43. package/dist/esm/graphs/applyGraphRuntimeConfig.mjs +14 -0
  44. package/dist/esm/graphs/applyGraphRuntimeConfig.mjs.map +1 -0
  45. package/dist/esm/graphs/createGraph.mjs +15 -0
  46. package/dist/esm/graphs/createGraph.mjs.map +1 -0
  47. package/dist/esm/graphs/index.mjs +1 -0
  48. package/dist/esm/hooks/createWorkspacePolicyHook.mjs +1 -1
  49. package/dist/esm/main.mjs +12 -10
  50. package/dist/esm/run.mjs +48 -38
  51. package/dist/esm/run.mjs.map +1 -1
  52. package/dist/esm/session/JsonlSessionStore.mjs +2 -2
  53. package/dist/esm/tools/SubagentTool.mjs +4 -4
  54. package/dist/esm/tools/SubagentTool.mjs.map +1 -1
  55. package/dist/esm/tools/ToolNode.mjs +13 -9
  56. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  57. package/dist/esm/tools/eagerEventExecution.mjs +1 -1
  58. package/dist/esm/tools/local/LocalExecutionEngine.mjs +3 -3
  59. package/dist/esm/tools/local/LocalProgrammaticToolCalling.mjs +2 -2
  60. package/dist/esm/tools/search/rerankers.mjs +154 -1
  61. package/dist/esm/tools/search/rerankers.mjs.map +1 -1
  62. package/dist/esm/tools/search/tool.mjs +4 -1
  63. package/dist/esm/tools/search/tool.mjs.map +1 -1
  64. package/dist/esm/tools/subagent/SubagentExecutionRegistry.mjs +597 -0
  65. package/dist/esm/tools/subagent/SubagentExecutionRegistry.mjs.map +1 -0
  66. package/dist/esm/tools/subagent/SubagentExecutor.mjs +672 -343
  67. package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
  68. package/dist/esm/tools/subagent/SubagentReplay.mjs +3 -1
  69. package/dist/esm/tools/subagent/SubagentReplay.mjs.map +1 -1
  70. package/dist/esm/tools/subagent/childGraphConfig.mjs +272 -0
  71. package/dist/esm/tools/subagent/childGraphConfig.mjs.map +1 -0
  72. package/dist/esm/tools/subagent/index.mjs +1 -0
  73. package/dist/esm/tools/subagent/runtimeLimits.mjs +5 -0
  74. package/dist/esm/tools/subagent/runtimeLimits.mjs.map +1 -0
  75. package/dist/types/agents/AgentContext.d.ts +1 -1
  76. package/dist/types/graphs/Graph.d.ts +7 -2
  77. package/dist/types/graphs/MultiAgentGraph.d.ts +7 -1
  78. package/dist/types/graphs/applyGraphRuntimeConfig.d.ts +3 -0
  79. package/dist/types/graphs/createGraph.d.ts +13 -0
  80. package/dist/types/graphs/graphFactory.d.ts +13 -0
  81. package/dist/types/graphs/index.d.ts +2 -0
  82. package/dist/types/run.d.ts +2 -2
  83. package/dist/types/tools/SubagentTool.d.ts +1 -1
  84. package/dist/types/tools/search/rerankers.d.ts +28 -0
  85. package/dist/types/tools/search/types.d.ts +38 -1
  86. package/dist/types/tools/subagent/SubagentExecutionRegistry.d.ts +171 -0
  87. package/dist/types/tools/subagent/SubagentExecutor.d.ts +32 -37
  88. package/dist/types/tools/subagent/SubagentReplay.d.ts +5 -1
  89. package/dist/types/tools/subagent/childGraphConfig.d.ts +37 -0
  90. package/dist/types/tools/subagent/index.d.ts +1 -1
  91. package/dist/types/tools/subagent/runtimeLimits.d.ts +4 -0
  92. package/dist/types/types/graph.d.ts +184 -19
  93. package/package.json +3 -1
  94. package/src/agents/AgentContext.ts +1 -1
  95. package/src/graphs/Graph.ts +111 -60
  96. package/src/graphs/MultiAgentGraph.ts +149 -47
  97. package/src/graphs/__tests__/MultiAgentGraph.test.ts +86 -0
  98. package/src/graphs/__tests__/composition.smoke.test.ts +152 -0
  99. package/src/graphs/__tests__/createGraph.test.ts +206 -0
  100. package/src/graphs/applyGraphRuntimeConfig.ts +25 -0
  101. package/src/graphs/createGraph.ts +32 -0
  102. package/src/graphs/graphFactory.ts +12 -0
  103. package/src/graphs/index.ts +2 -0
  104. package/src/run.ts +50 -38
  105. package/src/specs/graph-subagent.live.test.ts +242 -0
  106. package/src/specs/graph-subagent.test.ts +322 -0
  107. package/src/specs/langfuse-routing.integration.test.ts +120 -1
  108. package/src/specs/subagent.test.ts +130 -4
  109. package/src/tools/SubagentTool.ts +4 -4
  110. package/src/tools/ToolNode.ts +22 -10
  111. package/src/tools/__tests__/SubagentExecutor.test.ts +198 -9
  112. package/src/tools/__tests__/SubagentReplay.test.ts +29 -0
  113. package/src/tools/__tests__/directToolHITLResumeScope.test.ts +28 -2
  114. package/src/tools/__tests__/subagentHooks.test.ts +90 -3
  115. package/src/tools/search/rag-api-reranker.test.ts +891 -0
  116. package/src/tools/search/rerankers.ts +260 -0
  117. package/src/tools/search/tool.ts +6 -0
  118. package/src/tools/search/types.ts +45 -1
  119. package/src/tools/subagent/SubagentExecutionRegistry.ts +1159 -0
  120. package/src/tools/subagent/SubagentExecutor.ts +1245 -563
  121. package/src/tools/subagent/SubagentReplay.ts +10 -1
  122. package/src/tools/subagent/__tests__/SubagentExecutionRegistry.test.ts +637 -0
  123. package/src/tools/subagent/__tests__/SubagentExecutor.lazy.test.ts +3054 -0
  124. package/src/tools/subagent/__tests__/childGraphConfig.test.ts +521 -0
  125. package/src/tools/subagent/childGraphConfig.ts +694 -0
  126. package/src/tools/subagent/index.ts +6 -0
  127. package/src/tools/subagent/runtimeLimits.ts +7 -0
  128. package/src/types/graph.ts +217 -19
@@ -0,0 +1,1159 @@
1
+ import type { RunnableConfig } from '@langchain/core/runnables';
2
+ import type { SubagentResumeExecution } from './SubagentReplay';
3
+ import {
4
+ getSubagentResumeManifest,
5
+ SUBAGENT_PARENT_BATCH_CONFIG_KEY,
6
+ SUBAGENT_RESUME_ATTEMPT_CONFIG_KEY,
7
+ } from './SubagentReplay';
8
+
9
+ const SUBAGENT_THREAD_ID_PREFIX = 'subagent:';
10
+ const SUBAGENT_EXECUTION_INVALIDATED_MESSAGE =
11
+ 'Subagent execution was invalidated.';
12
+
13
+ export type SubagentExecutionAddressInput = {
14
+ threadId?: string;
15
+ parentToolCallId: string;
16
+ parentConfigurable?: Record<string, unknown>;
17
+ };
18
+
19
+ export type SubagentExecutionAddress = Readonly<{
20
+ key: string;
21
+ baseChildThreadId: string;
22
+ branchChildThreadId: string;
23
+ currentChildRunId: string;
24
+ explicitResumeAttempt: boolean;
25
+ parentToolCallId: string;
26
+ resumeAttemptId: string;
27
+ }>;
28
+
29
+ export type SubagentExecutionIdentity = Readonly<{
30
+ childRunId: string;
31
+ childThreadId: string;
32
+ approvalExecutionScope: string;
33
+ }>;
34
+
35
+ export interface SubagentExecutionPreparationLease {
36
+ readonly ownsCheckpoint: boolean;
37
+ readonly ownsCreatedCheckpoint: boolean;
38
+ readonly ownsApprovalScope: boolean;
39
+ readonly ownsCreatedApprovalScope: boolean;
40
+ assertOwned(): void;
41
+ markCheckpointCreated(cleanup: () => Promise<void>): void;
42
+ markApprovalScopeCreated(cleanup: () => Promise<void>): void;
43
+ release(): void;
44
+ rollback(
45
+ cause: unknown,
46
+ cleanup?: (lease: SubagentExecutionPreparationLease) => Promise<void>
47
+ ): Promise<never>;
48
+ }
49
+
50
+ export type PreparedSubagentExecutionIdentity = Readonly<{
51
+ identity: SubagentExecutionIdentity;
52
+ lease: SubagentExecutionPreparationLease;
53
+ commit?: (lease: SubagentExecutionPreparationLease) => void;
54
+ rollback?: (lease: SubagentExecutionPreparationLease) => Promise<void>;
55
+ }>;
56
+
57
+ export type SubagentDefinitionBinding = Readonly<{
58
+ subagentType?: string;
59
+ configId?: string;
60
+ }>;
61
+
62
+ export type SubagentInvocationBinding = Readonly<{
63
+ description: string;
64
+ subagentType: string;
65
+ configId?: string;
66
+ }>;
67
+
68
+ export type SubagentSettlementBinding = Readonly<{
69
+ definitionAuthority: 'provisional' | 'effective';
70
+ fingerprint: string;
71
+ invocation: SubagentInvocationBinding;
72
+ subagentType?: string;
73
+ configId?: string;
74
+ }>;
75
+
76
+ export type SubagentExecutionPhase =
77
+ | 'registered'
78
+ | 'active'
79
+ | 'interrupted'
80
+ | 'failed'
81
+ | 'completed'
82
+ | 'invalidated';
83
+
84
+ export interface ExecutionSnapshot<TResult, TResolvedConfig, TActiveRun> {
85
+ readonly address: SubagentExecutionAddress;
86
+ readonly phase: SubagentExecutionPhase;
87
+ readonly identity?: SubagentExecutionIdentity;
88
+ readonly binding?: SubagentDefinitionBinding;
89
+ readonly invocation?: SubagentInvocationBinding;
90
+ readonly settlement?: SubagentSettlementBinding;
91
+ readonly resumeExecution?: SubagentResumeExecution;
92
+ readonly activeRun?: TActiveRun;
93
+ readonly completedResult?: TResult;
94
+ readonly resolvedConfig?: TResolvedConfig;
95
+ readonly started: boolean;
96
+ readonly completed: boolean;
97
+ readonly resolvingIdentity: boolean;
98
+ readonly resolving: boolean;
99
+ readonly executing: boolean;
100
+ readonly settling: boolean;
101
+ readonly settled: boolean;
102
+ }
103
+
104
+ export type SubagentExecutionRegistryOptions = {
105
+ parentRunId: string;
106
+ parentAgentId?: string;
107
+ durable: boolean;
108
+ };
109
+
110
+ type ResumeSelection = {
111
+ parentToolCallIds?: ReadonlySet<string>;
112
+ config?: RunnableConfig;
113
+ };
114
+
115
+ type OwnedPreparationResource = {
116
+ status: 'owned';
117
+ token: number;
118
+ created: boolean;
119
+ cleanup?: () => Promise<void>;
120
+ };
121
+
122
+ type CleaningPreparationResource = {
123
+ status: 'cleaning';
124
+ token: number;
125
+ };
126
+
127
+ type PreparationResourceState =
128
+ | OwnedPreparationResource
129
+ | CleaningPreparationResource;
130
+
131
+ function getPreparationResourceKey(
132
+ kind: 'checkpoint' | 'approval',
133
+ identity: string
134
+ ): string {
135
+ return JSON.stringify([kind, identity]);
136
+ }
137
+
138
+ function attachRollbackError(cause: unknown, rollbackError: unknown): void {
139
+ if (!(cause instanceof Error)) {
140
+ return;
141
+ }
142
+ try {
143
+ Object.defineProperty(cause, 'rollbackError', {
144
+ configurable: true,
145
+ value: rollbackError,
146
+ });
147
+ } catch {
148
+ return;
149
+ }
150
+ }
151
+
152
+ class PreparationLease implements SubagentExecutionPreparationLease {
153
+ constructor(
154
+ private readonly owners: Map<string, PreparationResourceState>,
155
+ private readonly token: number,
156
+ private readonly checkpointKey: string,
157
+ private readonly approvalKey: string
158
+ ) {}
159
+
160
+ get ownsCheckpoint(): boolean {
161
+ return this.owns(this.checkpointKey);
162
+ }
163
+
164
+ get ownsCreatedCheckpoint(): boolean {
165
+ return this.isCreated(this.checkpointKey);
166
+ }
167
+
168
+ get ownsApprovalScope(): boolean {
169
+ return this.owns(this.approvalKey);
170
+ }
171
+
172
+ get ownsCreatedApprovalScope(): boolean {
173
+ return this.isCreated(this.approvalKey);
174
+ }
175
+
176
+ assertOwned(): void {
177
+ if (!this.owns(this.checkpointKey) || !this.owns(this.approvalKey)) {
178
+ throw new SubagentExecutionInvalidatedError();
179
+ }
180
+ }
181
+
182
+ markCheckpointCreated(cleanup: () => Promise<void>): void {
183
+ this.markCreated(this.checkpointKey, cleanup);
184
+ }
185
+
186
+ markApprovalScopeCreated(cleanup: () => Promise<void>): void {
187
+ this.markCreated(this.approvalKey, cleanup);
188
+ }
189
+
190
+ release(): void {
191
+ this.releaseResource(this.checkpointKey);
192
+ this.releaseResource(this.approvalKey);
193
+ }
194
+
195
+ async rollback(
196
+ cause: unknown,
197
+ cleanup?: (lease: SubagentExecutionPreparationLease) => Promise<void>
198
+ ): Promise<never> {
199
+ let rollbackError: unknown;
200
+ let rollbackFailed = false;
201
+ try {
202
+ await cleanup?.(this);
203
+ } catch (error) {
204
+ rollbackError = error;
205
+ rollbackFailed = true;
206
+ }
207
+ const resourceRollback = await this.rollbackOwnedResources();
208
+ if (!rollbackFailed && resourceRollback.failed) {
209
+ rollbackError = resourceRollback.error;
210
+ rollbackFailed = true;
211
+ }
212
+ try {
213
+ if (rollbackFailed) {
214
+ attachRollbackError(cause, rollbackError);
215
+ }
216
+ } finally {
217
+ this.release();
218
+ }
219
+ throw cause;
220
+ }
221
+
222
+ private owns(key: string): boolean {
223
+ const owner = this.owners.get(key);
224
+ return owner?.status === 'owned' && owner.token === this.token;
225
+ }
226
+
227
+ private isCreated(key: string): boolean {
228
+ const owner = this.owners.get(key);
229
+ return (
230
+ owner?.status === 'owned' && owner.token === this.token && owner.created
231
+ );
232
+ }
233
+
234
+ private markCreated(key: string, cleanup: () => Promise<void>): void {
235
+ const owner = this.owners.get(key);
236
+ if (owner?.status !== 'owned' || owner.token !== this.token) {
237
+ throw new SubagentExecutionInvalidatedError();
238
+ }
239
+ owner.created = true;
240
+ owner.cleanup = cleanup;
241
+ }
242
+
243
+ private async rollbackOwnedResources(): Promise<{
244
+ failed: boolean;
245
+ error?: unknown;
246
+ }> {
247
+ const cleanups: Array<{
248
+ key: string;
249
+ state: CleaningPreparationResource;
250
+ cleanup: () => Promise<void>;
251
+ }> = [];
252
+ for (const key of [this.checkpointKey, this.approvalKey]) {
253
+ const owner = this.owners.get(key);
254
+ if (owner?.status !== 'owned' || owner.token !== this.token) {
255
+ continue;
256
+ }
257
+ if (!owner.created || owner.cleanup == null) {
258
+ this.owners.delete(key);
259
+ continue;
260
+ }
261
+ const state: CleaningPreparationResource = {
262
+ status: 'cleaning',
263
+ token: this.token,
264
+ };
265
+ this.owners.set(key, state);
266
+ cleanups.push({ key, state, cleanup: owner.cleanup });
267
+ }
268
+ const results = await Promise.all(
269
+ cleanups.map(({ key, state, cleanup }) =>
270
+ this.cleanResource(key, state, cleanup)
271
+ )
272
+ );
273
+ return results.find((result) => result.failed) ?? { failed: false };
274
+ }
275
+
276
+ private async cleanResource(
277
+ key: string,
278
+ state: CleaningPreparationResource,
279
+ cleanup: () => Promise<void>
280
+ ): Promise<{ failed: boolean; error?: unknown }> {
281
+ try {
282
+ await cleanup();
283
+ return { failed: false };
284
+ } catch (error) {
285
+ return { failed: true, error };
286
+ } finally {
287
+ if (this.owners.get(key) === state) {
288
+ this.owners.delete(key);
289
+ }
290
+ }
291
+ }
292
+
293
+ private releaseResource(key: string): void {
294
+ const owner = this.owners.get(key);
295
+ if (owner?.status === 'owned' && owner.token === this.token) {
296
+ this.owners.delete(key);
297
+ }
298
+ }
299
+ }
300
+
301
+ function getConfigurable(
302
+ config?: RunnableConfig
303
+ ): Record<string, unknown> | undefined {
304
+ return config?.configurable as Record<string, unknown> | undefined;
305
+ }
306
+
307
+ function getConfigurableString(
308
+ configurable: Record<string, unknown> | undefined,
309
+ key: string
310
+ ): string | undefined {
311
+ const value = configurable?.[key];
312
+ return typeof value === 'string' && value.length > 0 ? value : undefined;
313
+ }
314
+
315
+ function encodeThreadIdentity(identity: string[]): string {
316
+ return `${SUBAGENT_THREAD_ID_PREFIX}${Buffer.from(
317
+ JSON.stringify(identity)
318
+ ).toString('base64url')}`;
319
+ }
320
+
321
+ function assertSameIdentity(
322
+ current: SubagentExecutionIdentity,
323
+ next: SubagentExecutionIdentity
324
+ ): void {
325
+ if (
326
+ current.childRunId === next.childRunId &&
327
+ current.childThreadId === next.childThreadId &&
328
+ current.approvalExecutionScope === next.approvalExecutionScope
329
+ ) {
330
+ return;
331
+ }
332
+ throw new Error('Subagent execution identity cannot change after binding.');
333
+ }
334
+
335
+ function assertCompatibleResumeExecution(
336
+ current: SubagentResumeExecution,
337
+ next: SubagentResumeExecution
338
+ ): void {
339
+ if (
340
+ current.parentToolCallId === next.parentToolCallId &&
341
+ current.childRunId === next.childRunId &&
342
+ current.subagentType === next.subagentType &&
343
+ current.configId === next.configId
344
+ ) {
345
+ return;
346
+ }
347
+ throw new Error('Subagent resume source cannot change after binding.');
348
+ }
349
+
350
+ function assertSameInvocation(
351
+ current: SubagentInvocationBinding,
352
+ next: SubagentInvocationBinding
353
+ ): void {
354
+ if (
355
+ current.description === next.description &&
356
+ current.subagentType === next.subagentType &&
357
+ current.configId === next.configId
358
+ ) {
359
+ return;
360
+ }
361
+ throw new SubagentInvocationBindingError();
362
+ }
363
+
364
+ function assertSameSettlement(
365
+ current: SubagentSettlementBinding,
366
+ next: SubagentSettlementBinding
367
+ ): void {
368
+ if (
369
+ current.definitionAuthority === next.definitionAuthority &&
370
+ current.fingerprint === next.fingerprint &&
371
+ current.subagentType === next.subagentType &&
372
+ current.configId === next.configId
373
+ ) {
374
+ return;
375
+ }
376
+ throw new SubagentSettlementBindingError();
377
+ }
378
+
379
+ function getAbortReason(signal: AbortSignal): unknown {
380
+ return signal.reason ?? new Error('Subagent execution aborted.');
381
+ }
382
+
383
+ export function getSubagentApprovalExecutionScope(
384
+ childRunId: string,
385
+ resumeAttemptId: string
386
+ ): string {
387
+ return `subagent-approval:${Buffer.from(
388
+ JSON.stringify([childRunId, resumeAttemptId])
389
+ ).toString('base64url')}`;
390
+ }
391
+
392
+ export class SubagentExecutionInvalidatedError extends Error {
393
+ constructor() {
394
+ super(SUBAGENT_EXECUTION_INVALIDATED_MESSAGE);
395
+ this.name = 'SubagentExecutionInvalidatedError';
396
+ }
397
+ }
398
+
399
+ export class SubagentDefinitionBindingError extends Error {
400
+ constructor() {
401
+ super('Subagent effective definition binding cannot change.');
402
+ this.name = 'SubagentDefinitionBindingError';
403
+ }
404
+ }
405
+
406
+ export class SubagentInvocationBindingError extends Error {
407
+ constructor() {
408
+ super('Subagent invocation binding cannot change.');
409
+ this.name = 'SubagentInvocationBindingError';
410
+ }
411
+ }
412
+
413
+ export class SubagentSettlementBindingError extends Error {
414
+ constructor() {
415
+ super('Subagent settlement binding cannot change.');
416
+ this.name = 'SubagentSettlementBindingError';
417
+ }
418
+ }
419
+
420
+ /** In-process source of truth for one Durable Subagent Execution. */
421
+ export class SubagentExecutionRecord<
422
+ TResult,
423
+ TResolvedConfig,
424
+ TActiveRun,
425
+ TSettledOutput = never,
426
+ > {
427
+ private identityValue?: SubagentExecutionIdentity;
428
+ private bindingValue?: SubagentDefinitionBinding;
429
+ private bindingAuthority?: 'provisional' | 'effective';
430
+ private invocationValue?: SubagentInvocationBinding;
431
+ private settlementValue?: SubagentSettlementBinding;
432
+ private settledOutputValue?: TSettledOutput;
433
+ private resumeExecutionValue?: SubagentResumeExecution;
434
+ private activeRunValue?: TActiveRun;
435
+ private completedResultValue?: TResult;
436
+ private resolvedConfigValue?: TResolvedConfig;
437
+ private pendingIdentityResolution?: Promise<SubagentExecutionIdentity>;
438
+ private pendingConfigResolution?: Promise<TResolvedConfig>;
439
+ private pendingExecution?: Promise<TResult>;
440
+ private pendingSettlement?: Promise<void>;
441
+ private phaseValue: SubagentExecutionPhase = 'registered';
442
+ private startedValue = false;
443
+ private completedValue = false;
444
+ private settledValue = false;
445
+
446
+ constructor(
447
+ readonly address: SubagentExecutionAddress,
448
+ resumeExecution?: SubagentResumeExecution
449
+ ) {
450
+ this.resumeExecutionValue = resumeExecution;
451
+ }
452
+
453
+ get snapshot(): ExecutionSnapshot<TResult, TResolvedConfig, TActiveRun> {
454
+ return {
455
+ address: this.address,
456
+ phase: this.phaseValue,
457
+ ...(this.identityValue == null ? {} : { identity: this.identityValue }),
458
+ ...(this.bindingValue == null ? {} : { binding: this.bindingValue }),
459
+ ...(this.invocationValue == null
460
+ ? {}
461
+ : { invocation: this.invocationValue }),
462
+ ...(this.settlementValue == null
463
+ ? {}
464
+ : { settlement: this.settlementValue }),
465
+ ...(this.resumeExecutionValue == null
466
+ ? {}
467
+ : { resumeExecution: this.resumeExecutionValue }),
468
+ ...(this.activeRunValue == null
469
+ ? {}
470
+ : { activeRun: this.activeRunValue }),
471
+ ...(this.completedResultValue == null
472
+ ? {}
473
+ : { completedResult: this.completedResultValue }),
474
+ ...(this.resolvedConfigValue == null
475
+ ? {}
476
+ : { resolvedConfig: this.resolvedConfigValue }),
477
+ started: this.startedValue,
478
+ completed: this.completedValue,
479
+ resolvingIdentity: this.pendingIdentityResolution != null,
480
+ resolving: this.pendingConfigResolution != null,
481
+ executing: this.pendingExecution != null,
482
+ settling: this.pendingSettlement != null,
483
+ settled: this.settledValue,
484
+ };
485
+ }
486
+
487
+ get identity(): SubagentExecutionIdentity | undefined {
488
+ return this.identityValue;
489
+ }
490
+
491
+ get binding(): SubagentDefinitionBinding | undefined {
492
+ return this.bindingValue;
493
+ }
494
+
495
+ get resumeExecution(): SubagentResumeExecution | undefined {
496
+ return this.resumeExecutionValue;
497
+ }
498
+
499
+ get invocation(): SubagentInvocationBinding | undefined {
500
+ return this.invocationValue;
501
+ }
502
+
503
+ get activeRun(): TActiveRun | undefined {
504
+ return this.activeRunValue;
505
+ }
506
+
507
+ get settlement(): SubagentSettlementBinding | undefined {
508
+ return this.settlementValue;
509
+ }
510
+
511
+ get settledOutput(): TSettledOutput | undefined {
512
+ return this.settledOutputValue;
513
+ }
514
+
515
+ get completedResult(): TResult | undefined {
516
+ return this.completedResultValue;
517
+ }
518
+
519
+ get resolvedConfig(): TResolvedConfig | undefined {
520
+ return this.resolvedConfigValue;
521
+ }
522
+
523
+ get started(): boolean {
524
+ return this.startedValue;
525
+ }
526
+
527
+ get completed(): boolean {
528
+ return this.completedValue;
529
+ }
530
+
531
+ assertUsable(signal?: AbortSignal): void {
532
+ if (signal?.aborted === true) {
533
+ throw getAbortReason(signal);
534
+ }
535
+ if (this.phaseValue === 'invalidated') {
536
+ throw new SubagentExecutionInvalidatedError();
537
+ }
538
+ }
539
+
540
+ attachResumeExecution(resumeExecution?: SubagentResumeExecution): void {
541
+ if (resumeExecution == null) {
542
+ return;
543
+ }
544
+ if (this.resumeExecutionValue != null) {
545
+ assertCompatibleResumeExecution(
546
+ this.resumeExecutionValue,
547
+ resumeExecution
548
+ );
549
+ return;
550
+ }
551
+ this.resumeExecutionValue = resumeExecution;
552
+ }
553
+
554
+ bindIdentity(identity: SubagentExecutionIdentity): void {
555
+ this.assertUsable();
556
+ if (this.identityValue != null) {
557
+ assertSameIdentity(this.identityValue, identity);
558
+ return;
559
+ }
560
+ this.identityValue = Object.freeze({ ...identity });
561
+ }
562
+
563
+ resolveIdentity(
564
+ resolve: () => Promise<PreparedSubagentExecutionIdentity>
565
+ ): Promise<SubagentExecutionIdentity> {
566
+ this.assertUsable();
567
+ if (this.identityValue != null) {
568
+ return Promise.resolve(this.identityValue);
569
+ }
570
+ if (this.pendingIdentityResolution != null) {
571
+ return this.pendingIdentityResolution;
572
+ }
573
+ const pending = Promise.resolve()
574
+ .then(() => {
575
+ this.assertCurrentIdentityResolution(pending);
576
+ return resolve();
577
+ })
578
+ .then(async (resolution): Promise<SubagentExecutionIdentity> => {
579
+ try {
580
+ this.assertCurrentIdentityResolution(pending);
581
+ resolution.lease.assertOwned();
582
+ resolution.commit?.(resolution.lease);
583
+ this.assertCurrentIdentityResolution(pending);
584
+ resolution.lease.assertOwned();
585
+ this.bindIdentity(resolution.identity);
586
+ const boundIdentity = this.identityValue;
587
+ if (boundIdentity == null) {
588
+ throw new SubagentExecutionInvalidatedError();
589
+ }
590
+ resolution.lease.release();
591
+ return boundIdentity;
592
+ } catch (error) {
593
+ return resolution.lease.rollback(
594
+ error,
595
+ async (lease): Promise<void> => {
596
+ await resolution.rollback?.(lease);
597
+ }
598
+ );
599
+ }
600
+ })
601
+ .finally(() => {
602
+ if (this.pendingIdentityResolution === pending) {
603
+ this.pendingIdentityResolution = undefined;
604
+ }
605
+ });
606
+ this.pendingIdentityResolution = pending;
607
+ return pending;
608
+ }
609
+
610
+ bindDefinition(
611
+ binding: SubagentDefinitionBinding,
612
+ authority: 'provisional' | 'effective'
613
+ ): void {
614
+ this.assertUsable();
615
+ if (this.bindingAuthority === 'effective') {
616
+ if (authority === 'provisional') {
617
+ return;
618
+ }
619
+ if (
620
+ this.bindingValue?.subagentType !== binding.subagentType ||
621
+ this.bindingValue?.configId !== binding.configId
622
+ ) {
623
+ throw new SubagentDefinitionBindingError();
624
+ }
625
+ return;
626
+ }
627
+ if (authority === 'provisional' && this.bindingValue != null) {
628
+ return;
629
+ }
630
+ this.bindingValue = Object.freeze({ ...binding });
631
+ this.bindingAuthority = authority;
632
+ }
633
+
634
+ resolveConfig(
635
+ resolve: () => Promise<TResolvedConfig>,
636
+ signal: AbortSignal
637
+ ): Promise<TResolvedConfig> {
638
+ this.assertUsable(signal);
639
+ if (this.resolvedConfigValue != null) {
640
+ return Promise.resolve(this.resolvedConfigValue);
641
+ }
642
+ if (this.pendingConfigResolution != null) {
643
+ return this.pendingConfigResolution;
644
+ }
645
+ const pending = Promise.resolve()
646
+ .then(resolve)
647
+ .then((resolvedConfig): TResolvedConfig => {
648
+ this.assertUsable(signal);
649
+ if (this.pendingConfigResolution !== pending) {
650
+ throw new SubagentExecutionInvalidatedError();
651
+ }
652
+ this.resolvedConfigValue = resolvedConfig;
653
+ return resolvedConfig;
654
+ })
655
+ .finally(() => {
656
+ if (this.pendingConfigResolution === pending) {
657
+ this.pendingConfigResolution = undefined;
658
+ }
659
+ });
660
+ this.pendingConfigResolution = pending;
661
+ return pending;
662
+ }
663
+
664
+ execute(
665
+ invocation: SubagentInvocationBinding,
666
+ execute: () => Promise<TResult>
667
+ ): Promise<TResult> {
668
+ this.bindDefinition(
669
+ {
670
+ subagentType: invocation.subagentType,
671
+ ...(invocation.configId == null
672
+ ? {}
673
+ : { configId: invocation.configId }),
674
+ },
675
+ 'effective'
676
+ );
677
+ this.bindInvocation(invocation);
678
+ if (this.pendingExecution != null) {
679
+ return this.pendingExecution;
680
+ }
681
+ const pending = Promise.resolve()
682
+ .then(execute)
683
+ .finally(() => {
684
+ if (this.pendingExecution === pending) {
685
+ this.pendingExecution = undefined;
686
+ }
687
+ });
688
+ this.pendingExecution = pending;
689
+ return pending;
690
+ }
691
+
692
+ settle(
693
+ settlement: SubagentSettlementBinding,
694
+ settledOutput: TSettledOutput,
695
+ persist: () => Promise<void>
696
+ ): Promise<void> {
697
+ this.bindDefinition(
698
+ {
699
+ ...(settlement.subagentType == null
700
+ ? {}
701
+ : { subagentType: settlement.subagentType }),
702
+ ...(settlement.configId == null
703
+ ? {}
704
+ : { configId: settlement.configId }),
705
+ },
706
+ settlement.definitionAuthority
707
+ );
708
+ this.bindInvocation(settlement.invocation);
709
+ if (this.settlementValue != null) {
710
+ assertSameSettlement(this.settlementValue, settlement);
711
+ } else {
712
+ this.settlementValue = Object.freeze({
713
+ ...settlement,
714
+ invocation: Object.freeze({ ...settlement.invocation }),
715
+ });
716
+ }
717
+ if (this.settledValue) {
718
+ return Promise.resolve();
719
+ }
720
+ if (this.pendingSettlement != null) {
721
+ return this.pendingSettlement;
722
+ }
723
+ const pending = Promise.resolve()
724
+ .then(persist)
725
+ .then((): void => {
726
+ this.assertUsable();
727
+ if (this.pendingSettlement !== pending) {
728
+ throw new SubagentExecutionInvalidatedError();
729
+ }
730
+ this.settledValue = true;
731
+ this.settledOutputValue = settledOutput;
732
+ this.activeRunValue = undefined;
733
+ this.completedResultValue = undefined;
734
+ this.resolvedConfigValue = undefined;
735
+ })
736
+ .finally(() => {
737
+ if (this.pendingSettlement === pending) {
738
+ this.pendingSettlement = undefined;
739
+ }
740
+ });
741
+ this.pendingSettlement = pending;
742
+ return pending;
743
+ }
744
+
745
+ activate(activeRun: TActiveRun): TActiveRun {
746
+ this.transitionTo('active', [
747
+ 'registered',
748
+ 'active',
749
+ 'interrupted',
750
+ 'failed',
751
+ ]);
752
+ if (this.activeRunValue != null) {
753
+ return this.activeRunValue;
754
+ }
755
+ this.activeRunValue = activeRun;
756
+ return activeRun;
757
+ }
758
+
759
+ markStarted(): void {
760
+ this.assertUsable();
761
+ if (this.phaseValue === 'completed') {
762
+ throw new Error('Completed subagent execution cannot start again.');
763
+ }
764
+ this.startedValue = true;
765
+ }
766
+
767
+ markInterrupted(): void {
768
+ this.transitionTo('interrupted', ['active', 'interrupted']);
769
+ }
770
+
771
+ markFailed(): void {
772
+ this.transitionTo('failed', [
773
+ 'registered',
774
+ 'active',
775
+ 'interrupted',
776
+ 'failed',
777
+ ]);
778
+ this.activeRunValue = undefined;
779
+ this.resolvedConfigValue = undefined;
780
+ }
781
+
782
+ markCompleted(result: TResult): void {
783
+ this.transitionTo('completed', ['active']);
784
+ this.completedValue = true;
785
+ this.completedResultValue = result;
786
+ this.resolvedConfigValue = undefined;
787
+ }
788
+
789
+ releaseActiveRun(): TActiveRun | undefined {
790
+ const activeRun = this.activeRunValue;
791
+ this.activeRunValue = undefined;
792
+ return activeRun;
793
+ }
794
+
795
+ releaseResolvedConfig(): void {
796
+ this.resolvedConfigValue = undefined;
797
+ }
798
+
799
+ invalidate(): void {
800
+ this.phaseValue = 'invalidated';
801
+ this.identityValue = undefined;
802
+ this.bindingValue = undefined;
803
+ this.bindingAuthority = undefined;
804
+ this.invocationValue = undefined;
805
+ this.settlementValue = undefined;
806
+ this.settledOutputValue = undefined;
807
+ this.resumeExecutionValue = undefined;
808
+ this.activeRunValue = undefined;
809
+ this.completedResultValue = undefined;
810
+ this.resolvedConfigValue = undefined;
811
+ this.pendingIdentityResolution = undefined;
812
+ this.pendingConfigResolution = undefined;
813
+ this.pendingExecution = undefined;
814
+ this.pendingSettlement = undefined;
815
+ this.startedValue = false;
816
+ this.completedValue = false;
817
+ this.settledValue = false;
818
+ }
819
+
820
+ private bindInvocation(invocation: SubagentInvocationBinding): void {
821
+ if (this.invocationValue != null) {
822
+ assertSameInvocation(this.invocationValue, invocation);
823
+ return;
824
+ }
825
+ this.invocationValue = Object.freeze({ ...invocation });
826
+ }
827
+
828
+ private assertCurrentIdentityResolution(
829
+ pending: Promise<SubagentExecutionIdentity>
830
+ ): void {
831
+ this.assertUsable();
832
+ if (this.pendingIdentityResolution !== pending) {
833
+ throw new SubagentExecutionInvalidatedError();
834
+ }
835
+ }
836
+
837
+ private transitionTo(
838
+ next: SubagentExecutionPhase,
839
+ allowed: ReadonlyArray<SubagentExecutionPhase>
840
+ ): void {
841
+ this.assertUsable();
842
+ if (!allowed.includes(this.phaseValue)) {
843
+ throw new Error(
844
+ `Cannot transition subagent execution from ${this.phaseValue} to ${next}.`
845
+ );
846
+ }
847
+ this.phaseValue = next;
848
+ }
849
+ }
850
+
851
+ /** Owns canonical addressing, record selection, and invalidation. */
852
+ export class SubagentExecutionRegistry<
853
+ TResult,
854
+ TResolvedConfig,
855
+ TActiveRun,
856
+ TSettledOutput = never,
857
+ > {
858
+ private readonly recordsByAddress = new Map<
859
+ string,
860
+ SubagentExecutionRecord<
861
+ TResult,
862
+ TResolvedConfig,
863
+ TActiveRun,
864
+ TSettledOutput
865
+ >
866
+ >();
867
+ private readonly checkpointThreadIds = new Set<string>();
868
+ private readonly preparationResourceOwners = new Map<
869
+ string,
870
+ PreparationResourceState
871
+ >();
872
+ private nextPreparationToken = 0;
873
+
874
+ constructor(private readonly options: SubagentExecutionRegistryOptions) {}
875
+
876
+ open(
877
+ input: SubagentExecutionAddressInput
878
+ ): SubagentExecutionRecord<
879
+ TResult,
880
+ TResolvedConfig,
881
+ TActiveRun,
882
+ TSettledOutput
883
+ > {
884
+ const address = this.createAddress(input);
885
+ const resumeExecution = getSubagentResumeManifest(
886
+ input.parentConfigurable
887
+ )?.executions.find(
888
+ (execution) => execution.parentToolCallId === input.parentToolCallId
889
+ );
890
+ const current = this.recordsByAddress.get(address.key);
891
+ if (current != null) {
892
+ current.attachResumeExecution(resumeExecution);
893
+ return current;
894
+ }
895
+ const record = new SubagentExecutionRecord<
896
+ TResult,
897
+ TResolvedConfig,
898
+ TActiveRun,
899
+ TSettledOutput
900
+ >(address, resumeExecution);
901
+ this.recordsByAddress.set(address.key, record);
902
+ return record;
903
+ }
904
+
905
+ remove(
906
+ record: SubagentExecutionRecord<
907
+ TResult,
908
+ TResolvedConfig,
909
+ TActiveRun,
910
+ TSettledOutput
911
+ >
912
+ ): void {
913
+ if (this.recordsByAddress.get(record.address.key) !== record) {
914
+ return;
915
+ }
916
+ this.recordsByAddress.delete(record.address.key);
917
+ record.invalidate();
918
+ }
919
+
920
+ beginIdentityPreparation(
921
+ record: SubagentExecutionRecord<
922
+ TResult,
923
+ TResolvedConfig,
924
+ TActiveRun,
925
+ TSettledOutput
926
+ >,
927
+ identity: SubagentExecutionIdentity
928
+ ): SubagentExecutionPreparationLease {
929
+ record.assertUsable();
930
+ if (this.recordsByAddress.get(record.address.key) !== record) {
931
+ throw new SubagentExecutionInvalidatedError();
932
+ }
933
+ const checkpointKey = getPreparationResourceKey(
934
+ 'checkpoint',
935
+ identity.childThreadId
936
+ );
937
+ const approvalKey = getPreparationResourceKey(
938
+ 'approval',
939
+ identity.approvalExecutionScope
940
+ );
941
+ const resourceKeys = [checkpointKey, approvalKey];
942
+ const currentResources = resourceKeys.map((key) =>
943
+ this.preparationResourceOwners.get(key)
944
+ );
945
+ if (currentResources.some((resource) => resource != null)) {
946
+ throw new SubagentExecutionInvalidatedError();
947
+ }
948
+ this.nextPreparationToken += 1;
949
+ const token = this.nextPreparationToken;
950
+ for (let i = 0; i < resourceKeys.length; i++) {
951
+ const key = resourceKeys[i];
952
+ this.preparationResourceOwners.set(key, {
953
+ status: 'owned',
954
+ token,
955
+ created: false,
956
+ });
957
+ }
958
+ return new PreparationLease(
959
+ this.preparationResourceOwners,
960
+ token,
961
+ checkpointKey,
962
+ approvalKey
963
+ );
964
+ }
965
+
966
+ selectForResume(
967
+ selection: ResumeSelection = {}
968
+ ): SubagentExecutionRecord<
969
+ TResult,
970
+ TResolvedConfig,
971
+ TActiveRun,
972
+ TSettledOutput
973
+ >[] {
974
+ if (selection.config != null) {
975
+ return this.selectScopedRecords(selection);
976
+ }
977
+ const counts = new Map<string, number>();
978
+ const candidates: SubagentExecutionRecord<
979
+ TResult,
980
+ TResolvedConfig,
981
+ TActiveRun,
982
+ TSettledOutput
983
+ >[] = [];
984
+ for (const record of this.recordsByAddress.values()) {
985
+ if (record.identity == null) {
986
+ continue;
987
+ }
988
+ const parentToolCallId = record.address.parentToolCallId;
989
+ if (
990
+ selection.parentToolCallIds != null &&
991
+ !selection.parentToolCallIds.has(parentToolCallId)
992
+ ) {
993
+ continue;
994
+ }
995
+ candidates.push(record);
996
+ counts.set(parentToolCallId, (counts.get(parentToolCallId) ?? 0) + 1);
997
+ }
998
+ return candidates.filter(
999
+ (record) => counts.get(record.address.parentToolCallId) === 1
1000
+ );
1001
+ }
1002
+
1003
+ rememberCheckpointThread(...threadIds: string[]): void {
1004
+ for (const threadId of threadIds) {
1005
+ this.checkpointThreadIds.add(threadId);
1006
+ }
1007
+ }
1008
+
1009
+ getCheckpointThreadIds(
1010
+ getNestedThreadIds?: (activeRun: TActiveRun) => ReadonlyArray<string>
1011
+ ): string[] {
1012
+ const threadIds = new Set(this.checkpointThreadIds);
1013
+ if (getNestedThreadIds == null) {
1014
+ return [...threadIds];
1015
+ }
1016
+ for (const record of this.recordsByAddress.values()) {
1017
+ if (record.activeRun == null) {
1018
+ continue;
1019
+ }
1020
+ for (const threadId of getNestedThreadIds(record.activeRun)) {
1021
+ threadIds.add(threadId);
1022
+ }
1023
+ }
1024
+ return [...threadIds];
1025
+ }
1026
+
1027
+ resetCheckpointThreadIds(): void {
1028
+ this.checkpointThreadIds.clear();
1029
+ }
1030
+
1031
+ retireResumeSources(
1032
+ current: SubagentExecutionRecord<
1033
+ TResult,
1034
+ TResolvedConfig,
1035
+ TActiveRun,
1036
+ TSettledOutput
1037
+ >,
1038
+ sourceThreadIds: ReadonlySet<string>,
1039
+ retire: (
1040
+ source: SubagentExecutionRecord<
1041
+ TResult,
1042
+ TResolvedConfig,
1043
+ TActiveRun,
1044
+ TSettledOutput
1045
+ >
1046
+ ) => void
1047
+ ): void {
1048
+ for (const record of this.recordsByAddress.values()) {
1049
+ if (
1050
+ record === current ||
1051
+ record.identity == null ||
1052
+ !sourceThreadIds.has(record.identity.childThreadId)
1053
+ ) {
1054
+ continue;
1055
+ }
1056
+ try {
1057
+ retire(record);
1058
+ } finally {
1059
+ this.remove(record);
1060
+ }
1061
+ }
1062
+ }
1063
+
1064
+ clear(
1065
+ dispose?: (
1066
+ record: SubagentExecutionRecord<
1067
+ TResult,
1068
+ TResolvedConfig,
1069
+ TActiveRun,
1070
+ TSettledOutput
1071
+ >
1072
+ ) => void
1073
+ ): void {
1074
+ for (const record of this.recordsByAddress.values()) {
1075
+ dispose?.(record);
1076
+ record.invalidate();
1077
+ }
1078
+ this.recordsByAddress.clear();
1079
+ }
1080
+
1081
+ private selectScopedRecords(
1082
+ selection: ResumeSelection
1083
+ ): SubagentExecutionRecord<
1084
+ TResult,
1085
+ TResolvedConfig,
1086
+ TActiveRun,
1087
+ TSettledOutput
1088
+ >[] {
1089
+ const parentConfigurable = getConfigurable(selection.config);
1090
+ const threadId = getConfigurableString(parentConfigurable, 'thread_id');
1091
+ const parentToolCallIds =
1092
+ selection.parentToolCallIds ??
1093
+ new Set(
1094
+ [...this.recordsByAddress.values()].map(
1095
+ (record) => record.address.parentToolCallId
1096
+ )
1097
+ );
1098
+ const records: SubagentExecutionRecord<
1099
+ TResult,
1100
+ TResolvedConfig,
1101
+ TActiveRun,
1102
+ TSettledOutput
1103
+ >[] = [];
1104
+ for (const parentToolCallId of parentToolCallIds) {
1105
+ const address = this.createAddress({
1106
+ threadId,
1107
+ parentToolCallId,
1108
+ parentConfigurable,
1109
+ });
1110
+ const record = this.recordsByAddress.get(address.key);
1111
+ if (record?.identity != null) {
1112
+ records.push(record);
1113
+ }
1114
+ }
1115
+ return records;
1116
+ }
1117
+
1118
+ private createAddress(
1119
+ input: SubagentExecutionAddressInput
1120
+ ): SubagentExecutionAddress {
1121
+ const durableParentId = input.threadId ?? this.options.parentRunId;
1122
+ const parentFork =
1123
+ getConfigurableString(input.parentConfigurable, 'checkpoint_id') ??
1124
+ 'root';
1125
+ const parentBatch =
1126
+ getConfigurableString(
1127
+ input.parentConfigurable,
1128
+ SUBAGENT_PARENT_BATCH_CONFIG_KEY
1129
+ ) ?? 'batch';
1130
+ const configuredResumeAttemptId = getConfigurableString(
1131
+ input.parentConfigurable,
1132
+ SUBAGENT_RESUME_ATTEMPT_CONFIG_KEY
1133
+ );
1134
+ const resumeAttemptId =
1135
+ configuredResumeAttemptId ?? this.options.parentRunId;
1136
+ const identity = [
1137
+ durableParentId,
1138
+ parentFork,
1139
+ this.options.parentAgentId ?? 'agent',
1140
+ input.parentToolCallId,
1141
+ parentBatch,
1142
+ ];
1143
+ const baseChildThreadId = encodeThreadIdentity(identity);
1144
+ const branchChildThreadId = this.options.durable
1145
+ ? encodeThreadIdentity([...identity, resumeAttemptId])
1146
+ : baseChildThreadId;
1147
+ return Object.freeze({
1148
+ key: branchChildThreadId,
1149
+ baseChildThreadId,
1150
+ branchChildThreadId,
1151
+ currentChildRunId: `${this.options.parentRunId}_sub_${baseChildThreadId.slice(
1152
+ SUBAGENT_THREAD_ID_PREFIX.length
1153
+ )}`,
1154
+ explicitResumeAttempt: configuredResumeAttemptId != null,
1155
+ parentToolCallId: input.parentToolCallId,
1156
+ resumeAttemptId,
1157
+ });
1158
+ }
1159
+ }