AI & context

Ontology-Driven Design: what if the ontology became the safety net for AI-generated code?

When an AI agent writes the code, the tests pass but the business logic can drift without anyone noticing: two names for one concept, a rule contradicted, an abandoned notion creeping back. An ontology extracted from the code, held up against the intent before each change and against the code afterwards, makes that drift visible and tells an intended change from a mistake.

Info

Originally written in French. Translated by AI — the meaning has been preserved, not the prose.

Cover of the book Le Contexte
Building on the book “Le Contexte”

This article builds on what I wrote in my book Le Contexte. To read it, download it or buy it on Amazon. The Amazon edition is available in French only; if you would like a version in another language, contact me.

TDD uses tests to guide development. What if we also used an ontology to keep a piece of software's business logic consistent? That is the idea I am exploring with Claude Code: start from the code to build an ontology, then gradually turn the process around so that the ontology guides, checks and supports the way the software changes.

The code works. But does it still get the business right?

With AI agents like Claude Code, we have entered a period where producing code is getting less and less difficult.

I describe a feature. The agent explores the existing code, modifies several classes, adds functions, adapts interfaces, writes tests. A few minutes later, it shows me its work.

The code compiles. The tests pass.

But one question remains: does this new code still respect my application's business logic?

An agent can produce a technically correct implementation and still introduce an inconsistency into the domain model. It can use two names for the same concept, bring back a notion that was abandoned, or create a rule that contradicts the ones already in place.

The problem gets particularly interesting as the application grows and AI agents make a growing share of the changes.

How do you give them a shared business reference? How do you detect drift? And how do you stop that reference from becoming yet another piece of documentation that ends up no longer matching the software?

Those questions are what led me to experiment, in my personal development work with Claude Code, with an approach I am calling Ontology-Driven Design (ODD).

To illustrate it, I will use the example of a fictional ERP.

Step one: extract the ontology from the code

My starting point was fairly simple.

I treat the source code as the source of truth for the behaviour that is actually implemented. A specification describes an intent; the code reveals what the application really does.

So I asked Claude Code to explore my application's code and extract a representation of the business domain into a Markdown file: ontology.md.

Among other things, this file describes:

  • the business concepts: Customer, Order, Quote, Invoice, Credit Note…;
  • their properties and relationships;
  • the business rules and invariants;
  • the states and their transitions;
  • the vocabulary used to refer to these concepts.

In our fictional ERP, a customer order is linked to a customer. It has one or more lines. It can originate from a quote. It can be confirmed, cancelled or invoiced.

This information is not necessarily described in one place in the code. Some rules are explicit; others are scattered across several functions.

Claude Code can look for them and try to piece them together.

The result is not a perfect specification. It is a structured interpretation of the business, derived from its implementation, with the limits that implies: an implicit rule can slip through, and a historical behaviour can be mistaken for an intentional rule.

But I get something interesting: a representation of the domain that is independent of the technical details of the implementation.

The ontology.md file becomes a point of reference for understanding the software.

Step two: check new code against the ontology

Once this ontology was available, I asked Claude Code to use it whenever it developed new features.

The idea was to compare the changes made with the concepts and rules in ontology.md.

Suppose, for example, that the ontology says a confirmed invoice can no longer be modified.

If Claude Code adds a feature that lets you change its lines after confirmation, it has to flag that contradiction.

Likewise, if the ontology clearly distinguishes a quote from an order, the agent should not introduce a function that mixes up the responsibilities of the two concepts without an explicit reason.

That is a first safety net.

But it has a flaw.

The check happens after the code has been written.

The agent has already done its work. It may have created several classes, added methods and built tests around a wrong reading of the business.

We can detect the problem, but then we have to go back over the code that was produced.

In other words, I had added an after-the-fact check. Useful, but not enough.

Step three: borrow from Test-Driven Development

That is when I thought of TDD.

The principle of Test-Driven Development is well known: before implementing a behaviour, you write a test that expresses what you expect. The test fails, you write the code needed to make it pass, then you improve the implementation while keeping that safety net.

Why not apply similar logic to the ontology?

So I asked Claude Code to change its process.

Before coding, it first has to hold the requested feature up against the existing ontology.

The order of operations changes.

Take a new request:

"I want to allow a customer order to be created without necessarily going through a quote."

If the ontology says an order must always originate from a quote, the agent detects a contradiction before touching the code.

But that contradiction is not necessarily a mistake in the request.

It may reflect a perfectly legitimate change in the business.

So the agent has to recognise that the developer's intent modifies an existing rule, propose the change to that rule and work out the consequences for the related concepts.

It can then develop the feature on the basis of the revised domain model.

The process becomes:

  1. Understand the requested feature.
  2. Identify the ontology's concepts and rules that are involved.
  3. Flag any contradictions between the request and the existing model.
  4. Resolve those contradictions, telling an intended change apart from a design error.
  5. Develop the feature.
  6. Check that the code produced is consistent with the expected domain model.

This is not TDD in the strict sense: an ontology is not a suite of automated tests, and an analysis by an LLM does not offer the same guarantees as a deterministic assertion.

But the philosophy is close: set out the expectations and constraints before implementing, rather than discovering the inconsistencies afterwards.

At this point, the ontology starts to play an active role in development.

Which brings up a new problem.

Step four: an ontology that has to change with the code

If the ontology becomes a shared reference for development, how do you keep it up to date?

The last thing I want is to create a new document that someone has to remember to update by hand every time the application changes.

That would take us back to the original problem: documentation that falls out of sync with the code.

So the next idea is to bring a mechanical part into the process.

A script analyses the changes to the source code, notably from Git, and identifies what was added, modified or deleted:

  • functions and methods;
  • classes and interfaces;
  • modules and packages;
  • signatures and API contracts;
  • tools, resources and contracts exposed via MCP;
  • business relationships and behaviours identifiable in the changes.

The goal is not to rebuild the ontology from the application's technical details.

On the contrary, I want to stay independent of implementation choices. A hexagonal architecture, Domain-Driven Design or any other way of organising the code (even a very messy one) must not impose its structure on the ontology.

The ontology represents the business domain, not the technical architecture.

A class is not necessarily a business concept. A new package may be nothing more than a technical reorganisation. A function can implement a business rule without being a new concept in itself.

So two operations have to be kept apart.

The first is mechanical: determine precisely what has changed.

The second is semantic: understand whether those changes introduce, modify or contradict business concepts.

That is where the LLM comes in.

The script produces an inventory of the changes. The LLM holds that inventory up against the current ontology and against the directives that triggered the development.

Several situations can then arise.

A new business concept appears. The agent can propose or prepare its addition to the ontology, with its relationships and the associated rules.

An existing concept changes meaning. You have to determine whether it is an intended change or drift.

Two concepts become inconsistent. The agent flags the contradiction, with references to the code concerned.

The vocabulary diverges. Two names refer to the same business reality, or an existing name is used for a different notion.

The change is purely technical. No change to the ontology is needed.

One point remains essential: the code must not automatically be right.

If one agent introduces a mistake, I do not want a second agent to modify the ontology to make that mistake acceptable.

The triangle: intent, code and ontology

This, I think, is where the idea really takes a step forward.

Comparing the code and the ontology is not enough.

Three things have to be set against each other:

The intent: what the developer asked Claude Code to do.

The code: what was actually implemented.

The ontology: the reference business concepts and rules.

It is the comparison of these three that lets you understand the nature of a gap.

Back to our fictional ERP.

The ontology says a customer order must always originate from a quote.

The developer explicitly asks for the quote to be made optional.

Claude Code modifies the relevant functions and introduces the ability to create an order directly.

The ontology and the new code now contradict each other. But the code respects the intent.

So the right outcome is not to reject the change by default. It is to propose the corresponding change to the ontology, then look for the other places in the software that still assume a quote is mandatory.

On the other hand, if Claude Code had removed that requirement without any directive asking for it, the gap would be suspicious.

The difference is fundamental.

An inconsistency is not always a bug. It can signal a deliberate transformation of the business.

The system's job is not to prevent change, but to make it explicit, consistent and traceable.

Towards a genuine ontology-driven development cycle

I now picture a process that steps in at three points.

Before development: understand the business

When a new feature comes in, Claude Code identifies the ontology concepts involved.

It does not need to load the whole ontology. It can select the concepts, relationships and rules relevant to the request.

It then checks that the feature is compatible with that model, or flags the changes it requires.

Development thus starts from an explicit understanding of the domain.

During and after development: detect drift

A mechanical analyser picks up the changes to the source code.

Tools like Tree-sitter can analyse its syntactic structure and identify the elements that changed (that is my next project). Changes to API contracts and MCP interfaces also tell you about the capabilities the application exposes.

The LLM relies on these elements to look for inconsistencies with the ontology.

This step could be built into the Claude Code workflow through hooks, for example after a modification or at the end of a task.

The point is not to ask an LLM to blindly reread the whole application on every change, but to focus the analysis on the elements affected.

Before integration: check consistency

One last check could happen before the code is merged.

It would produce a report showing:

  • new or modified concepts;
  • business rules that may be affected;
  • inconsistencies detected;
  • proposed changes to the ontology;
  • decisions that need human sign-off.

The goal is a reproducible check, precise enough to be useful, without turning every code change into an endless review of the domain model.

A complementary improvement would be to maintain traceability between the ontology and the code.

A concept like Invoice could be associated with several classes, functions, events or API operations that take part in its implementation.

These associations do not define the concept. They show where it is implemented.

That distinction would make it possible to answer some interesting questions.

If I modify the invoice concept, which parts of the code are likely to be affected?

If a business function disappears, which rule in the ontology might no longer be implemented?

If a new API operation appears, does it expose an existing concept or reveal a new notion?

And if a class is renamed, is that a simple refactoring or a real change to the domain?

This map could be built up gradually as the code changes.

It should nonetheless keep the evidence for each association: files, symbols and observed changes. A relationship inferred by the LLM must not be mistaken for a demonstrated correspondence.

From ontology invariants to automated tests

Another avenue is to make part of the ontology executable.

Some business rules can be expressed as invariants.

For example:

  • a confirmed invoice can no longer be modified;
  • an invoice must have at least one line;
  • a credit note references the invoice it corrects;
  • a cancelled order cannot be shipped.

Some of these rules could be used to generate automated tests, or to check that existing tests cover them.

We would then have two complementary levels.

The first would be semantic: the LLM examines the consistency between intents, concepts and code.

The second would be deterministic: tests and executable constraints verify the behaviours that can be formalised.

So the ontology would replace neither unit tests nor integration tests.

It could help explain why certain behaviours need testing, and spot business rules that are not yet checked by anything.

A living ontology, but not an opportunistic one

This setup carries a major risk.

If the ontology systematically changes to follow the code, it is no longer a safety net. It becomes mere documentation, continuously regenerated.

Conversely, if it forbids any change that contradicts its current rules, it stops the product from changing.

So changes to the ontology have to be handled explicitly.

I distinguish three situations.

The change is intentional and clear. The system can prepare a versioned modification of the ontology, or even apply it in a working branch before sign-off.

The modification looks inconsistent with the intent. The system proposes a fix to the code rather than an update to the ontology.

The intent is ambiguous. The system asks for a decision instead of inventing a new business rule.

The ontology thus becomes an evolving contract between intent and implementation.

That also means keeping a history, for example with a small decision log inspired by ADRs. A renamed concept must not turn into two independent concepts. A deleted rule must remain traceable to the decision that justified removing it.

Changes to the ontology must be as traceable as changes to the code.

Measuring consistency rather than counting lines of code

This approach could also produce a few interesting indicators.

Ontology coverage would measure the proportion of business concepts identified in the code that are represented in the ontology.

Traceability would measure how many concepts have verifiable references to their implementation.

The number of confirmed, unresolved inconsistencies would give an idea of the drift between the code and the model.

Finally, the proportion of invariants linked to executable tests could help gauge how solid the safety net is.

These figures would not be absolute quality indicators. Their value depends entirely on the system's ability to tell real business concepts from technical noise.

But they could make observable something that today remains largely implicit: the conceptual consistency of a piece of software as it changes over time.

What if the process worked both ways?

At first, my process ran in one direction.

The code produced the ontology.

Then I added a check the other way round: the ontology was used to verify the new code.

Drawing on TDD, I introduced a preliminary step: holding the intents up against the ontology before development starts.

The next step is to automate detecting what has changed and working out what it means, to keep that consistency going over time.

But I now see a further possibility.

The code could make the ontology evolve, and the ontology could guide the way the code changes.

If a code change brings a new business concept to light, the system proposes adding it to the ontology.

If a request implies a change to an existing concept, the ontology makes it possible to identify the rules and implementations that may be affected before work even begins.

The shared reference is no longer merely produced by the software. It takes part in transforming it.

That loop is what interests me.

An old idea, a use still to be reinvented

It would be wrong to present Ontology-Driven Design as an entirely new idea.

Using ontologies to design applications has been around for a long time. As early as 2005, Pavel Hruby was describing an approach called Ontology-Based Domain-Driven Design.

More recently, work such as ODDToolkit has explored generating software artefacts from formal ontologies.

My exploration is a little different.

I am not necessarily trying to generate the software from an ontology. Nor do I want to impose OWL, RDF or any other formalism to get started.

What I am after is using a business ontology as a living shared reference for consistency, for AI agents that modify the source code.

The distinction matters.

The domain model does not replace the implementation. It does not claim to describe the full complexity of a piece of software. It does not, on its own, guarantee the absence of bugs.

It provides an explicit frame of reference so that each agent, on each new task, does not rebuild its own interpretation of the business.

What remains to be proven

This is still a personal experiment.

I started by generating an ontology from the code, then asking Claude Code to check its changes against it. I then changed the directives to move that check upstream of development.

The mechanical synchronisation loop, systematic traceability and automated checks are the next things to work on.

Many questions remain open.

Will the analyser be able to detect a real business change when no class name changes? How many false positives will the LLM produce? Could JEV meet this need? That is another project, for another day. How do you avoid cluttering the ontology with technical details? How often should the correspondences between concepts and code be re-examined? And above all, will the gain in consistency justify the cost of running and maintaining the setup?

This approach will have to be tested on real software changes, with deliberate business changes, artificially introduced inconsistencies and purely technical refactorings.

So I am not claiming to have defined a new, validated development method.

I am describing a direction I am experimenting with.

The business as a safety net

Development practices have gradually added more and more safeguards.

Unit tests protect behaviours. Integration tests protect interactions. Type checking and static analysis tools catch certain categories of error. Architecture rules help preserve the structure of the software.

But when AI agents produce a growing share of the code, another question becomes important:

Who protects the consistency of the domain model these agents are working with?

DDD has long provided part of the answer, with ubiquitous language, domain models and bounded contexts. Hexagonal architecture helps isolate the business from technical concerns.

The Ontology-Driven Design I am exploring would seek to add an operational mechanism to those principles: make the domain model explicit, hold it up against development intents, then continuously check its consistency with the code.

Ultimately, my goal is not to produce more documentation.

It is to let Claude Code develop faster without silently reinventing the business with every feature.

The code remains the truth of the implemented behaviour. The ontology becomes the reference for business meaning. The intent explains why that meaning can change.

And it is from setting them against each other that a new safety net for AI-assisted development could emerge.