Example flows
What you get
Three flows that drive the agent-facing halves of this repo end to end, plus
the fixture they all read. persona_eval.py ingests a labelled corpus, has
the model react to every item as each persona, and scores those reactions
against hand labels. mcp_e2e.py spawns attest-mcp over stdio and calls
every tool on every agent surface — the path an agent actually takes.
training/train_mlflow.py trains four real MLflow arms and lets the run
ledger read the directory it wrote. run_all.py runs all three and prints
one summary. The fixture is corpus/: forty hand-written Atom entries, two
personas, and a label for every item under every persona. It is
hand-written rather than scraped because the labels are the point, and it
deliberately carries no dates — feed.list and rank_items default to a
fourteen-day window, so dated entries would have aged the demonstration out
of existence with no test going red.
Prerequisites
none — pure local computation
--offline points every flow at stub_openai.py, a stdlib http.server
speaking /v1/embeddings and /v1/chat/completions with hashed embeddings
and schema-shaped answers — no Ollama, no network. It proves the plumbing,
not the models, and every output line says so. A --live run against
Ollama needs a model server at LLM_BASE_URL; see Next.
Run it
uv sync --group examples
uv run --group examples python run_all.py --offline
Relative to this directory. Each flow also runs alone, and each takes
--json PATH to write its report; --skip NAME drops one from the run.
What it prints
mcp_e2e ok
Abridged — the full run prints each flow's own progress (127 MCP calls with per-call timing, the persona-eval confusion matrices, the MLflow training arms) and ends with:
=== summary
train_mlflow ok
persona_eval ok
mcp_e2e ok
What it demonstrates
persona_eval.py — precision, recall and AUC are agreement with forty
hand labels, not a statement about model quality. The corpus is small and
written to be separable; the number that would be alarming is a low one, not
a high one. The confusion matrix, the unsure count and the confidence
histogram are printed beside every score so the reader can see what the
model actually did. The ranker AUCs beneath them are rank.evaluate_user
over the same simulated clicks.
mcp_e2e.py — 127 calls over stdio across the feed, provenance,
knowledge and symbolic surfaces and the full server, in the order a
person would call them. Every row is ok, refused (an expected refusal,
checked as such) or FAILED. Refusals are asserted, not tolerated: a tool
that stopped refusing an unknown persona would fail this flow. It also
exercises the response-size contract by calling feed.list at limit 4 and at
the cap of 13; the 7000-character ceiling itself is asserted by
tests/test_response_size.py.
training/train_mlflow.py — four LogisticRegression arms on
scikit-learn's breast-cancer set, C in [0.01, 0.1, 1.0, 10.0], written to
a real mlruns/ in under two seconds (1.9 s in the run recorded in
RESULTS.md). attest runs scan then reads that directory through
ledger_adapters/generic.py — the first time that reader has met an MLflow
directory it did not have transcribed from documentation — and
training/FINDINGS.md carries one deliberately wrong claim so
attest claims demonstrates a contradiction.
training/mlruns is committed, because the ledger reader needs a real
directory to read in CI and in this offline flow. run_all.py runs the
trainer into a temporary --out so a demonstration run never rewrites it.
Regenerating it is deliberate and explicit:
uv run --group examples python training/train_mlflow.py
That rewrites mlruns/ and FINDINGS.md in place, run ids and all; the
diff is expected to be large and should be reviewed as a fixture change.
When it goes wrong
--offlineand--liveare mutually exclusive and one is required; omitting both raises an argparse error before anything runs.- A live run with no reachable
LLM_BASE_URLfails at the first model call inside whichever flow runs first (train_mlflowneeds no model, so it ispersona_evalthat fails); the flow's own report records the failure rather than the summary silently going green. --write-resultsafter a failing flow does not rewriteRESULTS.md:render_resultsraises if any report's mode is notlive, so an offline run cannot be filed as results by accident.
Next
Live, against Ollama at LLM_BASE_URL (ten to fifteen minutes):
uv run --group examples python run_all.py --live --write-results
See docs/superpowers/specs/2026-08-28-example-flows-design.md for why each
flow exists and what it is evidence for, and the catalogue at
examples/README.md for the other golden paths.