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.
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.
ollamaprovider exists but is dev/test only, gated behindCORVUS_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-serveradvertises 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. Onlypg_execute_queryis allowlisted; its own handler independently rejects anything that isn'tSELECT/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":
{
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 touchingkpi/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:
Hetzner Wave 1, Model Gateway, MCP host, Tool Registry, reasoning loopβ done, verified viatest:integrationandsmoke:kpi-selection.- Auth / real multi-tenancy (SDE) β the one blocker left before "ship to 5 design partners."
- Entity Catalog schema + migration β backfill the existing
dealsentity; zero behavior change, just structure (Β§7.3). - Metric Registry additions β versioning, synonyms, mandatory descriptions on the 6 seeded KPIs (Β§7.4).
discover_metricstool β the actual reasoning-loop change (Β§7.5).- Golden eval set, wired into CI β before KPI #7 is added, not after (Β§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-layerskill 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.