Inspiration

Before you take on a client, hire a vendor, sign a partnership or accept a job offer, you look them up. The people who most need to do that properly usually have nobody to do it for them. A freelancer wondering whether a new client will actually pay. A small agency picking a vendor. Someone weighing a remote job offer from a company they've never heard of. That last group is getting hit hard: job scam losses reported to the FTC went from $90 million in 2020 to $501 million in 2024 (FTC), and people reported losing nearly $1 billion to business impersonators in 2025 (FTC).

AI research tools make looking someone up fast, and every sentence comes with a source, which feels thorough. I found out the hard way that it isn't. An early version of Recon told me to walk away from Crossover, a remote-hiring company, because "its recruiting operation is a scam". It was 85% confident and every claim was cited. Then I actually opened the sources. The funding round was Crossover Markets, a crypto exchange. The software release was CodeWeavers' CrossOver, a Mac app. The CEO ran Crossover Health, a clinic chain. And the scam reports were about scammers pretending to be Crossover.

Four claims, three other companies, one wrong verdict. That's when it clicked for me: a citation is not a check. Recon is built around that idea.

What it does

You type in an organisation and say what you're about to do with it. That decides what Recon looks into:

I am about to Recon investigates
Take them on as a client Identity, payment behaviour, financial health, legal, reputation
Hire them as a vendor Identity, delivery track record, reputation, legal, financial health
Partner with them Identity, ownership and leadership, track record with partners, legal, financial health
Accept their job offer Identity, recruiting legitimacy, life as an employee, financial health, recent news
Just look them up Identity, what they do, financial health, legal, reputation, recent news

Then you watch the file get built. An agent researches the company on the live web, a small model writes the first draft, and a much bigger model goes back through every page the draft cites and rules on every single claim: held, corrected or struck. Drafts show up dimmed. Claims that hold up brighten. Corrected ones show what the draft originally said. Struck ones get a redaction bar that lifts into a strike-through, with the judge's reason right underneath.

At the end you get a decision: proceed, caution or stop, made only from the claims that survived. You also get the red flags behind it, the questions to ask the other party before you commit, the pages that were thrown out for being about someone else, and what the check cost.

Here's a real one. I checked a job offer from Turing. The draft said Turing's AI was being tested in cryptanalysis, but the article it cited was about Alan Turing and the Enigma, so Ultra struck it. The draft also said applicants had reported being asked for upfront fees, a classic scam sign. The source only said some applicants never heard back, so Ultra corrected it. The decision was caution: a real, well-funded company, but with repeated reports of layoffs and short contracts.

How I built it

Every model call goes to NVIDIA Nemotron on Nebius Token Factory, and each size does the job it's best at:

Stage Model What happens
Identify Nemotron 3 Super Works out which organisation you mean, and separates its own names ("Crossover for Work") from different organisations with similar names ("CrossOver by CodeWeavers")
Sweep Tavily search One search per question the intent asks, written for that question
Investigate Nemotron 3 Super, tool calling Fills the gaps with Tavily search, read and map_site: 10 calls at most and 2 per question, enforced in code
Screen Nemotron 3 Super Throws out pages about similarly named organisations, but never the company's own website
Draft Nemotron 3 Nano Writes each section from its sources only, and every claim must cite a source it was shown
Cross-examine Nemotron 3 Ultra, reasoning on Reads each claim next to the text of the pages it cites and rules supported, overstated (with a correction), unsupported, or wrong entity
Verdict Nemotron 3 Ultra Proceed, caution or stop, red flags and questions to ask, from surviving claims only

Getting the most out of Token Factory

  • Reasoning only where it pays. I switch it per call with chat_template_kwargs.enable_thinking: off for extraction, where it just adds latency, and on for the judge. The reasoning comes back in its own field, so it never ends up in my JSON.
  • I tested before I built. A small probe script checks tool calling, JSON mode and JSON schema on all three models. I didn't build on anything until I'd seen it work.
  • Models can't disappear on me. At startup Recon checks its model ids against /v1/models, and each size has a backup chain. My previous provider retired the model my app ran on, and I only found out when the live demo started filing empty reports. I wasn't going to let that happen twice.
  • Slow models get benched. Serverless endpoints queue under load, and Nano once took 12 seconds to return {"ok":true}. If a call runs past its time limit, that model sits out for two minutes and the call drops to the next one in the chain (Nano, then Lightning, then Super).
  • Every check shows its cost. Token Factory publishes per-token prices, so the bottom of every file shows exactly what that check cost, by model.

Across the eval, the money went where it should:

Model Calls Share of cost
Nemotron 3 Nano (drafting, extraction) 116 2%
Nemotron 3 Super (agent, screening) 113 12%
Nemotron 3 Ultra (judging, verdict) 116 86%

Nearly all of Ultra's share is reasoning spent checking claims. That's the part that matters, so that's where I want the money going.

Tavily is how the agent sees the web

  • Searches shaped by the decision. Each check starts with one search per question, written for that question. If you're taking on a client it looks for things like "unpaid invoices late payment freelancers". If you're weighing a job offer it looks for "recruitment scam fake job offer". Recent news uses Tavily's news topic, limited to the past year.
  • Three tools for the agent. Super can search, read a page in full (Tavily extract with a focus query and chunks_per_source: 3, so a long filing comes back as the parts that matter), or map_site (Tavily map at depth 1, to find the company's own about, legal and careers pages).
  • Full pages for the judge. When a page has been read in full, the drafter gets more than a snippet, and that full text is what Ultra reads when it rules on a claim that cites it.
  • Search finds names, not companies. 137 of the 837 pages in the eval were about someone else: CodeWeavers' CrossOver in a Crossover file, the Alan Turing Institute in a Turing file. Super screens them out before anything gets written.
  • Every credit counted. Every request sets include_usage. Across the eval that came to 177 searches, 16 full-page reads and 9 site maps, 195 credits in all, about 10 per check. The full-page reads reached primary sources like UK Companies House, SEC EDGAR filings and companies' own legal pages.

The confidence score is calculated, not asked for. Early on I let the model grade itself, and it said 85 on a file that was mostly about the wrong company. Now the score comes from what survived cross-examination:

$$\text{confidence} = 100 \times s \times (0.4 + 0.6\,c) \times (0.6 + 0.4\,d)$$

Here \( s \) is the share of claims that held (a corrected claim counts as half), \( c \) is the share of questions with at least one claim left standing, and \( d \) is how many independent sites back those claims, out of 8. They're multiplied rather than averaged, so one strong number can't cover for a weak one. A file where every claim holds but only one question in five got answered still scores low.

The rest. React and Vite on the front end, Node and Express on the back end, with every step streamed to the browser over server-sent events. No database. It runs on Render, with live checks capped at 15 a day and 3 per visitor so the public demo can't run up an unlimited bill. Past the cap you get a recorded live run and a note saying why.

Challenges I ran into

  • Super blew its budget. I told it to spread its tool calls across all the questions, and it spent every one of them chasing a single registry number. The limit is enforced in code now, not in the prompt.
  • Screening threw out the company itself. The first version discarded 43 of 44 pages, including the company's own website, because it listed the company's old name as a look-alike. Now the company's own names and the look-alikes are kept in separate lists, its own domain can never be thrown out, and if screening tries to discard most of a file, the result is ignored.
  • Latency spikes. Nano once took 12 seconds on a trivial call and was fine a minute later. That's why every model now has a time limit and a backup.
  • My error handling backfired. I'd built every stage to survive its own failure, so when I mistyped an API key on the host, Recon happily filed a CAUTION verdict made entirely of error messages. Now if a provider refuses the account (bad key, no balance, plan used up), the check stops, the visitor's quota is refunded, live checks pause for ten minutes, and the visitor gets the recorded run with a notice.
  • Judging the judge. A percentage of corrections doesn't tell you whether they're real or nitpicks. So I sat down and read 26 of them myself (more on that below).

Accomplishments that I'm proud of

I ran a 20-company eval across all five intents, against the live models. The drafting model wrote 431 claims, and every one of them cited a source it had been shown. Then Ultra ruled on them:

Ruling Claims Share
Held 247 57%
Corrected 151 35%
Struck 33 8%

So roughly two out of every five cited, fluent, believable claims would have reached the reader wrong or overstated without the second pass.

  • The corrections are real. I read 26 of them one by one, and 17 changed something you'd actually act on: Mercor's Series C put in 2023 instead of 2025, "300,000 customers" where the source says 200,000, a Deel client the source never mentions, one Reddit post written up as "Reddit users".
  • Every known-bad company got STOP: Byju's (payment defaults), Builder.ai (insolvency) and Wirecard (the 2020 fraud).
  • It's quick and cheap. A typical check takes 62 seconds and costs $0.06 in inference plus about 10 Tavily credits. The whole eval cost $1.20.

To be clear, these rulings are Ultra's, not a human audit, so they show what the pipeline catches, not ground truth. Every check, every struck claim and the judge's reason for it are in eval/RESULTS.md, and node scripts/eval.mjs --reuse regenerates the numbers from the saved runs.

What I learned

Grounding a model isn't the same as checking it. Making a model cite what it was shown stops it inventing sources, but it doesn't stop it misreading them, stretching them, or pinning them on the wrong company. What actually worked was a second pass by a bigger model whose only job is to find what's wrong, with the final decision made only from what survives.

I also learned where the money should go. Judging is 86% of the cost of a check, because judging is the part that matters. The small model is good enough for everything that comes before it.

What's next

  • Paid company-registry and court-record sources for the identity and legal questions, so a company with a thin web presence doesn't get a thin file.
  • Files you can share with a partner or a client.
  • Re-checks that tell you what's changed since the last time you looked.

Recon started in August as a company research prototype on a different model provider. Everything described here was built during the submission period, and CHANGES.md lists exactly what changed.

Built With

Share this project:

Updates

Submission history