I work on a digital trust platform: qualified electronic signature, certified delivery, sealed evidence, digital identity. Around fourteen microservices, multi-tenant, running across two clouds and four environments. It is regulated, which mostly means that every question about a system eventually becomes a question about who did what and when.
A year ago I started putting agents into how the team operates. Not writing features. Operating: incidents, dead letter queues, database alerts, release documents, pull request review. Today there are twelve command-line skills in daily use and a shared standards corpus distributed to sixteen repositories.
Not one of them can change anything.
The objection is never the one you prepare for
I went in ready to argue about code quality. Whether the thing writes decent Groovy, whether it understands our domain, whether it hallucinates a method that does not exist.
Nobody asked me any of that. The question a lead actually asks, and asks first, is what happens the day it purges the wrong queue.
It is a good question. On a platform with an audit trail, "the agent decided to" is not a sentence you can say to a compliance officer, a client, or your own head of engineering. There is no version of that conversation that goes well, and everyone in the room knows it before you open your mouth.
Read-only answers the question before it gets asked. That turns out to be worth more than any capability I gave up.
Most of an incident is reading
Take an actual on-call hour apart and look at where it goes.
Something is stuck. You check dead letter depth and find a queue that went from
zero to four hundred at 03:12. You pull the logs around 03:12, not the last hour,
because the window is what matters. You find a trace id and follow that one trace
across three services. You open the handler that threw. You check whether the
error was typed recoverable or terminal, because that decides whether the retry
behaviour you are staring at is correct or a bug. You run git log
against the window and find the deploy.
Fifty minutes. Then the fix is three lines and you already knew which three.
All of that is reading. Every step. The expensive part of an incident was never the change, it was finding out what to change, and that part needs no write access at all. An agent that only reads gives you most of the value and none of the argument.
What is actually underneath
Two pieces, and the boring one matters more.
A standards corpus, split into atomic files
Ten documents. One concern each, under a hundred lines each: how errors are typed across service boundaries, what gets tested against a container instead of a mock, how queues are named. Every one shows the wrong version next to the right one, which is the part that does the work. A rule stated in the abstract gets four readings from four engineers.
Atomic sounds like fussiness until the second month:
- When a rule changes you edit one file, and sixteen repositories get it on the next bump.
- When an agent needs a rule it loads sixty lines instead of the whole corpus.
- When two rules contradict each other, separate files make it obvious. A single long document hides the contradiction, sometimes for years.
There is an eleventh file, INDEX.md, and it is the one people skip.
It is a routing table: situation on the left, document on the right. Without it you
have ten markdown files and a guessing game. With it, a new hire and an agent open
the same file for the same problem, which is a strange and useful thing to watch
happen.
It ships as a git submodule. One corpus, sixteen consumers, each pinning the commit it trusts. The alternative is copy-paste, and copy-paste means four repositories quietly disagreeing about error handling by spring.
Skills that encode operations
The ones that stuck are unglamorous. Reviewing dead letter queues across two clouds. Diagnosing a database alert from the metric that fired it, down to the affected rows. Investigating an incident across queues, logs and source. Reviewing a merge request against the acceptance criteria of its ticket, which is the check no linter performs and the one that catches real gaps. Writing the release document.
Each one ends the same way: a diagnosis, evidence a colleague can chase themselves, and the recommended command printed and not executed.
Read-only is enforced, not requested
This is the part that gets skipped when people describe setups like this, and it is the only part that holds up when somebody asks how you know.
The first layer is the harness. A committed
.claude/settings.json pre-approves the read-only scripts and
puts every mutating one behind an explicit prompt, so it stops for a human
on every single call. There is no flag anyone can set on a busy afternoon
to skip that.
The second layer is the tools. Our read-only database wrapper forces the
session into default_transaction_read_only=on, which means an
UPDATE fails at the database itself, whatever the credentials
in use would have permitted. The queue reader peeks messages with requeue
and has no code path that acknowledges one.
That difference matters more than it looks on the page. A prompt that says do not write is an instruction, and instructions hold until they do not. A connection that cannot write is a property of the system, and it holds identically for a new hire, a misread ticket, and a model having a bad day.
It also changes the conversation. "We told it not to" invites a discussion about how much you trust a model. "It has no write path" ends one.
Half of what I wrote is dead
Twelve skills survive. I wrote considerably more than twelve.
The pattern is consistent enough that I now use it as a filter. The skills that live automate something I did on a Wednesday and resented. The skills that die were written because they sounded impressive in my head on a Sunday. There is no middle category.
So write them cheaply. A skill is a markdown file. If abandoning one costs you an afternoon, you will keep a bad one alive out of sunk cost, and a bad skill is worse than no skill because someone will trust it once.
Three things I got wrong
I started with the corpus. Ten careful documents that nobody read, because nothing needed them yet. The order that works is one skill first, one person, no kickoff meeting. The standards get written when a skill starts needing a rule to point at.
My first documents were too long. Four hundred lines of testing guidance that was really six standards in a trench coat. Nobody read those either. Splitting them was what made the corpus start being used.
I cannot tell you how much time this saved. I never instrumented it, and I am not going to reverse-engineer a percentage for the sake of a tidier article. What I can tell you is narrower and true: nobody on the team has written a release document by hand since March, and the first thing I now do with a stuck queue is ask for a diagnosis instead of opening four browser tabs.
The rule, restated
Read-only by default. Human approval for anything that mutates state. No exceptions, including the obvious ones.
The temptation arrives in week one, when the fix is trivial and the agent could just apply it and you are tired. The moment an agent can change production on its own judgement, the conversation in your company stops being about time saved and becomes a risk review, and six months of goodwill goes with it. I would rather keep the goodwill and type the command myself.
The scaffolding, if you want it
I put the pattern on GitHub as agent-ops: the atomic standards structure with its routing index, three read-only skills, and a five-day adoption path that starts with one person and no meetings. MIT.
It deliberately does not contain my team's skills. Those encode a specific stack and would be useless to you. What transfers is the shape, and the shape is most of it.
If you are trying to do this inside a team where someone is going to ask the audit question, write to me at hi@ferlogs.com. I am interested in the ones where the answer has to hold up.