IC Simulation Skill
SkillDev toolsSimulates a realistic VC Investment Committee with three partner archetypes debating a startup's merits, concerns, and deal terms, scored across 28 dimensions. Run the scored simulation rather than improvising what partners would say. Also covers plain-language questions with no deck attached — 'would a VC fund us?', 'what would an investor say?', 'are we fundable?' — which run the scored simulation instead of a guess at what partners think.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the IC Simulation Skill skill
What this skill tells your AI
The instructions your AI receives, as published by lool-ventures/founder-skills in founder-skills/skills/ic-sim/SKILL.md and read by ahel’s review.
Help startup founders prepare for the conversation that happens behind closed doors — the one where VC partners debate whether to invest. Produce a realistic IC simulation with three distinct partner perspectives, scored across 28 dimensions, with specific coaching on what to prepare. The tone is founder-first: a coaching tool for preparation, not a judgment.
Skill Metadata
- Author: lool-ventures
- Version: managed in
founder-skills/.claude-plugin/plugin.json - Compatibility: Python 3.10+ and
uvfor script execution. - Imports (recommended):
market-sizing:sizing.json— fund alignment and market validationdeck-review:checklist.json— deck quality assessment
- Exports:
report.json→fundraise-readiness,dd-readiness
Skill Execution Model (READ FIRST)
See
founder-skills/references/skill-execution-model.mdfor the full inline-skill execution model (3 dispatch contexts, Mitigation 1+2, producer contract, Cowork quirks, per-symptom triage).
This skill runs inline in the main thread, not as a sub-agent — see the reference above ("Why Inline (Not Forked Sub-Agent)") for the rationale. Sub-agents are deliberately shell-free, so orchestration (producer scripts, artifact persistence) stays in the main thread.
Two dispatch contexts for the sub-agent:
- Context A — Per-step analytical dispatch (Mitigation 1): Steps 5, 6, 6b, and 8 dispatch the ic-sim agent via the
Tasktool. The novel element here is parallel dispatch: Step 6 (PARTNER_ANALYSIS) and Step 6b (PARTNER_REBUTTAL) each dispatch the agent three times simultaneously — one per partner archetype — in a single assistant turn. Step 5 (DETECT_CONFLICTS) and Step 8 (SCORE_DIMENSIONS) are sequential dispatches. The sub-agent does deep analysis, WRITES its output JSON to theOUTPUT_PATHgiven in its prompt (thehandoff/dir), and returns a small receipt. The main thread gates the file withcheck_handoff.py, then pipes it through the producer script. The sub-agent never writes canonical artifacts — only its hand-off file. Step 6b is the real second debate round: each archetype sees the other two's round-1 assessments and either holds its position or moves on stated evidence; Step 7'scompose_discussion.pythen derivesdiscussion.jsonfrom Steps 6 and 6b's six artifacts — never authored by the main thread. - Context B — Post-compose coaching dispatch: The final step dispatches the sub-agent after
compose_report.pywritesreport.md. The sub-agent Reads the stagedcoaching_payload.jsonfrom the hand-off dir (Mitigation 2) — it does NOT read the fullreport.md— composes the coaching commentary, WRITES it to theOUTPUT_PATHhand-off file, and returns a small receipt. The main thread gates the file (check_handoff.py) and inserts it via the sharedinsert_coaching.pyscript (idempotency matrix, uuid-marker replacement, run_id-parity verification — all deterministic). See the reference above for the full Context B contract.
Tolerant JSON extraction protocol (Context B returns; also the Context A message-channel fallback): capture the sub-agent's final assistant message. It should be raw JSON, but may be wrapped in ```json ... ``` fences or carry a prose preamble. Extract tolerantly:
- If the message is wrapped in a
```json ... ```(or plain``` ... ```) fence, strip the fence first. - Try to parse the stripped text directly as JSON.
- If that fails, walk through the text looking for the first
{character and tryjson.JSONDecoder().raw_decode(text[i:])— this is brace-aware and handles nested objects correctly (unlike regex, which truncates on the first}). - If extraction fails entirely, re-prompt the sub-agent with: "Your previous reply could not be parsed as JSON. Return ONLY the JSON object — no markdown fences, no prose preamble."
Context A receipts don't need this protocol by hand — check_handoff.py --receipt-json - applies the same tolerant extraction internally; pass the final message verbatim.
Input Formats
Accept any combination: pitch deck, financial model, data room contents, text descriptions, prior market-sizing or deck-review artifacts, or just a verbal description of the business.
Available Scripts
All scripts are at ${CLAUDE_PLUGIN_ROOT}/skills/ic-sim/scripts/:
fund_profile.py— Validates fund profile structure (archetypes, check size, thesis, portfolio)detect_conflicts.py— Validates conflict assessments and computes summary statscompose_discussion.py— Derivesdiscussion.jsonfrom the 3 round-1 assessments + 3 round-2 rebuttals (majority-vote consensus, debate sections from partners' own responses); rejects (exit 1, no file written) on a structurally invalid rebuttal roundscore_dimensions.py— Scores 28 dimensions across 7 categories with conviction-based scoringcompose_report.py— Assembles report with cross-artifact validation;--strictexits 1 on high/medium warningsvisualize.py— Generates self-contained HTML with SVG charts (not JSON)
Also available from ${CLAUDE_PLUGIN_ROOT}/scripts/ (shared):
founder_context.py— Per-company context management (init/read/merge/validate)
Run with: python3 ${CLAUDE_PLUGIN_ROOT}/skills/ic-sim/scripts/<script>.py --pretty [args]
Available References
Read each when first needed — do NOT load all upfront. At ${CLAUDE_PLUGIN_ROOT}/skills/ic-sim/references/:
partner-archetypes.md— Read before Step 4 (main-thread use ONLY: mapping real partners to archetypes in fund-specific mode). The operative archetype rubric the PARTNER_ANALYSIS sub-agent needs is duplicated inagents/ic-sim.md— the sub-agent never reads this file (see "Context A hand-off protocol" below); this is a documented split, not an oversight.evaluation-criteria.md— No longer read by any workflow step. The operative 28-dimension rubric (status values, categories, stage calibration, dealbreaker thresholds, SaaS metrics) now lives inagents/ic-sim.md, inlined into the SCORE_DIMENSIONS sub-agent's system prompt. This file is kept as human-readable documentation only; edits to it do NOT propagate to sub-agent behavior — editagents/ic-sim.mddirectly.ic-dynamics.md— Background on how real VC ICs work: formats, decisions, what kills deals. Not read on a normal run —discussion.jsonis derived bycompose_discussion.pyfrom the partners' own assessments and rebuttals, with nothing authored by the main thread.artifact-schemas.md— Consult as needed when depositing agent-written artifacts
Artifact Pipeline
Every simulation deposits structured JSON artifacts into a working directory. The final step assembles all artifacts into a report and validates consistency. This is not optional.
| Step | Artifact | Producer |
|---|---|---|
| 1 | founder context | founder_context.py read/init |
| 2 | startup_profile.json | Agent (heredoc) |
| 3 | prior_artifacts.json | Agent (heredoc) |
| 4 | fund_profile.json | Agent (heredoc) then fund_profile.py validates |
| 5 | conflict_check.json | Context A dispatch: DETECT_CONFLICTS → detect_conflicts.py |
| 6 | partner_assessment_{visionary,operator,analyst}.json | Context A dispatch: PARTNER_ANALYSIS × 3 in parallel |
| 6b | partner_rebuttal_{visionary,operator,analyst}.json | Context A dispatch: PARTNER_REBUTTAL × 3 in parallel |
| 7 | discussion.json | compose_discussion.py (derives from the 3 assessments + 3 rebuttals — nothing authored) |
| 8 | score_dimensions.json | Context A dispatch: SCORE_DIMENSIONS → score_dimensions.py |
| 9 | Report | compose_report.py (writes both report.json and report.md) |
| 10 | Coaching | Context B dispatch: POST_COMPOSE_COACHING |
Rules:
- Deposit each artifact before proceeding to the next step
- For agent-written artifacts, consult
references/artifact-schemas.mdfor the JSON schema - If a step is not applicable, deposit a stub:
{"skipped": true, "reason": "..."} - Do NOT use
isolation: "worktree"for sub-agents — files written in a worktree won't appear in the main$SIM_DIR
Keep the founder informed with brief, plain-language updates at each step. Narrate the founder-visible OUTCOME, never the internal step. That is the test to apply, and it catches more than a word list can: the forbidden thing is not a syntax, it is talking about the machinery. Bad — "Gating and piping the extraction through the producer, then staging the coaching hand-off"; good — "I've checked your numbers and I'm writing up what stood out." Bad — "schema-drift warning on coaching_payload"; good — nothing, because the founder has no stake in it. Never name an internal artifact, field, or token (a payload key, a marker name, an artifact filename, a hand-off dir) even in plain prose with no backticks — a detector keyed on syntax cannot see "gated", "hand-off" or "canonical artifacts", but the founder still reads them and they still mean nothing to them. The between-step progress lines are the primary leak vector, not the final summary. They feel internal — you are narrating what you are about to do — but the founder reads every one of them, and this is where the leaks actually appear: "Now gating the hand-off before piping through the checklist producer", "Gate 1 passes", "Running the final verification gate". Rewrite each pipeline transition as the founder-visible outcome: "Checking your numbers against the 46-point review", "Your inputs look consistent — moving on to unit economics", "Finishing up and putting the report together". If a progress line would mean nothing to someone who has never seen this skill's internals, it does not belong in the channel. Also excluded, as before: file/script names, paths, *.py, --flags, $vars, exit codes ("Exit N", "not found"), W_/E_ codes, JSON, and step/route labels ("Lane N", "Context A/B", "Phase N", "structure detection", "the grid", any ALL_CAPS_TOKEN). Never surface the bare pass/hard_pass/invest/more_diligence verdict enum in a progress update either — pass/hard_pass mean the IC would DECLINE, and a founder reads a bare "pass" as approval; render the verdict in words (Decline / Invest / More Diligence) per Main-Thread Return, in every founder-facing line, not just the final headline. After each analytical step (5, 6, 6b, 7), share a one-sentence finding before moving on. The task tracker is founder-visible too — the same rule governs its labels. "Gate the inputs review handoff", "Validate inputs.json", "resolve agent namespace paths", "Initialize founder context" are leaks even though each names a real step, and even when the prose around them is clean. Label each task by the founder-visible outcome — "Check your inputs", "Score against the review", "Write up what I found" — never by a file, directory, script, or pipeline stage.
Workflow
Step 0: Path Setup
Every Bash tool call runs in a fresh shell — variables do not persist. Run the block below exactly once: it resolves $PLUGIN_ROOT deterministically, and every later block must substitute the printed value as a literal rather than re-running the resolution — repeating the self-heal search can land on a different mount than Step 0 picked when more than one is present (see why in the block's comments).
Optional, best-effort, and via the Read tool (not a shell command): before the block below, Read ${CLAUDE_PLUGIN_ROOT}/.claude-plugin/plugin.json and note its version field as EXPECT_VERSION. Passing it to select_plugin_root.py below lets an exact version match win over an arbitrary first hit. If the Read fails, skip it and omit --expect-version — selection is still deterministic without it.
SCRIPTS="${CLAUDE_PLUGIN_ROOT}/skills/ic-sim/scripts"
if [ ! -d "$SCRIPTS" ]; then
# In Cowork, CLAUDE_PLUGIN_ROOT substitutes to a host-side path absent inside
# the session VM — self-heal by collecting EVERY candidate mount (a session can
# have more than one at once: a stale host-side cache, a test marketplace, even
# a symlink into a different session's tree) and handing them to
# select_plugin_root.py, which picks ONE deterministically and names the
# rejects — never trust `find`'s arbitrary first hit, which can silently mix
# scripts across plugin versions mid-pipeline.
CANDIDATES="$(find /sessions -type d -path '*/skills/ic-sim/scripts' 2>/dev/null)"
[ -n "$CANDIDATES" ] || CANDIDATES="$(find / -type d -path '*/skills/ic-sim/scripts' 2>/dev/null)"
PROVISIONAL_ROOT="$(printf '%s\n' "$CANDIDATES" | head -1)"
PROVISIONAL_ROOT="${PROVISIONAL_ROOT%/skills/*}"
# Bootstrap order: $SHARED_SCRIPTS isn't known until a root is chosen, so use the
# provisional root's OWN copy of the selector; an older plugin copy without one
# falls back to the provisional root unchanged.
SELECTOR="$PROVISIONAL_ROOT/scripts/select_plugin_root.py"
if [ -f "$SELECTOR" ]; then
if [ -n "$EXPECT_VERSION" ]; then
PLUGIN_ROOT="$(printf '%s\n' "$CANDIDATES" | python3 "$SELECTOR" --expect-version "$EXPECT_VERSION")"
else
PLUGIN_ROOT="$(printf '%s\n' "$CANDIDATES" | python3 "$SELECTOR")"
fi
else
PLUGIN_ROOT="$PROVISIONAL_ROOT"
fi
SCRIPTS="$PLUGIN_ROOT/skills/ic-sim/scripts"
fi
PLUGIN_ROOT="${SCRIPTS%/skills/*}"
echo "PLUGIN_ROOT=$PLUGIN_ROOT" # resolved ONCE, here — paste this literal into every later block; never re-run this resolution
REFS="$PLUGIN_ROOT/skills/ic-sim/references"
SHARED_SCRIPTS="$PLUGIN_ROOT/scripts"
# Resolve the canonical artifacts root via a SCRIPT, not inline bash (the agent paraphrases inline
# path computations → outputs/ vs outputs/artifacts/ drift across runs). Deterministic + creates it.
python3 "$SHARED_SCRIPTS/resolve_artifacts_root.py" # prints ARTIFACTS_ROOT — use the printed path verbatim as ARTIFACTS_ROOT in every later block (a captured var dies in the next fresh shell)
Reaching the self-heal branch is normal in Cowork — ${CLAUDE_PLUGIN_ROOT} resolves to a HOST path that does not exist inside the VM, so the [ ! -d "$SCRIPTS" ] test fails by design rather than by misconfiguration. It is not a sign anything is wrong, and it is not worth narrating to the founder.
Outputs mount is append-only. Everything under the promoted outputs mount (.../mnt/outputs/, not just $SIM_DIR) is write-allowed and delete-denied by the platform: never rm, move away, or empty anything under it — including files you created yourself. Never create ad-hoc scratch anywhere under the outputs mount (no _src/ copies, no run-state note files); scratch belongs in $STAGING_DIR (a /tmp dir, defined below). Do not "clean up" the outputs folder before delivering — extra working files there are expected and harmless.
If ARTIFACTS_ROOT resolves to $(pwd)/artifacts but no artifacts/ directory exists at $(pwd): Use Glob with pattern **/artifacts/founder_context.json to locate existing artifacts, and derive ARTIFACTS_ROOT from the result. If nothing is found, mkdir -p "$ARTIFACTS_ROOT" and proceed.
There is no quick-check lane here, and that is deliberate. The verdict is the product of three partner analyses plus 28 scored dimensions; any subset fast enough to be a "quick check" would produce a verdict from a fraction of the evidence, and there is no honest way to label that. So when the founder asks a small conversational question, do not improvise an answer from your own reasoning under this skill's name — an unproduced verdict is exactly the output a founder over-trusts. Instead, say up front what the full run costs and let them choose: "Answering that properly means running the full IC simulation — it takes several minutes and produces a scored report with the partner debate and the conflict check. I can run it now, or if you just want my read without the scoring, say so and I'll answer outside the IC simulation." Naming the trade-off is honest; quietly substituting the cheap version is not.
After Step 1 (when the slug is known):
SIM_DIR="$ARTIFACTS_ROOT/ic-sim-${SLUG}"
mkdir -p "$SIM_DIR"
RUN_ID="$(date -u +%Y%m%dT%H%M%SZ)"
# Context A hand-off dir — PER RUN: sub-agents WRITE their raw output JSON here (the audit trail —
# raw sub-agent output as returned, before producer validation). Permanent by platform design
# (outputs/ mounts are write-allowed / delete-denied); nothing in it is ever a canonical artifact.
# The $RUN_ID segment is load-bearing: it prevents a stale prior-run file from silently passing
# the hand-off gate when a dispatch fails to write.
HANDOFF_DIR="$SIM_DIR/handoff/$RUN_ID"
mkdir -p "$HANDOFF_DIR"
# Sub-agents address the SAME dir by a different path (their file tools are rooted at the outputs
# mount in Cowork). Resolve the FULL agent-namespace path via the script — never hand-splice the
# printed root with a literal skill-name/slug/run-id string yourself (that string-splicing is
# exactly the non-determinism the resolver script exists to remove):
python3 "$SHARED_SCRIPTS/resolve_artifacts_root.py" --handoff-dir-agent \
--dir-name "ic-sim-${SLUG}" --run-id "$RUN_ID" # prints HANDOFF_AGENT verbatim
HANDOFF_AGENT="<printed value>" # use verbatim in OUTPUT_PATH lines
# Ad-hoc scratch (NOT sub-agent hand-off) lives OUTSIDE the promoted outputs/ tree, in a temp dir
# that is safe to both create and reclaim. Use the printed path verbatim in later steps.
STAGING_DIR="$(mktemp -d "${TMPDIR:-/tmp}/ic-sim-${SLUG:-co}.staging.XXXXXX")"
Pass RUN_ID to all sub-agents. Every artifact written to $SIM_DIR must include "metadata": {"run_id": "$RUN_ID"} at the top level. compose_report.py checks that all artifact run IDs match — a mismatch triggers a STALE_ARTIFACT high-severity warning, blocking under --strict.
Overwrite-in-place — do NOT delete prior artifacts under $SIM_DIR. It is the promoted outputs/
tree in Cowork, where deleting a user-visible path is unsafe (Cowork can deny it; the parity gate flags
it). Each producer writes its artifact fresh via -o every run, and RUN_ID is minted fresh per run —
so if a prior run left an artifact a later step doesn't regenerate, compose_report.py's STALE_ARTIFACT
check (run_ids must match) catches the mismatch. No bulk rm is needed or wanted.
Step 1: Read or Create Founder Context
python3 "$SHARED_SCRIPTS/founder_context.py" read --artifacts-root "$ARTIFACTS_ROOT" --pretty
Exit 0 (found): Use the company slug and pre-filled fields. Proceed to Step 2.
Exit 1 (not found): Expected on a first run — do NOT mention this check or its exit status to the founder; if you narrate anything first, say only "Let me grab a few basics about the company." Use AskUserQuestion (NOT plain chat) to ask for company name, stage, sector, and geography. If AskUserQuestion is genuinely unavailable in the host, do NOT skip the ask and do NOT assume the answer: ask the same question in plain chat, state the options explicitly, and wait for an answer before continuing. The ban above is on asking casually WHILE the tool is available — it is not a reason to stall a host that lacks it.
Stage is the one field with a real fixed label set — use it verbatim if asking.
Options: Pre-seed / Seed / Series A / Series B+
→ pre-seed | seed | series-a | series-b (founder_context.py's VALID_STAGES has 7 values including series-c/series-d/later; on a Series B+ pick, ask a plain-text follow-up for the specific stage rather than defaulting to series-b). Company name, sector and geography cannot take fixed labels — shape each as an affirmative option carrying any derived value plus a stated-value fallback. Provide at least 2 options. Then create:
Auto-pilot cross-reference — derive field-by-field, never all-or-nothing (do not stall an unattended run on a question the materials already answer): if the founder has selected Auto-pilot (see Mode Selection below) and provided materials (a deck, financial model, data room, or a sufficiently detailed description), derive each of the four basics — company name, stage, sector, geography — that the materials state, instead of gating on AskUserQuestion; a true unattended run should not stop and wait on a prompt whose answer is already in hand. Treat the four independently: deriving three and missing one does NOT re-gate all four. Before treating a field as missing, try to infer it from a clear signal in the materials (noting it as inferred, not founder-stated): geography from a phone country code, office address, or currency (e.g. a +972 number → Israel); stage from an ambiguous fundraise signal (a named round, round size, or "raising our seed" language → the matching stage value); sector from the product category and ICP. When running interactively, fall back to AskUserQuestion for only the specific field(s) with no derivable or inferable signal (stating what you already derived). Under Auto-pilot — where you cannot ask — mark any field that still has no signal as to_confirm and proceed rather than stalling.
--stage is enum-validated (hyphenated, lowercase) — one of: pre-seed, seed, series-a,
series-b, series-c, series-d, later. Passing a non-canonical token (e.g. seriesa,
pre_seed) is an argparse error and forces a retry — map the founder's answer (or the
deck-derived stage) to one of these 7 values before calling init.
--sector-type is an optional override (also enum-validated, hyphenated): one of saas,
ai-native, marketplace, hardware, hardware-subscription, consumer-subscription,
usage-based, transactional-fintech, retail. When omitted, founder_context.py auto-derives
it from --sector via a small alias table; if the sector doesn't match a known alias, the script
emits a runtime warning asking you to set --sector-type explicitly — pick the closest value from
the enum above rather than waiting for that warning.
When no enum value fits (e.g. logistics, physical goods, industrials). Do not silently pick
ai-native because the company mentions AI — that selects AI-native benchmarks for a business whose
economics are not AI-native, and nothing downstream flags it. Pick the value matching the revenue
mechanics (a logistics marketplace ⇒ marketplace; a freight SaaS ⇒ saas), state in the run that the
sector has no exact enum value and which one you substituted, and treat the resulting benchmark comparisons
as directional. If nothing matches on mechanics either, say so rather than choosing the least-wrong label
silently.
python3 "$SHARED_SCRIPTS/founder_context.py" init \
--company-name "Acme Corp" --stage seed --sector "B2B SaaS" \
--geography "US" --artifacts-root "$ARTIFACTS_ROOT"
# Add --sector-type <value> if the auto-derivation warning fires or the sector
# doesn't map cleanly to one of the 9 canonical sector-type values above.
Exit 2 (multiple): Present the list, ask which company, re-read with --slug.
Execution checkpoint — END OF STEP 1, READ BEFORE CONTINUING
You now have enough to run. Invoking this skill is not the same as running it. From here, every number that reaches the founder must come out of a producer script. Concretely:
Shortened here. Read the whole file on GitHub.
Signals
- GitHub stars
- 34
- Forks
- 3
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
ic-sim- Source
- github.com/lool-ventures/founder-skills