Skip to main content

A lightweight, local-first ontology engine for AI agents.

Project description

Mimir

Mimir is a small, local-first ontology engine for AI agents. You describe your domain in a YAML schema (types, properties, links), feed the engine claims from your data sources, and it materializes a typed graph that an agent can query over MCP — with every value traceable to the claim, and therefore the source, it came from.

The engine is event-sourced. The only source of truth is an append-only log of claims ("source X asserts fact Y"); the queryable graph is a deterministic projection of that log. The same claims always materialize into the byte-identical graph, nothing is ever mutated in place, and conflicts between sources are resolved by explicit rules instead of overwrites — the losing claims stay reachable.

Everything runs in one process on your machine: one SQLite file, one YAML schema, one MCP server. There is no LLM inside the engine and no network access in the core, by design.

Install

uv add mimir-ontology             # or: pip install mimir-ontology

The distribution is named mimir-ontology; the package you import and the CLI command are mimir.

Quickstart

The repository ships a demo world: a fictional plumbing business asserted by two fixture sources that overlap, disagree and occasionally make mistakes — on purpose. The full walkthrough with expected output and explanations is in examples/plumber/demo.md; the short version:

$ uv run mimir init demo-ws
$ uv run mimir schema load examples/plumber/schema.yaml --dir demo-ws
$ uv run mimir ingest examples/plumber/claims.jsonl --dir demo-ws
Ingested claims.jsonl: 88 accepted, 0 duplicates, 0 quarantined.
$ uv run mimir materialize --dir demo-ws
Materialized 88 claims: 43 entities, 35 links, 1 extra properties, 2 quarantined.
Graph hash: grf_42d6a5d8cb69ff50

Along the way the engine merged the customers both sources asserted (natural keys), resolved a phone-number disagreement by observation time, kept an undeclared property in the entity's extra pocket instead of dropping it, and quarantined two unprocessable claims with machine-readable reasons. Ingest is idempotent: run it again and you get 88 duplicates.

Inspect the graph from the shell (--json for machine output):

$ uv run mimir search Job --filter status=done --limit 3 --dir demo-ws
$ uv run mimir show ent_6f65b3bf159ff099 --dir demo-ws
$ uv run mimir query '{"type": "Job",
    "where": [{"property": "status", "op": "eq", "value": "done"},
              {"link": "invoice", "exists": false}],
    "aggregate": {"function": "sum", "property": "estimated_value"}}' --dir demo-ws
sum(estimated_value) = 40800.0
  (3 matched, 0 missing the property)

Serve it to an agent:

$ uv run mimir serve-mcp --dir demo-ws

The server speaks MCP over stdio and exposes seven read-only tools: describe_schema, describe_type, search, query, traverse, get, find. In a clone of this repository, .mcp.json already registers the server for Claude Code — run the quickstart above, start claude, and ask:

Which finished jobs have no invoice, and what is the total uninvoiced amount?

The agent reads the schema, narrows with a query, and lets the engine compute the sum in the same call — every figure traceable to a specific claim. When it guesses a link name, an operator or an enum value that does not exist, the server replies with a structured error naming the valid options.

How agents should query

The read surface is designed around how an LLM agent actually works: small working memory, a tendency to hallucinate structure, and no patience for inventing pagination. The intended pattern:

  1. Overview first. describe_schema returns the map — every type with its description, entity count and links, but no property definitions. It stays small even at a hundred types.
  2. Then the chapter. describe_type(type_name) returns one type in full: properties with enums and descriptions, outgoing links, incoming links, entity count.
  3. Narrow. search for simple property filters; query for link predicates (both directions), ordering and pagination. Results are compact cards; every list response carries the true total, and a cursor continues exactly where the page ended.
  4. Walk. traverse follows declared links from one entity. Expansion is bounded and every truncation is reported (complete: false) — nothing is ever cut silently.
  5. Let the engine count. Every count, sum, average, minimum, maximum and group breakdown is one query call with an aggregate block. An agent adding up figures it paged through is the failure mode this surface exists to prevent; aggregation walks are never truncated, and float sums are bit-stable.
  6. Drill to provenance. List views show plain values; get(type_name, entity_id) returns the full dossier — every value with its observed_at and claim_id. Every aggregate names the filter that reproduces its input set, so every number can be re-derived and audited.

Wrong turns fail loudly and teach: an unknown field, an inapplicable operator or a typo'd enum value returns a structured error listing the valid options instead of silently matching nothing.

Semantic search

Exact reads answer questions you can spell precisely. find answers the other kind — "which jobs were about water damage?" — by ranking entities by meaning. It needs an embedding model and a vector index:

$ pip install 'mimir-ontology[local]'    # local model (downloads on first use), or:
$ pip install 'mimir-ontology[gemini]'   # Gemini API (key from GEMINI_API_KEY)
$ uv run mimir embed --dir demo-ws       # build the index; re-runs embed only what changed
$ uv run mimir find "water damage" --type Job \
    --where '[{"link": "invoice", "exists": false}]' --dir demo-ws

The provider is configured per workspace in mimir.toml (provider = "local" uses Qwen/Qwen3-Embedding-0.6B by default; provider = "gemini" uses gemini-embedding-2). Results are the k nearest entities with cosine distances, and the optional type/where filters use the same predicate language as query — applied before the ranking, so you get the nearest entities among those that match. find is also the seventh MCP tool; the intended agent pattern is semantic first to locate, exact to verify.

The index is a projection, never truth: embeddings are derived from the materialized graph, live in the same SQLite file, and can be dropped and rebuilt at any time. Every index carries a passport — which model, which dimensions, which graph — and find answers only when all three match what is being served: a stale or mismatched index is a structured error naming the fix (mimir embed), never a silently wrong ranking. Embeddings are cached by content, so re-materializing and re-embedding a large workspace costs only the entities whose rendered text actually changed.

How it works

claims.jsonl ──ingest──▶  claim log            append-only, content-addressed (SQLite)
                             │
                        materialize            pure function: (schema, claims) → graph;
                             │                 same input, byte-identical output
                             ▼
                        typed graph            entities + links, every value with provenance
                             │
                     ┌───────┴───────┐
                embed│               │serve-mcp
                     ▼               ▼
               vector index     read-only MCP tools for agents
               (sqlite-vec)     (find searches the index)

Five primitives: Claim (an atomic assertion by a source), Schema (your ontology, loaded from YAML — the engine hardcodes no domain type), Entity and Link (the materialized graph), Provenance (who said it, based on what, when). Identity is content-derived throughout: the same natural keys land on the same entity no matter which source asserted them, and re-materialization can rebuild the whole graph from the log at any time.

This release adds the semantic layer: a vector index over the materialized entities (sqlite-vec, in the same workspace file), pluggable embedding providers behind a small port (a local sentence-transformers model or the Gemini API), the find tool, and query v2 — link predicates that filter on the linked entities' properties. The claim log, canonical identity and materialization are untouched; earlier workspaces open as-is.

Development

Requires Python 3.12+ and uv.

uv sync                  # install dependencies
uv run pytest            # run tests
uv run ruff check .      # lint
uv run ruff format .     # format
uv run mypy .            # strict type check
uv run mimir --help      # CLI

License

AGPL-3.0-only. See LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

mimir_ontology-0.3.1.tar.gz (139.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

mimir_ontology-0.3.1-py3-none-any.whl (91.5 kB view details)

Uploaded Python 3

File details

Details for the file mimir_ontology-0.3.1.tar.gz.

File metadata

  • Download URL: mimir_ontology-0.3.1.tar.gz
  • Upload date:
  • Size: 139.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for mimir_ontology-0.3.1.tar.gz
Algorithm Hash digest
SHA256 e4918bdd603737867a8e3a8483a050a674ce853e869702c3889312784d328ddf
MD5 ea711d2ef91a580a11e828293a50d942
BLAKE2b-256 1fd1950cb1566d74f9af2883f1ef3f221f20fad062802e116991956e7d9b691e

See more details on using hashes here.

File details

Details for the file mimir_ontology-0.3.1-py3-none-any.whl.

File metadata

  • Download URL: mimir_ontology-0.3.1-py3-none-any.whl
  • Upload date:
  • Size: 91.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for mimir_ontology-0.3.1-py3-none-any.whl
Algorithm Hash digest
SHA256 e4a203761e02cd309c2e1d61033cbb502fb0eb295a9fe6f9b062d6c6b09e4e89
MD5 1a71a5ed0b14159f1141d422901e7f9e
BLAKE2b-256 1d3d20f3f27bdb0f349d90efc1e0b10eec5b9ad4740d4fc885964346b51339d3

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page