# statewright

Statewright is a Rust state-machine engine for AI coding agents that gates *which tools are available in which phase*. The agent declares a workflow (e.g. `planning → implementing → testing → completed`); the engine enforces it per state. Tools outside the current state's `allowed_tools` are invisible to the model. Tagline: "Agents are suggestions, states are laws."

## The thesis

Most agent failures are framed as model-quality problems and met with bigger models or longer prompts. Statewright argues the failure mode is the *option space*: a model with 40+ tools and an open-ended problem spends tokens on flailing instead of reasoning. Shrinking the available tool set per phase is the cheap intervention.

The research-results table in the README shows the effect on local models: in a 5-task SWE-bench subset, gpt-oss:20b (13.8 GB) and gemma4:31b (19.9 GB) both **went from 2/10 to 10/10** with statewright constraints — same hardware, same tasks. Below 13 GB models can call tools but can't hold enough file content for accurate edits, so the floor is set by the model, not the harness. Frontier models also use fewer tokens to completion.

## How it works

The engine is a Rust crate (`crates/engine`, Apache-2.0, embeddable, no runtime deps) that evaluates state-machine JSON definitions: states, transitions, guards, tool restrictions. No LLM in the loop — every decision is deterministic.

On top sits a plugin layer that wires the engine into the coding agent over MCP. Statewright registers MCP tools (`statewright_start`, `statewright_get_state`, `statewright_transition`, `statewright_create_workflow`, `statewright_deactivate`) and per-tool-call hooks enforce the current-state's `allowed_tools`. The model only ever *sees* the tools it's allowed to use in this state.

### Guardrails per state

| Guardrail | Effect |
|-----------|--------|
| `allowed_tools` | Tools invisible to the agent when not listed |
| Bash discernment | Blocks `>>` redirects, destructive ops (`rm`, `shred`), scripting interpreters in non-write states |
| `max_edit_lines` | Reject diffs exceeding the limit |
| `max_files_per_state` | Cap files edited per state |
| `allowed_commands` | Prefix-matched shell-command allowlist |
| Conditional transitions | Guards with predicates (`eq`, `gt`, `exists`) over context data |
| `requires_approval` | Pause for human review before a transition |
| `blocked_env` / `env_overrides` | Per-state env-var scoping |
| `CLAUDE_SESSION_ID` | Per-session state, multiple workflows in parallel |

A state-machine isn't a DAG — states loop and retry (`testing → implementing` on test failure), which matches how iterative agent work actually behaves.

### Example workflow

```json
{
  "id": "bugfix",
  "initial": "planning",
  "states": {
    "planning": {
      "allowed_tools": ["Read", "Grep", "Glob"],
      "max_iterations": 8,
      "on": { "READY": "implementing" }
    },
    "implementing": {
      "allowed_tools": ["Read", "Edit", "Write"],
      "max_edit_lines": 20,
      "max_files_per_state": 3,
      "on": { "DONE": "testing" }
    },
    "testing": {
      "allowed_tools": ["Read", "Bash"],
      "allowed_commands": ["pytest", "cargo test", "npm test"],
      "on": {
        "PASS": { "target": "completed", "guard": "tests_passed" },
        "FAIL_TEST": "implementing"
      }
    },
    "completed": { "type": "final" }
  },
  "guards": {
    "tests_passed": { "field": "test_result", "op": "eq", "value": "pass" }
  }
}
```

## Supported agents

| Agent | Integration | Enforcement |
|-------|------------|------------|
| Claude Code | Hooks + MCP | Hard (protocol layer) |
| Codex | Hooks | Hard (alpha) |
| opencode | TypeScript plugin | Hard (alpha) |
| Pi | Skills extension | Hard (alpha) |
| Cursor | MCP + rules | Advisory (alpha) |

Hard = tool calls blocked at the protocol layer before the model sees them. Advisory = rules injected into context, model can still try. The Cursor case is a structural limitation — MCP alone can't gate tool calls in Cursor's architecture.

## Where it fits

Statewright is the structural answer to several recurring problems in the wiki:

- [[sandboxing-ai-agents]] — Filesystem / network / HTTP / syscall isolation tells you *what the agent can touch*. Statewright is a fifth axis: *what tools the agent can call, conditioned on phase*. Crabtrap-style HTTP-policy and statewright-style tool-policy are layered defenses.
- [[skill-atrophy-supervision-paradox]] — Constraining the agent in advance is the workflow [[cult-of-vibe-coding|Cohen's audit-discuss-execute]] enforces by human discipline, encoded as a machine-checkable spec.
- [[acceptance-criteria-ids]] / [[specsmaxxing]] — `specs as IDs that agents reference back` and `state-machines as IDs that agents transition through` are complementary moves toward navigable structure around the model.

The "bash discernment" guardrail (block `>>` and destructive ops in non-write states) is the same pattern [[toolbox/hazmat|hazmat]] hits via Seatbelt + pf at the OS layer — statewright catches it earlier, before the call reaches the runner.

## Pricing

Free tier (3 workflows, 200 transitions/month). Pro ($29), Team ($99). Engine is Apache-2.0 and self-hostable; single-developer and single-team self-hosting of the full stack is permitted under FSL-1.1-ALv2 (auto-converts to Apache-2.0 on 2029-05-03).

## Limitations

- MCP is required for hard enforcement; Codex and opencode use hooks instead (alpha).
- Cursor support is advisory only — MCP can't gate Cursor's built-in tools.
- Workflow definitions are hand-authored JSON; the `statewright_create_workflow` MCP tool lets the agent generate them but the upstream schema still has to be loaded into context.
- A too-restrictive workflow can leave the agent stuck; `statewright_deactivate` is the escape hatch.
- Research validation is a 5-task SWE-bench subset, not the full 2294 instances.

## Quickstart (Claude Code, free tier)

```
/plugin marketplace add statewright/statewright
/plugin install statewright
/reload-plugins
```

Then say `start the bugfix workflow` or `/statewright start bugfix`. The first call asks for the API key from statewright.ai. (The README notes Claude may be cautious about pasted API keys — confirm intent if it asks.)

Repo: https://github.com/statewright/statewright (Apache-2.0 / FSL, 88 stars, fresh). On the watchlist as a young single-vendor project.
