Project Structure
A stepbook project is just a directory with a config file, an entrypoint, and some step files. There's no hidden global state — everything lives in your repo.
Anatomy
my-pipeline/
├── stepbook.config.json # names the pipeline, points at the entrypoint + steps
├── pipeline.ts # the entrypoint — definePipeline({ name, runner })
├── steps/
│ ├── parse.step.ts # one file per unit, default-exported defineStep({...})
│ └── analyze.step.ts
├── cases/ # named fixed inputs for running a unit in isolation
│ └── parse/
│ └── hello.json
├── inputs/
│ ├── hello.json # sample inputs to run the pipeline against
│ └── sample.md
└── state/ # gitignored — runs, snapshot cache, reports
├── runs/ # one JSON blob per run
├── runs.jsonl # append-only run index (totals, timestamps, SHAs)
├── snapshots/ # file-based snapshot cache
└── reports/ # Markdown reports from --mdstepbook.config.json
The config names the pipeline and tells stepbook where to find its entrypoint and step files. The config used by the demo:
{
"name": "demo",
"steps": ["steps/**/*.step.ts"],
"pipelineFile": "pipeline.ts"
}pipelineFile— path to the entrypoint thatstepbook runexecutes.steps— a glob (or array of globs) for your step files, so stepbook can load and register each unit by name (this is what letsctx.run('parse', …)and the dashboard resolve a unit).
At least one of steps or pipelineFile must be set. See the Config Reference for every field.
Config formats
stepbook.config.json, stepbook.config.mjs, and stepbook.config.js are all supported. JSON is simplest; the .mjs/.js forms let you compute values.
pipeline.ts — the entrypoint
The default export of pipeline.ts is a definePipeline({ name, runner }). Its runner composes your units in code — a line for a pipeline, a loop for an agent. This is what stepbook run executes.
import { definePipeline } from '@stepbook/runner'
import parse from './steps/parse.step'
import analyze from './steps/analyze.step'
export default definePipeline({
name: 'demo',
runner: async (input) => {
const parsed = await parse(input)
return analyze(parsed)
},
})An entrypoint can also declare beforeAll / afterAll hooks that fire around an entire run. See Pipelines & Trajectories.
steps/*.step.ts — units
One file per unit. Each file's default export is a defineStep({...}) call. Stepbook globs these at load time (via the steps glob) and registers each by name — inferred from the filename when you don't set one, so 03-analyze.step.ts registers the unit analyze.
import { defineStep } from '@stepbook/runner'
export default defineStep({
type: 'parse',
runner: async (input) => ({ words: input.text.split(/\s+/) }),
})Files matching the glob that don't default-export a step registration (helpers, fixtures, colocated tests) are silently skipped, so you can keep related code next to your units.
cases/<step>/*.json
A case is a named, fixed input for running a single unit in isolation — a "story" for a step. A cases/parse/hello.json file supplies an input for the parse unit:
{ "input": { "text": "hello world" } }Cases can also be authored as named exports inside the step file. See Steps & Step Types.
inputs/*.json
Sample inputs the whole pipeline runs against. stepbook run with no argument uses the canonical input — the first file in inputs/, or the one named by canonicalInput in your config. Pass a specific file with stepbook run inputs/other.json.
state/ — your local data
Everything stepbook persists goes under state/, which you should gitignore. It holds:
runs/— one JSON blob per run (the full trajectory: inputs, outputs, checks, telemetry).runs.jsonl— an append-only index of runs with totals, timestamps, and git SHAs. This is whatstepbook listand the History panel read.snapshots/— the file-based snapshot cache, keyed per node by its input + config + source hash. See The Cache.reports/— Markdown reports written by commands run with--md.
Because it's all local files, you can inspect it directly, delete it to start fresh, or stepbook prune it to trim old runs.
Packages
Stepbook itself is a small monorepo. As a user you mostly interact with the CLI and the dashboard, but it's useful to know the layers:
| Package | Purpose |
|---|---|
@stepbook/runner | Execution engine. Pure Node, no UI. Composition, cache/replay, assertions, persistence, similarity. |
@stepbook/query | Query engine: the JSONata evaluator plus a structured query AST that compiles to JSONata. |
@stepbook/cli | The stepbook command. Built on the runner. |
@stepbook/dashboard | React + Vite UI. Calls into the runner via a Vite dev-server plugin. |
@stepbook/plugin-api | Public types for third-party dashboard plugins. No runtime. |
Step files import defineStep (and friends) from @stepbook/runner; the entrypoint imports definePipeline.