license:Apache-2.0 mcp:native shape:agent

A semantic layer that agents can actually drive.

Semantic Rails governs your data while allowing your agent to formulate any question at runtime: with inline scope classification, structured errors when it hits a boundary, explainable plans, and analytic policies. DuckDB locally; Snowflake, BigQuery, Databricks, Postgres and 4 other native connectors.

Companies like Anthropic, Block, and Lyft are moving toward semantic governance. Are you?

Apache-2.0 licensed Local DuckDB Snowflake CLI & native connector Hosted MCP demo (synthetic data)
agent ↔ semantic-rails · jaffle_shop
discover inspect build-options validate compile execute

Efficient discovery and governance.

An agent that "just writes SQL" guesses object IDs, hallucinates join paths, and hands you a query it can't explain. Semantic Rails replaces the guess with inspectable MCP tools: capabilities for orientation through execute for warehouse results, plus plan, valid-values, and three segment-* tools.

Sheet 01 · Pipeline / Tool Sequence
Pipeline 7 MCP tools (of 13)
Semantic Rails: 7-station pipeline A horizontal schematic rail with seven MCP-tool stations. Each station is clickable to reveal details. 0+00 2+00 4+00 6+00 8+00 MCP capabilities01 · ORIENT catalog02 · DISCOVER inspect03 · DISCOVER build-options04 · BUILD validate05 · GATE compile06 · EMIT execute07 · RUN

Scope classification runs inline on discover and plan - out-of-scope intents come back as structured blocks before the planner touches the warehouse. plan, catalog, valid-values, and segment helpers are also exposed for agents that need them.

Entities. Entities. Entities.

Entities are first-class abstractions, meaning that you don't need to configure the entire universe of possibilities by hand. The planner walks the graph, checks direction and cardinality at every hop, and reports the chosen route in a hop_profile next to the SQL. Below are real examples against the bundled jaffle_shop package.

Sheet 02 · Entity Graph / Hop Trace
Hop trace package jaffle_shop
All ids and payloads come from real compile responses
plan · "item revenue by customer type" measure `item_revenue_usd` lives on order_items · dimension `customer_type` lives on customers · 2 hops apart
Storefront session session_id Product product_type Item item_revenue_usd Order order_id Customer customer_type · lifetime_spend_usd Store store_name hop 1 · N:1 · safe hop 2 · N:1 · safe hop 2 · N:1 · safe reverse · requires_rewrite ⚠ reverse ⚠ ✕ refused before SQL
hop_profileroot_entity: entity.jaffle_item · hop_limit: 4
targets.entity.jaffle_customer · hop_count: 2 · alternates_considered: 1
relationship.order_items_order item → order forward · N:1 · safe
relationship.orders_customer order → customer forward · N:1 · safe
max_hop_count: 2 · long_hop_targets: [] · nothing flagged for acceleration
hop_profileroot_entity: entity.jaffle_item · hop_limit: 4
targets.entity.jaffle_store · hop_count: 2 · alternates_considered: 2
relationship.order_items_order item → order forward · N:1 · safe
relationship.orders_store order → store forward · N:1 · safe
max_hop_count: 2 · long_hop_targets: [] · nothing flagged for acceleration
errorcode: MIXED_GRAIN_INVALID
"Path to 'entity.jaffle_product' requires a rewrite that is not supported for group_by"
recovery_hints[0] · kind: replace_measure · "Measure 'measure.jaffle.item_revenue_usd' aggregates at a grain that can group by 'dimension.jaffle_product_type'"
No SQL was generated. The next call is already in the hint.

Why agentic semantics are different.

Existing solutions reach determinism by constraining your data to a fixed set of solutions. An agent runtime enables determinism through expressive representation of your data. By defining how data can be used and the relationships of your business, this agent can answer any question that a human analyst could.

01

Collaborative MCP tools

capabilities, discover, inspect, plan, build-options, validate, compile, and execute are each their own MCP tool - alongside catalog, valid-values, and three segment helpers. Agents can stop, branch, or backtrack at any step instead of being forced through one opaque "give me SQL" call.

02

Structured errors with recovery hints

When a query doesn't fit, the runtime returns a typed envelope with recovery_hints and closest_matches - usable directly by the next tool call, no prompt-engineering glue required.

03

Inline scope classification

Every discover and plan call runs the scope classifier inline. Intents that share no content tokens with the package come back as out_of_scope or low_relevance blocks - off-topic questions don't quietly hit the warehouse.

04

Planner-owned rewrites

Mixed-grain requests, metric predicates, temporal-validity joins, and same-store conversion live in the planner - not as helper views every team has to re-author.

05

Explainable plans

compile's response carries the full explain payload: alias resolution, chosen join paths, rewrite strategy, logical plan, SQL AST, and rendered warehouse SQL - flattened into compile_response_metadata so reviewers see everything before trusting the result.

06

Packages as code, tested like code

Authored as versioned YAML with graphs, joins, clocks, value domains, examples, and package-local tests. The CLI ships parse-config, validate-config, and test-package.

Expressive data that follows your rules.
Verbatim rendered_sql for examples/jaffle_shop_revenue_by_store.json
compile · @examples/jaffle_shop_revenue_by_store.json → status: ok
WITH leaf_1 AS (
fanout_strategy: independent leaf aggregate
SELECT
jaffle_store.store_name AS g1,
alias_resolution: dimension.jaffle_store_name → g1
SUM(jaffle_order.order_total_cents / 100.0) AS m1
measure: measure.jaffle.revenue_usd · expr from the package
FROM jaffle_order
hop_profile.root_entity: entity.jaffle_order
INNER JOIN jaffle_store
chosen_paths: 4 candidates → relationship.orders_store
ON jaffle_order.store_id = jaffle_store.store_id
contract: fk_to_pk · N:1 · safe
GROUP BY jaffle_store.store_name
)
SELECT
base.g1 AS "dimension.jaffle_store_name",
output_columns: stable object ids
base.m1 AS revenue_usd
FROM leaf_1 AS base
ORDER BY revenue_usd DESC
LIMIT 5
from the request · checked by validate
Every clause traces to a package object or to the request. The same response carries rendered_sql · logical_plan · sql_plan · hop_profile · explain.alias_resolution · explain.chosen_paths · explain.fanout_strategy

Try it in ten minutes.

Clone, install with uv, validate the Jaffle reference package, and start the MCP server. The whole loop runs locally against a sample dataset.

  • Clone the repo and run uv sync --group dev.
  • Validate the active Jaffle package with validate-config.
  • Start semantic-rails mcp stdio and connect from your agent.
  • Optionally swap to Snowflake via the showcase package and Snow CLI.
quickstart.sh
uv sync --group dev
uv run semantic-rails validate-config --package jaffle_shop
uv run semantic-rails serve --package jaffle_shop --port 8081
uv run semantic-rails mcp stdio --package jaffle_shop

Built for the agents you're shipping this year.

Read the source. Run the quickstart. If it doesn't fit the way your agent thinks, tell us why.