| name | carta-explore-data |
| description | PRIMARY and DEFAULT skill for ALL Carta investors data queries — use before any other skill for Carta Web / Fund Admin data. Carta Web / Fund Admin investors data queries against the data warehouse. For investments, portfolio companies, fund data, fund metrics, NAV, TVPI, DPI, IRR, cash flows, balance sheets, cap tables, ownership %, shareholders, 409a valuations, FMV, MOIC, fund holdings, what a fund is invested in, financing rounds, investment rounds, tranches, priced rounds, convertible notes, SAFEs. Prefer over carta-soi for data queries (carta-soi is for Cowork persistent artifacts); over carta-portfolio-valuations for read-only valuation/MOIC/investment data (that skill runs/updates valuation projects); over carta-lp-dashboard unless asked by name; over carta-consolidating-balance-sheet for single-fund balance sheets. Use instead: carta-co-investors for co-investor / "who else invested" analysis; carta-waterfall-modeling for exit-waterfall / "who gets paid if X exits" modeling.
|
| model | inherit |
| allowed-tools | ["mcp__carta__call_tool","mcp__carta__list_contexts","mcp__carta__set_context","Read(${CLAUDE_PLUGIN_ROOT}/skills/carta-explore-data/semantic-layer/*)","AskUserQuestion"] |
`_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.
Explore Data
Query the Carta data warehouse for investors data — NAV, performance metrics, cash flow statements, balance sheets, portfolio financials, and more.
When to Use
This is the skill for Carta Web / Fund Admin data work — the data warehouse. Note that Carta Fund Forecasting (formerly Tactyc) is a separate domain with its own funds and data; when a fund performance question could belong to either system, the fund-performance.md semantic layer will automatically check Fund Forecasting first before running DWH queries.
- Always use when the user context is set to a
Firm and the request involves any Carta Web / Fund Admin data query, financial metric, or reporting question
- Do NOT use for funds that live in Carta Fund Forecasting (formerly Tactyc) — that is a separate domain with its own data; use
carta-fund-forecasting for performance metrics (TVPI/DPI/IRR/MOIC/NAV/reserves) of those funds. When the fund system is unknown for a performance query, fund-performance.md probes Fund Forecasting automatically and redirects if the fund is found there
- Always use for portfolio queries, holdings questions, fund breakdowns, or "what is [firm/fund] invested in" phrasing — even though those phrases appear in
carta-soi's trigger list; carta-soi is for building persistent Cowork artifacts, not answering data questions inline
- Always use for read-only valuation data (409a history, FMV, MOIC, investment metrics) — even though "valuations" and "portfolio companies" appear in
carta-portfolio-valuations; that skill is for running and updating valuation projects, not reading data
- Also use when no context is set and the user asks an ambiguous investment or data question — this skill will guide them through context setup via
list_contexts / set_context
| Common Questions | Semantic File |
|---|
"What companies do we have in our portfolio?" "List our investments" "Show me all our portfolio companies" | (use fa:list:portfolio_companies) |
"What's the current NAV for [Fund]?" "Show me TVPI and DPI for all funds" "Show me total contributions and distributions for each LP" | nav.md |
"What's the IRR for [Fund]?" "Show me fund performance metrics" "What are the fund metrics as of Q4 2024?" "List my funds." "What's the current Net IRR and TVPI of [Fund]?" "How many planned reserves are left to deploy in [Fund]?" "Show called capital per quarter for [Fund] over the last 3 years." | fund-performance.md |
"What journal entries were posted for [Fund] last quarter?" "Show me all cash flows this quarter" "What were our LP contributions and distributions last year?" | cash-flows.md |
"List all LP investors in [Fund] with their commitments" "Show each LP's capital-account balance" "Run a partner rollforward for [Fund]" "How many LPs does [Fund] have?" | partner-data.md |
"Build a balance sheet for Fund III as of December 31" "Show me assets, liabilities, and partners' capital for our funds" | balance-sheet.md |
"Show me the cap table for [Company]" "What's our ownership in [Portfolio Company]?" "What share classes does [Company] have?" "What's our fully diluted stake in [Company]?" "List shareholders for [Company]" "Who are the shareholders of [Company]?" "Show me the shareholder list" "Who owns [Company]?" "Show me the financing rounds for [Company]" "How much has [Company] raised / what's its post-money?" "Show me the portfolio event history for [Company]" "What certificate activity has [Company] had?" "Has [Company] had any warrant exercises or share class conversions?" | cap-table.md |
"Show me 409a valuation history for [Company]" "What's the fair market value / FMV for [Company]?" | valuations.md |
"Show me new investments made in [year]" "Which investments have the highest MOIC?" "Which portfolio companies have the highest MOIC?" "Which portfolio companies in [Fund] have the highest MOIC?" "Break down [Fund]'s investments by entry round." | investments.md |
"Show me revenue and KPIs for [portfolio company]" "What are the financials for [portfolio company]?" | company-financials.md |
Prerequisites
The user must have the Carta MCP server connected. If this is the first query in the session:
- Call
list_contexts to see which firms are accessible
- Call
set_context with the target firm_id if needed
- For cap table queries — confirm the corporation ID before running. If the user names a portfolio company, resolve its
CORPORATION_ID from CORPORATION_BASIC_INFO_V2 first (see Step 2 table below)
Tool priority (firm context): fa:* MCP commands → dwh__execute__question → semantic-layer SQL (Steps 2–4) → raw dwh__execute__query. Never call cap_table:* or cap_table_chart in firm context — those require a direct tenant role unavailable to investor-portal portcos; use the DWH queries in cap-table.md instead.
Step 0 — Fetch portfolio companies (MANDATORY GATE)
After setting context, always fetch the list of portfolio companies the user has access to:
call_tool({"name": "fa__list__portfolio_companies", "arguments": {}})
Required even for specific-company queries — establishes accessible companies and resolves corporation_id values needed for cap table queries.
- If the result is empty, tell the user their firm context may not be set correctly and call
list_contexts to diagnose.
- If the user asked about a specific company, use the result to resolve the exact
corporation_id for that company before continuing to Step 1.
Step 1 — Try execute:question (PRIMARY query path)
Structural questions ("what tables exist?", "what columns does X have?") skip Steps 1–3 entirely. Go directly to dwh__list__tables (omit schema to list all) or dwh__get__table_schema. Do not run execute:question for schema discovery — it has no visibility into raw table structure and will hallucinate.
Before loading any semantic layer, call the plain-English query interface with the user's question verbatim (or lightly rephrased for clarity):
call_tool({"name": "dwh__execute__question", "arguments": {"question": "<user's question>", "include_links": true}})
Always pass include_links: true — it enriches result rows with _links entries pointing to the corresponding Carta product pages for supported entity UUID fields (see Deep Links below).
If the call succeeds and returns meaningful rows → format and present the results using the General Presentation Rules below. Stop here — do not continue to Steps 2–4.
Fall through to Step 2 when any of the following occur:
- The tool returns an error or exception
- The result set is empty and the user's question implies data should exist
- The returned columns don't match what the user asked for (e.g. wrong metric, wrong granularity)
- The tool indicates it cannot interpret the question or lacks the required data
Do NOT retry execute:question with a rephrased question — fall through immediately.
Step 2 — Identify the Query Domain
Use this table to pick the right context file before running any query:
| User is asking about | Context file to read | Primary table / tool |
|---|
| Available investments or list of portfolio companies | — | call_tool({"name": "fa__list__portfolio_companies", "arguments": {}}) (already run in Step 0) |
| Current NAV, TVPI, DPI, MOIC, cumulative LP contributions/distributions | nav.md | MONTHLY_NAV_CALCULATIONS |
| Fund performance — IRR, DPI, TVPI, dry powder, expense breakdown | fund-performance.md | AGGREGATE_FUND_METRICS (latest), TEMPORAL_FUND_COHORT_BENCHMARKS (as of a past date/quarter-end) |
| Cash flows in a period (contributions, distributions, fees, expenses) | cash-flows.md | JOURNAL_ENTRIES grouped by event_type |
| Balance sheet (assets, liabilities, partners' capital) | balance-sheet.md | JOURNAL_ENTRIES summed by account_type |
| Cap table — share classes, ownership %, firm stake, fully-diluted ownership, shareholders / stakeholders / who-owns prompts (cap-table.md explains the firm-context limitation for shareholder-level data) | cap-table.md | SUMMARY_CAP_TABLE, FUND_CORPORATION_OWNERSHIP (firm context required) |
| Portfolio events — certificate issuance/transfer, conversions, warrant exercises | cap-table.md | NEWSFEED (firm context required) |
| 409a valuations, fair market value, FMV, common stock price | valuations.md | IRC409A_VALUE |
| Investments — cost basis, FMV, MOIC, activity by year, unrealized gain/loss | investments.md | AGGREGATE_INVESTMENTS, AGGREGATE_INVESTMENTS_HISTORY (point-in-time) |
| Per-LP/GP data — commitments, contributions, capital accounts, partner rollforward, LP count | partner-data.md | PARTNER_DATA, PARTNER_MONTHLY_NAV_CALCULATIONS |
| Portfolio company financials — revenue, ARR, headcount, KPIs | company-financials.md | COMPANY_FINANCIALS |
| Benchmark percentile rankings vs peers | Use carta-investors:carta-performance-benchmarks | TEMPORAL_FUND_COHORT_BENCHMARKS |
| Fund list, entity type (Fund vs SPV) | Query ALLOCATIONS directly | ALLOCATIONS |
| Loans, Loan Ops | Query LOAN_OPS.LOAN directly | LOAN_OPS.LOAN |
Step 3 — Load the Context File
Read the matching file from ${CLAUDE_PLUGIN_ROOT}/skills/carta-explore-data/semantic-layer/<domain>.md:
The file contains the SQL query, column reference, and presentation rules for that domain. Follow them exactly.
Cap table prerequisite check — before loading cap-table.md, verify:
- The MCP context is set to a firm (not a fund or LP). Call
list_contexts if unsure.
- A
CORPORATION_UUID is available. If the user named a company, resolve it from CORPORATION_BASIC_INFO_V2 — match by name, UUID, or integer ID depending on what the user supplied:
SELECT DISTINCT CORPORATION_ID AS corporation_integer_id, CORPORATION_UUID, CORPORATION_NAME
FROM FUND_ADMIN.CORPORATION_BASIC_INFO_V2
WHERE LOWER(CORPORATION_NAME) LIKE '%<user-supplied name>%'
OR CORPORATION_UUID = '<user-supplied uuid>'
OR CORPORATION_ID = <user-supplied integer id>
LIMIT 10
If multiple matches are found, use AskUserQuestion to confirm which one before continuing.
- IMPORTANT: if a specific semantic layer was not found, check for Saved Questions by running
call_tool({"name": "fa__list__saved_queries", "arguments": {}}) to get a list of existing questions and descriptions saved on the Data Warehouse. Use call_tool({"name": "fa__get__saved_query", "arguments": {"name": "<query_name>"}}) to retrieve the SQL of a matching saved query, where <query_name> is the name field returned by fa__list__saved_queries.
Step 4 — Execute the Query
MANDATORY pre-query checklist — run for every query, no exceptions:
- Determine the schema from the domain routing table in Step 2: if the table is listed with an explicit schema prefix (e.g.
LOAN_OPS.LOAN), use that schema. Otherwise FUND_ADMIN is the default and most common schema.
- Verify the table exists:
call_tool({"name": "dwh__list__tables", "arguments": {"schema": "<SCHEMA>"}}) — use the schema from step 2. If the target table does not appear in the result, it does not exist — check the wrong→right table name reference in ## SQL Compilation Safety Rules before continuing. Do not query a table that is not listed.
- Verify column names:
call_tool({"name": "dwh__get__table_schema", "arguments": {"table_name": "<TABLE>", "schema": "<SCHEMA>"}}) — use the schema from step 2. Confirm every column you plan to SELECT or filter on appears in the schema with its exact name. Check the wrong→right column name reference in ## SQL Compilation Safety Rules if a column is missing.
Then resolve any remaining uncertainty:
- Unclear intent — ask immediately. If the user's request contains a term that doesn't map to any known domain, table, or Carta concept in the Step 2 table, immediately call
AskUserQuestion with focused options. Do not respond in prose first — go straight to AskUserQuestion.
- Ask up to 2 clarifying questions. If, after checking saved queries (Step 3) and schema inspection, you still cannot identify the right table or domain, use
AskUserQuestion to ask the user at most 2 focused questions — e.g. fund-level vs company-level, metric type, entity name. After receiving answers, re-run Steps 2–3 before querying.
Never assume a table or column name. Every wrong guess produces a Snowflake compilation error visible in production logs.
Use the MCP commands in sequence, substituting <SCHEMA> with the schema determined in the checklist above:
- Browse tables:
call_tool({"name": "dwh__list__tables", "arguments": {"schema": "<SCHEMA>"}})
- Inspect schema:
call_tool({"name": "dwh__get__table_schema", "arguments": {"table_name": "<TABLE>", "schema": "<SCHEMA>"}})
- Run the query:
call_tool({"name": "dwh__execute__query", "arguments": {"sql": "...", "format": "ndjson", "include_links": true}})
Always pass format: "ndjson" and include_links: true on every dwh__execute__query call — ndjson is required for include_links to embed _links objects in each result row. See Deep Links for how to parse the response and use the links.
Output format: Present results as a markdown table. Use fund or company names as row headers — never raw UUIDs. Currency values use $X,XXX format with commas; percentages use X.XX%. Bold totals and summary rows.
General Query Rules
- Always include LIMIT — default
LIMIT 200; use 50–500 for aggregations
- Only SELECT — no INSERT, UPDATE, DELETE, or DDL
- Single SELECT only — no UNION / UNION ALL, no SHOW commands — the tool enforces one SELECT at a time;
SHOW TABLES LIKE '%...' and other SHOW * commands also return Only a single SELECT statement is allowed. Use call_tool({"name": "dwh__list__tables", ...}) for table discovery and run separate call_tool calls when you need counts from multiple tables.
- Do not query
INFORMATION_SCHEMA — it is not supported in this data warehouse and returns a hard ValueError: Querying INFORMATION_SCHEMA is not allowed. Use call_tool({"name": "dwh__list__tables", ...}) to list tables and call_tool({"name": "dwh__get__table_schema", ...}) to inspect columns. These MCP tools are the only valid schema-discovery path.
LATERAL (including LATERAL FLATTEN) is not permitted — returns ValueError: Lateral is not permitted in query execution. To access keys in a VARIANT/ARRAY column, use explicit JSON path notation (e.g. col:key::STRING) rather than LATERAL FLATTEN.
- Date fields —
effective_date for JOURNAL_ENTRIES; month_end_date for MONTHLY_NAV_CALCULATIONS; investment_date for AGGREGATE_INVESTMENTS
- Deduplication — for
MONTHLY_NAV_CALCULATIONS and AGGREGATE_FUND_METRICS, use QUALIFY ROW_NUMBER() OVER (PARTITION BY fund_uuid ORDER BY last_refreshed_at DESC) = 1
- ALLOCATIONS has multiple rows per fund — always
GROUP BY fund_uuid with MAX(fund_name) when using it for fund metadata
SQL Compilation Safety Rules
- Always schema-qualify tables:
FUND_ADMIN.TABLE_NAME (or LOAN_OPS.TABLE_NAME for loans). A bare name defaults to PUBLIC where no customer tables exist.
- Only query schemas visible in
dwh__list__tables: never query a schema that does not appear in that tool's output — unrecognized schemas are either internal-only or non-existent and will always fail.
dwh__execute__query does NOT accept a schema argument — the schema is encoded directly in the SQL as SCHEMA.TABLE_NAME. Never pass "schema" inside the arguments dict.
set_context takes firm_id as a UUID string — pass the UUID value returned by list_contexts, not a bare integer.
- Use
fund_uuid (VARCHAR), not fund_id — the integer fund_id is internal-only and not available in customer-facing views.
- Snowflake syntax only:
LIMIT N not FETCH FIRST N ROWS ONLY; LIKE/RLIKE not SIMILAR TO; ROW_NUMBER() OVER (...) not bare ROW(); DATE_TRUNC not ROUND on dates; UUID values are strings (fund_uuid = '<uuid>').
- Wrong → right table names — if the user or context uses any name on the left, use the right instead:
| ❌ Do NOT query | ✅ Use instead |
|---|
FUND_NAV / NAV_HISTORY | MONTHLY_NAV_CALCULATIONS |
FUND_METRICS / FUND_PERFORMANCE_SUMMARY / FUND_PERFORMANCE_METRICS / FUND_PERFORMANCE | AGGREGATE_FUND_METRICS |
CAPITAL_CALLS / FUND_CAPITAL_CALLS | CAPITAL_ACTIVITIES |
INVESTMENTS (bare) | AGGREGATE_INVESTMENTS |
PORTFOLIO_COMPANIES | call_tool({"name": "fa__list__portfolio_companies"}) — not a queryable table |
FINANCIAL_STATEMENTS / FINANCIALS / PROFIT_AND_LOSS / KPIS / PORTFOLIO_KPIS | COMPANY_FINANCIALS (KPIs) or JOURNAL_ENTRIES (P&L) |
INVESTORS_PARTNER | PARTNER_DATA |
FUNDADMIN_DATASHARE_* (with full dbt prefix) | Use short name: e.g. MONTHLY_NAV_CALCULATIONS |
- Wrong column names: Domain-specific corrections are in each semantic layer file's
⚠️ Common Mistakes section. Always run dwh__get__table_schema to verify column names before querying. Cross-domain shortcuts that frequently produce invalid identifier errors:
| ❌ Do NOT use | ✅ Use instead | Table |
|---|
NET_IRR / IRR | net_lp_irr (LP net) or deal_irr (gross) | AGGREGATE_FUND_METRICS |
PRICE_PER_SHARE | ORIGINAL_ISSUE_PRICE | FINANCING_HISTORY |
AMOUNT_RAISED | ESTIMATED_CASH_RAISED or CALCULATED_CASH_RAISED | FINANCING_HISTORY |
HEADQUARTERS_CITY / HEADQUARTERS_STATE / HEADQUARTERS_COUNTRY | CITY / STATE / COUNTRY | CORPORATION_BASIC_INFO_V2 |
LEGAL_NAME / NAME / COMPANY_NAME | CORPORATION_NAME | CORPORATION_BASIC_INFO_V2 |
TRANSACTION_DATE / POSTING_DATE / ENTRY_DATE | effective_date | JOURNAL_ENTRIES |
OWNERSHIP_PERCENTAGE / OWNERSHIP_PCT | PERCENTAGE (TEXT — cast with TRY_TO_DECIMAL) | FUND_CORPORATION_OWNERSHIP |
OUTSTANDING_QUANTITY | OUTSTANDING_SHARES | SUMMARY_CAP_TABLE |
BOOL_OR(col) | BOOLOR_AGG(col) | (any table) — Snowflake has no BOOL_OR |
SHARE_CLASS_NAME | SHARECLASS_NAME | FINANCING_HISTORY — one word, no underscore between SHARE and CLASS |
rows / ROWS (as a column alias) | any other alias (e.g. row_count, cnt) | (any table) — ROWS is a Snowflake reserved word; using it as a column alias causes syntax error unexpected 'ROWS' |
DWH Tool Invocations — Exact Forms Required
Use call_tool with these exact double-underscore names. Any other form (colon syntax, single underscores, direct tool invocations) returns NotFoundError: Unknown tool.
| Task | Exact invocation |
|---|
| Run SQL | call_tool({"name": "dwh__execute__query", "arguments": {"sql": "SELECT ...", "format": "ndjson", "include_links": true}}) |
| Natural-language question | call_tool({"name": "dwh__execute__question", "arguments": {"question": "...", "include_links": true}}) |
| List tables in a schema | call_tool({"name": "dwh__list__tables", "arguments": {"schema": "FUND_ADMIN"}}) |
| Get a table's columns | call_tool({"name": "dwh__get__table_schema", "arguments": {"table_name": "TABLE_NAME", "schema": "FUND_ADMIN"}}) |
dwh__execute__query key is sql (not query). format: "ndjson" and include_links: true are mandatory on every call — ndjson is required for _links to be embedded in rows; without it, link data is lost.
dwh__execute__question keys: question (required) and include_links (optional). Do not pass sql, fund_uuid, firm_uuid, format, or any other key.
- JSON keys with spaces in VARIANT columns —
col:'Key With Spaces' and col["Key With Spaces"] both fail with SQL compilation error. For keys containing spaces, use escaped inner quotes: col:'"Key With Spaces"'::STRING. This applies to AGGREGATE_INVESTMENTS.TAGS_JSON and any other VARIANT column with spaced key names.
ORDER BY with SELECT DISTINCT — columns used in ORDER BY must also appear in the SELECT list when using DISTINCT; otherwise Snowflake raises is not a valid order by expression.
Deep Links
include_links: true is always required (see table above). It adds a _links entry to each row for supported entity UUID columns:
include_links adds a _links entry to each row for supported entity UUID columns. Supported fields and their requirements:
| Column | Links to | Requires |
|---|
journal_entry_gluuid | Journal entry page | fund_uuid column in result |
journal_entry_line_id | Journal tab | fund_uuid column in result |
asset_id | Investments tab | fund_uuid column in result |
partner_interest_group_id | Partners tab | fund_uuid column in result |
entity_link_id | Portfolio company page | fund_uuid or firm_carta_id column in result |
issuer_entity_link_id | Portfolio company page | fund_uuid or firm_carta_id column in result |
Always include the resolver column(s) in every SELECT — _links is silently empty without them:
- Include
fund_uuid whenever the result contains journal_entry_gluuid, journal_entry_line_id, asset_id, or partner_interest_group_id
- Include
fund_uuid or firm_carta_id whenever the result contains entity_link_id or issuer_entity_link_id
- Include these columns even when you don't display them to the user
Using _links: when a row has a _links entry, hyperlink the entity's display name (fund name, company name, LP name) to row["_links"][field]["web_url"]. Use the value verbatim — never reconstruct or guess URLs.
General Presentation Rules
Each semantic file's ## Presentation section is the source of truth for its domain. When a semantic file does not specify, fall back to these defaults:
- Render results as a markdown table with clear column headers
- Use names, never raw UUIDs as row identifiers — fund name, company name, LP name
- Currency —
$X,XXX with commas; negatives/outflows in parentheses ($X,XXX); bold totals **$X,XXX**
- Percentages —
X.XX%
- Multiples —
X.XXx (e.g. MOIC, TVPI, DPI)
- Missing values — show
— rather than 0 or null to avoid implying a real zero
- Use Carta voice — "your fund's NAV", "your portfolio", not "query results"
Terms
| Acronym | Definition |
|---|
| NAV | Net Asset Value |
| TVPI | Total Value to Paid-In |
| DPI | Distributions to Paid-In |
| IRR | Internal Rate of Return |
| MOIC | Multiple on Invested Capital |
| FMV | Fair Market Value |