●─▶●─▶●─▶● relay docs
●─▶●─▶●─▶● relay

Deterministic multi-step Claude flows.

Write the flow once. Run it the same way every time. A typed, checkpointed DAG of Claude Code invocations that resumes after crashes and never bills the API by surprise.

What Relay is

Relay is a TypeScript library and CLI for running multi-step Claude workflows. You describe the workflow as a graph of steps in a typed config file. Relay compiles that file into a DAG, executes each step in topological order, and writes a checkpoint after every step. A crash, a network blip, or a deliberate ^C never costs you a redo — relay resume <runId> picks up at the last completed step.

It ships as two packages:

PackageWhat it does
@ganderbite/relay The relay CLI. Runs flows, resumes failed runs, scaffolds new flows, inspects auth.
@ganderbite/relay-core The TypeScript library. Compiles a flow spec into a validated DAG and executes it.

A flow is a directed acyclic graph of steps — prompt, script, branch, parallel, loop, ask, terminal — that pass typed JSON handoffs between them.

Why Relay exists

If you delegate work to AI agents, you have probably hit these walls:

Relay turns a multi-agent run into a typed, checkpointed graph:

The honest line. Relay can't make Claude deterministic. It can make the orchestration around Claude deterministic — same flow, same step order, same retries, same checkpoints.

60-second tour

Three commands take you from install to a real artifact on disk.

npm install -g @ganderbite/relay
relay init                                 # configure your provider
relay doctor                               # check your environment
relay run codebase-discovery --repoPath=.  # produce a real artifact

relay init writes your provider choice to ~/.relay/settings.json. Without it, the CLI exits with NoProviderConfiguredError before any step executes. relay doctor tells you whether your environment is safe to run.

What a flow looks like

import { defineFlow, step, z } from "@ganderbite/relay-core";

export default defineFlow({
  name: "hello",
  version: "0.1.0",
  input: z.object({ name: z.string() }),
  steps: {
    greet: step.prompt({
      promptFile: "prompts/01_greet.md",
      output: { handoff: "greeting" },
    }),
    summarize: step.prompt({
      promptFile: "prompts/02_summarize.md",
      dependsOn: ["greet"],
      contextFrom: ["greeting"],
      output: { artifact: "greeting.md" },
    }),
  },
});

A flow package is a directory containing flow.ts, a prompts/ folder, and a package.json. The CLI runs it; the library compiles and executes it. The handlebars-style templating engine substitutes input fields and prior handoffs into the prompt files at step start.

What you get

DAG execution Steps run in topological order. Cycles and missing dependencies fail at defineFlow() time, before a single token is spent.
Checkpoint & resume Every step writes a checkpoint; relay resume <runId> continues a failed or aborted run from the last completed step.
Prompt templating Handlebars interpolation of the flow's input and prior handoffs into prompt files. Write the shape once.
Typed handoffs Zod schemas validate the JSON each step produces before the next step reads it. HandoffSchemaError exits with a distinct code so CI can route on it.
Subagent definitions Declare ephemeral subagents per step — name, system prompt, tools, model. Resolved before the provider is invoked.
Cost tracking Per-step tokens and dollar estimates persisted to metrics.json during the run.
Billing safety The CLI refuses to silently route tokens to a paid API account when subscription credentials are configured.
A testing path @ganderbite/relay-core/testing exports MockProvider, a zero-network, zero-cost provider for Vitest suites.

Where to go next