/** * workflow() — sequential steps whose hand-offs are checked by the compiler. * * WHY this exists: `Sequence` is the workhorse for "A, then B, then C", * and every step it accepts has the same shape — takes `{ message }`, * returns `string`. That is exactly right for chaining LLM calls, and * exactly wrong the moment a step wants to hand the next one something * structured: `Sequence` coerces any non-string step output to `''` * (Sequence.ts, the step `outputMapper`), so a step that returns a parsed * ticket silently hands the next step nothing at all. The mistake shows up * as an empty prompt three steps later, at runtime, in production. * * `workflow()` closes that gap from both ends: * * - **At compile time** — step N's OUTPUT type must be what step N+1 * accepts. A `Runner<{ message: string }, Ticket>` followed by a * `Runner<{ orderId: string }, string>` does not compile. The chain is * proven before you run it, not debugged after. * - **At run time** — a step's value is handed to the next step * UNCHANGED. Objects stay objects. The one convenience is the house * convention: a step that returns a `string` feeds the next step's * `{ message }`, because that is what every LLM runner here wants. * * Pattern: Adapter over footprintjs's `addSubFlowChartNext`, with the * type-level handoff proof carried by overloads (1–8 steps). * Role: core-flow/ layer, alongside Sequence/Parallel/Conditional/Loop. * Pure control flow — no LLM dependency. * Emits: agentfootprint.composition.enter / exit, reported as kind * `'Sequence'` — a workflow IS a sequential composition, and * widening the public `CompositionKind` union would break * exhaustive switches in consumer code for no behavioural gain. * * THREE HONEST LIMITS, all inherited from the engine and all verified in * `test/core-flow/scenario/Workflow.test.ts` — worth knowing before you * put rich objects on the wire: * * 1. Only PLAIN DATA crosses a step boundary. A value with a prototype * (Date, Map, Set, a class instance) arrives as `{}`, and `undefined` * fields are dropped. Send strings, numbers, arrays and plain * objects; send a timestamp as an ISO string, not a `Date`. * 2. A step must RETURN its output — the value handed forward is the * step chart's traversal result. A step whose last stage returns * nothing hands its whole scope forward instead. * 3. The workflow's own input keys stay visible to LATER steps too * (footprintjs's `getArgs()` inherits the run's arguments). A key the * previous step actually produced always wins; a key it did NOT * produce can still be read from the original input rather than * coming back `undefined`. * * @example a typed three-step chain * ```ts * interface Ticket { orderId: string; angry: boolean } * * const parse: Runner<{ message: string }, Ticket> = …; * const lookup: Runner = …; * const reply: Runner<{ refundUsd: number }, string> = …; * * const intake = workflow(parse, lookup, reply); * const answer = await intake.run({ message: 'where is my refund?' }); * // ^? string — the chain's last output type * * workflow(parse, reply); // ✗ compile error: Ticket is not { refundUsd } * ``` */ import { type FlowchartCheckpoint, type RunOptions, type StructureRecorder } from 'footprintjs'; import type { RunnerPauseOutcome } from '../core/pause.js'; import type { Runner } from '../core/runner.js'; import { RunnerBase } from '../core/RunnerBase.js'; /** * What the NEXT step must accept, given what the previous one returns. * * A `string` output feeds `{ message }` — the convention every runner in * this library already speaks (LLMCall, Agent, Sequence, Swarm). Anything * else is handed over as-is, so the next step's input type must be that * same type. */ export type NextStepInput = TPreviousOutput extends string ? { message: string; } : TPreviousOutput; /** Any runner, viewed only as "a thing with a chart" — the workflow never * needs a step's own input/output types at run time. */ type AnyStep = Runner; export interface WorkflowOptions { /** Human-friendly name for events + topology. Default `'Workflow'`. */ readonly name?: string; /** Stable id used for topology + events. Default `'workflow'`. */ readonly id?: string; /** * Optional build-time recorders passed through to footprintjs's * `flowChart()` factory — they observe this workflow's own nodes (Seed + * one mount per step + Finalize). Not propagated into the mounted step * charts; attach them to each step runner for full coverage. */ readonly structureRecorders?: readonly StructureRecorder[]; } /** * A sequential composition that passes values through untouched. Build one * with {@link workflow} — that factory carries the type-level chain proof. */ export declare class Workflow extends RunnerBase { readonly name: string; readonly id: string; private readonly steps; private readonly opts; private currentRunContext; constructor(steps: readonly AnyStep[], opts?: WorkflowOptions); run(input: TIn, options?: RunOptions): Promise; resume(checkpoint: FlowchartCheckpoint, input?: unknown, options?: RunOptions): Promise; private createExecutor; private finalizeResult; private buildChart; } /** * Chain 1–8 runners into one, with every hand-off checked by the compiler. * * Step N's output type must be what step N+1 accepts — a `string` output * feeds the next step's `{ message }` (the house convention), anything * else is handed over as-is. A chain that does not line up is a COMPILE * error, not a silent empty value at run time. * * @example LLM steps chain as they always have * ```ts * const draft = LLMCall.create({ provider, model }).system('Draft it.').build(); * const edit = LLMCall.create({ provider, model }).system('Tighten it.').build(); * * const pipeline = workflow(draft, edit); * const text = await pipeline.run({ message: 'a note about refunds' }); * ``` * * @example structured hand-offs survive * ```ts * const classify: Runner<{ message: string }, { topic: string }> = …; * const answer: Runner<{ topic: string }, string> = …; * * await workflow(classify, answer).run({ message: 'my card was declined' }); * ``` */ export declare function workflow(s1: Runner): Workflow; export declare function workflow(s1: Runner, s2: Runner, C>): Workflow; export declare function workflow(s1: Runner, s2: Runner, C>, s3: Runner, D>): Workflow; export declare function workflow(s1: Runner, s2: Runner, C>, s3: Runner, D>, s4: Runner, E>): Workflow; export declare function workflow(s1: Runner, s2: Runner, C>, s3: Runner, D>, s4: Runner, E>, s5: Runner, F>): Workflow; export declare function workflow(s1: Runner, s2: Runner, C>, s3: Runner, D>, s4: Runner, E>, s5: Runner, F>, s6: Runner, G>): Workflow; export declare function workflow(s1: Runner, s2: Runner, C>, s3: Runner, D>, s4: Runner, E>, s5: Runner, F>, s6: Runner, G>, s7: Runner, H>): Workflow; export declare function workflow(s1: Runner, s2: Runner, C>, s3: Runner, D>, s4: Runner, E>, s5: Runner, F>, s6: Runner, G>, s7: Runner, H>, s8: Runner, I>): Workflow; export {};