Stripe puts its AI agents in boxes. I did the same with my accounting system.
What caught my attention in Stripe's Minions, what I borrowed, what I left out on purpose, and how my own agents found the holes in the box I built for them.
In February, Stripe published two articles (part 1 and part 2) about Minions, its internal coding agents. The number everyone repeated was the PR count: more than 1,300 a week merged into main, written start to finish by an agent, reviewed by people but without a single line of human code.
The number didn't move me much. What stopped me was an idea from the second part: the system gets more reliable the more they lock the model into small boxes. Reading it, I realized something uncomfortable about my own agent workflow: almost everything that protected it was a paragraph in a Markdown file.
This article is about how I changed that.
What Stripe does, in five ideas
- Unattended, end to end. An engineer tags the bot in a Slack thread and the next thing they see is a PR that passes CI, with no interaction in between. They do it because the company's scarcest resource is developer attention, and an unattended agent lets you launch several tasks in parallel.
- Isolation is what enables autonomy. Each run happens on a devbox, the same development machine humans use, ready in about 10 seconds and cut off from production and the internet. Since a mistake can only break that one devbox, the agent runs with full permissions and no confirmation prompts.
- Blueprints. The flow is a state machine written in code that mixes two kinds of node: deterministic ones (run linters, push), which never call an LLM, and agent ones (implement the task, fix CI), which do make decisions. Whatever can be anticipated is done by code.
- Curated context, not abundant context. They have almost no global rules: rules load per subdirectory as the agent walks the repo. Their internal MCP server has close to 500 tools, and each agent gets only a small subset.
- Feedback shifted left, with a cap. Anything that would fail in CI is caught earlier, by local linters that answer in under five seconds. And there are at most two rounds of CI: after that, the PR goes back to a human.
Underneath it all is one idea that sums up their philosophy: what's good for humans is good for agents too. They didn't build new infrastructure for AI; the agents use the same tools their engineers have been tuning for years.
My context: one person, not a thousand
I'm building PyME Contable, a concept-based accounting system for small construction companies in Colombia, meant to grow into an ERP. Laravel 13, PostgreSQL 17 and a React SPA in TypeScript, all in Docker. I'm the only developer, working with an accountant who validates the accounting and tax rules.
Some things in this system are never negotiable: a journal entry always balances, a posted document is never modified, a company never sees another company's data (RLS in Postgres), sequential numbers have no gaps, and an amount is never a float. When two goals collide, the order is fixed: accounting integrity, security, compliance, performance, simplicity and, last, convenience.
My agent workflow existed before I read Stripe. It's the evolution of the one I described in the previous article:
- Architect (the main session, Opus): plans, delegates and doesn't write code.
- Tester (Sonnet): writes one test and watches it fail before the code exists.
- Coder (Sonnet): writes the minimum to turn it green, without touching the test.
- Code, security and performance reviewers (Opus): read, probe and report; they don't fix.
- Me: I approve every plan and every commit.
The hole Stripe showed me
Comparing it to blueprints, I saw the problem. "The tester only writes in the tests folder", "the coder never touches tests", "reviewers don't write to the database": all of those were instructions. Sentences in .claude/agents/*.md that the model reads and, almost always, respects.
Almost always is not a guarantee. A model can forget a rule in a long conversation, interpret it creatively or, worse, receive contrary instructions from a dependency's README or a web page. In a system whose first priority is accounting integrity, "almost always" isn't enough.
Stripe doesn't trust the agent to behave: it trusts the box. That's what I wanted to borrow.
What I didn't borrow, on purpose
Before what I did borrow, what I didn't:
- No unattended runs and no parallelism. Stripe needs them because its bottleneck is the attention of thousands of engineers. Mine isn't volume, and my approvals aren't an obstacle: they're part of the control.
- No custom harness. Stripe forked an open-source agent. With a single developer, that's pure overengineering. I use Claude Code and its mechanisms: subagents, hooks and permissions.
What transfers from Stripe are the patterns, not the parts.
What I did borrow: deterministic gates
I turned the most important rules into gates: code that always runs and doesn't depend on the model remembering anything. I numbered them G0 to G5 so I could close them one by one.
| Gate | What it does | Stripe idea | Status |
|---|---|---|---|
| G0 | One branch per step, PR into a protected main, required CI and no admin bypass |
Push and CI as deterministic nodes | Done |
| G1 | A hook that intercepts every tool call and allows or blocks it based on the agent's role | Put the LLM in a box | Done |
| G2 | Fingerprint of approved tests: if the coder touches a test, it doesn't pass | Code handles what can be anticipated | Pending |
| G3 | SQL mutations: deliberately break each Postgres invariant and require a test to fail | Automated feedback to iterate against | Pending |
| G4 | CI with actionlint, hadolint, composer audit and npm audit, which I also run locally before the PR |
Shift feedback left | Done |
| G5 | Retire LLM review where a linter does better (CI, Dockerfile, lockfiles) | An LLM only where judgment adds value | Pending |
G1: the box
The core piece is a Claude Code PreToolUse hook: a Python script that receives every attempt to use a tool, along with the type of agent making it, and answers yes or no. The rules, in short:
- The tester can only write to
backend/tests/**and to the frontend's*.test.*files. - The coder can't touch tests,
docs/,.claude/,CLAUDE.mdor.env, not even through Bash with>,sed -iorcp. - Neither of them runs git operations that write, migrations, or dependency installs.
- The code reviewer can only use
git diff,status,log,showand read files. - The security and performance reviewers can query Postgres, but as the application role, never as superuser, and any SQL that writes must run inside
BEGIN ... ROLLBACK. - Any other subagent is read-only.
- If the script crashes or Python is missing, it blocks. It fails closed.
This is what a reviewer sees when it tries to delete a table outside a transaction:
Blocked by .claude/hooks/role_guard.py: writing SQL must run inside
BEGIN ... ROLLBACK, never COMMIT. If you need it, stop and ask the architect.
exit=2
The same query wrapped in BEGIN ... ROLLBACK goes through. The instruction in the Markdown is still there, but it's no longer the only thing.
My agents reviewed the box, and broke it
I ran the hook through my own workflow: the code reviewer and the security reviewer in parallel, on the uncommitted change. They found things nobody had seen: ways to slip past the rules with quoting and heredocs, a psql meta-command left open, and a test for the hook itself that passed for the wrong reason, because the command it was supposed to block was already blocked by another rule. A test that can't fail proves nothing, which is exactly what this workflow exists to prevent.
My rule, like Stripe's, is at most two rounds of fixes. Here I ran a third one, as an explicit exception, because the findings crossed real boundaries.
Then the architect pointed out something no reviewer had said: the subagents run in WSL as my user. Any interpreter, a simple python3 -c that writes a file, slips under the hook without it being able to read what it does. And there's something worse: the tester and the coder need docker compose to run tests, and whoever can run Docker is root inside the containers.
The lesson: a guardrail is not a barrier
That finding changed how I understand gate G1:
- For the agents that write (tester and coder), the hook is a guardrail: it stops them when they make a mistake, which is what happens almost every time. It doesn't stop them if something induces them to evade it, and patching a blocklist for them has diminishing returns.
- For the reviewers, it is a real barrier, because there it works as an allowlist: whatever isn't explicitly authorized doesn't run.
The realistic threat isn't a malicious agent. It's a prompt injection arriving in a file or a page. Against that, the real barriers live outside the hook: a protected main with no bypass, so my key can only push branches; an SSH key with a passphrase; and I do the pushes and merges myself.
Here's the honest difference from Stripe. Their real box isn't the prompt or the hook: it's the devbox, a machine that can't see production, the internet, or anyone's credentials. I don't have devboxes. My box has holes, I know them, and they're written down in decisiones.md as a trade-off, not hidden. The next step is evaluating Claude Code's Bash sandbox, which restricts reads, writes and network at the operating system level, to close the biggest ones.
"Good for humans, good for agents", at a scale of one
The idea that helped me most wasn't technical. I work alone and, even so, every step gets its own branch and goes in through a PR. It isn't bureaucracy: the PR is where CI runs and where the step's summary lives, with the reviewers' findings. It's the audit trail I'd want if I reviewed this system a year from now, and it's the same one an agent needs to understand what happened.
And one rule I took from their "shift feedback left": whatever a reviewer finds once becomes a permanent test. If the security reviewer spots a misconfigured SECURITY DEFINER function, the fix doesn't end there: a test on the Postgres catalog stays behind and checks every function of that kind. That way, not repeating the mistake doesn't depend on another reviewer having the same good day.
What's next
Now I want to pull this workflow out of PyME Contable and use it in all my projects. I'm trying two ways of packaging it in parallel, a Claude Code plugin and a template, to compare the results. The rule for splitting is simple: roles, gates and the order of the flow are generic; the accounting invariants stay in the CLAUDE.md of the repo that needs them.
What I take from Stripe fits in one line: the instruction guides the agent, but what stops it is code. And when code can't stop it, the honest thing is to write that down.
Sources: Minions, part 1 and part 2, from Stripe's engineering blog. Tools: Claude Code with file-defined subagents, a Python PreToolUse hook and GitHub Actions.