2421 words
12 min read

AI Agent Governance — One Engineer, Over a Hundred Repositories and the Decisions No Agent Makes

By · Docker Captain · CNCF Ambassador · IBM Champion
Steel block with three guarded switches, AI agent governance

Thirty days, over a hundred public repositories, 3,380 commits to main. AI agents wrote most of the 3,072 that carry my name. The commit log cannot tell my work from theirs, so AI agent governance cannot rest on who made a change. It rests on decision rights.

The public argument about agents is about capability: how close the models are to doing everything. The exposure inside a repository is quieter, and it sits in defaults that decide on the agent’s behalf, written for teams with reviewers to spare. GitHub’s documented recipe for merging dependency updates automatically will merge a pull request whose checks are red, as long as none of them is marked required, and nothing marks a check required by default. The fleet’s own workflow has said so since September, the command’s source confirms it, and on 18 October I watched it merge a red pull request in two seconds.

This is the other half of the problem I wrote about this month in AI reviews that read nothing: an agent’s account of its own work is not evidence. A default nobody chose is not a decision.

What AI agent governance looks like when every commit has one author#

Required reviews and required checks are the standard answer. The first assumes a second person. The second assumes a rule in every repository that names the right job and is kept current as jobs change. This fleet has one maintainer, me, plus my agents, and it carries neither, by decision 16: branch rules block force-push and deletion, and nothing else. In that state, this is the recipe GitHub documents for merging Dependabot’s pull requests:

# dependabot-auto-merge.yml, shortened from the GitHub documentation recipe: safe only where a branch rule requires the checks
name: Dependabot auto-merge
on: pull_request
permissions:
contents: write
pull-requests: write
jobs:
dependabot:
runs-on: ubuntu-latest
if: github.event.pull_request.user.login == 'dependabot[bot]'
steps:
- name: Enable auto-merge for Dependabot PRs
run: gh pr merge --auto --merge "$PR_URL" # nothing required: gh merges at once, red checks included
env:
PR_URL: ${{ github.event.pull_request.html_url }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

The documentation pairs it with advice to require status checks on the target branch, and with that rule in place the recipe is sound. Without it, the GitHub command line does not wait. Its source merges at once in three merge states, and two of them matter here: clean, and unstable, which GitHub defines as mergeable with a non-passing commit status.

A dependency update whose verification failed is still mergeable, because nothing that failed was required. It merges the moment the job runs.

I reproduced it on 18 October in a private throwaway repository with no branch rules and one check that fails on every pull request. With the check red, GitHub reported the merge state as unstable, and the documented command merged the pull request two seconds later without ever switching auto-merge on. Then I added one ruleset that requires that check and opened a second pull request. The same command switched auto-merge on and waited, and nothing merged.

So the ruleset is the right fix wherever someone maintains it. Across more than a hundred repositories it is a string that has to match a job name in each one, and a renamed job blocks every merge in that repository until somebody edits the rule.

The workflow below carries the dependency inside itself. It cannot run before the verification finishes, because the verification finishing is what starts it.

# 18 October 2026, GitHub CLI 2.97.0, a private repository whose only check fails on every pull request
pull request 1, no rule requires the check
before: mergeable=MERGEABLE mergeStateStatus=UNSTABLE verify=FAILURE
gh pr merge --auto --merge -> merged at 15:52:09 UTC, auto-merge never enabled
pull request 2, a ruleset requires verify
before: mergeStateStatus=BLOCKED verify=FAILURE
gh pr merge --auto --merge -> auto-merge enabled at 15:52:42 UTC, not merged
# dependabot-automerge.yml, shortened from the workflow every template here carries: the verification on that commit decides
name: Dependabot auto-merge
on:
workflow_run:
workflows: ["Deployment Verification"]
types: [completed]
permissions:
contents: read
jobs:
merge:
if: github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'pull_request'
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
actions: write
steps:
- env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPO: ${{ github.repository }}
SHA: ${{ github.event.workflow_run.head_sha }}
VERIFY: ${{ github.event.workflow_run.path }}
run: |
pr="$(gh api "repos/$REPO/commits/$SHA/pulls" \
--jq '.[] | select(.state=="open" and .user.login=="dependabot[bot]") | .number' | head -1)"
[ -n "$pr" ] || exit 0
gh pr merge "$pr" --repo "$REPO" --squash --delete-branch
# a push made with this token starts no workflows, so verify main as merged
gh workflow run "$(basename "$VERIFY")" --repo "$REPO" --ref main

Here the decision belongs to the repository’s own verification, run on that commit. A red run means the job does nothing. The update waits for a person. The last line closes a second gap: a merge pushed with the job’s own token starts no workflows, so main carried a merge nobody had verified. On 17 September that was 14 repositories out of 94, up from four two days earlier.

In a decade of production infrastructure, the controls that held never trusted who made a change; they checked what the change did. Authorship is not a control. Verification is.

Why this keeps happening#

Governance for agents is borrowed from governance for people. A human team scales review by adding reviewers. An agent multiplies the changes and adds no one, so the second pair of eyes that every default assumes is the one resource that shrinks. What is left is to approve faster or to let the agent approve.

Either way, the audit trail records a person approving everything.

Credentials make it worse. An agent running under an engineer’s account inherits whatever that account can do: spend, publish, delete. Nothing in the account knows whose hand is on the keyboard. Decision 7 took one class of key out of reach. The model credentials come from workload identity federation, so no API key exists to leak, and the only thing that can spend them is a workflow on one branch of one repository.

The good defaults are invisible too. Since 14 July 2026, Dependabot waits until a release has been on its registry for three days before proposing it, unless a configuration file shortens the wait or switches it off. Security updates are exempt and open at once. A default that needs no configuration is also a default nobody notices being turned off, which is why a fleet rule here fails any repository that shortens it.

A default is a decision made for a team that is not yours.

Risk and blast radius#

Change management is what an auditor reads first. The change-management criterion of SOC 2 expects each change to be authorized, tested and approved before it is implemented. When an agent commits under an engineer’s account and auto-merge decides, the record shows the engineer authorizing every change. The engineer saw none of them. The auditor’s finding lands on the control.

The usual answer is to give each agent an account of its own. That fixes attribution, which an auditor will ask for: the record would say which changes an agent wrote. It does not fix authorization. An agent account with write access merges what the engineer’s account merges, and the documented recipe merges its red pull requests the same way. Attribution answers who. The control has to answer whether.

The supply chain is the second exposure, and it is measured. This fleet merged 309 dependency updates in those 30 days, a median of 4.3 minutes after each was proposed. Fast is right when the verification decides and the release is three days old. Without the cooldown, a poisoned release could reach main within minutes of being published, and with a red check that is not required, it would reach main anyway. The blast radius of a template fleet is every server that pulls a template, not the repository. The recipe travels, too. Copied into a customer’s repository by a consultant or a forward-deployed engineer, it inherits that repository’s branch rules, or their absence, and nobody on either side has looked.

Three exposures never show up in CI at all: what an agent spends, what it publishes and what it destroys. Those are the three decisions kept out of its reach here.

Ways to decide whether a change merges, compared#

Who decides the mergeNeeds a second personWhen the verification is redHuman time per updateFits
A required human reviewyesblocked, if the reviewer reads the checksminutes of reading, every timeteams with reviewers to spare
Auto-merge with required checksno, but someone keeps the required list currentblockednonerepositories whose branch rules require the checks
Auto-merge without required checksnomerged at once: the state is unstablenonenowhere
A merge triggered by the verification’s own successnonothing happens, the update waitsonly when the run is redone maintainer with agents, as here
The agent merges on its own reportnowhatever the agent saysnonenowhere

The table is about who holds the decision. Four of the five merge within minutes either way.

Framework: three decisions held by mechanism, the rest decided by checks#

Layer 1: write the three decisions down as configuration (week 1)#

Money, publication and anything that cannot be undone stay with a person, and each is held by a mechanism rather than by trust. History is the cheapest to hold: one ruleset on every default branch in the fleet, with no force-push and no deletion. Money takes a budget whose action attaches deny-all to the role that creates resources once spending crosses a ceiling, which the AWS bootstrap sets at thirty dollars a month. Publication needs no code. Nothing reaches this site without my go-ahead, and the newsletter and the podcast follow only from that. Credentials reach a tool from the keychain on standard input, never as an argument a process list would show.

Terminal window
# rules.sh, with the GitHub CLI: the one ruleset every default branch here carries
gh api --method POST "repos/${OWNER:?}/${REPO:?}/rulesets" --input - <<'JSON'
{
"name": "default branch: no rewrite, no delete",
"target": "branch",
"enforcement": "active",
"conditions": { "ref_name": { "include": ["~DEFAULT_BRANCH"], "exclude": [] } },
"rules": [{ "type": "non_fast_forward" }, { "type": "deletion" }]
}
JSON
# a credential reaches the CLI on standard input from the macOS keychain, never as an argument
security find-generic-password -s github-fleet -w | gh auth login --with-token

Owner: the person whose account the agents commit under, with the security lead signing off the list of decisions that stay human.

Layer 2: replace Dependabot auto-merge with the repository’s own verification (week 2)#

Replace the auto-merge flag with the workflow above: triggered by the verification’s completion, merging only on its success, write permissions on the one job that merges, and a second verification on main as merged. Then write the cooldown into the configuration. A later edit that shortens it becomes a visible diff instead of a silent default.

# .github/dependabot.yml: Dependabot's three-day cooldown stated, so shortening it shows up in review
version: 2
updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
cooldown:
default-days: 3

Owner: the platform team, which also owns the rule that fails a repository for turning the cooldown down.

Layer 3: prove every gate with a planted violation, and publish the misses (week 3 onward)#

A check an agent cannot argue with is a check that has been shown its own violation. The reference architectures here plant 101 changes that CI must refuse and 30 manifests the running cluster must refuse, against 7 it must admit, and a daily sweep shows every fleet rule a planted breach first. A check that stays quiet, or refuses for the wrong reason, fails the build. What still gets through goes into the ledger with the rule it produced. Five entries came from 15 October alone; one of them is three gates that stayed green because a pipe swallowed their exit status.

Terminal window
# plants.sh, in bash: each line of plants.tsv is a file, the text to replace, its replacement, and the check that must name it
set -uo pipefail
missed=0
while IFS=$'\t' read -r file old new check; do
[ -n "$file" ] || continue
cp "$file" "$file.orig"
body="$(cat "$file")"
if [[ "$body" != *"$old"* ]]; then
echo "stale plant: $file no longer contains its text"
missed=1
else
printf '%s\n' "${body/"$old"/"$new"}" > "$file"
out="$(./run-checks.sh 2>&1 || true)"
if grep -qF -- "FAILED $check" <<<"$out"; then
echo "caught by $check: $file"
else
echo "MISSED by $check: $file"
missed=1
fi
fi
mv "$file.orig" "$file"
done < plants.tsv
exit "$missed"

Owner: SRE, with the security lead reading every miss and every stale plant.

Tradeoffs#

Holding decisions by mechanism costs speed exactly where speed is dangerous. A dependency update waits three days for its release to age, then a median of 4.3 minutes for verification. One opened while verification is broken stays open until a person looks, and two sat for four and seven days in a private repository before the heartbeat learned to look there. The model jobs cannot run from a laptop. OpenSSF Scorecard’s code-review check stays at zero, and the evidence page says so beside the score.

The alternative has a number too: 309 updates in 30 days, at five minutes of human reading each, is more than 25 hours a month spent approving lockfiles, and that review becomes a rubber stamp by the second week. The machine spends a median of 4.3 minutes on each and asks a person only when it says no.

The closing argument#

Agents will keep getting faster at writing changes, and arguing about how capable they are does not change what they are allowed to do. In this fleet they write, test and merge anything a check can judge, under my name. They do not spend past thirty dollars, publish or rewrite history. Capability is the vendor’s problem. Decision rights are yours.

Sources#

Discussion#

If you run AI agents against a repository with one reviewer or none, or you have a reason to trust auto-merge without required checks, drop a comment below. Counterarguments are welcome and the comment thread is where I respond first. For longer back-and-forth with senior practitioners, join the discussion on Discord.


Vladimir Mikhalev

Docker Captain  ·  IBM Champion  ·  AWS Community Builder

The Verdict — production-tested analysis on YouTube.

The Verdict

Inconvenient truths about shipping in the AI era

Container security, platform engineering, and the agentic shift — tested in production, argued without the hype. The verdict reaches your inbox the moment there's one worth sending.

Related Posts

Same category
  1. 1
    AI Reviews That Read Nothing — Thirty-Four Verdicts in a Hundred
    AI & MLOps · A third of the images my fleet pins would have had their next AI review written against nothing. How to prove what an AI review read before anyone acts on it.
  2. 2
    watsonx.governance Enforcement Tracking — Sampled Compliance and the Unevidenced Run
    AI & MLOps · EU AI Act Article 12 wants a per-event record. Enforcement Tracking evidences thresholds on a schedule. What that gap costs, and the join key that closes it.
  3. 3
    The Intake Gate Your CISO Is Missing — 300 Million AI Chat Messages Were Public by Default
    AI & MLOps · Half of AI-enabled apps on major backends carry severe misconfigurations. Inside the 300M-message Firebase breach and a 3-layer protocol to stop Agent Sprawl.
  4. 4
    Docker MCP — Turn GPT into a Real DevOps Assistant (Slack, GitHub, Stripe)
    AI & MLOps · Turn GPT into a real DevOps assistant with Docker MCP. How AI agents automate Slack, GitHub, Stripe, and more — securely and at scale.

Random Posts

Random
  1. 1
    Optimal Active Directory Structure
    SysAdmin & IT Pro · Learn how large enterprises design scalable and secure Active Directory structures. Explore best practices for organizing domains, OUs, users, and servers.
  2. 2
    Install Windows Admin Center on Windows Server 2019 Server Core
    SysAdmin & IT Pro · Install Windows Admin Center on Windows Server 2019 Server Core — a PowerShell guide covering domain setup, secure access, and server management.
  3. 3
    Install ownCloud Using Docker Compose
    Self-Hosting · Learn how to install ownCloud with Docker Compose on Ubuntu using Traefik and Let's Encrypt. Secure, scalable file storage and sharing for your server.
  4. 4
    Install Exchange Server 2019 on Windows Server 2019
    SysAdmin & IT Pro · Step-by-step guide to install Exchange Server 2019 on Windows Server 2019, including prerequisites, Active Directory setup, and admin tips.
AI Agent Governance — One Engineer, Over a Hundred Repositories and the Decisions No Agent Makes
https://heyvaldemar.com/ai-agent-governance-decision-rights/
Author
Vladimir Mikhalev
Published
2026-10-19
License
CC BY-NC-SA 4.0