drafts, critiques itself, then revises

Ask an LLM for a training plan and it will happily ramp you into an injury.

This one tells you the goal is wrong before it writes anything.

A LangGraph agent that assesses whether your goal time is realistic, drafts a periodized week-by-week build, tailors it to your actual race using course and weather findings, and then critiques and revises its own work until it passes a set of deterministic safety checks. The plan you get has survived review rather than just generation.

Marathon Training Plan Agent banner artwork.
5 nodes

assess → plan → adjust → critique → revise, with a real loop back from revise into critique.

3:20

what it recommended when asked for 3:05 off 22 miles a week. It argues the case rather than just complying.

5 issues

caught by its own critique node on the first draft of that plan, then fixed and re-checked clean.

Every run

prints a full agent trace, so you can see which node did what and how many issues each pass caught.

Start · why an agent

A prompt cannot check its own arithmetic

Ask a language model for a sixteen-week marathon plan and you get something that looks right. It may also ramp mileage 30% in a week, forget the taper, or prescribe a 26-mile long run — all errors that are obvious to a coach and invisible inside fluent prose.

Wrapping the model in a graph changes what is possible. The critique node runs deterministic checks in plain code — ramp rate, taper presence, long-run caps — alongside an LLM review, and the revise node fixes what it flags and loops back for another pass, up to a revision budget.

assess

Judges whether the goal time is realistic given current fitness, then sets a target and a peak mileage.

plan

Drafts the periodized build: Base → Build → Peak → Taper.

adjust

Tailors to the specific race using my own course-difficulty and heat-tax findings.

critique

Deterministic safety checks plus an LLM review. The deterministic half is what makes it more than a second opinion.

revise

Fixes flagged issues and loops back into critique until clean or the budget runs out.

The trace

Every run prints what each node did, so the self-correction is visible rather than claimed.

LangGraph state machine diagram for the training plan agent, showing assess, plan, adjust, critique and revise nodes with a loop from revise back into critique.
The graph. The loop from revise back to critique is the point of the whole thing.

↓ a real run

Asked for 3:05, it pushed back

This is a recorded run, verbatim, from examples/sample_run.md: a Boston goal of 3:05 in sixteen weeks off 22 miles a week and a nine-mile longest run, on a 74°F race day.

$ python -m src.cli --race boston --goal 3:05:00 --weeks 16 \
      --current-mpw 22 --longest 9 --days 5 --temp 74

================================================================
ASSESSMENT
================================================================
Goal looks ambitious .  Target: 3:20:00  (peak ~45 mpw)

A 3:05 marathon (~7:03/mi) typically requires a 40-55 mi/week base with
long runs already near 16-18 miles; starting from 22 mi/week and a 9-mile
longest run leaves too little runway in 16 weeks to safely build both the
mileage and the speed. Targeting 3:20 is an aggressive-but-achievable
stretch that lets you peak around 45 mi/week without high injury risk.

It does not simply refuse, and it does not simply comply. It names the mileage base a 3:05 normally requires, points at the two things that have to grow at once, and proposes a target that still stretches.

↓ the self-correction

Five issues, caught by the agent on itself

The trace from that same run. The critique node found five problems in the first draft — including a week-5 mileage jump above 20% — and the revise pass fixed them, after which a second critique came back clean.

================================================================
AGENT TRACE
================================================================
  • assess     → feasible=False, target 3:20:00, peak 45 mpw
  • plan       → drafted 16 weeks
  • adjust     → tailored plan to course & weather
  • critique   → 5 issue(s): week 5 jumps mileage >20% (23→28)
  • revise #1  → addressed 5 issue(s)
  • critique   → clean
  ✔ final plan passed all safety checks

The resulting plan is Boston-specific in ways a generic template is not: it programs downhill quad conditioning from week 3 for the course's punishing early descent, Newton-style hill repeats in the build, a week-12 race simulation with the hills at 15–19 miles, and heat acclimation throughout for the 74°F forecast.

Wk 12 · Peak · 53 mi · long 19 — "19 mi race sim: downhill start, Newton hills at ~15-19 mi"

One row of the generated plan, verbatim

↓ watching it work

A Streamlit UI that streams the nodes

streamlit run app.py wraps the agent so you can set your race in the sidebar and watch it move through assess → plan → adjust → critique → revise live, then read the plan table, a weekly mileage chart, the safety verdict and the full trace.

The adjust node draws on the same findings as the rest of this portfolio — course difficulty and the heat tax — so a hot Boston plan genuinely does look different from a cool Berlin one.

↓ the gap, reported not hidden

The safety-lift numbers have not been captured

src/eval.py exists and is designed to answer the obvious question: does the critique/revise loop actually make plans safer? It runs the agent across a panel of synthetic runners and compares each plan's safety before the loop against after it, reporting the pass-rate lift and the drop in mean issues per plan.

Those numbers have not been captured yet. The code is verified and runnable; the run was not completed because of CLI throttling during the build. So this page can show you a real trace catching five real issues, and it cannot yet tell you the average lift across many plans. That is the difference between a demonstration and a measurement, and this is currently the former.

Finish · what it is built on

Where the numbers come from

Framework
LangGraph, a state machine with a genuine cycle rather than a linear chain
Safety checks
Deterministic Python in src/graph.py: ramp rate, taper, long-run caps
Knowledge
src/knowledge.py — course-difficulty and heat-tax figures from my own analyses
Generation
Claude CLI by default; CLAUDE_MODEL trades quality for speed across the agent's several calls
Robustness
src/util.py extracts JSON from LLM output that is not always clean JSON
UI
Streamlit, streaming each node as it runs
Sibling
The Wine Pairing Agent, the same critique-loop pattern with retrieval as a tool

A plan that argued with itself first