| name | carta-agm-deck-builder |
| version | 1.0.0 |
| description | End-to-end AGM deck generator. Extracts a firm's brand identity from their website, pulls live fund data from the Carta MCP (NAV, TVPI, portfolio, capital activity, benchmarks, fees, balance sheet, LP base), generates a branded HTML presentation with built-in slide navigation, and applies the "Powered by Carta" badge to every slide. Supports an optional past-deck reference for style matching. Trigger phrases: "AGM deck", "generate AGM deck", "create AGM presentation", "build AGM slides", "annual general meeting deck", "investor meeting deck", "LP meeting deck", "fund presentation", "branded fund deck", "build an AGM deck".
|
| allowed-tools | ["Read","Write","Edit","Bash(mkdir *)","Bash(cp *)","Bash(rm *)","Bash(open *)","Bash(grep *)","Bash(echo *)","Bash(ls *)","Bash(uv run *)","Bash(python3 *)","Bash(carta scope set write *)","Bash(carta scope clear *)","Bash(carta fa *)","WebFetch","Agent","mcp__claude_ai_carta__welcome","mcp__claude_ai_carta__search_tools","mcp__claude_ai_carta__call_tool","mcp__claude_ai_carta__list_accounts","mcp__claude_ai_carta__list_contexts","mcp__claude_ai_carta__set_context","mcp__cowork__present_files","mcp__claude_ai_carta__skill_checkpoint","mcp__carta__skill_checkpoint"] |
`_instrumentation_v2` is anonymous Carta skill-usage telemetry. ALWAYS include it in your Carta MCP tool-call arguments โ the plugins' hooks overwrite it when they run, so always sending it is safe. Shape:
`_instrumentation_v2: {"skills": [":", ...], "model": ""}`
`skills` is cumulative for the session: list every Carta skill loaded so far, not only the one driving this call. Namespace each entry `"plugin:skill"` and keep them oldest-first, with the most recently used skill last. List only Carta skills (`carta-cap-table:*`, `carta-crm:*`, `carta-investors:*`) โ never skills from non-Carta plugins.
AGM Deck Builder
Overview
This skill is the single entry point for building AGM (Annual General Meeting) decks. It orchestrates three bundled capabilities:
- Carta MCP (preferred) โ fetches live fund performance data (NAV, TVPI, DPI, IRR, portfolio companies, capital activity, benchmarks, fees, balance sheet, LP base) from the
FUND_ADMIN data warehouse. If not connected, falls back to manual user input.
- Brand extraction (bundled) โ extracts the firm's visual identity from their website using
analyze_website.py and generates the multi-slide deck themed to that identity. Checks for a previously saved brand board first via the Carta MCP (fa:get:brand_board) with CLI fallback (carta fa get brand-board), and saves new extractions back for future reuse.
- Carta badge (bundled) โ auto-injects the "Powered by Carta" lockup on every slide via the bundled
carta-mark.js script.
All three capabilities are built into this plugin. No additional plugins need to be installed.
When to Use
Fire this skill when the user asks to:
- Generate, create, or build an AGM deck or presentation
- Produce an annual general meeting slide deck
- Create a branded fund performance presentation
- Build an LP (limited partner) meeting deck
- Generate investor meeting slides from fund data
- Create any fund deck that should carry the firm's brand and the Carta badge
When NOT to fire
- Brand board only (no deck) โ use the bundled
carta-brand-extraction skill directly
- Carta-internal presentations โ use
carta-presentation
- Non-AGM artifacts that just need the Carta badge โ use the bundled
powered-by skill directly
How the deck is generated
Claude writes no slide HTML. All slide HTML is produced by compile_deck.py โ a deterministic compiler that reads template.html and substitutes token values. Claude's only outputs are:
brands/<slug>.css โ brand colors and fonts (written once via the Write tool)
creative-values.json โ 28 short text strings for headlines and narrative copy
Everything else โ every <section>, every chart config, every data table โ is generated by the compiler from the canonical template. Editing slide HTML directly is always wrong.
Reference files (read before running the compiler)
| File | When to read | Why |
|---|
references/slide-spec.md | Before Pass 1 | Understand which query feeds which slide and what each token expects |
references/template.html | Only if debugging a specific slide | See the exact token positions โ do not copy/edit HTML from it |
references/slide-templates/README.md | Only if building brand CSS | Design system primitives and CSS variable reference |
Static assets (never write these โ copy them)
| File | Role |
|---|
deck-stage.js | Custom element sizing each <section> to 1920ร1080 |
deck-nav.js | Presentation navigation: โ โ arrows, fullscreen, slide counter |
design-system/system.css | Typography, chrome, layout, accent utilities |
design-system/tokens.css | Brand โ intent token mapping |
design-system/charts.js | Declarative line / donut / hbar chart renderer |
design-system/carta-mark.js | Auto-injects "Powered by Carta" on every slide |
Workflow
Gate 0: Collect required inputs
Before doing any work, ensure you have these inputs. If any are missing, ask the user:
-
Reporting period (required)
- Use
AskUserQuestion with the question "What period should the AGM deck cover?" and these three computed options (derive quarter boundaries from today's date):
- Last quarter โ e.g. "Q2 2026 (through June 30)"
- Previous quarter โ e.g. "Q1 2026 (through March 31)"
- Full year โ e.g. "Full year 2025"
- Always compute the actual quarter names and end dates from today's date โ do not hardcode. Q1 = JanโMar, Q2 = AprโJun, Q3 = JulโSep, Q4 = OctโDec.
- The selected period becomes the
as_of_date passed to Agent A (last day of the chosen period, ISO format: YYYY-MM-DD). For full year, use December 31 of that year.
- If the user selects "Something else", ask them to type the date range and derive the
as_of_date from their answer.
-
Website URL (required)
- Prompt: "What is the firm's website URL? (e.g., acme.com)"
- Always ask the user to type the URL. Do not offer multiple-choice options or guess the URL from context. The user must provide this value directly.
- Accept bare domains โ the extraction script prepends
https:// automatically.
-
Fund selection (required)
- If the user named a specific fund, use that to filter in Step 1.
- If not, Step 1 will list available funds and ask the user to pick one.
-
Logo (optional input โ mandatory handling rule below)
- Prompt: "You can provide your firm's logo file (PNG, JPG, or SVG). If you don't, I'll try to extract one from the website."
๐จ MANDATORY LOGO RULE โ NO EXCEPTIONS:
- Logo provided โ copy it verbatim (Priority 1). Run the bash copy command in Step 3b immediately. Do NOT fetch, download, or recreate another logo.
- No logo provided โ use
firm_logo_url from Agent A if present (Priority 2), otherwise extract from website via Agent B (Priority 3).
-
Past AGM deck (optional)
- Prompt: "Do you have a past AGM deck you'd like me to use as a style reference? If so, provide the file path. Otherwise I'll use our default design system."
- If provided: Read the file to extract style cues โ slide ordering preferences, layout patterns, color usage, chart styles, content density, typography choices. Use these as the primary style guide for the new deck, adapting the design system tokens to match.
- If not provided: Use the bundled design system at
${CLAUDE_PLUGIN_ROOT}/skills/carta-agm-deck-builder/references/ as the style guideline. Read the design toolkit README (references/slide-templates/README.md) to understand all available primitives before composing slides.
Do not proceed past Gate 0 until you have at least the reporting period and website URL.
Checkpoint: Call mcp__<SERVER>__skill_checkpoint(skill_name="carta-investors:carta-agm-deck-builder", checkpoint_label="skill_started") before proceeding.
Steps 1 & 2: Fetch fund data + Extract brand identity (PARALLEL)
These two steps are independent and MUST run in parallel. Both run as sub-agents (they require MCP tool access and multi-step reasoning).
Tell the user: "Fetching fund data and extracting brand identity in parallel โ this saves significant time."
Fire both in a single message (two Agent tool calls in the same response):
Agent A: Fund Data Fetcher
Dispatch a sub-agent with the firm UUID, fund selection, and Carta MCP tool access. The agent should return the assembled fund_data JSON.
๐จ CRITICAL โ use the playbook. Before dispatching Agent A, read the compact playbook file with the Read tool:
${CLAUDE_PLUGIN_ROOT}/skills/carta-agm-deck-builder/references/agent-a-playbook.md
Pass the entire content of that file as Agent A's prompt (prepend the firm UUID, fund selection, and any user context). The playbook contains the exact MCP call sequence, saved-query gate, batch structure, default SQL, and efficiency rules โ all in ~80 lines.
The playbook is the single source of truth for the data-fetch logic. Do NOT duplicate it in the agent prompt.
Agent B: Brand Extractor
Dispatch a sub-agent with the website URL, firm UUID (if already known), and past deck path (if provided). The agent should return the brand_data JSON plus logo assets.
๐จ CRITICAL โ use the playbook. Before dispatching Agent B, read the compact playbook file with the Read tool:
${CLAUDE_PLUGIN_ROOT}/skills/carta-agm-deck-builder/references/agent-b-playbook.md
Pass the entire content of that file as Agent B's prompt (prepend the website URL, firm UUID if known, and past deck path if provided). The playbook contains the exact MCP gate sequence, brand board check, website extraction fallback, save logic, and custom font handling.
The playbook is the single source of truth for the brand-extraction logic. Do NOT duplicate it in the agent prompt.
While Agents A and B run, also read the design toolkit README (${CLAUDE_PLUGIN_ROOT}/skills/carta-agm-deck-builder/references/slide-templates/README.md) and the example deck (references/template.html) so you have all design primitives and layout patterns loaded for Step 3 as soon as the agents return.
After both agents complete, merge their results and proceed to Step 3. If either agent fails, handle the failure independently (e.g., MCP fallback for fund data, manual theme for brand) โ a failure in one does not block the other.
Checkpoint: Call mcp__<SERVER>__skill_checkpoint(skill_name="carta-investors:carta-agm-deck-builder", checkpoint_label="deck_generation_started") before proceeding.
Step 1: Fetch fund data from Carta MCP
(Runs as Agent A in parallel with Step 2)
The full data-fetch logic โ MCP connection, saved-query gate, 3-batch query execution, default SQL, and fallback โ lives in the playbook at references/agent-a-playbook.md. That file is the single source of truth; do not duplicate its content here.
What Agent A returns: a fund_data JSON object with results from up to 13 query slots (fund metrics, NAV time series, benchmarks, portfolio investments, company info, etc.). Every field is optional โ slides with missing data are automatically skipped in Step 3.
Step 2: Extract brand identity
(Runs as Agent B in parallel with Step 1)
The full brand-extraction logic โ saved brand board check (MCP gate), website extraction fallback, brand summary, save-after-extraction, and custom font handling โ lives in the playbook at references/agent-b-playbook.md. That file is the single source of truth; do not duplicate its content here.
What Agent B returns: a brand_data JSON object with brand_palette (colors with hex/role/count), typography (fonts, optional font_faces for custom @font-face blocks), logos (candidates with URLs), and source ("saved" or "extracted").
Brand color mapping (required)
After Agent B returns, map the brand palette into the generated brand CSS. All three roles must be present โ never drop secondary or accent colors.
| Extraction role | CSS variable | Used for |
|---|
primary (highest-count) | --brand-accent-1 | .ds-dark slide backgrounds, primary emphasis, chart series 1 |
secondary (highest-count) | --brand-accent-2 | Secondary emphasis (.ds-on-accent-2), chart series 2 |
accent (highest-count) | --brand-accent-3 | Tertiary accent, chart series 3 |
Also map surface and ink colors from the extraction:
background / light-background โ --brand-paper, --brand-paper-alt
text / dark-text โ --brand-ink, --brand-ink-2, --brand-ink-mute
border โ --brand-rule
Quick self-check: if --brand-accent-1, --brand-accent-2, and --brand-accent-3 are all the same value, or any one is missing โ stop and fix the brand CSS before proceeding. See references/brands/example.css for the complete variable list.
Custom font loading
If brand_data.typography.font_faces is non-empty, generate @font-face blocks and inject them in a <style> tag in the deck's <head>, after the Google Fonts <link> and before the brand CSS <link>. Prefer woff2 sources. If font_faces is empty, skip this entirely.
Step 3: Generate the deck
Pre-generation gate
Confirm all before running the compiler:
- โ
Slide spec read:
references/slide-spec.md โ understand which query feeds each slide and what each token expects
- โ
Brand CSS written: three distinct hex values in
--brand-accent-1, --brand-accent-2, --brand-accent-3; --brand-on-accent is a light color (white or cream) when --brand-accent-1 is dark
- โ
Output folder ready: the bash
cp block in step 3b ran โ design-system/, deck-stage.js, deck-nav.js exist without having been passed through the Write tool
If any box is unchecked, complete that step now before proceeding.
3a. Determine active slides
Walk the Slide Roster in order. For each slide apply exactly ONE of these two outcomes โ no other outcomes exist:
- Data present โ render the slide using the query named in the "Data Source(s)" column as the primary source. Start there. Data from other queries may be used as complementary information on the same slide (additional KPIs, supporting context, related metrics) โ but the primary chart and the main table must be driven by the designated query's result set, not substituted with values from another slide's query.
- Data absent or empty from the designated query โ check whether another query's result set contains the expected data for this slide. If it does, use that data and note the source in the post-generation audit. If no query has usable data for this slide, skip the slide entirely and record the reason.
๐จ CRITICAL โ DON'T MIX COLUMNS ACROSS QUERIES. The Slide Roster designates a query per slide to prevent subtle data errors caused by column reuse:
- When the designated query is available and non-empty, always prefer its columns over similarly-named columns from other queries.
๐จ CRITICAL โ NO DATA FABRICATION. This is a hard stop, not a guideline:
- Do NOT construct trend lines by interpolating between a start and end value.
- Do NOT estimate sector breakdowns, geography splits, or any other composition percentages unless the query returned them explicitly.
- Do NOT invent company names, round sizes, lead investors, or deal details.
- Do NOT extrapolate from benchmark data to produce fund-specific values.
- Do NOT fill a slide with "representative" or "illustrative" numbers.
- If a field is null or absent in the query result, treat the entire slide as having no data.
๐จ ABSOLUTE RULE โ EVERY ROSTER SLIDE IS A SEPARATE <section>. NO EXCEPTIONS.
Every slide listed in the Slide Roster MUST be rendered as its own independent <section> block. This is not a style guideline โ it is a hard structural requirement:
- The only valid reason to omit a slide is zero usable data. If data exists (even partial), the slide must be rendered.
- Slides 11 and 11b are TWO slides. Slides 12 and 12b are TWO slides. They share a query but each gets its own full
<section>.
- Slide 13 is its own slide โ never fold it into Slide 11 even if it feels redundant.
- "The slide fits better combined with another" is not a valid reason to merge. Neither is "the data overlaps" or "the layout would be cleaner." Design each slide independently to fit its own data.
- A merged slide is counted as SKIPPED for BOTH entries in the post-generation audit, and is a failure of the deck.
The goal is a complete deck. A 28-slide deck with real data is always better than a 15-slide deck where half the slides were silently merged away.
3b. Visualization rules (NON-NEGOTIABLE)
Every slide that presents quantitative data MUST include at least one chart visualization. Text-only tables and bullet-point lists of numbers are never acceptable when a chart would work.
When composing chart markup:
- Line charts: always include
"area": true, "dots": true, "highlightLast": true, "endLabel": "<current value>" in the first series. Benchmark series use "dashed": true.
- Donut charts: always include
"centerLabel" and "centerNumber".
- Hbar charts: always include
"valueLabel" on each row and "series" for color coding.
Tables are acceptable ONLY alongside a chart or KPI strip for detail-heavy data โ never as the sole visualization.
3c. Build the HTML deck
The output is an HTML file with sibling asset directories that the user opens in any browser. No Playwright, Chromium, or npm dependencies are needed. The HTML includes built-in slide navigation (deck-nav.js) with prev/next buttons, keyboard arrows, fullscreen mode, and viewport scaling.
(a) Write the brand CSS file. Create a <firm-slug>.css file following the structure in references/brands/example.css. Map all extracted brand colors to --brand-* variables as described in the "Brand color mapping" section above.
(b) Set up the working directory โ MANDATORY: use bash cp, never the Write tool.
The design-system files (tokens.css, system.css, charts.js, carta-mark.js, deck-stage.js, deck-nav.js) are static. They must be copied with a single bash command. Do NOT read them into context and re-write them โ that wastes ~3 minutes and ~10k output tokens for zero benefit.
Run this bash block exactly as written:
OUTPUT_DIR="/tmp/agm-deck"
PLUGIN_DIR="${CLAUDE_PLUGIN_ROOT}"
mkdir -p "$OUTPUT_DIR/design-system" "$OUTPUT_DIR/brands" "$OUTPUT_DIR/assets"
cp -r "$PLUGIN_DIR/references/design-system/." "$OUTPUT_DIR/design-system/"
cp "$PLUGIN_DIR/references/deck-stage.js" "$OUTPUT_DIR/"
cp "$PLUGIN_DIR/references/deck-nav.js" "$OUTPUT_DIR/"
cp "$PLUGIN_DIR/references/deck-shell.html" "$OUTPUT_DIR/"
Logo โ priority chain (resolve before writing brand CSS):
- User provided a file โ
cp it verbatim, use it. Stop here.
firm_logo_url from Agent A โ run process-logo. Stop here.
- Website extraction from Agent B โ use the best candidate found.
Priority 1 โ user-provided file:
PROVIDED_LOGO_PATH="<absolute path the user gave>"
LOGO_FILENAME=$(basename "$PROVIDED_LOGO_PATH")
cp "$PROVIDED_LOGO_PATH" "$OUTPUT_DIR/assets/$LOGO_FILENAME"
echo "Logo: $OUTPUT_DIR/assets/$LOGO_FILENAME"
Priority 2 โ firm_logo_url from Agent A (downloads + removes background):
uv run "${CLAUDE_PLUGIN_ROOT}/skills/carta-agm-deck-builder/scripts/compile_deck.py" process-logo \
--url "<firm_logo_url from Agent A result>" \
--output "$OUTPUT_DIR/assets/firm-logo.png"
LOGO_FILENAME="firm-logo.png"
First run downloads the rembg model (~170MB, cached after that). SVGs skip background removal automatically.
Set both --brand-logo-light and --brand-logo-dark in the brand CSS to url("../assets/$LOGO_FILENAME").
After the cp commands succeed, the only files you write from scratch are:
$OUTPUT_DIR/brands/<firm-slug>.css โ generated from Agent B's brand extraction
$OUTPUT_DIR/<firm-slug>-agm-<year>.html โ the composed deck
Do NOT write generated deck files into the plugin's references/ directory.
(c) Compose the HTML deck โ deterministic compiler + 28 creative tokens.
This is a two-pass process. The compile_deck.py script handles 190 tokens mechanically; you only need to supply 28 short creative text strings.
Pass 1 โ Run the compiler (deterministic substitution):
uv run "${CLAUDE_PLUGIN_ROOT}/skills/carta-agm-deck-builder/scripts/compile_deck.py" compile \
--queries-dir /tmp/agm-queries \
--template "${CLAUDE_PLUGIN_ROOT}/skills/carta-agm-deck-builder/references/template.html" \
--brand-slug "<firm-slug>" \
--firm-name "<Firm Name>" \
--period-label "<Full Year 2025>" \
--as-of-date <YYYY-MM-DD> \
--output "$OUTPUT_DIR/<firm-slug>-agm-<year>.html" \
--creative-prompt "$OUTPUT_DIR/creative-tokens.md"
Pass 2 โ Fill the creative tokens:
Read creative-tokens.md (it is very short โ one line per field with data context). Write creative-values.json:
{
"COVER_HERO_LINE1": "Investing in the future of",
"COVER_HERO_EMPHASIS": "financial services.",
"COVER_STANDFIRST": "A full-year review of ...",
...all creative fields...
}
Then apply:
uv run "${CLAUDE_PLUGIN_ROOT}/skills/carta-agm-deck-builder/scripts/compile_deck.py" apply \
--partial "$OUTPUT_DIR/<firm-slug>-agm-<year>.html" \
--creative-values "$OUTPUT_DIR/creative-values.json" \
--output "$OUTPUT_DIR/<firm-slug>-agm-<year>.html"
Verify:
grep -c 'data-screen-label' "$OUTPUT_DIR/<firm-slug>-agm-<year>.html"
Checkpoint: Call mcp__<SERVER>__skill_checkpoint(skill_name="carta-investors:carta-agm-deck-builder", checkpoint_label="deck_generation_finished") before proceeding.
The compiler's apply command overwrites the partial file in place with the final HTML. If the script reports remaining {{ tokens, use the Edit tool to patch them before opening.
Navigation is automatic. The deck-nav.js script (loaded via deck-shell.html) provides:
- Prev/Next buttons in a fixed bar at the bottom of the viewport
- Keyboard navigation: โ โ arrows, Space (next), Home/End, F (fullscreen)
- Slide counter showing current position (e.g., "3 / 27")
- Fullscreen / Present mode โ button in the nav bar or press F to toggle
- Viewport scaling โ the 1920ร1080 slide canvas scales to fit the user's browser window
No additional rendering step is needed. The .html file is the final deliverable.
Carta badge: carta-mark.js (loaded via deck-shell.html) auto-injects a "Powered by Carta" footer on every <section>. The SVG uses fill="currentColor", so it inherits correct contrast automatically โ no manual placement needed.
decl-shell replacement: deck-shell.html' **Overflow guard**: slides use overflow: hidden(set bydeck-stage.js), so content that doesn't fit is clipped silently. Design each slide to fit: use .ds-body--smfor dense tables, paginate long tables across slides, keep chartheight` within the available vertical space, and use compact legend layouts for donuts with many items.
Step 4: Deliver
-
Open the HTML file for the user:
open <output-file>
-
Clean up temporary files โ run immediately after opening:
rm -rf /tmp/agm-queries/
rm -f "$BLOB_PATH"
rm -f "$OUTPUT_DIR/creative-tokens.md" "$OUTPUT_DIR/creative-values.json"
echo "Done."
Checkpoint: Call mcp__<SERVER>__skill_checkpoint(skill_name="carta-investors:carta-agm-deck-builder", checkpoint_label="skill_finished") before proceeding.
-
Confirm delivery:
- "AGM deck ready: acme-agm-2025.html โ open in any browser. Use โ โ arrows to navigate, F for fullscreen presentation mode."
- "Note: keep the HTML file in its folder alongside the
design-system/, brands/, and assets/ directories โ it references them for styling and charts."
-
๐จ CRITICAL โ Post-generation audit. After every deck build, output the following three sections verbatim. This is non-negotiable and must appear even if all slides were included:
Before rendering these tables, call call_tool({"name": "fa__list__saved_queries", "arguments": {}}) and build a lookup map: for each entry whose collection or subcollection_name matches "AGM Deck", record name โ _links.web_url. Skip this prefetch silently if the Carta MCP was not connected. Do NOT construct, guess, or fabricate URLs.
Slides included (list each slide by number and title):
| # | Slide | Data source |
|---|
(one row per rendered slide; if a saved query was found for this slide's data source, hyperlink the data source name to its _links.web_url; otherwise render it as plain text) | | |
Slides skipped (list each skipped slide and the exact reason):
| # | Slide | Reason skipped |
|---|
| (one row per skipped slide, or "None โ all slides included"; hyperlink the data source name the same way when a match exists) | | |
Data provenance confirmation:
All figures in this deck come directly from Carta MCP query results or your explicit input. No data was invented, estimated, extrapolated, or inferred. If a slide required data that was not returned by Carta, it was skipped (see table above).
MANDATORY: If you cannot honestly make that statement โ if any number was estimated or extrapolated โ replace it with an explicit disclosure of exactly which values were fabricated and why, and offer to regenerate those slides once the user provides real data.
Slide Roster
Slides are produced in this order. All slides with available data are included.
| # | Slide | Data Source(s) | Required Visualization |
|---|
| 1 | Cover โ fund name, firm name, as-of date, confidential | Fund Performance Summary | โ (hero layout, .ds-dark, .ds-display headline) |
| 2 | Agenda โ auto-generated from active slide titles | Auto | โ (2-column list with .ds-rule dividers) |
| 3 | Fund Performance Summary โ hero KPIs + fund metrics | Fund Performance Summary | KPI strip (4-col .ds-num with .ds-kpi-highlight) + line chart (TVPI trajectory, area: true, highlightLast: true) |
| 4 | Fund IRR vs. Benchmarks โ fund IRR vs. vintage/size cohort percentiles | Fund IRR vs. Benchmarks | line chart โ fund solid (area: true), cohort p25/p50/p75 dashed: true |
| 5 | NAV Trend โ monthly NAV over time | NAV Trend | line chart with area: true, dots: true, highlightLast: true, endLabel |
| 6 | Multi-Fund Performance โ all firm funds side-by-side | Fund Performance Summary | hbar chart (TVPI or NAV by fund) + table with KPI strip header |
| 7 | Capital Deployment / Dry Powder โ % deployed, pace vs. cohort | Capital Deployment / Dry Powder | line chart (deployment pace vs. cohort, solid + dashed) + KPI strip |
| 10 | LP Geography โ LP locations by state/country | LP Geography | donut chart (top regions by commitment) + legend block |
| 11 | Portfolio Overview โ NAV % by company + investments by vintage | Portfolio Overview | donut chart (NAV composition by sector/thesis) + .ds-card legend |
| 11b | Investment Detail & Performance โ SOI-style table | Investment Detail & Performance | hbar chart (top 5โ8 investments by MOIC or FMV) + table with KPI strip |
| 12 | Portfolio Company Logo Grid โ active investments as cards | Portfolio Company Logo Grid | Logo grid using .ds-card--flat cards |
| 12b | Logo Leaderboard โ top N companies ranked visually | Logo Leaderboard | hbar chart with company names, value as MOIC or total value |
| 13 | Asset Type Breakdown โ Preferred Equity / SAFE / Note composition | Asset Type Breakdown | donut chart with centerLabel + centerNumber + legend |
| 14 | Investment Performance Buckets โ capital bucketed 0x / 1x / 1โ3x / 3โ10x / 10x+ | Investment Performance Buckets | hbar chart โ one row per bucket, valueLabel showing count and $amount |
| 15 | Top Performing Investments โ ranked by MOIC | Top Performing Investments | hbar chart (MOIC ranking) + supporting list with .ds-accent-border |
| 16 | Annual Markups / Markdowns โ unrealized gain/loss YoY | Annual Markups / Markdowns | hbar chart (gain/loss per company, multi-series for markup vs markdown) + KPI strip |
| 17 | Deal-Level IRR โ cost vs FMV + IRR | Deal-Level IRR | hbar chart (invested vs. FMV per deal, multi-series) + table |
| 18 | Geographic Portfolio Mix โ US vs. international | Geographic Portfolio Mix | donut chart (US vs. international) + legend |
| 19 | SPV Performance โ per-SPV invested, contributions, MOIC | SPV Performance Table | hbar chart (MOIC by SPV) + KPI strip header |
| 20 | Portfolio KPI Highlights โ ARR, MRR, headcount, retention | Portfolio KPI Highlights | line chart (trend for top metric) + KPI strip + table |
| 21 | Profitability Milestone Tracker โ profitability by quarter | Profitability Milestone Tracker | hbar chart (companies by profitability status) + KPI strip |
| 22 | Financing Round History โ raises, pre/post-money valuations | Financing Round History | line chart (cumulative capital raised over time) + KPI strip (total raised, avg pre-money) + table |
| 25 | Portfolio Company Deep Dives โ per-company cards | Portfolio Company Deep Dives | 2ร2 .ds-card grid with rotating accent borders |
| 27 | Fund Expenses Breakdown โ fees, legal, audit, admin | Fund Expenses Breakdown | donut chart (expense composition) or hbar chart + KPI strip |
| 28 | Market Outlook & Themes โ 3 thematic focus areas | User input | 3-col .ds-card grid on .ds-alt surface with accent borders |
| 29 | Closing / Contact โ firm contact info, IR email | User input | โ (hero layout, .ds-dark, .ds-display headline) |
Every slide in this roster must appear in the deck unless its data is completely absent. The only valid skip reason is zero usable data โ not layout preference, not data overlap with another slide, not perceived redundancy. Skip = no data. Present = render it.
Note on slide numbering: The roster follows the numbering above (gaps like 8โ9, 24 are intentional โ reserved for future slides). Slides 11b and 12b are sub-slides that appear after their parent โ they are always separate <section> blocks, never merged into 11 or 12.
User-facing output
- Gate 0 prompts: Ask for missing inputs conversationally. Don't dump a form.
- During generation: "Fetching fund data and extracting brand identity in parallel โ this saves significant time."
- After brand data obtained: "Brand loaded โ primary: #XX, secondary: #XX. Building deck." (whether from saved brand board or fresh extraction)
- After completion: One sentence confirming the file and that brand, Carta badge, and slide navigation are included. Mention keyboard shortcuts (โ โ arrows, F for fullscreen).
- On Building HTML Deck: "Running token replacement script and creative sentences."
- Do NOT dump raw JSON, narrate each phase in detail, or explain which scripts were invoked.
Verification checklist
Run these checks before delivering. Every item is a command or a visual confirm โ not a memory check.
๐จ Logo (must pass)
๐จ Data integrity (must pass)
Brand
Content
Output
Common pitfalls (anti-patterns)
โ Writing slide HTML directly โ Claude never writes <section> blocks. The compiler generates all slide HTML from template.html. Using Write or Edit to produce slide HTML bypasses the template entirely and is the primary cause of structural variation between decks. If the compiler fails, fix the compiler โ do not hand-write slides.
โ Writing agent prompts inline instead of using the playbooks โ do not write data-fetch or brand-extraction instructions from memory or paraphrase the logic. Always Read the playbook files (references/agent-a-playbook.md, references/agent-b-playbook.md) and pass their content as the agent prompts. The playbooks are the single source of truth for each agent's logic.
โ Using the old multi-call data-fetch pattern โ do not fall back to fa:list:saved_queries โ fa:get:saved_query โ dwh:execute:queries unless fa:get:agm_deck_data explicitly fails. The new command fetches, executes, and returns all ~21 AGM queries in a single call with server-side parallelism. The old pattern is retained only as a documented fallback in the playbook.
โ Silently substituting a different query's columns โ when the designated query is available and non-empty, always use its columns as the primary data source. Do not quietly swap in a similarly-named column from another query. A deal_irr decimal from Top Performing Investments is not the same value as deal_irr_pct from Deal-Level IRR โ they differ in scale and context. Complementary data from other queries is welcome on any slide, but it should enrich, not replace, the designated query's output.
โ Using cat >> bash appends to write slide batches โ appended content lands outside </deck-stage> because the Write call in Step 1 already closed the file with </deck-stage></body></html>. The result: slides 01โ03 display correctly, every slide after that is invisible (outside the custom element). Always write the complete deck in one Write call. If the output is truncated, use the Edit tool to insert missing slides before </deck-stage> โ never append with bash.
โ Ignoring a provided logo โ if the user gave you a logo file, copy it with cp and use it. Do NOT fetch, download, redraw, or generate an SVG version. Do NOT use any other image.
โ Generating slides without reading the canonical template first โ read references/template.html before writing any HTML. The template defines the exact <section> structure, grid layout, class names, and {{TOKEN}} positions for every slide in the Roster. Skipping it means inventing layouts that will differ deck to deck โ the root cause of the variation problem this template solves.
โ Deviating from the template structure โ the template's grid configs, element order, and class names are not suggestions. Do not move elements, change column counts, swap chart types, or add extra wrappers. Replace {{TOKEN}} values only. If a token has no data, skip the entire slide โ do not fill it with placeholder text or restructure the layout.
โ Monochrome slides โ if only one brand color is visible, you're not using the accent utilities. Every deck must feature all three --brand-accent-* values through .ds-dark slides, .ds-card borders, .ds-kpi-highlight, .ds-accent-bar, .ds-pill, chart series, and <em> text. A monochrome deck is a failed deck.
โ Tables without charts โ a slide full of numbers but no chart is a failed slide. If a slide has quantitative data, it gets a line/donut/hbar chart. Tables are only acceptable alongside a chart or KPI strip.
โ Inventing or extrapolating data โ if a query returned no results, the slide is skipped. Do NOT fill it with estimated values, trend lines constructed from partial data, plausible sector splits, representative company names, or any other fabricated content. This is the most critical failure mode: invented numbers in an LP presentation can cause legal and reputational harm. When in doubt, skip the slide.
โ Merging two roster slides into one โ if the roster lists Slide 13 and Slide 11 as separate slides, they must be rendered as two separate <section> blocks even if their data source overlaps. Never collapse one slide's content into another slide's layout. A merged slide is a skipped slide for both entries in the post-generation audit.
โ Dropping slides with empty data silently โ always record every skipped slide (name + reason) so you can report them to the user in the Step 4 post-generation audit. Silent omissions undermine trust.
โ Using the Write tool for design-system files โ tokens.css, system.css, charts.js, carta-mark.js, deck-stage.js, deck-nav.js, and deck-shell.html are static files. Reading them into context and re-writing them costs ~3 minutes and ~10k output tokens for zero benefit (the model strips comments and truncates sections, producing a degraded copy). The only correct path is the bash cp block in step 3b. The Write tool is only valid for <firm-slug>.css and <firm-slug>-agm-<year>.html โ the two files that are genuinely generated.
โ Inlining all CSS in <style> without the design-system folder โ inline styles break the token inheritance chain and make brand theming impossible. Always set up the working directory with design-system/, brands/, and assets/ as relative paths.
โ Forgetting deck-nav.js โ without the navigation script, the HTML file renders all slides stacked vertically with no way to navigate. Always copy deck-nav.js into the working directory alongside deck-stage.js.
Troubleshooting
- Carta MCP not connected: Both agent playbooks detect this and fall back gracefully. Agent A falls back to manual data; Agent B falls back to website extraction.
- No funds returned from query: The firm context may be wrong, or the fund may not have NAV data yet. List available firms/funds and let the user pick.
- Partial fund data: If some queries return empty results, proceed with what's available โ slides with missing data are skipped and reported in the post-generation audit.
- Past deck format: The skill can read PDF and PPTX files for style reference. If the past deck is in another format, ask the user to convert it.
- Brand extraction fails: Agent B's playbook handles the fallback chain (saved board โ website โ WebFetch โ manual colors).
- Carta badge missing: Verify
carta-mark.js is loaded in deck-shell.html. It auto-injects the footer on every <section>.
- Navigation not appearing: Verify
deck-nav.js is copied to the working directory root (same level as the HTML file).
- HTML file broken when moved: The HTML references sibling directories (
design-system/, brands/, assets/) via relative paths. The entire output folder must be moved together.