Customizing pipelines

Define a project-level pipeline, select it at run time, and override model/effort per step.

The shipped schemas (contractor-base, contractor-lite) come with a default pipeline that's the right starting point for most repos. But the moment you want a different shape — skip the gate, add a pnpm test step, run a fast review-only iteration — you don't fork a schema. You define a named pipeline in contractor/config.yaml and select it at run time.

This guide walks through one end-to-end change: adding a fast-iteration pipeline that implements and reviews without closing, and pinning it as the default for a single blueprint.

1. Define the pipeline

Project-level pipelines live under the pipelines: key in contractor/config.yaml. The same shape is accepted at ~/.contractor/config.yaml for user-level pipelines you want available across every repo on your machine — project pipelines win on name collisions.

Steps come in three kinds: agent (spawns the configured AI backend with a slash-command prompt), shell (runs a command in the worktree), and gate (pauses for a user decision). See Pipelines for the full step reference.

A few rules:

  • slashCommand references a file under .claude/commands/blueprint/. It must exist after contractor ai install.
  • critical: true (the default) means a step failure ends the run; critical: false lets the pipeline continue past failure. Use false for non-essential steps like a final close or an advisory review.
  • needs_prepared_worktree: true injects a synthetic prepare step before this one if the worktree's prepare marker is missing or stale (see Worktrees).

2. Select the pipeline

Three ways to pick a custom pipeline at run time, in resolution order:

If none of those match, contractor falls back to the schema's pipeline: section, then to the hardcoded contractor-lifecycle default. The full chain is documented in Pipelines § Resolution precedence.

For a one-off run, --pipeline is right. For a blueprint that always wants the same custom pipeline, pin it in .contractor.yaml so you don't have to remember the flag:

For a repo-wide default, set pipeline: at the top of contractor/config.yaml. An unknown name fails the run with Unknown pipeline "<name>". Available pipelines: …, listing everything resolvable from the project + global maps.

3. Override model and effort per step

Agent steps accept model and effort overrides. The shipped contractor-base pipeline uses these to pin the implement and review phases to opus[1m] and tune the close phase to effort: low:

The same fields work in a project pipeline:

Run-time overrides via --model and --effort apply to every agent step in the run, while step-level fields apply per step. Use the CLI flags for ad-hoc experiments; bake the per-step values into the pipeline once you know what each step deserves.

effort is one of low, medium, high, max. Only backends that honor reasoning effort (today the Claude backend) react to it.

4. Add a shell gate

A common reason to define a custom pipeline is to insert a shell step — typically a typecheck or scoped test — between agent phases. Shell steps support {{scope}} substitution and when: scope guards, which makes them especially useful in monorepos:

The when: scope guard skips the step when the active blueprint has no scope, so unscoped blueprints sail past it instead of running the whole-monorepo test suite. See the using scopes guide for the full scope mechanics.

5. Verify with contractor doctor

Once you have a pipeline defined, run contractor doctor from the repo root to confirm the configuration parses cleanly. Doctor's "Schema graph" and "Pipelines" sections call out unresolvable references, unknown step kinds, or pipelines that point at slash-command files that don't exist.

If --pipeline fast-iteration rejects with Unknown pipeline, check that:

  1. The pipeline is defined under pipelines: (not at the top level) of contractor/config.yaml.
  2. The file is valid YAML — a stray indentation error silently drops the whole map.
  3. You're invoking from inside the repo whose contractor/config.yaml defines it (or that you've put the definition in ~/.contractor/config.yaml for cross-repo availability).

See also