Onboarding an agent to a legacy schema
Run factum derive on a table export, confirm the notes, and the agent has a
conceptual schema for a database nobody documented — in an afternoon rather than never.
It does not have to. factum-mcp hands it the conceptual schema — as facts, with every rule stated.
A coding agent working in your repository has access to your code, your migrations and your table
names. What it does not have is the reasoning that produced them: which rules are enforced because
the business demands it, which columns are nullable for a real reason and which by accident, what
a row in enrolment actually means. So it reconstructs. It is very good at
reconstructing. It is also unfalsifiable, because nothing on disk disagrees with it.
Every prompt you write that begins “in our system, a customer can have several accounts, but…” is a fact type you are re-typing because it was never written down.
Install the extension (which bundles the CLI and the server), then point any MCP client at it. It speaks stdio, runs locally, and reads files you already have.
{ "mcpServers": { "factum": { "command": "factum-mcp" } } }
That is the whole integration. There is no account, no index to build, no embedding step and no daemon — the model is a file, and the server reads it when asked.
Seven read tools and one guarded write. Nothing here is a chat wrapper around a diagram — each tool answers a question the agent would otherwise answer by inference.
| Tool | What it returns | Why the agent needs it |
|---|---|---|
read_model | An outline of object types, fact types, readings and subtypes | The shape of the domain, before answering anything about it |
verbalize_model | The FORML sentences for every fact type and constraint | The rules, in the format the model reasons best in |
read_population | Sample facts read back through the readings | Grounded examples — few-shot data you did not have to write |
validate_model | Well-formedness problems, and samples that contradict constraints | To check its own work instead of asserting it is fine |
generate_schema | SQL DDL or LadybugDB Cypher DDL, with mapping notes | To get from concepts to a migration without inventing the middle |
diff_models | What changed, as sentences rather than a JSON diff | To explain a change in a review a human will read |
detect_drift | Where a pg_dump and the model disagree, with reconciling statements | To notice that production moved on without the model |
apply_model | A validated write of the whole document | To record a decision — and to be refused when the result is broken |
apply_model validates before it writes and refuses a model with blocking errors
unless explicitly overridden. This is the load-bearing part: an agent can be wrong, but it cannot
quietly commit an incoherent schema.
Same repository, same prompt, same model. The only variable is whether the fact layer exists.
> add support for contractors who
work for more than one company
// Looked at: schema.sql, Person.ts
- I'll add a `company_id_2` column
- ...or a join table, if you prefer?
- Note: existing rows assume one
company; I can't tell whether that
was a rule or just the data.
factum-mcp> add support for contractors who
work for more than one company
// verbalize_model, read_population
- "Each Person works for exactly one
Company" is a mandatory role plus a
uniqueness constraint — a stated rule,
not an accident of the data.
- Contractor and Employee are already
subtypes of Person, so I moved the
uniqueness onto Employee rather than
relaxing it for everyone.
validate_model passes.
- Diff for review:
- Each Person works for at most
one Company.
+ Each Employee works for at most
one Company.
The second answer is not a smarter model. It is the same model with the constraint in front of it — and, just as importantly, with a validator that would have caught it if it had relaxed the rule for everyone.
Run factum derive on a table export, confirm the notes, and the agent has a
conceptual schema for a database nobody documented — in an afternoon rather than never.
The agent maps from the model rather than editing DDL by hand, so a mandatory role stays
NOT NULL and a value constraint stays a CHECK.
The bundled Action comments with what the model now says. Reviewing an agent's schema change becomes reading one sentence.
validate_model checks constraints against your recorded sample facts. An
invented rule that your own data contradicts is reported, not believed.
Ask for either target. The n-ary fact type no edge can hold gets reified into a node, because the mapping knows what the fact type is.
The whole domain, verbalized, is small enough to put in a prompt. The agent reasons over all of it, not the three chunks a retriever returned.
Nothing on this page calls a language model. Factum has no chat panel, no API key, no telemetry and no inference of its own — the verbalizer, the validator and the mappers are deterministic code that would work exactly the same in 2005.
That is the point. The agent is your agent, running where you already run it, reading a file you own. Factum's job is to make sure the file says something true.