@agent-compose/sdk 0.2.2 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/README.md +145 -33
  2. package/dist/agent/agent-loop.d.ts +83 -5
  3. package/dist/agent/run-agent.d.ts +34 -9
  4. package/dist/client.d.ts +247 -99
  5. package/dist/index.d.ts +26 -11
  6. package/dist/index.js +1968 -746
  7. package/dist/processors/builtins.d.ts +35 -0
  8. package/dist/processors/index.d.ts +4 -0
  9. package/dist/processors/processor.d.ts +91 -0
  10. package/dist/processors/processor.test.d.ts +1 -0
  11. package/dist/processors/runner.d.ts +19 -0
  12. package/dist/request-context/index.d.ts +2 -0
  13. package/dist/request-context/request-context.d.ts +159 -0
  14. package/dist/request-context/request-context.test.d.ts +1 -0
  15. package/dist/runtimes/claude.d.ts +27 -50
  16. package/dist/runtimes/openai-desktop.js +1919 -742
  17. package/dist/runtimes/vercel.d.ts +34 -0
  18. package/dist/runtimes/vercel.js +474 -0
  19. package/dist/sandbox.d.ts +29 -25
  20. package/dist/step-invocation/__tests__/invoker.test.d.ts +1 -0
  21. package/dist/step-invocation/__tests__/protocol.test.d.ts +1 -0
  22. package/dist/step-invocation/__tests__/server.test.d.ts +1 -0
  23. package/dist/step-invocation/index.d.ts +25 -0
  24. package/dist/step-invocation/invoker.d.ts +65 -0
  25. package/dist/step-invocation/protocol.d.ts +44 -0
  26. package/dist/step-invocation/server.d.ts +63 -0
  27. package/dist/step-invocation/types.d.ts +72 -0
  28. package/dist/tools/coding.d.ts +49 -0
  29. package/dist/tools/coding.test.d.ts +1 -0
  30. package/dist/tools/index.d.ts +2 -0
  31. package/dist/types/events.d.ts +36 -0
  32. package/dist/types/execution-context.d.ts +22 -0
  33. package/dist/types/runtime.d.ts +32 -0
  34. package/dist/types/sandbox-environment.d.ts +5 -2
  35. package/dist/types/sandbox.d.ts +14 -12
  36. package/dist/types/workflow-metadata.d.ts +51 -0
  37. package/dist/types/workflow-plan.d.ts +19 -0
  38. package/dist/types/workflow.d.ts +57 -17
  39. package/dist/utils/bundler.d.ts +62 -3
  40. package/dist/workflow-steps/__tests__/observability.test.d.ts +1 -0
  41. package/dist/workflow-steps/index.d.ts +10 -0
  42. package/dist/workflow-steps/observability.d.ts +58 -0
  43. package/dist/workflow-steps/runner.d.ts +96 -0
  44. package/dist/workflow-steps/step.d.ts +25 -0
  45. package/dist/workflow-steps/types.d.ts +135 -0
  46. package/dist/workflow-steps/workflow-steps.test.d.ts +1 -0
  47. package/dist/workflow-steps/workflow.d.ts +50 -0
  48. package/dist/workflows/engine.d.ts +27 -13
  49. package/dist/workflows/invoke-child.d.ts +10 -0
  50. package/package.json +25 -15
  51. package/src/agent/agent-loop.ts +197 -26
  52. package/src/agent/run-agent.ts +40 -15
  53. package/src/client.ts +326 -76
  54. package/src/index.ts +124 -10
  55. package/src/processors/builtins.ts +72 -0
  56. package/src/processors/index.ts +15 -0
  57. package/src/processors/processor.ts +103 -0
  58. package/src/processors/runner.ts +42 -0
  59. package/src/request-context/index.ts +17 -0
  60. package/src/request-context/request-context.ts +302 -0
  61. package/src/runtimes/claude.ts +123 -254
  62. package/src/runtimes/vercel.ts +180 -0
  63. package/src/sandbox.ts +53 -21
  64. package/src/step-invocation/index.ts +33 -0
  65. package/src/step-invocation/invoker.ts +204 -0
  66. package/src/step-invocation/protocol.ts +57 -0
  67. package/src/step-invocation/server.ts +184 -0
  68. package/src/step-invocation/types.ts +70 -0
  69. package/src/tools/coding.ts +126 -0
  70. package/src/tools/index.ts +8 -0
  71. package/src/types/events.ts +40 -0
  72. package/src/types/execution-context.ts +30 -0
  73. package/src/types/runtime.ts +24 -0
  74. package/src/types/sandbox-environment.ts +7 -5
  75. package/src/types/sandbox.ts +16 -12
  76. package/src/types/workflow-metadata.ts +84 -0
  77. package/src/types/workflow-plan.ts +24 -0
  78. package/src/types/workflow.ts +139 -25
  79. package/src/utils/bundler.ts +213 -19
  80. package/src/utils/source-loader.ts +2 -2
  81. package/src/workflow-steps/index.ts +30 -0
  82. package/src/workflow-steps/observability.ts +103 -0
  83. package/src/workflow-steps/runner.ts +244 -0
  84. package/src/workflow-steps/step.ts +38 -0
  85. package/src/workflow-steps/types.ts +134 -0
  86. package/src/workflow-steps/workflow.ts +95 -0
  87. package/src/workflows/engine.ts +69 -40
  88. package/src/workflows/invoke-child.ts +29 -0
@@ -6,29 +6,36 @@
6
6
  * - `ctx` carries facts + observability about THIS run (id, input,
7
7
  * setMetadata, step). Metadata bag.
8
8
  * - `sandbox` is a capability handed to you by the engine for doing work
9
- * (exec commands, write files). Pass it to `runAgent({ sandbox, ... })`
9
+ * (exec commands, write files). Pass it to `agent({ sandbox, ... })`
10
10
  * and to any helper that takes a SandboxProvider (git utilities, file
11
11
  * writers). Constructed once per run; reuse it.
12
12
  *
13
- * LLM agent loops live in `runAgent(opts)` (sdk/src/agent/run-agent.ts).
13
+ * LLM agent loops live in `agent(opts)` (sdk/src/agent/run-agent.ts).
14
14
  * Invoking other workflows uses `AgentComposeClient.invoke[AndWait](...)`.
15
15
  */
16
16
  import type { SandboxNetworkPolicy } from "../sandbox.js";
17
17
  import type { SandboxProvider } from "./sandbox.js";
18
- /** The identity of this workflow run. */
19
- export interface WorkflowRun {
20
- id: string;
18
+ import type { AgentLifecycleEvent } from "../agent/agent-loop.js";
19
+ import type { Processor } from "../processors/processor.js";
20
+ import type { StepWorkflowDefinition } from "../workflow-steps/workflow.js";
21
+ import type { Workflow } from "../workflow-steps/types.js";
22
+ import type { BaseExecutionContext } from "./execution-context.js";
23
+ export type { WorkflowRun, InvokeChild, BaseExecutionContext } from "./execution-context.js";
24
+ export type { WorkflowMetadata } from "./workflow-metadata.js";
25
+ export interface AgentEventSink {
26
+ emit(event: AgentLifecycleEvent): void | Promise<void>;
21
27
  }
22
- /** Turn/iteration budget for `runAgent(opts)`. Re-exported here so authors
28
+ import type { WorkflowMemoryConfig } from "./workflow-metadata.js";
29
+ export type { WorkflowMemoryConfig };
30
+ /** Turn/iteration budget for `agent(opts)`. Re-exported here so authors
23
31
  * can type per-invoke budget overrides they pass as workflow input. */
24
32
  export interface AgentBudget {
25
33
  turnsPerIteration: number;
26
34
  maxIterations: number;
27
35
  }
28
36
  /** Context passed to a workflow function — facts + observability for this run. */
29
- export interface WorkflowCtx {
30
- run: WorkflowRun;
31
- input?: Record<string, unknown>;
37
+ export interface WorkflowCtx<TInput extends Record<string, unknown> = Record<string, unknown>> extends Omit<BaseExecutionContext, "sandbox"> {
38
+ input?: TInput;
32
39
  /** Persist key-value metadata on the run record (e.g. prUrl, planUrl). */
33
40
  setMetadata: (data: Record<string, unknown>) => Promise<void>;
34
41
  /**
@@ -37,9 +44,19 @@ export interface WorkflowCtx {
37
44
  * visible on the run's timeline (setup, external API calls, submit).
38
45
  */
39
46
  step<T>(name: string, fn: () => Promise<T>): Promise<T>;
47
+ /** Pass to `agent({ events: ctx.agentEvents })` to stream agent lifecycle events. */
48
+ agentEvents: AgentEventSink;
49
+ /**
50
+ * Workflow-level processors registered via `defineWorkflow({ processors })`.
51
+ * Read-only here. Authors merge with agent-specific lists when calling
52
+ * `agent({ processors: [...ctx.processors, mySpecific] })`.
53
+ *
54
+ * Empty array when the workflow declared no processors.
55
+ */
56
+ processors: readonly Processor[];
40
57
  }
41
- /** A workflow is `async (ctx, sandbox) => T`. */
42
- export type WorkflowFn<T = unknown> = (ctx: WorkflowCtx, sandbox: SandboxProvider) => Promise<T>;
58
+ /** A workflow is `async (ctx, sandbox) => TOutput`. */
59
+ export type WorkflowFn<TOutput = unknown, TInput extends Record<string, unknown> = Record<string, unknown>> = (ctx: WorkflowCtx<TInput>, sandbox: SandboxProvider) => Promise<TOutput>;
43
60
  /**
44
61
  * Declare a workflow with server-side metadata.
45
62
  * Use `export default defineWorkflow({ run, networkPolicy, ... })` to attach
@@ -49,8 +66,8 @@ export type WorkflowFn<T = unknown> = (ctx: WorkflowCtx, sandbox: SandboxProvide
49
66
  * Without defineWorkflow, a plain `export default async (ctx) => {...}` still
50
67
  * works — the workflow just runs with secrets passed directly in env.
51
68
  */
52
- export interface WorkflowDefinition<T = unknown> {
53
- run: WorkflowFn<T>;
69
+ export interface WorkflowDefinition<TOutput = unknown, TInput extends Record<string, unknown> = Record<string, unknown>> {
70
+ run: WorkflowFn<TOutput, TInput>;
54
71
  /**
55
72
  * Reference to a snapshot the runner should boot from at run start.
56
73
  * Accepts a run UUID, a workflow name, or `name@version` — any workflow
@@ -82,6 +99,13 @@ export interface WorkflowDefinition<T = unknown> {
82
99
  * }
83
100
  */
84
101
  networkPolicy?: SandboxNetworkPolicy;
102
+ /**
103
+ * Workflow-level processor chain — runs around every `agent(...)` loop the
104
+ * workflow body launches, ahead of any agent-specific processors. Use for
105
+ * org-wide gating (deny destructive tools, require scopes). Authors merge
106
+ * with agent-specific lists via `[...ctx.processors, ...]`.
107
+ */
108
+ processors?: readonly Processor[];
85
109
  /**
86
110
  * Optional placeholder env var values for secrets referenced in the network policy.
87
111
  * By default, brokered secrets are removed from the runner env entirely — the real
@@ -96,16 +120,32 @@ export interface WorkflowDefinition<T = unknown> {
96
120
  * }
97
121
  */
98
122
  placeholders?: Record<string, string>;
123
+ /** Workflow Memory extraction. Defaults to "default" when omitted.
124
+ * Set false to skip memory extraction for this workflow, or point at a
125
+ * custom memory workflow once custom extractors are supported. */
126
+ memory?: WorkflowMemoryConfig;
99
127
  }
100
128
  /**
101
- * Attach server-side metadata to a workflow function.
102
- * The returned function is a valid WorkflowFn with metadata fields attached
103
- * for the bundler / registration layer to read.
129
+ * Declare a workflow. Two forms; both return a `Workflow` whose
130
+ * `metadata` field carries the server-readable declarations.
131
+ *
132
+ * Run form — wraps the legacy `(ctx, sandbox) => T` body as a single
133
+ * step internally. Lifecycle granularity is workflow-level (one step).
134
+ *
135
+ * Step form — the typed builder. Multiple steps with explicit input/
136
+ * output schemas, durability + replay at every step boundary.
137
+ *
138
+ * Both forms produce the same downstream shape: the bundler reads
139
+ * `workflow.metadata.networkPolicy` / `placeholders` / etc.; the server
140
+ * stores the step plan; runner subprocesses execute one step at a time
141
+ * via the StepInvocation seam.
104
142
  */
105
- export declare function defineWorkflow<T = unknown>(def: WorkflowDefinition<T>): WorkflowFn<T> & Pick<WorkflowDefinition, "networkPolicy" | "placeholders" | "snapshot" | "saveSnapshot">;
143
+ export declare function defineWorkflow<TOutput = unknown, TInput extends Record<string, unknown> = Record<string, unknown>>(def: WorkflowDefinition<TOutput, TInput>): Workflow<TInput, TOutput>;
144
+ export declare function defineWorkflow<TInput, TOutput>(def: StepWorkflowDefinition<TInput, TOutput>): import("../workflow-steps/workflow.js").WorkflowBuilder<TInput, TInput>;
106
145
  /** Observability-only lifecycle hooks — passed to the workflow engine, not workflow authors. */
107
146
  export interface WorkflowHooks {
108
147
  onStepStart?: (step: string) => void;
109
148
  onStepComplete?: (step: string, durationMs: number) => void;
110
149
  onStepFail?: (step: string, durationMs: number, reason: string) => void;
150
+ onAgentLifecycleEvent?: (event: AgentLifecycleEvent) => void;
111
151
  }
@@ -4,13 +4,51 @@
4
4
  * Shared between the CLI (agentc register) and tests. Uses `Bun.build`
5
5
  * to bundle TypeScript sources into a self-contained ESM module.
6
6
  *
7
- * Post de-broker there are no agent bundles to produce — workflows embed
8
- * their agent loops inline via `runAgent(...)`, and workflows invoke other
9
- * workflows via the public SDK client.
7
+ * Two layers of validation run before the bundle leaves this function:
8
+ * 1. AST check (`assertDefaultExportIsDefineWorkflow`) on the bundled
9
+ * output — proves the default export is a `defineWorkflow(...)` call,
10
+ * not a raw `async function`. Catches harnesses that hand-roll their
11
+ * own register flow without going through `defineWorkflow`.
12
+ * 2. Runtime brand check (`isWorkflow`) — confirms the value the module
13
+ * actually exports carries the `WORKFLOW_BRAND` symbol that
14
+ * `defineWorkflow` / `defineWorkflow(...).build()` stamps. Catches
15
+ * indirection (re-exports, aliasing) that the AST walk can't follow.
16
+ *
17
+ * Both checks happen here, in the user's dev environment. The server NEVER
18
+ * parses or imports user source — it only validates the structured manifest
19
+ * this function returns alongside the bundled bytes, and cross-checks the
20
+ * manifest's `sourceHash` against the source it received.
10
21
  */
22
+ import type { WorkflowMemoryConfig } from "../types/workflow.js";
11
23
  import type { SandboxNetworkPolicy } from "../sandbox.js";
24
+ import { type WorkflowPlan } from "../types/workflow-plan.js";
25
+ /** Bumped when the manifest contract changes in a way the server should
26
+ * notice. The server pins its manifest schema to this exact value (no
27
+ * `>=` accepted) so a manifest forged with a future version is refused
28
+ * by zod before any other validation runs. */
29
+ export declare const BUNDLER_VERSION = 1;
30
+ /**
31
+ * Manifest emitted alongside the bundled source. Fully serialisable JSON;
32
+ * the server validates its shape with zod and never inspects the source
33
+ * bytes themselves. Manifest is bound to source via `sourceHash` so the
34
+ * server can refuse manifests detached from the source they describe.
35
+ */
36
+ export interface WorkflowManifest {
37
+ /** Always `true`. The bundler stamps this only after both validation
38
+ * gates pass; the server's zod schema pins it as `z.literal(true)`. */
39
+ definitionBrand: true;
40
+ /** sha256 hex digest of `source`. The server cross-checks this against
41
+ * `hashSource(body.source)` to bind the manifest to the bytes it describes. */
42
+ sourceHash: string;
43
+ /** Bundler contract version. See `BUNDLER_VERSION`. */
44
+ bundlerVersion: number;
45
+ }
46
+ export declare class WorkflowSourceValidationError extends Error {
47
+ constructor(message: string);
48
+ }
12
49
  export interface BundledWorkflow {
13
50
  source: string;
51
+ manifest: WorkflowManifest;
14
52
  networkPolicy?: SandboxNetworkPolicy;
15
53
  placeholders?: Record<string, string>;
16
54
  /** Run UUID, workflow name, or `name@version` referencing the snapshot
@@ -19,7 +57,28 @@ export interface BundledWorkflow {
19
57
  snapshot?: string;
20
58
  /** Default capture-on-success flag from the workflow definition. */
21
59
  saveSnapshot?: boolean;
60
+ workflowPlan: WorkflowPlan;
61
+ /** Workflow Memory extractor config. */
62
+ memory?: WorkflowMemoryConfig;
22
63
  }
64
+ /**
65
+ * Parse the bundled source and assert the default export is a CallExpression
66
+ * to an identifier named `defineWorkflow` (or to a `.step(...).build()` chain
67
+ * rooted in one). This catches:
68
+ *
69
+ * - Raw TypeScript shipped to the registry (parse fails on type syntax)
70
+ * - `export default async function run(ctx)` (default is not a call)
71
+ * - `export default { run }` etc. (default is not a call)
72
+ * - `export default someFunction` (default is not a defineWorkflow call)
73
+ *
74
+ * The check is intentionally syntactic — it doesn't follow imports or trace
75
+ * through aliases. The runtime brand check (`isWorkflow`) picks up edge
76
+ * cases the AST can't see, so the two layers compose.
77
+ *
78
+ * Exported so the unit tests can exercise this exact code path; do NOT call
79
+ * from outside the SDK — `bundleWorkflow` is the supported entrypoint.
80
+ */
81
+ export declare function assertDefaultExportIsDefineWorkflow(source: string, label: string): void;
23
82
  /** Bundle a workflow from source. */
24
83
  export declare function bundleWorkflow(workflowPath: string, overrides?: {
25
84
  networkPolicy?: SandboxNetworkPolicy;
@@ -0,0 +1,10 @@
1
+ export { defineStep } from "./step.js";
2
+ export type { DefineStepOpts } from "./step.js";
3
+ export { createStepWorkflow, isWorkflow } from "./workflow.js";
4
+ export type { StepWorkflowDefinition, WorkflowBuilder } from "./workflow.js";
5
+ export { runWorkflowSteps, runWorkflowSingleStep, StepValidationError, WorkflowInputValidationError, WorkflowOutputValidationError, } from "./runner.js";
6
+ export type { RunWorkflowStepsOpts, RunWorkflowStepsResult, RunWorkflowSingleStepOpts, RunWorkflowSingleStepResult, } from "./runner.js";
7
+ export type { Step, StepContext, StepRunResult, Workflow, } from "./types.js";
8
+ export { WORKFLOW_BRAND } from "./types.js";
9
+ export { StepObservabilityCollector } from "./observability.js";
10
+ export type { StepObservability, SubStepEvent } from "./observability.js";
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Per-step observability collector — buffers metadata writes, agent
3
+ * lifecycle events, and `ctx.step("name", fn)` sub-step records during one
4
+ * step's execution. The runner flushes the snapshot through the
5
+ * tokenised stdout sentinel; the activity persists the snapshot to the
6
+ * run's metadata + lifecycle event tables.
7
+ *
8
+ * STREAMING — known gap. `agentEvents` are currently batched at step
9
+ * end. For long-running steps that emit many agent events, this hides
10
+ * progress from the dashboard until the step completes. The follow-up
11
+ * is to bring back a minimal `/internal/runs/:runId/steps/:stepIndex/events`
12
+ * route gated by a per-step JIT-signed token (mint in the activity,
13
+ * stamp into the runner env, verify on receive) so `agentEvents.emit`
14
+ * POSTs in real time. Until then `metadata` and `subSteps` are
15
+ * effectively boundary events (no streaming need) and batching them
16
+ * matches their natural granularity.
17
+ */
18
+ import type { AgentLifecycleEvent } from "../agent/agent-loop.js";
19
+ import type { AgentEventSink } from "../types/workflow.js";
20
+ /** One named sub-step (from `ctx.step("name", async () => ...)`).
21
+ * Becomes a `workflow_substep_*` lifecycle event on the run timeline. */
22
+ export interface SubStepEvent {
23
+ name: string;
24
+ startedAt: number;
25
+ durationMs: number;
26
+ status: "completed" | "failed";
27
+ /** Present when status="failed" — the user error's message. */
28
+ error?: string;
29
+ }
30
+ /** Snapshot of everything the step's observability hooks recorded. All
31
+ * fields are optional so a step that uses none of the hooks produces an
32
+ * empty snapshot (and the wire payload omits the field entirely). */
33
+ export interface StepObservability {
34
+ metadata?: Record<string, unknown>;
35
+ events?: AgentLifecycleEvent[];
36
+ subSteps?: SubStepEvent[];
37
+ }
38
+ /**
39
+ * Append-only collector bound to a single step's `StepContext`. The
40
+ * step's `setMetadata` / `step` / `agentEvents` properties all point at
41
+ * this instance. After the step finishes, the engine calls `snapshot()`
42
+ * to extract the bundle for transport.
43
+ *
44
+ * Metadata writes merge (later keys win) — matches the legacy
45
+ * `mergeRunMetadata` semantics so authors don't see a behaviour change
46
+ * across migrations.
47
+ */
48
+ export declare class StepObservabilityCollector {
49
+ private metadata;
50
+ private events;
51
+ private subSteps;
52
+ readonly setMetadata: (data: Record<string, unknown>) => Promise<void>;
53
+ readonly step: <T>(name: string, fn: () => Promise<T>) => Promise<T>;
54
+ readonly agentEvents: AgentEventSink;
55
+ /** Snapshot the accumulated state. Returns `undefined` when nothing
56
+ * was recorded so the wire payload can drop the field entirely. */
57
+ snapshot(): StepObservability | undefined;
58
+ }
@@ -0,0 +1,96 @@
1
+ /**
2
+ * `runWorkflowSteps` — in-process step executor for a `Workflow`.
3
+ *
4
+ * Walks the step chain sequentially:
5
+ * 1. Validate workflow input against `workflow.input`.
6
+ * 2. For each step:
7
+ * a. Optionally check `getCachedOutput(stepIndex, step.name)` — if a
8
+ * previous run completed this step, skip and reuse its output.
9
+ * (Phase 1b uses this for crash recovery.)
10
+ * b. Validate current input against `step.input`.
11
+ * c. Call `step.run({ input, ... })`.
12
+ * d. Validate return value against `step.output`.
13
+ * e. Call `onStepCompleted(stepIndex, step.name, output, durationMs)`.
14
+ * f. Output becomes the next step's input.
15
+ * 3. Validate final output against `workflow.output`.
16
+ *
17
+ * Errors during any step bubble through `onStepFailed` and re-throw so the
18
+ * caller can decide whether to mark the run failed.
19
+ *
20
+ * The cache + completion hooks are injection points — a sandbox engine
21
+ * adapter (Phase 1c) wires them to `workflow_step_runs` rows. The default
22
+ * is an in-process map for tests.
23
+ */
24
+ import { z } from "zod";
25
+ import type { Workflow, StepRunResult } from "./types.js";
26
+ import type { RequestContext } from "../request-context/request-context.js";
27
+ import type { SandboxProvider } from "../types/sandbox.js";
28
+ import type { WorkflowRun, WorkflowCtx } from "../types/workflow.js";
29
+ import { type StepObservability } from "./observability.js";
30
+ export declare class StepValidationError extends Error {
31
+ readonly stepName: string;
32
+ readonly side: "input" | "output";
33
+ readonly kind: "step-validation";
34
+ constructor(stepName: string, side: "input" | "output", cause: z.ZodError);
35
+ }
36
+ export declare class WorkflowInputValidationError extends Error {
37
+ readonly workflowId: string;
38
+ readonly kind: "workflow-input-validation";
39
+ constructor(workflowId: string, cause: z.ZodError);
40
+ }
41
+ export declare class WorkflowOutputValidationError extends Error {
42
+ readonly workflowId: string;
43
+ readonly kind: "workflow-output-validation";
44
+ constructor(workflowId: string, cause: z.ZodError);
45
+ }
46
+ export interface RunWorkflowStepsOpts<TInput, TOutput> {
47
+ workflow: Workflow<TInput, TOutput>;
48
+ input: TInput;
49
+ run: WorkflowRun;
50
+ requestContext: RequestContext;
51
+ sandbox?: SandboxProvider;
52
+ abortSignal?: AbortSignal;
53
+ /**
54
+ * Crash-recovery hook. Called before a step executes. Return the cached
55
+ * output to skip execution; return undefined to run the step.
56
+ *
57
+ * Phase 1b implementations will look up `workflow_step_runs` rows for
58
+ * (runId, stepIndex, stepName) and return completed step outputs here.
59
+ * Default: always undefined (no caching).
60
+ */
61
+ getCachedOutput?(stepIndex: number, stepName: string): unknown | undefined | Promise<unknown | undefined>;
62
+ /** Fire after a step's `execute` and output validation succeed. */
63
+ onStepCompleted?(stepIndex: number, stepName: string, output: unknown, durationMs: number): void | Promise<void>;
64
+ /** Fire when a step throws or fails validation. The error is re-thrown after this returns. */
65
+ onStepFailed?(stepIndex: number, stepName: string, error: Error, durationMs: number): void | Promise<void>;
66
+ /** Fire before a step runs (after cache check / before input validation). */
67
+ onStepStarted?(stepIndex: number, stepName: string): void | Promise<void>;
68
+ /** Child workflow invocation implementation. Defaults to a clear unsupported error. */
69
+ invokeChild?: WorkflowCtx["invokeChild"];
70
+ }
71
+ export interface RunWorkflowStepsResult<TOutput> {
72
+ output: TOutput;
73
+ /** Per-step results in order — useful for tests and lifecycle event emission. */
74
+ steps: ReadonlyArray<{
75
+ name: string;
76
+ } & StepRunResult>;
77
+ }
78
+ export interface RunWorkflowSingleStepOpts {
79
+ workflow: Workflow<unknown, unknown>;
80
+ stepIndex: number;
81
+ input: unknown;
82
+ run: WorkflowRun;
83
+ requestContext: RequestContext;
84
+ sandbox?: SandboxProvider;
85
+ abortSignal?: AbortSignal;
86
+ invokeChild?: WorkflowCtx["invokeChild"];
87
+ }
88
+ /** Result of one step run — output plus whatever the step's observability
89
+ * hooks recorded. `observability` is undefined when nothing was buffered,
90
+ * so callers can drop it from the wire payload entirely. */
91
+ export interface RunWorkflowSingleStepResult {
92
+ output: unknown;
93
+ observability?: StepObservability;
94
+ }
95
+ export declare function runWorkflowSingleStep(opts: RunWorkflowSingleStepOpts): Promise<RunWorkflowSingleStepResult>;
96
+ export declare function runWorkflowSteps<TInput, TOutput>(opts: RunWorkflowStepsOpts<TInput, TOutput>): Promise<RunWorkflowStepsResult<TOutput>>;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * `defineStep` — typed factory for one workflow step.
3
+ *
4
+ * Required:
5
+ * - `name` stable identifier (also the step row key)
6
+ * - `input` Zod schema; validated before `run` executes
7
+ * - `output` Zod schema; validated against `run`'s return value
8
+ * - `run` step body
9
+ *
10
+ * Validation is required (not optional) because the durability story rides
11
+ * on every step boundary being a recordable, replayable JSON value. A step
12
+ * without a schema is invisible to the engine's persistence layer.
13
+ *
14
+ * Type inference flows: `defineStep` infers TInput/TOutput from the schemas
15
+ * so `run(ctx)` is fully typed via `ctx.input` at the call site.
16
+ */
17
+ import type { z } from "zod";
18
+ import type { Step, StepContext } from "./types.js";
19
+ export interface DefineStepOpts<TInput, TOutput> {
20
+ name: string;
21
+ input: z.ZodType<TInput>;
22
+ output: z.ZodType<TOutput>;
23
+ run(ctx: StepContext<TInput>): TOutput | Promise<TOutput>;
24
+ }
25
+ export declare function defineStep<TInput, TOutput>(opts: DefineStepOpts<TInput, TOutput>): Step<TInput, TOutput>;
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Shared types for the declarative workflow shape.
3
+ *
4
+ * Workflows-as-data — every workflow is a list of typed steps with explicit
5
+ * input/output Zod schemas. Mastra's shape, adapted for our runtime.
6
+ *
7
+ * Why: durable suspend/resume requires step boundaries to be addressable as
8
+ * data, not opaque async-function bodies. Each step's input + output is
9
+ * serialisable JSON so engine adapters (in-process, sandbox, future
10
+ * Inngest/Temporal) can record completion and replay from the last
11
+ * completed step.
12
+ */
13
+ import type { z } from "zod";
14
+ import type { BaseExecutionContext } from "../types/execution-context.js";
15
+ import type { AgentEventSink } from "../types/workflow.js";
16
+ import type { WorkflowMetadata } from "../types/workflow-metadata.js";
17
+ /**
18
+ * Per-step execution context. Threaded into every step's `execute(...)` so
19
+ * the step can read tenant identity and run identity, log progress, and
20
+ * invoke sandbox commands.
21
+ *
22
+ * Sandbox is optional because in-process tests run steps without a sandbox.
23
+ */
24
+ export interface StepContext<TInput = unknown> extends BaseExecutionContext {
25
+ /** Validated step input — already parsed against the step's `input` schema. */
26
+ input: TInput;
27
+ /** Step name — useful for logging. */
28
+ stepName: string;
29
+ /** AbortSignal that fires on workflow cancellation. */
30
+ abortSignal: AbortSignal;
31
+ /**
32
+ * Merge key-value metadata onto the run record. Buffered during the
33
+ * step and flushed when the step completes; the durable engine writes
34
+ * it via the same `mergeRunMetadata` path the legacy runner used, so
35
+ * the dashboard sees the same shape. Later keys win.
36
+ */
37
+ setMetadata(data: Record<string, unknown>): Promise<void>;
38
+ /**
39
+ * Wrap a named sub-step for observability. Emits
40
+ * `workflow_substep_started` / `workflow_substep_completed` /
41
+ * `workflow_substep_failed` lifecycle events on the run timeline with
42
+ * the measured `durationMs`. Use for long sub-phases inside one step
43
+ * (setup, external API call, submit).
44
+ *
45
+ * The block runs even if observability flushing fails — instrumentation
46
+ * never breaks the workflow.
47
+ */
48
+ step<T>(name: string, fn: () => Promise<T>): Promise<T>;
49
+ /**
50
+ * Sink for `agent({ events: ctx.agentEvents })`. Each emitted
51
+ * `AgentLifecycleEvent` is buffered and flushed at step end, then
52
+ * fanned out to the run's SSE stream. Mid-step the events are not
53
+ * yet visible to consumers — that's a deliberate tradeoff for the
54
+ * step-mode, /internal/*-free runner.
55
+ */
56
+ agentEvents: AgentEventSink;
57
+ }
58
+ /**
59
+ * Step definition — a single typed unit of work in a workflow chain.
60
+ *
61
+ * Both `input` and `output` are required: the workflow shape's value
62
+ * comes from every step boundary being recordable. Optional schemas
63
+ * would defeat the durability story.
64
+ *
65
+ * `run` may be sync or async; the engine awaits it uniformly.
66
+ */
67
+ export interface Step<TInput, TOutput> {
68
+ /** Stable identifier — used as the step row key and for logs/metrics. */
69
+ readonly name: string;
70
+ /** Zod schema validated against the step's input before `run`. */
71
+ readonly input: z.ZodType<TInput>;
72
+ /** Zod schema validated against `run`'s return value. */
73
+ readonly output: z.ZodType<TOutput>;
74
+ /** Step body. Receives a `StepContext<TInput>` and returns the typed output. */
75
+ run(ctx: StepContext<TInput>): TOutput | Promise<TOutput>;
76
+ }
77
+ /**
78
+ * The result of running one step. Engine adapters persist these into the
79
+ * `workflow_step_runs` table (Phase 1b) so subsequent runs can skip
80
+ * completed steps. `observability` carries the snapshot of
81
+ * `ctx.setMetadata` / `ctx.step` / `ctx.agentEvents` recorded during
82
+ * the step; undefined when no hooks were used.
83
+ */
84
+ export type StepRunResult<TOutput = unknown> = {
85
+ status: "completed";
86
+ output: TOutput;
87
+ durationMs: number;
88
+ observability?: import("./observability.js").StepObservability;
89
+ } | {
90
+ status: "failed";
91
+ error: string;
92
+ durationMs: number;
93
+ observability?: import("./observability.js").StepObservability;
94
+ };
95
+ /**
96
+ * Workflow — a list of typed steps plus the workflow's input/output
97
+ * schemas plus its server-side metadata bag. Returned by `defineWorkflow(...)`
98
+ * (run form) and `defineWorkflow(...).step(...)...build()` (step form).
99
+ * Engine adapters consume this shape.
100
+ *
101
+ * `input` validates the workflow input before the first step runs.
102
+ * `output` validates the final step's output before the workflow
103
+ * completes successfully.
104
+ *
105
+ * `metadata` carries server-readable declarations — network policy,
106
+ * brokered-secret placeholders, snapshot reference/capture defaults,
107
+ * workflow-level processor chain. The bundler reads these at registration
108
+ * time. Always present, possibly empty.
109
+ *
110
+ * Historically named `CompiledWorkflow` back when an uncompiled form
111
+ * existed (a bare `WorkflowFn` was its own engine input). After the
112
+ * legacy delete in #55 every workflow goes through this shape, so the
113
+ * "Compiled" prefix carried no information — it's just `Workflow` now.
114
+ */
115
+ export interface Workflow<TInput, TOutput> {
116
+ /** Stable workflow identifier. */
117
+ readonly id: string;
118
+ /** Validates the initial workflow input. */
119
+ readonly input: z.ZodType<TInput>;
120
+ /** Validates the final step's output. */
121
+ readonly output: z.ZodType<TOutput>;
122
+ /** Sequential list of steps to execute. Type chain proven at compile time. */
123
+ readonly steps: ReadonlyArray<Step<any, any>>;
124
+ /** Server-readable declarations: network policy, placeholders, snapshot
125
+ * defaults, processors. Always set; empty object when nothing declared. */
126
+ readonly metadata: WorkflowMetadata;
127
+ }
128
+ /**
129
+ * Discriminator brand stamped on every `Workflow` so `isWorkflow` can
130
+ * distinguish a real workflow from an arbitrary default export at
131
+ * registration / dispatch time. Workflow authors never see this —
132
+ * `defineWorkflow` and `defineWorkflow(...).step(...).build()` set it
133
+ * internally.
134
+ */
135
+ export declare const WORKFLOW_BRAND: unique symbol;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,50 @@
1
+ /**
2
+ * `createStepWorkflow` — internal typed builder for a step-based workflow.
3
+ *
4
+ * const wf = defineWorkflow({
5
+ * id: "my-workflow",
6
+ * input: z.object({ url: z.string().url() }),
7
+ * output: z.object({ pageTitle: z.string() }),
8
+ * })
9
+ * .step(fetchStep)
10
+ * .step(parseStep)
11
+ * .build();
12
+ *
13
+ * The chain enforces that each step's input matches the previous step's
14
+ * output at compile time. `.build()` returns a `Workflow` and verifies
15
+ * at construction time that the workflow's declared `output` schema
16
+ * matches the final step's `output` schema.
17
+ *
18
+ * Workflows-as-data — the result is consumable by any engine adapter
19
+ * (in-process today; sandbox + Inngest/Temporal in future PRs).
20
+ */
21
+ import type { z } from "zod";
22
+ import type { Step, Workflow } from "./types.js";
23
+ import type { SandboxNetworkPolicy } from "../sandbox.js";
24
+ import type { Processor } from "../processors/processor.js";
25
+ export interface WorkflowBuilder<TInput, TCurrent> {
26
+ /** Append a step whose input matches the current builder output. */
27
+ step<TOutput>(step: Step<TCurrent, TOutput>): WorkflowBuilder<TInput, TOutput>;
28
+ /**
29
+ * Freeze the chain into a `Workflow`. Throws when no steps have been
30
+ * added or when the final step's `output` schema is not the same Zod
31
+ * schema instance as the workflow's declared `output` schema.
32
+ *
33
+ * The schema-identity check catches the "I edited one of two schemas"
34
+ * footgun that's easy to introduce when output types drift.
35
+ */
36
+ build(): Workflow<TInput, TCurrent>;
37
+ }
38
+ export interface StepWorkflowDefinition<TInput, TOutput> {
39
+ id: string;
40
+ input: z.ZodType<TInput>;
41
+ output: z.ZodType<TOutput>;
42
+ networkPolicy?: SandboxNetworkPolicy;
43
+ placeholders?: Record<string, string>;
44
+ snapshot?: string;
45
+ saveSnapshot?: boolean;
46
+ processors?: readonly Processor[];
47
+ }
48
+ export declare function createStepWorkflow<TInput, TOutput>(opts: StepWorkflowDefinition<TInput, TOutput>): WorkflowBuilder<TInput, TInput>;
49
+ /** Type guard — true when `value` is a `Workflow`. */
50
+ export declare function isWorkflow(value: unknown): value is Workflow<unknown, unknown>;
@@ -1,17 +1,17 @@
1
1
  /**
2
- * Workflow engine — runs a workflow function with a minimal ctx.
2
+ * Workflow engine — drives a `Workflow`'s steps.
3
3
  *
4
- * Post de-broker there is no agent-spawning primitive here. Workflows that
5
- * want to embed an LLM agent call `runAgent(opts)` directly from
6
- * `@agent-compose/sdk`. Workflows that want to invoke OTHER workflows use
7
- * `AgentComposeClient.invoke[AndWait](...)`. The engine's only job now is:
4
+ * Single execution entry point: `runWorkflow`. Every workflow has the same
5
+ * shape (defineWorkflow always returns a `Workflow`), so there's one
6
+ * runner.
8
7
  *
9
- * - build the `ctx` the workflow function receives
10
- * - funnel `onStepStart / onStepComplete / onStepFail` hooks
11
- * - classify errors into `WorkflowError` vs `EngineError` for the runner
12
- * harness to emit on /fail
8
+ * Errors classified into `WorkflowError` (user code threw) vs `EngineError`
9
+ * (platform problem) for the runner harness to surface upstream.
13
10
  */
14
- import type { WorkflowHooks, WorkflowCtx, WorkflowFn } from "../types/workflow.js";
11
+ import type { WorkflowHooks, WorkflowCtx } from "../types/workflow.js";
12
+ import { RequestContext } from "../request-context/request-context.js";
13
+ import type { Workflow } from "../workflow-steps/types.js";
14
+ import { type RunWorkflowStepsOpts } from "../workflow-steps/runner.js";
15
15
  /**
16
16
  * Thrown from `runWorkflow` when the authored workflow threw.
17
17
  * User-level; not a bug in the platform.
@@ -44,7 +44,21 @@ export interface WorkflowResult<T = unknown> {
44
44
  /** The value returned by the workflow function. `null` for void workflows. */
45
45
  response: T | null;
46
46
  }
47
- export declare function runWorkflow<T = unknown>(wf: WorkflowFn<T>, ctx: Pick<WorkflowCtx, "run" | "input">, opts?: {
47
+ export interface RunWorkflowOptions {
48
48
  hooks?: WorkflowHooks;
49
- setMetadata?: (data: Record<string, unknown>) => Promise<void>;
50
- }): Promise<WorkflowResult<T>>;
49
+ requestContext?: RequestContext;
50
+ getCachedOutput?: RunWorkflowStepsOpts<unknown, unknown>["getCachedOutput"];
51
+ onStepStarted?: RunWorkflowStepsOpts<unknown, unknown>["onStepStarted"];
52
+ onStepCompleted?: RunWorkflowStepsOpts<unknown, unknown>["onStepCompleted"];
53
+ onStepFailed?: RunWorkflowStepsOpts<unknown, unknown>["onStepFailed"];
54
+ /** Provider-specific child workflow invocation. Temporal/Inngest providers
55
+ * inject their native child-workflow primitive; the LocalProvider injects
56
+ * the public Agent Compose API client. */
57
+ invokeChild?: WorkflowCtx["invokeChild"];
58
+ }
59
+ export declare function runWorkflow<TInput, TOutput>(wf: Workflow<TInput, TOutput>, ctx: {
60
+ run: {
61
+ id: string;
62
+ };
63
+ input?: TInput;
64
+ }, opts?: RunWorkflowOptions): Promise<WorkflowResult<TOutput>>;