M Made Scientific MADE OS · agent anatomy · SCOUT

SCOUT — prospect research, end to end

You give SCOUT a company. Ninety seconds later you have a BD brief that names their modality, their clinical stage, their manufacturing posture, who runs CMC, what they announced recently, and three specific questions to open a conversation with — sourced, cited, and costing about ten cents.

It works. It has produced 11 briefs and the most recent one was two weeks ago. That combination — a capable agent nobody is using — is the honest headline of this page.

Live-verified 2026-08-04 against scout_briefs and the deployed Edge Function source. Zero failed runs in its history.
Verified working Works, with a limit Believed working, isn't Not built
At a glance

The operational facts

JobOn-demand single-company BD research brief for Account Atlas.
Harness · archetypeedge-functions · research-agent. Correct choice: a human clicks and waits, and the whole run fits inside a request.
Triggeron_demand — the "Research this account" button in Account Atlas, or a direct POST. Also reachable via CHIEF as route=scout, mode=research.
HostEdge Function scout-research on Transfer (jrfcfayphcmaxsixxupu), 526 lines. Not on bc-made.
Modelclaude-sonnet-5, max_tokens: 4000, one synthesis call per run.
Observed cost$0.05 – $0.12 per brief. Mean across 11 runs: $0.099.
GuardrailsShared daily scrape budget (300/day, reserved via RPC) · 30-day scrape cache · 6 pages max per company · hard 422 when a company can't be resolved.
RED lineRead plus brief-write only. No Salesforce, pipeline, price or revenue writes.
OwnerJoe Sinclair. Last registry verification 2026-07-09.
The run

What actually happens between the click and the brief

Four phases. The design principle throughout is gather deterministically, synthesise once — every source is fetched by plain code, and the model is called exactly one time, over only what was actually retrieved. That is why the briefs are specific rather than plausible.

Phase A

Resolve the company, or refuse

Before spending anything, SCOUT establishes it knows who it is being asked about.
A1
Resolve to a canonical company
Match the request to a company_id and a domain on Transfer. Reuses the existing identity graph rather than a name string.
Free · SQL
A2
No domain → hard stop
Returns HTTP 422 with status: failed. It does not guess a domain and it does not proceed on a name alone.
Free
A3
Key preflight
Missing Firecrawl or Anthropic key → 503 with a named cause, not a generic failure.
Free
Worth copying: the 422 is the most important line in this function. An agent that will not run without a resolved identity cannot produce a brief about the wrong company — which is the failure that destroys trust in a research tool permanently. Refusing is cheap; a confidently wrong brief handed to a rep is not.
Phase B

Gather — five sources, all deterministic, all capped

No model involvement in this phase at all. Every source is best-effort: if one fails, the run continues with less.
B1
Directed page discovery
Brave, scoped site:domain, across six fixed topics: pipeline, technology platform, about, manufacturing, news, leadership.
Cheap · Brave
B2
Scrape, budgeted + cached
Firecrawl, max 6 pages. Reserves against a shared 300/day budget by RPC; 30-day cache means a repeat company is nearly free.
Metered
B3
Recent news
Brave with freshness=py, 6 results — funding, clinical, partnership, appointments.
Cheap
B4
SEC & investor press
Brave scoped to sec.gov, IR pages, 10-K / 8-K / S-1. Turns a public company into a much richer brief.
Cheap
B5
ClinicalTrials.gov
v2 API, sponsor query. Real NCT ids, phases and conditions — the highest-signal source for a therapeutic developer.
Free · API
B★

The budget reservation is the guardrail that matters

Verified in source
How it worksBefore any scrape, SCOUT calls firecrawl_reserve_scrape(p_max_per_day: 300). If the reservation fails, the scrape does not happen — and the budget is shared with account-scrape, so the two agents cannot collectively overrun it.

Why shared, not per-agentTwo agents with 300/day each is 600/day. A shared pool means the ceiling is the ceiling regardless of how many callers appear later — which is the only version that survives a third agent being added.

Daily scrape ceiling300
Cache TTL30 days
Pages per company6 max

Behaviour at the capReturns status: 'failed' with 'no sources gathered (budget or fetch failure)' — HTTP 200, explicitly labelled. It degrades loudly.

B-LIM

Every source is Brave — including the ones that sound authoritative

Honest limit

What's actually happeningThe "SEC / press" step is not an EDGAR API call. It is a Brave search scoped to site:sec.gov plus press-release phrasing, returning 4 snippets. Same for news and contact background. Only ClinicalTrials.gov is a real structured API.

Why that's a defensible choiceSnippets are enough for a conversation-opening brief and cost almost nothing. Parsing actual filings is a different, much larger agent.

But be precise about it internally. A brief section titled "SEC" implies filings were read. They were not — search result descriptions were. For BD talking points that's fine. For anything financial it is not, and nobody should build on it thinking otherwise.

Consequence in the outputFinancial fields come back honest but thin — one real brief reads "reported Q1 2026 financial results (details not in source); no specific runway figures stated." The model correctly declines to invent. That is the system working.

Phase C

Synthesise — one model call, structured output

A single claude-sonnet-5 call at max_tokens: 4000 over everything gathered, returning a fixed JSON shape.
C1
Structured extraction
Segment, modality, allo/auto, clinical stage, manufacturing posture, CMC leadership, facilities, funding, pipeline, platform, partnerships.
~$0.10
C2
Trials, constrained
NCT ids only if present in the ClinicalTrials.gov section — the prompt forbids inventing them.
C3
The BD angle
Where Made specifically fits, argued from the gathered evidence. This is the part a rep actually reads.
C4
Three talking points
Specific questions quoting the prospect's own stated position back to them.
C5
Confidence + failure path
Self-declared confidence. Synthesis failure → status: 'partial', never a silent empty brief.
C★

What good output actually looks like

Real brief, 2026-07-08$0.0956
Cabaletta Bio — the BD angle it produced "Cabaletta is running its lead asset rese-cel simultaneously across 5 autoimmune indications while pursuing a 2027 BLA in myositis — a period of intense manufacturing complexity requiring parallel supply from existing CDMOs and a newly contracted Cellares automated platform. Made can position as a redundancy/backup CDMO partner or analytical/comparability specialist to de-risk supply chain during this multi-CDMO transition."

And a talking point from the same brief"You've stated you're maintaining existing CDMO partners for the myositis registrational trial while transitioning to Cellares for commercial scale — how are you managing comparability/analytical bridging between the two manufacturing sources ahead of your 2027 BLA?"

That is a question only someone who read their press releases could ask. It names the specific tension — two manufacturing sources, one BLA — and it is answerable. It cost ten cents and about ninety seconds, and it is better preparation than most reps get for a first call.

Why it landsBecause the model only saw six pages of Cabaletta's own site plus their own press releases. Nothing generic could enter, because nothing generic was retrieved.

C-LIM

One call, no validator — the deliberate asymmetry

Differs from house pattern

The gapGuardrail G-2 says generation gets a hold-out validator on a different model. SSO AGENT has one and it can reject. SCOUT does not. Its output goes straight to a human.

Why that is arguably correct hereA validator exists to stop bad output reaching the outside world. An SSO message gets sent to a prospect — it needs a gate. A SCOUT brief is read by a rep who is entirely capable of noticing it is wrong, and who is the validator.

Where it would stop being correct: the moment a brief feeds another agent instead of a person. If huntsman or DOSSIER ever consumes SCOUT output programmatically, the human validator disappears and G-2 has to be honoured.

Current mitigationSelf-declared confidence on every brief, plus a full source list — so a reader can check.

Phase D

Persist, render, merge

The brief is stored, a branded PDF is fired off without blocking, and the structured fields are merged back onto the company record.
D1
Persist
Row into scout_briefs: markdown, JSON, sources, model, cost, resolved name and domain.
Free
D2
Fire-and-forget render
POSTs scout-render for a branded PDF. Deliberately not awaited — the user gets the brief immediately.
Free
D3
Merge to company
Writes the structured intel back with scrape_source: scout:*, so the next agent inherits it.
Free
D4
Surfaced in Atlas
Brief, talking points, PDF download with auto-refresh, and brief history on the account page.
Free
The fire-and-forget render is verified working, which was worth checking. Fire-and-forget is a classic place for silent failure. Of 11 briefs, 10 show render_status: done. The single pending is the very first row — a UX smoke test from 2026-07-08 that predates the render function being wired. So: not a bug, and the pattern holds.
Evidence

Its entire operating history, all eleven runs

Small enough to print in full — which is itself the finding.

Briefs produced
11
all time
Status ok
11
zero failures
Renders done
10
+1 pre-wiring
Mean cost
$0.099
range $0.05–$0.12
Total spend, ever
$1.09
across all runs
Last used
14d
2026-07-21
DateCompanyStatusRenderCostBy
2026-07-21Triumvira Immunologicsokdone$0.0977app
2026-07-21Tr1Xokdone$0.0921app
2026-07-13Mirror Biologicsokdone$0.0507app
2026-07-13Obsidian Therapeuticsokdone$0.1115app
2026-07-13Obsidian Therapeuticsokdone$0.1086app
2026-07-10Outpace Biookdone$0.1228app
2026-07-09Alloplex Biotherapeuticsokdone$0.1151app
2026-07-09Verismo Therapeuticsokdone$0.0843app
2026-07-09Obsidian Therapeuticsokdone$0.1043app
2026-07-08Cabaletta Biookdone$0.1050scout
2026-07-08Cabaletta Biookpending$0.0956scout
Honest limits

Three real problems, and only one of them is the code's fault

L-01

Adoption, not capability, is the binding constraint ★

The real finding
The numbersEleven briefs in total. Nine distinct companies. Two weeks since the last one. Total lifetime spend $1.09. Zero failures.

Read it plainlyThis is not a broken agent. It is a working agent that nobody reaches for. The briefs are good — genuinely good, as the Cabaletta example shows — and produced at a cost that makes the question "should we run SCOUT on this account" absurd to even ask. Ten cents.

Why that's likely happeningThe most plausible explanation is discoverability rather than distrust: the button lives on the Account Atlas page, so you have to already be there to know it exists. Nothing prompts a rep before a call to say "there is a brief available for this account, for ten cents."

The cheapest experiment: pre-generate briefs for the accounts with meetings on the calendar this week and put them where the rep already looks. If they get read, the constraint was distribution. If they don't, it's trust — and that is a different, more interesting problem.

Not a build taskSCOUT needs no engineering. It needs a habit.

L-02

No brief-level dedup — Obsidian was researched three times in five days

Minor, real

What happenedObsidian Therapeutics has three briefs: 2026-07-09, and twice on 2026-07-13 (six minutes apart). Cabaletta has two, 49 minutes apart. Five of eleven briefs are repeats.

Why the cache didn't helpThe 30-day cache is on the scrape, not the brief. So a repeat run correctly avoids re-scraping, then pays for a fresh $0.10 synthesis anyway. The expensive half is the unguarded half.

Duplicate briefs5 of 11
Wasted synthesis~$0.35
Trivial in absolute terms — thirty-five cents. It matters only because it will scale linearly with adoption, and because two briefs for one company six minutes apart suggests someone clicked twice not knowing the first had worked. That's a UX signal as much as a cost one.

FixCheck for a brief newer than N days before synthesising; offer the existing one with a "refresh anyway" option.

L-03

The contact half of the brief is nearly always empty

Structural

What's thereThe output schema has a full contact block — name, role, seniority, decision relevance, personalisation hooks. There is a dedicated Brave step (contactSnippets) to populate it.

What actually comes backIn the verified Cabaletta brief: contact_snippets: 0 and every contact field empty. The company half is rich; the person half is blank.

WhySCOUT is invoked from an account page with a company, not a person. With no name passed, contactSnippets returns immediately by design. The capability is built and simply not being fed.

This is a free upgrade sitting unclaimed. The code path exists and works. Invoking SCOUT with the contact you are actually meeting — rather than just their employer — turns a company brief into a person brief at no additional engineering cost.

FixPass a contact from the meeting or the contact record. Nothing to build.

L-04

No freshness signal on a stored brief

Future

The issueA brief is a snapshot. Cabaletta's cites a Cellares agreement from April 2026 and EULAR data from June. Read in December, several of those "recent news" lines are stale — and nothing in the UI says so.

Why future and not urgentEvery brief carries created_at, and the history list is visible on the account page. The information needed is present; it just isn't emphasised.

Cheap version: show age prominently, and past ~90 days label it "may be stale — refresh." Same discipline as the verification date in this page's own footer.

WhenWhenever adoption rises enough that old briefs get read at all.

Verdict

The best-behaved agent in the fleet, and the least used

SCOUT does everything the method page asks for. It refuses rather than guesses when it cannot resolve a company. It gathers deterministically and synthesises once. It has a shared budget ceiling rather than a private one. It degrades loudly, labelling partial results as partial. It reuses the identity graph instead of building a parallel one. Eleven runs, zero failures, ten cents each.

None of its three real limitations is an engineering problem. Two of them — the empty contact block and the missing dedup — are resolved by calling it differently. The third is that almost nobody calls it at all.

Which makes this page's recommendation a distribution recommendation, not a build one: put a brief in front of a rep before a meeting they already have booked, and see whether it gets read. That test costs about a dollar.