# Corvus — Platform Blueprint v2.0 *Corvus · Platform Blueprint v2.0 · August 2026 · Confidential* *Solo-track-owned document — AI Engineer reference, shared with SDE co-founder* **This version supersedes v1.0 (June 2026).** Where they disagree, this document reflects what was actually built and decided since — not what was originally planned. Section 0 explains what changed and why, so nothing here reads as a silent reversal. --- ## 0. What changed since v1.0, and why v1.0 was written pre-code, as a complete company blueprint. Since then Corvus went through a real build (`PLAN.md`, Wave 1), a code review (`docs/code-review.md`, 28 verified findings), and a scoping pass specifically on the AI-reasoning side (§7 below). The result is a narrower, more honest v2. Nothing in the pivot changes the core thesis — it changes the plan for reaching it. | v1.0 said | v2.0 says | Why | |---|---|---| | Salesforce + HubSpot + CSV connectors, bespoke MCP servers you write | **MCP-as-connectors**: reuse existing community MCP servers (Postgres-MCP first) | Writing and maintaining bespoke connectors was the single biggest scope item in v1.0. Existing MCP servers exist for most of this; Corvus's job is to be a good MCP *host*, not a connector author. | | Railway / Fly.io → AWS at scale | **Self-host on Hetzner, staged in waves** | Chosen deliberately over "managed-first." Cheaper, and data-sovereignty positioning (§4) is stronger when Corvus's own infra is self-hosted, not merely BYOK at the model layer. | | 6-layer stack incl. ClickHouse, S3, Vault, ~12 services from day one | **4 containers at MVP** (Postgres+pgvector, Redis, app, Caddy); ClickHouse/observability/MinIO are Wave 3 | Standing up 12 services before a product exists was identified as the exact trap the original blueprint warned against in its own risk section. Staged waves fix that. | | Subagent orchestration, hooks system, episodic/semantic memory store | **Deferred, not cancelled.** Wave 1 ships one bounded reasoning loop (`ask.ts`, `MAX_TURNS = 8`) | Anthropic's own finding (§7.1) and Corvus's own build experience agree: the loop itself should stay simple. Complexity earns its place once the semantic layer under it is solid — not before. | | CORVUS.md as a literal config file the harness parses at runtime | **CORVUS.md as this document** — a human/AI-readable blueprint, not a runtime artifact | The functional equivalent — the tenant's actual business definitions — now lives in the **Metric Registry** and **Entity Catalog** (§7.3–7.4), which is queryable and versioned, not a markdown file the harness re-parses per request. | | "Cursor for business data," CFO/audit/compliance scope included | **Narrower**: sales/ops-facing BI, compliance explicitly cut | Kept from v1.0 — this one didn't change. Compliance-grade reporting has different liability and audit requirements than an ops manager asking about win rate; conflating them was correctly identified as scope creep. | | One data source, live-call only | **Same for MVP** — canonical-table sync (a local warehouse) is explicitly Wave 2, gated on the Insight Feed needing it | Confirmed still correct: live-call MCP is fine for ad-hoc queries; it breaks for scheduled background scans and heavy aggregation, so sync is deferred until something actually needs it. | The one-paragraph version: **v1.0 was the vision, correctly ambitious for a company document. v2.0 is what two engineers can actually build and defend, in an order that proves the trust story (§1) before anything else.** --- ## 1. What Corvus is Corvus is a **multi-tenant business-intelligence harness**: a thin web app in front of a harness core that takes a plain-English question from a business user — a sales VP, an ops manager, not a developer — figures out what data it needs, fetches **verified** numbers from the company's connected systems, and returns an answer with **citations and a confidence level**. **Tagline:** *Your data. Your model. Total clarity.* **Positioning:** "Cursor for business data" — a serious professional tool a business user can trust in front of leadership, in the register of Linear, Vercel, Stripe, or Hex. Not a consumer AI chatbot. **The one non-negotiable rule**, unchanged since v1.0 and the thing everything else in this document exists to protect: > **The AI model never computes numbers.** It calls a tool; the tool — deterministic, > tested code — returns a verified number; the model only explains it. A wrong number costs Corvus the customer instantly. Every architectural decision below is in service of never presenting an unverified number as if it were verified. --- ## 2. The one loop that matters > User connects **one** data source → asks a question in plain English → > gets a **correct answer with a citation to the source rows** + a confidence level. Everything else in this document is addition. This loop already works end-to-end in the current build (`askCorvus()` in `src/harness/ask.ts`). Ship it to design partners, let their real questions decide what's next. ```mermaid sequenceDiagram participant U as User (browser) participant R as /api/ask route (SDE) participant A as askCorvus() — ask.ts (AI) participant G as callModel() — gateway (SEAM) participant T as Tools — registry.ts (AI) participant S as Semantic Layer (AI, NEW — §7) participant C as MCP connectors (SDE) U->>R: "Which products are about to run out of stock?" R->>A: askCorvus(tenantId, question) A->>A: assemble tools = built-in + discover_metrics + MCP-discovered A->>G: callModel({tenantId, messages, tools}) G-->>A: "I need to call discover_metrics, then calculate_kpi" A->>S: discover_metrics(query) → governed metric match A->>T: runTool(tenantId, call) T->>C: (if raw query) callMcpTool → real DB query C-->>T: real rows T-->>A: ToolResult { value, citation } A->>G: feed tool results back, call model again G-->>A: (repeat until no more tool calls) A-->>R: { answer, citations, confidence } R-->>U: rendered answer + "show me the data" ``` --- ## 3. The stack, as built ``` Layer 1 Experience Next.js web app (query box + answer + "show me the data") Layer 2 API + Auth Next.js API routes · Better Auth (multi-tenant) · JWT Layer 3 Harness Core Model Gateway · MCP Host · Tool Registry · Semantic Layer (NEW) Layer 4 Connectors Existing MCP servers (Postgres-MCP first; platform MCPs as needed) Layer 5 Data PostgreSQL + pgvector · Redis (canonical tables added when sync is needed) Layer 6 Infra Hetzner VPS · Docker · Caddy (auto-TLS) ``` **Layer 3 is the moat**, same as v1.0 said — that part never changed. What changed is *what's in it*. v1.0 imagined Context Engine + Semantic Layer + Agent Orchestrator + Hooks + Memory as parallel systems built together. What's actually true a build cycle later: the **Semantic Layer is the load-bearing piece**, and it should be built deliberately, on top of a loop that's already proven simple and correct. §7 is the current, concrete design for it — this is the piece v1.0 gestured at ("Business Semantic Layer... build alongside first connector") but never fully specified. ### Model Gateway (BYOK) Unchanged in principle from v1.0, real and shipped in code: - `callModel(tenantId, messages, tools) → ModelCallResult` — the single door every provider call goes through (`src/harness/gateway/`). - **BYOK is architectural, not a feature.** A tenant's own Anthropic/OpenAI key, encrypted at rest (AES-256-GCM, HKDF per-version keys), decrypted only inside the gateway. One managed default (Claude) for tenants without their own key. - This remains the enterprise sales unlocker v1.0 identified: BYOK removes the procurement blocker, honors data-processing agreements for sensitive-data customers, and means Corvus never becomes obsolete as models improve — a tenant upgrades their own key. - `ollama` provider exists but is dev/test only, gated behind `CORVUS_DEV_PROVIDER=ollama`, never in a production code path. ### MCP Host — Layer 3 as MCP *client*, Layer 4 as connectors This is the single biggest architectural pivot from v1.0, and it's a simplification, not a compromise: - **What it buys:** no bespoke Salesforce/HubSpot/Odoo API integration to write or maintain. The model calls MCP tools through a standard interface. Corvus is architecturally the host/harness; MCP servers are the tool layer — exactly the positioning v1.0 wanted, achieved with far less code. - **What it doesn't solve** (still true, still designed around): MCP servers are thin API wrappers returning raw data, not canonical schema — the semantic layer (§7) is still Corvus's to build. Most are live-call, not synced — fine for ad-hoc queries, blocks aggregation-heavy analytics and the Insight Feed until Wave 2 adds a sync layer. Corvus inherits each server's maintenance quality and auth model — vet, pin versions, keep a fallback. - **The shortcut that made Postgres-first correct:** the most mature MCP server in existence is the Postgres MCP server, and self-hosted Odoo — common among Arab SMBs, and among Corvus's own early prospects (BeExpress, Sweet&Fit) — runs on Postgres. Point a read-only Postgres MCP server at the Odoo DB and skip the Odoo API entirely for MVP. - **Security posture, hard-won and non-negotiable:** the community `@henkey/postgres-mcp-server` advertises 18 tools including arbitrary SQL and mutations, and does **not** block them without extra config — verified directly against a live instance, not assumed from docs. Corvus enforces its own tool allowlist at the host layer (`mcp/host.ts`) rather than trusting a connector's internal safety switch. Only `pg_execute_query` is allowlisted; its own handler independently rejects anything that isn't `SELECT`/`WITH`. This same don't-trust-the-connector discipline applies to every future MCP server added. ### Tool Registry — the trust primitive, unchanged `query_source`, `calculate_kpi`, `compare_periods` — three deterministic tools. The model never does math; tools return verified numbers with citations, the model explains them. This is the actual moat, and it hasn't moved since v1.0 first stated the rule. --- ## 4. What makes it defensible Carried forward from v1.0, still the correct list, now field-tested: - **The model never computes numbers** — only tested, deterministic code does. Competitors letting a model estimate a number are one hallucination from losing a customer's trust. - **Every answer is cited** — a user can always see the underlying rows, not just trust a black box. `confidence` (`high`/`medium`/`low`) is scored honestly: no tool calls, a tool error, or hitting the reasoning-loop turn cap all suppress `"high"` — the system tells the truth about its own uncertainty rather than presenting every answer with false confidence. - **BYOK** — cost control and data-handling comfort at the enterprise end; see §3. - **Multi-tenant from day one** — every table has `tenant_id`; every query is scoped. - **Data sovereignty**, elevated since v1.0: self-hosting Corvus's own infra (not just BYOK at the model layer) is the strongest differentiator against platform-native agentic BI (Oracle, SAP, Microsoft Fabric) and semantic-layer incumbents (Snowflake Cortex, Databricks, Looker/Gemini) — none of which offer a cross-platform, self-hosted, BYOK harness. This was validated in a competitive pass after v1.0 shipped: the "connect data to AI" premise is increasingly covered by incumbents; sovereignty over both the model *and* the infrastructure isn't. --- ## 5. Who it's for Unchanged from v1.0: operations- and sales-facing professionals at small/medium businesses running a self-hosted, database-backed system (Odoo is the anchor case), who currently wait on an analyst or don't ask the question at all because the friction is too high. Validate with a small number of design partners before expanding sources or customer base — v1.0's own instinct here ("talk to 20 business users first... the three most painful questions they can't answer, that's your MVP scope") held up and shaped the Wave 1 cut directly. Global-first positioning, Arab market as the geographic wedge — not an Arab-only product. Odoo depth + Arabic-language handling is the defensible niche *within* a global addressable market, not the whole market. --- ## 6. Co-founder split Unchanged in spirit from v1.0; updated to match what the codebase's own seam (`src/harness/types.ts`) now encodes formally rather than just describing: **AI Engineer (you) owns:** - The reasoning loop (`ask.ts`) and confidence scoring - The Tool Registry (`tools/registry.ts`) — specs + dispatch, deterministic bodies - The Semantic Layer (§7 — Entity Catalog, Metric Registry, `discover_metrics`) - Provider SDK integration inside the gateway (`gateway/providers/*.ts`) - The eval suite (§7.5) **Software Engineer (co-founder) owns:** - Auth, multi-tenancy, the encrypted key vault, usage logging - The MCP host's connection/transport machinery (`mcp/host.ts`) and each connector's launch/allowlist definition (`mcp/connectors/*.ts`) - Database, migrations, infra (Docker, Caddy, Hetzner) - The web app shell and every route around `askCorvus()` **Together:** the seam itself (`types.ts` — `callModel`, `askCorvus`, `ToolResult`, `Citation`) — v1.0's "shared schema" instinct was right; what changed is that it's now a committed TypeScript file both tracks build against and can't change solo, not a conceptual agreement. Same discipline applies to any future shared file (flagged explicitly in `docs/ai-track-guide.md §8` — "Don't touch" isn't a wall, it's "that's a conversation, not a solo edit"). --- ## 7. The Semantic Layer — the piece v1.0 gestured at, specified for real This is new since v1.0 and is the direct answer to two questions asked while researching what Corvus still needs on the AI side: how Anthropic's own data team gets self-service analytics to ~95% automated accuracy, and how WrenAI's open "context layer" makes text-to-SQL governed instead of merely plausible. ### 7.1 The finding Corvus already has the right foundation: the model never computes, tools do, every answer is cited. What's missing is the layer that tells the model **what's queryable in the first place** before it calls a tool — and this is not a hypothetical gap. Anthropic's internal data-science team frames analytics accuracy as *"a context and verification problem, not a code generation issue,"* and names three failure modes: **concept↔entity ambiguity**, **staleness**, and **retrieval failure**. Independently, dbt Labs' 2026 benchmark found semantic-layer grounding lifts text-to-SQL accuracy from 90.0%→98.2% and 84.1%→100% across two frontier models, and that **schema/semantic failures — not syntax — account for ~81% of text-to-SQL errors**. WrenAI's whole premise (an open "context layer" — governed models, definitions, and memory beneath generation) is the open-source expression of the same finding. Corvus's current `kpi/schema.ts` allowlist (`ENTITIES`, `fields`, `numericFields`) is **already a primitive semantic layer** — it's just one entity deep (`deals`) and disconnected from how the model discovers what exists (KPI names are currently a string baked into a tool description). This already broke once, concretely: the model hallucinated `pipeline_coverage`, a KPI that was never real, because nothing told it otherwise. The fix is to grow the existing primitive, not build something new beside it. ### 7.2 Shape of the change ``` BEFORE (Wave 1) AFTER (v2) ───────────────── ────────── ask.ts ask.ts └─ buildTools(tenantId) └─ buildTools(tenantId) └─ 6 hardcoded KPI names └─ Semantic Layer as a string in a ├─ Entity Catalog (grows ENTITIES tool description │ into a real, browsable model) ├─ Metric Registry (grows KpiDefinition │ into versioned, governed metrics) ├─ discover_metrics tool (NEW) └─ Business Glossary / synonyms (NEW) ``` The seam does not change. This is additive: a layer between `buildTools()` and the model, plus one new tool that lets the model *ask what exists* instead of Corvus guessing what to cram into a description string. ### 7.3 Entity Catalog Extends `EntitySpec` (not a rewrite — the validation logic in `kpi/schema.ts` stays): `displayName`, `description`, `grain`, `fieldDescriptions`, approved `relationships` only, and `source: { connectorId, syncedAt }` for provenance. Hand-authored or Claude-drafted-then-human-approved — **never model-inferred at query time**. Anthropic tried auto-generating definitions with an LLM from raw tables; it *"produced plausible-looking definitions that encoded the very ambiguities [they] were trying to eliminate"* — net negative on their evals. Generate the documentation with Claude; a human owns the definition. ### 7.4 Metric Registry Grows `KpiDefinition` + `kpi/engine.ts` — same structured formula, same allowlisted, parameterized execution, no free-form SQL, ever. Adds: mandatory `displayName`/ `description` (the only thing the model sees when discovering a metric), append-only **versioning** (editing `win_rate` creates `win_rate@2`, doesn't mutate `win_rate@1` — the direct fix for the staleness failure mode), `synonyms[]` populated from real question logs, and `asOf` freshness carried into `Citation` — the smallest version of Anthropic's "provenance footer" that fits Corvus's existing types. ### 7.5 `discover_metrics` — the highest-leverage single change Replaces cramming every KPI name into `calculate_kpi`'s description. Modeled on WrenAI's discover→select→execute flow and Anthropic's "agent is structurally required to hit the semantic layer first": ```ts { name: "discover_metrics", description: "Search the tenant's governed metric catalog by keyword or business concept " + "before calculating anything. ALWAYS call this before calculate_kpi if you are " + "not certain a metric name is exact — do not guess a plausible-sounding name.", inputSchema: { type: "object", properties: { query: { type: "string" } }, required: ["query"] }, } ``` Keyword/synonym match over the Metric Registry — same shape as Mercer's BM25+LSH entity retrieval, smaller corpus, no vector DB needed at Corvus's current scale. `calculate_kpi`'s description shrinks back to static; one line added to `ask.ts`'s system prompt: *"Always try discover_metrics or an existing KPI before calling query_source directly."* This is what lets Corvus scale past a handful of KPIs without the tool description growing unboundedly. ### 7.6 What this explicitly does not include yet Matching the same "write it down so it stops nagging you" discipline `PLAN.md` uses: no vector DB (Anthropic's own ablation found raw-corpus retrieval moved accuracy *less than a point*, even with the right answer present and read); no adversarial- reviewer sub-agent (+6% accuracy for +32% tokens/+72% latency — a real lever, wrong priority before a design partner); no dashboard generation / WrenAI's "Deploy" beat; no cross-connector joins; no automated correction-harvesting agent — a manual weekly `QueryLog` review does this until question volume justifies automating it. ### 7.7 Validation — the part that doesn't exist yet Today Corvus has one signal the reasoning loop works: a 6-question smoke test run once. Minimum viable eval, sized correctly for a two-person team: - **~20–30 golden Q&A pairs per tenant/domain** (diminishing returns past a few dozen per topic, and that ceiling drops with each model generation — don't over-invest). - **Pinned to seeded, deterministic data**, never live — the exact trap Anthropic calls out: *"an eval written against live data goes stale the moment the underlying number moves."* - **Wired into `test:integration`**, not optional — any PR touching `kpi/schema.ts`, `kpi/engine.ts`, or the semantic layer re-runs it. - **Assert confidence tiers, not just correctness** — a wrong-but-confident answer is the actual product risk Corvus exists to prevent; the eval set should test for that directly, not just for the right KPI name. ### 7.8 Security posture — extends, never relaxes, the existing discipline `discover_metrics` and the Entity Catalog are read-only, tenant-scoped metadata lookups — same `tenant_id` scoping as every query in `kpi/engine.ts`, no new attack surface. Governance stays **compile-time**: `validateConditions` runs before any SQL is built, re-checked even for a stored definition — the Entity Catalog must preserve that ordering, never introduce a "generate SQL, then check" path. `query_source` remains the only raw-query tool, still routed through the same MCP allowlist discipline as §3's MCP Host section. Growing the catalog makes `query_source` *safer* to use — it does not add a new way to reach the database. --- ## 8. Build sequence Where §0's cut narrowed *what* to build, this is *what order*, current as of this version: 1. ~~Hetzner Wave 1, Model Gateway, MCP host, Tool Registry, reasoning loop~~ — **done**, verified via `test:integration` and `smoke:kpi-selection`. 2. **Auth / real multi-tenancy** (SDE) — the one blocker left before "ship to 5 design partners." 3. **Entity Catalog schema + migration** — backfill the existing `deals` entity; zero behavior change, just structure (§7.3). 4. **Metric Registry additions** — versioning, synonyms, mandatory descriptions on the 6 seeded KPIs (§7.4). 5. **`discover_metrics` tool** — the actual reasoning-loop change (§7.5). 6. **Golden eval set**, wired into CI — before KPI #7 is added, not after (§7.7). 7. **Ship to 5 design partners.** Their questions decide the second data source, which KPIs get added next, and which synonyms the Metric Registry actually needs — not a roadmap guess. **Not now** (unchanged from `PLAN.md`, still correct): microservices, ClickHouse, self-hosted observability, MinIO, the Insight Feed, a Chrome extension, a Slack bot, multi-platform joins, 3+ connectors, Hijri calendar, Arabic generation, a Report Builder, a public API, Stripe/billing. --- ## 9. Risks worth tracking Carried forward from v1.0, amended where the semantic layer changes the picture: - **A wrong answer is an instant-churn event** — unchanged as the central design constraint. The semantic layer (§7) is a direct mitigation, not a new risk: it closes the concept↔entity ambiguity gap that's the *dominant* real-world failure mode for this category of system (~81% of text-to-SQL errors, per §7.1). - **Semantic layer maintenance becomes its own ongoing cost** as KPI/entity count grows — this is the trade Anthropic's team also made, and their mitigation (colocate definition + doc changes in the same PR, enforced by review discipline) is adopted directly in the `corvus-semantic-layer` skill referenced in §7. - **MCP server quality/maintenance** — still not fully in Corvus's control. Mitigate: vet + pin versions, keep the CSV/Postgres fallback, never trust a connector's internal safety switch (§3). - **No local warehouse while live-call MCP is the only path** — blocks aggregation-heavy analytics and the Insight Feed. Mitigate: canonical-table sync is explicitly Wave 2, gated on having a concrete reason to build it. - **Self-host ops tax** — every service is a potential 2am page. Mitigate: staged waves (§0), automated backups, no service added without a reason. --- *Corvus · Platform Blueprint v2.0 · August 2026 · Confidential* *Reflects the actual state of the build as of Wave 1 completion + the semantic-layer scoping pass. Supersedes v1.0 in full; v1.0 remains available as historical record of the original company-level vision.*