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 is 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.