Open context layer · MCP-native

AI agents that query your data correctly.

canonic sits between your data stack and your agents: an auto-built, git-versioned description of what your data means and how to query it safely. No guessing. And when it isn't sure, it refuses and asks.

Get started → View on GitHub
uv tool install canonic
The problem

Schema access makes an agent fluent. It doesn't make it correct.

An agent pointed straight at your warehouse sees tables and columns, not meaning. So it guesses, and a confident, well-formatted, wrong number is the worst kind of wrong. Real output from a live run:

✗ WITHOUT CANONIC: THE GUESS
$ canonic sql "SELECT SUM(amount) FROM fct_orders"

  sum
 ─────────
  4050.50
Includes two refunded orders ($260). Refunds are reversals, not revenue. This number is off by 6.4%.
✓ WITH CANONIC: THE TRUTH
$ canonic query --metrics revenue

  total_revenue
 ───────────────
  3790.50

  ▸ resolved: revenue → orders.total_revenue
  ▸ guardrail: revenue-excludes-refunds
"revenue" resolves to its canonical definition; the guardrail is compiled into the SQL either way. Zero guessing.
Three layers, plain files

Context that lives in your git repo, reviewed like code.

semantics/**/*.yaml

Semantics

"How do I query this safely?" Tables, types, grains, joins, measures: the executable semantic layer.

auto-maintained
knowledge/**/*.md

Knowledge

"What does this mean to the business?" Definitions, caveats, policies: searchable prose a human can trust.

auto-maintained
contracts/**/*.yaml

Contracts

"Which definition is canonical, and what must the answer obey?" Metrics, guardrails, assertions.

human-owned

A knowledge page explains why "amount includes refunds unless filtered." A contract makes the SQL obey it. Documented caveats become enforced guardrails.

What canonic does

Correct definitions, correct aggregation, and the proof attached.

contracts

Canonical definitions, enforced

An agent asks for a metric by name. canonic resolves it to the one agreed definition and compiles the SQL. Guardrails such as mandatory filters, required dimensions, source restrictions and minimum trust are part of the contract, so a documented caveat can't be skipped.

compiler

The right aggregation

Semi-additive measures, ratios recomputed at the requested grain, distinct counts, percentiles and running totals each get their own strategy. Joins that would fan out a fact table are caught, and when several join paths are valid canonic asks instead of guessing.

trust

Answers carry their trust

Every result ships its definition, the guardrails that fired, freshness, final vs. provisional rows, and a trust tier with the reasons behind it. Assertions gate CI, and confirmed-wrong outcomes lower a metric's trust.

trustedprovisionalcaution
ingest

The context builds itself

Ingest drafts semantics from your live schema, a dbt manifest or an Apache Ossie model, and learns from Looker and Metabase usage, Notion pages and web docs. Every change is a reviewable diff, and drift is flagged.

knowledge

Business knowledge for agents

Definitions, policies and caveats live as Markdown pages. Agents search them, read them with live-rendered definitions, and get the relevant caveats attached to an answer.

errors

Refuse and ask

Ambiguous or unsafe questions come back as a structured reason, such as an ambiguous join path or a blocked guardrail, that the agent can act on. Never as a guess.

What an agent gets back

The number, and why to believe it.

Real output, abridged, from canonic query --metrics gross_revenue in the saas-analytics example.

  • Which definition answered, and the SQL it compiled to.
  • Which guardrails shaped the query.
  • Why the number is only provisional, so the agent can say so.
{
  "result": { "rows": [["77071.00"]] },
  "compiled": {
    "sql": "SELECT SUM(\"fct_invoices\".\"amount\") AS \"gross_revenue\"
            FROM \"fct_invoices\" AS \"fct_invoices\"
            WHERE \"fct_invoices\".\"status\" <> 'refunded'
              AND \"fct_invoices\".\"is_trial\" = FALSE",
    "dialect": "duckdb"
  },
  "metadata": {
    "resolved": { "metrics": { "gross_revenue": "fct_invoices.total_amount" } },
    "guardrails_fired": [
      { "id": "revenue-excludes-refunds", "kind": "mandatory_filter" },
      { "id": "revenue-excludes-trials", "kind": "mandatory_filter" }
    ],
    "freshness": [{ "source": "fct_invoices", "stale": false }],
    "trust_score": {
      "tier": "provisional",
      "reasons": ["gross_revenue: assertion unverified"]
    }
  }
}
What you can rely on

Built to never produce a confidently-wrong answer.

⏢

Read-only by design

canonic never mutates your warehouse. It reads. There is no write path back.

±

Propose-only

It never silently edits your context. Every change is a reviewable diff anchored to evidence.

?

Refuse-and-ask

Ambiguous or unsafe? It returns a structured reason, not a guess.

≡

No LLM in the answer path

Queries compile deterministically: the same question always produces the same SQL. An LLM only drafts context, never computes an answer.

⌂

Local-first & air-gapped

Run entirely on your machine with a local model and local embeddings. Nothing has to leave your network.

%

Measurable trust

A local event log tracks accuracy, freshness, and answer quality. "Trustworthy" is something you can check.

Tenancy & access control

One deployment. Every tenant sees only its own truth.

Multi-tenant isolation and role-based access are opt-in policies, not application code. Add a contract, and every query, yours or an agent's, is scoped before it runs.

contracts/policies/tenancy.yaml

Tenancy

Row-level isolation enforced by compiling a tenant predicate into every joined source, not just the table in the query. The tenant comes only from a verified Principal token, never from anything an agent or LLM supplies.

contracts/policies/roles.yaml

Roles

Role-based access to metrics, dimensions, and knowledge tags, with column-level masking (null, hash, partial). A denied lookup looks identical to a nonexistent one, so there's no existence oracle for an agent to probe.

Enforcement happens inside the deterministic compiler, not as a bolt-on filter, and every answer logs its tenant, roles, and exemptions for audit.

Why canonic, not something else

It feeds the stack you already have. It doesn't replace it.

Alternative
What goes wrong
With canonic
Raw schema / SQL access for the agent
Fluency without correctness; it guesses definitions and picks the wrong tables.
Resolved canonical definitions, enforced guardrails, never a silent wrong number.
Hand-building a semantic layer
Months of modeling before any value.
Context auto-drafted from your live schema on day one; you review, not author from zero.
Adopting yet another metrics platform
Definitions you already wrote in dbt / MetricFlow get re-modeled by hand.
canonic ingests existing semantic layers as evidence. Your dbt models become context, not a rebuild.
A hosted "AI analytics" SaaS
Your data and definitions leave your environment.
Local-first, fully air-gapped-capable: nothing has to leave your machine.
In one line: canonic builds the context for you, keeps it honest, and refuses to lie when it isn't sure.
Works with your stack

Your warehouse, your modeling, your agent.

Warehouses, read-only
PostgresRedshiftMySQLSnowflakeDatabricksClickHouseSQLiteDuckDB (incl. CSV and Parquet)
Definitions
dbtApache Ossie
Usage and docs as evidence
LookerMetabaseNotionWeb pages
Agents (MCP)
Claude CodeCursorCodexAny MCP client
Try an example

Seven ready-to-run projects, no database required for most.

jaffle-shop ecommerce rental saas-analytics dutch-railway marketplace ossie-retail
Quick start

Your first correct answer in minutes.

Point canonic setup at a local SQLite or DuckDB file. It introspects the schema, drafts your semantics, and ends by answering a real question from your data. Then connect Claude, Cursor, or any MCP client, no separate integration to build. canonic needs Python 3.13 or newer, and uv fetches it for you.

uvx canonic --version   # ephemeral, no install
uv tool install canonic # persistent, global

canonic setup           # point at a .db / .duckdb file
canonic query --metrics revenue
read-only · local-first · air-gapped-capable