Flow visualizers
Versions marked “overview” do not contain this page. Selecting one opens that version’s documentation overview.
On this page
A flow visualizer is a single, self-contained HTML file that explains one Praxis config as an interactive diagram: the filter pipeline, the clusters and external services it talks to, the scenarios it serves, and — for agentic configs — the iterative request router (IRR) tool loop, complete with on-the-wire request/response examples and step-by-step timelines.
The canonical example documents the Responses API full-flow agentic gateway:
- Config:
examples/configs/openai/responses/full-flow-agentic.yaml - Visualizer:
examples/configs/openai/responses/full-flow-agentic.visualizer.html
The HTML is generated, not hand-maintained. This document explains how, and how to change it safely.
Exploring the pages
The curated page opens on Topology, with separate Filters, Wire exchange, and Timeline views. Select a scenario to update all four views; switching views preserves the selection. Play trace opens the filter view and follows active filters in order. The phase control opens the topology and emphasizes request or response arcs. These are illustrations of the selected scenario, not live traffic monitoring.
Scenario controls and view tabs support arrow keys, Home, and End. Motion respects the system’s reduced-motion preference. On narrow screens, diagrams scroll horizontally to keep their labels readable.
The generic config explorer provides section navigation and a filter search covering names, types, branch details, and redacted configuration values. Listeners and clusters stay visible while filtering. Open a document-model JSON with the file picker or drag it onto the page to explore another config.
The three inputs
The generated HTML is a deterministic function of three checked-in inputs plus
the pinned praxis_core parser:
| Input | File | Owns |
|---|---|---|
| Config | …/full-flow-agentic.yaml | The structure: which filters run, in what order, the clusters/endpoints, and the scalar limits (“knobs”). This is the same file Praxis actually loads. |
| Sidecar | …/full-flow-agentic.visualizer.yaml | The semantics: synthetic filter ids, display names, groups, phase/promotes prose, per-scenario notes, wire payloads, timelines, and cluster/service annotations. |
| Template | …/full-flow-agentic.visualizer.template.html | The static shell: inline CSS, the vanilla-JS renderer, inline SVG, and six @@BLOCK@@ placeholders the generator fills. No network, no build step. |
The generator (xtask/src/flow_generator.rs)
merges these into the checked-in HTML.
Structure vs. semantics
The split is deliberate and enforced. Structure is never invented by the
sidecar — it is read from the config through the real parser
(xtask/src/flow_graph.rs) and cross-checked
against the sidecar at generation time:
- The flattened listener pipeline (its referenced filter chains composed in order, each chain’s inherited conditions folded in, plus IRR inference steps) determines filter count, order, and type. If the sidecar’s filter list disagrees at any position — an added, removed, reordered, relocated, or mistyped filter — generation fails with an actionable message.
- Every
load_balancercluster (however deeply nested, including inside branch chains and IRR steps) and its endpoints must match the sidecar’s declared clusters. - Every scalar limit surfaced as a knob (registered in
xtask/src/flow_visualizer.rs) must equal its authoritative config value.
Everything the config cannot express — the prose, the wire examples, the timelines — comes from the sidecar verbatim. The generator does not fabricate behavioral explanations, and it never silently ignores a structural change.
Regenerating and verifying
# Rewrite the checked-in HTML from the current config + sidecar.
cargo xtask sync-flow-visualizers --fix
# Verify the checked-in HTML is exactly what the inputs produce (no write).
cargo xtask sync-flow-visualizers
The verify form runs as part of make lint, so CI fails if:
- the config changed but the visualizer was not regenerated,
- the generated HTML was hand-edited, or
- the sidecar drifted from the config’s structure (this always fails, even under
--fix, because it is a real disagreement a human must resolve).
Generation is deterministic: every data block is emitted as order-preserving
pretty JSON, so --fix is a no-op when nothing changed and diffs stay legible.
Making changes
- Change the narration (a scenario note, a wire example, a timeline step):
edit the sidecar YAML, then run
cargo xtask sync-flow-visualizers --fix. - Change the pipeline (add/remove/reorder a filter, change a cluster or a
limit): edit the config YAML. Regeneration will fail until you update the
matching sidecar entry (id, name, group, prose, per-scenario states) so
structure and semantics agree again, then run
--fix. - Change the look or interactions: edit the template HTML. Because the renderer reads the data blocks at runtime, appearance changes never touch the data.
- Never hand-edit the generated
*.visualizer.html. It is overwritten on the next--fixand the lint gate rejects manual edits.
Adding a new visualizer
- Author a config (if it does not already exist) under
examples/configs/. - Author a sidecar
<name>.visualizer.yamlbeside it and a template<name>.visualizer.template.html(start from the full-flow files). - Register the tuple in
VISUALIZERSinxtask/src/flow_visualizer.rs: the config, template, sidecar, and output HTML paths, the listener name whose composed chains the HTML flattens, and the knob checks that bind surfaced limits to their config source. - Run
cargo xtask sync-flow-visualizers --fixto produce the HTML, thenmake lintto confirm the gate is green.
Sidecar files use the *.visualizer.yaml suffix, which is excluded from the
example-config integration-test requirement (they are documentation data, not
Praxis configs).
Visualizing an arbitrary config
The curated full-flow visualizer above is bespoke: it pairs one config with a hand-authored sidecar of prose, wire examples, and timelines. For any other Praxis config — one without a sidecar — there is a generic renderer:
# Render any config to a self-contained, offline HTML document.
cargo xtask visualize-config path/to/config.yaml --output /tmp/config.html
# Also emit the extracted "document model" as JSON for offline reuse (below).
cargo xtask visualize-config path/to/config.yaml \
--output /tmp/config.html --json /tmp/config.model.json
This tool has no sidecar, so it shows only what can be derived truthfully:
- Structure — listeners, top-level clusters, and every filter chain flattened
with IRR inference steps hoisted inline — comes from the same
FlowGraphparser the curated generator uses. - Semantics — a one-line “what it does” description on a filter — is shown
only for filters this repository defines (the catalog is
filter_docs::filter_descriptions, the samesyn-parsed source metadata that drivesdocs/filters/). A filter Praxis core defines (e.g.trace_context,load_balancer,iterative_request_router) is labelled “semantics unavailable”; its raw configuration is still shown so the structure is never hidden. The tool never fabricates behavior it cannot ground in this repository’s source.
Configuration values whose keys look sensitive (token, secret, password,
api_key, authorization, credential, private_key, client_secret, bearer, …) are
redacted to the «redacted» sentinel before embedding, and highlighted in the
output.
The template lives at
xtask/src/assets/generic_visualizer.html
and is compiled into the binary with include_str!; the generator escapes the
model JSON and substitutes it into the single @@MODEL@@ placeholder.
Offline reuse (drag-and-drop) and why the CLI stays authoritative
A generated page includes a file picker and drag-and-drop target. They load a
document-model .json (the artifact of --json above) and re-render in the
browser — no server, no network. They deliberately do not accept a raw YAML
config.
The reason is a hard design constraint: structure extraction runs through
praxis_core, the real Rust parser, so the topology the page shows is exactly
what Praxis would load. Teaching the browser to parse YAML would mean a second,
independent parser that could disagree with the authoritative one — precisely the
drift this whole system exists to prevent. So YAML → model extraction remains the
CLI’s job, and the browser only ever consumes a model the CLI already produced.
To visualize a new config offline, run visualize-config … --json once and drop
the resulting .json onto any generated page.