The most load-bearing rule in agentic operations: agents never mutate systems directly — every change lands as a reviewable diff on a branch. Not a limitation you tolerate but the adapter that lets an unpredictable component plug into a control plane you already trust: branch protection, required checks, CODEOWNERS, merge audit.
The rule holds whatever the change is — infrastructure, config, docs, or an incident remediation. GitHub's Copilot coding agent, GitLab Duo, and Atlassian's Rovo Dev all converged on exactly this shape, because the PR is the one approval artifact every engineering org already knows how to govern.
Paged. A payments fintech; Marcus, the SRE on call: elevated 429s on the checkout API.
He finds it: the rate-limiter config on the edge proxy says burst: 50, but the value that has been reviewed, load-tested, and recorded in the repo is burst: 200. Somebody "temporarily" changed it during last month's incident and never changed it back — classic config drift.
He does what tired on-call engineers do: edits the live config directly, reloads the proxy, watches the 429s fall, and goes back to bed. No commit, no diff, no reviewer, no record beyond a Slack message that scrolls away.
The proxy is redeployed from the repo, the drift silently reverts, and the same page fires again — incident number two, caused by the undocumented fix to incident number one.
Put an agent holding write credentials to the live proxy in Marcus's seat and every risk gets worse: it acts faster, acts at 2 am without judgment, leaves the same nonexistent paper trail, and if it is wrong — hallucinated key, wrong service, overcorrection — the mutation is already live before any human has seen it.
Direct-write agents fail exactly the way direct-write humans fail, at machine speed.
propose_change() is the agent's only way to change the worldMarcus gets a page that links to a ready PR. He reads the diff on his phone — reviewing three lines is a two-minute job even at 2 am — approves, and merges; the existing deploy pipeline applies it to the proxy.
Total human effort: two minutes of judgment instead of eleven minutes of spelunking plus an unrecorded mutation. And the paper trail is complete by construction: the diff, the author identity, the rationale, the approval, and the deploy are all in git history, because they could not have happened any other way. Three weeks later the redeploy changes nothing, because the repo and the live system agree.
The deeper point: this one adapter makes every agent pattern safer without redesigning any of them. A remediation agent, a doc-updating scribe, an IaC generator, a dependency bumper — the moment their output is "a branch plus a PR," they all inherit the same enforcement plane:
You govern one choke point instead of N agents.
Do not try to make agents trustworthy enough to write to production — make production writable only through PRs, and let branch protection and CI do the distrusting for you. Safety lives in the architecture, not in the prompt.
Client-safe by design — the same lab runs on an approved tenancy (Claude on AWS Bedrock, Azure OpenAI) or fully local via Ollama, with hostnames, service names, and IPs stripped from configs before prompting, or the drift comparison done deterministically in code with the model only drafting the rationale. Full setup is in the build guide below.
Blue = AI-added step · Amber = human control point · Gray = your existing tools.
| Step | Who acts | What changes | Why it matters |
|---|---|---|---|
| Agent detects a needed change | AI agent (read-only) | Drift, misconfig, stale doc, or fix identified with evidence | Detection at machine speed and patience — the part humans do worst at 2 am |
Agent calls propose_change() | AI agent | The change becomes a branch + commit — the agent's only write path | The mutation is quarantined: it exists, but nothing running in production has changed |
| Commit under agent identity | AI agent | git log shows exactly which agent authored what, with rationale in the message | Attribution survives forever; "who changed this?" always has an answer |
| PR description generated | AI agent | Rationale, evidence, and a reviewer checklist arrive pre-assembled | Reviewer effort drops from investigation to verification of stated claims |
| CI verifies the branch | Your existing CI | Tests, linters, policy checks run on the agent's diff | Agents get zero verification discount — same gates as human commits |
| Human reviews the diff | Engineer | Approve, request changes, or close — a bounded, evidence-backed decision | The human gate operates at its best: small diff, stated intent, green checks |
| Merge triggers deploy | Your existing pipeline | The change reaches the live system through the same path human changes take | One deploy path to secure, observe, and roll back — not a special agent side-door |
| History becomes the audit sink | Git provider | Diff + author + approver + timestamps recorded by construction | Compliance and postmortems read git history instead of reconstructing Slack archaeology |
| Component | In this lesson's build | In your stack |
|---|---|---|
| Event source | Drift check comparing config/desired.yaml vs config/live.yaml | Scheduled drift scans, terraform plan in cron, alert webhooks, dependency-update feeds |
| Context stores (read) | The two config files + git history | Config repos, live-state exports, CMDB, monitoring APIs via MCP servers |
| Action interface (allowlisted writes) | pr_gate.propose_change() — a branch + commit; nothing else exists | Push rights restricted to agent/* branches on GitHub/GitLab/Bitbucket; zero credentials for live systems |
| Approval channel | You, reading proposals/*.md and the branch diff | PR review + CODEOWNERS + required approvals; chat notification via Slack/Teams |
| Verification | verify.sh runs on the branch before the proposal is announced | Required CI checks on the PR: tests, terraform validate/plan, OPA/Conftest policy |
| Audit sink + identity | Git commits authored as drift-agent@agents.local | Per-agent bot accounts (GitHub App, GitLab service account); protected main; merge audit logs |
What you tune here is merge autonomy, and the ladder is per change-class, not per agent.
The PR is the approval artifact, so "advise-only" here just means PRs that humans merge — which is already the design.
Only when the class is narrow, reversible, and boring — e.g. patch-level dependency bumps or doc typo fixes. Encode the class in a merge-queue rule, not in the agent.
Anything touching credentials, IAM, network policy, data migrations, or paths outside the agent's declared scope.
Review cadence: monthly — sample five agent PRs, check unedited-approval rate, and re-affirm which classes (if any) keep auto-merge.
The pattern's integrity depends on the agent holding no other write credentials. An agent that opens PRs but also holds a live kubeconfig "for emergencies" is a direct-write agent with good manners — the enforcement plane only works when the PR path is the only path.