Attest · Hyperion AI
We kept having to check the research our own tools produced.
So we built something that checks it first. Attest is a research harness: it reads sources, pulls out what they actually say, saves a copy of every page it used, and confirms each claim against its source before you see it. It's a Python library on top of LangGraph, and it runs on your own infrastructure.
What it is
A harness is something you build on, rather than something you use as-is.
Most research tools are finished products. They work well until a client needs something slightly different, and then you're either waiting on someone else's roadmap or maintaining a fork. We wanted the opposite: a core that handles the hard parts, and a clear place to put whatever a particular engagement needs.
One graph, not a team of agents
There's no crew of agents passing messages to each other. It's a single graph where each step reads the same shared state, so nothing gets lost in translation between them.
The state object is what you program against
Everything the system knows lives in one place. That means the decisions about what to search next and when to stop are ordinary functions you can read and test without calling a model at all.
New domains build on the same core
Market research, security work, diligence — each adds its own fields and steps. The parts that took longest to get right are shared by all of them.
How it works
It keeps going until the evidence is actually there.
Most systems run a fixed number of search rounds and then write whatever they have. This one scores how well each part of the question is answered, and only stops when the gaps that remain wouldn't change the answer. Here's the whole thing — have a look at any step.
The loop
Pick a step
Each one is a small, ordinary piece of code. The interesting part is the dashed line on the left — that's the system deciding it hasn't found enough yet and going back around.
click or tab to a step
Where it looks
Most of what's worth knowing isn't on the open web.
It's in a contract archive nobody has opened in two years, a database with no documentation, a folder of scanned correspondence, and a spreadsheet three people maintain by hand. Research tools that only read web pages are useless for the questions that actually come up inside a company.
Attest reads all of it through the same set of adapters. Each one knows how to find things in its own kind of source, how to pull them out, and — this is the part that takes the work — how to say precisely where something came from afterwards.
Search, pages, papers, filings
PDF, Word, PowerPoint, email archives
Correspondence and records that only exist on paper
Including the ones with merged cells and notes in the margins
Read-only, with the query kept as the reference
Ticketing, wikis, CRM, whatever you run
Numbers get calculated, not read.
When a figure comes from a database, it would be strange to hand fifty thousand rows to a language model and ask it to add them up. So we don't. The system writes a query, runs it, and keeps the query as the reference — which means the number can be reproduced exactly, by you, later.
"Revenue grew to 4.2M in the fourth quarter."
Backed by a saved copy of the page and the sentence it came from.
SELECT SUM(amount) FROM revenue
WHERE region='EMEA' AND quarter='2025Q4'
Backed by the query itself. Run it again and you'll get the same answer — or find out it changed.
Six months on, it can tell you what's moved.
Because the queries are kept, a delivered report can be re-checked against the systems it came from. Figures that have since been revised come back flagged, with the old value and the new one side by side. It's a small thing that turns out to matter a lot when the report is being discussed months after it was written.
When nothing can leave
It can run entirely inside your network.
Banks, government bodies and anyone under a serious data-residency obligation can't send their documents to somebody else's API, which rules out every hosted research product on the market. Attest has an offline mode: internal sources only, a model running on your own hardware, nothing crossing the boundary.
One thing worth knowing about closed collections — agreement means less in them. Three internal documents saying the same thing are often three descendants of one memo. The system accounts for that, and weighs internal agreement more carefully than it would three unrelated sources on the open web.
What actually runs
The whole thing is a Python service and four stores.
There's no cluster to stand up and no vendor platform underneath it. It's a library you import, a service you run, and somewhere to keep what it collects. The dashed line is the part worth paying attention to — everything inside it sits on your own infrastructure, and in offline mode nothing crosses it at all.
It's not heavy
Most of the wall-clock time is spent waiting on searches and model calls, not computing. A small VM handles it. Storage grows by roughly a megabyte of saved pages per research run.
Different jobs, different models
Planning and writing use a capable model. Reading pages and comparing statements — the bulk of the calls — use a small fast one. That split is what keeps a full run at around a dollar fifty rather than fifteen.
It picks up where it stopped
Progress is written down after every step. A run that dies halfway through — bad network, restarted container, hit a rate limit — resumes rather than starting over and paying for the same searches twice.
What it stores
It works in claims rather than in documents.
This turned out to be the decision everything else depended on. Most systems keep chunks of text and trust the model to read them properly at the end. Attest splits each source into individual statements, resolves the pronouns and dates while it still has the context, and attaches where each one came from.
Once evidence is stored that way, you can do things that are awkward otherwise: spot two sources disagreeing, notice that five articles are all repeating one press release, or check a single sentence against the page it came from.
class Claim(BaseModel): text: str # one statement, nothing left ambiguous source_id: str source_tier: Tier # filing, reporting, blog, or questionable cluster_id: str # which sources are genuinely separate snapshot_hash: str # the copy of the page we kept verification: Verdict # does the page really say this verified_span: str | None # the sentence that backs it up contradictions: list[Conflict] source_language: str = "en"
One claim, as it appears in a report
Armenia's ICT sector grew 21 percent in 2024, outpacing overall GDP growth.
- The source is still online
- A copy travels with the report
- The page really does say this
- Three unrelated sources agree
If a claim can't pass these, it doesn't go in. What we couldn't find gets written down as something we couldn't find.
Extending it
When a project doesn't quite fit, you add to it.
A security engagement needs different inputs than a market study, but both need the same careful handling of evidence. So a new domain adds the fields it needs and slots in its own steps, and the parts that were hard to get right — the scoring, the verification, the conflict handling — come along unchanged. Nothing in the core gets edited.
class SecurityState(ResearchState): asset_scope: list[str] cve_ids: list[str] = [] graph = build_harness(state=SecurityState) graph.add_node("asset_enrichment", enrich) graph.add_edge("grader", "asset_enrichment") # that's the whole integration
Something we ran into
Some pages contain instructions meant for the software reading them.
It's a real and growing problem. Text is hidden from human visitors with a bit of CSS, but sits there in the markup where any automated reader will pick it up — often telling it to ignore what it was asked and say something else instead. Attest removes that text before anything reads the page, and then treats the attempt as what it is: a good reason to be sceptical of the site.
There isn't much to take over
It can't write files, send mail, or run code. The only thing it produces is a document for you to read, which limits how much damage a bad page can do.
Agreement only counts if it's independent
A regulatory filing carries more weight than a blog post, and ten outlets carrying the same press release are counted as one source, not ten.
Sources are read in their own language
Translating a whole page before reading it loses things. We read and verify in the original, and translate the finished claim instead.
Useful when someone's going to ask where a number came from.
Board papers, diligence, regulatory work — the kind of research where being roughly right isn't enough and you need to show your working. If that sounds like what you're dealing with, we're happy to walk you through it.