An answer should not have to be taken on trust. I'm building Nexa because I believe a coding assistant should be able to show you what it checked — which rule, from which source, and whether the code it just wrote actually obeys it — grounded in a corpus you can read rather than in a confident paragraph. The first version is now finished, and this is why it exists and how it's built. The deeper technical walkthrough lives in the companion article on dev.to.
The gap between an answer and a check
Most debates about AI and code ask whether the model hallucinates. It does, but hallucination is the easy failure, because it announces itself. An invented API doesn't exist; the import fails, the type checker complains, the test goes red. The tooling you already have catches it.
The failure that matters is quieter. During development, my working model cited odoo.onchange-is-not-validation — a real rule, in my corpus, traceable to upstream documentation — and then wrote an @api.onchange to guard the value. Every citation verified. The answer was still wrong. Citation existence is not rule compliance, and nothing in the ordinary pipeline was ever going to notice the difference.
The tools that check code already exist, and they are strong. Type checkers catch what types can express; linters catch what patterns can express; test suites catch what someone thought to write a test for. What I could not find was a layer sitting above the model — something that checks a generated answer against the specific rules of a stack at the moment of generation, before that code reaches any of the tools downstream, and while the reasoning behind it is still on screen to be argued with. Nexa is my attempt to build that missing layer.
A checking layer, not a model
Nexa is not a model, and it is not a rival to the models it uses. No weights ship with it. It is a few hundred kilobytes of Python — standard library only — that orchestrates open models you pull from Ollama's registry, which keep their own upstream licenses. Its job is to let a general model answer a domain question while being held to rules it cannot quietly break.
That position dictates everything else. A layer that sits above a model it does not own, and constrains it without retraining it, has a very particular set of constraints, and each design choice below is an answer to one of them. The same structure has to serve one developer asking one question on a laptop with no network, and a corpus grown to cover stacks I have never touched, without changing shape between the two.
The choices that keep it honest
The first is that the rules are data, not prose. The source of truth is a collection of schema-validated rule files; the system prompts and the retrieval index are generated from them and must never be hand-edited. The rule I hold to is data over prose — a rule is written once, in a structured form, and flows to the prompt, the index, and the compliance checker together. I arrived at that the slow way. Prompt tuning plateaued after two iterations, and hardening the Odoo rule about returning false for an empty field fixed the scalar cases while simultaneously breaking the Many2one tuple handling that had been correct the day before. Prose has no schema: you cannot diff it meaningfully, you cannot validate it, and you cannot test whether a paragraph still means what it meant before you edited it. If the prompt were the source of truth, then "the rules" would quietly come to mean "whatever the current wording happens to produce," and every change would trade one error for another.
The second is that client code stays out of the tool entirely. This runs on a machine used for client work, and a telemetry file is the last place client code should accumulate. So the measurements record rule identifiers, counts, model names and timings — never question or answer text, unless you deliberately turn that on. Nothing leaves the machine at all: no API key, no network call, nothing phoning home. The same discipline governs the documentation a rule cites, which is read when the rule is authored and never at query time. Looking things up mid-answer would need internet, add latency, and reintroduce exactly the hallucination surface the corpus exists to remove. Verify once, at the boundary, and bake the checked knowledge into offline data.
Building on what already works
The most consistent decision across the whole project is that I tried to invent as little as possible. Before defining anything, I checked whether a mature standard or an existing system already solved it, and bound to that instead of building a parallel version.
Nexa trains nothing; the models come from Ollama's registry and stay there. Rules validate against JSON Schema rather than a format of my own invention. Escalation control is delegated to the LLM Workflow Router, so the bound on the expensive model is declared topology rather than a retry counter buried in a function — and because that engine is content-blind, seeing only which container it is in, how deep, and what transition was requested, the decision stays deterministic and independently testable. Measurement is written as plain line-delimited JSON under a namespace I own, with an optional OpenTelemetry bridge that does nothing when the package is absent. And the rules themselves bind outward: twenty-seven of thirty-two carry a link to upstream documentation, so a claim can be traced past me to its source.
That discipline paid for itself immediately. Reading the Odoo ORM reference while authoring surfaced a rule I had missed entirely: @api.constrains silently ignores dotted field names, while @api.depends accepts them. The two decorators look interchangeable and are not, and the failure is silent — the module installs and the constraint simply never fires. The router did the same thing from the other direction, rejecting the first workflow topology I wrote because a cycle containing a no-re-entry step is dead by construction. That was caught at load time, before any model ran.
The reason for the discipline is partly humility and partly durability. A tool that reinvented retrieval, workflow control, and telemetry would be both arrogant about excellent work others have already done and brittle against the systems it is meant to sit above. Binding to established interfaces lets Nexa inherit their maturity and stay swappable as they evolve.
Proving the checks can fail, not just pass
A corpus that says "these patterns catch violations" is making a claim, and I wanted that claim to be testable rather than taken on faith. Every rule already carries a wrong sample and a correct one, so the test data existed for free. The build fails on three specific faults: a pattern that does not match its own wrong sample catches nothing; a pattern that fires on its own correct sample cries wolf on good code; and a clearing pattern that clears its own wrong sample is inert by construction. A checker that fires on good code is worse than no checker at all, because it teaches you to dismiss it — and it will then be dismissed on the day it was right.
Self-testing proves a pattern handles its own case and says nothing about whether it goes off on unrelated good code, so a second pass fires every pattern at every other rule's correct sample. That is the false-positive rate, and it decides whether a checker gets trusted or ignored. The first run found three real defects: one pattern fired on any comparison of a quantity against zero, including inside SQL where the rule does not apply; another fired on any parsed asynchronous result; and a third correctly flagged a sample in my own corpus that did not follow its own rule. That last one is the one I would keep — the checker was right and the corpus was wrong. There are now zero cross-hits across thirty-two rules.
To confirm the self-check is doing real work rather than passing vacuously, there is a negative test for the validator itself: a deliberately broken pattern that the build must reject. A suite that only ever passes is not evidence, and I wanted to watch it fail on purpose before I trusted it to pass.
What it is, and what it isn't yet
I want to be precise about scope, because a project like this is easy to overstate. What exists today is a coherent rule corpus — thirty-two rules across Odoo, TypeScript and JavaScript, thirteen of them critical, twenty-seven with an automated check and twenty-seven traceable to upstream documentation — a pipeline that verifies both citations and compliance before showing you an answer, and a demonstration that the patterns do not collide with each other. It runs offline, on a six-gigabyte laptop GPU, with your code never leaving the machine.
What does not yet exist is proof that any of this holds up outside my own corpus. Five rules have no automated check at all: two need set comparison between a decorator's arguments and the attributes its body reads, which is syntax-tree work rather than pattern work; one lives in a diff between module versions; two depend on context a code snippet does not contain. Those return no verdict rather than a false pass, because eighty-four percent coverage with an honest list of the gap beats a hundred percent with five checkers producing noise. Beyond that, compliance is only checked against the rules retrieved for a given question, so a rule that was never retrieved is a rule that was never enforced — and a verdict of verified means the checks that exist found nothing, which is not the same as correct. The rules were also written by an assistant rather than harvested from production incidents, and rules drawn from real incidents would be worth more.
The next steps follow directly from that gap: a syntax-tree checker for the two critical rules that patterns cannot express, and enough real runs to decide whether escalating to the larger model actually rescues answers often enough to earn its cost. If it turns out that it doesn't, the honest response is to delete it and put those cases in the corpus instead. This is deferred, not abandoned — measure the small claim before making the large one.
The hope: tools that show their work
My hope reaches further than any one project. I want to see verifiability become ordinary in AI tooling — not a green checkmark, but a specific and readable claim about what was checked, what wasn't, and what the absence of a finding actually proves. A tool that can tell you precisely where its confidence runs out is more useful than one that is right slightly more often and silent about the difference.
This is not a new idea. It is the same instinct behind type systems, property-based testing, and design by contract: make the machine prove a narrow thing rather than asking a person to trust a broad one. What has changed is that the thing writing the code is now fluent enough that our oldest heuristic for detecting uncertainty — hearing someone hedge — has stopped working. A colleague who is unsure hedges, and you can hear it. A model that is unsure produces the same well-formed, confidently-cited paragraph as a model that is right.
There is a second reason I want these tools to be small, local, and open. A tool that runs on hardware you own, grounded in rules you wrote, is a tool that cannot be withdrawn from you: no key to expire, no price to change, no model to be deprecated, nothing to lose when the network is gone. For anyone working under a duty of confidentiality, that is not a feature but a precondition. I do not imagine one tool delivering all of this. I imagine many small, honest ones — each narrow enough to say exactly what it checked.
So if one idea survives this whole essay, let it be this:
A model's confidence is not evidence. What makes an answer checkable is someone deciding, in advance and in writing, what would make it wrong — rule by rule, pattern by pattern, sample by sample — and then building the thing that looks.
I would rather use a tool that can tell me it failed than one that never says so.