Fact-checking a database with research agents

Research agents are good at finding sources and bad at quoting them. What I check after them, learned on a run over about 14,900 places.

CityChat's catalog holds places with descriptions, opening hours, menus and contacts, and all of it goes stale: restaurants close, hotels rebrand, museums switch to winter hours. To check it I sent every place to research agents (Claude models, coordinated by a Claude Code session) and let them read the web. It only worked because nothing an agent said went into the database without passing through code that checks it. Most of the setup applies to any job like this - a product catalog, a directory, a price list.

Give the agents a source policy

A goal like "check if this restaurant is still open" isn't enough. Write down which sources count: the venue's own site and its social page, official city and tourist portals, local news. And which don't: map listings and their mirrors, review sites, delivery apps and booking sites, which copy each other and go stale together. Hours are quoted from a page and never inferred, and dish names are copied as printed, never translated.

Put those rules in the prompt itself. I first kept them in a protocol file that the prompt pointed to, and the agents skimmed it; a rule in the prompt gets followed more often, though still not always. Batches of 30 places with about eight agents running at once worked well, and every proposal had to come back with the address of the page it came from.

Check every claim in code

The trap that matters most: the web fetch tool an agent uses returns a model's summary of the page, not the page. So "the page says 9 to 17" can be the summary's guess. Every proposed opening time, dish and price is fetched again, raw, and has to appear in the source word for word, or it doesn't go in. This gate caught most of the invented hours.

Structured data on websites needs the same care. Many site builders fill in "Mo–Su 09:00–17:00" by default and the owner never changes it. A restaurant page can carry its bar's data, and my own script that read it merged a day listed twice, morning and afternoon, into one range, so a café with split hours came through with only its afternoon. Treat structured data as a hint and read the visible text.

The mistakes I saw again and again, worth a test each:

  • days the page doesn't name, filled in anyway;
  • a day with split hours flattened into one range, or dropped;
  • hours taken from another branch of the same chain, from a guide site or from a search snippet;
  • menus translated from the English version instead of quoted;
  • too few searches: told to make three to five per place without a website, the agents averaged one or two.

Deleting needs more evidence than adding. A place goes as closed only on the venue's own announcement, the operator, an official portal or local news; a dead domain or a directory listing isn't enough, and anything uncertain stays. A building and the institution inside it, like a palace and the museum in it, stay as two places.

Write as if it will fail halfway

All writes go through one queue, one job at a time, so two jobs never edit the same record. The state of every record before the write goes into a ledger, and every write is read back. When a job fails, the queue stops and the rest waits. In my run the admin token expired overnight halfway through a city; the queue stopped cleanly and carried on from the ledger the next morning, and every city finished with no failed writes.

Some facts come with a date. Hours written in October are autumn hours, and nothing refreshes them on its own, so the ones that change with the season get a reminder.

Over five days about 14,900 places in 16 cities went through this. In the first city 433 places were researched and 340 changed: 278 new descriptions, opening hours set or corrected for 64 places and removed as wrong for 8, and 28 priced menus from the venues' own sites. One restaurant's menu turned out to be a kebab shop's list. Places without a website stayed thin; with nothing to read, many of them ended as "unverified".

← All writing Get in touch