{"course":{"slug":"agents-and-automation","title":"Agents and Automation","url":"https://addaly.com/learn/agents-and-automation","level":"intermediate","track":"build"},"lesson":{"slug":"the-research-agent","title":"Pattern: search, read, cite, and verify the citation","url":"https://addaly.com/learn/agents-and-automation/the-research-agent","position":50,"of":72,"minutes":10,"answer":"A research agent’s output is prose with real-looking sources, so the only defence that scales is checking mechanically that every quoted span exists in the page it is attributed to.","keyIdea":"A research agent’s output is prose with real-looking sources, so the only defence that scales is checking mechanically that every quoted span exists in the page it is attributed to."},"body":{"format":"markdown","text":"## The shape\n\nA question comes in. The agent searches, opens pages, reads, and produces an answer with citations. It is the most requested automation in most organisations and the one most likely to be wrong, because everything about the output signals reliability: it is fluent, it is structured, and it has links.\n\n## The check that makes it usable\n\nRequire every claim to carry a source URL **and a verbatim quoted span** from that page. Then verify in code that the span actually appears in the text you fetched.\n\n```python\npage = fetched[claim[\"url\"]]\nif normalise(claim[\"quote\"]) not in normalise(page):\n    claim[\"status\"] = \"unverified\"     # never shown as supported\n```\n\nThis is the same mechanism as the extraction quote check, and it is the single most valuable line of code in this pattern. It catches the failure that matters: a real URL attached to a claim that page does not make. A model can invent a sentence; it cannot make the sentence appear in a document you have in front of you.\n\nStore the fetched pages with the report. Link rot, paywalls and edits mean the page a reader visits next month is not the page you read, and a stored snapshot is the difference between a defensible answer and an argument.\n\n## One search is not research\n\nLeft alone, an agent will run one query, read the top three results and write confidently. That is a summary of a search-engine ranking, not an answer.\n\nForce breadth structurally: require at least three differently phrased queries, require sources from more than one domain, and make **\"I could not find this\"** a legitimate output with its own field. An agent that cannot report absence will always report something.\n\nThe specific failure to design against is a niche question where the only pages available are content farms. The agent will use them, because they are what exists, and nothing in its output will distinguish them from a standards document.\n\n## Source quality is your job, not the model's\n\nModels are poor judges of authority. A well-formatted SEO page and a primary standards document read as equally credible, and the SEO page is often clearer, because it was written to be.\n\nSo rank domains yourself. Keep a list: official documentation, standards bodies, regulators, peer-reviewed journals and named primary sources at the top; general press below; content aggregators excluded. Pass the tier to the agent with each result and require it to prefer the higher tier when sources conflict. This is a hand-maintained list of thirty domains and it does more for output quality than any prompt engineering.\n\n## Design for disagreement\n\nReal questions have contested answers. If the output schema has one `answer` field, the agent will produce one answer and silently pick a side.\n\nAdd a `disagreements` array, and require it to be populated when sources conflict. The structure forces the behaviour: a field that exists gets filled, and a reader who sees \"two sources say X, one says Y, and here is the difference\" is being told something true. This matters most in exactly the areas where it is tempting to give a clean answer (law, medicine, tax, anything cross-border) and giving a clean answer there is where a research tool does actual harm.\n\n## Recency cuts both ways\n\nSearch rankings favour recent content, so an agent asking about a stable technical fact may get a blog post from last month rather than the specification from 2019. Meanwhile, for fast-moving subjects, the model's own knowledge is stale and it may override fresh sources with what it remembers.\n\nTwo habits: include the publication date with every source in the context and require it in the citation, and for anything time-sensitive, put today's date in the final message so the agent is not reasoning from an assumed year.\n\n## Budget it, because this is the expensive pattern\n\nSearch, fetch, read, repeat: this is the shape that runs forty steps and re-sends a growing transcript each time. Cap the number of pages fetched, summarise each page down to its relevant passages before adding it to the transcript, and store full text on disk behind a handle. A research agent with no page budget is the most reliable way to discover the quadratic cost curve from the context-window lesson in the form of an invoice."},"check":{"question":"A research agent produces a report where every claim has a real, working URL, yet several claims are not supported by the pages they cite. Which control catches this at scale?","options":[{"text":"Requiring a verbatim quoted span per claim and checking in code that the span appears in the fetched page.","correct":true},{"text":"Having a second model read each source and judge whether it supports the claim.","correct":false,"tempting":"A reviewer model helps at the margins but is judging plausibility with the same weakness as the first, and costs a call per claim."},{"text":"Checking that each URL resolves and returns a 200 response before publishing.","correct":false,"tempting":"Confirms the page exists, which is already true here: the failure is about what the page says, not whether it loads."},{"text":"Instructing the agent to cite only sources it has actually opened during the run.","correct":false,"tempting":"Sensible wording, but the agent did open them; the gap is between the page’s content and the claim, which an instruction cannot verify."}]},"prev":{"slug":"the-triage-queue","title":"Pattern: sort, enrich, escalate","url":"https://addaly.com/learn/agents-and-automation/the-triage-queue"},"next":{"slug":"the-monitor","title":"Pattern: watch something, say when it changes","url":"https://addaly.com/learn/agents-and-automation/the-monitor"},"authorship":"Founder and series editor: Dr Mustafa Mun. Written by AI models to a written standard. Exam keys checked blind by a second model: 3,665 of 3,666 agreed. Academic review invited.","review":{"status":"Draft, open for academic review"},"cite":"Addaly (2026) 'Pattern: search, read, cite, and verify the citation', in Agents and Automation. Edited by M. Mun. Addaly. Available at: https://addaly.com/learn/agents-and-automation/the-research-agent (Accessed: 13 October 2026).","licence":{"name":"CC BY-NC-SA 4.0","url":"https://creativecommons.org/licenses/by-nc-sa/4.0/"},"reader":{"schema":1,"title":"Pattern: search, read, cite, and verify the citation","courseTitle":"Agents and Automation","module":{"index":6,"slug":"automation-patterns","title":"Eight patterns that keep working"},"answer":null,"keyIdea":"A research agent’s output is prose with real-looking sources, so the only defence that scales is checking mechanically that every quoted span exists in the page it is attributed to.","hero":{"kind":"plate","image":null,"alt":null,"caption":null},"blocks":[{"t":"h2","id":"the-shape","runs":[{"text":"The shape"}]},{"t":"p","runs":[{"text":"A question comes in. The agent searches, opens pages, reads, and produces an answer with citations. It is the most requested automation in most organisations and the one most likely to be wrong, because everything about the output signals reliability: it is fluent, it is structured, and it has links."}]},{"t":"h2","id":"the-check-that-makes-it-usable","runs":[{"text":"The check that makes it usable"}]},{"t":"p","runs":[{"text":"Require every claim to carry a source URL "},{"text":"and a verbatim quoted span","b":true},{"text":" from that page. Then verify in code that the span actually appears in the text you fetched."}]},{"t":"code","lang":"python","label":"Python","text":"page = fetched[claim[\"url\"]]\nif normalise(claim[\"quote\"]) not in normalise(page):\n    claim[\"status\"] = \"unverified\"     # never shown as supported"},{"t":"p","runs":[{"text":"This is the same mechanism as the extraction quote check, and it is the single most valuable line of code in this pattern. It catches the failure that matters: a real URL attached to a claim that page does not make. A model can invent a sentence; it cannot make the sentence appear in a document you have in front of you."}]},{"t":"p","runs":[{"text":"Store the fetched pages with the report. Link rot, paywalls and edits mean the page a reader visits next month is not the page you read, and a stored snapshot is the difference between a defensible answer and an argument."}]},{"t":"h2","id":"one-search-is-not-research","runs":[{"text":"One search is not research"}]},{"t":"p","runs":[{"text":"Left alone, an agent will run one query, read the top three results and write confidently. That is a summary of a search-engine ranking, not an answer."}]},{"t":"p","runs":[{"text":"Force breadth structurally: require at least three differently phrased queries, require sources from more than one domain, and make "},{"text":"”I could not find this”","b":true},{"text":" a legitimate output with its own field. An agent that cannot report absence will always report something."}]},{"t":"p","runs":[{"text":"The specific failure to design against is a niche question where the only pages available are content farms. The agent will use them, because they are what exists, and nothing in its output will distinguish them from a standards document."}]},{"t":"h2","id":"source-quality-is-your-job-not-the-model-s","runs":[{"text":"Source quality is your job, not the model’s"}]},{"t":"p","runs":[{"text":"Models are poor judges of authority. A well-formatted SEO page and a primary standards document read as equally credible, and the SEO page is often clearer, because it was written to be."}]},{"t":"p","runs":[{"text":"So rank domains yourself. Keep a list: official documentation, standards bodies, regulators, peer-reviewed journals and named primary sources at the top; general press below; content aggregators excluded. Pass the tier to the agent with each result and require it to prefer the higher tier when sources conflict. This is a hand-maintained list of thirty domains and it does more for output quality than any prompt engineering."}]},{"t":"h2","id":"design-for-disagreement","runs":[{"text":"Design for disagreement"}]},{"t":"p","runs":[{"text":"Real questions have contested answers. If the output schema has one "},{"text":"answer","code":true},{"text":" field, the agent will produce one answer and silently pick a side."}]},{"t":"p","runs":[{"text":"Add a "},{"text":"disagreements","code":true},{"text":" array, and require it to be populated when sources conflict. The structure forces the behaviour: a field that exists gets filled, and a reader who sees “two sources say X, one says Y, and here is the difference” is being told something true. This matters most in exactly the areas where it is tempting to give a clean answer (law, medicine, tax, anything cross-border) and giving a clean answer there is where a research tool does actual harm."}]},{"t":"h2","id":"recency-cuts-both-ways","runs":[{"text":"Recency cuts both ways"}]},{"t":"p","runs":[{"text":"Search rankings favour recent content, so an agent asking about a stable technical fact may get a blog post from last month rather than the specification from 2019. Meanwhile, for fast-moving subjects, the model’s own knowledge is stale and it may override fresh sources with what it remembers."}]},{"t":"p","runs":[{"text":"Two habits: include the publication date with every source in the context and require it in the citation, and for anything time-sensitive, put today’s date in the final message so the agent is not reasoning from an assumed year."}]},{"t":"h2","id":"budget-it-because-this-is-the-expensive-pattern","runs":[{"text":"Budget it, because this is the expensive pattern"}]},{"t":"p","runs":[{"text":"Search, fetch, read, repeat: this is the shape that runs forty steps and re-sends a growing transcript each time. Cap the number of pages fetched, summarise each page down to its relevant passages before adding it to the transcript, and store full text on disk behind a handle. A research agent with no page budget is the most reliable way to discover the quadratic cost curve from the context-window lesson in the form of an invoice."}]}],"check":{"question":[{"text":"A research agent produces a report where every claim has a real, working URL, yet several claims are not supported by the pages they cite. Which control catches this at scale?"}],"options":[{"text":[{"text":"Requiring a verbatim quoted span per claim and checking in code that the span appears in the fetched page."}],"correct":true,"tempting":null},{"text":[{"text":"Having a second model read each source and judge whether it supports the claim."}],"correct":false,"tempting":[{"text":"A reviewer model helps at the margins but is judging plausibility with the same weakness as the first, and costs a call per claim."}]},{"text":[{"text":"Checking that each URL resolves and returns a 200 response before publishing."}],"correct":false,"tempting":[{"text":"Confirms the page exists, which is already true here: the failure is about what the page says, not whether it loads."}]},{"text":[{"text":"Instructing the agent to cite only sources it has actually opened during the run."}],"correct":false,"tempting":[{"text":"Sensible wording, but the agent did open them; the gap is between the page’s content and the claim, which an instruction cannot verify."}]}],"order":[1,2,3,0],"why":null},"prev":{"slug":"the-triage-queue","title":"Pattern: sort, enrich, escalate","minutes":9},"next":{"slug":"the-monitor","title":"Pattern: watch something, say when it changes","minutes":9}}}