DEV Community

Zohar Babin
Zohar Babin

Posted on

Building a 13-Agent AI System for M&A Due Diligence — Architecture Deep Dive

The Problem Nobody Was Solving

As a corp dev lead, I spent weeks doing the same thing after every deal: assembling the cross-domain picture from siloed advisor reports.

Legal would flag a termination clause. Finance would flag revenue concentration. Same entity. Nobody connected the dots.

This happens because due diligence is split into parallel workstreams — legal, financial, commercial, tax, regulatory — each run by separate teams with separate deliverables. The cross-referencing happens in someone's head, over coffee, two days before the IC memo is due.

The numbers back this up:

  • 31% of M&A failures trace back to DD shortcomings (HBR, McKinsey, KPMG research)
  • DD timelines keep compressing — six weeks becomes three, same scope
  • Corp dev teams screen 200-1,000+ companies/year but close 1-3%

I built Due Diligence Agents to fix this.

What It Does

13 AI agents analyze every document in an M&A data room across 9 specialist domains — Legal, Finance, Commercial, ProductTech, Cybersecurity, HR, Tax, Regulatory, and ESG — then cross-reference findings automatically and trace each one to the exact page, section, and quote.

pip install dd-agents
dd-agents auto-config "Buyer" "Target" --data-room ./your_data_room
dd-agents run deal-config.json
Enter fullscreen mode Exit fullscreen mode

Output: an interactive HTML report, a 14-sheet Excel workbook, and per-subject JSON findings. See a sample report from synthetic data.

The Architecture

The system has four layers:

Layer 1: 38-Step Async Pipeline

The orchestrator (engine.py) is a state machine with 38 async steps grouped into phases:

  1. Setup (steps 1-5): Load config, validate data room, resolve entities
  2. Discovery (steps 6-13): Extract documents, build inventory, classify files, compute precedence
  3. Analysis (steps 14-17): Build specialist prompts, route documents, spawn agents in parallel, check coverage
  4. Cross-Domain (steps 18-20): Symbolic trigger evaluation, targeted respawn, merge
  5. Quality (steps 21-26): Judge review, merge findings, validate, deduplicate
  6. Reporting (steps 27-38): Generate HTML, Excel, JSON, knowledge base

Every step supports checkpoint/resume. If the pipeline crashes at step 23, it restarts from step 23 — not from scratch. Steps are typed, and the state object serializes cleanly to JSON.

Layer 2: 13 Agents

9 specialists + 4 meta-agents, each spawned via Anthropic's claude-agent-sdk:

Specialists: Legal, Finance, Commercial, ProductTech, Cybersecurity, HR, Tax, Regulatory, ESG. Each gets domain-specific prompts, the relevant documents, and a set of tools (file read, search, finding write).

Meta-agents:

  • Judge: Reviews specialist findings for quality, consistency, and missed coverage
  • Executive Synthesis: Produces the deal-level summary with go/no-go signals
  • Red Flag Scanner: Pattern-matches across all findings for deal-killers
  • Acquirer Intelligence: Tailors findings to the buyer's strategic context

Specialists run in parallel (batched by resource constraints). Meta-agents run sequentially after all specialists complete.

Layer 3: Neurosymbolic Cross-Domain Analysis

This is the part that solved my original problem.

After specialists produce their findings (pass 1), a deterministic rule engine scans them for cross-domain dependencies. No LLM calls — just Python pattern matching.

# Example: Finance finds revenue recognition issue
# → Rule fires → Legal agent re-examines specific contracts
# for enforceability, clawback clauses, delivery milestones
Enter fullscreen mode Exit fullscreen mode

Seven built-in trigger rules cover the most common M&A cross-domain dependencies:

Source → Target When It Fires
Finance → Legal Revenue recognition finding needs contract enforceability check
Legal → Finance Change-of-control clause needs financial exposure quantification
Legal → Finance Termination-for-convenience needs revenue-at-risk calculation
Legal → ProductTech IP ownership dispute needs technical dependency assessment
ProductTech → Legal Data privacy finding needs DPA/GDPR compliance review
Commercial → Finance SLA risk with >10% service credits needs financial quantification
Finance → Commercial Pricing discrepancy needs commercial rate card validation

When a rule fires, it creates a CrossDomainTrigger with the specific contracts to re-examine and instructions for the target agent. The target agent runs a targeted pass-2 review — only on the cited contracts, not the full data room. This keeps costs bounded.

Budget-capped, priority-ordered. If no triggers fire, zero additional cost.

The design is inspired by the FAOS Platform — asymmetric coupling where symbolic rules constrain the LLM's scope while the LLM provides judgment. Symbolic decides when intelligence is needed; the LLM provides what to do about it.

Layer 4: 5 Blocking Quality Gates

Every finding goes through validation before it reaches the report:

  1. Coverage gate: Did the agent analyze every assigned document?
  2. Schema validation: Does every finding have the required fields (severity, citations, category)?
  3. Citation verification: Can we trace the finding back to a specific page and quote?
  4. Semantic dedup: Are two agents saying the same thing about the same document? (rapidfuzz token_sort_ratio ≥ 80)
  5. Numerical audit: Do financial figures in findings match what's in the source documents?

Fail-closed. If validation fails, the pipeline stops — it doesn't silently produce bad output.

The Chat Mode (My Favorite Feature)

After the pipeline runs, you can interrogate the results:

dd-agents chat --report _dd/forensic-dd/runs/latest
Enter fullscreen mode Exit fullscreen mode

The chat agent has 14 MCP tools: citation verification against source PDFs, cross-contract search, entity resolution, and sandboxed document generation. Ask "build me a board summary of all P0 findings with revenue impact" and it writes a Python script, executes it in a sandbox, and hands you the .xlsx file.

15 Things I Learned Building This

These lessons apply to any system doing cross-document analysis at scale — not just M&A.

1. Extraction is harder than analysis. By a lot.

Everyone focuses on the LLM prompts. But 80% of the real engineering is getting clean text out of messy documents. Our extraction pipeline has 4 tiers: pymupdf → pdftotext → OCR (Tesseract → GLM-OCR) → Claude vision as last resort. Each tier has 6 quality gates (min chars, printable ratio, density, readability, watermark detection, corruption check). Confidence scales with method quality — pymupdf gets 0.9 base, OCR gets 0.65.

2. Entity resolution is your invisible foundation

"IBM", "International Business Machines", and "Red Hat" — are these the same entity? We use a 6-stage cascade: exact match → normalized (strip legal suffixes) → alias expansion → fuzzy match (rapidfuzz) → TF-IDF cosine similarity → learned matches from prior runs. Names ≤5 characters are blocked from fuzzy matching — without this, "Inc." matches random entities.

3. Don't dump everything into one context. Map-merge-resolve.

A 200-page master agreement might have the deal-killer on page 147. You can't skip large files. But dumping them into one context drops accuracy from 95% to 74% (Addleshaw Goddard, 510 contracts). Instead: chunk at page boundaries (150K chars, 15% overlap), analyze each chunk independently, merge with priority logic (YES beats NO, specific beats generic), and only invoke LLM arbitration when chunks disagree. The 21-point accuracy gain is entirely engineering — no model change.

4. Hallucination is an engineering problem, not a model problem

No single defense works. We use 5 layers: (1) Pydantic schema validation on every response, (2) mandatory citation with file_path/page/exact_quote verified against source, (3) explicit "NOT_FOUND" escape valve — without this, models fabricate clauses rather than admit ignorance, (4) adversarial Judge review with accusatory framing ("this finding appears fabricated — prove it with a direct quote"), (5) 6-layer deterministic numerical audit.

Layer 3 changed everything. When you tell the model "if you can't find this clause, say NOT_FOUND," hallucination drops dramatically.

5. Know when to stop using LLMs

We had an LLM agent doing validation and report synthesis. We replaced it with deterministic Python. Quality went up, cost went down. The rule: use LLMs for analysis and synthesis; use Python for validation, dedup, and audit. If you can write the logic as deterministic code, do it.

6. Self-verification works — but only with accusatory framing

After agents produce findings, a follow-up pass challenges them on high-severity claims. Polite prompts ("please review your finding") have near-zero effect — models confirm their own output. Accusatory prompts ("this finding appears fabricated," "the cited clause doesn't exist") force re-examination and produce a 9.2% accuracy improvement.

7. Cross-agent dedup is different than you think

When 4 agents analyze the same document, they find the same issue but describe it differently. Three rules: (1) never dedup within the same agent — two similar findings from Legal are intentionally distinct, (2) only dedup across agents on the same document — similar findings on different documents are different findings, (3) keep contributing agent metadata so you know which domains flagged it.

8. Context window engineering is a first-class discipline

It's not just about fitting data in — it's about where things go. Critical instructions go at the start (highest recall zone). Document content goes in the middle (lowest recall — ~40% worse). Constraints and format rules go at the end (second-highest recall). We budget 40% of the context window for tool calls and reasoning.

9. Quality gates must be blocking, not advisory

If validation just logs a warning, nobody reads it. If it halts the pipeline, quality is non-negotiable. Same for agent guardrails: hard turn limits (soft at 200, force-kill at 3x), path guards (agents can only write under _dd/), bash guards (24 blocked patterns — no rm -rf, no sudo, no pipe-to-shell). Better to produce nothing than unreliable output.

10. Every claim must be traceable to source

Citation verification uses 4 scopes: exact page match → adjacent pages ±1 → full document fuzzy match (80%+) → cross-file search. That last one matters — if the quote isn't in the cited file, we search all files for that entity. Auto-corrects file misattribution.

11. Most of what AI finds is noise

Run 9 agents across hundreds of documents and you'll get thousands of findings. We use a 3-stage classification: noise filter (15 patterns for extraction artifacts), data quality filter (14 patterns for "data unavailable" gaps), then material findings. Plus 5 severity recalibration rules — e.g., a change-of-control clause that only applies to competitors gets downgraded from P0 to P3 automatically.

12. Same clause, different deal, different severity

An anti-assignment clause is P0 in an asset purchase (blocks contract transfer) but P3 in a stock purchase (entity doesn't change). Deal-type context must flow through the entire pipeline: prompt-time rules, post-hoc deterministic adjustments, and executive judgment overrides — with full audit trail.

13. Every API call is a deal cost

Three model profiles: economy (Haiku for extraction), standard (Sonnet for analysis), premium (Opus for synthesis). Per-agent cost tracking. Hard budget limits that halt the pipeline. Right model for right task.

14. Pydantic v2 everywhere

137+ models with model_json_schema() for structured outputs. Strict mypy across 199 source files. The type system catches real bugs — a finding with evidence instead of citations gets blocked by the schema guard hook before it's written to disk.

15. Make every run smarter than the last

Inspired by Karpathy's "LLM Wiki" pattern: a persistent knowledge base compounds across runs. Finding lineage via SHA-256 fingerprinting tracks findings even when wording changes. A NetworkX knowledge graph with 11 typed edge types captures entity relationships, contradictions, and clause interactions. Run 2 knows what Run 1 found — and catches what changed.

Try It

pip install dd-agents
dd-agents auto-config "Buyer" "Target" --data-room ./your_data_room
dd-agents run deal-config.json --dry-run  # Preview without API calls
Enter fullscreen mode Exit fullscreen mode

Sample report (synthetic data, no install needed)

GitHub — Apache 2.0, 3,714 tests, strict mypy.

Built on Anthropic's Claude Agent SDK. Looking for feedback — especially from anyone who's dealt with data room analysis and can tell me whether the report structure maps to how DD findings are actually consumed.

Top comments (0)