VS Code extension · ORM 2 · human-first, AI-native

Model the facts,
not the tables.

Object-Role Modeling in VS Code. Draw a conceptual schema, read it back as sentences a domain expert can confirm, and map it to a relational or property graph schema. Then hand the same file to your coding agent — which, until now, has been reconstructing your domain from column names.

readable by people and by LLMs MCP server included reads & writes .orm .fbm free & MIT

The Factum diagram editor showing an ORM schema for Person, Company and Skill, with the live FORML verbalization panel beside it.
An ORM 2 diagram and its live verbalization. Every sentence is generated from the model.
Human-first, AI-native

One artifact. Three readers.

Fact-based modelling says everything in sentences, states every rule explicitly, and refuses to leave anything implied. That is what a domain expert needs in order to approve a model. It is also, exactly, what a language model needs in order to stop guessing.

An elementary fact is the smallest thing a domain expert and a language model can both check — human-readable and machine-checkable at the same time, with no drift between the two.

☺

The domain expert reads sentences

“Each Person works for exactly one Company.” No notation to learn. They can say no, some contractors work for two — and that is a bug caught before a table exists.

⌁

The agent reads facts

An LLM’s native input is a sentence. Verbalization hands it the domain as a few hundred declarative statements with every rule stated — not table names to infer meaning from.

⌘

The build reads constraints

The same file validates in CI, maps to DDL, and diffs into a pull request comment. What the agent proposes, a human approves as one sentence.

ORM facts are the layer LLMs have been missing

Point a coding agent at a codebase and it reconstructs your domain from the only evidence it has: column names, foreign keys and a few migrations. Is a nullable column optional by rule, or by accident? Is that unique index a business rule or an old performance fix? It will answer. It has no way to be right — because nobody wrote the answer down.

What it has to guess from

CREATE TABLE person (
  id          int,
  company_id  int NULL
);

What the fact layer states

Each Person works for exactly one Company.
It is necessary that each Person has
  exactly one PersonNr.
It is obligatory that each Employee
  has a Salary.

Retrieval gives a model text that is similar to your domain. A conceptual schema gives it text that is true about your domain. Factum keeps that text in a file in your repository, checks it in CI, and serves it to your agent over MCP.

Three ways in

The same model, the same rules, three surfaces. Nothing is locked inside the editor — the core has no dependency on VS Code at all, which is why the other two exist.

▤

The editor

A real ORM 2 diagram editor as a VS Code custom editor, with live verbalization, validation in the Problems panel, and both mappings in a side panel.

$

The command line

factum validate, verbalize, diff, drift, ddl, derive. Run it in CI and the build fails when someone breaks the model.

⌁

Your coding agent

factum-mcp is an MCP server over the model, so Claude Code or Copilot reads the conceptual schema instead of guessing it from your table names, and a pack of skills teaches it the modelling procedure itself. Eight tools, one line of config.

Why fact-based modeling

ORM describes a domain as elementary facts — Person works for Company — rather than as tables or classes. Because those facts carry no attributes, every constraint has to be stated explicitly, and the whole model can be read back in plain language.

✓

Validated by people, not guesswork

A schema you can read aloud is a schema a domain expert can approve or reject, before a single table exists.

◇

No premature attributes

Deciding what becomes a column is a design step, not a modeling step. ORM keeps the two apart, so the model outlives the schema.

⇄

One model, two targets

The same conceptual schema maps to relational tables and to a labeled property graph. Compare them side by side and pick.

What it does

Everything runs locally, in a VS Code custom editor over a plain JSON file.

▤

Full ORM 2 notation

Entity and value types with reference modes, unary through n-ary fact types, multiple readings, subtyping, objectification and derived fact types.

⊸

Every constraint

Internal and external uniqueness, preferred identifiers, simple and disjunctive mandatory, frequency, all ten ring types, subset, exclusion, equality, value and cardinality — each with an alethic or deontic modality.

❝

Live verbalization

A FORML rendering of the model that updates as you draw. Click a sentence to select what it describes.

⚠

Validation as you go

Missing reference schemes, fact types without uniqueness, constraints too narrow to keep a fact type elementary, subtype cycles — all in the Problems panel.

⇄

Four formats, both ways

NORMA .orm, the FBM Exchange MetaModel, Apache Ossie ontologies and the Unified Modelling Schema — imported and exported, not just read.

◉

Sample populations

Real example rows stored with the model. The verbalizer substitutes them into the readings, and the validator checks your constraints against them.

↗

Export & layout

SVG and PNG export for papers and docs, plus deterministic force-directed auto-layout.

Read your model back in English

Verbalization is what makes a conceptual schema reviewable. Factum generates it from the constraints themselves, so the sentences cannot drift from the diagram.

Each Person works for exactly one Company.
In each population of "Person has Skill", each Person, Skill combination occurs at most once.
It is necessary that the possible values of GenderCode are {'M', 'F'}.
Each Manager is a kind of Person.

A mandatory role plus a uniqueness constraint reads as exactly one; drop the mandatory dot and the same fact type reads at most one. The wording follows the model.

Two mappings from one schema

A conceptual schema is not a database design. Factum derives both, and explains every choice it made rather than leaving you to reverse-engineer the output.

Relational — Rmap

Functional fact types are absorbed as columns, compound-unique fact types get their own table, unaries become booleans, mandatory roles become NOT NULL, and value constraints become CHECK constraints. Emits PostgreSQL, SQL Server, MySQL, SQLite or ANSI SQL.

CREATE TABLE "Person" (
    "personNr" integer NOT NULL,
    "companyName" varchar(255) NOT NULL, -- From "Person works for Company"
    "genderCode" varchar(1) NOT NULL,
    CONSTRAINT "PK_Person" PRIMARY KEY ("personNr"),
    CONSTRAINT "CK_Person_genderCode" CHECK ("genderCode" IN ('M', 'F'))
);

Property graph — LadybugDB

ORM knows more than a hand-drawn graph model does, and the mapping uses it: uniqueness constraints become relationship multiplicities, lexical value types become properties, and an n-ary fact type — which no edge can hold — is reified into a node.

CREATE NODE TABLE Person(nr INT64 PRIMARY KEY, genderCode STRING);
CREATE REL TABLE WORKS_FOR(FROM Person TO Company, MANY_ONE);
CREATE REL TABLE HAS_STUDENT(FROM Enrolment TO Student, MANY_ONE);

// Constraints the schema cannot enforce:
//   [mandatory] It is necessary that each Person works for some Company.
The Graph tab listing node tables, relationship tables with their multiplicities, and the constraints the schema cannot enforce.
The Graph tab: node tables, relationships with multiplicities, and the rules a property graph cannot hold.

Learn the method, not just the tool

Factum ships with a short book on ORM 2 — elementary facts, the constraint family, subtyping and objectification, and Halpin's seven-step design procedure worked end to end. Every diagram in it was drawn by the extension, and every figure links to the model behind it, so you can open any example and take it apart.

02

Elementary facts

What an elementary fact is, the and-test for splitting compound ones, and the population check that catches invented fact types.

05

Uniqueness

The constraint that carries the most meaning, the three binary patterns, and the arity check that tells you when an n-ary fact type should be split.

09

The design procedure

The seven steps of the CSDP, applied to a real staff directory report from first sentence to finished schema.

The book

Fact-Based Agents

The cover of Fact-Based Agents by Volodymyr Pavlyshyn.

The mini book above teaches the notation. This one is the whole argument: why coding agents reconstruct your domain from schemas and code, and what an ORM 2 conceptual schema gives them instead — eighteen chapters, 61 diagrams and 38 working models.

Every diagram is rendered by Factum's own renderer from a model that ships with the manuscript, and every FORML sentence, validator message and line of generated SQL quoted in the text is real tool output. It ends with three real schemas modelled properly: a six-layer agentic memory architecture, a promise graph for multi-agent coordination, and an audit log whose hash chain is stated as constraints rather than assumed.

You don’t have to pick a format

The fact-based modelling community has no agreed exchange standard, and three candidates are in play. Rather than back one, Factum reads and writes all of them — so a model can move between tools instead of being trapped in whichever one drew it.

FormatImportExportWhat it is
.ormYesYesNORMA’s ORM 2 XML, the de-facto archive format
.fbmYesYesThe FBM Exchange MetaModel, built for interchange
OssieYesYesApache Ossie’s ontology, an incubating ASF standard
UMSYesYesThe Unified Modelling Schema, a logical target

Every conversion reports what it could not carry rather than dropping it quietly. Exporting a model with an objectified fact type to Ossie tells you Ossie has no objectification; importing UMS tells you that what came back is the shape of the data rather than the facts behind it.

It reviews like code

A model stored as text can go through the same gate as everything else you ship. The bundled GitHub Action validates it and comments on the pull request with what the model now says — not with the JSON that changed.

- Each Person works for at most one Company.
+ Each Person works for exactly one Company.
+ It is necessary that each Employee has a Salary.

That is a reviewer reading one sentence and knowing whether to approve it. The same comparison runs locally with factum diff, and --exit-code lets a job fail on it.

In your workflow

- uses: Volland/factum-orm@v0
  with:
    model: model/domain.orm.json
    base: /tmp/base.orm.json
    strict: 'true'

Or in any shell

factum validate model.orm.json --format github
factum diff before.orm.json after.orm.json
factum drift model.orm.json schema.sql
factum derive people.csv -o people.orm.json

Check the model against reality

Against your own examples

Fact types carry real sample rows, stored in the same file. The verbalizer substitutes them back into the readings, so a domain expert reads “101 works for Acme” rather than a placeholder.

The validator then checks the constraints you drew against the examples you gave. A uniqueness, mandatory, frequency or value constraint your own data contradicts is reported — not believed.

Against the database you deployed

factum drift compares the schema your model maps to against SQL that already exists — a pg_dump, a migration — and prints the differences with the ALTER statements that would reconcile them.

It reads SQL text rather than connecting, so there is no driver to install and nothing to grant access to.

Start from data, not a blank diagram

The hardest part of modelling is the first fact type. Point Factum at a spreadsheet export and it proposes a first draft — the step both Halpin’s design procedure and FCO-IM actually begin with.

$ factum derive employees.csv

note: "employee_nr" is unique and never empty across all 240 rows,
      so it became the reference mode of Employee.
note: "department" takes only 4 distinct values, so a value
      constraint was proposed. Confirm the list is closed.

An identifying column becomes the entity’s reference mode, types and enumerations are inferred from the values, and every row is kept as a sample fact. Nothing is proposed that the data does not support, and each assumption comes back as a note for you to confirm.

Models are just files

A model is one .orm.json file holding the schema, its layout and its examples, described by a published JSON Schema so any tool can validate it. It diffs, merges and reviews like the rest of your source, and it is safe to hand-edit — the diagram is a custom editor over the text document, so VS Code's dirty state, undo stack and file watching all work normally.

Every element can carry metadata and per-target generation hints, and any x- prefixed key is yours to use — preserved untouched, so another tool can round-trip data we know nothing about.

Start modeling

Install the extension and run ORM: New ORM Model, or point factum derive at a CSV you already have. Either way you have a working schema in a minute — and it is a file you can commit.