Understanding Your Model
You author by conversation, but you still own the result — and you should be able to read it without the agent. For a model your repository owns, commit two files:
model.modelith.yaml— the canonical source. Self-describing (kind+version), it's what the agent edits and what CI validates.model.modelith.md— the rendered Markdown (with an embedded Mermaid diagram). This is the easiest-to-read form; people and agents read it directly, and CI fails if it drifts from the YAML.
Commit both. The Markdown is generated from the YAML — never hand-edit it; change the YAML (via the agent) and re-render. A model vendored from another repository is a copy with different rendering obligations.
Model contents
A model can define glossary, enums, entities, scenarios, and model-level
invariants:
glossary— ubiquitous-language terms that aren't entities (roles likeOwner, states, domain nouns), each with a definition.enums— first-class enumerated types, referenced by an attribute'stype.entities— the named concepts, each with a definition, relationships, attributes, actions, and invariants (each invariant carries a stableid).scenarios— short narratives that exercise the entities to stress-test whether the model hangs together. Scenarios render as formatted text steps today;sequenceDiagramrendering is a roadmap item.
Model-level invariants hold rules that span several entities and have no
natural single owner (for example, "when a Project is archived, none of its
Policies remain enabled"). They use the same {id, statement} shape as entity
invariants and share their ID namespace.
A model can also declare imports to reference enums and entities defined by
another model. Imports use files already in your repository. If the other model
originates elsewhere, vendoring copies it into your
repository; it remains a vendored model with different rendering obligations.
See the Schema Reference for these and all other
top-level fields.
A minimal model looks like this:
# yaml-language-server: $schema=https://modelith.sh/schema/domain-model/v1.json
kind: DomainModel
version: v1
title: My Product
glossary:
Owner: "A `User` with full control of a `Project`."
Member: "A `User` with access to a `Project` but no ownership rights."
entities:
Project:
definition: >
A container owned by at least one `User`.
relationships:
- entity: User
cardinality: "n:n"
role: "`Owner` or `Member`"
invariants:
- id: at-least-one-owner
statement: "Must have at least one `Owner` at all times"
User:
definition: A human principal who owns or belongs to `Projects`.
invariants:
- id: unique-email
statement: "Email address is unique across all `Users`"
scenarios:
- name: Create a project
actors: [User]
steps:
- "A `User` creates a `Project` and becomes its `Owner`"
invariants_touched: [at-least-one-owner]
See the Schema Reference for every field, and Reading the Diagrams for how the rendered ER diagram works.
The backtick convention
In freeform text (definitions, steps, invariants), entity names are wrapped in
backticks — `Project` — so the renderer formats them as code and the
linter can check they reference real entities. Freeform text renders as
Markdown; raw HTML is rendered as literal text. In structured fields that
already imply an entity (actors, relationship entity:, entity keys), the
backticks are skipped. The agent follows this automatically; it's worth
recognizing when you read the YAML.