Stripe mete a sus agentes de IA en cajas. Hice lo mismo en mi sistema contable.
Lo que me llamó la atención de los Minions de Stripe, qué copié, qué dejé fuera a propósito y cómo mis propios agentes encontraron los agujeros de la caja que les construí.
En febrero, Stripe publicó dos artículos (parte 1 y parte 2) sobre Minions, sus agentes de programación internos. El número que todos repitieron fue el de los PRs: más de 1.300 por semana que entran a main escritos de principio a fin por un agente, revisados por personas pero sin una sola línea de código humano.
A mí el número no me movió tanto. Lo que me hizo parar fue una idea de la segunda parte: el sistema se vuelve más fiable cuanto más encierran al modelo en cajas pequeñas. Y al leerla me di cuenta de algo incómodo sobre mi propio flujo de agentes: casi todo lo que lo protegía era un párrafo en un archivo Markdown.
Este artículo cuenta cómo lo cambié.
Lo que hace Stripe, en cinco ideas
- Desatendidos de punta a punta. Un ingeniero etiqueta al bot en un hilo de Slack y lo siguiente que ve es un PR que pasa CI, sin interacción en medio. Lo hacen porque el recurso más escaso de la empresa es la atención de sus desarrolladores, y un agente desatendido permite lanzar varias tareas en paralelo.
- El aislamiento es lo que permite la autonomía. Cada ejecución corre en un devbox, la misma máquina de desarrollo que usan los humanos, lista en unos 10 segundos y separada de producción y de internet. Como un error solo puede romper ese devbox, el agente corre con todos los permisos y sin pedir confirmación.
- Blueprints. El flujo es una máquina de estados escrita en código que mezcla dos tipos de nodo: los deterministas (correr linters, hacer push), que no llaman a ningún LLM, y los de agente (implementar la tarea, arreglar el CI), que sí deciden. Lo que se puede anticipar, lo hace el código.
- Contexto curado, no abundante. Casi no tienen reglas globales: las reglas se cargan por subdirectorio a medida que el agente recorre el repo. Su servidor MCP interno tiene cerca de 500 herramientas, y cada agente recibe solo un subconjunto pequeño.
- Feedback a la izquierda y con tope. Lo que fallaría en CI se detecta antes, con linters locales que responden en menos de cinco segundos. Y como mucho hay dos rondas de CI: después, el PR vuelve a un humano.
Debajo de todo hay una idea que resume su filosofía: lo que es bueno para los humanos también lo es para los agentes. No construyeron infraestructura nueva para la IA; los agentes usan las mismas herramientas que sus ingenieros llevan años afinando.
Mi contexto: una persona, no mil
Estoy construyendo PyME Contable, un sistema contable por conceptos para pymes de construcción en Colombia, que quiere crecer hasta convertirse en un ERP. Laravel 13, PostgreSQL 17 y una SPA en React con TypeScript, todo en Docker. Lo desarrollo solo, con una contadora que valida las reglas contables y fiscales.
Hay cosas que en este sistema no se negocian nunca: un asiento siempre cuadra, un documento contabilizado no se modifica, una empresa nunca ve los datos de otra (RLS en Postgres), los consecutivos no tienen huecos y un monto jamás es un float. Cuando dos objetivos chocan, el orden es fijo: integridad contable, seguridad, cumplimiento, rendimiento, simplicidad y, al final, comodidad.
Mi flujo de agentes ya existía antes de leer a Stripe. Es la evolución del que conté en el artículo anterior:
- Arquitecto (la sesión principal, Opus): planifica, reparte y no escribe código.
- Tester (Sonnet): escribe una prueba y la ve fallar antes de que exista el código.
- Coder (Sonnet): escribe lo mínimo para ponerla en verde, sin tocar la prueba.
- Revisores de código, seguridad y rendimiento (Opus): leen, prueban y reportan; no arreglan.
- Yo: apruebo cada plan y cada commit.
El agujero que me mostró Stripe
Al compararlo con los blueprints vi el problema. «El tester solo escribe en la carpeta de tests», «el coder no toca las pruebas», «los revisores no escriben en la base de datos»: todo eso eran instrucciones. Frases en .claude/agents/*.md que el modelo lee y, casi siempre, respeta.
Casi siempre no es una garantía. Un modelo puede olvidar una regla en una conversación larga, interpretarla de forma creativa o, peor, recibir instrucciones contrarias desde el README de una dependencia o una página web. En un sistema donde la primera prioridad es la integridad contable, «casi siempre» no alcanza.
Stripe no confía en que el agente se porte bien: confía en la caja. Eso es lo que quise copiar.
Lo que no copié, a propósito
Antes de lo que sí, lo que no:
- Ni ejecución desatendida ni paralelismo. Stripe los necesita porque su cuello de botella es la atención de miles de ingenieros. El mío no es el volumen, y mis aprobaciones no son un estorbo: son parte del control.
- Ni un harness propio. Stripe hizo un fork de un agente de código abierto. Con un solo desarrollador, eso es sobreingeniería pura. Uso Claude Code y sus mecanismos: subagentes, hooks y permisos.
De Stripe se transfieren los patrones, no las piezas.
Lo que sí copié: puertas deterministas
Convertí las reglas más importantes en puertas: código que se ejecuta siempre y que no depende de que el modelo se acuerde de nada. Las numeré de G0 a G5 para ir cerrándolas una por una.
| Puerta | Qué hace | Idea de Stripe | Estado |
|---|---|---|---|
| G0 | Una rama por paso, PR a main protegida, CI obligatorio y sin bypass de administrador |
Push y CI como nodos deterministas | Hecha |
| G1 | Un hook que intercepta cada herramienta y la permite o la bloquea según el rol del agente | Meter al LLM en una caja | Hecha |
| G2 | Huella de los tests aprobados: si el coder toca una prueba, no pasa | Lo anticipable lo hace el código | Pendiente |
| G3 | Mutaciones SQL: romper a propósito cada invariante de Postgres y exigir que un test falle | Feedback automático para iterar | Pendiente |
| G4 | CI con actionlint, hadolint, composer audit y npm audit, que también corro en local antes del PR |
Mover el feedback a la izquierda | Hecha |
| G5 | Retirar la revisión con LLM donde un linter es mejor (CI, Dockerfile, lockfiles) | Un LLM solo donde aporta criterio | Pendiente |
G1: la caja
La pieza central es un hook PreToolUse de Claude Code: un script en Python que recibe cada intento de usar una herramienta, junto con el tipo de agente que lo hace, y responde sí o no. Las reglas, resumidas:
- El tester solo escribe en
backend/tests/**y en los archivos*.test.*del frontend. - El coder no puede tocar tests,
docs/,.claude/,CLAUDE.mdni.env, tampoco por Bash con>,sed -iocp. - Ninguno de los dos hace operaciones de git que escriban, ni migraciones, ni instala dependencias.
- El revisor de código solo puede usar
git diff,status,log,showy leer archivos. - Los revisores de seguridad y rendimiento pueden consultar Postgres, pero con el rol de la aplicación, nunca como superusuario, y cualquier SQL que escriba tiene que ir dentro de
BEGIN ... ROLLBACK. - Cualquier otro subagente es de solo lectura.
- Si el script falla o falta Python, bloquea. Falla cerrado.
Esto es lo que ve un revisor que intenta borrar una tabla fuera de una transacción:
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
La misma consulta envuelta en BEGIN ... ROLLBACK pasa. La instrucción en el Markdown sigue ahí, pero ya no es lo único que hay.
Mis agentes revisaron la caja, y la rompieron
Pasé el hook por mi propio flujo: el revisor de código y el de seguridad en paralelo, sobre el cambio sin commitear. Encontraron cosas que nadie había visto: formas de evadir las reglas con comillas y heredocs, un meta-comando de psql que había quedado abierto y un test del propio hook que pasaba en falso, porque el comando que debía bloquear ya lo bloqueaba otra regla. Un test que no puede fallar no prueba nada, que es justo lo que este flujo existe para evitar.
Mi regla, igual que la de Stripe, es un máximo de dos rondas de corrección. Aquí hice una tercera, como excepción explícita, porque los hallazgos cruzaban límites reales.
Y entonces el arquitecto señaló algo que ningún revisor había dicho: los subagentes corren en WSL con mi usuario. Cualquier intérprete, un simple python3 -c que escriba un archivo, pasa por debajo del hook sin que este pueda leer lo que hace. Y hay algo peor: el tester y el coder necesitan docker compose para correr las pruebas, y quien puede correr Docker es root dentro de los contenedores.
La lección: baranda y barrera no son lo mismo
Ese hallazgo cambió cómo entiendo la puerta G1:
- Para quien escribe (tester y coder), el hook es una baranda: los detiene cuando se equivocan, que es lo que pasa casi siempre. No los detiene si alguien los induce a evadirlo, y seguir parchando una lista negra para ellos tiene retornos decrecientes.
- Para los revisores sí es una barrera, porque ahí funciona como lista permitida: lo que no está autorizado de forma explícita, no se ejecuta.
La amenaza realista no es un agente malicioso. Es una inyección de prompt que llega en un archivo o en una página. Contra eso, las barreras de verdad están fuera del hook: main protegida sin bypass, de modo que con mi llave solo se pueden subir ramas; la llave SSH con passphrase; y los push y los merges los hago yo.
Aquí está la diferencia honesta con Stripe. Su caja de verdad no es el prompt ni el hook: es el devbox, una máquina que no ve producción, ni internet, ni las credenciales de nadie. Yo no tengo devboxes. Mi caja tiene agujeros, los conozco y están escritos en decisiones.md como un trade-off, no escondidos. El siguiente paso es evaluar el sandbox de Bash de Claude Code, que restringe lectura, escritura y red a nivel de sistema operativo, para cerrar los más grandes.
«Bueno para humanos, bueno para agentes», a escala de uno
La idea que más me sirvió no fue técnica. Trabajo solo y, aun así, cada paso va en su rama y entra por PR. No es burocracia: el PR es donde corre el CI y donde queda el resumen del paso con los hallazgos de los revisores. Es la pista de auditoría que yo querría tener si revisara este sistema dentro de un año, y es la misma que necesita un agente para entender qué pasó.
Y una regla que tomé de su «feedback a la izquierda»: lo que un revisor encuentra una vez se convierte en un test permanente. Si el revisor de seguridad detecta una función SECURITY DEFINER mal configurada, el arreglo no termina ahí: queda un test sobre el catálogo de Postgres que revisa todas las funciones de ese tipo. Así, que no se repita el error no depende de que otro revisor tenga el mismo buen día.
Qué sigue
Ahora quiero sacar este flujo de PyME Contable y usarlo en todos mis proyectos. Estoy probando en paralelo dos formas de empaquetarlo, un plugin de Claude Code y una plantilla, para comparar resultados. La regla para separar es simple: los roles, las puertas y el orden del flujo son genéricos; las invariantes contables se quedan en el CLAUDE.md del repo que las necesita.
Lo que me llevo de Stripe cabe en una línea: la instrucción guía al agente, pero lo que lo detiene es el código. Y cuando el código no alcanza a detenerlo, lo honesto es dejarlo escrito.
Fuentes: Minions, parte 1 y parte 2, del blog de ingeniería de Stripe. Herramientas: Claude Code con subagentes definidos por archivo, un hook PreToolUse en Python y GitHub Actions.