The problem
ICP-fit research is the bottleneck behind every meaningful outbound conversation — but it’s also the easiest task to fake with surface signals. Clay tables score companies by populating columns; what they can’t do is reason about why a “strong fit by signals” might still be wrong (a sales-tech vendor matches every ICP keyword but is actually a competitor, not a buyer). The naive answer is “use Claude in a Clay column” — but that gives you scoring without self-correction, evidence without auditable reasoning, and binary disqualifiers that miss the cases where the rule is right AND the exception is right too.
What it does
Input: one company domain (e.g. vercel.com).
Output: a decision-ready markdown brief with an ICP fit score, evidence quotes, a recommendation (pursue / nurture / pass / research-more), a pitch angle grounded in the evidence, and — when applicable — a transparent Disqualifier Override section explaining why the agent ignored a rule and chose to recommend pursue anyway.
The architecture is a LangGraph state machine: research nodes (scraper with inline jobs extraction, web_enricher with Anthropic’s native web_search) feed extractor nodes (icp_extractor, gtm_maturity), which feed a critic that scores the brief and — when gaps exist — emits structured Gap objects naming which specific node should re-fire to close them. The orchestrator selectively re-dispatches via LangGraph’s Send API. Self-correcting research, not one-shot enrichment.
Architecture
Blue nodes call Claude Sonnet 4.6 for reasoning and tool use. Orange nodes call Claude Haiku 4.5 for structured extraction. The two subgraphs (Research and Extractors) run their nodes in parallel within a single super-step — that’s the design choice that lets the critic loop fire exactly once per iteration rather than chasing race conditions.
Architectural decisions worth defending
1. Code-enforced disqualifier policy, not prompt-enforced. A naive ICP-fit agent treats disqualifiers as binary (agency → score 1, Series F → score 1). Real GTM judgment is more nuanced. This agent has two paths when a disqualifier hits: honor it (default — code clamps fit_score ≤ 1) or override it (the agent must populate override_reasoning with a specific, dated, source-attributable signal — and the brief surfaces the override in a dedicated section). The clamp lives in code, so the LLM cannot accidentally skip the rule. Override is opt-in via an explicit field, not opt-out via convincing prose.
2. LangGraph for the loop, Anthropic SDK direct for the leaf nodes. LangChain wrappers add abstraction debt without earning their weight at the leaf-node level. The framework only carries the state machine and the critic loop; node bodies call the SDK directly with forced tool use for structured output, native web_search for the research node. No LangChain wrappers, no provider lock-in.
3. The critic emits structured Gap.target_node, not just “this is incomplete.” When the critic finds gaps, it names which specific node should re-fire to close them — funding-stage gap → re-fire web_enricher with a hint; pricing page missing → re-fire scraper with /pricing. The orchestrator dispatches selectively. That’s the difference between self-correcting research and one-shot enrichment.
See it work
Run the agent on vercel.com (Series F, $9.3B valuation — well past a typical Series A-C target):
ICP fit: 4.0/5 (Series F · B2B SaaS — developer platform / AI cloud infrastructure) Recommendation: Pursue — confirmed GTM Engineer JD + new COO signals live buying window.
Disqualifier override: Series F stage hit the
series-d-plus-legacyrule, but COO Grosser published a Nov-2025 thesis on the GTM Engineer role she’s hiring. Internal Vercel Agent deployment confirms agentic GTM posture. These are concrete, dated signals that Vercel is actively building — not maintaining — GTM infrastructure.
A Clay table can score 4.0. It cannot tell you why it overrode the rule. That gap is what this tool demonstrates.
Four full sample briefs in outputs/samples/ on GitHub — a clean buyer-fit (Retool, with Head of RevOps Jonathan Krangel cited by name), two peer-vendor edge cases (Clay, Apollo), and one disqualifier (Deloitte).
Part of the pre-outbound stack
This is the first repo in a three-stage agentic pre-outbound system. Each repo runs standalone; together they compose end-to-end.
| Repo | Answers | Output contract |
|---|---|---|
| Account Research Agent (this tool) | Who fits | JSON via python run.py <domain> --json |
| Signal Monitor | When to act | JSON via python run.py --only <domain> --json |
| Meeting Prep Agent | What to say | Markdown + JSON sidecar via python run.py <input.yaml> |
Downstream, Outbound Campaign Crew (CrewAI) consumes this tool’s summary.json to write claim-traceable sequences — and refuses, in code, to write outbound to any account this agent disqualified.
And the discipline layer above all of them:
| Repo | Role |
|---|---|
| eval-watch | How do you know your agents — all of them — are right? Monthly GH Actions cron, regression gate vs anchored baseline, STATUS.md auto-committed to the repo. |
Three MIT repos. Three architecturally-coherent state machines. One thesis: code-enforced rules over prompt-asked-nicely rules, critic-driven self-correction, modular composition through stable CLI contracts. Same pattern shows up seven times across the portfolio: disqualifier clamp (here) → length compliance (sister project, the blog autopilot) → three clamps in Signal Monitor → concurrency clamp in SM runtime → four clamps + sequential composition in MPA → eval discipline enforced in CI (eval-watch) → Flow gates + task guardrails in CrewAI (Outbound Campaign Crew).
Use it
The agent is MIT licensed. Bring your own Anthropic and Firecrawl API keys. Configure the ICP for your own buyer profile in config.yaml (the policy and rule structures are domain-agnostic — they map cleanly to any consultative B2B context).
git clone https://github.com/aehirota/account-research-agent
cd account-research-agent
pip install -r requirements.txt
python run.py acme.com
Architecture deep-dive in docs/architecture.md. Operational docs in CLAUDE.md. Known limitations in KNOWN_ISSUES.md.