ReAct Loop Viz
An interactive walkthrough of the ReAct (Reason + Act) agent loop — the foundational pattern behind modern LLM agents. The agent thinks (chain-of-thought reasoning), acts (tool call), observes (tool result), then loops. Stepping through three turns of a worked task fills in the scratchpad on the left and ticks up the per-call token-cost panel on the right.
The teaching insight: the scratchpad grows with each turn, and the model must re-read all prior turns on every call. Cost is O(n²) in turns — a fact that disappears under the "I made three LLM calls" framing and shows up clearly when each call's input-token count is plotted alongside the cumulative total.
This is the ReAct loop — Reason, Act, Observe, repeat. Step through each turn to watch the agent's scratchpad grow. Pay attention to how much context the model must re-read on every call.
Installation
npx shadcn@latest add https://craftbits.dev/r/react-loop-viz.jsonUsage
import { ReActLoopViz } from "@craft-bits/viz/react-loop-viz";
<ReActLoopViz />Swap the worked task:
<ReActLoopViz query="Compare the population of Tokyo and Delhi" />Supply your own scratchpad trace and per-call cost ladder:
<ReActLoopViz
steps={[
{ kind: "thought", turn: 1, text: "I should look this up." },
{ kind: "action", turn: 1, text: 'search("...")' },
{ kind: "observation", turn: 1, text: "Found 42 results." },
{ kind: "done", turn: 1, text: "Task complete" },
]}
turnCosts={[
{ turn: 1, inputTokens: 280, label: "system + query + thought" },
]}
/>Subscribe to the final tally:
<ReActLoopViz
onComplete={({ totalActual, naiveEstimate }) => {
/* lift the contrast into your own analytics chart */
}}
/>Understanding the component
- Scratchpad. The left column streams in each
thought/action/observationentry as you pressNext Step. Entries are coloured by kind (thought = info, action = warning, observation = success, done = accent) and grouped byturnwith a thin separator at every turn boundary. - Cost panel. The right column lists each LLM call as a horizontal bar proportional to the largest call's input-token count. Bars and breakdown labels (
system + query + thought,all of turn 1 + obs₁ + thought, …) fade in as their turn becomes the active one. - Cumulative line. Below the bars sits a tiny SVG chart with a dashed
O(n²)reference curve and the actual cumulative-token line. The actual line tracks the reference closely once the stepper reachescost-revealphase — the quadratic-growth insight, made visual. - Insight callout. After the last step, a final press reveals the "naive vs actual" contrast:
firstCallCost × numTurnsvs the true sum across all turns. - Phases. State derives from
visibleCountandinsightSeenand exposes viadata-phaseon the root:observe→stepping→cost-reveal→insight. - Reduced motion. Under
prefers-reduced-motion: reduce, every entrance animation collapses to a snap and the cumulative-token chart skips its path-draw.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
query | string | "Find the GDP of France and compare it to Germany's" | User task shown above the first scratchpad entry. |
steps | readonly ReActLoopVizStep[] | 3-turn GDP trace | Ordered scratchpad entries with per-entry turn numbers. |
turnCosts | readonly ReActLoopVizTurnCost[] | [330, 510, 730] | Per-call input-token costs with breakdown labels. |
transition | Transition | SPRINGS.smooth | Override entrance spring for scratchpad entries and the cost graph. |
onComplete | (summary) => void | — | Fires once the insight is revealed with { totalActual, naiveEstimate, perTurn }. |
className | string | — | Merged onto the root via cn(). |
Accessibility
- The root is
role="figure"with anaria-labelsummarising the visualisation so screen-reader users get the headline. - An
sr-onlypolite live region announces each step transition (Step 4 of 9: thought in turn 2) and the final totals. - The
Next Stepbutton has a visible focus ring, disables itself once the insight is shown, and exposes the current progress in itsaria-label. - Keyboard model:
Spaceor→advance,←step back,Rresets. The handler chains to user-providedonKeyDownand skips default-prevented events. - The cumulative-token chart is
role="img"with anaria-labelthat names every call's token count, so the trend is conveyed without colour. - Colour is never the only signal — every cost bar pairs its colour with the call number and a numeric token readout, and every scratchpad entry pairs its tint with an uppercase kind badge.
- Motion respects
prefers-reduced-motion: reduce— every entrance collapses to a snap and the SVG path-draws disable.
Credits
- Extracted from:
craftingattention(app/src/lessons/primitives/systems/ReActLoopViz.tsx). Inline OKLCH role colours and CA palette tokens (--color-ink-*,--color-surface-raised,--color-accent-400,--color-success-400,ca-narration) are remapped to semanticcb-*tokens (var(--cb-info)/var(--cb-warning)/var(--cb-success)/var(--cb-accent)/var(--cb-fg-*)). The hard-coded 3-turn GDP trace + cost ladder is lifted intosteps/turnCosts/queryprops (defaults preserved asDEFAULT_STEPS/DEFAULT_TURN_COSTS). Re-architected toforwardRef+cn()+...propsspread + chainedonKeyDown. InlineSPRINGS.snappy/SPRINGS.gentlere-key to canonicalSPRINGS.snap/SPRINGS.smoothfrom@craft-bits/core/motion;STAGGER.tightcollapses to the canonicalSTAGGERscalar (0.04).lessonId/SvgLabel/ChallengeBtnlesson chrome stripped.