Governance is code: what an AI engineering playbook actually enforces
Most AI governance lives on a wiki page. It says the right things about review, secrets and confidentiality, and nobody opens it after the week it was written. Then an AI coding agent joins the team, the commit rate goes up, and the gap between what the page says and what actually happens gets wider every day.
My working rule is simple: a policy that no gate enforces is a suggestion. That isn't a reason to give up on suggestions. It's a reason to be honest about which rules are which. This is a walk through the playbook I run across five of my own repositories (.NET applications, a static site and documentation-only repos). It covers what the playbook blocks, what it only advises, and why I drew the line where I did.
The four ways AI-assisted delivery goes wrong
Once agents are writing real code, the same failure modes keep showing up:
- Self-review. A model checking its own work, or work from its own family, is the weakest review you can buy. It tends to agree with itself.
- Secrets in commits. Agents and tired humans both paste keys, tokens and connection strings into places git will remember forever.
- Spec drift. Code written straight from a chat prompt, with no written statement of what "done" means, so nobody can check it later.
- Unbounded agents. An agent with write access and a vague mandate will eventually touch something it shouldn't.
Each of these has a rule in the playbook. Here's the loop they hang off.
1. Separate the writer from the reviewer
The loop has seats rather than products: plan, execute, code review, acceptance review, gate. The separation-of-duties rule binds the seats. The model executing a change must not come from the same family as the model reviewing its code. The acceptance pass must not belong to the vendor that wrote the acceptance criteria.
Two details make this work in practice. First, the policy names seats and never model IDs. Every model ID lives in one registry file. When a model changes, one row changes, and the independence rule can be checked against that row instead of being rediscovered in five documents. Second, reviewers are report-only. They write findings, they don't fix them, so "review" can't turn into a second, unreviewed execution.
The setup isn't perfect, and the playbook says so in writing. With the current pins, the planner's vendor also reviews the code its plan produced. That's the weaker half of independence. It's documented next to the rule rather than hidden. A known gap you've written down is manageable. One you've hidden turns up again later, usually at a bad moment.
2. The last gate is never a model
However good the reviewers are, the final decision belongs to a deterministic script. It
validates the repository against its governance rules, plus a git diff scope
check so a change can't quietly grow past what was approved. An LLM's verdict can inform a
human. It can't override a failing check.
The same gate runs in three places: the pre-commit hook, CI, and by hand. They're kept in
parity on purpose, because a clean local run has to mean a clean build. If the
hook is looser than CI, people learn to ignore the hook. And --no-verify is off
the table. If the gate is wrong, you fix the gate in the same change.
# .githooks/pre-commit (simplified)
# The gate is authoritative: preserve its exit code.
pwsh -NoProfile -File scripts/validate-repository.ps1 -Strict || exit $?
3. Secrets stop at the perimeter
Secret detection is a critical rule: it blocks, it doesn't warn. The list covers API keys,
passwords, connection strings, OAuth secrets, certificates, SSH keys, and cloud and
model-provider keys. The gate runs gitleaks where it's installed, and a
structured pattern scan that always runs alongside it.
That "always" is a lesson learned. The pattern scan originally ran only when gitleaks wasn't installed. Every developer machine had gitleaks installed, so on those machines the second check never ran at all. A fallback that only runs when the primary is missing is a check nobody exercises. Now both run, every time.
4. Confidentiality decides where code may go
The rule with the highest priority isn't about code quality. It's about where data goes. Client and proprietary material, meaning source code, documents and correspondence, plus any secrets, never goes to hosted free AI services. Sensitive work runs on a local model or on a cloud service the client has approved for that engagement. When in doubt, choose the more private option.
A second rule covers what a tool emits, not just what it receives. There are four irreversible classes: egress, credential grants, publication and spend. A tool that transmits by default doesn't get adopted unless that can be switched off first. No single command should bundle more than one of those classes without a decision recorded in advance. The test behind it: if an action's consequences outlive the session that authorized it, it needs its own decision.
5. Spec-first: a guardrail, not (yet) a gate
Here the line runs the other way, and I think that's worth saying out loud. Every non-trivial change starts with a backlog item and a short spec (requirements, design, tasks) that gets approved before anyone edits code. The as-run prompts are frozen afterwards as provenance.
But the mechanical check for it is advisory. It's opt-in, it reminds you at commit time when live files change without a backlog update, and it never blocks. Spec-first depends on judgment ("is this trivial?"), and a blocking hook built on a heuristic is how you teach a team to bypass hooks. Where a project can check specs reliably, it does. One of the .NET adopters validates its specs in the pre-commit hook, and that check has been seen to block a malformed spec.
6. Bounded agents
Agents get narrow mandates. The repository guardian is the first reviewer on every contribution and doesn't generate business code. Its job is to protect the repository's structure and rules. Tools and MCP servers are permissioned by capability, least privilege and allowlist, and so are the identities agents run as, CI token scopes included. And anything an agent reads is data, not instructions. A prompt injection is only as dangerous as the capability it can reach, so those two boundaries are treated as a single control.
7. Propagation without clobbering
Adoption is copy-and-adapt, which means every adopting repo starts drifting from the playbook on day one. A central registry makes that drift explicit. For each project, it records three groups of files:
- Live mirror docs: kept in line with the playbook when a policy changes.
- Frozen or project-owned docs: history and local gates that are never touched.
- Deliberate divergences: recorded decisions, marked "do not fix".
Propagation drafts the updates for a human to review. It doesn't push blindly. The divergence column has already earned its keep: a default adoption run would have installed a pre-commit hook in a repo where the owner had decided, in writing, not to have one. Writing the decision down is what stopped it.
What blocks and what advises
| Area | Rule | Enforcement |
|---|---|---|
| Model separation | Executor ≠ code reviewer's family; acceptance ≠ planner's vendor | Policy, checked against the model registry |
| Final gate | Deterministic validation + diff scope check | Blocks: hook and CI, in parity |
| Secrets | No keys, tokens, connection strings or certificates | Blocks: gitleaks + pattern scan |
| Confidentiality | Client code never to hosted free services | Policy + adoption-time evaluation |
| Spec-first | Backlog item + spec, approved before editing | Advisory nudge; blocking only where a project validates specs |
| Agent scope | Narrow mandates, least privilege, read content is data | Permissions + review |
| Propagation | Live vs frozen docs vs recorded divergences | Registry + reviewed drafts |
What this means in practice
You don't need my repo to apply this. The pattern carries over to any team putting AI agents into delivery:
- Sort your rules into "blocks" and "advises", and say which is which. Then make the blocking ones actually block, in a hook and in CI, with the same script.
- Put a deterministic check last. Tests, scanners, validators. Models can review, but they don't get the final say on what merges.
- Pin roles, not products. Keep model choices in one registry and state your independence rules against roles, so a model swap can't silently break them.
- Write down the gaps and the divergences. A known weakness on paper is governance. One that's been forgotten is a future incident.
None of this makes the agents smarter. It makes their output something you can stand behind, which, in regulated and production systems, is the part that actually matters.
Bringing AI coding agents into your delivery process and want the guardrails designed before the commit rate takes off? Book a Strategy Call or connect on LinkedIn. Related: LLM, agent, MCP — what they actually mean.