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.

Agent Scratchpad
Press “Next Step” to begin
Tokens read per call
Call 1
Call 2
Call 3
Total read
Space / Arrow to step
ReAct loop visualisation ready. Press Next Step or Space to begin.

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.

Customize
Cost ladder
330
1.55×

Installation

npx shadcn@latest add https://craftbits.dev/r/react-loop-viz.json

Usage

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

  1. Scratchpad. The left column streams in each thought / action / observation entry as you press Next Step. Entries are coloured by kind (thought = info, action = warning, observation = success, done = accent) and grouped by turn with a thin separator at every turn boundary.
  2. 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.
  3. 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 reaches cost-reveal phase — the quadratic-growth insight, made visual.
  4. Insight callout. After the last step, a final press reveals the "naive vs actual" contrast: firstCallCost × numTurns vs the true sum across all turns.
  5. Phases. State derives from visibleCount and insightSeen and exposes via data-phase on the root: observesteppingcost-revealinsight.
  6. Reduced motion. Under prefers-reduced-motion: reduce, every entrance animation collapses to a snap and the cumulative-token chart skips its path-draw.

Props

PropTypeDefaultDescription
querystring"Find the GDP of France and compare it to Germany's"User task shown above the first scratchpad entry.
stepsreadonly ReActLoopVizStep[]3-turn GDP traceOrdered scratchpad entries with per-entry turn numbers.
turnCostsreadonly ReActLoopVizTurnCost[][330, 510, 730]Per-call input-token costs with breakdown labels.
transitionTransitionSPRINGS.smoothOverride entrance spring for scratchpad entries and the cost graph.
onComplete(summary) => voidFires once the insight is revealed with { totalActual, naiveEstimate, perTurn }.
classNamestringMerged onto the root via cn().

Accessibility

  • The root is role="figure" with an aria-label summarising the visualisation so screen-reader users get the headline.
  • An sr-only polite live region announces each step transition (Step 4 of 9: thought in turn 2) and the final totals.
  • The Next Step button has a visible focus ring, disables itself once the insight is shown, and exposes the current progress in its aria-label.
  • Keyboard model: Space or advance, step back, R resets. The handler chains to user-provided onKeyDown and skips default-prevented events.
  • The cumulative-token chart is role="img" with an aria-label that 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 semantic cb-* 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 into steps / turnCosts / query props (defaults preserved as DEFAULT_STEPS / DEFAULT_TURN_COSTS). Re-architected to forwardRef + cn() + ...props spread + chained onKeyDown. Inline SPRINGS.snappy / SPRINGS.gentle re-key to canonical SPRINGS.snap / SPRINGS.smooth from @craft-bits/core/motion; STAGGER.tight collapses to the canonical STAGGER scalar (0.04). lessonId / SvgLabel / ChallengeBtn lesson chrome stripped.