Architecture - 2026-09-21 - 9 min read

Stage 5 of enterprise AI adoption: governance hardening

The fifth stage of enterprise AI adoption compares declared policy against what the records actually show, records which policy version allowed each action in an append-only ledger, and stops a run that is already out of bounds rather than refusing its next call.

Enterprise AIArchitectureAI-Native EngineeringGovernance

This is part 5 of a series that started with the full reference diagram in part 0, covered identity and the gateway in part 1, data and retrieval in part 2, sandboxed agents with read-only connectors in part 3, and write and send behind approval in part 4. Stage 5 is the whole bottom row marked 5, GOVERNANCE & OBSERVABILITY, with four boxes: Policy & gates, labeled "versioned as code, declared vs actual"; Audit ledger, labeled "who / what / when / which policy"; Halt & budget caps, labeled "run-level stop"; and Cost & quality metrics, labeled "p95, not averages". It is easy to read this as governance starting here. Logging and Rate limit & budget have been running inside the gateway since stage 1, and every call has been recorded and charged against a budget from the first day. What this stage adds is comparison, attribution to a version of a rule, and the ability to stop something that is already running.

Stage 5: governance and observability across the whole stack

The company is the same one as in parts 1 through 4, Qingchuan, about 40 people, 8 engineers and 2 in marketing. Qingchuan is made up and only serves to walk the stage. Coming out of stage 4, everyone logs in through the identity provider, every call carries a role through the gateway, retrieval is scoped to the asker, agents run inside a sandbox, and the writes and sends that leave the company carry an approval record the system wrote rather than one the agent reported. By this stage Qingchuan usually has several agents or pipelines running at once. Two things are now missing rather than one. Nothing compares the rules Qingchuan wrote down against what those records actually show, and nothing can stop a run that is already out of bounds.

Policy & gates: declared against actual

The first box holds the rules, versioned as code, and the gates that evaluate them. Its label pairs declared and actual because those are two different things, and this box is where they get compared.

The failure this catches is drift, and drift is invisible from inside the rule. One from my own week. The repository that runs my site carries written rules for anyone adding an article, and one of them said articles were English only and told you not to add a Chinese variant of the file. The code had supported an optional Chinese version since September 1, and by the time I corrected the rule on September 21 all fifteen articles on the site had one, every one of them added under a rule that forbade it. Nobody was ignoring the rule. It described a system that no longer existed, and reading it could never have shown that, because drift is only visible against the record.

The comparison itself is not exempt. A status checker of mine built the address of each Chinese article by joining a prefix to the slug, while the real path had a different shape, and it reported ten live articles as dead. A gate that is wrong reports with exactly the same confidence as one that is right, so the output of a gate is evidence about the gate as much as about the system.

One property worth asking for when you pick or build the gate. The policy engine I keep evaluates with explicit unknown and conflict states rather than folding them into allow or deny. "No rule covers this" and "two rules disagree" are different facts from "denied", and an engine that cannot say them will pick one for you, in a direction you did not choose and cannot see afterwards.

Done looks like this: the policy is a file under version control, changing it goes through the same review as code, every decision a gate makes names the policy version it was made under, and the comparison between declared and recorded runs on a schedule rather than when somebody suspects something.

Audit ledger: which policy allowed it

Stage 4 left four fields behind: which agent or person acted, on what, when, and who approved it. This box adds the fifth, which rule allowed it and at which version.

Without the version you can establish that an action was approved and not under which rule. If the rules changed last month, a record that complied at the time and a record that violates the current rule look the same, and the row cannot tell you which question you just answered.

The ledger has to be append-only, written by the system that performed the action, and outside the agent's reach, which is the same requirement stage 3 put on the read log and stage 4 put on the approval record. Mine is short of it in a specific way. My publishing log records time, brand, article, language, title, platform, status, note, and address. There is no policy column at all, and the approver still sits inside the free text. I can answer whether something was published. Answering which rule allowed it means reading sentences and remembering what the rule was that week.

There is a second way to fall short that looks like the opposite. A field can be declared and read by nothing. The syndication block in my own article files records where each piece was republished, and no code reads it. As bookkeeping that is fine. The problem is that a recorded field and an enforced field look identical from the outside, both being structured data in a file, so a review that counts fields scores them the same. A field nobody reads is a note, not a control.

Done looks like this: append-only, written by the acting system, one row per action carrying the policy version in force at that moment, and answering which rule allowed a given action is a query rather than a reconstruction.

Halt & budget caps: stopping a run, not refusing the next call

The third box is the one people assume they already have because they have a rate limit. A rate limit refuses the next call. This box stops the run that is already going.

The difference shows up once a run does many things. By the time call 41 is refused, calls 1 through 40 have happened, and after stage 4 some of them wrote to an internal system or sent something outside. The rate limit was working correctly the whole time.

In my own runtime the stop is at the level of the run: scope violations, LLM call count, tool execution count, and total tokens each have a cap, any one of them being reached stops the entire run, sub-agents it dispatched stop with it, and that run cannot be resumed, only started again. The caps ship switched off, and turning one on is an explicit decision rather than a default.

The cost of that is measurable, and I measured it. In an ablation on a small local model, a simple task, and 20 runs per arm, an enforced gate that stopped the agent from reporting a task done when the artifact was not there moved unsupported completions from 18 of 20 to 0 of 20. Input tokens went to 1.66 times the control arm, and p95 wall clock went from 87 seconds to 169 seconds. Every blocked run gets retried, and the cost of retries lands in the tail rather than the average. Those numbers show that the mechanism works and that stopping runs is not free. They do not predict what a frontier model on your repository will do, and I would not carry them into anyone's production estimate.

There is one more requirement on a stop, which I learned by having it fail. When my continuous integration stopped running because a spending limit had been hit, the jobs did not report that they had been stopped. They came back as failing jobs, within seconds, with no logs, and a red light from a budget cut looks exactly like a red light from broken code. A cap that stops work without saying so produces a false signal rather than no signal, and everything downstream reads the false one. My working rule since has been to write "CI did not run, the evidence is this local test" rather than treating the red as a result.

Done looks like this: a running agent can be stopped by something that is not the agent, the stop is recorded as a stop rather than surfacing as a failure, and somebody can answer what resuming means before the first cap is switched on.

Cost & quality metrics: p95, not averages

The last box in the row. Its label is the whole argument, and the argument is worth having because averages are what gets reported upward.

From the same ablation: in one cell the mean moved by 16 percent while the median went from 18,612 to 37,068 tokens. The middle of the distribution doubled and the average barely registered it. In another cell there were degenerate runs stuck in a read loop at around 197 thousand tokens. An average across runs would have shown neither the doubling nor the loop.

The metric I would put next to cost is not a cost metric. It is the count of runs that reported completion with nothing to show for it, and it has to be counted outside the agent, because the agent is the thing being measured. That was a principle in stage 3 when the question was whether to trust an agent's report. Here it is a column, and 18 out of 20 is what it looked like the one time I counted it without a gate in the way.

My own gap in this box is the plain kind. My publishing log records a status per publish and no duration and no cost, so there is no distribution to take a p95 of. I can count publishes. I cannot measure them.

Done looks like this: median and p95 reported side by side, a count of unsupported completions measured outside the agent, and cost attributed to the per-workflow account from stage 4 rather than pooled into one number for "AI".

The check that repeats

There is no stage 6 and no exit check here, because governance is not a stage you leave. What replaces the exit check is a comparison you run on a schedule.

Take one rule out of the policy file. Pull every action from the ledger that the rule covers, and check each one against the version of the policy that was in force when it happened. A mismatch is the finding. Finding nothing, repeatedly, is also a finding, and usually means the rule covers nothing anybody actually does.

The second half is the same shape as stage 4's refusal test, moved up a level. Start a run that is going to exceed a cap, and confirm that something stops it while it is running and that the ledger records a stop. If the run finishes, the cap is documentation, and every report built on it has been describing a control that was not there.

The GOVERNANCE & OBSERVABILITY row is drawn identically on the self-hosted variant of the diagram, and hosting changes nothing at this stage either: the rules, the ledger, the caps and the metrics are the same whoever holds the weights. The control plane I keep for my own work on this is open source at aine-control-plane. It evaluates policy and writes decisions to an append-only ledger. It does not enforce anything on an external system, which stays where part 4 put it, in the tool layer. Part 6 closes the series by reading the same diagram from the other side, as the eight questions a manager asks before any of this gets budget.

Working on something like this?

I help teams ship AI-native systems — architecture, governable autonomy, and the evidence discipline to back them. One conversation is enough to see whether it fits.

Discuss fit