# Agentic research agent for capital projects data

> A multi-agent tool I built to fill gaps in a client's capital-project records, ready for their database, with cited sources and checks for analysts to review.

- Role: Designed and developed
- Period: Jul 2025 – Nov 2025
- Status: Completed
- Technology: Python, OpenAI API, Google Gemini API, doc-processing-toolkit, Pydantic, pandas, Streamlit, Playwright
- In numbers:
  - **~1 week → 2–3 h** estimated research time per batch in the client's use, including analyst review
  - **219 s** one-record demo run, 21 October 2025: 19 of 19 fields, 13 of 14 sources archived
- URL: https://www.rounakzoeb.com/work/agentic-research-agent

---

![Pipeline diagram: an OpenAI researcher agent and a Gemini validator agent in a feedback loop, then a Gemini finaliser agent, deterministic checks and analyst review, with cited sources archived as PDFs.](https://www.rounakzoeb.com/_astro/01_pipeline.Bd9SF_ZV_sJuUm.png)

## The problem

A client's data and analytics team maintained a benchmarking dataset of capital projects. Many records had gaps, such as missing schedule dates, cost changes or contracting details. Filling them meant desk research, which involved searching the web, reading announcements and reports, and checking that the dates made sense. A batch took about a person-week. Doing this by hand also meant handling the data twice: after the research, someone had to reformat the values into the structure another database required, so that the completed records could be loaded into it.

The team wanted this to go faster, and they needed each value the tool filled in to be traceable to a source an analyst could check. The tool also had to produce its records in that database's format, so analysts could review the content without reworking the data.

## How the research works

I designed and developed a multi-agent research system for this, built on an early, project-specific version of my agent framework (now [doc-processing-toolkit](https://www.rounakzoeb.com/work/doc-processing-toolkit)). Each record goes through three agents in turn.

Take a record that's missing, say, its completion date and its contracting details. A researcher agent, an OpenAI model with built-in web search, looks for that information and returns research notes with links to its sources. A validator agent running on Gemini then checks the notes for completeness, date logic and source credibility. The validator is a form of what's now called LLM-as-a-judge: a second model, with a different job, deciding whether the first one's work is good enough. It returns a completion status and, where needed, feedback for another research pass. In a recorded demo on 21 October 2025, for example, it rejected the first pass because a mid-project milestone had been taken as the project's start date, and the second pass was accepted.

That feedback is what made repeat passes worthwhile. A retry without guidance tends to run the same search again, while one that's told what's wrong usually finds something new.

The loop stops when the validator judges the information sufficient, or when it reaches a configurable limit on research passes (two by default). The limit keeps processing time and cost under control, but reaching it doesn't necessarily mean the research is complete; choosing its value is a trade-off between cost and accuracy. A finaliser agent, also on Gemini, then turns the results into structured records in the format the client's database needed, validated against a schema with Pydantic (a Python data-validation library). If the loop stopped at its limit, the finaliser works with what was found, so some fields can stay empty or uncertain, and the record still goes through the checks and analyst review.

I chose the models after testing alternatives early in the build. I measured them against an evaluation set: records the client's team had already researched by hand, so the correct values were known. Later I used the same set to benchmark the whole system. An OpenAI researcher with built-in web search, with Gemini validating and finalising, gave the best balance of cost and quality for this task.

## Working from the team's data model

The records followed the team's own data model: 19 fields, two of which take their values from a defined list, with a definition for each value: 15 reasons a project's schedule or cost changed, and 7 contract strategies. I wrote that model once, as a schema, and generated all three agents' instructions from it. A new field or category reaches the researcher, the validator and the finaliser at the same time, and the finished records are checked against the same definitions, so the agents use the team's own terms.

## Checks and analyst review

I didn't want the models doing date arithmetic or applying consistency rules, so once the agents finish, rule-based Python checks take over. Language models are good at finding and reading sources, but a calculation done by a model costs more and is less reliable than the same calculation in code, which gives the same answer every time. Using each for what it does well was also the simplest design that worked. The checks recalculate project durations and delays from the dates, flag inconsistencies and verify key fields. An analyst then reviews the record, with its cited sources and anything the checks flagged, and decides what to accept.

## Archiving sources

Web pages change and disappear, so a link alone wasn't enough for an analyst to rely on. A supporting tool saves each cited source as a PDF and picks the capture method to suit the page. It downloads the file directly when the source is already a PDF, renders dynamic, JavaScript-heavy pages in a browser with Playwright, and converts static pages with wkhtmltopdf, an HTML-to-PDF tool. If browser rendering fails, it falls back to HTML-to-PDF conversion.

The PDFs are organised by project and linked to the research results, so an analyst can see each source as it looked when the research was done. Archiving worked for most sources; a few edge cases still need the analyst to check the original.

## How I measured it

The client's team had been filling these records by hand, so their work gave me something to measure against. Of the first ten projects, four had been researched in full, with a source for every value, and six had part of their data.

I used the four complete projects to tune the system, comparing each run with the known values. The differences showed what to change. The instructions now say which sources to prefer and which inconsistencies to look out for, and they ask for evidence before a field is marked as not found. I also tightened the schema, added rule-based checks on the dates, and changed the models. In the first week of August 2025 the researcher moved from gpt-4.1-mini to o4-mini, and the validator and finaliser from Gemini's Flash-Lite models to Gemini 2.5 Pro.

I then ran it on the other six projects, for the fields their records didn't have, and checked its answers against the sources myself. By my checks, the share of fields it got right rose from about half to about 90% over those rounds. Dates were the hardest, because sources often give several versions of the same milestone. After the full run of 57 projects I spot-checked several by hand before handover, and the client later confirmed that they were finding the results around 90% accurate in practice.

The checks stay on in normal use. On the full run they flagged 26 of the 57 projects for an analyst to look at, most often because a project appeared to have finished sooner than planned. That review, with a person in the loop, still takes a fraction of the time that researching every record by hand did, and it's what keeps the dataset accurate enough to rely on.

## Results and handover

In the recorded demo on 21 October 2025, a single record took 219 seconds. The run completed 19 of its 19 fields and archived 13 of its 14 cited sources.

The batch figures are my estimates from the client's production use after handover. Research that had taken about a person-week per batch came down to two to three hours, including human review. Roughly 30 minutes of that was automated processing, and the remaining two to two and a half hours was analyst review.

I delivered the system with a Streamlit web interface and documentation for the team, and handed it over to the client in November 2025. The client's team now runs it.

## What I learned

The evaluation set is what made the improvements measurable. Without records whose correct values were known, I'd have been judging each change to the prompts and models by whether the output looked plausible, and a date taken from the wrong milestone can look just as plausible as the right one.

Prompts that push a model to fill every field invite made-up answers. The agent has to be able to say it couldn't find something, and its accuracy has to be checked field by field. Here the researcher could mark a field as not found if it gave evidence that it had looked, so analysts could see which values were missing, and the evaluation scored each field on its own. The cheapest quality improvement in this project was putting a validator between the researcher and the output, a second model whose only job is to say "not yet" and explain why.

If I were extending the system, I'd finish wiring up the per-role model picker, so the cost and quality comparison could be re-run against the evaluation set as new models come out.

## More from the project

![Diagram of the research loop: researcher, validator, a decision that ends the loop when sufficient information is found or the iteration limit is reached, and the finaliser.](https://www.rounakzoeb.com/_astro/02_research_loop.CTAbMdXA_1Nh5ry.png)

*The research loop. An OpenAI researcher gathers project information, a Gemini validator checks it and asks for more research where needed, and a Gemini finaliser turns the result into structured, schema-validated records. The number of research passes is configurable, to control processing time and cost.*

![Diagram of how each cited URL is archived as a PDF by direct download, browser rendering or HTML-to-PDF conversion, with HTML-to-PDF conversion as the fallback if browser rendering fails.](https://www.rounakzoeb.com/_astro/03_source_archiving_strategy.DDxaBxa3_cqf7O.png)

*How sources are archived. Depending on the source, the tool downloads a PDF directly, renders the page in a browser or converts static HTML to PDF, and falls back to HTML-to-PDF conversion if browser rendering fails.*
