Skip to content

Quick Start

Scaffold the project with marivo init (see Installation), then author your declarations. A minimal Marivo project has a manifest, datasource declarations, and semantic declarations:

Agents: consult md.help("authoring") then ms.help("authoring") for the staged authoring workflow, and md.help("ai_context") for the shared ms.ai_context(...) contract, before authoring.

your-project/
marivo.toml
models/
datasources/
warehouse.py
semantic/
sales/
_domain.py

Declare a datasource:

import marivo.datasource as md
import marivo.semantic as ms
md.duckdb(
name="warehouse",
path="warehouse.duckdb",
ai_context=ms.ai_context(
business_definition="Local DuckDB warehouse for sales analysis.",
guardrails=["Use only for development or approved local analysis."],
),
)

Declare semantic objects:

import marivo.datasource as md
import marivo.semantic as ms
ms.domain(
name="sales",
owner="Mina Zhang",
ai_context=ms.ai_context(
business_definition="Sales order analysis domain.",
guardrails=["Revenue metrics should only use completed orders."],
),
)
orders = ms.entity(
name="orders",
datasource=md.ref("datasource.warehouse"),
source=ms.table("orders"),
primary_key=["order_id"],
ai_context=ms.ai_context(
business_definition="One row per sales order.",
guardrails=["Exclude cancelled test data in metric definitions."],
),
)
region = ms.dimension_column(
name="region",
entity=orders,
column="region",
ai_context=ms.ai_context(
business_definition="Sales region assigned to the order.",
guardrails=["Do not treat missing region as a real region."],
),
)
order_date = ms.time_dimension_column(
name="order_date",
entity=orders,
column="order_date",
granularity="day",
is_default=True,
ai_context=ms.ai_context(
business_definition="Calendar date when the order was placed.",
guardrails=["Use this as the default time axis for order metrics."],
),
)
amount = ms.measure_column(
name="amount",
entity=orders,
column="amount",
additivity="additive",
unit="CNY",
ai_context=ms.ai_context(
business_definition="Completed order amount at row level.",
guardrails=["Use only where order amount is already net of cancellations."],
),
)
revenue = ms.aggregate(
name="revenue",
measure=amount,
agg="sum",
ai_context=ms.ai_context(
business_definition="Total completed order amount.",
guardrails=["Use only where order amount is already net of cancellations."],
),
)

You don’t have to write these by hand. marivo init installed the marivo-semantic skill into .agents/skills/, .claude/skills/, and .codex/skills/, so a coding agent running in the project can build the semantic layer with you. Point it at your tables and ask:

Use the marivo-semantic skill to model the orders table in the sales domain.

The skill walks the agent through a disciplined authoring loop, one object at a time:

  • Follow the ladder — domain → entity → dimension → time dimension → measure → metric → relationship — so each object’s dependencies exist before it.
  • Discover, then write. For datasource-backed objects the agent calls the matching md.discover_* API, settles constructor values with ms.help(...), and writes one declaration into models/semantic/<domain>/_domain.py. The default metric path is md.discover_measures(...), author and verify the measure, then declare ms.aggregate(...).
  • Verify before advancing. After each object it runs ms.verify_object(ref) and does not move on while that fails.
  • Gate at closeout. It finishes with ms.readiness() and resolves blockers before handing the catalog to analysis.

The agent only asks you the decisions it cannot infer from the data or project docs (for example, whether an amount is already net of refunds). Everything it produces is the same Python declarations shown above — reviewable in git like any other code.

Discover the catalog before analysis. For datasource-backed authoring, the discovery-first sequence gathers bounded evidence, then authors one object at a time:

import marivo.datasource as md
import marivo.semantic as ms
warehouse = md.ref("datasource.warehouse")
orders_source = md.table("orders")
md.inspect_table(warehouse, orders_source).show()
md.inspect_partitions(warehouse, orders_source).show()
scope = md.partition({"dt": "20260629"})
md.discover_entity(warehouse, orders_source, scope=scope).show()
ms.help("entity")

Use discovery evidence and the ms.help("entity") contract to author exactly one object, then run ms.verify_object(ref) before advancing.

Load the catalog and check readiness:

import marivo.semantic as ms
catalog = ms.load()
catalog.list("domain").show()
report = catalog.readiness()
if report.status == "blocked":
report.show()

Run an analysis session after the catalog is ready:

import marivo.analysis as mv
session = mv.session.get_or_create(name="revenue-check", question="Why did Q4 drop?")
catalog = session.catalog
revenue = catalog.get("metric.sales.revenue")
region = catalog.get("dimension.sales.orders.region")
current = session.observe(
revenue,
time_scope={"start": "2026-10-01", "end": "2027-01-01"},
grain="month",
dimensions=[region],
)
baseline = session.observe(
revenue,
time_scope={"start": "2025-10-01", "end": "2026-01-01"},
grain="month",
dimensions=[region],
)
delta = session.compare(current, baseline)
attribution = session.attribute(delta, axes=[region])
attribution.show()
delta.contract().affordances # mechanical compatibility, not recommendations
delta.quality_summary # cheap metadata projection
quality = session.assess_quality(delta)
quality.show()

When several same-scope metrics share one window, pass them as a sequence to session.observe(...). Marivo fuses same-datasource metrics into one query and outer-joins cross-datasource metrics on the time axis. Use frame.metric(id) to project an arity-1 frame for drill-down on a single metric without re-querying the backend.

report = session.observe(
[
catalog.get("metric.sales.revenue"),
catalog.get("metric.sales.total_orders"),
catalog.get("metric.sales.failed_orders"),
],
time_scope={"start": "2026-10-01", "end": "2027-01-01"},
grain="month",
)
report.show() # bucket_start + three value columns
revenue = report.metric("sales.revenue") # arity-1 frame for drill-down

For custom Ibis work that must re-enter Marivo’s typed metric flow, use session.derive_metric_frame(...). Semantic refs identify metric and axis bindings, while query output columns are plain strings. Across follow-up scripts, recover previous artifacts with session.frame_summaries() and session.get_frame(ref) instead of re-running upstream queries. Use artifact.contract().affordances for mechanical compatibility facts.

The same project structure scales from a one-off script to a reviewed, shared analysis project. Three habits make the difference.

Build the semantic layer as a shared knowledge base

Section titled “Build the semantic layer as a shared knowledge base”

The semantic layer is not just plumbing to reach tables — it is the knowledge base an agent reads before it analyzes. Invest in ms.ai_context(...) on every object:

  • business_definition — what the metric or dimension means, in business terms.
  • guardrails — rules an agent must respect: required filters, exclusions, scope limits.
  • synonyms and examples — so an agent resolves a natural-language question to the right object instead of guessing.

A well-enriched object answers an agent’s “can I use this, and how?” without a human in the loop. Readiness enforces the floor: a missing business_definition blocks analysis, and missing guardrails raises a warning. See the Semantic Layer for the full ms.ai_context(...) contract.

A Marivo project is plain text: marivo.toml plus the Python files under models/. That makes the semantic layer a reviewable, shareable artifact — treat it like application code.

  • Version the contract. Commit marivo.toml and models/. Every change to a metric definition or guardrail shows up as a diff.
  • Review semantic changes like code. Land definition changes through pull requests so a domain owner approves what a metric means before agents use it.
  • Share through the repo. Anyone who clones the project — and any agent that runs in it — gets the same trusted catalog.
  • Keep state and secrets out of git. Add .marivo/ to .gitignore: it holds project-local session and evidence state, not the contract. Credentials are authored as *_env references and resolved from the environment (or cached in user-global ~/.marivo/secrets.toml) — they are never written into the project.

A typical .gitignore:

.marivo/

Run ms.readiness() after loading and resolve blockers before any analysis session. A project can load while readiness is still blocked, so never pass a blocked catalog to an agent. See Readiness.