Observability
Every pipeline is instrumented from its first run. For any pipeline, Millstream answers what is running now, how many items went through each step, what failed and why, what each agent step cost, and how often a person stepped in.
One source of truth
Every state change is an event in one table, appended by the engine's single writer. Metrics, run reports, insights, the live view and the API are all projections of it, so they never disagree.
millstream tail | grep runSettled | tail -1
{"seq":117,"ts":"2026-10-20T16:26:17.508Z","pipeline":"listings","runId":"run_20261020T162614Z-960e8ee4_adhoc","kind":"runSettled","data":{"costUsd":0,"durationMs":2869,"state":"succeeded"}}
Lineage for every item
Items are immutable, with ids derived from their content and origin. Each one links to the items it came from and the items it produced, across steps and across pipelines. An item's page shows its whole journey: each step it went through, every attempt with its duration, cost, error and agent transcript, its payload and artifacts, and where it was filtered and why.
Metrics with nothing to install
Metrics are rolled up per minute in the log store, so a laptop needs no time-series database. The engine also exposes
/metrics in Prometheus format for the stack you already run.
Steps
- Items in, out, filtered, failed, dead and unrouted
- Queue depth and oldest pending age
- Delivery latency, wait and work time, retries
Agents
- Tokens in and out, cost and turns, by model
- Schema-validation failures
- Fallback coverage and audit agreement
Review
- Decisions by outcome and reason code
- Time to decision and edit rate
- Auto-approval rate, sampled agreement, intervention rate
Platform
- Nodes, slots, leases and fencing refusals
- Engine role, failovers and writer batches
- Insights by kind and proposals by state
Actions add their own with one metric message, such as pages_fetched from a crawler, labelled with the run, step
and delivery that sent it.
millstream metrics --pipeline property-sourcing --since 1h
millstream metrics --prometheus
Run reports
Every run gets a report straight from the events: the critical path with each step's wait and work time, the slowest steps, failures grouped by cause, cost, and a comparison with the median of the last ten runs. A running run shows an ETA computed from history.
Run run_replay_20261020T162620Z-b9df9171 of listings (replay): succeeded
Started 2026-10-20T16:26:20Z, settled 2026-10-20T16:26:21Z, 540ms, cost $0.0000
Items: listings.ready 1, listings.renovate 1
Critical path (540ms):
STEP DELIVERY LATENCY WAIT WORK BACKOFF REVIEW SHARE
affordable dl_719ce262d713c5774527c64ea86e39e2 540ms 539ms 1ms 0s 0s 100%
A live web UI, in the open-source core
The UI is embedded in the binary and served on 127.0.0.1:7070 by millstream run. It streams live data over
server-sent events and is built to stay open all day.
| View | Shows |
|---|---|
| Graph | Steps and streams with live overlays: in flight, throughput, error rate, p95 latency, queue depth, insight badges |
| Runs | A timeline per step, working versus waiting, the run report and an ETA |
| Streams and items | Items filtered with CEL, lineage up and down, attempts, transcripts, artifacts |
| Dead letters | Grouped by error, with replay and discard |
| Inbox | The review queue and samples, keyboard-first |
| Improve | Insights with their evidence, proposals with diff and validation, accept or reject |
| Metrics | Any metric grouped by step, action, run or label, the view kept in the URL |
| Nodes | Nodes, slots, leases and health, the active and standby engines |
It is localised in English and French, works in light and dark, has a command palette, and meets WCAG 2.2 AA, checked by axe in its end-to-end tests.
Logs stay where your logs are
Millstream stores no logs. The engine and nodes write JSON lines through Go's log/slog, each carrying the pipeline,
run, step, item, delivery, attempt and node. An action's stderr is re-emitted with the same keys, so a delivery's logs
are one query in the tools you already use. While someone watches a run in the UI or with millstream tail, nodes
stream the matching lines live. Payload values never reach a log.