Case study · SOMOS.tech · October 2026

Letting AI agents fix production without handing them the keys

An AI coding agent can often write the fix for a production bug faster than I can. The product question isn't whether it can. It's what it should be allowed to do without me, and how to make sure that limit holds even when the agent is wrong or someone is trying to trick it.

In short

The problem

SOMOS.tech is a volunteer-run nonprofit whose community reaches 8,000+ people. I'm the only engineer. The platform has 1,100+ API routes, payments, messaging, and real member data, and it has no staging environment: production is the only one. Alerts don't wait for my day job to end.

I already write most of the code with Claude Code, under a set of engineering rules I wrote, each drawn from a real incident. The next step was obvious: let an agent pick up an alert, find the cause, and propose the fix while I'm asleep or in meetings. The risk was just as obvious. An agent that can change production is one bad diagnosis, or one prompt injection, away from touching member data.

Constraints

Options I considered

OptionWhy not, or why
Agent with production access fixes things directlyRejected. Fastest, but the agent's mistakes and anyone who can influence its input would reach production unchecked.
Agent opens PRs, I review every oneSafe, but I become the bottleneck, which defeats the point of coverage when I'm unavailable.
Separation of duties plus a risk gateChosen. The agents do the work, systems they can't touch enforce the limits, and I only spend attention where the risk is.

The design

How an alert becomes a shipped fix Alerts are batched and correlated, then a SRE agent writes a fix and opens a draft pull request. A separate reviewer agent approves one exact commit, and CI runs on it. Both agents sit inside a boundary: no production credentials and no ability to change the gate. A risk gate that runs from main reads the real diff. Low-risk fixes auto-merge; anything sensitive waits for the maintainer, who approves with one click signed for that commit. Then main deploys. low risk sensitive Alerts fire server and browser errors Batch and correlate one run per incident, ranked likely cause SRE agent diagnoses, writes the fix, opens a draft PR Reviewer agent separate agent, approves one exact commit No production credentials. Can't change the gate. CI full test suite on the approved commit Risk gate reads the real diff, runs from main Auto-merge small, nothing sensitive Me, one click signed for this commit main deploys the same pipeline as my own changes
The agents do the work inside the dashed boundary. Everything that decides whether their work ships runs outside it.

The agent that writes a fix can't approve it

A second agent with its own instructions reviews every fix, and its approval names one exact commit. If the branch changes after approval, the approval no longer counts and the fix goes back for review. This is the same separation of duties I'd expect between two engineers, applied to two agents.

Judge the diff, not the description

An agent's summary of its own change is a claim, not evidence. The gate ignores it and reads the files and lines that actually changed. Anything that touches a sensitive part of the platform, or gives the code a capability it didn't have before, is high risk by definition, and a high-risk fix always waits for me, no matter how confident the reviewer is. I'm not publishing the exact rules. They're the first thing someone would want if they were trying to sneak a change past the gate.

Agents can't edit their own rules

The gate's policy lives in a protected part of the repository that only admins can change. The merge workflow always runs the version on main, never the agent's branch, and it reads agent-written text only as data. An agent can propose anything; it can't change what counts as safe.

No keys, and untrusted input is labeled as such

The agents never receive a credential for the production API. Each one is started with a token that can do exactly one thing: start that agent. The alert batch it receives is marked as untrusted, and anything in it that a user wrote is marked again.

The human step is one click, but it's bound to the change

When a fix needs me, I get one message with the reason ("changes member messaging"), and I approve it with one click in the admin portal. The approval is signed for that exact commit, so approving one version can't be replayed onto another. There's also a single switch that stops all auto-merges, and a daily cap.

What actually happened

I paused the first version soon after turning it on. It started one agent per alert, and real incidents arrive as several alerts within minutes, so several agents chased one root cause and opened competing fixes. It was too noisy and too expensive in CI minutes for what it found.

I rebuilt it around standby: the agents only run during windows I choose. Alerts are collected into one batch per incident, grouped by the most likely cause, with the fixes already in progress named so the agent doesn't open a second one. Here are the first three weeks:

FixReviewerGateOutcome
Member feedback emails cut an emoji in halfApprovedNeeds me (member messaging)I approved it. Merged.
Notifications missed members who signed in with a second providerApprovedNeeds me (notifications)Correct, and 3 hours before my own fix for the same bug. Closed as a duplicate.
Agent changes which alerts it receivesApprovedNeeds me (agent services)Correct analysis. Rejected on principle.
Clearer error text in social post alertsApprovedNeeds meClosed to clear the queue before a live event.

The rejection was the most useful result. The agent noticed that some alerts were things only a member could resolve, like an unverified email, and proposed filtering them out of its own input. It was right. I closed it anyway, because an agent deciding what it gets to see is the kind of change that should never come from the agent. I made the same change myself, outside the agent's reach.

Two more lessons came from watching real incidents:

Tradeoffs I accepted

What I'd do differently

What I took from it

Autonomy is a product decision, and you express it in permissions, not prompts. The limits that matter are enforced by systems the agent can't modify, and the evidence comes from what the agent actually changed, not what it says it changed. Start conservative, measure what the agent gets right, and widen autonomy one category at a time when the record supports it. After three weeks the record supports the design. It doesn't yet support more autonomy, and the system is built so that I'm the one who makes that call.


Built with Claude Code on GitHub Actions and Azure. Security-sensitive specifics, like exact limits and the full list of protected areas, are left out on purpose.

← Back to all work