@agentproto/workflow-runtime 0.11.1 → 0.13.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
@@ -1,6 +1,5 @@
1
1
  import { DriverHandle, ResolverContext } from '@agentproto/driver';
2
2
  import { ToolContext, ToolHandle } from '@agentproto/tool';
3
- import { ZodType } from 'zod';
4
3
  import { WorkflowHandle } from '@agentproto/workflow';
5
4
  import { FsPort, FsStat, FsLockHandle } from '@agentproto/corpus';
6
5
 
@@ -27,6 +26,13 @@ interface Bindings {
27
26
  readonly item?: unknown;
28
27
  /** Present inside a `map` body — the element's index. */
29
28
  readonly index?: number;
29
+ /** AIP-58 §4 Run workspace — present when the host wires
30
+ * {@link RunWorkflowArgs.workspace}. `$run.workspace` / `{{run.workspace}}`
31
+ * is a convenience alias for the same absolute path AIP-16 injects as
32
+ * `$input._workflowFsRoot` — steps may use either. */
33
+ readonly run?: {
34
+ readonly workspace: string;
35
+ };
30
36
  }
31
37
  /** The AIP-16 IO seam: read a value out of the run bindings. */
32
38
  type Selector<T> = (bindings: Bindings) => T;
@@ -67,6 +73,14 @@ type FanOutOutcome<T = unknown> = {
67
73
  readonly item: unknown;
68
74
  /** `err.message` if the throw was an `Error`, else `String(err)`. */
69
75
  readonly error: string;
76
+ } | {
77
+ /** Never started: the fan-out's spawn circuit breaker opened first
78
+ * (see {@link MapStep.maxConsecutiveSpawnFailures}). */
79
+ readonly status: "skipped";
80
+ readonly index: number;
81
+ readonly item: unknown;
82
+ /** `circuit-open: <first error of the failure streak>`. */
83
+ readonly reason: string;
70
84
  };
71
85
  /**
72
86
  * The bound output of a `map`/`pipeline` step run with `onError: "collect"`:
@@ -77,7 +91,17 @@ interface TolerantFanOutResult<T = unknown> {
77
91
  readonly results: readonly FanOutOutcome<T>[];
78
92
  readonly succeeded: number;
79
93
  readonly failed: number;
94
+ /** Items never started because the spawn circuit breaker opened. */
95
+ readonly skipped: number;
96
+ /** Set when the breaker opened: the first error of the spawn-failure
97
+ * streak that tripped it. */
98
+ readonly circuitOpen?: {
99
+ readonly error: string;
100
+ };
80
101
  }
102
+ /** Default for {@link MapStep.maxConsecutiveSpawnFailures} /
103
+ * {@link PipelineStep.maxConsecutiveSpawnFailures}. */
104
+ declare const DEFAULT_MAX_CONSECUTIVE_SPAWN_FAILURES = 3;
81
105
  /**
82
106
  * Run a sub-step once per element of an array, optionally with bounded
83
107
  * concurrency. The element + index are exposed to the body via `bindings.item`
@@ -99,6 +123,17 @@ interface MapStep {
99
123
  * silently dropped.
100
124
  */
101
125
  onError?: "throw" | "collect";
126
+ /**
127
+ * `"collect"` only: once this many items IN A ROW fail because an agent
128
+ * step's session could not be spawned ({@link AgentSpawnError}), stop
129
+ * starting new items — the failure is systemic, not per-item. In-flight
130
+ * items finish; every item not yet started is reported skipped
131
+ * (`circuit-open: <first error>`) and the map returns normally. A settled
132
+ * item that isn't a spawn failure resets the streak. Default
133
+ * {@link DEFAULT_MAX_CONSECUTIVE_SPAWN_FAILURES}; `0` disables the breaker.
134
+ * (`"throw"` already stops at the first failure of any kind.)
135
+ */
136
+ maxConsecutiveSpawnFailures?: number;
102
137
  }
103
138
  /**
104
139
  * Run N items through K sequential stage bodies with NO cross-item barrier:
@@ -120,6 +155,8 @@ interface PipelineStep {
120
155
  * `"collect"` runs every item's chain to completion and binds a
121
156
  * {@link TolerantFanOutResult} instead of a bare array. */
122
157
  onError?: "throw" | "collect";
158
+ /** Same semantics as {@link MapStep.maxConsecutiveSpawnFailures}. */
159
+ maxConsecutiveSpawnFailures?: number;
123
160
  }
124
161
  /** Run one of two branches based on a predicate over the bindings. */
125
162
  interface BranchStep {
@@ -128,6 +165,10 @@ interface BranchStep {
128
165
  cond: Selector<boolean>;
129
166
  then: readonly RunStep[];
130
167
  otherwise?: readonly RunStep[];
168
+ /** The authored step this node was compiled from, when it differs from
169
+ * `id` — a multi-arm manifest `kind: branch` compiles to a chain of
170
+ * nodes (`<id>`, `<id>__branch1`, …); skip reports name `<id>`. */
171
+ sourceId?: string;
131
172
  }
132
173
  /** Repeat a body while a predicate holds, up to a hard iteration ceiling. */
133
174
  interface LoopStep {
@@ -268,6 +309,37 @@ type AgentSandboxRef = string | {
268
309
  provider: string;
269
310
  [k: string]: unknown;
270
311
  };
312
+ /**
313
+ * The minimal structural contract {@link AgentStep.outputSchema} must
314
+ * satisfy — exactly the `safeParse` shape `execAgentStep` consumes (never
315
+ * `.parse`, `._def`, or any other zod-specific member). A real zod
316
+ * `ZodType` satisfies this automatically (structural typing — zod's own
317
+ * `SafeParseReturnType` is a superset of this shape), so a TS-authored step
318
+ * can still pass a zod schema directly. `compileAgentStep` additionally
319
+ * builds one of these from a WORKFLOW.md-authored JSON Schema object (ajv
320
+ * `validateAgainstJsonSchema`-backed) for the declarative manifest path,
321
+ * where `outputSchema` is plain JSON Schema, not a zod instance.
322
+ */
323
+ interface OutputSchemaLike {
324
+ safeParse(value: unknown): {
325
+ success: true;
326
+ data: unknown;
327
+ } | {
328
+ success: false;
329
+ error: {
330
+ issues: readonly {
331
+ path: readonly PropertyKey[];
332
+ message: string;
333
+ }[];
334
+ };
335
+ };
336
+ /** Set by `compileAgentStep`'s declarative (WORKFLOW.md) path to the raw
337
+ * JSON Schema object it wrapped — lets F27's prompt-affordance render the
338
+ * EXACT schema text instead of re-deriving it from the `safeParse`
339
+ * closure. Absent on a TS-authored zod schema (rendered via
340
+ * `z.toJSONSchema` instead — see `describeOutputSchemaForPrompt`). */
341
+ jsonSchema?: unknown;
342
+ }
271
343
  /**
272
344
  * Spawn or reuse an agent session and send it a prompt, waiting for the
273
345
  * turn to complete. The host injects an {@link AgentSessionHost} — this
@@ -278,7 +350,12 @@ interface AgentStep {
278
350
  id: string;
279
351
  /** Adapter slug to spawn a NEW session. Omit to reuse via sessionRef. */
280
352
  adapter?: Selector<string> | string;
281
- /** Reuse an earlier AgentStep's spawned session, by that step's id. */
353
+ /** Reuse an earlier AgentStep's spawned session, by that step's id. Inside
354
+ * a `map`/`pipeline` body, a `{{index}}` placeholder resolves to the
355
+ * current item index — `"review[{{index}}]"` names the session THIS item's
356
+ * `review` step spawned (the host indexes every fan-out spawn under its
357
+ * `stepKey`, `<stepId>[<index>]`), where a bare `"review"` would resolve to
358
+ * whichever item spawned last. */
282
359
  sessionRef?: string;
283
360
  /** Model id override for this spawn — same semantics as `agent_start.model`
284
361
  * (a literal string or a per-run selector resolving to one; `undefined` ⇒
@@ -308,8 +385,11 @@ interface AgentStep {
308
385
  } | {
309
386
  awaiting: "fail";
310
387
  };
311
- /** Validate the session's final message against this schema; re-prompt on mismatch. */
312
- outputSchema?: ZodType<unknown>;
388
+ /** Validate the session's final message against this schema; re-prompt on
389
+ * mismatch. A zod `ZodType` (TS-authored steps) or anything else
390
+ * satisfying {@link OutputSchemaLike} (a WORKFLOW.md-authored JSON
391
+ * Schema object compiles into one of these — see `compileAgentStep`). */
392
+ outputSchema?: OutputSchemaLike;
313
393
  /** Re-prompt-and-retry attempts on schema mismatch before failing. Default 2. */
314
394
  maxRetries?: number;
315
395
  /** Cache this step's output under the run's cacheKey; the resolved prompt +
@@ -324,6 +404,54 @@ interface AgentStep {
324
404
  options?: Record<string, boolean | number | string>;
325
405
  /** Harness pinning for this step's spawn. See {@link AgentHarness}. */
326
406
  harness?: AgentHarness;
407
+ /** The resolved agent manifest's declared `tools` (AGENT.md `tools:`), set
408
+ * by {@link CompileWorkflowOptions.agentRefs} resolution. Forwarded to the
409
+ * host's `spawn` so it can mount its own tool gateway scoped to this list
410
+ * (names the gateway doesn't serve — harness-native tools — match
411
+ * nothing). Unset ⇒ the agent declared no tools list. Only meaningful
412
+ * with `adapter`; ignored on a `sessionRef` reuse. */
413
+ agentTools?: readonly string[];
414
+ }
415
+ /**
416
+ * AIP-58 §4 Run workspace — declare a file under the run workspace
417
+ * (`$run.workspace` / `_workflowFsRoot`) as a run artifact: hashed, sized,
418
+ * and copied into the host's `artifactsDir` (`<runsRoot>/<runId>/artifacts/`).
419
+ * Bound output (and the value passed to {@link RunWorkflowArgs.onArtifact})
420
+ * is the resulting {@link ArtifactEntry}.
421
+ *
422
+ * Cache-aware when the run has `cache`/`cacheKey` wired (independent of any
423
+ * `cacheable` flag — there is none on this step; declaring the same key/path
424
+ * again under the same cacheKey is always cheap to re-verify): a hit COPIES
425
+ * the previously-cached file forward from its original `artifactsDir` into
426
+ * THIS run's own — never shares a directory across runs (AIP-58 §4 "two runs
427
+ * MUST NEVER share a workspace") — so a step declaring an artifact from a
428
+ * cache-hit-replayed upstream step still works. See `run-workflow.ts`'s
429
+ * `case "artifact"` for the exact mechanics.
430
+ */
431
+ interface ArtifactStep {
432
+ kind: "artifact";
433
+ id: string;
434
+ /** Artifact key — also becomes its filename under `artifactsDir` (sanitized). */
435
+ key: Selector<string> | string;
436
+ /** Path to the source file. Relative to `$run.workspace`; MUST resolve
437
+ * inside it (an absolute path or a `..`-escaping relative one throws). */
438
+ path: Selector<string> | string;
439
+ contentType?: Selector<string> | string;
440
+ }
441
+ /**
442
+ * AIP-58 §4/§1 `ArtifactEntry` — one run-scoped copy of a declared output
443
+ * file. `path` is always `"artifacts/<sanitized key>"`, relative to the RUN
444
+ * WORKSPACE ROOT (`<runsRoot>/<runId>/`, the parent of `$run.workspace`
445
+ * itself) — never the original in-workspace location the file was read
446
+ * from.
447
+ */
448
+ interface ArtifactEntry {
449
+ key: string;
450
+ path: string;
451
+ sha256: string;
452
+ size: number;
453
+ stepId: string;
454
+ contentType?: string;
327
455
  }
328
456
  /**
329
457
  * `kind: "gate"` — run a shell command through the host's subprocess runner
@@ -407,6 +535,13 @@ interface AgentRefResolution {
407
535
  adapter: string;
408
536
  /** Adapter option id → value merged onto the compiled step's `options`. */
409
537
  options?: Record<string, boolean | number | string>;
538
+ /** AGENT.md's declared `model` — the compiled step's DEFAULT when the
539
+ * step itself sets none (a step-level `model:` still wins). Forwarded
540
+ * through the same `harness.model` channel a step-level `model` uses. */
541
+ model?: string;
542
+ /** AGENT.md's declared `tools` (string ids only) — becomes the compiled
543
+ * step's {@link AgentStep.agentTools}. */
544
+ tools?: readonly string[];
410
545
  }
411
546
  /**
412
547
  * The element generics on `ToolStep` are erased to `any` so heterogeneous
@@ -417,13 +552,41 @@ interface AgentRefResolution {
417
552
  * issue #21534); tightening the union to `ToolStep<unknown, …>` rejects a
418
553
  * concrete `ToolStep<MarketSearchInput, …>` on the zod `ZodType<T>` variance.
419
554
  */
420
- type RunStep = ToolStep<any, any, any> | TransformStep | MapStep | PipelineStep | BranchStep | LoopStep | ParallelStep | ApprovalStep | SuspendStep | GroupStep | SubworkflowStep | AgentStep | GateStep;
555
+ type RunStep = ToolStep<any, any, any> | TransformStep | MapStep | PipelineStep | BranchStep | LoopStep | ParallelStep | ApprovalStep | SuspendStep | GroupStep | SubworkflowStep | AgentStep | GateStep | ArtifactStep;
556
+ /** One `outputsFiles.<key>` declaration carried onto a compiled
557
+ * {@link RuntimeWorkflow} — the AIP-16 file contract, amended with
558
+ * `required` (AIP-58 §4). `path` MAY use the `<runId>`/`<workflowId>`/
559
+ * `<isoDate>` interpolation tokens AIP-16 names (`<toolId>` is not
560
+ * resolvable at the workflow level and is left literal). */
561
+ interface OutputsFileContract {
562
+ path: string;
563
+ /** `true`: missing when the run's steps finish ⇒ `failed { code:
564
+ * "missing-artifact" }` (AIP-58 §4/§10). Absent (the default) or `false`
565
+ * ⇒ advisory only — a `console.warn`, the run still succeeds (mirrors
566
+ * `StepRecord.hint`'s "advisory, never load-bearing" posture; matches the
567
+ * AIP-58 V3 vector's own note: "with required absent or false, the same
568
+ * scenario would be a warning"). */
569
+ required?: boolean;
570
+ contentType?: string;
571
+ }
421
572
  interface RuntimeWorkflow {
422
573
  id: string;
423
574
  description?: string;
424
575
  steps: readonly RunStep[];
576
+ /**
577
+ * Cleanup steps that ALWAYS run once `steps` ends — succeeded, failed, or
578
+ * cancelled (they run without the abort signal). They see the same
579
+ * bindings (a step that never ran is simply absent). A failing `finally`
580
+ * step fails an otherwise-successful run; after a failed or cancelled run
581
+ * the original outcome wins and the cleanup error is only reported.
582
+ */
583
+ finally?: readonly RunStep[];
425
584
  /** Pick the run's final output (default: the last top-level step's output). */
426
585
  output?: Selector<unknown>;
586
+ /** AIP-16 `outputsFiles` (as amended by AIP-58 §4) — checked ONCE, after
587
+ * every top-level step finishes successfully. See {@link OutputsFileContract}
588
+ * and `run-workflow.ts`'s `checkOutputsFiles`. */
589
+ outputsFiles?: Readonly<Record<string, OutputsFileContract>>;
427
590
  }
428
591
  /** A human/host decision on one approval request. `who` records WHO decided
429
592
  * ("human", "timeout", "cancelled", …); `note` is optional free text. */
@@ -445,6 +608,16 @@ interface ResumeRequest {
445
608
  stepId: string;
446
609
  on: readonly string[];
447
610
  }
611
+ /**
612
+ * AIP-58 §3(a) explicit signal, recorded by the host when the step's own
613
+ * session calls `run.requestInput` before its turn ends. Passed to
614
+ * {@link RunWorkflowArgs.onInputRequired} to durably suspend the step.
615
+ */
616
+ interface InputRequiredRequest {
617
+ stepId: string;
618
+ prompt: string;
619
+ schema?: Record<string, unknown>;
620
+ }
448
621
  interface AgentSessionHost {
449
622
  /** Spawn a new agent session and return its id. A `sandbox` ref asks the
450
623
  * host to run the session inside that sandbox (provider slug or inline
@@ -459,13 +632,40 @@ interface AgentSessionHost {
459
632
  options?: Record<string, boolean | number | string>;
460
633
  /** Harness pinning for this spawn (see {@link AgentStep.harness}). */
461
634
  harness?: AgentHarness;
635
+ /** The agent manifest's declared tools (see {@link AgentStep.agentTools}). */
636
+ agentTools?: readonly string[];
637
+ /** Run-unique key of the spawning step when it runs inside a
638
+ * `map`/`pipeline` item: `stepId[<index>]`, the same key the run's step
639
+ * hooks report. Absent outside a fan-out (the key is then `stepId`). */
640
+ stepKey?: string;
462
641
  }): Promise<string>;
642
+ /**
643
+ * The run is done with a session it spawned: end it (if still live) and
644
+ * archive it. Called once per spawned session — when its `map`/`pipeline`
645
+ * item settles, else when the run itself ends (ok or error) — never
646
+ * earlier, so a later step's `sessionRef` can still reuse it. The session's
647
+ * id stays on the step's output. Best-effort: a throw is swallowed.
648
+ */
649
+ releaseSession?(sessionId: string): Promise<void>;
463
650
  /** Send a prompt to an existing session and wait for its turn to end. */
464
651
  sendPromptAndWait(sessionId: string, prompt: string): Promise<void>;
465
652
  /** Look up a session by the step id that spawned it (for sessionRef reuse). */
466
653
  resolveByLabel(stepId: string): string | undefined;
467
654
  /** Handle an awaiting-input policy for a session. */
468
655
  onAwaitingInput?(sessionId: string, policy: AgentStep["policy"]): Promise<void>;
656
+ /**
657
+ * AIP-58 §3(a) explicit signal: consume (and clear) a pending
658
+ * `run.requestInput` recorded for this session — checked by
659
+ * {@link AgentStep} execution right after a turn ends, before the
660
+ * outputSchema retry loop (and again inside it, after every reprompt).
661
+ * `undefined` when no request is pending. Optional: a host that omits
662
+ * this never suspends a step on this signal — the outcome rule's other
663
+ * branches (missing-output / vacuous success) still apply.
664
+ */
665
+ takeInputRequest?(sessionId: string): {
666
+ prompt: string;
667
+ schema?: Record<string, unknown>;
668
+ } | undefined;
469
669
  /** Return the session's final assistant message text (for outputSchema validation). */
470
670
  readFinalMessage?(sessionId: string): Promise<string>;
471
671
  /** Current cumulative cost (USD) of a session, for run-level budgeting. */
@@ -485,6 +685,12 @@ interface AgentSessionHost {
485
685
  interface StepCacheEntry {
486
686
  output: unknown;
487
687
  resolvedInputHash: string;
688
+ /** Set only by a `kind: "artifact"` step (see {@link ArtifactStep}): the
689
+ * absolute `artifactsDir` this entry's file was copied into when first
690
+ * cached. A hit in a LATER run (a different `artifactsDir`, since AIP-58
691
+ * §4 forbids two runs sharing a workspace) copies the file forward from
692
+ * here into the new run's own `artifactsDir` instead of re-declaring it. */
693
+ artifactsDirAtCache?: string;
488
694
  }
489
695
  /** Opt-in journal for cacheable steps. Host-injected; file-backed in the runtime. */
490
696
  interface StepCache {
@@ -493,6 +699,31 @@ interface StepCache {
493
699
  /** Write/overwrite a step's journal entry. */
494
700
  set(stepCacheKey: string, entry: StepCacheEntry): Promise<void>;
495
701
  }
702
+ /** Extra context passed to `onStepStart`/`onStepComplete`. */
703
+ interface StepHookInfo {
704
+ /** The step's output was replayed from the {@link StepCache} journal —
705
+ * it was not executed (no spawn, no tool dispatch) this run. */
706
+ cached?: boolean;
707
+ }
708
+ /** Why `onStepSkipped` fired. */
709
+ interface StepSkippedInfo {
710
+ /** `"branch-not-taken"`: the step sits in an untaken `branch` arm.
711
+ * `"circuit-open"`: a `map`/`pipeline` item never started because the
712
+ * fan-out's spawn circuit breaker opened
713
+ * ({@link MapStep.maxConsecutiveSpawnFailures}). */
714
+ reason: "branch-not-taken" | "circuit-open";
715
+ /** Id of the authored step whose decision skipped the step — the `branch`
716
+ * step, or (circuit-open) the `map`/`pipeline` step. */
717
+ branchId: string;
718
+ /** `"circuit-open"` only: the first error of the spawn-failure streak
719
+ * that tripped the breaker. */
720
+ message?: string;
721
+ }
722
+ /** What `onStepFailed` reports. */
723
+ interface StepFailedInfo {
724
+ /** `err.message` if the throw was an `Error`, else `String(err)`. */
725
+ error: string;
726
+ }
496
727
  interface RunWorkflowArgs {
497
728
  workflow: RuntimeWorkflow;
498
729
  input?: unknown;
@@ -503,12 +734,41 @@ interface RunWorkflowArgs {
503
734
  approve?: (req: ApprovalRequest) => boolean | ApprovalDecision | Promise<boolean | ApprovalDecision>;
504
735
  /** Supply a {@link SuspendStep}'s resume payload. Default: throw + suspend. */
505
736
  resume?: (req: ResumeRequest) => unknown | Promise<unknown>;
737
+ /**
738
+ * AIP-58 §3(a)/§5 outcome rule: suspend an {@link AgentStep} that
739
+ * signalled `run.requestInput` (see
740
+ * {@link AgentSessionHost.takeInputRequest}), resolving with the resume
741
+ * payload once an external event supplies one. The runtime sends that
742
+ * payload (JSON) as the step's next prompt to the SAME session and
743
+ * re-applies the outcome rule — the step may suspend again, fail
744
+ * `missing-output`, or succeed. Default (undefined) ⇒
745
+ * {@link AgentInputRequiredError} throws instead, the same
746
+ * no-hook-supplied shape {@link WorkflowSuspendedError} uses for
747
+ * {@link SuspendStep}.
748
+ */
749
+ onInputRequired?: (req: InputRequiredRequest) => unknown | Promise<unknown>;
506
750
  /** Host-injected agent session runtime. Undefined ⇒ {@link AgentStep} throws. */
507
751
  agents?: AgentSessionHost;
508
752
  /** Working directory for spawned agent sessions. */
509
753
  cwd?: string;
510
754
  /** Workspace slug for spawned agent sessions. */
511
755
  workspaceSlug?: string;
756
+ /** AIP-58 §4 Run workspace — absolute path to this run's own scratch
757
+ * directory (AIP-16's `_workflowFsRoot`), exposed to steps as
758
+ * `$run.workspace` / `{{run.workspace}}`. Undefined ⇒ the `run` binding
759
+ * and `kind: "artifact"` steps are unavailable. */
760
+ workspace?: string;
761
+ /** AIP-58 §4 — absolute path to this run's `artifacts/` directory. Required
762
+ * alongside `workspace` for a `kind: "artifact"` step (or a declared
763
+ * `RuntimeWorkflow.outputsFiles`) to run. */
764
+ artifactsDir?: string;
765
+ /** This run's id — used only to interpolate the `<runId>` token in a
766
+ * declared `outputsFiles.<key>.path` (AIP-16). Purely informational
767
+ * otherwise. */
768
+ runId?: string;
769
+ /** Called once per {@link ArtifactEntry} recorded — by a `kind: "artifact"`
770
+ * step, or by the end-of-run `outputsFiles` check — cache hit or fresh. */
771
+ onArtifact?: (entry: ArtifactEntry) => void;
512
772
  /** Run-level cost ceiling (USD). Once the summed cost of spawned sessions
513
773
  * reaches this, the next AgentStep spawn fails with `budget_exceeded`. */
514
774
  maxTotalCostUsd?: number;
@@ -517,10 +777,24 @@ interface RunWorkflowArgs {
517
777
  /** Namespacing label for this run's cache lookups (the workflow_start cacheKey).
518
778
  * Both `cache` and `cacheKey` must be set for any caching to happen. */
519
779
  cacheKey?: string;
520
- /** Called when a step begins execution (before spawn/prompt). */
521
- onStepStart?: (stepId: string) => void;
522
- /** Called when a step completes execution, with its output. */
523
- onStepComplete?: (stepId: string, output: unknown) => void;
780
+ /** Called when a step begins execution (before spawn/prompt). A cacheable
781
+ * step replayed from the journal still fires this, with `info.cached`. */
782
+ onStepStart?: (stepId: string, info?: StepHookInfo) => void;
783
+ /** Called when a step completes execution, with its output — `info.cached`
784
+ * when the output was replayed from the journal instead of executed. */
785
+ onStepComplete?: (stepId: string, output: unknown, info?: StepHookInfo) => void;
786
+ /** Called for every step in a `branch` arm that was NOT taken, once the
787
+ * branch decides — the step will not run this time. Only statically-known
788
+ * steps are reported (a `map`/`pipeline`/`subworkflow` step under the arm
789
+ * reports its own id, not its body's); a step id that also sits in the
790
+ * taken path is never reported. */
791
+ onStepSkipped?: (stepId: string, info: StepSkippedInfo) => void;
792
+ /** Called when a step inside a tolerant (`onError: "collect"`) `map`/
793
+ * `pipeline` item throws — the item is recorded as rejected and the run
794
+ * goes on, so this is the only signal the failing step gets. `stepId` is
795
+ * the innermost step that threw, indexed like `onStepStart`'s. A throw
796
+ * that fails the run is NOT reported here (the run's own failure is). */
797
+ onStepFailed?: (stepId: string, info: StepFailedInfo) => void;
524
798
  /** Host-injectable subprocess runner for `kind: "gate"` steps. Undefined ⇒
525
799
  * the runtime's own `node:child_process`-backed default. */
526
800
  runGateCommand?: GateCommandRunner;
@@ -548,6 +822,59 @@ declare class WorkflowSuspendedError extends Error {
548
822
  readonly on: readonly string[];
549
823
  constructor(stepId: string, on: readonly string[]);
550
824
  }
825
+ /**
826
+ * AIP-58 §3(a) — thrown when an {@link AgentStep}'s session signals
827
+ * `run.requestInput` but no host `onInputRequired` hook is provided (the
828
+ * same "no resume hook supplied" shape {@link WorkflowSuspendedError} uses
829
+ * for {@link SuspendStep}). A host that wires `onInputRequired` never sees
830
+ * this thrown — it durably suspends the step instead.
831
+ */
832
+ declare class AgentInputRequiredError extends Error {
833
+ readonly stepId: string;
834
+ readonly prompt: string;
835
+ readonly schema?: Record<string, unknown> | undefined;
836
+ constructor(stepId: string, prompt: string, schema?: Record<string, unknown> | undefined);
837
+ }
838
+ /**
839
+ * AIP-58 §3 Outcome rule — thrown when an {@link AgentStep} declares an
840
+ * `outputSchema` and its turn ends (after exhausting retries) without ever
841
+ * producing output that validates against it, and no explicit
842
+ * input-required signal (§3(a)/(b)) was observed either. `hint` is set when
843
+ * the final message matches the "trailing question mark" heuristic — it is
844
+ * ONLY a triage aid; it never changes the outcome (still `missing-output`).
845
+ */
846
+ declare class StepOutcomeError extends Error {
847
+ readonly stepId: string;
848
+ readonly code: "missing-output";
849
+ readonly hint?: "possible-input-request" | undefined;
850
+ constructor(stepId: string, code: "missing-output", message: string, hint?: "possible-input-request" | undefined);
851
+ }
852
+ /**
853
+ * AIP-58 §4/§10 — thrown when a declared `outputsFiles.<key>` (`required`
854
+ * absent or `true`) does not exist under the run workspace once every
855
+ * top-level step has finished. `stepId` is the last top-level step that ran
856
+ * (the manifest names no step for a workflow-level contract, so the last one
857
+ * to finish is the best available attribution).
858
+ */
859
+ declare class MissingArtifactError extends Error {
860
+ readonly key: string;
861
+ readonly stepId: string | undefined;
862
+ readonly code: "missing-artifact";
863
+ constructor(key: string, stepId: string | undefined);
864
+ }
865
+ /**
866
+ * Thrown when an {@link AgentStep}'s session could not be spawned at all
867
+ * (`AgentSessionHost.spawn` rejected) — distinct from a session that spawned
868
+ * and then failed its turn. A tolerant fan-out counts these toward its spawn
869
+ * circuit breaker ({@link MapStep.maxConsecutiveSpawnFailures}): a spawn that
870
+ * fails for one item usually fails for every item (missing cwd, adapter
871
+ * gone, process limits), so burning through the rest is pure noise.
872
+ */
873
+ declare class AgentSpawnError extends Error {
874
+ readonly stepId: string;
875
+ readonly cause: unknown;
876
+ constructor(stepId: string, cause: unknown);
877
+ }
551
878
  declare function runWorkflow(args: RunWorkflowArgs): Promise<WorkflowRunResult>;
552
879
 
553
880
  /**
@@ -561,6 +888,10 @@ declare function runWorkflow(args: RunWorkflowArgs): Promise<WorkflowRunResult>;
561
888
  *
562
889
  * - data refs: `$input` · `$input.a.b` · `$steps.<id>` · `$steps.<id>.a`
563
890
  * · `$item` · `$item.a` · `$index` (`$$` escapes a literal `$`)
891
+ * - prompts: the above refs PLUS mustache-style `{{name}}` /
892
+ * `{{a.b}}` interpolation, `{{#name}}…{{/name}}` conditional
893
+ * and `{{^name}}…{{/name}}` inverted sections (see
894
+ * {@link interpolateTemplate})
564
895
  * - `step.inputs`: a JSON object whose leaf strings may be refs (resolved
565
896
  * recursively); non-`$` strings are literals
566
897
  * - `map.over`: a ref to an array
@@ -570,9 +901,12 @@ declare function runWorkflow(args: RunWorkflowArgs): Promise<WorkflowRunResult>;
570
901
  * Scope: the **linear / structured subset** of AIP-15 — steps run in document
571
902
  * order; `map`/`loop`/`parallel` nest their child step lists. Non-linear `next`
572
903
  * gotos are rejected with a clear diagnostic. `kind:"branch"` compiles in its
573
- * **forward-only** form: every `branches[].next`/`default` must name a later
574
- * sibling in the SAME step list (not backward, not into a nested map/loop/
575
- * parallel body) — see `compileBranchChain` below. Compiling a full goto graph
904
+ * **forward-only** form: every `branches[].next`/`default`/`join` must name a
905
+ * later sibling in the SAME step list (not backward, not into a nested map/
906
+ * loop/parallel body). Arms are EXCLUSIVE by default — exactly one arm body
907
+ * runs, then execution continues at the join (see `compileExclusiveBranch`);
908
+ * `fallthrough: true` opts into the legacy "target + everything after it"
909
+ * semantics (see `compileBranchChain`). Compiling a full goto graph
576
910
  * (backward jumps, cross-scope targets) is a separable follow-up; hand-author
577
911
  * a `loop` step for retry-style control flow instead.
578
912
  */
@@ -610,6 +944,19 @@ declare function resolveRefPrefixed(value: string, b: Bindings): {
610
944
  resolved: unknown;
611
945
  rest: string;
612
946
  } | undefined;
947
+ /**
948
+ * Interpolate mustache-style `{{…}}` templates against the run bindings.
949
+ *
950
+ * - `{{name}}` / `{{a.b}}` → the resolved value stringified; a MISSING
951
+ * value leaves the placeholder as-is (never crashes, never renders
952
+ * "undefined" into an agent prompt).
953
+ * - `{{#name}}…{{/name}}` renders the inner text only when `name` is
954
+ * truthy; `{{^name}}…{{/name}}` only when falsy. When a section tag
955
+ * sits alone on its line (only whitespace around it), the whole line
956
+ * collapses too — so a dropped section doesn't leave blank lines behind.
957
+ * - Sections nest: inner text is interpolated recursively.
958
+ */
959
+ declare function interpolateTemplate(template: string, b: Bindings): string;
613
960
  /** Recursively resolve a value node: refs in strings, into arrays/objects. */
614
961
  declare function resolveValue(node: unknown, b: Bindings): unknown;
615
962
  /** Evaluate a `while`/`when` predicate string against the bindings. */
@@ -643,6 +990,90 @@ declare function compileWorkflow(handle: WorkflowHandle, opts: CompileWorkflowOp
643
990
  */
644
991
  declare function compileWorkflowManifest(source: string, opts: CompileWorkflowOptions): RuntimeWorkflow;
645
992
 
993
+ /**
994
+ * AIP-58 §3 Outcome rule — "invalid or missing required input is checked
995
+ * before any step runs". AIP-16 declares a workflow's `inputs` block as
996
+ * JSON Schema, but most existing WORKFLOW.md authors write a shorthand flat
997
+ * map instead (`inputs: { url: { type, description, default } }` — see
998
+ * `youtube-transcriber`'s `transcribe/WORKFLOW.md`). This module normalizes
999
+ * that shorthand into real JSON Schema, then validates a run's input
1000
+ * against it with the same ajv machinery `@agentproto/tool` already uses for
1001
+ * TOOL contracts (`define-tool.ts`'s `validateJsonSchema`).
1002
+ */
1003
+ /**
1004
+ * Normalize a WORKFLOW.md `inputs` field to a JSON Schema object.
1005
+ *
1006
+ * Already-canonical JSON Schema (`{ type: "object", properties, required?
1007
+ * }`) passes through unchanged — its own `required[]`, if any, is honored
1008
+ * as-is (this is the shape AIP-58's V1 vector authors directly).
1009
+ *
1010
+ * The shorthand flat map (`{ <name>: { type, description?, default?,
1011
+ * required? } }`) is lifted: each key becomes a `properties` entry (minus
1012
+ * its shorthand-only `required` marker); a property gets added to the
1013
+ * schema's `required[]` ONLY when its shorthand spec sets `required: true`
1014
+ * AND declares no `default` — a `default` always makes a field optional to
1015
+ * omit, regardless of `required`. A shorthand property with no `required`
1016
+ * marker at all stays optional, so normalizing an existing manifest that
1017
+ * never used the marker (e.g. `youtube-transcriber`'s `transcribe/
1018
+ * WORKFLOW.md`) never newly fails validation for it.
1019
+ */
1020
+ declare function normalizeWorkflowInputsSchema(inputs: unknown): Record<string, unknown>;
1021
+ type WorkflowInputValidation = {
1022
+ valid: true;
1023
+ schema: Record<string, unknown>;
1024
+ } | {
1025
+ valid: false;
1026
+ schema: Record<string, unknown>;
1027
+ /** AIP-58 §10 error code — this validator only ever produces one. */
1028
+ code: "invalid-input";
1029
+ /** Missing/invalid field names (deduped), for a caller that wants to
1030
+ * point at exactly what's wrong without re-parsing `message`. */
1031
+ fields: readonly string[];
1032
+ /** Human-readable message naming the missing/invalid fields. */
1033
+ message: string;
1034
+ };
1035
+ /**
1036
+ * AIP-58 §9 `run.requestInput`/`run.resume`: `true` when `schema` is a
1037
+ * usable JSON Schema (ajv can compile it) — a plain object is necessary but
1038
+ * not sufficient (e.g. `{ type: "not-a-type" }` compiles-fails). Used to
1039
+ * reject a malformed `schema` argument before it's ever recorded as a
1040
+ * step's `StepRecord.suspend.schema`.
1041
+ */
1042
+ declare function isCompilableJsonSchema(schema: unknown): schema is Record<string, unknown>;
1043
+ /** One structural (zod-`ZodIssue`-shaped) validation failure — the common
1044
+ * currency between ajv's `ErrorObject[]` and zod's `ZodError.issues`, see
1045
+ * {@link OutputSchemaLikeIssue}. */
1046
+ interface SchemaValidationIssue {
1047
+ path: readonly (string | number)[];
1048
+ message: string;
1049
+ }
1050
+ /**
1051
+ * Generic JSON Schema validation, used both by AIP-58 §3/§9 (validate a
1052
+ * `run.resume` payload against the suspended step's `StepRecord.suspend
1053
+ * .schema` BEFORE the resume transition happens — an invalid payload MUST
1054
+ * leave the run suspended, never transition it) and by `compileAgentStep`
1055
+ * (adapt a WORKFLOW.md-authored JSON Schema `outputSchema` into the
1056
+ * {@link OutputSchemaLike} shape `execAgentStep` consumes). `issues` mirrors
1057
+ * zod's `ZodError.issues` shape so both call sites format errors the same
1058
+ * way regardless of which schema language declared the contract.
1059
+ */
1060
+ declare function validateAgainstJsonSchema(schema: Record<string, unknown>, value: unknown): {
1061
+ valid: true;
1062
+ } | {
1063
+ valid: false;
1064
+ message: string;
1065
+ issues: readonly SchemaValidationIssue[];
1066
+ };
1067
+ /**
1068
+ * Validate a run's `input` against a WORKFLOW.md's declared `inputs`
1069
+ * (shorthand or canonical JSON Schema — see
1070
+ * {@link normalizeWorkflowInputsSchema}). The caller is responsible for
1071
+ * calling this BEFORE dispatching any step and BEFORE spawning any session
1072
+ * on an invalid result — this function only judges the input, it doesn't
1073
+ * gate execution itself.
1074
+ */
1075
+ declare function validateWorkflowInput(inputsField: unknown, input: unknown): WorkflowInputValidation;
1076
+
646
1077
  /**
647
1078
  * Shared `AgentStep` construction — the one place that applies AgentStep's
648
1079
  * defaults (`policy` → `{ awaiting: "fail" }`, a literal `prompt` wrapped as
@@ -657,6 +1088,8 @@ interface AgentStepFields {
657
1088
  * steps resolve `$steps.*` refs into one before calling this). */
658
1089
  prompt: string | Selector<string>;
659
1090
  adapter?: string;
1091
+ /** See {@link AgentStep.cwd}. */
1092
+ cwd?: Selector<string>;
660
1093
  model?: Selector<string> | string;
661
1094
  sessionRef?: string;
662
1095
  sandbox?: AgentSandboxRef;
@@ -666,6 +1099,7 @@ interface AgentStepFields {
666
1099
  maxRetries?: number;
667
1100
  options?: Record<string, boolean | number | string>;
668
1101
  harness?: AgentHarness;
1102
+ agentTools?: readonly string[];
669
1103
  }
670
1104
  /** Build a runtime {@link AgentStep} from field values, applying the same
671
1105
  * defaults everywhere: `policy` defaults to `{ awaiting: "fail" }`. */
@@ -723,4 +1157,4 @@ declare class NodeFsPort implements FsPort {
723
1157
  lock(): Promise<FsLockHandle>;
724
1158
  }
725
1159
 
726
- export { type AgentHarness, type AgentRefResolution, type AgentSandboxRef, type AgentSessionHost, type AgentStep, type AgentStepFields, type ApprovalDecision, type ApprovalRequest, type ApprovalStep, type Bindings, type BranchStep, type CompileWorkflowOptions, type FanOutOutcome, type GateCommandResult, type GateCommandRunner, type GateReportEvent, type GateStep, type GroupStep, type HarnessKnowledgeSelector, type KnowledgeAppliedRecord, type LoopStep, type MapStep, type MaterializedKnowledge, NodeFsPort, type ParallelStep, type PipelineStep, type ResumeRequest, type RunStep, type RunWorkflowArgs, type RuntimeWorkflow, type Selector, type StepCache, type StepCacheEntry, type SubworkflowStep, type SuspendStep, type TolerantFanOutResult, type ToolStep, type TransformStep, WorkflowCompileError, type WorkflowRunResult, WorkflowSuspendedError, buildAgentStep, compileWorkflow, compileWorkflowManifest, evalPredicate, materializeKnowledge, resolveKnowledgeSelectors, resolveRef, resolveRefPrefixed, resolveValue, runWorkflow };
1160
+ 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, 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, WorkflowCompileError, type WorkflowInputValidation, type WorkflowRunResult, WorkflowSuspendedError, buildAgentStep, compileWorkflow, compileWorkflowManifest, evalPredicate, interpolateTemplate, isCompilableJsonSchema, materializeKnowledge, normalizeWorkflowInputsSchema, resolveKnowledgeSelectors, resolveRef, resolveRefPrefixed, resolveValue, runWorkflow, validateAgainstJsonSchema, validateWorkflowInput };