@agentproto/workflow-runtime 0.16.0 → 0.17.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.
package/dist/index.d.ts CHANGED
@@ -110,6 +110,19 @@ interface TolerantFanOutResult<T = unknown> {
110
110
  /** Default for {@link MapStep.maxConsecutiveSpawnFailures} /
111
111
  * {@link PipelineStep.maxConsecutiveSpawnFailures}. */
112
112
  declare const DEFAULT_MAX_CONSECUTIVE_SPAWN_FAILURES = 3;
113
+ /** An {@link AgentStep}'s transport-failure retry policy — the same shape as
114
+ * {@link GateStep.retry}. `initialMs` is the wait before the first retry;
115
+ * `"exponential"` doubles it on each further one. */
116
+ interface AgentStepRetry {
117
+ maxAttempts: number;
118
+ backoff: "fixed" | "exponential";
119
+ initialMs?: number;
120
+ }
121
+ /** What an {@link AgentStep} without its own `retry` gets: two attempts —
122
+ * one retry after a 1 s pause — so a single dropped connection doesn't fail
123
+ * a long run. Applies to a session lost mid-turn only, never to a spawn
124
+ * failure or a completed turn (see {@link AgentStep.retry}). */
125
+ declare const DEFAULT_AGENT_TRANSPORT_RETRY: Readonly<AgentStepRetry>;
113
126
  /** Default for {@link ToolStep.timeoutMs} / {@link GateStep.timeoutMs} when
114
127
  * the step declares none — 10 minutes. Bounds an otherwise-unbounded `tool`
115
128
  * or `gate` step dispatch (F45). */
@@ -404,6 +417,22 @@ interface AgentStep {
404
417
  outputSchema?: OutputSchemaLike;
405
418
  /** Re-prompt-and-retry attempts on schema mismatch before failing. Default 2. */
406
419
  maxRetries?: number;
420
+ /** Re-spawn on a TRANSPORT failure, up to `maxAttempts` total attempts —
421
+ * the same shape as {@link GateStep.retry}. Only a failure that is the
422
+ * session's plumbing, never its content, is retried: the spawn rejected
423
+ * ({@link AgentSpawnError}), or the session died before its FIRST turn
424
+ * ended (the host threw an error marked `transport: true`, e.g.
425
+ * {@link AgentSessionLostError} for a killed session or a closed ACP
426
+ * connection). A step whose turn completed — including one that then
427
+ * failed its `outputSchema` re-prompts, or died during one — is never
428
+ * retried, nor is a cancelled run or a `sessionRef` reuse (there is no
429
+ * fresh session to spawn). Each retry spawns a NEW session with the same
430
+ * resolved prompt, cwd and harness, in the same run workspace.
431
+ * Unset ⇒ {@link DEFAULT_AGENT_TRANSPORT_RETRY} (one retry), and then
432
+ * for a session lost mid-turn ONLY — a spawn failure is usually
433
+ * deterministic (bad cwd, missing adapter), so it is re-spawned past only
434
+ * when the step declares `retry` itself. `maxAttempts: 1` disables it. */
435
+ retry?: AgentStepRetry;
407
436
  /** Cache this step's output under the run's cacheKey; the resolved prompt +
408
437
  * adapter + model + sessionRef are hashed. Default false — most agent steps have
409
438
  * side effects. */
@@ -555,6 +584,20 @@ interface GateReportEvent {
555
584
  report: unknown;
556
585
  attempt: number;
557
586
  }
587
+ /** One transport-failure retry of a `kind: "agent"` step, reported right
588
+ * before the fresh session is spawned — the `step.retrying` event. */
589
+ interface AgentRetryEvent {
590
+ stepId: string;
591
+ /** The attempt that is about to start (2 for the first retry). */
592
+ attempt: number;
593
+ maxAttempts: number;
594
+ /** The failed attempt's error message. */
595
+ error: string;
596
+ /** The failed attempt's session, when it got far enough to have one. */
597
+ sessionId?: string;
598
+ /** How long the runtime waits before spawning the next attempt. */
599
+ delayMs: number;
600
+ }
558
601
  /** What a declarative agent-step's `agent.ref` resolves to — the adapter to
559
602
  * spawn under plus any adapter options that pick which concrete agent runs
560
603
  * (e.g. mastra-agent's `agent` option, an emitted AGENT.md path). Built by
@@ -616,6 +659,14 @@ interface RuntimeWorkflow {
616
659
  * every top-level step finishes successfully. See {@link OutputsFileContract}
617
660
  * and `run-workflow.ts`'s `checkOutputsFiles`. */
618
661
  outputsFiles?: Readonly<Record<string, OutputsFileContract>>;
662
+ /**
663
+ * `false` ⇒ no step anywhere in this workflow reuses another step's session
664
+ * (`sessionRef`), so each top-level step's sessions are released as soon as
665
+ * that step finishes instead of being held until the run ends (a run parked
666
+ * on an approval would otherwise keep every finished step's session live).
667
+ * Omitted/`true` ⇒ the conservative default: release at run end.
668
+ */
669
+ reusesSessions?: boolean;
619
670
  }
620
671
  /** A human/host decision on one approval request. `who` records WHO decided
621
672
  * ("human", "timeout", "cancelled", …); `note` is optional free text. */
@@ -847,6 +898,9 @@ interface RunWorkflowArgs {
847
898
  /** Called once per `kind: "gate"` command attempt (every attempt, not just
848
899
  * the last) — the `gate-report` lifecycle event. */
849
900
  onGateReport?: (ev: GateReportEvent) => void;
901
+ /** Called before each transport-failure retry of a `kind: "agent"` step
902
+ * (see {@link AgentStep.retry}) — never for the first attempt. */
903
+ onAgentRetry?: (ev: AgentRetryEvent) => void;
850
904
  }
851
905
  interface WorkflowRunResult {
852
906
  output: unknown;
@@ -921,6 +975,28 @@ declare class AgentSpawnError extends Error {
921
975
  readonly cause: unknown;
922
976
  constructor(stepId: string, cause: unknown);
923
977
  }
978
+ /**
979
+ * Thrown by an {@link AgentSessionHost} when a spawned session died under a
980
+ * step before its turn ended — killed or crashed mid-turn, its ACP
981
+ * connection closed, its prompt never delivered — as opposed to a turn that
982
+ * ended and produced the wrong thing. The `transport: true` marker (not the
983
+ * class identity) is what {@link isAgentTransportFailure} reads, so a host
984
+ * bundling its own copy of this package still classifies correctly.
985
+ */
986
+ declare class AgentSessionLostError extends Error {
987
+ readonly sessionId: string;
988
+ readonly cause?: unknown | undefined;
989
+ readonly transport: true;
990
+ constructor(sessionId: string, message: string, cause?: unknown | undefined);
991
+ }
992
+ /**
993
+ * True when `err` is a transport failure an {@link AgentStep}'s `retry` may
994
+ * re-spawn past: an {@link AgentSpawnError}, or any error carrying
995
+ * `transport: true` (see {@link AgentSessionLostError}). Everything else —
996
+ * an empty or errored turn, a schema mismatch, a budget cap — is the step's
997
+ * own outcome and is never retried.
998
+ */
999
+ declare function isAgentTransportFailure(err: unknown): boolean;
924
1000
  /**
925
1001
  * Thrown instead of dispatching a step whose run was cancelled
926
1002
  * (`ctx.signal.aborted`) — checked BEFORE any work for the step begins (see
@@ -1155,6 +1231,8 @@ interface AgentStepFields {
1155
1231
  policy?: AgentStep["policy"];
1156
1232
  outputSchema?: AgentStep["outputSchema"];
1157
1233
  maxRetries?: number;
1234
+ /** See {@link AgentStep.retry}. */
1235
+ retry?: AgentStep["retry"];
1158
1236
  options?: Record<string, boolean | number | string>;
1159
1237
  harness?: AgentHarness;
1160
1238
  agentTools?: readonly string[];
@@ -1215,4 +1293,4 @@ declare class NodeFsPort implements FsPort {
1215
1293
  lock(): Promise<FsLockHandle>;
1216
1294
  }
1217
1295
 
1218
- export { type AgentHarness, AgentInputRequiredError, type AgentRefResolution, type AgentSandboxRef, type AgentSessionHost, AgentSpawnError, type AgentStep, type AgentStepFields, type ApprovalDecision, type ApprovalRequest, type ApprovalStep, type ArtifactEntry, type ArtifactStep, type Bindings, type BranchStep, type CompileWorkflowOptions, DEFAULT_MAX_CONSECUTIVE_SPAWN_FAILURES, DEFAULT_STEP_TIMEOUT_MS, type FanOutOutcome, type GateCommandResult, type GateCommandRunner, type GateReportEvent, type GateStep, type GroupStep, type HarnessKnowledgeSelector, type InputRequiredRequest, type KnowledgeAppliedRecord, type LoopStep, type MapStep, type MaterializedKnowledge, MissingArtifactError, NodeFsPort, type OutputSchemaLike, type OutputsFileContract, type ParallelStep, type PipelineStep, type ResumeRequest, type RunStep, type RunWorkflowArgs, type RuntimeWorkflow, type SchemaValidationIssue, type Selector, type StepCache, type StepCacheEntry, type StepFailedInfo, type StepHookInfo, StepOutcomeError, type StepSkippedInfo, type SubworkflowStep, type SuspendStep, type TolerantFanOutResult, type ToolStep, type TransformStep, WorkflowCancelledError, WorkflowCompileError, type WorkflowInputValidation, type WorkflowRunResult, WorkflowSuspendedError, buildAgentStep, compileWorkflow, compileWorkflowManifest, evalPredicate, interpolateTemplate, isCompilableJsonSchema, materializeKnowledge, normalizeWorkflowInputsSchema, resolveKnowledgeSelectors, resolveRef, resolveRefPrefixed, resolveValue, runWorkflow, validateAgainstJsonSchema, validateWorkflowInput };
1296
+ export { type AgentHarness, AgentInputRequiredError, type AgentRefResolution, type AgentRetryEvent, type AgentSandboxRef, type AgentSessionHost, AgentSessionLostError, AgentSpawnError, type AgentStep, type AgentStepFields, type AgentStepRetry, type ApprovalDecision, type ApprovalRequest, type ApprovalStep, type ArtifactEntry, type ArtifactStep, type Bindings, type BranchStep, type CompileWorkflowOptions, DEFAULT_AGENT_TRANSPORT_RETRY, DEFAULT_MAX_CONSECUTIVE_SPAWN_FAILURES, DEFAULT_STEP_TIMEOUT_MS, type FanOutOutcome, type GateCommandResult, type GateCommandRunner, type GateReportEvent, type GateStep, type GroupStep, type HarnessKnowledgeSelector, type InputRequiredRequest, type KnowledgeAppliedRecord, type LoopStep, type MapStep, type MaterializedKnowledge, MissingArtifactError, NodeFsPort, type OutputSchemaLike, type OutputsFileContract, type ParallelStep, type PipelineStep, type ResumeRequest, type RunStep, type RunWorkflowArgs, type RuntimeWorkflow, type SchemaValidationIssue, type Selector, type StepCache, type StepCacheEntry, type StepFailedInfo, type StepHookInfo, StepOutcomeError, type StepSkippedInfo, type SubworkflowStep, type SuspendStep, type TolerantFanOutResult, type ToolStep, type TransformStep, WorkflowCancelledError, WorkflowCompileError, type WorkflowInputValidation, type WorkflowRunResult, WorkflowSuspendedError, buildAgentStep, compileWorkflow, compileWorkflowManifest, evalPredicate, interpolateTemplate, isAgentTransportFailure, isCompilableJsonSchema, materializeKnowledge, normalizeWorkflowInputsSchema, resolveKnowledgeSelectors, resolveRef, resolveRefPrefixed, resolveValue, runWorkflow, validateAgainstJsonSchema, validateWorkflowInput };
package/dist/index.mjs CHANGED
@@ -33,6 +33,7 @@ function buildAgentStep(id, fields) {
33
33
  policy: fields.policy ?? { awaiting: "fail" },
34
34
  ...fields.outputSchema !== void 0 ? { outputSchema: fields.outputSchema } : {},
35
35
  ...fields.maxRetries !== void 0 ? { maxRetries: fields.maxRetries } : {},
36
+ ...fields.retry !== void 0 ? { retry: fields.retry } : {},
36
37
  ...fields.harness !== void 0 ? { harness: fields.harness } : {},
37
38
  ...fields.agentTools !== void 0 ? { agentTools: fields.agentTools } : {}
38
39
  };
@@ -355,6 +356,16 @@ function collectStepIds(steps, ids = /* @__PURE__ */ new Set()) {
355
356
  }
356
357
  return ids;
357
358
  }
359
+ function usesSessionRef(steps) {
360
+ for (const s of steps) {
361
+ if (s?.sessionRef !== void 0 || s?.kind === "subworkflow") return true;
362
+ if (Array.isArray(s?.steps) && usesSessionRef(s.steps)) return true;
363
+ if (Array.isArray(s?.branches)) {
364
+ for (const br of s.branches) if (Array.isArray(br?.steps) && usesSessionRef(br.steps)) return true;
365
+ }
366
+ }
367
+ return false;
368
+ }
358
369
  function compileWorkflow(handle, opts) {
359
370
  const steps = handle.steps;
360
371
  if (handle.start && handle.start !== steps[0]?.id) {
@@ -374,7 +385,8 @@ function compileWorkflow(handle, opts) {
374
385
  steps: compileSiblingsToSteps(steps, ctx),
375
386
  ...finallySteps?.length ? { finally: compileSiblingsToSteps(finallySteps, finallyCtx) } : {},
376
387
  ...output ? { output } : {},
377
- ...outputsFiles ? { outputsFiles } : {}
388
+ ...outputsFiles ? { outputsFiles } : {},
389
+ ...usesSessionRef([...steps, ...finallySteps ?? []]) ? {} : { reusesSessions: false }
378
390
  };
379
391
  }
380
392
  function compileSiblingsToSteps(steps, ctx) {
@@ -548,6 +560,7 @@ function compileAgentStep(step, id, ctx) {
548
560
  }
549
561
  const rawCwd = step.cwd;
550
562
  const cwd = typeof rawCwd === "function" ? rawCwd : typeof rawCwd === "string" ? (b) => resolveRefString(id, "cwd", rawCwd, b, "error") : void 0;
563
+ const retry = compileAgentRetry(step.retry, id);
551
564
  return buildAgentStep(id, {
552
565
  prompt: (b) => renderPrompt(prompt, b),
553
566
  ...adapter !== void 0 ? { adapter } : {},
@@ -560,10 +573,31 @@ function compileAgentStep(step, id, ctx) {
560
573
  policy: step.policy,
561
574
  ...outputSchema !== void 0 ? { outputSchema } : {},
562
575
  ...step.maxRetries !== void 0 ? { maxRetries: step.maxRetries } : {},
576
+ ...retry !== void 0 ? { retry } : {},
563
577
  ...step.harness !== void 0 ? { harness: step.harness } : {},
564
578
  ...agentTools !== void 0 ? { agentTools } : {}
565
579
  });
566
580
  }
581
+ function compileAgentRetry(raw, id) {
582
+ if (raw === void 0) return void 0;
583
+ if (typeof raw !== "object" || raw === null) {
584
+ throw new WorkflowCompileError(`agent step '${id}': 'retry' must be an object { max_attempts, backoff?, initial_ms? }`);
585
+ }
586
+ const r = raw;
587
+ const maxAttempts = r.max_attempts ?? r.maxAttempts;
588
+ if (typeof maxAttempts !== "number" || !Number.isInteger(maxAttempts) || maxAttempts < 1 || maxAttempts > 20) {
589
+ throw new WorkflowCompileError(`agent step '${id}': 'retry.max_attempts' must be an integer between 1 and 20`);
590
+ }
591
+ const backoff = r.backoff ?? "fixed";
592
+ if (backoff !== "fixed" && backoff !== "exponential") {
593
+ throw new WorkflowCompileError(`agent step '${id}': 'retry.backoff' must be "fixed" or "exponential"`);
594
+ }
595
+ const initialMs = r.initial_ms ?? r.initialMs;
596
+ if (initialMs !== void 0 && (typeof initialMs !== "number" || !Number.isInteger(initialMs) || initialMs < 0)) {
597
+ throw new WorkflowCompileError(`agent step '${id}': 'retry.initial_ms' must be a non-negative integer`);
598
+ }
599
+ return { maxAttempts, backoff, ...initialMs !== void 0 ? { initialMs } : {} };
600
+ }
567
601
  function compileGateStep(step, id) {
568
602
  const command = f(step, "command");
569
603
  if (typeof command !== "string" || command.trim().length === 0) {
@@ -784,6 +818,11 @@ function resolveRefString(stepId, field, value, b, onUnmatched = "passthrough")
784
818
 
785
819
  // src/types.ts
786
820
  var DEFAULT_MAX_CONSECUTIVE_SPAWN_FAILURES = 3;
821
+ var DEFAULT_AGENT_TRANSPORT_RETRY = Object.freeze({
822
+ maxAttempts: 2,
823
+ backoff: "fixed",
824
+ initialMs: 1e3
825
+ });
787
826
  var DEFAULT_STEP_TIMEOUT_MS = 10 * 60 * 1e3;
788
827
  var NodeFsPort = class {
789
828
  root;
@@ -1026,6 +1065,23 @@ var AgentSpawnError = class extends Error {
1026
1065
  stepId;
1027
1066
  cause;
1028
1067
  };
1068
+ var AgentSessionLostError = class extends Error {
1069
+ constructor(sessionId, message, cause) {
1070
+ super(message);
1071
+ this.sessionId = sessionId;
1072
+ this.cause = cause;
1073
+ this.name = "AgentSessionLostError";
1074
+ }
1075
+ sessionId;
1076
+ cause;
1077
+ transport = true;
1078
+ };
1079
+ function isAgentTransportFailure(err) {
1080
+ return err instanceof AgentSpawnError || isAgentSessionLost(err);
1081
+ }
1082
+ function isAgentSessionLost(err) {
1083
+ return typeof err === "object" && err !== null && err.transport === true;
1084
+ }
1029
1085
  var WorkflowCancelledError = class extends Error {
1030
1086
  constructor(stepId) {
1031
1087
  super(`step '${stepId}': run cancelled \u2014 not started`);
@@ -1055,7 +1111,7 @@ function view(ctx, item, index) {
1055
1111
  }
1056
1112
  function withIndexedHooks(ctx, index) {
1057
1113
  const cacheKeySuffix = `${ctx.cacheKeySuffix ?? ""}[${index}]`;
1058
- if (!ctx.onStepStart && !ctx.onStepComplete && !ctx.onStepSkipped && !ctx.onStepFailed) {
1114
+ if (!ctx.onStepStart && !ctx.onStepComplete && !ctx.onStepSkipped && !ctx.onStepFailed && !ctx.onAgentRetry) {
1059
1115
  return { ...ctx, cacheKeySuffix };
1060
1116
  }
1061
1117
  return {
@@ -1064,7 +1120,8 @@ function withIndexedHooks(ctx, index) {
1064
1120
  onStepStart: ctx.onStepStart ? (id, info) => ctx.onStepStart(`${id}[${index}]`, info) : void 0,
1065
1121
  onStepComplete: ctx.onStepComplete ? (id, out, info) => ctx.onStepComplete(`${id}[${index}]`, out, info) : void 0,
1066
1122
  onStepSkipped: ctx.onStepSkipped ? (id, info) => ctx.onStepSkipped(`${id}[${index}]`, info) : void 0,
1067
- onStepFailed: ctx.onStepFailed ? (id, info) => ctx.onStepFailed(`${id}[${index}]`, info) : void 0
1123
+ onStepFailed: ctx.onStepFailed ? (id, info) => ctx.onStepFailed(`${id}[${index}]`, info) : void 0,
1124
+ onAgentRetry: ctx.onAgentRetry ? (ev) => ctx.onAgentRetry({ ...ev, stepId: `${ev.stepId}[${index}]` }) : void 0
1068
1125
  };
1069
1126
  }
1070
1127
  var failureOrigin = /* @__PURE__ */ new WeakMap();
@@ -1299,25 +1356,90 @@ async function runSequence(steps, ctx, item, index) {
1299
1356
  return last;
1300
1357
  }
1301
1358
  async function sendPromptAndAwaitOutcome(ctx, step, sessionId, prompt) {
1302
- let next = prompt;
1359
+ await ctx.agents.sendPromptAndWait(sessionId, prompt);
1360
+ await settleInputRequests(ctx, step, sessionId);
1361
+ }
1362
+ async function settleInputRequests(ctx, step, sessionId) {
1303
1363
  for (; ; ) {
1304
- await ctx.agents.sendPromptAndWait(sessionId, next);
1305
1364
  const req = ctx.agents.takeInputRequest?.(sessionId);
1306
1365
  if (!req) return;
1307
1366
  if (!ctx.onInputRequired) {
1308
1367
  throw new AgentInputRequiredError(step.id, req.prompt, req.schema);
1309
1368
  }
1310
1369
  const payload = await ctx.onInputRequired({ stepId: step.id, prompt: req.prompt, schema: req.schema });
1311
- next = JSON.stringify(payload);
1370
+ await ctx.agents.sendPromptAndWait(sessionId, JSON.stringify(payload));
1371
+ }
1372
+ }
1373
+ function agentRetryDelayMs(retry, attempt) {
1374
+ if (!retry.initialMs) return 0;
1375
+ return retry.backoff === "exponential" ? retry.initialMs * 2 ** (attempt - 2) : retry.initialMs;
1376
+ }
1377
+ function sleepUnlessAborted(ms, signal) {
1378
+ if (ms <= 0 || signal?.aborted) return Promise.resolve();
1379
+ return new Promise((resolve2) => {
1380
+ const onAbort = () => {
1381
+ clearTimeout(timer);
1382
+ resolve2();
1383
+ };
1384
+ const timer = setTimeout(() => {
1385
+ signal?.removeEventListener("abort", onAbort);
1386
+ resolve2();
1387
+ }, ms);
1388
+ signal?.addEventListener("abort", onAbort, { once: true });
1389
+ });
1390
+ }
1391
+ async function spawnOrResolveSession(step, ctx, b, spawnOpts) {
1392
+ let sessionId;
1393
+ if (step.adapter) {
1394
+ if (ctx.signal?.aborted) throw new WorkflowCancelledError(step.id);
1395
+ const { cwd, sandbox, harness } = spawnOpts;
1396
+ try {
1397
+ sessionId = await ctx.agents.spawn(resolveSel(step.adapter, b), {
1398
+ cwd,
1399
+ workspaceSlug: ctx.workspaceSlug,
1400
+ stepId: step.id,
1401
+ ...sandbox !== void 0 ? { sandbox } : {},
1402
+ ...step.options !== void 0 ? { options: step.options } : {},
1403
+ ...harness !== void 0 ? { harness } : {},
1404
+ ...step.agentTools !== void 0 ? { agentTools: step.agentTools } : {},
1405
+ ...b.index !== void 0 ? { stepKey: `${step.id}[${b.index}]` } : {}
1406
+ });
1407
+ } catch (err) {
1408
+ throw new AgentSpawnError(step.id, err);
1409
+ }
1410
+ } else {
1411
+ sessionId = ctx.agents.resolveByLabel(resolveSessionRef(step.sessionRef, b));
1412
+ }
1413
+ if (!sessionId) throw new Error(`step '${step.id}': no session (adapter and sessionRef both unresolved)`);
1414
+ return sessionId;
1415
+ }
1416
+ async function retireLostSession(ctx, sessionId) {
1417
+ const agents = ctx.agents;
1418
+ if (agents.readCostUsd) {
1419
+ try {
1420
+ ctx.state.costBySession.set(sessionId, await agents.readCostUsd(sessionId));
1421
+ } catch {
1422
+ }
1423
+ }
1424
+ const idx = ctx.spawned?.indexOf(sessionId) ?? -1;
1425
+ if (idx >= 0) ctx.spawned.splice(idx, 1);
1426
+ if (agents.releaseSession) {
1427
+ try {
1428
+ await agents.releaseSession(sessionId);
1429
+ } catch {
1430
+ }
1312
1431
  }
1313
1432
  }
1314
1433
  async function execAgentStep(step, ctx, b) {
1315
1434
  ctx.onStepStart?.(step.id);
1316
- if (step.adapter && ctx.state.maxTotalCostUsd !== void 0 && spentUsd(ctx.state) >= ctx.state.maxTotalCostUsd) {
1317
- throw new Error(
1318
- `step '${step.id}': budget_exceeded \u2014 run spend $${spentUsd(ctx.state).toFixed(4)} >= cap $${ctx.state.maxTotalCostUsd}`
1319
- );
1320
- }
1435
+ const assertBudget = () => {
1436
+ if (step.adapter && ctx.state.maxTotalCostUsd !== void 0 && spentUsd(ctx.state) >= ctx.state.maxTotalCostUsd) {
1437
+ throw new Error(
1438
+ `step '${step.id}': budget_exceeded \u2014 run spend $${spentUsd(ctx.state).toFixed(4)} >= cap $${ctx.state.maxTotalCostUsd}`
1439
+ );
1440
+ }
1441
+ };
1442
+ assertBudget();
1321
1443
  const stepCwd = step.cwd ? resolveSel(step.cwd, b) : void 0;
1322
1444
  const cwd = step.harness?.cwd ?? (stepCwd !== void 0 ? ctx.cwd !== void 0 ? resolve(ctx.cwd, stepCwd) : stepCwd : ctx.cwd);
1323
1445
  let knowledgeOut;
@@ -1351,45 +1473,53 @@ ${inner(b2)}` };
1351
1473
  ...step.harness ?? {},
1352
1474
  ...model !== void 0 && step.harness?.model === void 0 ? { model } : {}
1353
1475
  } : void 0;
1476
+ const inputRequestAffordance = ctx.agents.takeInputRequest ? "\n\nIf you need information you don't have, call the run_request_input tool instead of asking in your reply." : "";
1477
+ const outputSchemaNote = step.outputSchema ? describeOutputSchemaForPrompt(step.outputSchema) : void 0;
1478
+ const outputSchemaAffordance = outputSchemaNote ? `
1479
+
1480
+ ${outputSchemaNote}` : "";
1481
+ const firstPrompt = step.prompt(b) + inputRequestAffordance + outputSchemaAffordance;
1482
+ const retry = step.adapter ? step.retry ?? DEFAULT_AGENT_TRANSPORT_RETRY : void 0;
1483
+ const maxAttempts = Math.max(1, Math.floor(retry?.maxAttempts ?? 1));
1354
1484
  let sessionId;
1355
- if (step.adapter) {
1356
- if (ctx.signal?.aborted) throw new WorkflowCancelledError(step.id);
1485
+ for (let attempt = 1; ; attempt++) {
1486
+ let attemptSession;
1357
1487
  try {
1358
- sessionId = await ctx.agents.spawn(resolveSel(step.adapter, b), {
1359
- cwd,
1360
- workspaceSlug: ctx.workspaceSlug,
1488
+ attemptSession = await spawnOrResolveSession(step, ctx, b, { cwd, sandbox, harness });
1489
+ if (step.adapter) ctx.spawned?.push(attemptSession);
1490
+ if (knowledgeWarnings.length > 0 && ctx.agents.emitHarnessWarning) {
1491
+ ctx.agents.emitHarnessWarning({
1492
+ sessionId: attemptSession,
1493
+ warnings: knowledgeWarnings,
1494
+ label: step.id
1495
+ });
1496
+ }
1497
+ await ctx.agents.sendPromptAndWait(attemptSession, firstPrompt);
1498
+ sessionId = attemptSession;
1499
+ break;
1500
+ } catch (err) {
1501
+ const retryable = step.retry !== void 0 ? isAgentTransportFailure(err) : isAgentSessionLost(err);
1502
+ if (attempt >= maxAttempts || !retryable || ctx.signal?.aborted) throw err;
1503
+ if (attemptSession !== void 0) await retireLostSession(ctx, attemptSession);
1504
+ const delayMs = agentRetryDelayMs(retry, attempt + 1);
1505
+ ctx.onAgentRetry?.({
1361
1506
  stepId: step.id,
1362
- ...sandbox !== void 0 ? { sandbox } : {},
1363
- ...step.options !== void 0 ? { options: step.options } : {},
1364
- ...harness !== void 0 ? { harness } : {},
1365
- ...step.agentTools !== void 0 ? { agentTools: step.agentTools } : {},
1366
- ...b.index !== void 0 ? { stepKey: `${step.id}[${b.index}]` } : {}
1507
+ attempt: attempt + 1,
1508
+ maxAttempts,
1509
+ error: errorMessage(err),
1510
+ ...attemptSession !== void 0 ? { sessionId: attemptSession } : {},
1511
+ delayMs
1367
1512
  });
1368
- } catch (err) {
1369
- throw new AgentSpawnError(step.id, err);
1513
+ await sleepUnlessAborted(delayMs, ctx.signal);
1514
+ if (ctx.signal?.aborted) throw new WorkflowCancelledError(step.id);
1515
+ assertBudget();
1370
1516
  }
1371
- } else {
1372
- sessionId = ctx.agents.resolveByLabel(resolveSessionRef(step.sessionRef, b));
1373
- }
1374
- if (!sessionId) throw new Error(`step '${step.id}': no session (adapter and sessionRef both unresolved)`);
1375
- if (step.adapter) ctx.spawned?.push(sessionId);
1376
- if (knowledgeWarnings.length > 0 && ctx.agents.emitHarnessWarning) {
1377
- ctx.agents.emitHarnessWarning({
1378
- sessionId,
1379
- warnings: knowledgeWarnings,
1380
- label: step.id
1381
- });
1382
1517
  }
1518
+ await settleInputRequests(ctx, step, sessionId);
1383
1519
  const harnessOut = harness !== void 0 ? {
1384
1520
  ...harness,
1385
1521
  ...harness.tools && harness.tools.length > 0 ? { toolsApplied: false } : {}
1386
1522
  } : void 0;
1387
- const inputRequestAffordance = ctx.agents.takeInputRequest ? "\n\nIf you need information you don't have, call the run_request_input tool instead of asking in your reply." : "";
1388
- const outputSchemaNote = step.outputSchema ? describeOutputSchemaForPrompt(step.outputSchema) : void 0;
1389
- const outputSchemaAffordance = outputSchemaNote ? `
1390
-
1391
- ${outputSchemaNote}` : "";
1392
- await sendPromptAndAwaitOutcome(ctx, step, sessionId, step.prompt(b) + inputRequestAffordance + outputSchemaAffordance);
1393
1523
  if (step.policy && ctx.agents.onAwaitingInput) {
1394
1524
  await ctx.agents.onAwaitingInput(sessionId, step.policy);
1395
1525
  }
@@ -1877,6 +2007,7 @@ async function execStepBody(step, ctx, item, index) {
1877
2007
  cacheKey: ctx.cacheKey,
1878
2008
  runGateCommand: ctx.runGateCommand,
1879
2009
  onGateReport: ctx.onGateReport,
2010
+ onAgentRetry: ctx.onAgentRetry,
1880
2011
  spawned: ctx.spawned,
1881
2012
  usedArtifactNames: ctx.usedArtifactNames
1882
2013
  });
@@ -2034,6 +2165,7 @@ async function runWorkflowInner(workflow, input, hooks, maxTotalCostUsd) {
2034
2165
  const out = await execStep(step, ctx, void 0, void 0);
2035
2166
  state.steps[step.id] = out;
2036
2167
  completeStep(ctx, step.id, out);
2168
+ if (ownsScope && workflow.reusesSessions === false) await releaseScope(ctx);
2037
2169
  lastId = step.id;
2038
2170
  }
2039
2171
  await checkOutputsFiles(workflow.outputsFiles, ctx, workflow.id, lastId);
@@ -2071,7 +2203,8 @@ async function runWorkflow(args) {
2071
2203
  onStepSkipped: args.onStepSkipped,
2072
2204
  onStepFailed: args.onStepFailed,
2073
2205
  runGateCommand: args.runGateCommand,
2074
- onGateReport: args.onGateReport
2206
+ onGateReport: args.onGateReport,
2207
+ onAgentRetry: args.onAgentRetry
2075
2208
  }, args.maxTotalCostUsd);
2076
2209
  }
2077
2210
  function compileWorkflowManifest(source, opts) {
@@ -2080,6 +2213,6 @@ function compileWorkflowManifest(source, opts) {
2080
2213
  return compileWorkflow(handle, opts);
2081
2214
  }
2082
2215
 
2083
- export { AgentInputRequiredError, AgentSpawnError, DEFAULT_MAX_CONSECUTIVE_SPAWN_FAILURES, DEFAULT_STEP_TIMEOUT_MS, MissingArtifactError, NodeFsPort, StepOutcomeError, WorkflowCancelledError, WorkflowCompileError, WorkflowSuspendedError, buildAgentStep, compileWorkflow, compileWorkflowManifest, evalPredicate, interpolateTemplate, isCompilableJsonSchema, materializeKnowledge, normalizeWorkflowInputsSchema, resolveKnowledgeSelectors, resolveRef, resolveRefPrefixed, resolveValue, runWorkflow, validateAgainstJsonSchema, validateWorkflowInput };
2216
+ export { AgentInputRequiredError, AgentSessionLostError, AgentSpawnError, DEFAULT_AGENT_TRANSPORT_RETRY, DEFAULT_MAX_CONSECUTIVE_SPAWN_FAILURES, DEFAULT_STEP_TIMEOUT_MS, MissingArtifactError, NodeFsPort, StepOutcomeError, WorkflowCancelledError, WorkflowCompileError, WorkflowSuspendedError, buildAgentStep, compileWorkflow, compileWorkflowManifest, evalPredicate, interpolateTemplate, isAgentTransportFailure, isCompilableJsonSchema, materializeKnowledge, normalizeWorkflowInputsSchema, resolveKnowledgeSelectors, resolveRef, resolveRefPrefixed, resolveValue, runWorkflow, validateAgainstJsonSchema, validateWorkflowInput };
2084
2217
  //# sourceMappingURL=index.mjs.map
2085
2218
  //# sourceMappingURL=index.mjs.map