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")thenms.help("authoring")for the staged authoring workflow, andmd.help("ai_context")for the sharedms.ai_context(...)contract, before authoring.
your-project/ marivo.toml models/ datasources/ warehouse.py semantic/ sales/ _domain.pyDeclare a datasource:
import marivo.datasource as mdimport 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 mdimport 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
orderstable in thesalesdomain.
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 withms.help(...), and writes one declaration intomodels/semantic/<domain>/_domain.py. The default metric path ismd.discover_measures(...), author and verify the measure, then declarems.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 mdimport 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.catalogrevenue = 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 recommendationsdelta.quality_summary # cheap metadata projectionquality = 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 columnsrevenue = report.metric("sales.revenue") # arity-1 frame for drill-downFor 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.
Best practices
Section titled “Best practices”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.synonymsandexamples— 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.
Manage the project with git
Section titled “Manage the project with git”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.tomlandmodels/. 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*_envreferences and resolved from the environment (or cached in user-global~/.marivo/secrets.toml) — they are never written into the project.
A typical .gitignore:
.marivo/Gate before handoff
Section titled “Gate before handoff”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.