Agentic Workflow Automation
A deterministic health pipeline for seven public market-data feeds, with an LLM triage agent invoked only when a check fails. On a clean day: zero LLM calls. Across six staged failures: 4 correct verdicts, 2 wrong-but-gated, 0 wrong-and-confident.
Overview
Market-data teams watch many feeds, and most days nothing is wrong — which is exactly the problem. A monitor that pages a human for every holiday gap trains people to ignore it; one that silently swallows a decimal-shifted price is worse. The fashionable answer is 'put an agent on it'. The engineering answer, built here as feed-health-pipeline: detect anomalies mechanically, spend judgment — machine or human — only on real exceptions, and never let an uncertain judgment pass silently.
Seven deliberately heterogeneous public feeds (four formats, four cadences) flow through payload and schema guards, format-specific parsers and five deterministic checks — freshness, gaps, spikes, bounds, duplicates — with every threshold in config, not code. When all checks pass, the run costs zero LLM calls. Only when one fails does a bounded tool-use agent investigate that single incident: it can read the feed spec, recent observations and the previous snapshot, gets a hard six-turn limit, and must submit exactly one classified verdict with its full tool trace saved.
The verdict then hits a gate written in code, not vibes: confidence below 0.8, any consequential action, or any unknown classification routes to an append-only human review queue. The staged-failure showcase proves the design point — both wrong verdicts arrived with low confidence, were caught by the gate, and were corrected on the record with dated notes. Not a single incident was dropped, and nothing wrong was ever confident.
Approach
Fetch
One HTTP request per source with snapshot rotation — the previous snapshot is kept deliberately, as the comparison material for the agent's provenance tool.
Parse & guard
A payload guard catches empty and HTML responses, a schema guard catches missing fields; seven format-specific parsers reduce everything to uniform (date, value) observations.
Check
Five deterministic checks — freshness, largest gap, spike, bounds, duplicate dates — every limit read from the per-feed spec in data/feeds.json. All pass → report written, zero LLM calls.
Triage
Exceptions only: a Claude tool-use loop with a capped toolbox, a hard 6-turn limit with forced verdict, retries with backoff, and strict validation that coerces unknown output to safe values.
Gate & review
Confidence < 0.8, consequential actions and unknowns go to an append-only review queue with dated resolution notes. No API key or persistent failure → straight to the queue. Nothing is silently dropped.
Watch the agent work
Every frame below is replayed from the committed run artifacts — the observations the checks saw, the tools the agent actually called, its verdict and confidence, and what the gate did with it. Pick a scenario.
Six staged
failures, one
scorecard
A scenario builder deterministically tampers the committed baseline snapshots into six failure cases — byte-identical on every rebuild — and each was run against the live agent, with the full artifacts committed. Result: 4 correct, 2 wrong-but-gated, 0 wrong-and-confident. Both misses carried low confidence, landed in the review queue, and were corrected by a human with dated notes.
| Scenario | Tampering | Agent verdict | Outcome |
|---|---|---|---|
| price_spike | ×10 decimal shift (EUR/NOK) | data_error · 0.93 · refetch | correct — auto-logged |
| btc_drawdown | −30% price move (BTC/USD) | data_error · 0.78 · use last good | correct — gated < 0.8 |
| schema_change | value column renamed (US debt) | source_change · 0.85 · update_spec | correct — consequential, gated |
| html_payload | HTML bot wall (Eurostat) | source_change · 0.60 · refetch | correct — gated < 0.8 |
| stale_feed | last 30 days removed (USD/EUR) | benign · 0.62 | wrong — gated, corrected |
| gap | 10 business days cut (SEK/EUR) | benign · 0.55 | wrong — gated, corrected |
REAL RUN DATA — results/demo/<scenario>/triage/*.json · claude-sonnet-5
The judgment
pair
The standout result. The same spike check fired for two very different events: a ×10 decimal shift and a genuine −30% market move. A threshold cannot tell them apart — that is exactly the rare, unstructured judgment the agent exists for.
The agent distinguished them on provenance grounds — comparing against the rotated previous snapshot through its tools — not on headline size.
Nothing passes
the gate
silently
A verdict is auto-logged only when it is confident and its action is safe. Everything else routes to an append-only human review queue — entries are never deleted, and resolving one records a dated note, so wrong verdicts stay on the record next to their correction.
Fail-safe by construction: the pipeline never requires the LLM to run, and never silently drops an incident.
It caught a
real one
On the first live run the freshness check flagged euro-area HICP as 189 days stale. Not a bug: Eurostat had genuinely frozen the dataflow after a methodology change. The feed was retired, replaced by the EA21 unemployment series, and every threshold was recalibrated against observed history — all documented in the repo.
Public endpoints changing and dying is treated as a feature of the demo, not a footnote: the guards exist precisely because “the fetch succeeded” does not mean “the data is fine.”
Seven real
feeds
Deliberately heterogeneous — four formats, four cadences, seven independent public endpoints — so the parsers, payload/schema guards and per-feed thresholds all earn their keep. Every threshold lives in config, not code: a wrong limit is a visible, fixable config change, not a bug.
| Feed | Source | Format | Cadence |
|---|---|---|---|
| EUR/NOK spot rate | Norges Bank | SDMX-CSV (semicolon) | business daily |
| SEK/EUR daily fixing | Sveriges Riksbank | JSON array | business daily |
| US GDP, current US$ | World Bank | JSON (metadata + rows) | annual |
| EA21 unemployment (SA) | Eurostat | SDMX-CSV (comma) | monthly |
| Total public debt | US Treasury | plain CSV | business daily |
| BTC/USD daily price | CoinGecko | JSON (prices array) | daily 24/7 |
| USD/EUR reference rate | Frankfurter (ECB) | JSON (rates object) | business daily |
Known
limitations,
documented
Thresholds are hand-tuned
Calibrated on observed history rather than statistically fitted — a regime change needs a recalibration pass.
The gate trusts the model’s calibration
A wrong-and-confident verdict would pass it. None did in the showcase — but the limit is stated, not hidden.
No trend storage
Reports are point-in-time snapshots; only the review queue persists across runs.