Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@ dist/
*.tsbuildinfo
app/m0-app.png
app/m1-live.png
app/m2-drill.png
app/m3-cost.png
app/m3-flame.png
app/m3-flame-real.png
# (m2-drill.png + m3-flame.png are tracked — they illustrate the README's drill-down/flame sections)

# capture / render working dirs + large media (regenerable)
.rec/
Expand Down
13 changes: 11 additions & 2 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -232,9 +232,18 @@ only `?source=live` needs the Node OTLP+SSE server (deferred).
base has no routing downside.
- **Demo clip.** `app/record.mjs` records the running `app/dist` on the real GPU (serve dist → `channel:'chrome'`
+ `--force_high_performance_gpu` → wait `[fathom]` ready → `?source=real` → `recordVideo`), then ffmpeg emits
`.mp4` + a two-pass-palette `.gif`. The GIF (`app/fathom-demo.gif`, ~30fps for size) is the README hero and is
git-tracked; `webm`/`mp4` are gitignored (regenerable; the mp4 is uploaded directly at launch). Honesty: the
`.mp4` + a two-pass-palette `.gif` (16fps · 760px · 128 colors · 8s ≈ 6 MB — dense particle motion is
worst-case for gif; the mp4/webm carry full framerate). The GIF (`app/fathom-demo.gif`) is the README hero and
is git-tracked; `webm`/`mp4` are gitignored (regenerable; the mp4 is uploaded directly at launch). Honesty: the
clip is a real capture, replayed — the HUD's "requests replayed" / "replayed as a live stream" says so.
- **Launch surface (M5 Fable pass).** `index.html` carries the unfurl cards (`og:`/`twitter:` +
`public/og.png` 1200×630 + `public/favicon.svg`). First-visit failure modes are surfaced, not silent:
`createRiver`/`createFlame` accept `onError` (adapter-null → the shared `NO_ADAPTER_MSG` from
`gpu/capabilities.ts`; init throws → message) rendered in App's full-screen error state; live mode with no
gateway answering shows `ui/LiveNotice.tsx` after 4s (honest "no gateway connected" + how to run one +
one-click switch to replay — it self-dismisses if a local server connects). The river legend hints the M2
pick (`click a comet → its span`). A minimal mobile pass lives at the end of `styles.css` (≤720px compact
panels; in flame view the flame panel owns the top via `.flame ~ #hud`; ≤480px hides the legend).
- **Deferred (live mode).** Hosting the Node server for public `?source=live` needs a live process (Fly.io/Render;
Cloudflare Pages is static-only, a Worker+Durable Object is the advanced alt). Not required for the launch demo.

Expand Down
33 changes: 21 additions & 12 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ v1 build (M0–M4 done; see `SPEC.md`):
## Layout
| Path | Role |
|---|---|
| `app/` | **cinema** — Vite + React + TS; raw-WebGPU core `app/src/gpu/` (incl. `motion.ts` = pick math mirroring `shaders/river-sim.wgsl` M2/M4 · `bloom.ts` = HDR bloom chain M4), SSE client `app/src/lib/stream.ts`, shell `app/src/ui/` (incl. `SpanDetail.tsx`) |
| `app/` | **cinema** — Vite + React + TS; raw-WebGPU core `app/src/gpu/` (incl. `motion.ts` = pick math mirroring `shaders/river-sim.wgsl` M2/M4 · `bloom.ts` = HDR bloom chain M4), SSE client `app/src/lib/stream.ts`, shell `app/src/ui/` (incl. `SpanDetail.tsx` · `LiveNotice.tsx` M5); `app/public/` = replay data + `og.png`/`favicon.svg` unfurl assets (M5) |
| `app/public/traces.json` · `.sample.json` | trace data the app fetches in replay mode (copy of the root files) |
| `server/` | **live server** (M1/M2) — OTLP receiver + SSE + `/traces/:id` (`src/otlp.ts`, `src/hub.ts`, `src/index.ts`); `tools/` = emit/live-demo/sentinel-otlp/pick-e2e checks; `e2e.ts` |
| `app/tools/pick-check.ts` | M2 pick-math unit harness (mirrors the shader; no GPU) |
Expand Down Expand Up @@ -133,15 +133,24 @@ GPU/frame with bloom on** — 157× under the 16.7 ms budget on the 4070 (`PROOF
`app/m4-richness.png`, also the README hero). Curl samples global time → replay loops drift subtly within
±0.02 NDC (documented caveat); the curl amplitude budget (jitter 0.03 + turb 0.035 + curl 0.015 = 0.080
< 0.09 lane gap) is load-bearing — don't raise it without redoing the arithmetic (pick-check enforces).
**M5 in progress (hosted demo — Opus half done)**: the static replay demo is deployed to **GitHub Pages**
via CI — `.github/workflows/deploy.yml` builds `app/dist` and publishes on every push to `main`
(`ci.yml` gates PRs). The demo runs `?source=real` — the **labeled client-side replay**, no server (live
SSE mode is deferred). Vite `base` is now **`'./'`** (relative) so the bundle works under the Pages subpath
*and* at local root (the recorder serves `dist` at root). The **README hero is now an animated GIF**
(`app/fathom-demo.gif`, tracked) captured by `app/record.mjs` on the 4070 (real capture, replayed — the HUD
says so); `webm`/`mp4` are gitignored (regenerable; mp4 uploaded at launch). GIF is ~30fps for size; the
demo *runs* at 60fps. One-time manual step: repo **Settings → Pages → Source = GitHub Actions**. URL:
`https://manvendra-github.github.io/Fathom/`. Still missing (M5, Fable half): README hero copy + public-demo
visual polish + launch (X/HN). Deferred: hosting the Node OTLP+SSE server for public `?source=live`.
**The v1 build plan is in [`SPEC.md`](./SPEC.md)** (milestones M0–M5). **Next: finish M5 (Fable polish + launch).**
**M5 nearly done (hosted demo — both halves shipped; launch pending)**: the static replay demo is deployed
to **GitHub Pages** via CI — `.github/workflows/deploy.yml` builds `app/dist` and publishes on every push to
`main` (`ci.yml` gates PRs). The demo runs `?source=real` — the **labeled client-side replay**, no server
(live SSE mode is deferred). Vite `base` is **`'./'`** (relative) so the bundle works under the Pages subpath
*and* at local root (the recorder serves `dist` at root). URL: `https://manvendra-github.github.io/Fathom/`.
**Opus half**: deploy/CI + the recorder (`app/record.mjs` → `app/fathom-demo.gif`, tracked README hero;
`webm`/`mp4` gitignored, mp4 uploaded at launch; gif recipe = 16fps·760px·128 colors·8s, shipped in the
recorder — the gif is ~16fps for size, the demo *runs* at 60fps). **Fable half (launch surface)**: README
rebuilt to SPEC §7's shape (hook → hero gif → demo link + the 0.106 ms/157× number → "why this exists" →
substance; `m2-drill.png` + `m3-flame.png` now tracked and embedded); unfurl cards in `app/index.html`
(`og:`/`twitter:` + `app/public/og.png` 1200×630 + `favicon.svg`); **init errors surface on-screen** —
`onError` threaded through `createRiver`/`createFlame` (adapter-null was a silent blank canvas; shared copy
in `gpu/capabilities.ts` `NO_ADAPTER_MSG`); **live mode with no gateway** shows an honest "no gateway
connected" notice after 4s (`ui/LiveNotice.tsx`; self-dismisses on connect, one-click switch to replay);
river legend hints `click a comet → its span`; humane replay-fetch errors; **minimal mobile pass**
(≤720px compact panels + flame-owns-the-top via `.flame ~ #hud`, ≤480px hides the legend). Verified:
pick-check 21/21 · cost-check 50/50 · pick-e2e 8/8 · 4-state screenshot iteration on the 4070
(`PROOF.md` §8c). Still missing (M5): **launch** (X first, then HN 12–17 UTC — plan in `SPEC.md` §7).
Deferred: hosting the Node OTLP+SSE server for public `?source=live`.
**The v1 build plan is in [`SPEC.md`](./SPEC.md)** (milestones M0–M5). **Next: launch.**
Decision context: vault note `next-flagship-project-research.md`.
26 changes: 19 additions & 7 deletions PROOF.md
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ curl flow, model sub-streams, `est. $ saved (cache) —` honest unpriced state).

---

## 8. M5 — hosted demo build + capture (Opus half)
## 8. M5 — hosted demo: build + capture (Opus half) · launch surface (Fable half)
The public demo is the static `app/dist` on **GitHub Pages** (CI-built), running `?source=real` — the
labeled client-side replay, no server. Deploy config: `.github/workflows/deploy.yml` (build → Pages on
`main`) + `ci.yml` (PR build gate). URL: `https://manvendra-github.github.io/Fathom/`.
Expand All @@ -302,22 +302,34 @@ npm run build
**(b) Demo clip captured on the real GPU** (`app/record.mjs` → serve dist → Chrome
`--force_high_performance_gpu` → `[fathom]` ready → `?source=real` → `recordVideo` → ffmpeg):
```bash
npm run app:record
npm run app:record # re-run after the M5 Fable polish (hint line, notice, meta) — 2026-07-04
# [record] serving app/dist at http://localhost:8976 source=real
# [page] [fathom] replay mode · 24,982 particles · nvidia · lovelace
# [page] [fathom] replay mode · 25,146 particles · nvidia · lovelace
# [record] hero still -> fathom-demo.png
# [record] video -> fathom-demo.webm
# [record] ffmpeg -> fathom-demo.mp4, fathom-demo.gif
# artifacts: fathom-demo.gif 6.69 MB (760×428, 128 frames, ~30fps) · fathom-demo.mp4 5.32 MB ·
# fathom-demo.webm 3.52 MB · fathom-demo.png 0.42 MB
# outputs: fathom-demo.webm 3.43 MB · fathom-demo.mp4 5.15 MB · fathom-demo.gif 6.17 MB
# (gif: 760×428, 16fps × 8s = 128 frames, 128 colors — the recipe now shipped in app/record.mjs)
```
`fathom-demo.gif` is the README hero (git-tracked); `webm`/`mp4` are gitignored (regenerable; the mp4 is
uploaded directly at launch). The clip is a **real capture, replayed** — the HUD reads "requests replayed"
and the panel says "replayed as a live stream" (honest, no manipulation beyond replay-order interleaving).

**M5 Opus exit criterion met:** the static replay demo builds and deploys via CI to a public URL, and the
README autoplay GIF is captured from the shipping bundle on the 4070. GIF is ~30fps for size; the demo
*runs* at 60fps (§7). Remaining (Fable half): README hero copy + public-demo polish + launch.
README autoplay GIF is captured from the shipping bundle on the 4070. GIF is ~16fps for size; the demo
*runs* at 60fps (§7).

**(c) Fable half — launch surface + first-visit polish** (same-day pass, screenshot-iterated on the 4070):
README rebuilt to the SPEC §7 launch shape (hook → hero gif → demo link + the 0.106 ms/157× number →
"why this exists" → substance); unfurl cards (`og:`/`twitter:` + `og.png` 1200×630 + SVG favicon);
adapter-null and init failures now surface on-screen (`onError` threaded through `createRiver`/`createFlame`
— previously a silent blank canvas); live mode with no gateway shows an honest **"no gateway connected"**
notice after 4s (self-dismisses on connect; one-click switch to the real capture); river legend gains
`click a comet → its span`; replay-fetch errors are worded for humans; minimal mobile pass (≤720px compact
panels, ≤480px canvas-first; flame panel owns the top on phones). Verified: build green · pick-check 21/21 ·
cost-check 50/50 · pick-e2e 8/8 (`m2-drill.png` regenerated with the polished UI) · 4-state screenshot
iteration (desktop river / live notice / mobile river / mobile flame — 2 defects caught + fixed: mid-word
code wrapping, flame-over-HUD overlap). Remaining: launch (X/HN).

---

Expand Down
55 changes: 37 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,44 @@
# Fathom

**A WebGPU "observability cinema" for LLM-ops telemetry.**
**I made LLM-gateway telemetry run at 60fps on the GPU.**

Every particle is one real LLM-gateway span. Requests flow as glowing comets into lanes by their
real outcome: **cache hits** stream off as a cyan tributary, **429s** retry and fail over in amber,
**PII** is caught and blocked in-path as red flares, and plain **spans/misses** run as the blue river.
[![Fathom — real LLM-gateway spans as a WebGPU particle river (real capture, replayed; gif is ~16fps for size — the demo runs at 60fps)](./app/fathom-demo.gif)](https://manvendra-github.github.io/Fathom/)

[![Fathom cinema — real sentinel spans: curl-noise flow + bloom, replayed at 60fps](./app/fathom-demo.gif)](https://manvendra-github.github.io/Fathom/)
> **▶ Live demo — https://manvendra-github.github.io/Fathom/**
> 460 real gateway spans, replayed client-side on *your* GPU (needs a [WebGPU](https://caniuse.com/webgpu)
> browser). The clip above is a real capture, replayed — the HUD says so. The gif is ~16fps for file
> size; the demo itself runs at 60fps.
>
> **The number:** the full pipeline — curl-noise compute, model-shaded sub-streams, HDR bloom — costs
> **0.106 ms of GPU time per frame** on an RTX 4070, measured per-pass with `timestamp-query`. That's
> **157× under the 60fps budget** ([PROOF.md §7](./PROOF.md)). Even *integrated* Intel UHD renders a
> million particles in 3.47 ms.

> **▶ Live demo — https://manvendra-github.github.io/Fathom/** · real sentinel spans, replayed
> client-side at 60fps on the GPU (needs a [WebGPU](https://caniuse.com/webgpu) browser). The clip
> above is a real capture, replayed — the HUD says so.
## Why this exists

> Perf and the real-data look are proven (see [`PROOF.md`](./PROOF.md)); the v1 build is underway per
> [`SPEC.md`](./SPEC.md). **Done: M0** (the app) **+ M1** (a generic OTLP live server — real sentinel spans
> stream in as comets) **+ M2** (drill-down — click a comet → its real span attributes via `GET /traces/:id`)
> **+ M3** (a `river`↔`$ flame` toggle → a rotating **3D cost flame graph**: provider monoliths + model bars
> extruded from the live aggregation, rising embers, orbit camera, hover pick — numbers reconcile with the HUD)
> **+ M4** (richness pass — curl-noise flow in a stateless compute pass, a real HDR **bloom** chain with a
> toggle, model-shaded **sub-streams** within each lane, an honest `est. $ saved (cache)` HUD counter — the
> whole pipeline measured at **0.106 ms GPU/frame** on the 4070, 157× under the 60fps budget).
> **M5 (underway):** the static replay demo is deployed to **GitHub Pages** via CI (link above);
> README hero GIF + launch polish next.
LLM-ops dashboards are flat. Langfuse, Phoenix, Helicone, LiteLLM — tables and 2D charts of the most
consequential telemetry a team has: money leaving per request, PII trying to leave, 429s quietly
eating the latency budget. **Money and PII deserve to be seen.**

Fathom plugs onto **any** OpenTelemetry-emitting gateway and renders every span as a glowing comet,
laned by its real outcome: **cache hits** stream off as a cyan tributary, **429s** retry and fail
over in amber, **PII** is caught and blocked in-path as red flares, and plain **misses** run as the
blue river. Shade within a lane is the model that served it.

And it's a tool, not a screensaver: **click any comet** and it opens as a real span — route, outcome,
usage, raw `gen_ai.*` attributes — reconciled against the server's `GET /traces/:id`.

![Drill-down — click a comet, get its real span attributes in an outcome-lit callout](./app/m2-drill.png)

Toggle to the **`$ flame` view** and cost stacks up as a rotating 3D flame graph — provider monoliths,
model bars, rising embers — aggregated from the same spans and reconciled with the HUD *by
construction* (one reduction feeds both).

![$ flame — cost by provider → model as a rotating 3D flame graph with orbit camera and hover pick](./app/m3-flame.png)

> **Status:** v1 milestones M0–M4 shipped, M5 (launch) underway — the plan is
> [`SPEC.md`](./SPEC.md), every measured claim is [`PROOF.md`](./PROOF.md), the design is
> [`ARCHITECTURE.md`](./ARCHITECTURE.md).

---

Expand Down Expand Up @@ -113,6 +130,8 @@ develop the renderer.
mixed stream — the load harness emits them in scenario phases, which isn't real arrival timing. Spans and
proportions are unchanged; only replay position is shuffled. The panel says "replayed as a live stream."
- Model names show as `std`/`pii` — the literal routed-model ids in sentinel's load config, not prettied.
- The hero gif is **~16fps** (gif palettes and file size, not the renderer); measured frame times live
in [`PROOF.md`](./PROOF.md) — 0.106 ms GPU/frame with everything on.
- ffmpeg isn't required; without it you get `.webm` (plays everywhere). With it, `record.mjs` also emits `.mp4` + `.gif`.

## Requirements
Expand Down
2 changes: 1 addition & 1 deletion SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ Restructuring is **M0 work**, not done in this spec turn.
| **M2 ✅ done** | Drill-down | ✅ clicking a comet opens the correct span's real attributes, reconciled against `GET /traces/:id` on the real GPU (see `PROOF.md` §5); pick is CPU math mirroring the shader (`app/src/gpu/motion.ts`) | **Opus** (pick math, `/traces/:id`, wiring) ✅; **Fable** (attribute-panel UI — outcome-lit callout card: reticle + leader line, card parked opposite the comet) ✅ |
| **M3 ✅ done** | 3D cost flame graph | ✅ toggle + cost-by-model/provider from the live buffer, reconciles with the HUD on screen; rendered as a **3D WGSL scene** (orbiting monoliths + embers, 4.5 ms/frame — `PROOF.md` §6) | **Opus** (cost aggregation + toggle + reconciliation) ✅; **Fable** (3D WGSL flame: monoliths, embers, orbit camera, ray pick, tracked DOM labels) ✅ |
| **M4 ✅ done** | Richness pass | ✅ bloom (HDR post chain + toggle) + curl-noise (stateless compute pass, pick mirror intact) + model sub-streams + `est. $ saved` counter — measured at **0.106 ms GPU/frame** with bloom on (157× headroom, `timestamp-query`; see `PROOF.md` §7) | **Fable** (curl field, bloom look, sub-streams) ✅; **Opus** (perf measurement, $-saved honesty, pick-mirror integrity) ✅ |
| **M5 🚧 in progress** | Hosted demo + launch | **Opus half ✅:** static replay demo deployed to **GitHub Pages** via CI (`deploy.yml` builds `app/dist` on `main`, `ci.yml` gates PRs; `?source=real`, `base:'./'`), README autoplay GIF captured on the 4070 (`app/fathom-demo.gif`, real-capture-replayed, `PROOF.md` §8). **Fable half:** README hero copy + public-demo polish; **launch** (X/HN) pending | **Opus** (deploy/CI config + GIF capture) ✅; **Fable** (README hero + public-demo polish) — pending |
| **M5 🚧 in progress** | Hosted demo + launch | **Opus half ✅:** static replay demo deployed to **GitHub Pages** via CI (`deploy.yml` builds `app/dist` on `main`, `ci.yml` gates PRs; `?source=real`, `base:'./'`), README autoplay GIF captured on the 4070 (`app/fathom-demo.gif`, real-capture-replayed, `PROOF.md` §8). **Fable half:** README rebuilt to the §7 launch shape (hook → hero → number → why); unfurl cards + favicon; init errors surfaced; live-mode "no gateway" notice; river pick hint; mobile pass (`PROOF.md` §8c). **Launch (X/HN) pending** | **Opus** (deploy/CI config + GIF capture) ✅; **Fable** (README hero + public-demo polish) |

**Model policy** (Opus + Fable only): **Fable** for anything people *look at* (shaders, viz, UI polish, launch surface); **Opus** for anything that must be *provably correct* (pick math, cost reconciliation, OTLP mapping) and all plumbing/tests/doc-sync. Highest-value Fable milestone: **M4**. Don't drop below Opus on **M3's aggregation** — a wrong cost number undercuts the "money deserves to be seen" pitch.

Expand Down
Binary file modified app/fathom-demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified app/fathom-demo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading