@assemblyline-agents/runtime 3.0.0 → 3.2.0

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 (95) hide show
  1. package/dist/agent-hook-controller-factory.d.ts +28 -0
  2. package/dist/agent-hook-controller-factory.d.ts.map +1 -0
  3. package/dist/agent-hook-controller-factory.js +83 -0
  4. package/dist/agent-hook-controller-factory.js.map +1 -0
  5. package/dist/background-subagents.d.ts +43 -0
  6. package/dist/background-subagents.d.ts.map +1 -0
  7. package/dist/background-subagents.js +304 -0
  8. package/dist/background-subagents.js.map +1 -0
  9. package/dist/builtin-tool-executors.d.ts +1 -0
  10. package/dist/builtin-tool-executors.d.ts.map +1 -1
  11. package/dist/builtin-tool-executors.js +38 -30
  12. package/dist/builtin-tool-executors.js.map +1 -1
  13. package/dist/builtin-tools.d.ts.map +1 -1
  14. package/dist/builtin-tools.js +5 -4
  15. package/dist/builtin-tools.js.map +1 -1
  16. package/dist/capability-catalog.d.ts +8 -5
  17. package/dist/capability-catalog.d.ts.map +1 -1
  18. package/dist/capability-catalog.js +28 -1
  19. package/dist/capability-catalog.js.map +1 -1
  20. package/dist/conversation-turn-dispatch.d.ts +1 -1
  21. package/dist/conversation-turn-dispatch.d.ts.map +1 -1
  22. package/dist/conversation-turn-dispatch.js +25 -1
  23. package/dist/conversation-turn-dispatch.js.map +1 -1
  24. package/dist/dynamic-schedule-runtime.d.ts +42 -0
  25. package/dist/dynamic-schedule-runtime.d.ts.map +1 -0
  26. package/dist/dynamic-schedule-runtime.js +57 -0
  27. package/dist/dynamic-schedule-runtime.js.map +1 -0
  28. package/dist/harness-loop.d.ts +5 -5
  29. package/dist/harness-loop.d.ts.map +1 -1
  30. package/dist/harness-loop.js +16 -14
  31. package/dist/harness-loop.js.map +1 -1
  32. package/dist/in-memory-conversation-turn-store.d.ts +1 -0
  33. package/dist/in-memory-conversation-turn-store.d.ts.map +1 -1
  34. package/dist/in-memory-conversation-turn-store.js +11 -0
  35. package/dist/in-memory-conversation-turn-store.js.map +1 -1
  36. package/dist/index.d.ts +18 -40
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +144 -164
  39. package/dist/index.js.map +1 -1
  40. package/dist/local-sandbox.d.ts.map +1 -1
  41. package/dist/local-sandbox.js +2 -1
  42. package/dist/local-sandbox.js.map +1 -1
  43. package/dist/public-api.d.ts +2 -2
  44. package/dist/public-api.d.ts.map +1 -1
  45. package/dist/public-api.js.map +1 -1
  46. package/dist/replay-resume.d.ts.map +1 -1
  47. package/dist/replay-resume.js +3 -2
  48. package/dist/replay-resume.js.map +1 -1
  49. package/dist/run-heartbeat.d.ts +45 -18
  50. package/dist/run-heartbeat.d.ts.map +1 -1
  51. package/dist/run-heartbeat.js +120 -14
  52. package/dist/run-heartbeat.js.map +1 -1
  53. package/dist/run-outcome.d.ts +1 -1
  54. package/dist/run-outcome.d.ts.map +1 -1
  55. package/dist/run-outcome.js.map +1 -1
  56. package/dist/run-recovery.d.ts +9 -1
  57. package/dist/run-recovery.d.ts.map +1 -1
  58. package/dist/run-recovery.js +26 -1
  59. package/dist/run-recovery.js.map +1 -1
  60. package/dist/runtime-types.d.ts +15 -3
  61. package/dist/runtime-types.d.ts.map +1 -1
  62. package/dist/sandbox-session-lifecycle.d.ts +1 -0
  63. package/dist/sandbox-session-lifecycle.d.ts.map +1 -1
  64. package/dist/sandbox-session-lifecycle.js +48 -16
  65. package/dist/sandbox-session-lifecycle.js.map +1 -1
  66. package/dist/scheduled-delivery.d.ts +6 -0
  67. package/dist/scheduled-delivery.d.ts.map +1 -0
  68. package/dist/scheduled-delivery.js +117 -0
  69. package/dist/scheduled-delivery.js.map +1 -0
  70. package/dist/state-stores.js +1 -1
  71. package/dist/state-stores.js.map +1 -1
  72. package/dist/state.d.ts +2 -1
  73. package/dist/state.d.ts.map +1 -1
  74. package/dist/state.js +1 -0
  75. package/dist/state.js.map +1 -1
  76. package/dist/storage.d.ts +1 -0
  77. package/dist/storage.d.ts.map +1 -1
  78. package/dist/storage.js +3 -0
  79. package/dist/storage.js.map +1 -1
  80. package/dist/subagent-execution.d.ts +8 -1
  81. package/dist/subagent-execution.d.ts.map +1 -1
  82. package/dist/subagent-execution.js +132 -6
  83. package/dist/subagent-execution.js.map +1 -1
  84. package/dist/subagent-handoff.d.ts +44 -0
  85. package/dist/subagent-handoff.d.ts.map +1 -0
  86. package/dist/subagent-handoff.js +271 -0
  87. package/dist/subagent-handoff.js.map +1 -0
  88. package/dist/tool-apis.d.ts.map +1 -1
  89. package/dist/tool-apis.js +7 -6
  90. package/dist/tool-apis.js.map +1 -1
  91. package/dist/tool-execution.d.ts +5 -0
  92. package/dist/tool-execution.d.ts.map +1 -1
  93. package/dist/tool-execution.js +96 -9
  94. package/dist/tool-execution.js.map +1 -1
  95. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -10,10 +10,9 @@ import { rememberDefaultOutboundRoute } from "./default-outbound-route.js";
10
10
  import { audienceFromRun, audienceUserKey, normalizeAgentAudience } from "./audience.js";
11
11
  import { AgentSourceModuleLoader } from "./source-loader.js";
12
12
  import { AgentEventHandlerRegistry } from "./agent-event-handlers.js";
13
- import { immutableAgentRunContext } from "./agent-hook-evaluation.js";
14
- import { hookCapabilityCatalog } from "./agent-hook-context.js";
15
- import { flattenSubagents, rootAgentSurface, subagentSurface, surfaceForPath, surfaceForRun, durableAgentSurfaceScope } from "./agent-surface.js";
16
- import { createRuntimeAgentHookController, recoverAgentHookController, removeTerminalAgentHookState, resolvedInstructionsForRun as resolvedHookInstructionsForRun } from "./agent-hook-run-lifecycle.js";
13
+ import { createMainAgentHookController as createMainAgentHookControllerImpl, createSubagentHookController as createSubagentHookControllerImpl } from "./agent-hook-controller-factory.js";
14
+ import { durableAgentSurfaceScope, flattenSubagents, surfaceForPath, surfaceForRun } from "./agent-surface.js";
15
+ import { recoverAgentHookController, removeTerminalAgentHookState, resolvedInstructionsForRun as resolvedHookInstructionsForRun } from "./agent-hook-run-lifecycle.js";
17
16
  import { compiledChannelProvider, providerIngressSecurityError, providerIngressSecurityWarnings, resolveCoreToolPolicy, trustedAttachmentProviderForTurn } from "./security.js";
18
17
  export { constantTimeSecretEqual } from "./security.js";
19
18
  export { RUN_STREAM_TERMINAL_EVENTS } from "./run-stream.js";
@@ -24,7 +23,7 @@ import { resolveBooleanEnv, resolveNonNegativeIntegerEnv, resolvePositiveInteger
24
23
  import { humanInputModeForChannel, withHumanInputMode } from "./human-input-routing.js";
25
24
  import { RunEventBus, withRunEventPublishing } from "./run-stream.js";
26
25
  import { failRun as failRunImpl, handleRunExecutionError, logTerminalRunEvent, settleCompletionBoundaryControlImpl } from "./run-outcome.js";
27
- import { cacheRunControlSignal, registerRunAbortController, startRunHeartbeatImpl, trackActiveRunHeartbeat } from "./run-heartbeat.js";
26
+ import { eventRenewsRunProgress, RunProgressSupervisor } from "./run-heartbeat.js";
28
27
  import { redactTruncateJson } from "./content-capture.js";
29
28
  import { isJsonObject, safeJson } from "./json-util.js";
30
29
  import { sandboxSessionKeyForRun } from "./sandbox-session-key.js";
@@ -55,7 +54,8 @@ import { beginChannelTurnImpl, completeRunImpl, latestRunFailureMessageImpl, rec
55
54
  import { backfillMemoryDocumentsFromFileIndexesImpl, hydrateLogicalPathForToolImpl, listResourcesForRunImpl, memoryScopeForRunImpl, projectLoadedSkillsIntoSandboxImpl, projectResourcesForRunImpl, projectSkillIntoSandboxImpl, readResourceForRunImpl, readSandboxTombstonesWithMissingProjectedFilesImpl, recordSandboxTombstoneImpl, runMemoryEmbeddingBackfillImpl } from "./resource-projection.js";
56
55
  import { createSkillBundleIndex, createSkillResourceReader } from "./skill-bundles.js";
57
56
  import { createSandboxHandleImpl } from "./sandbox-handle.js";
58
- import { RuntimeWorkspaceManager, WorkspaceRuntimeMethods } from "./workspace-runtime.js";
57
+ import { RuntimeWorkspaceManager } from "./workspace-runtime.js";
58
+ import { DynamicScheduleRuntimeMethods } from "./dynamic-schedule-runtime.js";
59
59
  import { beginConnectionAuthorizationImpl, createConnectionRegistryForRunImpl, createConnectionRegistryForSessionImpl, createConnectionRegistryImpl, executeConnectionToolWithRecordImpl, skillLoadDetailsImpl } from "./builtin-tool-executors.js";
60
60
  import { createMemoryToolApiImpl, createResourceToolApiImpl, durableStepImpl } from "./tool-apis.js";
61
61
  import { executeToolImpl, executeToolWithRecordImpl, harnessExecuteToolImpl, modelToolsImpl, selectToolImpl, visibleToolSetImpl, wrapSandboxImpl } from "./tool-execution.js";
@@ -64,13 +64,15 @@ import { completeConnectionAuthorizationCallbackImpl, replayImpl, resumeApproval
64
64
  import { recordRunReplayStarted, rerunFromSnapshot } from "./run-replay.js";
65
65
  export { RunReplayError } from "./run-replay.js";
66
66
  import { buildRunAgentResult } from "./run-result.js";
67
- import { readSubagentImpl, runSubagentImpl, sendSubagentFollowupImpl } from "./subagent-execution.js";
67
+ import { createSubagentRunImpl, executeSubagentRunImpl, finalizeSubagentHandoffImpl, readSubagentImpl, runSubagentImpl, sendSubagentFollowupImpl } from "./subagent-execution.js";
68
+ import { acceptBackgroundSubagentHandoff } from "./subagent-handoff.js";
68
69
  import { createHarnessContextImpl, DEFAULT_MAX_MODEL_ITERATIONS, DEFAULT_OUTPUT_VALIDATION_MAX_RETRIES, executeHarnessTurnImpl, persistHarnessContinuationImpl, resolveAgentHarnessImpl, runHarnessLoopImpl } from "./harness-loop.js";
69
70
  import { getAgentControlImpl, requestRunControlImpl, setAgentEnabledImpl, setAgentMaintenanceImpl } from "./operator-controls.js";
70
71
  import { acceptChannelTurnImpl, channelAttachmentResolverImpl, dispatchHttpChannelImpl, loadChannelModuleImpl, observeChannelMessageImpl, startChannelAgentTurnImpl, startChannelIngressImpl } from "./channel-ingress.js";
71
72
  import { augmentChannelContextImpl, buildContextBundleImpl, prepareConversationImpl, recentHistoryImpl } from "./conversation-context.js";
72
73
  import { loadResumableConversationTranscriptImpl } from "./conversation-transcript.js";
73
74
  import { ConversationTurnDispatcher } from "./conversation-turn-dispatch.js";
75
+ import { BackgroundSubagentCoordinator } from "./background-subagents.js";
74
76
  import { instructionsForRunTargetImpl } from "./target-instructions.js";
75
77
  import { emitTelemetrySpanImpl, runSpanAttributesImpl } from "./telemetry.js";
76
78
  import { normalizeRuntimeState } from "./state-stores.js";
@@ -92,8 +94,8 @@ import { syncMemoryFromSandbox, syncSkillsFromSandbox } from "./sandbox-state-sy
92
94
  import { acquireSandboxForRun as acquireSandboxSessionForRun } from "./sandbox-session-lifecycle.js";
93
95
  import { resolveSandboxSnapshotPolicy } from "./sandbox-snapshot-policy.js";
94
96
  import { inferToolInput } from "./channel-http-util.js";
95
- import { connectionPrincipalKeyForGrant, durableAgentScopeForManifest, isInstrumentationDefinition, normalizeCapturePolicy, randomHex, stringValue, timestamp, } from "./runtime-util.js";
96
- export class AssemblyLineRuntime extends WorkspaceRuntimeMethods {
97
+ import { connectionPrincipalKeyForGrant, durableAgentScopeForManifest, isInstrumentationDefinition, normalizeCapturePolicy, randomHex, timestamp, } from "./runtime-util.js";
98
+ export class AssemblyLineRuntime extends DynamicScheduleRuntimeMethods {
97
99
  manifest;
98
100
  state;
99
101
  runEventBus = new RunEventBus();
@@ -103,6 +105,8 @@ export class AssemblyLineRuntime extends WorkspaceRuntimeMethods {
103
105
  blob;
104
106
  sandbox;
105
107
  scheduler;
108
+ scheduleStore;
109
+ schedulesConfig;
106
110
  mediaProcessors;
107
111
  deliveryMode;
108
112
  toolStubs;
@@ -130,10 +134,8 @@ export class AssemblyLineRuntime extends WorkspaceRuntimeMethods {
130
134
  skillStore;
131
135
  learningStore;
132
136
  memoryStore;
133
- scheduleStore;
134
137
  connectionDefinitionStore;
135
138
  skillsConfig;
136
- schedulesConfig;
137
139
  connectionsConfig;
138
140
  runtimeLearning;
139
141
  connectionScopeMigrated = false;
@@ -159,6 +161,7 @@ export class AssemblyLineRuntime extends WorkspaceRuntimeMethods {
159
161
  outputValidationMaxRetries;
160
162
  runSemaphore;
161
163
  conversationTurnDispatcher;
164
+ backgroundSubagents;
162
165
  memoryEmbeddingProvider;
163
166
  memoryEmbeddingsEnabled;
164
167
  resourceProjectionPolicy;
@@ -177,10 +180,8 @@ export class AssemblyLineRuntime extends WorkspaceRuntimeMethods {
177
180
  runControlIntents = new Map();
178
181
  /** Runs currently owned by this process, used to route immediate control signals without leaking orphan ids. */
179
182
  activeRunExecutions = new Set();
180
- /** Deadline-fired runs (read by shouldStopRun) + their in-flight abort controllers. */
181
- runDeadlines = new Set();
182
- runAbortControllers = new Map();
183
- maxRunDurationMs;
183
+ runProgress;
184
+ runStallTimeoutMs;
184
185
  constructor(options) {
185
186
  super();
186
187
  this.manifest = options.manifest;
@@ -286,7 +287,16 @@ export class AssemblyLineRuntime extends WorkspaceRuntimeMethods {
286
287
  this.deliveryQueueMaxAttempts = options.deliveryQueueMaxAttempts ?? resolvePositiveIntegerEnv(this.env.ASSEMBLY_LINE_DELIVERY_QUEUE_MAX_ATTEMPTS, 5);
287
288
  this.deliveryQueueIntervalMs = options.deliveryQueueIntervalMs ?? resolvePositiveIntegerEnv(this.env.ASSEMBLY_LINE_DELIVERY_QUEUE_INTERVAL_MS, 15_000);
288
289
  this.runHeartbeatMs = options.runHeartbeatMs ?? resolvePositiveIntegerEnv(this.env.ASSEMBLY_LINE_RUN_HEARTBEAT_MS, 30_000);
289
- this.maxRunDurationMs = options.maxRunDurationMs ?? resolveNonNegativeIntegerEnv(this.env.ASSEMBLY_LINE_MAX_RUN_DURATION_MS, 3_600_000);
290
+ this.runStallTimeoutMs = options.runStallTimeoutMs ?? resolveNonNegativeIntegerEnv(this.env.ASSEMBLY_LINE_RUN_STALL_TIMEOUT_MS, 3_600_000);
291
+ this.runProgress = new RunProgressSupervisor({
292
+ state: this.state,
293
+ logger: this.logger,
294
+ runHeartbeatMs: this.runHeartbeatMs,
295
+ runStallTimeoutMs: this.runStallTimeoutMs,
296
+ runControlIntents: this.runControlIntents,
297
+ activeRuns: this.activeRunExecutions,
298
+ debugSwallow: (message, fields) => this.debugSwallow(message, fields)
299
+ });
290
300
  this.runRecoveryIntervalMs = options.runRecoveryIntervalMs ?? resolvePositiveIntegerEnv(this.env.ASSEMBLY_LINE_RUN_RECOVERY_INTERVAL_MS, 60_000);
291
301
  this.maxModelIterations = resolvePositiveIntegerEnv(this.env.ASSEMBLY_LINE_MAX_MODEL_ITERATIONS, DEFAULT_MAX_MODEL_ITERATIONS);
292
302
  this.outputValidationMaxRetries = resolveNonNegativeIntegerEnv(this.env.ASSEMBLY_LINE_OUTPUT_VALIDATION_MAX_RETRIES, DEFAULT_OUTPUT_VALIDATION_MAX_RETRIES);
@@ -296,7 +306,7 @@ export class AssemblyLineRuntime extends WorkspaceRuntimeMethods {
296
306
  });
297
307
  this.conversationTurnDispatcher = new ConversationTurnDispatcher({
298
308
  state: this.state, logger: this.logger,
299
- agentScope: this.durableAgentScope(), maxRunDurationMs: this.maxRunDurationMs,
309
+ agentScope: this.durableAgentScope(),
300
310
  now: () => this.now(), agentEnabled: () => this.agentEnabled(),
301
311
  admitRunOrReject: () => this.admitRunOrReject(),
302
312
  runTurn: (turn, admission, onRunCreated) => this.run({
@@ -318,6 +328,16 @@ export class AssemblyLineRuntime extends WorkspaceRuntimeMethods {
318
328
  manifestRoot: this.manifest.root,
319
329
  sourceBundle: this.sourceBundle
320
330
  });
331
+ this.backgroundSubagents = new BackgroundSubagentCoordinator({
332
+ state: this.state,
333
+ logger: this.logger,
334
+ withRunAdmission: (work) => this.withRunAdmission(work),
335
+ execute: (childRunId) => executeSubagentRunImpl(this.subagentExecutionHost(), childRunId),
336
+ finalize: (timeline) => finalizeSubagentHandoffImpl(this.subagentExecutionHost(), timeline),
337
+ replay: (runId) => this.replay(runId),
338
+ cancel: (runId) => this.cancelRun(runId),
339
+ enqueueConversationTurn: (input) => this.conversationTurnDispatcher.enqueue(input)
340
+ });
321
341
  }
322
342
  boot() {
323
343
  if (this.manifest.schemaVersion !== ASSEMBLY_LINE_MANIFEST_SCHEMA_VERSION) {
@@ -374,6 +394,7 @@ export class AssemblyLineRuntime extends WorkspaceRuntimeMethods {
374
394
  async createCheckpoint(input) {
375
395
  const storedData = await this.checkpointDataForStorage(input.runId, input.name, input.data);
376
396
  const checkpoint = await this.state.createCheckpoint({ ...input, data: storedData });
397
+ await this.runProgress.renew(input.runId);
377
398
  if (COMPACTABLE_ACTIVE_CHECKPOINT_NAMES.includes(input.name)) {
378
399
  await this.compactCheckpoints({
379
400
  runId: input.runId,
@@ -477,53 +498,6 @@ export class AssemblyLineRuntime extends WorkspaceRuntimeMethods {
477
498
  startBackgroundReviewWorker(options = {}) {
478
499
  return this.runtimeLearning.startBackgroundReviewWorker(options);
479
500
  }
480
- async createDynamicSchedule(input) {
481
- if (!this.schedulesConfig.dynamic) {
482
- throw new Error("Dynamic automations are disabled (dynamicAutomations.dynamic is false).");
483
- }
484
- const trigger = input.trigger ? this.normalizeScheduleTriggerDefinition(input.trigger) : undefined;
485
- const prompt = stringValue(input.prompt) || stringValue(input.message) || (trigger ? `Run scheduled trigger ${trigger.handler}.` : "");
486
- if (!prompt)
487
- throw new Error("Dynamic schedules require prompt or message.");
488
- const createInput = {
489
- ...input,
490
- prompt
491
- };
492
- if (input.message)
493
- createInput.message = input.message;
494
- if (trigger)
495
- createInput.trigger = trigger;
496
- return this.scheduleStore.create(createInput);
497
- }
498
- async createDynamicAutomation(input) {
499
- return this.createDynamicSchedule(input);
500
- }
501
- async listDynamicSchedules(scope) {
502
- return this.scheduleStore.list(scope);
503
- }
504
- async listDynamicAutomations(scope) {
505
- return this.listDynamicSchedules(scope);
506
- }
507
- async updateDynamicSchedule(scope, id, patch) {
508
- if (!this.schedulesConfig.dynamic) {
509
- throw new Error("Dynamic automations are disabled (dynamicAutomations.dynamic is false).");
510
- }
511
- const normalized = { ...patch };
512
- if (Object.prototype.hasOwnProperty.call(patch, "trigger")) {
513
- const rawTrigger = patch.trigger;
514
- normalized.trigger = rawTrigger === null ? null : this.normalizeScheduleTriggerDefinition(rawTrigger);
515
- }
516
- return this.scheduleStore.update(scope, id, normalized);
517
- }
518
- async updateDynamicAutomation(scope, id, patch) {
519
- return this.updateDynamicSchedule(scope, id, patch);
520
- }
521
- async deleteDynamicSchedule(scope, id) {
522
- return this.scheduleStore.delete(scope, id);
523
- }
524
- async deleteDynamicAutomation(scope, id) {
525
- return this.deleteDynamicSchedule(scope, id);
526
- }
527
501
  /** Persist a runtime-provided connection definition (gated). Credentials route to the grant store. */
528
502
  async saveConnectionDefinition(input) {
529
503
  if (!this.connectionsConfig.dynamic) {
@@ -624,6 +598,7 @@ export class AssemblyLineRuntime extends WorkspaceRuntimeMethods {
624
598
  async acquireSandboxForRun(run) {
625
599
  return acquireSandboxSessionForRun({
626
600
  manifest: this.manifest,
601
+ env: this.env,
627
602
  state: this.state,
628
603
  sandbox: this.sandbox,
629
604
  sessionKey: (candidate) => this.sandboxSessionKey(candidate),
@@ -796,6 +771,17 @@ export class AssemblyLineRuntime extends WorkspaceRuntimeMethods {
796
771
  this.logger.warn("onRunCreated hook failed", { runId: run.id, error: error instanceof Error ? error.message : String(error) });
797
772
  }
798
773
  await this.state.appendEvent(run.id, "run.accepted", { agentRevision: this.manifest.agentRevision });
774
+ try {
775
+ await acceptBackgroundSubagentHandoff(this.state, run);
776
+ }
777
+ catch (error) {
778
+ const message = error instanceof Error ? error.message : String(error);
779
+ run = await failRunImpl({ state: this.state }, run.id, {
780
+ reason: "subagent.handoff_failed",
781
+ error: message
782
+ });
783
+ return this.result(run, `Artifact handoff failed: ${message}`, false, false, false);
784
+ }
799
785
  if (options.replay)
800
786
  await recordRunReplayStarted(this.state, run.id, options.replay);
801
787
  if (this.usesRevisionScopedDurableState() && (this.skillsConfig.enabled || this.connectionsConfig.dynamic)) {
@@ -916,8 +902,20 @@ export class AssemblyLineRuntime extends WorkspaceRuntimeMethods {
916
902
  const channelContext = mergeJsonObjects(media.channelContext, augmentation?.channelContext);
917
903
  if (channelContext)
918
904
  contextInput.channelContext = channelContext;
919
- if (hydratedOptions.promptContext)
920
- contextInput.promptContext = hydratedOptions.promptContext;
905
+ const activeWork = await this.backgroundSubagents.listForRoot(run);
906
+ if (hydratedOptions.promptContext || activeWork.length > 0) {
907
+ contextInput.promptContext = {
908
+ ...(hydratedOptions.promptContext ?? {}),
909
+ ...(activeWork.length > 0
910
+ ? {
911
+ activeWork: {
912
+ items: activeWork,
913
+ guidance: "These background tasks are still active. You may answer new messages while they run. Do not claim they are complete. Use manage_work to inspect or cancel them."
914
+ }
915
+ }
916
+ : {})
917
+ };
918
+ }
921
919
  if (hydratedOptions.target)
922
920
  contextInput.target = hydratedOptions.target;
923
921
  let bundle = options.contextSnapshot ? structuredClone(options.contextSnapshot) : await this.buildContextBundle(contextInput);
@@ -965,6 +963,13 @@ export class AssemblyLineRuntime extends WorkspaceRuntimeMethods {
965
963
  const input = hydratedOptions.input ?? inferToolInput(tool, hydratedOptions.message);
966
964
  const outcome = await this.executeTool(run, tool, input, sandboxHandle, hydratedOptions.approve ?? false);
967
965
  run = outcome.run;
966
+ const stalled = await this.runProgress.failIfStalled(run);
967
+ if (stalled) {
968
+ run = stalled;
969
+ await stopChannelLifecycle();
970
+ await sandboxHandle.dispose();
971
+ return this.result(run, "Run stalled without durable progress.", false, false, false);
972
+ }
968
973
  if (outcome.waitingForApproval || outcome.waitingForInput || outcome.waitingForConnection) {
969
974
  await stopChannelLifecycle();
970
975
  await this.emitSpan("ai.assembly-line.turn", spanStartedAt, this.runSpanAttributes(run, {
@@ -1063,24 +1068,9 @@ export class AssemblyLineRuntime extends WorkspaceRuntimeMethods {
1063
1068
  async settleCompletionBoundaryControl(run) {
1064
1069
  return settleCompletionBoundaryControlImpl({ state: this.state, runControlIntents: this.runControlIntents }, run);
1065
1070
  }
1066
- /** Bump `updatedAt` while a run executes; enforces the wall-clock deadline (run-heartbeat.ts). */
1071
+ /** Bump `updatedAt` while a run executes and supervise its progress lease. */
1067
1072
  startRunHeartbeat(runId) {
1068
- return trackActiveRunHeartbeat(this.activeRunExecutions, runId, startRunHeartbeatImpl({
1069
- state: this.state,
1070
- logger: this.logger,
1071
- runHeartbeatMs: this.runHeartbeatMs,
1072
- maxRunDurationMs: this.maxRunDurationMs,
1073
- runControlIntents: this.runControlIntents,
1074
- runDeadlines: this.runDeadlines,
1075
- abortRunControllers: (id) => {
1076
- for (const controller of this.runAbortControllers.get(id) ?? [])
1077
- controller.abort();
1078
- },
1079
- debugSwallow: (message, fields) => this.debugSwallow(message, fields)
1080
- }, runId));
1081
- }
1082
- registerRunAbort(runId, controller) {
1083
- return registerRunAbortController(this.runAbortControllers, this.runDeadlines, runId, controller, this.runControlIntents);
1073
+ return this.runProgress.start(runId);
1084
1074
  }
1085
1075
  conversationContextHost() {
1086
1076
  const runtime = this;
@@ -1136,10 +1126,10 @@ export class AssemblyLineRuntime extends WorkspaceRuntimeMethods {
1136
1126
  maxModelIterations: this.maxModelIterations,
1137
1127
  outputValidationMaxRetries: this.outputValidationMaxRetries,
1138
1128
  now: () => this.now(),
1139
- shouldStopRun: (runId) => this.runControlIntents.has(runId) || this.runDeadlines.has(runId),
1140
- runDeadlineExceeded: (runId) => this.runDeadlines.has(runId),
1141
- registerRunAbort: (runId, controller) => this.registerRunAbort(runId, controller),
1142
- maxRunDurationMs: this.maxRunDurationMs,
1129
+ shouldStopRun: (runId) => this.runProgress.shouldStop(runId),
1130
+ runStalled: (runId) => this.runProgress.isStalled(runId),
1131
+ registerRunAbort: (runId, controller) => this.runProgress.registerAbort(runId, controller),
1132
+ runStallTimeoutMs: this.runStallTimeoutMs,
1143
1133
  runEventBus: this.runEventBus,
1144
1134
  connectionAuthorizationSessionStore: this.connectionAuthorizationSessionStore,
1145
1135
  get defaultAgentHarness() {
@@ -1411,7 +1401,7 @@ export class AssemblyLineRuntime extends WorkspaceRuntimeMethods {
1411
1401
  state: this.state,
1412
1402
  boot: () => this.boot(),
1413
1403
  durableAgentScope: () => this.durableAgentScope(),
1414
- cacheRunControl: (runId, intent) => cacheRunControlSignal(this.runControlIntents, this.activeRunExecutions, this.runAbortControllers, runId, intent)
1404
+ cacheRunControl: (runId, intent) => this.runProgress.cacheControl(runId, intent)
1415
1405
  };
1416
1406
  }
1417
1407
  async resumeHarnessLoop(run, harness, restored, resume) {
@@ -1420,6 +1410,7 @@ export class AssemblyLineRuntime extends WorkspaceRuntimeMethods {
1420
1410
  subagentExecutionHost() {
1421
1411
  return {
1422
1412
  state: this.state,
1413
+ blob: this.blob,
1423
1414
  manifest: this.manifest,
1424
1415
  sourceLoader: this.sourceLoader,
1425
1416
  resolveAgentHarness: (modelSpec) => this.resolveAgentHarness(modelSpec),
@@ -1440,6 +1431,29 @@ export class AssemblyLineRuntime extends WorkspaceRuntimeMethods {
1440
1431
  async runSubagent(parentRunId, subagentName, task) {
1441
1432
  return runSubagentImpl(this.subagentExecutionHost(), parentRunId, subagentName, task);
1442
1433
  }
1434
+ async queueSubagent(parentRunId, subagentName, task) {
1435
+ const child = await createSubagentRunImpl(this.subagentExecutionHost(), parentRunId, subagentName, task, { background: true });
1436
+ this.backgroundSubagents.requestDrain();
1437
+ return child;
1438
+ }
1439
+ async listBackgroundWork(rootRunId, includeTerminal = false) {
1440
+ const root = await this.state.getRun(rootRunId);
1441
+ if (!root)
1442
+ throw new Error(`Run not found: ${rootRunId}`);
1443
+ return this.backgroundSubagents.listForRoot(root, includeTerminal);
1444
+ }
1445
+ async getBackgroundWork(rootRunId, childRunId) {
1446
+ const root = await this.state.getRun(rootRunId);
1447
+ if (!root)
1448
+ throw new Error(`Run not found: ${rootRunId}`);
1449
+ return this.backgroundSubagents.getForRoot(root, childRunId);
1450
+ }
1451
+ async cancelBackgroundWork(rootRunId, childRunId) {
1452
+ const root = await this.state.getRun(rootRunId);
1453
+ if (!root)
1454
+ throw new Error(`Run not found: ${rootRunId}`);
1455
+ return this.backgroundSubagents.cancelForRoot(root, childRunId);
1456
+ }
1443
1457
  async readSubagent(childRunId, cursor) {
1444
1458
  return readSubagentImpl(this.subagentExecutionHost(), childRunId, cursor);
1445
1459
  }
@@ -1580,7 +1594,7 @@ export class AssemblyLineRuntime extends WorkspaceRuntimeMethods {
1580
1594
  async resumeRunFromContinuation(run, context) {
1581
1595
  return resumeRunFromContinuationImpl(this.replayResumeHost(), run, context);
1582
1596
  }
1583
- /** Start delivery, sandbox-sync, conversation-turn, background-review, and orphan-recovery workers. */
1597
+ /** Start delivery, sandbox-sync, conversation-turn, subagent, background-review, and orphan-recovery workers. */
1584
1598
  startBackgroundWorkers(options = {}) {
1585
1599
  return startBackgroundWorkersImpl({
1586
1600
  env: this.env,
@@ -1589,6 +1603,7 @@ export class AssemblyLineRuntime extends WorkspaceRuntimeMethods {
1589
1603
  startDeliveryWorker: (workerOptions) => this.startDeliveryWorker(workerOptions),
1590
1604
  startSandboxSyncWorker: (workerOptions) => this.startSandboxSyncWorker(workerOptions),
1591
1605
  startConversationTurnWorker: (workerOptions) => this.conversationTurnDispatcher.startWorker(workerOptions?.intervalMs),
1606
+ startBackgroundSubagentWorker: (workerOptions) => this.backgroundSubagents.startWorker(workerOptions?.intervalMs),
1592
1607
  startBackgroundReviewWorker: (workerOptions) => this.startBackgroundReviewWorker(workerOptions),
1593
1608
  startConnectionEventWorker: (workerOptions) => this.startConnectionEventWorker(workerOptions),
1594
1609
  recoverIncompleteRuns: (recoveryOptions) => this.recoverIncompleteRuns(recoveryOptions),
@@ -1870,6 +1885,9 @@ export class AssemblyLineRuntime extends WorkspaceRuntimeMethods {
1870
1885
  connectionsConfig: this.connectionsConfig,
1871
1886
  surfaceForRun: (run) => surfaceForRun(this.manifest, run),
1872
1887
  agentStateForRun: (runId) => this.agentStateForRun(runId),
1888
+ reportRunProgress: async (runId, data = {}) => {
1889
+ await this.state.appendEvent(runId, "run.progress_reported", data);
1890
+ },
1873
1891
  ensureSkillsSeeded: (run) => this.ensureSkillsSeeded(surfaceForRun(this.manifest, run).path),
1874
1892
  skillScopeForRun: (run) => this.skillScopeForRun(run),
1875
1893
  scheduleScopeForRun: (run) => this.scheduleScopeForRun(run),
@@ -1879,6 +1897,10 @@ export class AssemblyLineRuntime extends WorkspaceRuntimeMethods {
1879
1897
  loadConnectionDefinition: (connection) => this.loadConnectionDefinition(connection),
1880
1898
  loadTool: (tool, run) => this.loadTool(tool, run),
1881
1899
  runSubagent: (parentRunId, subagentName, task) => this.runSubagent(parentRunId, subagentName, task),
1900
+ queueSubagent: (parentRunId, subagentName, task) => this.queueSubagent(parentRunId, subagentName, task),
1901
+ listBackgroundWork: (rootRunId, includeTerminal) => this.listBackgroundWork(rootRunId, includeTerminal),
1902
+ getBackgroundWork: (rootRunId, childRunId) => this.getBackgroundWork(rootRunId, childRunId),
1903
+ cancelBackgroundWork: (rootRunId, childRunId) => this.cancelBackgroundWork(rootRunId, childRunId),
1882
1904
  projectSkillIntoSandbox: (run, sandbox, skillName) => this.projectSkillIntoSandbox(run, sandbox, skillName),
1883
1905
  hydrateLogicalPathForTool: (run, sandbox, path, options) => this.hydrateLogicalPathForTool(run, sandbox, path, options),
1884
1906
  recordSandboxTombstone: (run, sandbox, path) => this.recordSandboxTombstone(run, sandbox, path),
@@ -1908,10 +1930,24 @@ export class AssemblyLineRuntime extends WorkspaceRuntimeMethods {
1908
1930
  return selectToolImpl(this.toolExecutionHost(), options);
1909
1931
  }
1910
1932
  async executeTool(run, tool, input, sandboxHandle, approved, idempotencyKey) {
1911
- return executeToolImpl(this.toolExecutionHost(), run, tool, input, sandboxHandle, approved, idempotencyKey);
1933
+ const abortController = new AbortController();
1934
+ const unregisterAbort = this.runProgress.registerAbort(run.id, abortController);
1935
+ try {
1936
+ return await executeToolImpl(this.toolExecutionHost(), run, tool, input, sandboxHandle, approved, idempotencyKey, abortController.signal);
1937
+ }
1938
+ finally {
1939
+ unregisterAbort();
1940
+ }
1912
1941
  }
1913
1942
  async executeToolWithRecord(run, tool, toolCall, input, sandboxHandle, approvedToolCall = false) {
1914
- return executeToolWithRecordImpl(this.toolExecutionHost(), run, tool, toolCall, input, sandboxHandle, approvedToolCall);
1943
+ const abortController = new AbortController();
1944
+ const unregisterAbort = this.runProgress.registerAbort(run.id, abortController);
1945
+ try {
1946
+ return await executeToolWithRecordImpl(this.toolExecutionHost(), run, tool, toolCall, input, sandboxHandle, approvedToolCall, abortController.signal);
1947
+ }
1948
+ finally {
1949
+ unregisterAbort();
1950
+ }
1915
1951
  }
1916
1952
  async durableStep(runId, key, fn, options) {
1917
1953
  return durableStepImpl(this.toolExecutionHost(), runId, key, fn, options);
@@ -1980,94 +2016,38 @@ export class AssemblyLineRuntime extends WorkspaceRuntimeMethods {
1980
2016
  removeTerminalAgentHookState(runId, event, this.eventHandlers, this.agentHookControllers, this.agentStateHandles);
1981
2017
  }
1982
2018
  async handleRuntimeEvent(runId, event) {
2019
+ if (eventRenewsRunProgress(event.type))
2020
+ await this.runProgress.renew(runId);
1983
2021
  await this.dispatchHookEvent(runId, event);
1984
2022
  await this.runtimeLearning.handleRuntimeEvent(runId, event.type);
1985
2023
  }
1986
2024
  async createAgentHookController(run, options, channel, conversationId, attachments) {
1987
- const disabledTools = this.disabledToolsForRun(run);
1988
- const surface = rootAgentSurface(this.manifest);
1989
- const liveSkills = (await this.listSkills(surface.path)).map((skill) => skill.name).sort();
1990
- const baseCatalog = hookCapabilityCatalog(this.manifest, surface);
1991
- const created = await createRuntimeAgentHookController({
1992
- manifest: this.manifest,
1993
- state: this.state,
1994
- blob: this.blob,
1995
- sourceLoader: this.sourceLoader,
1996
- eventHandlers: this.eventHandlers,
1997
- logger: this.logger,
1998
- agentScope: this.durableAgentScope(),
1999
- conversationId,
2000
- run: immutableAgentRunContext({
2001
- id: run.id,
2002
- message: options.message,
2003
- channel,
2004
- conversationId,
2005
- ...(typeof run.input.userId === "string" ? { userId: run.input.userId } : {}),
2006
- ...(principalFromRun(run) ? { principal: principalFromRun(run) } : {}),
2007
- ...(initiatorFromRun(run) ? { initiator: initiatorFromRun(run) } : {}),
2008
- audience: audienceFromRun(run),
2009
- ...((options.metadata ?? options.turn?.metadata) ? {
2010
- metadata: (options.metadata ?? options.turn?.metadata)
2011
- } : {}),
2012
- attachments
2013
- }),
2014
- surface,
2015
- catalog: { ...baseCatalog, skills: new Set(liveSkills) },
2016
- defaultSkills: liveSkills,
2017
- disabledTools,
2018
- createCheckpoint: (input) => this.createCheckpoint(input),
2019
- listCheckpoints: (runId) => this.listHydratedCheckpoints(runId)
2020
- });
2021
- this.agentStateHandles.set(run.id, created.agentState);
2022
- this.agentHookControllers.set(run.id, created.controller);
2023
- return created.controller;
2025
+ const created = await createMainAgentHookControllerImpl(this.agentHookControllerFactoryHost(), run, options, channel, conversationId, attachments);
2026
+ return this.rememberAgentHookController(run.id, created);
2024
2027
  }
2025
2028
  async createSubagentHookController(run, subagent, message, conversationId) {
2026
- const surface = subagentSurface(subagent);
2027
- const liveSkills = (await this.listSkills(surface.path)).map((skill) => skill.name).sort();
2028
- const baseCatalog = hookCapabilityCatalog(this.manifest, surface);
2029
- const created = await createRuntimeAgentHookController({
2029
+ const created = await createSubagentHookControllerImpl(this.agentHookControllerFactoryHost(), run, subagent, message, conversationId);
2030
+ return this.rememberAgentHookController(run.id, created);
2031
+ }
2032
+ agentHookControllerFactoryHost() {
2033
+ return {
2030
2034
  manifest: this.manifest,
2031
2035
  state: this.state,
2032
2036
  blob: this.blob,
2033
2037
  sourceLoader: this.sourceLoader,
2034
2038
  eventHandlers: this.eventHandlers,
2035
2039
  logger: this.logger,
2036
- agentScope: `${this.durableAgentScope()}:subagent:${subagent.path}`,
2037
- conversationId,
2038
- agentSource: subagent.agentSource,
2039
- surface,
2040
- catalog: {
2041
- ...baseCatalog,
2042
- skills: new Set(liveSkills),
2043
- models: new Set(subagent.possibleModels),
2044
- connections: new Set(subagent.connections),
2045
- ...(subagent.maxReasoning !== undefined ? { maxReasoning: subagent.maxReasoning } : {})
2046
- },
2047
- defaultSkills: liveSkills,
2048
- run: immutableAgentRunContext({
2049
- id: run.id,
2050
- message,
2051
- channel: "subagent",
2052
- conversationId,
2053
- ...(typeof run.input.userId === "string" ? { userId: run.input.userId } : {}),
2054
- ...(principalFromRun(run) ? { principal: principalFromRun(run) } : {}),
2055
- ...(initiatorFromRun(run) ? { initiator: initiatorFromRun(run) } : {}),
2056
- audience: audienceFromRun(run),
2057
- ...(isJsonObject(run.input.metadata) ? { metadata: run.input.metadata } : {}),
2058
- attachments: []
2059
- }),
2060
- disabledTools: this.disabledToolsForRun(run),
2040
+ durableAgentScope: () => this.durableAgentScope(),
2041
+ listSkillNames: async (surfacePath) => (await this.listSkills(surfacePath)).map((skill) => skill.name).sort(),
2042
+ disabledToolsForRun: () => new Set(Object.entries(this.coreToolPolicy).filter(([, mode]) => mode === "disabled").map(([name]) => name)),
2061
2043
  createCheckpoint: (input) => this.createCheckpoint(input),
2062
2044
  listCheckpoints: (runId) => this.listHydratedCheckpoints(runId)
2063
- });
2064
- this.agentStateHandles.set(run.id, created.agentState);
2065
- this.agentHookControllers.set(run.id, created.controller);
2066
- return created.controller;
2045
+ };
2067
2046
  }
2068
- disabledToolsForRun(run) {
2069
- const disabledTools = new Set(Object.entries(this.coreToolPolicy).filter(([, mode]) => mode === "disabled").map(([name]) => name));
2070
- return disabledTools;
2047
+ rememberAgentHookController(runId, created) {
2048
+ this.agentStateHandles.set(runId, created.agentState);
2049
+ this.agentHookControllers.set(runId, created.controller);
2050
+ return created.controller;
2071
2051
  }
2072
2052
  agentStateForRun(runId) {
2073
2053
  const handle = this.agentStateHandles.get(runId);