Trace Waterfall

An agent run as a waterfall of spans. Bars are positioned against the whole run rather than each row’s own width, which is what makes concurrency visible — two tools that ran in parallel look identical to two that ran in sequence until you share the axis.

src/components/ui/trace-waterfall.tsx

About

From the component's own source, where it was written.

An agent run as a waterfall of spans.

The question a trace has to answer is "where did the time go", and a list of steps cannot answer it — steps tell you the order, a waterfall tells you the shape. Two tool calls that ran in parallel look identical to two that ran in sequence until you put them on a shared time axis.

**Bars are positioned against the root's window, not each row's own.** Every span is offset by start and sized by duration as fractions of the whole run, which is what makes overlap visible. Scaling each row to its own width — the easy version — draws every span as a full bar and hides exactly the concurrency you opened the trace to see.

A span that is a rounding error still gets a visible sliver: minWidth keeps a 2ms call from vanishing, because "it ran and was instant" and "it never ran" must not look the same.

Children collapse. A run with forty retrieval spans under one node is unreadable open, and the parent's own bar already says how long the subtree took.

Install

npx astralyx-ui add trace-waterfall

Usage

import { TraceWaterfall } from '@/components/ui/trace-waterfall'

<TraceWaterfall spans={spans} />

Composer

Change the props and the source below follows.

SpanTimeline4.82s
  • run
    4.82s
  • guard: pii_scan
    60ms
  • model: plan
    940ms
  • tools (parallel)
    2.18s
  • search_docs
    2.12s
  • get_customer
    240ms
  • get_invoice
    1.88s
  • model: answer
    1.48s
  • guard: tone
    60ms
Props
tsx
<TraceWaterfall
  spans={spans}
  defaultDepth={2}
  nameWidth={200}
/>

Examples

A run with a parallel fan-out and a failure

API

Generated from the source, so it cannot fall behind the code. One table per exported component, then hooks, functions and types.

TraceWaterfall

Extends Omit<ComponentProps<'div'>, 'children'>.

PropTypeDefaultDescription
spansTraceSpan[]required{ id, name, start, duration, kind?, error?, meta?, children? }. start and duration are milliseconds from the beginning of the run.
totalnumber—The run window. Defaults to the furthest span end, including nested ones.
defaultDepthnumber2Levels open on first render. Forty retrieval spans under one node are unreadable expanded.
formatDuration(ms: number) => stringdefaultFormatDefaults to ms under a second, then seconds.
nameWidthnumber | string200Width of the name column.
emptyLabelstring'No spans recorded.'—
labelstring'Run trace'Accessible name for the whole trace.

Functions

FunctionSignatureDescription
traceEnd(spans: TraceSpan[]) => numberThe furthest point any span reaches, including nested ones.

Types

TraceSpan

An agent run as a waterfall of spans.

FieldTypeDescription
idstring—
namestring—
startnumberMilliseconds from the start of the run.
durationnumberMilliseconds.
kind?'model' | 'tool' | 'retrieval' | 'guard' | stringDrives the bar colour. An unknown kind falls back to neutral rather than throwing.
error?booleanDraws the bar as failed, whatever its kind.
meta?ReactNodeTrailing detail — tokens, cost, a status code.
children?TraceSpan[]—

Behaviour

TopicDetailDescription
minimum bar2pxA span too short to draw still gets a sliver — “it ran and was instant” and “it never ran” must not look the same.