Everyone can generate a report now. The differentiator for a research agent is not whether the prose reads well; it is whether every sentence in that report can be traced back to a fetched source, with the URL attached. That is the hard part, and it is the part I spent most of my time on while building the ArchMind Research Agent, a Python, LangGraph and Streamlit system that plans a query, scrapes the web, normalizes what it finds and writes a cited brief. A report without verifiable citations is a confident rumor, and confident rumors are worse than no report at all, because they smuggle unverified claims into decisions.
The project and source: ArchMind Research Agent, code at github.com/KunjShah01/INTERNSHIP-ASSESSMENT. This is a build account, not a demo reel.
Why citation is the hard part, not generation
Generation is the solved half. Any competent model writes fluent paragraphs on demand. The unsolved half is grounding: for each claim the writer emits, there must exist a fetched document that supports it, and the system must attach that document's identity to the claim. Do this and you have a research tool. Skip it and you have a hallucination engine with a professional tone.
The architecture exists to serve exactly that constraint. The planner never lets the writer freelance; the writer never emits a claim without a source pointer; and the system is willing to say "not sourced" rather than smooth over a gap with plausible prose.
Architecture walkthrough: plan, search, scrape, normalize, write
Five stages, each with a contract and a failure mode. If any stage lies, the final report inherits the lie.
Plan: decompose before you retrieve
The planner breaks the request into sub-questions. This matters because broad questions retrieve broad, shallow junk. "What are the trade-offs of X" becomes three or four answerable sub-questions, each with its own retrieval pass. Contract: every sub-question must be individually answerable from sources; failure mode: sub-questions that are just rephrasings of the original vagueness, which means the planner has not actually reduced scope.
Search and scrape: fetch, do not imagine
Each sub-question triggers search, then scraping of the results worth reading. Contract: only fetched content enters the context; failure mode: the model reaching for parametric memory because the fetch failed and nobody noticed. I treat an empty scrape as a signal to widen or change the query, never as a cue to fill in from training data.
Extract: normalize the noisy web
Raw pages are unusable: nav bars, cookie walls, related-link farms, comment sections. The extractor applies readability-style extraction, deduplicates near-identical content across sources, scores sources for value, and drops the ones that will poison the report. Contract: output is clean, deduplicated, scored text with provenance metadata; failure mode: a JS-only shell page that passes extraction but contains nothing, or a login wall that yields a 200 status and zero content.
Write: grounded, cited, honest about gaps
The writer produces the report from retrieved evidence only, with each sourced statement carrying its URL. The report distinguishes what is sourced from what is inferred, and unverifiable claims get flagged, not smoothed over. Contract: no source, no claim; failure mode: a paragraph that starts as evidence and drifts into fluent speculation by its third sentence.
Attribution travels through the pipeline
One design decision that paid off: provenance metadata is attached at scrape time and never detached. The extractor does not just return clean text; it returns clean text with its source identity bound to it, and that binding survives the merge, the ranking and the write. The alternative, scraping first and re-attaching URLs later by matching sentences to pages, is where fabricated citations are born. If you cannot trace a sentence to a fetched document by construction, you will eventually cite a document that never contained the claim.
Taming noisy extraction
The web is predominantly noise. The extraction stage earns its keep by being picky:
- Readability extraction to recover the article body from the chrome: navigation, sidebars, ads, footers, cookie banners.
- Deduplication, because the same wire story republished across forty sites adds zero information and dilutes source diversity scores.
- Source scoring to rank what deserves context space: primary sources and substantive analysis outrank listicles, SEO spam and content farms.
- Hard drops for login-walled pages and JS-only shells that return markup without content. A 200 response tells you nothing about whether you got the thing.
- Listicle and spam triage: "Top 10 X Tools You NEED in 2026" style pages are structurally designed to rank, not to inform; they go to the bottom or out.
None of this is glamorous. It is the difference between a RAG pipeline that cites real evidence and one that cites the SEO-optimized version of a rumor.
Drift on broad queries
Every research system degrades on broad queries, and mine was no exception. Ask for a wide-ranging landscape and retrieval starts returning everything and nothing: adjacent topics, marketing pages, outdated versions of the answer. The failure feels like success because the report still reads well.
The fix is decomposition, not more searching. Break the broad query into sub-questions, retrieve per sub-question, then merge with explicit conflict resolution: when sources disagree, the report says so and shows both. Tightening scope beats searching wider, every time. If the evidence is thin, the correct move is to narrow the question, not to lower the evidence bar.
I also found that broad queries expose citation rot fast: the top results are frequently stale rewrites of older content, so freshness and source scoring have to be part of retrieval, not an afterthought applied at write time.
Verification: quote-level grounding
Citation is only meaningful if a reader can check it. My verification pass works at the quote level: the writer's claims are mapped to supporting spans in the fetched sources, and the report marks what is sourced versus what is inferred. Claims that cannot be grounded are flagged explicitly rather than rephrased into confident vagueness.
Concretely, each claim in the output carries one of three states: sourced (a specific fetched span supports it), inferred (drawn from sourced material but not directly stated in it), or unverifiable (no support found). Only the first two are allowed in the final report body, and inferred claims are labelled as such. Unverifiable claims are either dropped or presented as open questions for the researcher, never as findings. This triage is what turns "cited" from a cosmetic footer of URLs into an actual guarantee.
It also changes how you debug. When a user disputes a line in the report, you do not re-read the whole document; you trace that line to its span. If the span is missing, the bug is in the writer. If the span is present but misread, the bug is in comprehension. If the span is present and correct but the reader was misled, the bug is in presentation. Each failure mode has a different fix, and quote-level mapping is what makes them distinguishable.
This is the uncomfortable feature: the agent will tell you when the web does not support an answer. That refusal is a feature. A research tool that never says "I could not verify this" is optimizing for user comfort, not for truth.
Limitations
This is an assistant for a researcher, not a replacement for judgment. Paywalled sources are invisible to it. Fast-moving topics can outrun retrieval: by the time a page is scraped and scored, the situation may have shifted. Citation rot is real: URLs die, content changes under the same URL, and a citation that resolved at write time may resolve differently later. JS-heavy and login-walled sources still slip through occasionally. And source scoring encodes my priors about what good sources look like, which is a bias, carefully applied but a bias nonetheless.
The right posture is: let the agent do the gathering, normalizing and first-draft synthesis, and let the human own the judgment calls, the disagreements between sources, and the final word on what to trust.
Two practical habits help if you operate the agent yourself. First, spot-check citations on a sample of reports, not just the ones that felt weak while reading; confidence and accuracy diverge exactly where the answer looks most polished. Second, keep a running list of sources that produced misleading or unusable output, and feed it back into source scoring. The extractor gets better when its misses are actually reviewed, which most teams never do.
The lesson that outlives the stack
Provenance is the product. Every other property, the Streamlit UI, the LangGraph orchestration, the scraping pipeline, exists to make the citations real. If you are building a research agent, decide on day one what happens when evidence is missing. My answer: flag it, never smooth it over. Everything else in the system is in service of that rule.
If you need a custom research agent grounded in your own corpus, or a RAG system whose citations hold up under scrutiny, that is my work. Fixed scope from ₹25,000, fixed quote in writing, free 15-minute intro call: https://kunjshah.in/contact.