| name | ic-sim |
| description | Simulates a realistic VC Investment Committee with three partner archetypes debating a startup's merits, concerns, and deal terms, scored across 28 dimensions. |
| when_to_use | Use ONLY when the user has asked to simulate an IC discussion or to hear how partners would debate a specific startup, AND has provided enough context (a deck, a description of the company, or a specific fund). Do not auto-invoke on general fundraising questions.
|
| user-invocable | true |
IC Simulation Skill
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
uv for script execution.
- Imports (recommended):
market-sizing:sizing.json — fund alignment and market validation
deck-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.md for 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 5a, 5b-d, and 7 dispatch the ic-sim agent via the
Task tool. The novel element here is parallel dispatch: Step 5b-d (PARTNER_ANALYSIS) dispatches the agent three times simultaneously — one per partner archetype — in a single assistant turn. Step 5a (DETECT_CONFLICTS) and Step 7 (SCORE_DIMENSIONS) are sequential dispatches. The sub-agent does deep analysis, WRITES its output JSON to the OUTPUT_PATH given in its prompt (the handoff/ dir), and returns a small receipt. The main thread gates the file with check_handoff.py, then pipes it through the producer script. The sub-agent never writes canonical artifacts — only its hand-off file.
- Context B — Post-compose coaching dispatch: The final step dispatches the sub-agent after
compose_report.py writes report.md. The sub-agent reads the coaching_payload inlined in the dispatch prompt (Mitigation 2) — it does NOT read the full report.md — composes the coaching commentary, WRITES it to the OUTPUT_PATH hand-off file, and returns a small receipt. The main thread gates the file (check_handoff.py) and inserts it via the shared insert_coaching.py script (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 try json.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 stats
score_dimensions.py — Scores 28 dimensions across 7 categories with conviction-based scoring
compose_report.py — Assembles report with cross-artifact validation; --strict exits 1 on high/medium warnings
visualize.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 in agents/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 in agents/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 — edit agents/ic-sim.md directly.
ic-dynamics.md — Read before composing discussion.json. How real VC ICs work: formats, decisions, what kills deals
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 |
| 5a | conflict_check.json | Context A dispatch: DETECT_CONFLICTS → detect_conflicts.py |
| 5b-d | partner_assessment_{visionary,operator,analyst}.json | Context A dispatch: PARTNER_ANALYSIS × 3 in parallel |
| 6 | discussion.json | Main thread combines 3 partner returns |
| 7 | score_dimensions.json | Context A dispatch: SCORE_DIMENSIONS → score_dimensions.py |
| 8 | Report | compose_report.py (writes both report.json and report.md) |
| 9 | 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.md for 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. Never mention file names, scripts, or JSON. After each analytical step (5–7), share a one-sentence finding before moving on.
Workflow
Step 0: Path Setup
Every Bash tool call runs in a fresh shell — variables do not persist. Prefix every Bash call that uses these paths with the variable block below, or substitute absolute paths directly:
SCRIPTS="${CLAUDE_PLUGIN_ROOT}/skills/ic-sim/scripts"
if [ ! -d "$SCRIPTS" ]; then
SCRIPTS="$(find /sessions -type d -path '*/skills/ic-sim/scripts' 2>/dev/null | head -1)"
fi
if [ -z "$SCRIPTS" ] || [ ! -d "$SCRIPTS" ]; then
SCRIPTS="$(find / -type d -path '*/skills/ic-sim/scripts' 2>/dev/null | head -1)"
fi
PLUGIN_ROOT="${SCRIPTS%/skills/*}"
REFS="$PLUGIN_ROOT/skills/ic-sim/references"
SHARED_SCRIPTS="$PLUGIN_ROOT/scripts"
python3 "$SHARED_SCRIPTS/resolve_artifacts_root.py"
The Step 0 block self-heals when ${CLAUDE_PLUGIN_ROOT} doesn't resolve (Cowork). If it still comes up empty, locate the anchor manually: find / -path '*/skills/ic-sim/scripts/score_dimensions.py' 2>/dev/null | head -5 and derive the variables from it.
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.
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)"
HANDOFF_DIR="$SIM_DIR/handoff/$RUN_ID"
mkdir -p "$HANDOFF_DIR"
python3 "$SHARED_SCRIPTS/resolve_artifacts_root.py" --agent
HANDOFF_AGENT="<printed AGENT_ARTIFACTS_ROOT>/ic-sim-${SLUG}/handoff/$RUN_ID"
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): This is normal for a first run — do not treat it as an error. Use AskUserQuestion (NOT plain chat) to ask for company name, stage, sector, and geography. Provide at least 2 options. Then create:
python3 "$SHARED_SCRIPTS/founder_context.py" init \
--company-name "Acme Corp" --stage seed --sector "B2B SaaS" \
--geography "US" --artifacts-root "$ARTIFACTS_ROOT"
Exit 2 (multiple): Present the list, ask which company, re-read with --slug.
Mode Selection
Ask the user (or infer from context):
- Interactive — Pause between partner positions for founder input
- Auto-pilot — Run all sections without pausing
- Fund-specific — Research a real fund first. Combines with either mode.
Steps 2-3: Extract Startup Profile and Import Prior Artifacts
Read the provided materials and extract the startup profile directly. Import any prior market-sizing or deck-review artifacts from $ARTIFACTS_ROOT. Deposit both artifacts to $SIM_DIR.
Read ${CLAUDE_PLUGIN_ROOT}/skills/ic-sim/references/artifact-schemas.md before writing artifacts to ensure JSON schema compliance. (Use the literal ${CLAUDE_PLUGIN_ROOT} token for a file-tool Read — it is pre-resolved to a host-readable path; do NOT read the find /sessions-derived $REFS value, which a host-native file tool cannot reach.)
Write startup_profile.json:
cat <<'PROFILE_EOF' > "$SIM_DIR/startup_profile.json"
{
"company_name": "...",
"simulation_date": "YYYY-MM-DD",
"stage": "seed",
"one_liner": "...",
"sector": "...",
"geography": "...",
"business_model": "...",
"funding_history": "...",
"current_raise": "...",
"key_metrics": "...",
"materials_provided": ["..."],
"metadata": {"run_id": "<RUN_ID>"}
}
PROFILE_EOF
Write prior_artifacts.json (stub if no prior artifacts):
cat <<'PRIOR_EOF' > "$SIM_DIR/prior_artifacts.json"
{"imported": [], "skipped": true, "reason": "No prior artifacts available", "metadata": {"run_id": "<RUN_ID>"}}
PRIOR_EOF
Step 4: Build Fund Profile -> fund_profile.json
REQUIRED — read ${CLAUDE_PLUGIN_ROOT}/skills/ic-sim/references/partner-archetypes.md now. (Literal token, not $REFS — a file-tool Read of the find /sessions path is denied on host-loop.)
Generic mode: Build a standard early-stage fund profile with the three canonical archetypes (visionary, operator, analyst).
Fund-specific mode: Use WebSearch to research fund thesis, portfolio, partner backgrounds, check size range, and stage preference. Map real partners to archetype roles.
Validation constraints: check_size_range must be a dict (not a string), stage_focus must be a non-empty array, each source must have url or title.
cat <<'FUND_EOF' | python3 "$SCRIPTS/fund_profile.py" --pretty --run-id "$RUN_ID" -o "$SIM_DIR/fund_profile.json"
{...fund profile JSON...}
FUND_EOF
Accepted warnings: Add accepted_warnings array with code, match (case-insensitive), and reason. Compose downgrades matching warnings to "acknowledged".
Context A hand-off protocol (file transport + gate)
Every Context A dispatch prompt carries an OUTPUT_PATH: line built from $HANDOFF_AGENT. The
sub-agent WRITES its output JSON to that path with its Write tool and returns only a small receipt:
{"status": "complete", "output_path": "<echo of OUTPUT_PATH>"}. The payload leaves the model
exactly once (into the Write call) — never re-type sub-agent JSON into a heredoc.
Path idiom for dispatch prompts (host-loop path gate): OUTPUT_PATH is relative to the sub-agent's
file-tool cwd (the outputs mount) — built from the resolve_artifacts_root.py --agent namespace
($HANDOFF_AGENT). Never hand a sub-agent an absolute /sessions/... path for a file-tool Read/Write —
the host-loop path gate denies it. ic-sim sub-agents perform zero file reads (all inputs are inlined
into the prompt; the archetype/28-dimension rubric lives in agents/ic-sim.md), so only OUTPUT_PATH
(a write) needs the agent namespace. A bundled references/*.md a MAIN-THREAD step reads is passed as the
literal ${CLAUDE_PLUGIN_ROOT}/skills/ic-sim/references/... token (pre-resolved to a host-readable path);
never a find /sessions-discovered $REFS (a shell path a file tool can't read).
After EVERY Context A dispatch, gate before piping (<step> = the dispatch's file stem):
printf '%s' '<agent final message verbatim>' | \
python3 "$SHARED_SCRIPTS/check_handoff.py" "$HANDOFF_DIR/<step>_output.json" \
--agent-path "$HANDOFF_AGENT/<step>_output.json" --receipt-json -
Branch on the exit code (complete state machine — do not improvise):
- Exit 0 → pipe the file through the producer:
cat "$HANDOFF_DIR/<step>_output.json" | python3 "$SCRIPTS/<producer>.py" ...
- Exit 3 (missing/empty file — receipt may be fabricated) → redo-dispatch: fresh Task, same prompt plus one line: "your receipt claimed a file at
<path> but none exists; use Write to create exactly that path."
- Exit 4 (file exists, invalid JSON) → repair-dispatch: fresh Task: "Read
<OUTPUT_PATH>; it fails JSON parsing with <verbatim detail from the diagnostic>; fix and rewrite it; return the receipt."
- Exit 5 (receipt echoes a different path) → repair-dispatch telling the agent the exact expected OUTPUT_PATH (it wrote somewhere else).
- Exit 6 (receipt unparseable / no
output_path key) → redo-dispatch with "return ONLY the receipt JSON — no fences, no prose."
- Producer schema rejection (the pipe fails next) → repair-dispatch with the producer's stderr verbatim.
- Any other exit (script crash etc.) → STOP with the stderr.
- After ANY corrective dispatch, resume from
check_handoff.py — never pipe to the producer unchecked.
Retry budget: max 2 corrective dispatches per step, of any kind, in any combination (max 3
total dispatches). After the second corrective dispatch fails any gate: STOP and report the exact
diagnostic to the founder. The main thread MUST NOT author or patch analytical content itself —
filling in the JSON is the fabrication failure mode this architecture exists to prevent. A
status: "blocked" return is not a gate retry, but it is bounded: at most ONE input-fix
re-dispatch per step; a second blocked return STOPs with both reasons quoted.
Graceful degrade (fleet heterogeneity): if the FIRST corrective dispatch also exits 3 while the
agent's receipt claims complete with the correctly echoed path, treat the host's filesystem
topology as hand-off-incompatible: fall back to message-channel transport for the REST of this run
(sub-agent returns full JSON in its final message; apply the tolerant JSON extraction protocol;
stage to $STAGING_DIR/<step>_input.json; same producer pipe), and note the fallback in your
final summary.
Retries overwrite the same OUTPUT_PATH (the mount is write-allowed / delete-denied — never rm
under $SIM_DIR). Hand-off files are not canonical artifacts: producers consume them only via the
explicit pipe, and compose_report.py never reads handoff/.
Ad-hoc scratch (NOT sub-agent hand-off) still goes to $STAGING_DIR in /tmp — see the reference
(founder-skills/references/skill-execution-model.md). Hard rule: never stage scratch under
$SIM_DIR, and never delete anything under it.
Step 5a: Check Portfolio Conflicts -> conflict_check.json (Context A dispatch)
The sub-agent performs ZERO file reads. Read the two inputs it needs in the main thread and paste their content into the dispatch prompt below — do not send it a path to Read.
cat "$SIM_DIR/fund_profile.json"
cat "$SIM_DIR/startup_profile.json"
The two JSON files print to stdout — copy each verbatim into the matching FUND_PROFILE: / STARTUP_PROFILE: block below. (Never capture into a shell variable: each Bash call runs in a fresh shell.)
Dispatch the ic-sim sub-agent in Context A (DETECT_CONFLICTS). Call the Task tool with subagent_type: "founder-skills:ic-sim" (a type-less dispatch falls back to the wildcard general-purpose agent).
Dispatch prompt template:
CONTEXT: DETECT_CONFLICTS
SIM_DIR: <absolute path to SIM_DIR>
OUTPUT_PATH: <HANDOFF_AGENT>/detect_conflicts_output.json
RUN_ID: <RUN_ID>
You are the ic-sim agent dispatched in Context A (DETECT_CONFLICTS). All
inputs are inlined below — you perform ZERO file reads for this dispatch.
FUND_PROFILE:
<paste fund_profile.json content printed by the previous Bash command, verbatim>
STARTUP_PROFILE:
<paste startup_profile.json content printed by the previous Bash command, verbatim>
For each company in the fund's portfolio, assess whether it conflicts with the
startup. Assess each company for: direct conflict, adjacent conflict, or
customer overlap. Use consistent names between portfolio and conflicts.
Use your Write tool to write to OUTPUT_PATH exactly the shape expected by
detect_conflicts.py (portfolio_size and conflicts array — no metadata block;
producer script adds it):
{
"portfolio_size": <integer>,
"conflicts": [
{
"company": "<portfolio company name>",
"type": "direct|adjacent|customer_overlap",
"severity": "blocking|manageable",
"rationale": "<specific reason for conflict>"
}
]
}
Write an empty conflicts array if no conflicts found. portfolio_size must equal
the number of companies in the fund's portfolio.
Then return ONLY the receipt JSON in your final assistant message:
{"status": "complete", "output_path": "<echo of OUTPUT_PATH>"}
Do NOT write any file other than OUTPUT_PATH — canonical artifacts are
producer-script-only; anything else you write bypasses schema validation and
run_id stamping.
After the sub-agent returns: gate the hand-off per the Context A hand-off protocol, then pipe:
cat "$HANDOFF_DIR/detect_conflicts_output.json" | \
python3 "$SCRIPTS/detect_conflicts.py" --pretty --run-id "$RUN_ID" -o "$SIM_DIR/conflict_check.json"
Step 5b-d: Partner Assessments (PARTNER_ANALYSIS × 3 in parallel)
The sub-agent performs ZERO file reads — its archetype rubric is already in agents/ic-sim.md (no read needed), and the three dynamic inputs below must be inlined, not pointed at by path.
cat "$SIM_DIR/startup_profile.json"
cat "$SIM_DIR/fund_profile.json"
cat "$SIM_DIR/prior_artifacts.json"
The three JSON files print to stdout — copy each verbatim into the matching STARTUP_PROFILE: / FUND_PROFILE: / PRIOR_ARTIFACTS: block, IDENTICALLY, in each of the three dispatch prompts below (all three archetypes see the same shared context; only archetype: and OUTPUT_PATH: differ per dispatch). Never capture into a shell variable: each Bash call runs in a fresh shell.
Parallel dispatch recipe
Dispatch the ic-sim agent THREE TIMES in parallel via the Task tool — one per archetype (visionary, operator, analyst), each with subagent_type: "founder-skills:ic-sim" (a type-less dispatch falls back to the wildcard general-purpose agent). Use a SINGLE assistant turn with 3 Task tool calls (NOT three sequential turns). The Claude Code harness runs all three Task calls in parallel when they appear in the same assistant response.
Pseudocode for the dispatch (executed as 3 parallel Task tool_use blocks; <shared inputs> is the same STARTUP_PROFILE/FUND_PROFILE/PRIOR_ARTIFACTS block pasted into all three):
[
Task(subagent_type="founder-skills:ic-sim", description="Partner analysis: visionary",
prompt="CONTEXT: PARTNER_ANALYSIS\narchetype: visionary\nOUTPUT_PATH: <HANDOFF_AGENT>/partner_visionary_output.json\nRUN_ID: <id>\n<shared inputs>"),
Task(subagent_type="founder-skills:ic-sim", description="Partner analysis: operator",
prompt="CONTEXT: PARTNER_ANALYSIS\narchetype: operator\nOUTPUT_PATH: <HANDOFF_AGENT>/partner_operator_output.json\nRUN_ID: <id>\n<shared inputs>"),
Task(subagent_type="founder-skills:ic-sim", description="Partner analysis: analyst",
prompt="CONTEXT: PARTNER_ANALYSIS\narchetype: analyst\nOUTPUT_PATH: <HANDOFF_AGENT>/partner_analyst_output.json\nRUN_ID: <id>\n<shared inputs>"),
]
Full dispatch prompt template (used for each archetype, with archetype:/OUTPUT_PATH: changed — the three inlined blocks are IDENTICAL across all three dispatches):
CONTEXT: PARTNER_ANALYSIS
archetype: visionary|operator|analyst
SIM_DIR: <absolute path to SIM_DIR>
OUTPUT_PATH: <HANDOFF_AGENT>/partner_<archetype>_output.json
RUN_ID: <RUN_ID>
You are the ic-sim agent dispatched in Context A (PARTNER_ANALYSIS) for the
<archetype> archetype. Your archetype rubric is in your system prompt; you
perform ZERO file reads. Dynamic inputs are inlined below.
STARTUP_PROFILE:
<paste startup_profile.json content, verbatim>
FUND_PROFILE:
<paste fund_profile.json content, verbatim>
PRIOR_ARTIFACTS:
<paste prior_artifacts.json content, verbatim>
Embody the <archetype> perspective from your system-prompt rubric. Every
conviction point and concern must cite specific evidence from the startup
materials.
Use your Write tool to write to OUTPUT_PATH the partner assessment object (no
metadata block):
{
"partner": "<archetype>",
"verdict": "invest|more_diligence|pass|hard_pass",
"rationale": "<200+ word explanation of the verdict from this archetype's perspective>",
"conviction_points": ["<specific strength, min 2>", ...],
"key_concerns": ["<specific concern, min 2>", ...],
"questions_for_founders": ["<question the archetype would ask>", ...],
"diligence_requirements": ["<what this partner needs to see before committing>", ...]
}
Then return ONLY the receipt JSON in your final assistant message:
{"status": "complete", "output_path": "<echo of OUTPUT_PATH>"}
Do NOT write any file other than OUTPUT_PATH — canonical artifacts are
producer-script-only; anything else you write bypasses schema validation and
run_id stamping.
After all three sub-agents return: gate EACH hand-off per the Context A hand-off protocol
(run check_handoff.py per file, branch on exit codes). Then promote each assessment
deterministically — the JSON flows from the hand-off file, never re-typed:
cat "$HANDOFF_DIR/partner_visionary_output.json" | python3 -c "
import json, sys
data = json.load(sys.stdin)
data['metadata'] = {'run_id': '$RUN_ID'}
with open('$SIM_DIR/partner_assessment_visionary.json', 'w') as f:
json.dump(data, f, indent=2)
"
Verify after writes: check that $SIM_DIR contains all three partner_assessment_*.json files. If any are missing, re-run that dispatch before proceeding.
Step 6: Compose Discussion -> discussion.json
REQUIRED — read ${CLAUDE_PLUGIN_ROOT}/skills/ic-sim/references/ic-dynamics.md now. (Literal token, not $REFS — a file-tool Read of the find /sessions path is denied on host-loop.)
Read all 3 partner assessments from $SIM_DIR. Compose the IC discussion object: each partner presents, partners respond to each other, build toward consensus. In interactive mode, pause between positions.
Verdict reconciliation: Ensure each partner's verdict in partner_verdicts reflects their final position after debate, not their opening position. The compose report flags UNANIMOUS_VERDICT_MISMATCH when all partners contradict the consensus.
Write discussion.json directly:
cat <<'DISCUSSION_EOF' > "$SIM_DIR/discussion.json"
{
"assessment_mode": "sub-agent",
"assessment_mode_intentional": true,
"partner_verdicts": [
{"partner": "visionary", "verdict": "...", "rationale": "..."},
{"partner": "operator", "verdict": "...", "rationale": "..."},
{"partner": "analyst", "verdict": "...", "rationale": "..."}
],
"debate_sections": [
{
"topic": "...",
"exchanges": [
{"partner": "visionary", "position": "..."},
{"partner": "operator", "position": "..."}
]
}
],
"consensus_verdict": "...",
"key_concerns": ["..."],
"diligence_requirements": ["..."],
"metadata": {"run_id": "<RUN_ID>"}
}
DISCUSSION_EOF
Step 7: Score Dimensions -> score_dimensions.json (Context A dispatch)
The sub-agent performs ZERO file reads — its 28-dimension rubric is already in agents/ic-sim.md (no read needed). The five dynamic inputs below must be inlined, not pointed at by path. This is the largest inline of any ic-sim dispatch (5 JSON artifacts); inline the full files as-is — they are per-run analytical artifacts, not large machine-produced dumps.
cat "$SIM_DIR/startup_profile.json"
cat "$SIM_DIR/discussion.json"
cat "$SIM_DIR/partner_assessment_visionary.json"
cat "$SIM_DIR/partner_assessment_operator.json"
cat "$SIM_DIR/partner_assessment_analyst.json"
The five JSON files print to stdout — copy each verbatim into the matching block below. Never capture into a shell variable: each Bash call runs in a fresh shell.
Dispatch the ic-sim sub-agent in Context A (SCORE_DIMENSIONS). Call the Task tool with subagent_type: "founder-skills:ic-sim" (a type-less dispatch falls back to the wildcard general-purpose agent).
Dispatch prompt template:
CONTEXT: SCORE_DIMENSIONS
SIM_DIR: <absolute path to SIM_DIR>
OUTPUT_PATH: <HANDOFF_AGENT>/score_dimensions_output.json
RUN_ID: <RUN_ID>
You are the ic-sim agent dispatched in Context A (SCORE_DIMENSIONS). Your
28-dimension rubric is already in your system prompt — you perform ZERO file
reads for this dispatch. All dynamic inputs are inlined below.
STARTUP_PROFILE:
<paste startup_profile.json content printed by the earlier Bash command, verbatim>
DISCUSSION:
<paste discussion.json content printed by the earlier Bash command, verbatim>
PARTNER_ASSESSMENT_VISIONARY:
<paste partner_assessment_visionary.json content printed by the earlier Bash command, verbatim>
PARTNER_ASSESSMENT_OPERATOR:
<paste partner_assessment_operator.json content printed by the earlier Bash command, verbatim>
PARTNER_ASSESSMENT_ANALYST:
<paste partner_assessment_analyst.json content printed by the earlier Bash command, verbatim>
Score all 28 dimensions based on the evidence from the startup materials and
the partner assessments. Ensure scoring reflects the discussion conclusions —
if a dimension was debated as a dealbreaker, the score must reflect that.
Use your Write tool to write to OUTPUT_PATH the items array without summary
(producer script computes summary). Each item has this shape:
{
"items": [
{
"id": "team_founder_market_fit",
"category": "Team",
"status": "strong_conviction|moderate_conviction|concern|dealbreaker|not_applicable",
"evidence": "<specific evidence from startup materials>",
"notes": "<optional explanation>"
}
]
}
Score every one of these 28 dimensions — one item per id, no omissions, no
invented ids. The 28 dimension ids, grouped by category:
Team:
{"id": "team_founder_market_fit"}
{"id": "team_complementary_skills"}
{"id": "team_execution_speed"}
{"id": "team_coachability"}
Market:
{"id": "market_size_credibility"}
{"id": "market_timing"}
{"id": "market_growth_trajectory"}
{"id": "market_entry_barriers"}
Product:
{"id": "product_differentiation"}
{"id": "product_traction_evidence"}
{"id": "product_technical_moat"}
{"id": "product_user_love"}
Business Model:
{"id": "biz_unit_economics"}
{"id": "biz_pricing_power"}
{"id": "biz_scalability"}
{"id": "biz_gross_margins"}
Financials:
{"id": "fin_capital_efficiency"}
{"id": "fin_runway_plan"}
{"id": "fin_path_to_next_round"}
{"id": "fin_revenue_quality"}
Risk:
{"id": "risk_single_point_failure"}
{"id": "risk_regulatory"}
{"id": "risk_competitive_response"}
{"id": "risk_customer_concentration"}
Fund Fit:
{"id": "fit_thesis_alignment"}
{"id": "fit_portfolio_conflict"}
{"id": "fit_stage_match"}
{"id": "fit_value_add"}
Then return ONLY the receipt JSON in your final assistant message:
{"status": "complete", "output_path": "<echo of OUTPUT_PATH>"}
Do NOT write any file other than OUTPUT_PATH — canonical artifacts are
producer-script-only; anything else you write bypasses schema validation and
run_id stamping.
After the sub-agent returns: gate the hand-off per the Context A hand-off protocol, then pipe:
cat "$HANDOFF_DIR/score_dimensions_output.json" | \
python3 "$SCRIPTS/score_dimensions.py" --pretty --run-id "$RUN_ID" -o "$SIM_DIR/score_dimensions.json"
Step 8: Compose and Validate Report
python3 "$SCRIPTS/compose_report.py" --dir "$SIM_DIR" --pretty \
-o "$SIM_DIR/report.json" \
--write-md "$SIM_DIR/report.md"
compose_report.py writes both report.json and report.md deterministically. Do NOT read report_markdown out of report.json and re-write it via heredoc — agent heredoc handling can drift and produce unparseable output.
Fix high-severity warnings and re-run. Use --strict to enforce a clean report.
Post-write verification: compose_report.py exits non-zero (code 2) if the declared output files don't exist or are empty after writing. If compose exits non-zero, stop and report the exact stderr — do not proceed to Step 9.
Step 9: Post-Compose Coaching Commentary (Context B dispatch — POST_COMPOSE_COACHING)
Dispatch the ic-sim sub-agent in Context B. Call the Task tool with subagent_type: "founder-skills:ic-sim" after compose_report.py has successfully written both report.json and report.md.
Mitigation 2 protocol: the main thread reads the structured coaching_payload from report.json and inlines it into the dispatch prompt. The sub-agent does NOT Read full report.md — it consumes coaching_payload directly, composes the coaching commentary, and WRITES it to the OUTPUT_PATH hand-off file with its Write tool, returning only a small receipt (the same file transport as Context A — the commentary leaves the model exactly once, into the Write call; the main thread never re-types it). The main thread gates that file with check_handoff.py, then inserts it via the shared insert_coaching.py script (idempotency matrix, uuid-marker replacement, run_id-parity verification — all deterministic). See the ic-sim agent body's "Context B — Post-compose coaching dispatch (POST_COMPOSE_COACHING)" section for the full procedure.
python3 -c '
import json, sys
data = json.load(open(sys.argv[1]))
print(json.dumps(data["coaching_payload"], indent=2))
' "$SIM_DIR/report.json"
The payload prints to stdout — copy it from the tool result into the dispatch
prompt below. (Never capture it into a shell variable: each Bash call runs in a
fresh shell, so the variable would be unreadable and gone.)
Dispatch prompt template (substitute <HANDOFF_AGENT> with the Step-0 agent-namespace value — the same rule as every Context A dispatch; the sub-agent has no shell vars, so paste the printed value):
CONTEXT: POST_COMPOSE_COACHING
OUTPUT_PATH: <HANDOFF_AGENT>/coaching_commentary_output.json
You are dispatched to add coaching commentary to an IC simulation report.
The compose_report.py script has finished. The structured `coaching_payload`
from report.json is:
<paste the coaching_payload JSON printed by the previous Bash command here verbatim>
Follow your agent body's Context B procedure (POST_COMPOSE_COACHING):
1. Compose commentary from the inlined coaching_payload (dealbreakers,
concerns, summary, high_severity_warnings, company_name).
Do NOT Read the full report.md. Do NOT edit report.md or any canonical artifact.
2. Use your Write tool to write to OUTPUT_PATH exactly:
{"commentary_markdown": "<your coaching commentary — markdown body text, WITHOUT
a '## Coaching Commentary' heading and WITHOUT the insertion_marker string>"}
Do NOT write any file other than OUTPUT_PATH — insertion into report.md is the
main thread's job, via the shared insert_coaching.py script.
3. Return:
{"status": "complete", "output_path": "<echo of OUTPUT_PATH>"}
OR, if the payload is unusable (write no file):
{"status": "blocked", "reason": "<specific gap>"}
Stop after returning the receipt JSON. Do not narrate.
After the sub-agent returns: if its final message is a {"status": "blocked", "reason": ...} object, stop and report the reason to the founder — do not run the gate. Otherwise gate the hand-off, then (on gate exit 0) insert deterministically. The commentary leaves the model exactly once (into the sub-agent's Write call) — NEVER re-type the sub-agent's JSON into a heredoc or a python -c argument.
SHARED_SCRIPTS="${CLAUDE_PLUGIN_ROOT}/scripts"
if [ ! -d "$SHARED_SCRIPTS" ]; then
IS_SCRIPTS="$(find /sessions -type d -path '*/skills/ic-sim/scripts' 2>/dev/null | head -1)"
[ -n "$IS_SCRIPTS" ] || IS_SCRIPTS="$(find / -type d -path '*/skills/ic-sim/scripts' 2>/dev/null | head -1)"
SHARED_SCRIPTS="${IS_SCRIPTS%/skills/*}/scripts"
fi
printf '%s' '<agent final message verbatim>' | \
python3 "$SHARED_SCRIPTS/check_handoff.py" "$HANDOFF_DIR/coaching_commentary_output.json" \
--agent-path "$HANDOFF_AGENT/coaching_commentary_output.json" --receipt-json -
On gate exit 0, insert the gated hand-off FILE (feed the file, never re-type the message):
SHARED_SCRIPTS="${CLAUDE_PLUGIN_ROOT}/scripts"
if [ ! -d "$SHARED_SCRIPTS" ]; then
IS_SCRIPTS="$(find /sessions -type d -path '*/skills/ic-sim/scripts' 2>/dev/null | head -1)"
[ -n "$IS_SCRIPTS" ] || IS_SCRIPTS="$(find / -type d -path '*/skills/ic-sim/scripts' 2>/dev/null | head -1)"
SHARED_SCRIPTS="${IS_SCRIPTS%/skills/*}/scripts"
fi
python3 "$SHARED_SCRIPTS/insert_coaching.py" \
--report "$SIM_DIR/report.md" \
--marker '<EXACT insertion_marker string from report.json coaching_payload>' \
--commentary-file "$HANDOFF_DIR/coaching_commentary_output.json" \
--verify-artifact "$SIM_DIR/fund_profile.json" \
--verify-artifact "$SIM_DIR/conflict_check.json" \
--verify-artifact "$SIM_DIR/discussion.json" \
--verify-artifact "$SIM_DIR/score_dimensions.json"
The gate (check_handoff.py) verifies the sub-agent's hand-off file exists, parses, and matches the receipt's echoed path; on gate exit 0, insert_coaching.py performs the 6-state idempotency check, replaces the marker with ## Coaching Commentary + the commentary in a single in-place write, and verifies run_id parity across all 4 producer artifacts. Branch on the exit code (complete state machine — do not improvise):
- Exit 0 from the chain —
insert_coaching.py's receipt on stdout says inserted (or already_inserted on a resume). Present report_path to the founder and proceed.
check_handoff.py exit 3 (missing/empty file — receipt may be fabricated) → redo-dispatch: fresh Task, same prompt plus one line: "your receipt claimed a file at <path> but none exists; use Write to create exactly that path."
- Exit 4 (file exists, invalid JSON) → repair-dispatch: fresh Task: "Read
<OUTPUT_PATH>; it fails JSON parsing with <verbatim detail from the diagnostic>; fix and rewrite it; return the receipt."
- Exit 5 (receipt echoes a different path) → repair-dispatch telling the agent the exact expected OUTPUT_PATH.
- Exit 6 (receipt unparseable / no
output_path key) → redo-dispatch with "return ONLY the receipt JSON — no fences, no prose." (A status: "blocked" final message is NOT exit 6 — it was handled before the gate.)
insert_coaching.py exit 1 (blocked; stdout carries {"status": "blocked", "reason": ...}) → stop and report the exact reason. Do NOT hand-edit report.md — if the reason mentions a truncated report or a missing marker, re-run compose_report.py --write-md and retry the chain. If the reason is commentary_markdown missing or empty, treat as a malformed hand-off: repair-dispatch quoting the reason.
- After ANY corrective dispatch, resume from the gate chain — never feed
insert_coaching.py an ungated file.
Retry budget: max 2 corrective dispatches (same rule as Context A). Graceful degrade: if the FIRST corrective dispatch also exits 3 while the receipt claims complete with the correctly echoed path, treat the host topology as hand-off-incompatible — fall back to message-channel transport: apply the tolerant JSON extraction protocol to the sub-agent's final message, stage its commentary_markdown payload to $SIM_DIR/coaching_commentary.json via a quoted <<'COACHING_EOF' heredoc (single-quoted → apostrophe-safe; NEVER python -c, NEVER the outputs/ root), and run the same insert_coaching.py --commentary-file "$SIM_DIR/coaching_commentary.json" against that staged file.
Step 10 (Optional): Generate Visual Report
python3 "$SCRIPTS/visualize.py" --dir "$SIM_DIR" -o "$SIM_DIR/report.html"
Present the HTML file path to the user so they can open the visual report.
Step 11: Deliver Artifacts
Copy final deliverables to workspace root: {Company}_IC_Simulation.md, .html (if generated), .json (optional).
No cleanup needed: scratch lives in $STAGING_DIR (/tmp, reclaimed by the sandbox). Do not rm
anything under $SIM_DIR — it is the promoted outputs/ tree in Cowork, where deleting a
user-visible path is unsafe (and the parity gate flags it).
Main-Thread Return
This skill runs inline in the main thread (not as a sub-agent). The final outcome the main thread delivers to the founder is:
- The path to
$SIM_DIR/report.md — the primary deliverable.
- The headline outcome fields, sourced from the
coaching_payload you inlined in Step 9 (decision = summary.verdict, consensus_strength, key_concerns from concerns[].dimension, high_severity_warnings) plus the insert_coaching.py receipt (status, report_path, run_id). The Context B sub-agent no longer echoes these — do not source them from its return.
- Optionally: the HTML report path from Step 10.
Do NOT inline report_markdown in the assistant message. The founder reads the file via the path.
Scoring
- 28 dimensions, each:
strong_conviction / moderate_conviction / concern / dealbreaker / not_applicable
- Conviction score:
(strong*1.0 + moderate*0.5) / applicable * 100
- Verdicts:
invest (>=75%), more_diligence (>=50%), pass (<50%), hard_pass (any dealbreaker)
- One dealbreaker forces
hard_pass regardless of score
What-If Recomputation Rule
If the founder asks "what if [dimension] changed to [status]": re-run score_dimensions.py with the updated item statuses and present the script's output. Never recompute the conviction score by hand — the formula (strong×1.0 + moderate×0.5) ÷ applicable × 100 interacts with dealbreaker override logic in non-obvious ways, and the script is the authoritative source.
Cross-Agent Integration
This skill imports artifacts from prior market-sizing and deck-review analyses. Imported artifacts are recorded with dates. Imports older than 7 days are flagged as STALE_IMPORT.
Feedback
If a run ends blocked or failed, after you report the reason to the founder, add one line:
If this looks wrong or didn't finish, you can flag it: /founder-skills:feedback.
On unsolicited praise or frustration, you may mention /founder-skills:feedback once — never routinely, never mid-workflow, never more than once per session.