Stagehand

Script

The Script class API: constructor, defineInput, addPhase, addStep, use, outline, run, plus script(), stepFor, and routineFor.

Script<In, Ctx, Reserved, Slots, Open> is the builder every script starts from. Only the first two type parameters are ever written by hand (In as new Script<Input>(...), or inferred via defineInput); the rest are inferred automatically as addStep, addPhase, and use are chained. Full narrative coverage of each method lives in the guides linked below — this page is the flat signature reference.

new Script(options?)

constructor(options?: ScriptOptions | string)

Accepts a ScriptOptions object, or a bare string as shorthand for { name: options }. See Script Options and Results for every field.

script(options?)

function script<In = void>(options?: ScriptOptions | string): Script<In, {}>

A factory export equivalent to new Script<In>(options), for callers who prefer not to write new.

.defineInput(schema)

defineInput<TSchema>(
  schema: StandardSchemaV1<unknown, TSchema>,
): Script<TSchema, Ctx, Reserved>

Infers In from a Standard Schema-compliant validator’s output type, and validates whatever is passed to run() against it before any phase executes, throwing SchemaValidationError on failure. Call it first, immediately after construction: any addStep/addPhase called before it still sees the previous In. See The Typed Context.

.addPhase(name, options?)

addPhase(name: string, options?: PhaseOptions<In, Ctx>): Script<...>
addPhase(name: string, build: (script: Script<...>) => Script<...>): Script<...>
addPhase(name: string, options: PhaseOptions<In, Ctx>, build: (...) => Script<...>): Script<...>

Opens a new phase; subsequent addStep calls land in it until the next addPhase. The two-argument callback form groups a phase’s steps in a nested scope while preserving the same type flow as the flat form. Throws DuplicateNameError if the name is already used anywhere in the script.

PhaseOptions:

interface PhaseOptions<In, Ctx> {
  description?: string;
  when?: (context: { input: In; ctx: Ctx }) => Awaitable<boolean>;
  cache?: CacheSource<In, Ctx>;
}

See Phases and Steps and Caching.

.addStep(def)

addStep<Out, RollbackKeys, CleanKeys>(
  def: StepDef<In, Ctx, Out, RollbackKeys, Slots> & CleanField<Ctx, Reserved, CleanKeys>,
): Script<...>

Appends a step to the currently open phase. StepDef:

interface StepDef<In, Ctx, Out, RollbackKeys, Slots> {
  name: string;
  description?: string;
  handler: (context: StepContext<In, Ctx, Slots>) => Awaitable<Out>;
  rollbackKeys?: RollbackKeys & readonly (keyof Merge<Ctx, Out>)[];
  rollback?: (context: RollbackContext<In, RollbackData<...>, Out, Slots>) => Awaitable<void>;
  when?: (context: { input: In; ctx: Ctx }) => Awaitable<boolean>;
  cache?: CacheSource<In, Ctx>;
  retry?: RetryPolicy;
  timeoutMs?: number;
}

Plus clean?: readonly (keyof Ctx)[] via CleanField, checked element-wise against the step’s incoming context and the keys reserved by earlier rollbackKeys. Throws StepDefinitionError if clean names a reserved key, or DuplicateNameError if the step’s name is already used in its phase. See The Typed Context, Rollbacks, and Cleaning Context Keys.

StepContext<In, Ctx, Slots> (the handler’s parameter)

interface StepContext<In, Ctx, Slots> {
  readonly input: In;
  readonly ctx: Ctx;
  readonly signal: AbortSignal;
  readonly attempt: number;
  readonly phase: string;
  readonly step: string;
  log(message: string): void;
  info(message: string): void;
  warn(message: string): void;
  error(message: string): void;
  success(message: string): void;
  status(message: string): void;
  note(message: string): void;
  progress(options: { total: number; label?: string; value?: number }): ProgressHandle;
  task(label: string): TaskHandle;
  tasks<const K extends readonly string[]>(labels: K): TaskListHandle<K[number]>;
  readonly cache: CacheHandle<Slots>;
}

Full description of every member in The Handler Surface.

RollbackContext<In, Ctx, Out, Slots> (the rollback’s parameter)

interface RollbackContext<In, Ctx, Out, Slots> {
  readonly input: In;
  readonly ctx: Ctx;      // only the keys named in rollbackKeys
  readonly output: Out;   // exactly what the handler returned
  readonly error: unknown;
  readonly signal: AbortSignal;
  readonly phase: string;
  readonly step: string;
  log(message: string): void;
  status(message: string): void;
  note(message: string): void;
  progress(options: { total: number; label?: string; value?: number }): ProgressHandle;
  readonly cache: CacheHandle<Slots>;
}

Full description in Rollbacks.

ProgressHandle

interface ProgressHandle {
  update(value: number, label?: string): void;
  increment(by?: number): void;
  setTotal(total: number): void;
  setLabel(label: string): void;
  done(): void;
  readonly value: number;
  readonly total: number;
}

TaskHandle / TaskListHandle<K>

interface TaskHandle {
  start(text?: string): void;
  label(text: string): void;
  succeed(text?: string): void;
  fail(text?: string): void;
  skip(text?: string): void;
}

interface TaskListHandle<K extends string> {
  readonly tasks: Record<K, TaskHandle>;
  get(key: K): TaskHandle;
}

RetryPolicy

interface RetryPolicy {
  attempts: number;
  delayMs?: number | ((attempt: number) => number);
  retryIf?: (error: unknown, attempt: number) => boolean;
}

.use(source, options?)

use(routine: Routine<SubIn, Ctx, Out, R, RS>, options: { as?: string; input: MountInput<In, Ctx, SubIn> }): Script<...>
use(script: Script<SubIn, Out, R, SS, SO>, options: { as?: string; input: MountInput<In, Ctx, SubIn> }): Script<...>
use(script: Script<In, Out, R, SS, SO>, options?: { as?: string }): Script<...>
use(routine: Routine<In, Ctx, Out, R, RS>, options?: { as?: string }): Script<...>

Splices a Routine (from routineFor) or another Script into this one. With no input option the source must already accept this script’s In/Ctx directly; with input, a fixed value or a { input, ctx } => SubIn mapper feeds the mount something else, resolved once when the mount is reached. as prefixes the mounted phases’ (and their cache slots’) names, required when mounting the same source twice. Throws DuplicateNameError on a name collision, or StepDefinitionError if source is neither a Routine nor a Script. Full treatment in Reusable Scripts and Mounts.

.outline()

outline(): Array<{ phase: string; steps: string[] }>

Reports the declared structure — every phase and its step names, in order — without running anything.

.run(input, options?)

run(input: In, options?: RunOptions): Promise<RunResult<Ctx>>

interface RunOptions {
  signal?: AbortSignal;
  cache?: CacheMode; // "on" | "off" | "refresh" | "read-only"
}

Executes every phase and step in order, applying defineInput validation first if one was set. Resolves to a RunResult<Ctx> (see Script Options and Results), unless throwOnError is set, in which case a failing run rejects with the original error instead.

stepFor<In, Ctx, Slots>()

function stepFor<In, Ctx extends object = {}, Slots = {}>(): <Out, RollbackKeys>(
  def: StepDef<In, Ctx, Out, RollbackKeys, Slots>,
) => StepDef<In, Ctx, Out, RollbackKeys, Slots>

Binds the input and context a step declared outside any script expects, keeping full inference on its handler’s return type. The requirement is enforced when the resulting definition is passed to addStep on a real script. See Splitting Steps Across Files.

WithStepFor<Step, Rest>

type WithStepFor<
  Step extends { handler: (context: never) => unknown },
  Rest extends object = {},
>

The context a later step sees: Step’s output merged over Rest, so the next stepFor can declare its Ctx without restating an earlier step’s return shape. Nests (WithStepFor<typeof b, WithStepFor<typeof a, { rest }>>), with the outermost step winning a name collision, matching runtime shadowing. Reads only the output; the step’s own Ctx requirement is still checked at addStep. See Splitting Steps Across Files.

routineFor<In, Ctx>()

function routineFor<In, Ctx extends object = {}>(): <Out, R, S, O>(
  name: string,
  build: (script: Script<In, Ctx, never>) => Script<In, Out, R, S, O>,
) => Routine<In, Ctx, Out, R, Commit<S, O>>

Declares a reusable fragment of phases, built with an ordinary Script inside the callback and recorded under name. Mount it with .use(). See Reusable Scripts and Mounts.