Thanks to visit codestin.com
Credit goes to github.com

Skip to content

jamesmacaulay/fsmp

Repository files navigation

fsmp — FSM Prompter

fsmp runs prompt-driven workflows backed by extended finite state machines.

The primary user is an AI coding agent. The agent instantiates a pre-authored state machine and drives it one transition at a time. Each call returns:

  • the current step's instruction
  • the transitions that are valid now
  • the transitions that are blocked and why

This process helps agents follow complex workflows they might otherwise drift from.

What it does

  • Drives an agent through a workflow defined as states and transitions, re-stating the current step's instruction on every call.
  • Enforces sequencing: from a given state, only the legitimate next transitions are available. This targets omission failures — an agent skipping a required step (e.g. "prompt the reviewer to re-assess after the implementer responds").
  • Supports cyclic workflows (loops, retry rounds) and counter gates — e.g. a transition that stays blocked until "2 clean reviews" have been recorded.
  • Names blocked transitions and the reason they're blocked, so a rejected move redirects the agent rather than just erroring.
  • Keeps a transition log per instance as an audit trail.

What it does not do

  • It does not enforce content, only sequence. fsmp can't tell whether the agent reported a transition truthfully; it only guarantees the agent passes through the required states in order.
  • It's voluntary. As a CLI it can't force the agent to call it or to follow the returned instruction. It makes the correct path the available, legible one; it is not a sandbox. (Hard enforcement would need an MCP + hook layer — see Status.)
  • It does not run the workflow's actual steps (spawn agents, open PRs, etc.). It tracks where you are and what's allowed next; the caller does the work.
  • The definition is a fixed guardrail — the agent drives a machine but does not author or modify one at runtime.

Model

  • Definition — static, authored ahead of the run (by a person, or by an agent working with one — see "Authoring your own workflows"), kept in version control. The file extension selects the parser (case-insensitively): .yaml/.yml for YAML, .json for JSON; any other extension (or none) is rejected. States + params (set once at new, read-only) + context (mutable) + transitions with guards and effects.
  • Instance — a live run: a snapshot of the definition plus the current state, context, and transition log. Stored as JSON under ~/.fsmp/state/<id>/, never in version control. The definition is snapshotted at new, so editing the source file (or switching branches) doesn't mutate a running machine. FSMP_HOME overrides the ~/.fsmp home directory (which holds state/ alongside siblings like an installed bin/).

Guards are structured comparisons ({var, op, value|param|ctx}), all of which must hold (implicit AND). Effects are set / incr / decr / conditional. There is no expression language; guards and effects are plain data.

Commands

fsmp new  --def <path> [--id <id>] [--set k=v ...]   # instantiate; print the entry step
fsmp show --id <id>                                   # current state + valid/blocked transitions
fsmp do   <transition> --id <id> [--data k=v ...]     # attempt a transition; print the new step
fsmp log  --id <id>                                   # transition history
fsmp lint --def <path>                                # check a definition for authoring problems
fsmp guide [topic]                                    # print embedded authoring/driving docs

fsmp lint reports every authoring problem in a definition at once — unknown initial state, transition to an unknown state, unreachable state, dead-end (non-terminal with no exits), and terminal state that still declares transitions — and exits non-zero if any are found.

fsmp guide serves reference docs compiled into the binary: fsmp guide definition is the definition format plus an authoring pattern/anti-pattern catalog, fsmp guide driving is a short primer on driving any machine, and fsmp guide with no topic lists them. (The docs live in docs/*.md — that markdown is the single source of truth.)

Add --json for a machine-readable view (it does not apply to guide, which is prose to stdout). A rejected fsmp do (unknown transition, missing required data, or a failed guard) exits non-zero and prints the reason followed by the current step.

Example

.claude/skills/dev-cycle/ is a worked example that this repo also dogfoods: a skill (SKILL.md) that delegates its process sequencing to fsmp, plus the fsmp-definition.yaml it drives. The skill points its orchestrator agent at the definition:

fsmp new --def .claude/skills/dev-cycle/fsmp-definition.yaml --id myproj-1234 --set bar=2

and then drives fsmp do <transition> as the cycle progresses. The agent can't reach presenting until bar separate reviewers have each opened with a clean initial verdict and reached SATISFIED — a count the machine tracks rather than the agent — and even then only through a manual-verification state (convergence ≠ done). See .claude/skills/dev-cycle/README.md for the division of labour between the skill prose and the state machine.

Authoring your own workflows

To author a definition rather than drive one, start from fsmp guide definition (the format + patterns/anti-patterns). .claude/skills/author-fsmp-workflow/ is a skill that walks an agent and a user through it: design the state graph together, then drive a pipeline-with-retry-gates machine (fsmp-definition.yaml) that enforces the un-skippable tail — no YAML before the graph is signed off, no dry-run before lint is clean, no sign-off before a dry-run. It's a second worked example alongside dev-cycle.

Status

v1. new / show / do / log work, and the dev-cycle definition drives a full run (see .claude/skills/dev-cycle/fsmp-definition.yaml). Tested with cargo test (unit tests inline; integration tests run the binary against that definition).

Possible next steps: ls / defs inspection commands, and an --mcp-stdio mode that exposes the same engine over MCP, where PreToolUse hooks could turn the voluntary sequencing into hard gating. (fsmp lint, a definition linter for unreachable / dead-end states, has since landed.)

Install

With a Rust toolchain (rustup.rs):

cargo install fsmp

or straight from this repository:

cargo install --git https://github.com/jamesmacaulay/fsmp

Either installs the fsmp binary into ~/.cargo/bin (on your PATH if cargo is set up normally).

fsmp is also a library — the same engine the CLI drives (cargo add fsmp); API docs at docs.rs/fsmp.

The example skills (dev-cycle, author-fsmp-workflow) can be installed into a project with the skills CLInpx skills add jamesmacaulay/fsmp — or copied from this repo directly (see .claude/skills/dev-cycle/README.md).

Build & install from source

make build       # debug build (target/debug/fsmp)
make test        # unit + integration tests
make check       # fmt-check + clippy + test
make install     # release build, installs to ~/.fsmp/bin/fsmp

make install mirrors the runtime layout — the binary lands in ~/.fsmp/bin/ next to ~/.fsmp/state/. Add it to your PATH:

export PATH="$HOME/.fsmp/bin:$PATH"

FSMP_HOME relocates both the binary and state (make install FSMP_HOME=...). Run make help for the full target list; plain cargo build / cargo test also work.

License

Licensed under either of

at your option.

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

About

A CLI that steers AI coding agents through workflows by re-prompting them at each transition of a finite state machine.

Resources

License

Apache-2.0, MIT licenses found

Licenses found

Apache-2.0
LICENSE-APACHE
MIT
LICENSE-MIT

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors