A toy agent built with Theodosia: a coffee ordering workflow defined as a Burr state machine and served as an MCP server. An LLM drives it one transition at a time, and the server enforces the order of operations.
stateDiagram-v2
[*] --> take_order
take_order --> add_modifier
take_order --> pay
take_order --> cancel
add_modifier --> add_modifier
add_modifier --> pay
add_modifier --> cancel
pay --> fulfill
fulfill --> [*]
cancel --> [*]
This diagram is the contract. The agent can only move along these edges; the server refuses any step that is not a reachable transition. The workflow lives in the state machine, not in a prompt the model has to remember. Pay before ordering, or fulfill before paying, and the response is a structured refusal listing the actions that are actually reachable.
An LLM drives it over MCP. Here fast-agent connects a Llama-3.3-70B model
(on Together) and the model calls the step tool to walk the order:
The same workflow is observable from the terminal. coffee-agent render prints
the graph; coffee-agent sessions show replays a recorded run, refusals in red:
This whole repo is about 90 lines. The shape:
src/coffee_agent/app.pydefines the Burr graph (five@actions and the transitions between them).src/coffee_agent/cli.pywraps it in Theodosia'sbuild_cliso the package ships acoffee-agentcommand with the graph baked in.pyproject.tomldepends ontheodosiaand registers the console script.
git clone https://github.com/msradam/coffee-agent.git
cd coffee-agent
uv venv --python 3.13
uv pip install -e .coffee-agent serve now runs the MCP server over stdio. You usually do not run
it directly; an MCP client launches it for you (below).
The repo ships an .mcp.json pointing a client at coffee-agent serve. Pick a
client:
claude mcp add --transport stdio coffee-agent -- uv run coffee-agent serve
claudeThen ask: "Order a latte with an extra shot and pay for it." Claude calls the
step tool to walk the graph.
The repo ships a fastagent.config.yaml defining this server, so:
uvx fast-agent-mcp go --servers coffee-agent -m "Order a latte and pay for it, then fulfill the order."It uses Gemini by default (set GOOGLE_API_KEY). To drive it with a Together
model instead, set GENERIC_API_KEY and add
--model generic.meta-llama/Llama-3.3-70B-Instruct-Turbo. For a fully local
run, point the config's generic.base_url at Ollama.
npx @mcpjam/inspectorAdd the server with command uv and args run coffee-agent serve, then chat in
the playground.
Every step is recorded to Burr's tracker. From the repo:
uv run coffee-agent sessions ls
uv run coffee-agent sessions show # per-step timeline, refusals in red
uv run coffee-agent watch # live-tail a running session
uv run coffee-agent verify # check the session's hash-chained ledgercoffee-agent inherits these observability commands from Theodosia. Every step
and refusal is also hash-chained into a tamper-evident ledger.jsonl; verify
recomputes the chain and names any line that was edited or reordered after the
fact.
Apache 2.0. Built on Theodosia, Apache Burr, and FastMCP.

