Metadata Enrichment Agent
Tags incoming news with controlled-vocabulary subject, industry, geography and company codes as it arrives, and decides which articles can publish without an editor.
For a businessClassifies incoming news automatically, so editors spend less time tagging and more time editing, while wrong tags are stopped before they ever reach readers.
- Status
- Spec build: built to a real brief, tested on an answer key I wrote
- Result
- Silent errors cut from 27% to 8.6% (worst of three runs) on a 500-article stress test
- How it was measured
- My open-source evaluation harness, scoring every run against answers written before it ran
- Biggest limit
- Many articles still go to an editor who didn’t need to see them: the price of the guard
01 · Context The problem
The hard part of tagging news at ingest is not the tagging. It is deciding which articles can be trusted to publish with no editor looking — and knowing how often that call is wrong. An LLM can propose codes in seconds, but a wrong code that publishes silently does more damage than a slow correct one.
So the agent had three jobs: tag accurately, prove where every tag came from, and know which articles still need a person.
02 · Pipeline How it works
- Load the vocabulary
The controlled vocabulary lives in a Data Table, so taxonomists can add or retire codes without touching the workflow.
- Remove near-duplicates
Articles are compared with 5-word shingles at
Jaccard ≥ 0.50. The canonical copy is picked by source tier, then body length, then date, and duplicates are marked here, then suppressed by the guard after tagging. The run pays to tag them, which the cost figure includes. - Layer 1: match companies
Word-boundary regex against a 15-record authority file, with a common-noun guard so
"shell companies"never matches Shell plc. - Layer 2: LLM tagging
Claude proposes subject, industry and geography codes. It can only choose companies from the Layer 1 candidates, and labels each one primary or passing mention.
- Layer 3: taxonomy guard
Rejects
HALLUCINATED_CODE,FACET_MISMATCH,UNGROUNDED_EVIDENCE(the quoted span must appear word for word), retired codes with no successor,BELOW_CONFIDENCE_FLOORandFACET_CAP_EXCEEDED. Retired codes with a successor are remapped, and broader terms come from the hierarchy, not the model. - Route every record
AUTO_PUBLISH,EDITOR_REVIEW,SPECIALIST_REVIEWorDUPLICATE_SUPPRESSED. If the LLM call fails, the record goes to a person. - Score and brief
Precision, recall, F1 and exact-match rate per facet, plus cost per 1,000 articles. An LLM-written run brief may only cite scorecard figures, and the Word export still ships if the brief call fails.
- Export for measurement
A side branch writes every article in the shape the evaluation harness reads, with its route and the reason for it, so each run can be scored against the answer key without hand work.
Live · Run 49 See it run
The deterministic half of this pipeline — the structural scan and the conformance guard — is plain JavaScript, so it can run anywhere. On 22 September 2026 the workflow processed a 24-article test batch, and what the model returned on each article was recorded.
Those two node bodies are shipped to a page on this site and executed in your browser over that recorded output, so you can pick an article and watch the guard adjudicate every label the model proposed. Replaying it reproduces the recorded run figure for figure — all 56 numbers on its scorecard.
Run 49 is later than the evaluation report, so its numbers are better: silent errors fell from 1 in 6 auto-published records to 0 in 7. Seven records is far too small a base to call a 0% error rate stable, and the run’s own brief says so. Subject recall, at 73.2%, is still the weakest facet, and the review queue is currently covering that gap instead of the model.
03 · Choices Design decisions
Silent error rate is the headline
A tag an editor reviews costs minutes. A wrong tag that auto-publishes costs trust. The number that matters most is auto-published records carrying a wrong tag.
Cues can block, never add
Place names, regulators, industry words and the company's registered industry catch under-tagging. They can stop an article from auto-publishing, but they never add a tag, so they can't introduce errors.
Per-facet confidence floors
Subject 0.85, industry 0.70, geography 0.85. Below 0.60 is rejected, below 0.75 is marked provisional, and each facet is capped at 4 tags.
Secondary tags earn their place
A secondary subject has to clear the same confidence bar as the lead tag, so the model can't pad records with weak extras.
04 · Evidence How I tested it
A 24-article answer key written the way editors would tag, with planted traps: "shell companies," a passing mention, retired-code bait and a renamed-company alias.
Across 5 test runs the error rate fell from 37.5% to 16.7%. Company matching scored 100% precision and recall.
Three fresh runs on those 24 articles published 7, 8 and 8 with no editor, the same 8 articles each time, and no silent errors, and I reported the ceiling with it: zero errors in 8 is still consistent with a true rate up to 32%. Then the answer key grew to 500 articles, 34 of them duplicates of another, each built as a trap. Scored by the evaluation harness, the same pipeline’s silent error rate was 27% (54 of 200). The small set was not representative, and a likely range covers chance, not a test set that leaves out the hard cases.
Most of those 54 were one gap: 36 were missing a regulation or litigation subject beside the main one. A prompt revision took subject recall from 82.8% to 94.7% and the rate to 11.6%. A new guard rung then took it to 6.5%, 6.3% and 8.6% across three runs of identical code, a drop bigger than the spread between them. The worst run, 8.6% (18 of 210, likely 5.5–13.1%), is the one to quote: the runs send out the same articles again, so adding them together (44 of 615) would claim a narrower range than the evidence holds.
The new rung’s threshold was chosen on half the articles and checked on the other half, where the three runs gave 5.4%, 6.6% and 7.6% (worst: 8 of 105, likely 3.9–14.3%). Duplicates held up at scale: 33 or 34 of the 34 suppressed in every run, and no real article ever suppressed.
05 · Production readiness What it would take to run at scale
What the build already does, and what I would add before it carried real volume. The second column is a plan, not a claim.
Monitoring
Every run writes a scorecard (routing split, per-facet precision and recall, cost per 1,000) and a compact export the evaluation harness scores against the answer key.
An editor-audited sample of auto-published articles each week, scored by the harness, with an alert when the silent error rate leaves its range or straight-through moves outside its normal band.
Failures and retries
A failed model call sends the article to a specialist, never to publish. After two concurrent 500-record runs overloaded the API, the test copy sends model calls two at a time.
Retries with backoff on refused calls, a cap on the share of failed calls per run that pauses publishing, and a queue of failed articles to rerun.
Drift
The answer key is frozen and fingerprinted; a run stops if any row changed. Changes are judged against the spread of three identical runs, not one.
Rerun the golden set on every prompt, model or vocabulary change, and fail the release when the harness says the change is worse beyond noise.
Cost at 100×
$9.21 per 1,000 articles: the tokens the original pipeline used on a measured run, costed at Claude Sonnet 5’s price today of $2 / $10 per million tokens, including duplicates it paid to tag before suppressing them.
Skip the model call for copies already marked as duplicates, and cache the fixed vocabulary part of the prompt. Both cut cost without touching routing.
06 · Security Untrusted input and what the model can touch
- Article text reaches the model, so it is treated as untrusted. The model can only return codes from the controlled vocabulary and companies from the candidates the rule layer found; anything else is rejected as an invented code.
- Every tag must quote its evidence word for word from the article, or the guard rejects it. An instruction hidden in an article cannot add a tag the vocabulary and the text do not support.
- The exported workflow carries no credentials, webhook IDs or internal URLs; the build that produced the public copy fails on a match.
07 · Iteration Tuning log
- Run 42Dropped the industry floor to 0.70. Straight-through rose from 20.8% to 45.8%, but silent errors went from 1 of 5 to 3 of 11. Reverted.
- Later runRe-applied the 0.70 floor once completeness cues were in place. Straight-through settled at 33.3%.
- Prompt changeMerging two prompt rules into one cost 16 points of subject recall.
- Gate changeGating on the lowest-confidence tag instead of the lead tag sent 87.5% of clean records to review.
- Golden 500Scored on a 500-article answer key: 27% silent error rate, where the 24-article key had shown none in the same 8 articles three times.
- Prompt v2Said what a broader term is, and when a regulation or litigation subject is tagged beside the main one. 27% to 11.6%.
- Guard v3An article goes to an editor when the model proposed a code at 0.40 or more that the 0.60 floor set aside. 8.6% in the worst of three runs; 7.6% or less on the half its threshold wasn’t tuned on.
08 · Honest notes Known limits
- The price of the guard is reviews: about 41% of articles now publish with no editor, down from 47% with the revised prompt alone (40% originally), and more than half of the reviews are ones an editor did not need.
- Five articles are silent errors in every run of the current version. They are the next place to look.
- The prompt and guard changes were made in a test copy scored on the 500. The demo replays the original pipeline’s run.
- The company authority file holds 15 records; production would need the full list.
- The scorecard includes a 90-day rollout plan for moving from test set to live ingest.
Want to see this one run?
I'll screen-share the workflow, the test set and the results, including what didn't work.