Durable AI agents, by construction.
You write the workflow once as a Burr Application. Theodosia serves it over Model Context Protocol; any standards-compliant MCP client can drive it (verified against Claude Code, Cursor, fast-agent, and Gemini CLI), and the server only lets the agent take steps the graph allows. Every step it takes, and every step it tried but couldn't, is on the record.
No API key needed. theodosia primer walks a tiny coffee-order example offline and prints the same output every run.
The state machine decides which steps are legal.
Theodosia sits between any MCP client and your workflow, enforcing the legal-step boundary at the server.
Only legal steps.
Your Burr workflow defines what's allowed from each state. A step that isn't reachable from the current state is refused before the action body runs. The agent gets back a structured refusal naming the legal next moves and self-corrects.
Refused attempts are kept.
Burr's tracker logs the actions that ran. Theodosia adds a refusals.jsonl sidecar so the attempts that were refused are also on the record. The trail reflects what the agent tried, not its own account.
Forkable, chain-verified sessions.
Every entry is hash-chained with the session identity in the payload, so theodosia verify catches edits, reorders, and copies between sessions. Resume or branch from any past step with the fork_at / fork_from_past MCP tools.
A state machine you can see, and a log you can read.
An incident-response workflow: five states, four allowed transitions, one refusal. The agent attempted close_incident from engaged; the machine refused, recorded the attempt, and did not advance.
The diagram below is generated from the same Burr Application that theodosia.mount() serves over MCP and that theodosia verify checks the ledger against. One source of truth, three views.
incident = build_application() # see Quickstart for the 15 lines
theodosia.mount(incident).run() Got a Mermaid diagram already? Philip, a sibling library, lifts .mmd files into the same Burr Application.
stateDiagram-v2
direction LR
[*] --> triaged
triaged --> engaged: page_oncall
engaged --> verified: verify
verified --> resolved: resolve
resolved --> closed: archive
closed --> [*]
note right of engaged
close_incident · refused
not allowed from engaged; requires verified first
end note
classDef cur fill:#fcefd5,stroke:#ea9d34,stroke-width:2px,color:#0a0a0a
class engaged cur
Quickstart in 30 seconds.
Install the adapter, point it at a Burr state machine, and serve it over MCP. Your existing agent connects unchanged.
Application with actions and gated transitions.# A minimum-viable Burr workflow: 5 states, 4 transitions. from burr.core import ApplicationBuilder, State, action @action(reads=["status"], writes=["status"]) def page_oncall(state): return state.update(status="engaged") @action(reads=["status"], writes=["status"]) def verify(state): return state.update(status="verified") # plus resolve, archive ... incident = ( ApplicationBuilder() .with_actions(page_oncall, verify, resolve, archive) .with_transitions( ("triaged", "engaged", "page_oncall"), ("engaged", "verified", "verify"), ("verified", "resolved", "resolve"), ("resolved", "closed", "archive"), ) .with_state(status="triaged") .with_terminal("closed") .build() )
$ pip install theodosia # mount the Application as an MCP server $ theodosia serve app.py:incident ⊢ serving incident-response on stdio ⊢ 4 actions, 1 terminal ('closed') ⊢ ledger → ~/.theodosia/incident-response/<app-id>/ # later, confirm the ledger chain is intact $ theodosia verify ⊢ ledger intact · incident-response/<app-id>