steer

Flow

Multi-step processes the agent cannot skip.

The pattern we saw most often in real skills is ordering enforcement written as rhetoric: "You MUST complete each phase before proceeding", ALL-CAPS warnings, stop gates in markdown state files. Prose pleads; it doesn't enforce.

A Flow is the machinery: a DAG of steps where each step either verifies against reality or is a mandate the agent marks explicitly, and both are gated on prerequisites. The agent only ever sees the current step's directive.

When to use it

  • The process has ordered steps the agent must not shortcut: collect before analyze, review before publish, the failing test before the fix.
  • Ordering rhetoric is the hand-roll we saw most. superpowers' systematic-debugging states an Iron Law ("You MUST complete each phase before proceeding") that nothing checks, and humanizer ends its process with a rule (the output contains no em or en dashes) that nothing verifies. Both translate directly: the phase's written artifact or the final scan becomes a verify condition, and skipping ahead turns from a temptation into a refused command.
  • Skip it for a task prose already carries: two steps with no reason to cheat don't need a DAG. A flow earns its keep when skipping is tempting and expensive.

Declarative: flow.toml

Ship a flow.toml in the skill directory; no code is required.

Strings in flow.toml may use {skill_dir} (the directory containing the flow file) and {workspace}, expanded at load time, so a directive like command = "python3 {skill_dir}/scripts/collect.py" prints a command the agent can run verbatim from the workspace, even when the skill is installed under .claude/skills/.

name = "release"

[[steps]]
id = "configure"
description = "Create the release config"
directive = "Write out/config.json with the version and changelog entries"

[steps.verify]
file_exists = "out/config.json"

[[steps]]
id = "review"
description = "Human-judgment checkpoint"
directive = "Read out/config.json; confirm the changelog is accurate"
requires = ["configure"]

[[steps]]
id = "publish"
description = "Publish the release"
directive = "Run the publish script"
command = "scripts/publish.sh"
requires = ["review"]

[steps.verify]
command = "git tag --list | grep -q v2"

Verify conditions (all must hold; paths relative to the workspace):

ConditionComplete when
file_exists = "path"the file exists
dir_exists = "path"the directory exists
glob = "out/*.pdf"at least one match
command = "pytest -q"exit code 0
env = "VAR"the variable is set and non-empty

A step without [steps.verify] is a mandate step: the agent marks it with steer flow done <id>, and marking is refused while prerequisites are incomplete. Marking is not a way to skip ahead.

CLI

steer flow status              # progress bar + current directive (+ --json)
steer flow next                # just the current directive
steer flow done review        # mark a mandate step complete
steer flow reset [review]     # forget mandate completion (one or all)

Flow resolution: --file <flow.toml>, --skill <installed-skill>, or the nearest skill/flow.toml above the working directory. The workspace (--workspace, default .) is where verify conditions are evaluated and where mandate state persists: <workspace>/.steer/flows/<name>.json. The skill defines the process; the workspace holds the progress.

A generated skill spells these commands python3 scripts/steer.py flow ... through its bundled runtime; behavior is identical.

  RELEASE WORKFLOW
  ─────────────────────────────────────────
  Progress: 1/3 steps  ✓ configure  ● review  ○ publish

  ▸ Next: review
    Read out/config.json; confirm the changelog is accurate

In a SKILL.md

steer new --with flow writes the enforcement contract into the skill:

This skill has an enforced flow: steps verify themselves against
reality, and you cannot skip ahead.

1. Announce: "Working through the pr-review flow."
2. Run `python3 scripts/steer.py flow status` (in the workspace) to see
   progress and the current step.
3. Do what the directive says. Steps with a verify condition complete
   automatically once reality matches; for mandate steps, mark completion
   with `python3 scripts/steer.py flow done <step-id>`.
4. Run `python3 scripts/steer.py flow next` and repeat until it reports
   all steps complete.

Do NOT claim the work is done while `python3 scripts/steer.py flow
status` shows incomplete steps. The flow is defined in `flow.toml`.

Programmatic: Python

For verify logic beyond the declarative conditions:

from steer import Flow, Step

flow = Flow("release", workspace=".")
flow.add_step(Step(
    id="configure",
    description="Create the release config",
    verify=lambda ctx: validate_config(ctx.workspace),   # any callable
    get_directive=lambda ctx: ctx.directive(
        "Write out/config.json", command="my-tool init"),
))
flow.add_step(Step(id="review", requires=["configure"]))  # mandate

directive = flow.get_directive(flow.context())
flow.mark_complete("review")        # FlowBlockedError if prereqs unmet

CLI tools built on steer can also gate their own subcommands with flow.require_step("publish") / flow.after_step("configure", ctx): block a command until prerequisites verify, nudge the next step after one completes.

Flows track and mandate human-judgment steps, but a verify condition can only check reality (files, commands, env). "The user approved the design" stays a mandate step; steer makes it explicit and ordered, not provable.

A command verify condition runs through the shell every time the flow is evaluated, including by steer flow status and steer flow next. That is the point (verify observes reality), but it means checking the status of an installed skill executes that skill's verify commands: treat a third-party skill's flow.toml as code you have chosen to run.

On this page