The Problem
AI agents given unrestricted tool access fail in predictable ways: they re-read files, edit during review, deploy before tests pass. Bigger models and longer prompts help only marginally. Statewright's approach is to constrain the problem space itself—state machines that limit which tools an agent can use in each phase, rejecting out-of-phase calls with guidance on what's available and how to transition.
What This Does
Statewright is a collection of five self-contained projects, not a single codebase. The core is a pure Rust state machine engine (crates/engine) with no LLM in the loop—states, transitions, guards, and tool restrictions are deterministic. Around it sits an agent binary (crates/cli, binary sw-agent) that runs an Ollama-backed loop, enforcing tool access and streaming structured output.
The repo also includes an MCP gateway (crates/mcp-gateway) for integration with Claude Code, Codex, Cursor, opencode, and Pi, plus plugins (plugins/), a self-hosted deployment (self-hosted/), and experiments. The README claims two local models went from 2/10 to 10/10 passing on a 5-task SWE-bench subset under these constraints.
How It Is Wired
Execution starts at main in crates/cli/src/main.rs:1337, which reaches 205 functions. The shortest path out of the process is main -> chat via self.http.post(&url).json(&request).send—a network call to an Ollama endpoint. The CLI also runs external commands via execute_tool, called 22 times from main and 8 times from run_tdd_chain.
The call graph shows clear hubs: classify (called from 40 places), handle_message (39), and resolve_transition (32) in crates/engine/src/transition.rs. Changing these breaks the most. The engine's types.rs defines the core state machine types (target, guard_names, requires_approval). The MCP gateway (gateway.rs, session.rs, interceptors.rs) handles remote sessions, with pre_call_check (18 callers) enforcing tool restrictions before execution.
The module graph is shallow—37 internal modules, 7 import edges, no circular dependencies. The heaviest file is crates/cli/src/main.rs (57 functions, 9 types), followed by tools.rs (84 functions) and plugins/pi/src/index.ts (2872 lines). The wiring is not deeply layered; most logic sits in these few large files.
How To Use It
Setup: This is a Rust workspace. Build with cargo build --release from the root (Cargo.toml present). Docker is available via docker-compose.yml for self-hosted deployment.
Configuration: The agent connects to Ollama directly. Endpoint and model configuration live in crates/cli/src/main.rs and crates/agent/src/ollama_client.rs. For MCP-based clients, authentication uses an API key—see self-hosted/pocketbase/pb_migrations/002_bootstrap_api_key.js.
Running it: The README documents plugin installation for Claude Code:
/plugin marketplace add statewright/statewright
/plugin install statewright
Then start a workflow with /statewright start bugfix or a natural-language command. The CLI binary is sw-agent.
Real-World Use
A bugfix workflow: agent starts in planning phase with read-only tools. It reads files, then transitions to implementing where edit tools unlock with limited shell access. Write-via-redirect and destructive ops stay blocked even when Bash is allowed. Transition to testing permits only designated test commands. A rejected tool call returns what's available and how to transition.
Code Health & Issues
Static analysis found 55 findings (13 high, 42 medium). The dominant issues:
- High - Deep nesting (24 occurrences) -
crates/agent/src/executor.rs,ollama_client.rs,orchestrator.rs; max indentation depth 9. Fix with early returns. - High - Duplicated code (699 repeated 6-line blocks across 37 files) - extract shared helpers.
- High - Oversized files (13) -
plugins/pi/src/index.tsat 2872 lines,crates/cli/src/main.rs,tools.rs. Split by responsibility. - Medium - Empty catch blocks (4) -
self-hosted/pocketbase/pb_migrations/*.jssilently discard errors. - Medium - High branching density (13) -
plugins/pi/src/index.tshas 1208 branch points over 2872 lines.
SDLC concerns: no LICENSE file (all rights reserved by default), unpinned GitHub Actions (dtolnay/rust-toolchain@stable), no dependency vulnerability scan, no non-root USER in the Dockerfile, and CI workflows discard exit codes in release.yml. Tests and CI exist; no committed secrets found.
The Bottom Line
The core idea is sound—constraining agent tool access via state machines is a practical alternative to prompt engineering. The Rust engine is clean and deterministic. The weaknesses are structural: oversized files, duplicated logic, and missing license/security hygiene. Worth using if you run Ollama-based agents and need guardrails; expect to refactor before extending.