Budget Guard Viz

An interactive visualisation of why agent cost grows quadratically with the number of turns, and why budget guardrails are not optional. Pressing Run Agent animates two cost curves in parallel: an unguarded agent that keeps reading its growing scratchpad each turn (the red curve climbs past $47), and a guarded agent that hits its budget cap at 60% / 80% / 95% / 100% threshold markers before being terminated.

The cost model: each turn n reads its accumulated scratchpad — entry k weighs roughly S·√k tokens (S ≈ 11,200). Sum across n turns, multiply by $3 / 1M tokens, and the cumulative curve hits ≈$47 at turn 30 — the quadratic-growth insight. Four guardrail toggles (Budget cap, Loop detection, Turn limit, Human approval gate) change the guarded curve in real time — flip them all off and both agents run unchecked.

Unguarded$0.00
Guarded$0.00
Turn 0/30

Guardrails

Cost per turn (marginal)

turn 1$0.01
turn 5$0.12
turn 10$0.85
turn 20$5.50
turn 30$18.00

Model: $3/M input tokens. Turn n reads ~11200·√n accumulated scratchpad tokens.

Agent cost simulation ready. Press Run Agent to begin.

Each agent turn reads all previous turns. Press Run to watch what happens to cost as the agent loops.

Customize
Simulation
30
$5.0
$50
Guardrails

Installation

npx shadcn@latest add https://craftbits.dev/r/budget-guard-viz.json

Usage

import { BudgetGuardViz } from "@craft-bits/viz/budget-guard-viz";
 
<BudgetGuardViz />

Tune the cap or the y-axis ceiling:

<BudgetGuardViz budgetCap={10} yMax={80} />

Subscribe to the final tally for an external chart:

<BudgetGuardViz
  onSimulationComplete={({ unguardedCost, guardedCost }) => {
    /* lift the final burn into an external chart */
  }}
/>

Understanding the component

  1. The chart. A standard line chart with cumulative cost on the y-axis and agent turns on the x-axis. Both curves start at (0, 0) and trace identical paths until a guardrail intervenes.
  2. Cost model. Each turn n reads its accumulated scratchpad — entry k weighs roughly S·√k tokens. Sum across n turns, multiply by $3 / 1M tokens, and the cumulative curve hits ≈$47 at turn 30.
  3. Guardrails. Four toggles drive the guarded curve: the budget cap stops it at the dotted budget line; the turn limit caps it at 15 turns; loop detection and human-approval are visual signals — they don't change the curve in this simulation but gate which threshold markers render.
  4. Threshold markers. At 60% / 80% / 95% / 100% of the budget cap, dot markers appear on the guarded curve with their meaning: debug-log enable, observability metric, exponential backoff, hard stop.
  5. Reduced motion. Under prefers-reduced-motion: reduce, the path drawing collapses to a single snap, the run advances roughly three times faster, and every entrance animation disables.

Props

PropTypeDefaultDescription
maxTurnsnumber30Total agent turns modelled by the simulation.
budgetCapnumber5Hard spending cap (dollars) for the guarded agent.
yMaxnumber50Chart y-axis ceiling (dollars).
defaultGuardsPartial<Record<BudgetGuardVizGuardId, boolean>>all onInitial state of the four guardrail toggles.
transitionTransitionSPRINGS.snapOverride marker entrance / chart annotation transition.
onSimulationComplete(summary) => voidFires after the run finishes with { unguardedCost, guardedCost, guardedStoppedAtTurn }.
classNamestringMerged onto the root via cn().

Accessibility

  • The chart is role="figure" with an aria-label summarising the final unguarded vs guarded burn so screen-reader users get the headline without needing the visual.
  • Two polite live regions announce (a) the running cost readout and (b) a per-frame status string with the current turn count and both agents' costs — together they let an assistive-tech user follow the simulation in real time.
  • The Run Agent button has a visible focus ring and disables itself while the simulation is running so repeat-clicks can't queue a second run.
  • The four guardrail buttons use role="switch" with aria-checked; arrow keys and Space toggle them via the platform-default switch model.
  • Colour is never the only signal — the readout strip pairs every dollar number with its agent label, and the threshold marker text encodes the policy step (Debug logging enabled, HARD STOP — agent terminated, …).
  • Motion respects prefers-reduced-motion: reduce — the path drawing collapses to a single snap and every entrance / pulse disables.

Credits

  • Extracted from: craftingattention (app/src/lessons/primitives/systems/BudgetGuardViz.tsx). The source was a lesson component that bundled lesson narration, SvgLabel chrome, and a ChallengeBtn predict-the-outcome quiz. The viz extract keeps only the interactive simulation + cost-model + guardrail toggle panel — the quiz round was curriculum-specific and lives in the lesson source. Per-track palette tokens (--color-fail-400, --color-success-400, --color-warn-400, --color-accent-400, --color-ink-*) are remapped to var(--cb-error) / var(--cb-success) / var(--cb-warning) / var(--cb-accent) / var(--cb-fg-*) so consumer themes repaint freely, and inline SPRINGS.snappy / SPRINGS.gentle are re-keyed to the canonical SPRINGS.snap / SPRINGS.smooth from @craft-bits/core/motion. The infinite pulse-ring animation on threshold markers was dropped — userinterface-wiki duration-max-300ms rules it out, and the bouncy entrance + reach-state colour shift carry the signal.