Investor-grade financial-model review with sub-agent dispatch
Problem solved: Founders pitch with models that fail investor scrutiny; a 46-criteria automated review replaces a paid CFO-advisor pass and emits a stage-appropriate diagnostic report plus an interactive HTML explorer.
What
The most engineered skill in source-16: a production-grade finance review pipeline that ingests a founder's model (Excel / CSV / Sheets / PDF), extracts it into a canonical inputs.json, validates it with anti-hallucination cross-checks, scores 46 criteria across 7 categories with profile-based auto-gating by stage and geography, computes 11 unit-economics metrics against benchmarks, runs multi-scenario runway stress-tests, and composes a report.md plus an interactive HTML report and explorer. It uses a two-context sub-agent dispatch model — Context A for analytical work (extraction review, checklist, unit economics, runway scenarios) and Context B for coaching commentary — and runs in both a CLI agent and a restricted-tool sandbox. It imports from sibling skills (market-sizing, deck-review) and exports structured results to downstream skills (IC-simulation, fundraise-readiness, diligence-readiness, metrics-benchmarker), all referenced by function.
Why it matters
The reusable IP here is not the finance content — it is the anti-hallucination architecture for any multi-step agent pipeline that must not invent numbers:
- A hard STOP GATE: after extraction,
validate_inputs.py --fixthenvalidate_extraction.pycross-reference the raw model against the parsed inputs; the pipeline does not proceed untilvalid == true && has_critical_warnings == false. - STALE_ARTIFACT run_id matching: every producer artifact is stamped with a
RUN_ID;compose_report.py --strictrefuses to assemble a report if run_ids don't match across artifacts — this catches the classic "report built from a mix of old and new partial runs" bug. - Context-pressure mitigations: the coaching sub-agent reads a slim
coaching_payloadextracted fromreport.json, not the fullreport.md, to avoid 80–130K of context accumulation. - Tolerant JSON extraction: sub-agent replies are parsed by stripping code fences then brace-aware
raw_decode, with a re-prompt fallback — robust to LLMs wrapping JSON in prose.
The business outcome: it replaces a paid CFO-advisor pass with a stage-appropriate readiness review, and the 46-item checklist gives the founder a concrete "fix this before pitching" list with evidence citing specific values from their own model.
End-to-end
- Path setup (Step 0). Resolve the plugin root and an artifacts root (mounted workspace or
./artifacts); setREVIEW_DIRandRUN_ID=$(date -u +%Y%m%dT%H%M%SZ); clean prior artifacts. Every Bash call runs in a fresh shell — prefix every call with the path block (variables do not persist). - Founder context (Step 1). Read or create company context (name, stage [pre-seed / seed / series-a / series-b / later], sector, geography); batch-ask for current cash balance + date and monthly burn — the #1 cause of incomplete runway analysis.
- Extract model (Step 2). Run
extract_model.pyon the model in the main thread to producemodel_data.json(40–60 KB). Checkperiodicity_summary; divide flow metrics by 3 (quarterly) or 12 (annual). Do not convert stock metrics (cash balance, headcount, ARR). - Inputs-review dispatch (Step 3, Context A). Task-dispatch a sub-agent to read
model_data.jsonplus the input-schema / extraction-pitfalls / data-sufficiency references and return a correctedinputs.jsonwith achanges[]list. Apply the ARPU sanity check (ifarpu_monthly> total MRR it is aggregate revenue, not per-customer — divide by customer count), scale-denomination, payroll-aggregation, and collections-vs-revenue checks. - Validate — STOP GATE (Step 3.5).
validate_inputs.py --fix, thenvalidate_extraction.pyto cross-reference. Do not proceed untilvalid == true && has_critical_warnings == false. Generatereview.htmlfor the founder to inspect and submit corrections; apply viaapply_corrections.py. - Parallel analysis dispatch (Steps 4–6). In a single assistant message, Task-dispatch the CHECKLIST + UNIT_ECONOMICS + RUNWAY_SCENARIOS sub-agents (parallel dispatch is mandatory — all Task calls in one response). CHECKLIST scores the 46 items (STRUCT_01–09, UNIT_10–19, CASH_20–32, METRIC_33–35, SCENARIO_36–38, BRIDGE_37–38, SECTOR_39–44, OVERALL_45–46) with mandatory evidence; UNIT_ECONOMICS computes 11 metrics; RUNWAY_SCENARIOS runs the stress-test. Each returns JSON piped through its producer script (
checklist.py,unit_economics.py,runway.py). - Compose + verify (Step 7).
compose_report.py --strictwrites deterministicreport.jsonandreport.md; the STALE_ARTIFACT high-severity warning blocks under--strictif run_ids don't match. Verification Gate 1:verify_review.py --gate 1must exit 0. Optionally producereport.htmlandexplore.html. - Coaching + final gate (Step 8c–8d, Context B). Task-dispatch the coaching sub-agent with the inlined
coaching_payloadfromreport.json(not the full report.md); it does a Grep idempotency check, composes commentary from failed/warned items, makes a single Edit via a per-run UUID insertion marker, Grep-verifies all four producer artifacts' run_ids match, and returns a success payload. Clean.staging. Verification Gate 2:verify_review.py --pretty— if exit 0, present thereport.md/report.html/explore.htmlfile paths (do not inline markdown). Scoring:score_pct = (pass + 0.5*warn) / (total - not_applicable) * 100; bands strong ≥ 85%, solid ≥ 70%, needs_work ≥ 50%, major_revision < 50%.
Prompts
Context A inputs-review dispatch prompt, verbatim:
CONTEXT: INPUTS_REVIEW
REVIEW_DIR: <absolute path to REVIEW_DIR>
RUN_ID: <RUN_ID>
You are the financial-model-review agent dispatched in Context A (INPUTS_REVIEW).
Read model_data.json at <REVIEW_DIR>/model_data.json (the full extraction output).
Also read:
- ${CLAUDE_PLUGIN_ROOT}/skills/financial-model-review/references/schema-inputs.md
- ${CLAUDE_PLUGIN_ROOT}/skills/financial-model-review/references/extraction-pitfalls.md
- ${CLAUDE_PLUGIN_ROOT}/skills/financial-model-review/references/data-sufficiency.md
Construct a complete, valid inputs.json from the extracted data. Apply all
extraction pitfall checks (scale denomination, ARPU sanity, periodicity
conversion, company name sourcing, payroll aggregation, collections vs revenue).
Tolerant JSON extraction protocol, verbatim:
Tolerant JSON extraction protocol:
1. If the message is wrapped in a ```json... ``` (or plain ```... ```) fence, strip the fence first.
2. Try to parse the stripped text directly as JSON.
3. If that fails, walk through the text looking for the first `{` character and try
`json.JSONDecoder().raw_decode(text[i:])` — brace-aware, handles nested objects
correctly (unlike regex, which truncates on the first `}`).
4. If extraction fails entirely, re-prompt: "Your previous reply could not be parsed
as JSON. Return ONLY the JSON object — no markdown fences, no prose preamble."
Scoring formula, verbatim:
score_pct = (pass + 0.5 * warn) / (total - not_applicable) * 100
Overall: "strong" (>=85%), "solid" (>=70%), "needs_work" (>=50%), "major_revision" (<50%)
Gotchas
- Fresh shell per Bash call. Variables don't persist between calls — every Bash invocation must re-emit the path block or the pipeline silently writes to the wrong directory.
- Never run sub-agents in a worktree isolation mode. Files written in a worktree won't appear in
$REVIEW_DIR, so downstream steps see nothing and the report composes empty. - Parallel dispatch is mandatory for the analysis step. The three analysis sub-agents must be Task-dispatched in one assistant response; sequential dispatch breaks the pipeline's expectations and inflates context.
- The coaching sub-agent must read the slim payload, not the full report. Reading the full
report.mdinto Context B accumulates 80–130K of context and degrades the coaching output. - STALE_ARTIFACT is a feature, not noise. If run_ids don't match across artifacts under
--strict, stop and re-run from extraction — do not override; it means the report would mix runs.
Tools
- A CLI agent or a restricted-tool sandbox; Python 3.10+ with
uv;openpyxlfor Excel parsing;reportlabfor PDF output - Install:
claude plugin marketplace add lool-ventures/founder-skills && claude plugin install founder-skills@lool-founder-skills - Scripts shipped:
extract_model.py,validate_extraction.py,validate_inputs.py,checklist.py,unit_economics.py,runway.py,compose_report.py,apply_corrections.py,verify_review.py,visualize.py,explore.py,review_inputs.py - References shipped: 46-criteria checklist, input schema, artifact schemas, data-sufficiency, extraction-pitfalls, plus shared stage-expectations / benchmarks / revenue-model-types / common-mistakes; a stage/geography profile gates which criteria apply
- Uses the agent's
Tasktool for sub-agent dispatch and a batched-question tool for founder context