◣ BLACK BOX
← ENGINEERING

What an AI Facade Has to Actually Do

You do not govern generated code by reading it. Four things a real AI facade has to do — schema boundary, deterministic repair with receipts, fixtures that referee, an audit trail with no exceptions — each demoable against a Go binary in my closet.

  • engineering
  • aifacade
  • llm
  • governance
  • go
  • ollama
  • claude

I promised myself this post couldn't be written until it was true. It's true now — every claim below is a curl command against a Go binary running on a box in my closet.


A few weeks ago, a governance specialist asked me two very fair questions about a financial tool I'd built in six days with AI doing most of the typing:

Did you review every line it generated? And if models cross-check each other, what stops one model's drift from being ratified by the other?

I had a tempting answer ready — "my control plane handles that." It would have been a lie. The control plane existed, but it didn't yet do the things the claim implied. And he was the worst possible audience for an inflated claim: a person whose literal follow-up would have been show me.

So I gave the true answer instead: no, I did not review every line. I reviewed every behavior, and I built the system so line-review was unnecessary where it mattered. Then I made a deal with myself: the tooling claim gets written down the day it's demoable, and not one day earlier.

This is that post.

The thesis

You do not govern generated code by reading it. At the speed AI produces code and output, review-by-eyeball is theater — you will skim, you will approve, and the one value with a dropped decimal point will sail through, because a plausible-looking number is the single hardest thing for a tired human to catch.

You govern it with structure: build the system so it is unable to silently absorb the model's mistakes. I call the pattern an AI facade, in the plain Gang-of-Four sense — one surface through which AI enters the system, constructed so nothing consequential passes through unchecked. Not a wrapper around the model. A boundary around everything the model touches.

A real facade has to do four things. Here's each one, with the receipts.

1. A schema boundary the model cannot talk through

Every consequential completion declares an output_schema — a strict JSON Schema. That schema is enforced twice, on purpose.

First, at the model boundary: generation itself is constrained (local models via grammar-constrained decoding, cloud models via structured outputs). Second — and this is the part people skip — the returned output is validated again in deterministic code, because the belt should never trust the suspenders. Parse, then a bounded repair pass (strip markdown fences and prose preambles — code fixing wrappers, never a model fixing a model), then validate against the schema.

If the output can't be made valid, the request returns HTTP 422 with no output field at all. A rejected value isn't flagged-but-available. It is structurally absent. Downstream code cannot consume what the response does not contain.

2. Deterministic repair, with receipts

Here's the failure that started all of this. During the six-day build, a model read a credit-card statement's APR of 33.61% and returned 3361. Grammar-valid. Type-correct. Catastrophically wrong — and exactly the kind of plausible number that survives human review.

The schema's range bound (0 ≤ apr_percent ≤ 100) catches it. The first version of my facade stopped there: rejected, 422, done. But the original incident taught a better lesson — the error class is known. A percentage that is exactly 100× too large is a dropped decimal point or a basis-points confusion, and the fix is arithmetic, not judgment.

So the facade carries a normalization rule registry: deterministic transforms for known failure classes, under four hard rules —

  1. A rule fires only on a value that already violates its schema bounds. Valid output is untouchable.
  2. A repair must land the value in full schema validity, or it doesn't happen.
  3. If two rules propose different fixes, nothing is normalized. Ambiguity is rejection, never a guess.
  4. Every applied repair is recorded — rule, field, before, after — in the response and the audit log.

Today the 3361 replay comes back like this:

{
  "validation": { "status": "normalized",
    "repairs": [{ "rule": "scale-down-100", "field": "apr_percent",
                  "from": 3361, "to": 33.61 }] },
  "output": { "apr_percent": 33.61 }
}

Caught and healed, with provenance. And one more discipline, enforced by a unit test that fails otherwise: every rule in the registry must cite the fixture that discovered its failure class. No provenance, no registration. Rules are scar tissue; scar tissue has a story.

3. Fixtures referee. Models never do.

His second question — drift amplification — deserves a structural answer, not a reassuring one.

The facade runs two model families side by side (qwen and llama locally, with a Claude branch wired the same way). Any schema-constrained request can ask for cross-model validation: the same request runs against the other family, and deterministic code diffs the two parsed outputs field by field. When they agree, you get provenance. When they disagree, you get the diff:

outcome: DISAGREE
diff: number: expected 42687, got 854321

And that's all you get — a flag. No third model votes. No "smarter" model arbitrates. Disagreement is resolved by the other half of the system: a fixture suite — known-correct input→expected-output pairs, versioned with the repo, run through the full facade by an eval harness whose comparator is the same deterministic differ. Exact fields are exact; numeric fields declare their tolerance.

That's the drift answer in one sentence: model output is never promoted to ground truth, so there is no channel through which one model's drift can be ratified by another. Ground truth lives in fixtures, and fixtures only grow — every caught model failure becomes one before its fix counts as done.

4. An audit trail with no exceptions

Every completion the system executes — user chat, background task execution, the summarizer, the eval harness, the cross-model validators themselves — lands in one ledger: model, route, tokens, actual cost, what the cloud would have cost, latency, validation outcome, applied repairs, cross-check outcome. Failed completions too — a request that produced nothing still produces a row, with the error on it.

The ledger has a UI, but the principle matters more than the page: if it isn't in the ledger, it didn't happen. When the cloud branch is unreachable, requests degrade to the local model — and the routing decision records exactly why: force_cloud hint (cloud failed: API key unset; served locally). Degradation is fine. Silent degradation is the disease.

The supporting rules

Three smaller disciplines hold the four pillars up.

Models write prose; code writes facts. The system has an AI scribe that summarizes work sessions and maintains project briefs — narrative only. Every consequential number (counts, durations, costs, statuses) is computed in SQL. When session summaries are distilled into durable key points, even that extraction runs through the same schema pipeline as everything else, tagged with its source.

A model must never be able to mark work done. My task dispatcher can auto-execute simple tasks through a local model. The day I added "promote this tech-debt item to a task," I realized the dispatcher would happily hand "refactor the tilemap loader" to a 7B model, accept three paragraphs of prose, and mark the task completed. Fake work, marked done — the most dangerous output class there is. Promoted tasks now carry a tag the dispatcher is forbidden to touch. Structure again, not vigilance.

Honest claims only. While building this, I caught myself writing "Phase complete — all 8 items" into the plan when item 3 wasn't done. I'd studied a dead project that marked epics Complete for code that didn't exist; that autopsy is why this rule exists. The plan now says 7 of 8, with the open item named. If your status documents lie, your containment story is already over.

What this runs on

None of this needed a platform team. One Go binary, SQLite, an embedded web UI, a consumer GPU box running 7-billion-parameter models, and a cloud fallback that costs cents when it's used at all. That's the part I find genuinely encouraging: containment is what makes small local models usable for consequential work. A 7B model inside this facade is more trustworthy than a frontier model on the honor system — because the trust was never in the model. It's in the structure.

The point

The question was never "did you read what the AI wrote." The question is: what happens when it's wrong? If the answer involves a human noticing, you don't have governance — you have hope.

Build the surface it enters through. Bound what it can say. Repair only what you can repair deterministically, and write down every repair. Let fixtures referee, never models. Log everything, including the failures. Then you can give the governance specialist the answer I can finally give:

No, I didn't review every line. The system did — and unlike me, it never skims.