The Governed Environment: Ex-Ante and Ex-Post

Everything so far assumes you know, in advance, what should be true about your software. If you do, the path is easy: write it all into a specification up front and hold the agent to it. But real software engineering rarely works that way — you discover what you are building by building part of it. So a governed environment has two halves: what you can decide before you start, and what you can only learn along the way. This chapter is about both halves, and about the two forms every governance mechanism finally takes.

Ex-ante: everything you can decide up front

Some things you genuinely know from the beginning, and you should write every one of them down. This is specification-driven development, and where you can do it, you should. A coding style guide is the everyday example — Google has one, Airbnb has one — full of rules like "every function carries a comment" or "we never call exec outside this one approved place." Give the agent those rules, and then write deterministic checks for them. If a program's output has a required format, write the checker that confirms it — or, better, have the agent write the checker. Determinizing what you know up front gives you a much stronger starting point, and it costs you almost nothing, because the checks are cheap and the agent writes them.

Ex-post: everything you learn by building

But you will not know everything, and pretending otherwise is the mistake. You will discover that performance is a problem and you have no performance model. You will discover that a particular model version has a tic — every LLM's fondness for the em-dash is the running joke — and that the tics move when you upgrade. So you will build governance ex-post: after the fact, iteratively, strapping on new mechanisms as the failures reveal themselves and as the software's real properties emerge. You define what you can up front, and you stay willing to discover the rest — the mistakes you are making, the mistakes the model is making, the gap between what you thought the customer wanted and what they did, between how you thought the code would perform and how it did.

The engine of the whole discipline is what you do with those recurring mistakes: you convert each one into a mechanism. This is the organizing move of the companion catalogue — a failure seen twice becomes a mechanism, so it is enforced on every agent thereafter instead of re-caught by inspection. A mistake caught in yesterday's manual audit becomes today's automatic check. There are two forms that conversion can take, and the difference between them is the difference between a smoke detector and a firewall.

Constraints and sensors

Two forms a failure-to-mechanism conversion can take: a control or an architecture A recurring failure, seen twice, is converted into a mechanism, which takes one of two forms. On the left, a sensor: it lets the mistake happen but detects it and surfaces it in time to stop the damage, like a test suite in CI or a smoke detector. On the right, a constraint: it makes the whole class of mistake impossible by scoping the action space, construction, like choosing a typed enumeration over free-form strings so an unlisted value cannot be represented, like a firewall. The control watches for the bug; the wall forbids it. The wall is cheaper to live behind than the sensor is to keep reading. A recurring failure seen twice — convert it into a mechanism Sensor — detects Lets the mistake happen, detects it before it ships. test suite in CI · smoke detector Constraint — prevents Makes the whole class of mistake impossible. typed enum over strings · firewall
The two forms a failure-to-mechanism conversion can take. A recurring failure, seen twice, becomes a mechanism — either a constraint or a sensor. A sensor lets the mistake happen but detects it in time to stop the damage, and fails the loop iteration so the agent runs again to fix it (a test suite in CI, a smoke detector). A constraint scopes the agent's action space so the whole class of mistake is impossible, and no iteration is wasted (a typed enumeration over free-form strings, a firewall). The sensor watches for the bug; the constraint forbids it.

A sensor detects drift. It lets the mistake happen and catches it after the fact — and because it fires after, it fails the loop iteration: the agent has to run again to fix what the sensor caught. Your test suite in CI is a sensor, and so are your lints. It would be better if the model never wrote the bug, but if it does, the sensor catches it before the code ships. The cost of a sensor is iterations — detect, fail, re-iterate — and that cost is the reason you do not reach for one first.

A sensor is only as good as the signal it can read. Observability is the property that a running system exposes enough signal to explain what it did and to catch what went wrong. It is the wiring under every sensor: the event a gate fires on, the log a checker reads, the trace that turns "the build failed" into "the build failed here, for this reason." Build the observability first and the sensor has something to watch; skip it and it stares at a blank wall.

A constraint prevents drift. Instead of detecting the mistake after the fact, a constraint scopes the agent's action space within the iteration, so the wrong move is never available to pick. The cleanest example is types. Models are, by default, careless with types — and a compiler exists precisely to confirm that functions are called with the right arguments. Suppose the agent has represented a state with bare strings. Because it is a language model, it will cheerfully reach for a synonym next time, and the synonym silently fails to match — and you only learn that an iteration later, when a sensor, the test suite, crashes. Tell it to use an enumeration instead, and the whole failure mode evaporates: it can no longer invent an unlisted value, and if it tries, the compiler rejects it on the spot. Choosing the enum over the string is not a sensor that watches for the bug — it is a constraint that forbids it, and because the wrong value can never be picked, no iteration is spent catching it. This is why the catalogue leans so hard on typed models and closed enumerations over free-form strings: a constraint that removes a mistake costs no iteration, while a sensor that catches it costs at least one.

<!-- gloss-only: Constraint | A governance mechanism that prevents drift by scoping the agent's action space so the wrong move is unavailable within a loop iteration (a typed enum instead of a string); the prevented mistake costs no failed iteration. --> <!-- gloss-only: Governance mechanism | A discrete named artifact that keeps the fleet's work aligned by preventing or detecting drift; the umbrella for a constraint or a sensor, replacing the ambiguous word "control". --> <!-- gloss-only: Governance package | A mechanism that bundles several: a soft constraint (a typed model that aims the agent) shipped with hard sensors (the lints and drift-parity gates that catch what the constraint only aims at). --> <!-- gloss-only: Sensor | A governance mechanism that detects drift after the fact and fails the loop iteration, so the agent must run again to fix what it caught (a lint, a test, a gate); it costs iterations. --> Two axes cross here, and it pays to hold them apart. A mechanism has a formsoft, it aims the agent, or hard, it holds regardless — and a move — a constraint, it prevents, or a sensor, it detects. The two are independent: a constraint can be soft (a typed model that aims the agent's reasoning) or hard (an enum the compiler enforces), and a sensor can be soft (a convention that reminds) or hard (a lint that blocks). Constraint-versus-sensor is what the mechanism does; soft-versus-hard is how firmly it does it.

Most real mechanisms are a package across both axes. A typed model is a soft constraint on the agent's reasoning — it aims the agent at the right shape without forcing it — and it ships with hard sensors, the lints and drift-parity gates that catch what the soft constraint only aims at. The definition-of-done review is the same shape: a runbook that constrains the steps, wrapped in sensors that flag a drifted one. So name the package, and tag its primary move rather than forcing it into one bin: prefer a constraint where you can build one, and add a sensor for the drift you cannot scope away. ("Architecture" is the design activity that builds constraints — not a third kind of thing.)

You will place some of each up front, from what you know about the domain, and add more of both as you go. That combination — determinize what you can, then convert every recurring surprise into a constraint or a sensor — is the governed engineering environment.

Contents
© James C. Davis, 2026–present