Agent Graph Viz

A teaching component for the production multi-agent pattern that won: a single supervisor agent routes a compound user query to specialist agents, passes a trimmed context across each handoff boundary, and aggregates the results into a single response. The visitor steps through the happy path — observe → routing → parallel → aggregating → complete — watches the per-hop token budget drop on the right-hand ladder, then flips on Show failures to probe three production failure modes (routing error, conflicting actions, context loss). An insight callout fires once the visitor has seen both paths.

UserI need a refund AND my account is locked
Context at each hop
Hop 01000
Hop 1700
Hop 2490
Hop 3343
Step 1 of 5: User Query. A user sends a compound request. The supervisor must figure out which specialists to involve and in what order.

A user sends a compound request. The supervisor must figure out which specialists to involve and in what order.

Customize
Context
1,000
30%

Installation

npx shadcn@latest add https://craftbits.dev/r/agent-graph-viz.json

Usage

import { AgentGraphViz } from "@craft-bits/viz/agent-graph-viz";
 
<AgentGraphViz />

Re-target at a different compound query:

<AgentGraphViz userQuery="Cancel my subscription and export my data" />

Tighten the per-hop context loss to model a structured-handoff system:

<AgentGraphViz handoffLoss={0.1} />

Drive the step index from outside (controlled mode):

const [step, setStep] = useState(0);
 
<AgentGraphViz stepIndex={step} onStepChange={setStep} />

Understanding the component

  1. Hub-and-spoke SVG graph. A Supervisor sits in the centre with Refund, Account, and Escalation specialists around it and a User node above. Edge geometry shortens each line so it never passes under a node circle, and arrowheads orient from the inferred angle between endpoints.
  2. Step-driven phase machine. stepIndex indexes into the five-entry STEPS array (observe → routing → parallel → aggregating → complete). The current phase decides which edges and nodes are active, which message bubbles render, and which colour the narration paragraph tints to.
  3. Per-hop token ladder. The right-hand panel renders four bars (Hop 0 → Hop 3) representing the compounding token loss across handoff boundaries. Each bar reveals on its corresponding phase, with a -N% lost tag underneath every non-initial hop.
  4. Edge-aligned token badges. While an edge is active, a small rounded rect renders at its midpoint with the remaining token count. The badge fades out when the phase advances.
  5. Failure-mode toggle. Tapping Show failures switches phase to a dedicated failure state, recolouring the affected edge, dropping a ! glyph at its midpoint, and replacing the narration with the active failure's prose.
  6. Insight callout. Once the visitor has both completed the happy path and seen at least one failure mode, an AnimatePresence-mounted callout reveals the punchline (hub-and-spoke = bounded depth, but even two hops compound to ~51% loss at the default handoffLoss = 0.3).
  7. Keyboard. The root is focusable (tabIndex={0}). ArrowRight / Space advance, ArrowLeft retreats; the buttons mirror the same actions for pointer users. A sr-only live region narrates the active phase without spamming on every render.
  8. Reduced motion. Under prefers-reduced-motion: reduce, every spring collapses to instant, the per-edge dot animation disables, and the sonar pulse on active nodes does not mount.

Props

PropTypeDefaultDescription
userQuerystring"I need a refund AND my account is locked"Banner query and User node's outgoing message.
initialTokensnumber1000Token budget on the original query.
handoffLossnumber0.3Fractional context lost at each handoff boundary, in [0, 1].
stepIndexnumberControlled step index into the happy-path STEPS array. Pair with onStepChange.
defaultStepIndexnumber0Uncontrolled initial step index.
onStepChange(next: number) => voidFires on every step advance, retreat, or jump.
transitionTransitionSPRINGS.snapOverride the spring used for node / edge state transitions.
classNamestringMerged onto the root via cn().

Accessibility

  • The SVG is role="img" with an aria-label summarising the layout; agent labels and message bubbles are aria-hidden so the live region drives the story for screen readers.
  • The container is focusable with a visible :focus-visible ring and supports ArrowLeft / ArrowRight / Space for step navigation.
  • A polite live region announces the active step (Step N of M: <label>. <narration>) or the active failure mode whenever either changes.
  • Colour is never the only signal — failure edges add a dashed stroke and a ! glyph, the misrouted specialist gets a dashed outline, and the token ladder labels the percentage as text.
  • All buttons (step pips, prev/next, reset, failure toggle, failure cards) have visible labels, hit ≥ 32px height, are reachable in tab order, and show a focus ring.
  • Motion respects prefers-reduced-motion: reduce — every spring collapses to instant attribute sets, the travelling dot disables, and the active-node sonar pulse does not mount.

Credits

  • Extracted from: craftingattention (app/src/lessons/primitives/systems/AgentGraphViz.tsx). The source was a lesson component tightly bound to per-track CSS palette tokens (--color-ink-*, --color-accent-400, --color-success-400) and inline per-agent OKLCH hexes, embedded inline SPRINGS.snappy / SPRINGS.bouncy / SPRINGS.gentle / STAGGER.tight from CA's lib/motion, and ended on a lesson-styled ca-narration paragraph. The viz extract remaps every palette to var(--cb-*) semantic tokens, re-keys motion to the canonical SPRINGS.snap / SPRINGS.bouncy / SPRINGS.smooth and scalar STAGGER from @craft-bits/core/motion, surfaces the step index as a controlled-or-uncontrolled Radix-style prop (stepIndex / defaultStepIndex / onStepChange), parameterises userQuery, initialTokens, and handoffLoss, and replaces the lesson narration chrome with a token-styled paragraph.