Fundamentos de ingeniería agentic: curso acelerado de 45 minutos
8 conceptos, 80% del uso real
Requisito previo: Curso acelerado de codificación agentic. Esa página enseña las herramientas (Claude Code, OpenCode, modo plan, CLAUDE.md, skills, MCP, hooks). Esta página enseña la disciplina con la que las usas. Ambas son complementarias: las herramientas sin disciplina producen vibe code; la disciplina sin herramientas es teoría.
"El código no es barato. El mal código es más caro que nunca." Matt Pocock
"Vibe coding consiste en elevar el piso de lo que cualquiera puede hacer en software. La ingeniería agentic consiste en preservar el estándar de calidad que ya existía en el software profesional." Andrej Karpathy
Circula una narrativa en la industria: la IA es un paradigma nuevo, así que las viejas reglas de ingeniería ya no aplican; las especificaciones son el nuevo código fuente; el modelo es el compilador; el diff no importa mientras el programa funcione. Es una idea cómoda, y es falsa.
La tesis de este capítulo, y el hilo conductor de cada Digital FTE de este libro, es la contraria. Los fundamentos del software importan más en la era de la IA que antes. La razón es mecánica, no sentimental. La interfaz que diseñas es la interfaz de la que aprende el agente; los nombres que eliges son los nombres que reutiliza; los límites que trazas son los límites que respeta. Un agente dentro de un codebase limpio y bien probado produce código varios niveles de calidad por encima del mismo agente dentro de uno enredado. La arquitectura ya no es solo una propiedad del código; es una entrada para el agente. El mal código produce malos agentes. El buen código produce agentes que parecen asombrosamente competentes.
Este capítulo enseña el flujo de trabajo que vuelve repetible esa competencia: un pipeline de siete etapas (idea → grilling → PRD → issues → implementación → revisión → QA) implementado mediante Skills pequeñas y componibles que funcionan igual en Claude Code y OpenCode. Las Skills, las especificaciones y los patrones arquitectónicos escritos para uno se pueden llevar al otro sin cambios. El método es la constante. La herramienta es la variable.
Al final del capítulo podrás:
- Ubicarte en el espectro vibe coding ↔ ingeniería agentic y elegir la disciplina que corresponde a lo que está en juego en tu trabajo.
- Diagnosticar los seis modos de falla de la codificación con IA y aplicar la cura para cada uno.
- Ejecutar un ciclo completo grill → PRD → issues de vertical slice → implementación AFK en Claude Code u OpenCode.
- Escribir un
SKILL.mdque el agente cargue solo cuando lo necesita, en lugar de gastar tokens en cada turno. - Refactorizar un codebase de "módulos superficiales" a "módulos profundos" para que los ciclos de retroalimentación con IA funcionen de verdad.
- Usar con fluidez el vocabulario de trabajo: smart zone, dumb zone, clearing, compaction, handoff, AFK, tracer bullet, design concept, grilling, jagged intelligence.
El pipeline de un vistazo
Antes de la teoría, esta es la forma operativa que enseña el capítulo. Siete etapas, cinco Skills, una dirección de flujo. Cada sección posterior explica una fila o la muestra en código.
| # | Etapa | Qué sucede | Entrada → salida | Skill | Sección |
|---|---|---|---|---|---|
| 1 | Idea → concepto alineado | El agente te entrevista socráticamente hasta compartir el diseño | deseo → design concept | grill-me | §6.1 |
| 2 | Concepto → destino | Sintetiza la conversación en un PRD | conversación → PRD | to-prd | §6.2 |
| 3 | PRD → backlog | Divide el PRD en tickets de vertical slice | PRD → issues tracer-bullet | to-issues | §6.3 |
| 4 | Issue → slice | Implementa una slice con pruebas primero | issue → diff revisable | tdd | §6.4 |
| 5 | Slices → backlog drenado | El bucle AFK vacía la cola dentro de sandboxes | issues → PRs | (orquestador) | §6.5 |
| 6 | Diff → decisión | El humano lee el diff y ejecuta QA | PR → merge o nuevo issue | (criterio, no automatizado) | §6.6 |
| 7 | Salud del codebase | Encuentra módulos superficiales y propone profundizaciones | codebase → RFC | improve-codebase-architecture | §7.4 |
Las etapas 1-3 son turno de día: humano en el bucle. Las etapas 4-5 son turno de noche: el agente se ejecuta AFK en un sandbox. La etapa 6 vuelve al turno de día. La etapa 7 se ejecuta en un cron semanal y alimenta nuevos issues a la etapa 3. Todo el pipeline funciona igual en Claude Code y OpenCode.
¿Eres nuevo en programación? Lee esto primero.
Este capítulo asume que ya has escrito código, usado
git, ejecutado una suite de pruebas y abierto una pull request. Si eso te resulta familiar, omite este recuadro y continúa.Si todavía no te resulta familiar, el capítulo sigue siendo legible como mapa conceptual. Obtendrás: la forma del flujo de trabajo, el vocabulario necesario para entender conversaciones sobre codificación con IA, un catálogo diagnóstico de fallas comunes y la filosofía arquitectónica que permite que los agentes funcionen bien en codebases reales. Aún no podrás ejecutar el código de ejemplo; eso requiere primero unas semanas de fundamentos de programación. El camino honesto es: lee este capítulo una vez para obtener el mapa, aprende los requisitos previos y luego vuelve para seguir el código.
El vocabulario mínimo indispensable para seguir las secciones conceptuales:
- Repo (abreviatura de repository): la carpeta de código de un proyecto, seguida por
git.- Branch: una versión paralela del repo donde puedes experimentar sin afectar el código principal. Worktree es un concepto relacionado: una copia del repo en disco, asociada a una branch.
- Commit: una instantánea guardada de cambios, con un mensaje corto que los describe.
- Pull request (PR): un cambio propuesto que se envía a revisión antes de fusionarlo con la rama principal. Es lo que los humanos revisan en la etapa 6 del capítulo.
- Test / test suite: código que comprueba que otro código es correcto, ejecutado automáticamente. "Las pruebas pasan" significa que todas las comprobaciones salieron en verde.
- Sandbox (o container): un entorno aislado, como un mini-equipo sellado, donde el agente puede ejecutarse, escribir archivos y romper cosas sin tocar el resto de tu sistema.
- Token: la unidad de texto que procesa un modelo de lenguaje. Aproximadamente 3/4 de una palabra en promedio. Una ventana de contexto de 100k tokens contiene unas 75.000 palabras.
- Terminal / shell / bash: la forma basada en texto de ejecutar comandos en un equipo. Las líneas que empiezan con
$en este capítulo son comandos que escribes en la terminal.
1. De vibe coding a ingeniería agentic
Dos cosas cambiaron en rápida sucesión. La primera hizo necesaria la segunda.
1.1 Software 3.0: un nuevo paradigma de computación
Andrej Karpathy describe el software en tres eras. Software 1.0 es lo que la mayoría de los ingenieros pasó su carrera escribiendo: código explícito, ejecutado por una CPU, que opera sobre datos estructurados. Software 2.0 es la era de los pesos aprendidos: programar mediante la curación de conjuntos de datos y el entrenamiento de redes neuronales, en lugar de escribir lógica de ramificación. Software 3.0 es la era en la que vivimos ahora: programar mediante prompting, donde el LLM es una especie de equipo programable, y lo que pones en la ventana de contexto es la palanca con la que lo mueves.
Lo que cambia entre eras es el artefacto que produces. En 1.0 el artefacto era código ejecutable. En 3.0 el artefacto es cada vez más un fragmento de texto pensado para un agente. Cuando OpenCode entrega su instalador, no entrega un script de bash; entrega un párrafo de lenguaje natural pensado para pegarse en un agente de codificación. El agente lee el entorno, depura dentro del bucle y llega a una instalación funcional. El instalador ya no es un programa; es una Skill.
Esto se generaliza. La documentación escrita para humanos ("ve a esta URL, haz clic en Settings...") se convierte en documentación escrita para agentes ("dale esto a tu agente de codificación y configurará tu proyecto"). Las UI ya no son la única interfaz; el agente se convierte en un usuario de segunda clase de cada sistema que construyes y de cada sistema del que dependes. La infraestructura nativa para agentes (APIs, documentación, tooling y pipelines de despliegue diseñados primero para agentes) es la siguiente capa de plataforma.
Este capítulo trata sobre operar en Software 3.0. Las Skills (§5) son artefactos 3.0. Los PRD y los tickets (§6) son artefactos 3.0. Los archivos AGENTS.md y CONTEXT.md (§3, Falla 2) son artefactos 3.0. El código en sí está cada vez más aguas abajo de todos ellos.
1.2 Vibe coding eleva el piso; la ingeniería agentic conserva el techo
Karpathy también acuñó vibe coding: dejar que el agente escriba código, aceptar su salida sin leer el diff y juzgarla por si el programa funciona. Vibe coding es real, útil y llegó para quedarse. Es la forma en que una persona que no programa entrega una herramienta útil en un fin de semana; es como Karpathy describe la construcción de MenuGen, su proyecto paralelo que convierte fotos de menús de restaurantes en menús con imágenes renderizadas de los platos. Vibe coding eleva el piso de lo que una persona puede producir en software. Las consecuencias económicas de esa elevación son grandes y en su mayoría positivas.
Ahora surge una segunda disciplina encima: ingeniería agentic. Donde vibe coding eleva el piso, la ingeniería agentic conserva el techo: el estándar de calidad del software profesional. El agente escribe la mayor parte; tú sigues siendo responsable de la seguridad, la integridad de los datos, la mantenibilidad, los contratos y la experiencia de usuario. Vibe coding no introduce vulnerabilidades; las introduce el ingeniero que lo usa sin cuidado. El estándar no cambia solo porque cambió quien teclea.
| Vibe coding | Ingeniería agentic | |
|---|---|---|
| Objetivo | Elevar el piso de lo que se puede construir | Conservar el techo de lo que es profesional |
| Revisor | A menudo ninguno; se juzga por si funciona | Un humano lee el diff; revisión automatizada encima |
| Arquitectura | Lo que emita el agente | Diseñada por el ingeniero; implementada por el agente |
| Pruebas | Opcionales | No negociables; TDD en la ruta crítica |
| Salud del codebase | Se acepta la deriva | Refactorización programada; profundización de módulos |
| Manejo de fallas | "A mí me funciona" | Reproducible, probado y explicado |
| Contexto adecuado | Proyectos paralelos, prototipos, herramientas desechables | Sistemas de producción, trabajo regulado, cualquier cosa multiusuario |
Los principios y flujos de trabajo de este capítulo son la disciplina de la ingeniería agentic, no la libertad del vibe coding. Cuando construyes un Digital FTE al que una organización confiará nómina, escalaciones de clientes o conciliación financiera, vibe coding es mala praxis. Necesitas el piso y el techo: mayor throughput y calidad preservada.
La brecha entre un ingeniero agentic mediocre y uno fuerte es mucho más amplia que la vieja brecha del "ingeniero 10×". Karpathy: "10× no es la aceleración que obtienes. Las personas que son muy buenas en esto alcanzan picos mucho mayores que 10× desde mi perspectiva actual." Cerrar esa brecha es el trabajo de este capítulo.
2. Tres restricciones que todo agente de codificación hereda
Un agente de codificación no es un ingeniero mágico; es un modelo envuelto en un harness. Tres propiedades de esa combinación dan forma a cada flujo de trabajo que construimos encima: un presupuesto de atención finito, ausencia de estado persistente y un perfil de capacidad irregular.
2.1 La smart zone y la dumb zone
Cuando un modelo predice el siguiente token (un fragmento de texto, aproximadamente tres cuartos de una palabra en inglés), pondera todos los demás tokens que ya están en la ventana de contexto. Cada token tiene un presupuesto de atención finito: una porción fija de influencia para gastar en el resto. Una ventana de N tokens tiene del orden de N² relaciones de atención compitiendo por ese presupuesto fijo.
La consecuencia no es negociable. Al inicio de una sesión, el agente está en su smart zone: agudo, enfocado, con buen recuerdo. A medida que la sesión crece, la señal de cada token se diluye entre competidores. El agente deriva hacia la dumb zone: olvida el esquema que pegaste arriba, inventa campos que no existen en el archivo de tipos, enlaza mal dos variables con el mismo nombre, contradice su propio razonamiento anterior. Mismo modelo, mismos parámetros; solo más bocas alimentándose del mismo plato.
El techo práctico, en los modelos frontier actuales, sin importar si el marketing promete una ventana de contexto de 200k o 1M, está muy por debajo de la ventana anunciada para trabajo de codificación. Los reportes de practitioners convergen en algo como 100k tokens como línea de flotación aproximada antes de que empiece a verse deriva, pero el número exacto importa menos que la forma: más allá de cierta fracción de la ventana anunciada, no recibiste más capacidad; recibiste más dumb zone en la que gastar dinero. Las ventanas más grandes ayudan con la recuperación sobre documentos largos; no extienden el horizonte de razonamiento para código en la misma proporción.
Token usage: 0k ────────── 50k ────── 100k ────── 200k ────── 1M
Quality: ████████████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░
↑ ↑
smart zone dumb zone begins
En concreto, ¿cómo se ve la transición en una sesión real? Más o menos así:
turn 5 → you paste users.ts schema (8 fields: id, email, name, ...)
turn 9 → agent uses User.email correctly
turn 23 → agent builds a route, refers to User.id, all good
turn 47 → context is now ~80k tokens
turn 52 → agent writes user.emailAddress ← field doesn't exist
turn 55 → agent invents user.preferences ← also not in the schema
⇒ smart zone exited.
⇒ /clear, re-paste schema in a fresh session, continue.
El mismo modelo, el mismo prompt en el turno 52 que en el turno 9. Lo único que cambió fue el presupuesto de atención. La cura no es insistir. Dimensiona cada unidad de trabajo para que quepa dentro de la smart zone y, cuando una unidad termine, desecha la sesión y empieza una nueva.
2.2 El problema Memento
Los modelos son sin estado. No llevan nada entre solicitudes al proveedor del modelo. La continuidad dentro de una sesión es el harness reenviando contexto en cada turno; la continuidad entre sesiones es algo que un sistema de memoria escribió en disco y vuelve a cargar al iniciar la siguiente sesión.
Esto es una ventaja. Lo más fiable de un agente es que limpiar el contexto lo devuelve a un estado conocido y bueno. El agente que acaba de pasar cuarenta turnos derivando hacia la dumb zone es el mismo agente que, cinco segundos después de un /clear, leerá tu prompt fresco con un presupuesto de atención fresco y producirá trabajo excelente.
Hay dos formas de recuperarse cuando una sesión se hincha:
- Clearing: terminar la sesión y empezar una nueva. Reinicio total.
- Compaction: resumir la sesión anterior y sembrar una nueva con el resumen. Con pérdida.
La mayoría de los desarrolladores recurre primero a la compaction porque parece menos destructiva. Desconfía de ese instinto: la compaction conserva parte del razonamiento de la dumb zone que te metió en problemas. El clearing, emparejado con un pequeño artefacto de handoff escrito (un PRD, un ticket, un AGENTS.md), le da a la siguiente sesión el mismo punto de partida cada vez. Los comienzos predecibles producen finales predecibles.
Principio de trabajo. Trata al agente como el protagonista de Memento. Diseña alrededor de su olvido. Haz que cada dato importante sobreviva en el entorno (
AGENTS.md, unCONTEXT.md, una Skill, un ticket), no en el historial de chat.
2.3 Inteligencia irregular
Las dos primeras restricciones tratan sobre cuánto puede atender el agente. La tercera trata sobre en qué es bueno, y es la que más toma por sorpresa a los ingenieros.
Los LLM son irregulares. No son inteligentes de manera uniforme; alcanzan picos abruptos en algunos dominios y se estancan en otros, con poca correlación con lo difícil que parezca la tarea para un humano. Un modelo de última generación puede refactorizar un codebase de cien mil líneas o encontrar una vulnerabilidad zero-day, y en la misma sesión decirte que camines hasta un autolavado a cincuenta metros en lugar de manejar. Las dos capacidades solo están conectadas por los entornos de RL en los que los laboratorios decidieron entrenar.
Los modelos frontier se entrenan intensamente con aprendizaje por refuerzo en tareas donde la salida es verificable: problemas matemáticos con respuestas comprobables, código que compila y pasa pruebas, demostraciones formales. El modelo aprende brillantemente dentro de esos circuitos porque la señal de recompensa es limpia. Fuera de ellos, opera sobre intuición de preentrenamiento sin retroalimentación comparable que la afile. El perfil de capacidad parece una cordillera con valles profundos: picos en codificación competitiva y refactorización de código, un valle en planificación de sentido común sobre distancias del mundo físico.
capability
│
│ ╱╲ ╱╲
│ ╱ ╲ ╱╲ ╱ ╲
│ ╱ ╲ ╱ ╲ ╱ ╲ ╱╲
│ ╱ ╲╱ ╲╱ ╲ ╱ ╲
│ ╱ ╲ ╱ ╲___
└────────────────────────────────────────► task
code refactor math car-wash common-sense
walking physical reasoning
La restricción de inteligencia irregular tiene cuatro implicaciones operativas.
Primero, el código es el dominio afortunado. Estás trabajando en uno de los picos más profundos de toda la superficie, no porque codificar sea intrínsecamente más fácil, sino porque los laboratorios lo priorizaron económicamente y lo entrenaron con intensidad. Trátalo como buena suerte, no como evidencia de que el modelo "es inteligente". Fuera de este pico, el mismo modelo puede equivocarse con seguridad en cosas que un niño acertaría.
Segundo, tus ciclos de retroalimentación son la forma de mantenerte en circuitos verificables. Los tipos estáticos, las pruebas automatizadas, los lints y los errores de compilación son la misma señal de recompensa contra la que se entrenó el modelo. Cuando el agente ejecuta tus pruebas y las ve fallar, opera con la forma de retroalimentación que produjo sus comportamientos más fuertes durante el entrenamiento. Sin esas señales, vuelve a la intuición de preentrenamiento sin corrección. Este es el porqué profundo detrás de la Falla 3 y la Skill tdd: las pruebas no solo detectan bugs; mantienen al agente en el pico.
Tercero, tienes que saber en qué circuito estás. Cuando el agente hace algo que ni un ingeniero junior habría hecho, a menudo es porque saliste del pico y entraste en una región para la que los laboratorios no entrenaron. "¿Por qué cruzarías referencias de usuarios por correo electrónico en lugar de por un user_id explícito?", pregunta Karpathy, después de ver a su agente hacer exactamente eso en su proyecto MenuGen. El agente estaba fuera de sus circuitos más fuertes en modelado de identidad entre servicios de terceros. La solución no fue un mejor prompt; fue Karpathy interviniendo con guía arquitectónica explícita.
Cuarto, al empezar desde cero, elige tu stack para caer dentro de un pico. El mapa irregular no es simétrico entre lenguajes o frameworks. Boris Cherny es directo sobre por qué Claude Code está construido en TypeScript y React: "Está muy on distribution para el modelo." Cuando otras restricciones lo permitan, prefiere opciones mainstream: Python y TypeScript sobre lenguajes de nicho, Postgres sobre almacenes exóticos, frameworks populares sobre soluciones hechas a mano. No estás eligiendo la tecnología en la que escribirías a solas; estás eligiendo aquello en lo que tu fuerza laboral de agentes escribe bien. La cola larga se pondrá al día; hasta entonces, las decisiones on-distribution compran años de apalancamiento efectivo.
Animales frente a fantasmas. Karpathy describe los LLM como fantasmas, no animales: simulaciones estadísticas moldeadas por datos y recompensa, no inteligencias biológicas moldeadas por la evolución. La consecuencia: gritarle a un agente no lo mejora; la simpatía no lo mejora; "piensa paso a paso" no despierta cognición dormida. Lo que funciona es poner al agente sobre un pico (contexto claro, retroalimentación verificable, código bien nombrado, una especificación precisa) y dejar que se active el comportamiento entrenado. Trata la psicología del agente como física, no como personalidad.
3. Los seis modos de falla de la codificación con IA
Las tres restricciones producen fallas predecibles. Seis en particular aparecen con tanta frecuencia que conviene tratarlas como un catálogo cerrado. La tabla siguiente es el diagnóstico; los párrafos posteriores expanden cada fila en síntoma, causa raíz y la cura que el resto del capítulo codifica como Skill.
| # | Síntoma | Causa raíz | Cura | Skill | Dónde |
|---|---|---|---|---|---|
| 1 | "El agente no hizo lo que quería." | No hay un design concept compartido entre tú y el agente | Forzar alineación antes de escribir cualquier artefacto, mediante entrevista socrática | grill-me | §5, §6.1 |
| 2 | "El agente es demasiado verboso." | No hay lenguaje ubicuo; tú y el agente nombran lo mismo de formas distintas | Mantener un CONTEXT.md con términos del dominio, cargado en cada sesión | grill-with-docs | §5, §6.1 |
| 3 | "El código no funciona." | Ciclos de retroalimentación débiles; el agente codifica a ciegas | Entorno ruidoso (tipos, pruebas, lints) + TDD red-green-refactor | tdd | §5, §6.4 |
| 4 | "Construimos una bola de lodo." | Módulos superficiales; los agentes los producen más rápido de lo que los humanos los limpian | Invertir a diario en diseño de módulos; pasada periódica de profundización | improve-codebase-architecture | §7 |
| 5 | "Mi cabeza no da abasto." | Estás leyendo cada línea a 5× la velocidad normal | Principio de caja gris: diseñar interfaces, delegar implementaciones | (hábito arquitectónico) | §7.3 |
| 6 | "Estoy revisando más código del que construyo." | El throughput movió el cuello de botella a la revisión | Dividir la revisión en capas automatizadas y humanas; las vertical slices mantienen los diffs pequeños | automated-review (receta en §6.5; no está en el pack upstream) | §6.5, §7 |
Falla 1: "El agente no hizo lo que quería."
La falla más común es la desalineación. Tenías una imagen clara de la función; el agente construyó algo sutilmente distinto; no están de acuerdo ni siquiera sobre qué significa "terminado". Es un problema de comunicación, no un problema del modelo. Frederick P. Brooks nombró lo que falta en The Design of Design: el design concept, la idea compartida y efímera de lo que se está construyendo. Los PRD, las especificaciones y las conversaciones son artefactos que intentan capturar el design concept; ninguno de ellos lo es.
Cura: fuerza que el design concept se estabilice antes de escribir código o cualquier artefacto formal. La técnica es grilling: el agente te entrevista socráticamente, una decisión a la vez, recorriendo cada rama del árbol de diseño y proponiendo su propia recomendación para cada pregunta, hasta que ambas partes quedan alineadas. La sección 5 muestra la Skill.
Falla 2: "El agente es demasiado verboso."
Un agente fresco que cae en tu proyecto no conoce tu jerga. Tu codebase las llama lecciones y el agente las llama unidades del curso. Tu equipo dice cascada de materialización y el agente escribe un párrafo describiendo la misma idea. Están hablando sin encontrarse y quemando tokens en el proceso.
Es el mismo problema que el diseño dirigido por el dominio resolvió hace más de veinte años: el lenguaje ubicuo. Un proyecto necesita un único vocabulario compartido del que beban el código, las pruebas, la conversación y la documentación. Con agentes tiene un segundo beneficio: un vocabulario más ajustado significa menos tokens de pensamiento gastados en desplegar ambigüedad y más atención en la tarea.
Cura: mantener un CONTEXT.md en la raíz del repo con los términos de dominio del proyecto, cargado en cada sesión. La sección 5 muestra cómo el grilling y CONTEXT.md se emparejan en la misma Skill.
Falla 3: "El código no funciona."
Te alineaste con el agente. Escribiste una especificación limpia. El agente produjo código, y el código está roto, a veces de forma obvia, a veces en silencio. El diagnóstico casi siempre es ciclos de retroalimentación débiles. El agente codifica a ciegas.
The Pragmatic Programmer advierte contra adelantarte a tus faros: asumir tareas más grandes de lo que la tasa de retroalimentación puede iluminar. Los agentes hacen esto constantemente, y peor que los humanos, porque escribirán felices mil líneas antes de comprobar si alguna compila. El IQ efectivo de un agente de codificación está limitado por la calidad de la retroalimentación que le da su entorno.
Cura: haz que el entorno sea ruidoso, con tipos estáticos, imports verificados por tipos, pruebas automatizadas, lints rápidos, un hook de pre-commit y acceso al navegador cuando el trabajo sea visual. Luego exige desarrollo guiado por pruebas para que el agente dé pasos pequeños y deliberados: prueba que falla, hacerla pasar, refactorizar, repetir. La Skill tdd de §5 codifica esto.
Falla 4: "Construimos una bola de lodo."
Los agentes aceleran todo, incluida la velocidad a la que un codebase se vuelve inmantenible. Sin intervención producen módulos superficiales (muchos archivos diminutos que exponen muchas funciones pequeñas, con dependencias implícitas entre ellos) porque los módulos superficiales son más fáciles de generar uno a uno. Un agente que no puede navegar su propio codebase produce peor código en cada pasada. El codebase se convierte en un bucle venenoso.
John Ousterhout, en A Philosophy of Software Design, ofrece la alternativa: módulos profundos. Pocos módulos grandes con interfaces simples y mucha funcionalidad escondida detrás. Los módulos profundos son más fáciles de probar para los agentes (el límite de prueba es la interfaz), más fáciles de razonar (los llamadores no necesitan conocer la implementación) y más fáciles de delegar (tú diseñas la interfaz; el agente escribe la implementación).
Cura: invertir en diseño de módulos todos los días (Kent Beck) y ejecutar improve-codebase-architecture periódicamente para encontrar módulos superficiales y proponer profundizaciones. La sección 7 cubre los principios en profundidad.
Falla 5: "Mi cabeza no da abasto."
Un modo de falla sorprendente, y serio. Los ingenieros senior que trabajan con agentes por primera vez a menudo reportan estar más cansados, no menos, pese a entregar más código. Con el agente produciendo código a entre tres y cinco veces la velocidad normal, el ingeniero sostiene todo el sistema en la cabeza al nuevo ritmo. Sin disciplina arquitectónica, la carga cognitiva se multiplica en lugar de dividirse.
Cura: el principio de caja gris. Diseña las interfaces de los módulos con toda tu atención; delega la implementación al agente; verifica el módulo desde fuera mediante sus pruebas, no leyendo cada línea interna. Tú sostienes el mapa arquitectónico; el agente rellena los ladrillos. La sección 7.3 lo desarrolla.
Falla 6: "Estoy revisando más código del que construyo."
La otra cara del throughput. Cuando el agente entrega rápido, el cuello de botella se mueve a la revisión de código, y el trabajo de revisión se expande hasta llenarlo. La cura es dividir la revisión en dos capas: una capa automatizada de alto throughput que detecta la mayoría de los problemas rutinarios y una capa humana de bajo throughput enfocada en lo que la capa automatizada no puede resolver.
Cura: una Skill automated-review que se ejecuta en una sesión fresca, con solo el diff, los estándares de codificación del proyecto y una checklist de seguridad como entrada, y produce un comentario estructurado en la PR antes de que el humano la abra. Ejecútala antes del merge como paso de CI; detecta regresiones de contrato, pruebas faltantes, antipatrones comunes de seguridad y desajustes contra las convenciones del proyecto. El revisor humano llega a una PR preclasificada, con la atención liberada para el criterio, el ajuste de producto y las decisiones ambiguas que marcó la capa automatizada. Las vertical slices (§6) mantienen pequeño cada diff; los bucles persistentes de revisión (§6.5.3) permiten que el revisor automatizado se ejecute con una agenda, no solo al momento del merge. Nada de esto elimina la revisión humana; reubica la atención humana donde el juicio no se puede sustituir.
Estas son las seis fallas que el resto del capítulo elimina, en orden.
4. El flujo de trabajo de extremo a extremo
Todo lo que sigue cuelga de este esqueleto: la forma de todo el pipeline, fijada en la mente antes de descender a Skills y código.
4.1 El modelo de turno de día / turno de noche
Dos tipos de trabajo. El trabajo human-in-the-loop requiere una persona en el teclado respondiendo preguntas y tomando decisiones de criterio: alineación, diseño, gusto, QA. El trabajo AFK ("away from keyboard") se ejecuta sin supervisión en un sandbox y te muestra el diff por la mañana: implementación, refactors, relleno de pruebas.
El pipeline alterna:
flowchart TD
subgraph DAY1["DAY SHIFT - human-in-the-loop"]
A[Idea] --> B[Grill]
B --> C[PRD]
C --> D[Issues - vertical slices]
end
D --> BACKLOG[(backlog of issues)]
subgraph NIGHT["NIGHT SHIFT - AFK, sandboxed"]
E[Implementation Loop<br/>TDD per slice] --> F[Automated Review<br/>separate session]
end
BACKLOG --> E
F --> PRS[(review-ready PRs)]
subgraph DAY2["DAY SHIFT - back to human"]
G[Human Review<br/>read the diff] --> H[QA] --> I[Merge]
end
PRS --> G
H -. new issues from QA .-> BACKLOG
classDef human fill:#e8f1ff,stroke:#3b6ea8,color:#0d2a4d
classDef afk fill:#fff5e6,stroke:#a36a1a,color:#3d2700
class DAY1,DAY2 human
class NIGHT afk
Cada transición es un handoff. Cada handoff está mediado por un artefacto pequeño y duradero (un CONTEXT.md, un PRD, un ticket, un diff), no por una sesión larga. Las sesiones largas mueren en la dumb zone; los artefactos duraderos sobreviven para siempre. Esta es la intuición arquitectónica que hace funcionar el resto.
4.2 Los límites de "specs-to-code"
Las especificaciones son útiles. Los PRD de §6.2 son especificaciones. Los issues de §6.3 son mini-especificaciones. CONTEXT.md es una especificación. El argumento aquí es más estrecho que un rechazo general: va contra tratar las especificaciones como todo el flujo de trabajo, donde escribes una especificación, la compilas mediante un agente, ignoras el código resultante y, si algo sale mal, editas la especificación y recompilas. Como una etapa del pipeline, las especificaciones son esenciales. Como bucle cerrado que reemplaza el resto del pipeline, fallan por dos razones.
El código es el campo de batalla. Dentro del código se esconden restricciones que la especificación no anticipó: el módulo existente con el que la función debe integrarse, la forma de datos que la base de datos realmente devuelve, el bug que solo aparece cuando la caché está fría. Una especificación que no responde a esto se aleja más de la realidad en cada recompilación, y cada ronda produce código peor que la anterior porque el agente hereda una historia más larga de sugerencias sin raíz.
Las especificaciones se degradan. Un gamification-prd.md escrito en marzo es, para julio, un documento sobre un sistema que ya no existe: los nombres cambiaron, los límites se movieron, los requisitos evolucionaron. Un agente que carga esa especificación para "extender" el sistema hereda un problema de fidelidad antes de escribir una línea.
El modelo correcto es el de §4.1: las especificaciones son artefactos de handoff en una etapa del pipeline, no la fuente de verdad del sistema. Guían una o dos sesiones de implementación y luego se retiran. Lo que persiste es el código, las pruebas y CONTEXT.md.
Karpathy hace la misma observación sobre el modo plan: se apresura a producir un artefacto antes de que el razonamiento esté asentado, cuando el movimiento correcto es "trabajar con tu agente para diseñar una especificación muy detallada" antes de escribir código. El pipeline grill-luego-PRD-luego-issues se ve así: el modo plan se precipita hacia un artefacto; el pipeline llega primero a un design concept y deja que el artefacto salga de él.
4.3 Vertical slices y tracer bullets
La decisión de forma más importante en §4.1 es cómo dividir un PRD en issues. La tentación es cortar horizontalmente: un issue para la base de datos, uno para el API, uno para la UI. Eso es incorrecto. Con el corte horizontal, el agente no recibe retroalimentación de extremo a extremo hasta que llega el tercer issue; los bugs se acumulan en los bordes; y cualquier issue puede bloquear a los demás.
La forma correcta es la vertical slice, una tracer bullet, según la analogía de The Pragmatic Programmer de las balas luminosas que permiten a un artillero antiaéreo ver hacia dónde va el disparo. Dispara una tracer para ver si la puntería es correcta, luego dispara a fondo sabiendo que acertarás.
flowchart LR
subgraph H["Horizontal slicing - bad<br/>(no integrated feedback until phase 3)"]
direction TB
H1[Frontend - phase 3]
H2[API - phase 2]
H3[Database - phase 1]
H1 -.- H2 -.- H3
end
subgraph V["Vertical slicing - good (tracer bullets)"]
direction TB
V1[Slice 1<br/>F→A→D] ~~~ V2[Slice 2<br/>F→A→D] ~~~ V3[Slice 3<br/>F→A→D] ~~~ V4[Slice 4<br/>F→A→D]
end
classDef bad fill:#fde8e8,stroke:#a83838,color:#5a0d0d
classDef good fill:#e8f5e8,stroke:#3b8a3b,color:#0d3a0d
class H bad
class V good
La sección 6.3 recorre cómo se ve el corte vertical en el ejemplo trabajado, incluida la forma en que el grafo de dependencias entre slices admite ejecución en paralelo. Por ahora, basta el concepto: cada issue entrega un camino de extremo a extremo; la secuenciación surge de las dependencias, no de fases.
5. Skills como proceso codificado
Cada cura necesita codificarse como un artefacto reutilizable y cargable por el agente. Ese artefacto es una Skill.
Principio frente a instancia. Cinco principios ejecutan este pipeline: grilling, síntesis de PRD, vertical slicing, TDD y profundización. Cada uno tiene una implementación actual de primera clase en el skill pack de alguien. Las implementaciones evolucionan; los principios no. El registro vivo de Skills de la comunidad es skills.sh; el pack de Matt Pocock vive en skills.sh/mattpocock y suministra los ejemplos trabajados de abajo. Cuando el próximo trimestre aparezca un
grill-memejor, cambia la instancia; el principio de grilling en tu pipeline no se mueve. El invariante arquitectónico es el mismo que enseña §7.3 a nivel de código: la interfaz es estable; la implementación es mutable.
5.1 Qué es una Skill y qué no es
Skill (s.): una capacidad enseñable empaquetada como unidad (instrucciones y recursos para hacer bien una tarea), mantenida en el entorno y cargada en la ventana de contexto solo cuando es relevante. La unidad de divulgación progresiva en un harness.
Una Skill es lo que el agente lee; una Tool es lo que el agente llama. Una Skill podría decir "cuando el usuario pida un deploy, ejecuta bash deploy.sh y verifica con la herramienta gh": la Skill es la prosa; bash y gh son las herramientas.
Una Skill también es bajo demanda. AGENTS.md se carga en cada turno y paga un costo de tokens en cada solicitud al proveedor del modelo; una Skill se carga solo cuando el agente decide que debe hacerlo. Todo lo que no necesite estar en contexto en cada turno pertenece a una Skill, no a AGENTS.md. Esta es la divulgación progresiva en acción.
Y una Skill es portable. El mismo SKILL.md se ejecuta sin cambios en Claude Code y OpenCode. La disciplina viaja con el archivo; el harness es intercambiable.
5.2 Dónde viven las Skills
Ambos harnesses escanean directorios conocidos al iniciar la sesión, leen el frontmatter YAML de cada SKILL.md y muestran los nombres y descripciones al agente. El cuerpo se carga solo cuando el agente decide que la Skill es relevante.
La CLI skills instala un pack comunitario en .agents/skills/, la ubicación estándar entre herramientas. Un directorio de skills instaladas se ve así:
project/
└── .agents/
└── skills/
└── grill-me/
└── SKILL.md
El mismo formato SKILL.md funciona sin cambios en ambos harnesses. Lo que cambia es qué directorios escanea cada harness, y eso cambia un paso de la instalación.
Claude Code 2.1.141 escanea .claude/skills/<name>/SKILL.md (y globalmente ~/.claude/skills/). No escanea .agents/skills/. La CLI skills instala en .agents/skills/, y enlaza la instalación dentro de .claude/skills/ solo cuando ese directorio ya existe. Así que créalo primero y luego instala:
mkdir -p .claude/skills
npx skills@latest add mattpocock/skills
Con .claude/skills/ presente antes de la instalación, cada skill queda enlazada ahí y Claude Code descubre el pack. (Si instalas primero y Claude Code no encuentra /grill-me, la causa es el directorio faltante: crea .claude/skills/ y vuelve a ejecutar la instalación.)
Invoca una Skill pidiéndolo en lenguaje natural ("grill me on this plan"), y el agente la carga cuando coincide con la descripción del frontmatter. Claude Code también acepta una invocación slash explícita: escribe /grill-me para cargar esa Skill por nombre.
Un formato, dos harnesses, sin paso de traducción. La ruta de instalación es lo único que difiere, y difiere por un mkdir.
5.3 Anatomía de un SKILL.md
Un SKILL.md tiene dos partes: frontmatter YAML (los metadatos que escanea el harness) y un cuerpo en markdown (las instrucciones que el agente lee al cargarlo).
La Skill más destacada del pack de Matt Pocock, grill-me, está aquí completa: siete líneas de cuerpo.
---
name: grill-me
description: Interview the user relentlessly about a plan or design until reaching shared understanding, resolving each branch of the decision tree. Use when user wants to stress-test a plan, get grilled on their design, or mentions "grill me".
---
Interview me relentlessly about every aspect of this plan until we reach a shared understanding. Walk down each branch of the design tree, resolving dependencies between decisions one-by-one. For each question, provide your recommended answer.
Ask the questions one at a time.
If a question can be answered by exploring the codebase, explore the codebase instead.
Esa es toda la Skill, y grill-me es la skill más usada de un pack que ha reunido decenas de miles de estrellas en GitHub. Tres observaciones se generalizan:
- Las Skills no tienen que ser largas para ser impactantes. Esta es básicamente tres frases y transforma la conversación de planificación. Añade longitud solo cuando la longitud se gana su lugar.
- El frontmatter hace trabajo real. El harness muestra al agente la
description, no el cuerpo, así que la descripción debe ser lo bastante específica para que el agente la cargue en los momentos correctos. "Use when user wants to stress-test a plan, get grilled on their design, or mentions 'grill me'" es mucho mejor que "for grilling." - El cuerpo se dirige al agente en segunda persona, con el mismo tono que usarías con un colaborador junior. "Interview me relentlessly." "Ask the questions one at a time." Directo, declarativo, sin rodeos.
Una Skill más elaborada (to-prd, to-issues, tdd, improve-codebase-architecture) extiende la misma forma con pasos numerados, una plantilla y referencias a otras Skills. El principio se mantiene: codifica el proceso; no codifiques la respuesta.
5.4 Los cinco principios diarios (y las mejores Skills actuales para cada uno)
Cinco principios corresponden uno a uno con etapas del pipeline de §4.1. Cada principio tiene una implementación actual de primera clase: un SKILL.md instalable hoy. La tabla siguiente referencia el pack más usado (el de Matt Pocock, en skills.sh/mattpocock). Cada nombre de Skill enlaza a su SKILL.md canónico; los cuerpos son cortos y vale la pena leerlos.
| Etapa | Skill | Qué hace |
|---|---|---|
| Idea → design concept alineado | grill-me | Entrevista socrática hasta alcanzar alineación. |
| Concepto alineado → documento de destino | to-prd | Sintetiza la conversación en un PRD con historias de usuario, decisiones de implementación y lista de módulos a modificar. |
| PRD → backlog de issues | to-issues | Divide el PRD en tickets de vertical slice con relaciones de bloqueo explícitas. |
| Issue → slice implementada | tdd | Red-green-refactor, una slice a la vez. |
| Salud del codebase, continua | improve-codebase-architecture | Encuentra módulos superficiales, propone profundizaciones y abre un issue RFC. |
Antes de ejecutar cualquiera de estas. El pack de Matt espera un paso bootstrap único por repo,
setup-matt-pocock-skills, que crea la configuración del issue tracker del repo, un bloque## Agent skillsen tuAGENTS.md/CLAUDE.mdy un directoriodocs/agents/. Las skills de ingeniería leen de esa estructura (yto-prd/to-issuestambién se apoyan endocs/adr/si existe), así que ejecuta el setup una vez después de instalar el pack y antes de la primera invocación deto-issuesotdd.
La description: del frontmatter de cada Skill es la línea que el harness escanea al iniciar la sesión para decidir qué exponer al agente. Esa descripción determina si el agente carga la Skill en el momento correcto, así que lleva el peso real. grill-me aparece con su SKILL.md completo literalmente en §5.3; para las demás, esto es lo que hace cada una (parafraseado desde las skills instaladas, no citado literalmente):
to-prdconvierte la conversación actual en un PRD y lo publica en el issue tracker del proyecto. No te entrevista de nuevo; sintetiza lo que ya está en contexto.to-issuesdivide un plan, especificación o PRD en issues que se pueden tomar de forma independiente en el issue tracker del proyecto, cortados verticalmente con relaciones de bloqueo explícitas, y etiqueta cada uno como listo para que un agente lo tome.tddejecuta un bucle estricto red-green-refactor para construir una función o corregir un bug: una prueba fallida primero, el código justo para pasar, refactorizar, repetir, con pruebas en las interfaces de módulo y no en helpers internos.improve-codebase-architectureencuentra oportunidades de profundización en un codebase, informado por el lenguaje de dominio enCONTEXT.mdy las decisiones endocs/adr/, y las propone sin modificar el código.
Un lector que quiera el frontmatter exacto debe ejecutar cat sobre los archivos SKILL.md instalados o abrir las fuentes enlazadas; la redacción anterior es un resumen fiel, no una cita. Observa un comportamiento que los resúmenes hacen explícito y que el lector verá directamente: to-prd y to-issues escriben en tu issue tracker, no solo en un archivo local.
Tres propiedades se generalizan en las cinco:
- La
descriptionhace el trabajo de carga. Debe ser lo bastante específica para que el agente reconozca cuándo cargar la Skill, no solo de qué trata. Las cláusulas "Use when..." y el alcance negativo explícito son donde vive esa especificidad. - Las Skills nombran sus límites.
to-prdno entrevista de nuevo;improve-codebase-architectureno modifica el codebase. Estas cláusulas negativas son la forma en que las Skills componen sin pisarse. - Las Skills nombran sus emparejamientos.
tddestá emparejada implícitamente con el issue que implementa;to-issuesestá emparejada con el PRD que divide. El pipeline es una cadena de Skills, cada una entregando a la siguiente.
La arquitectura de este pipeline (Skills, vertical slices, módulos profundos, sandboxes) es agnóstica al modelo. Su fiabilidad operativa no lo es. Un modelo frontier que sigue instrucciones con fuerza (Claude Sonnet/Opus, clase GPT-5, Gemini 2.5 Pro) carga la Skill correcta por coincidencia de descripción, ejecuta en orden un cuerpo de Skill con varios pasos y termina por sí mismo una entrevista de grilling cuando se alcanza la alineación. En un modelo económico o local (deepseek-chat, clase Haiku, Llama-70B, la mayoría de los modelos locales), esos comportamientos se degradan: las Skills no disparan, la secuenciación de varios pasos se desliza y se rompen los contratos de salida literal (la señal NO_MORE_TASKS en §6.5). La lección de §2.3 también es la cura aquí: en un modelo más débil, coloca más andamiaje. Invoca Skills explícitamente por nombre en lugar de depender de coincidencias de descripción, mantén los cuerpos de Skill cortos y declarativos, y declara lo que el modelo no debe hacer, no solo lo que debería hacer.
Una sexta Skill del pack de Matt Pocock cierra el bucle sobre la Falla 2 (agente verboso / sin vocabulario compartido): grill-with-docs. Es la misma entrevista socrática que grill-me, pero también actualiza CONTEXT.md y los Architecture Decision Records de docs/adr/ en línea a medida que las decisiones cristalizan durante la conversación. En la charla Software Fundamentals Matter More Than Ever de Matt, esto empezó como una "skill de lenguaje ubicuo" independiente que escaneaba el codebase y escribía un glosario de dominio; desde entonces se integró en la propia skill de grilling, bajo el principio de que la terminología se resuelve mejor en el momento en que se toma una decisión, no como una pasada posterior separada. Usa grill-me para conversaciones de diseño greenfield donde todavía no hay contexto de proyecto; usa grill-with-docs cuando el repo ya tenga un CONTEXT.md y ADRs que quieras mantener actualizados.
Construye tus propias Skills primero; recurre al pack de otra persona después. La mejor Skill es la que captura el proceso de tu equipo. Hacer fork de
mattpocock/skillses un gran punto de partida. Ser dueño del stack, poder leer cada Skill que cargas, es lo que te da observabilidad cuando algo sale mal.
6. El pipeline en la práctica
Esta sección ejecuta el flujo de trabajo de extremo a extremo sobre un ejemplo trabajado: añadir un servicio de gamificación a una plataforma de cursos. El mismo ejemplo se construye una vez en Python y una vez en TypeScript; cada paso se ejecuta en Claude Code y en OpenCode. El punto no es el ejemplo; es la forma. Interioriza la forma una vez y el lenguaje y el harness pasan a ser detalles.
6.1 Etapa 1: hacer grilling de la idea
Un mensaje de Slack de un stakeholder:
"Nuestros números de retención no son muy buenos. Los estudiantes se registran, hacen unas pocas lecciones y luego abandonan. Me encantaría añadir algo de gamificación a la plataforma: puntos, streaks, niveles."
Eso no es una especificación; es un deseo. Dáselo a un agente y di "constrúyelo", y recibirás algo que no coincide con lo que el stakeholder tenía en mente, porque ninguno de los dos sabe todavía qué tenía en mente. Invoca grill-me.
$ claude
> /grill-me
> Read the Slack message in client-brief.md and grill me on adding gamification.
El agente lee el brief, explora el codebase para entender los modelos existentes (Lesson, Quiz, Enrollment) y empieza a hacer preguntas, una a la vez, con una respuesta recomendada para cada una:
Q1. What earns points? My recommendation: lesson completion + quiz pass.
Skip video-watch events (too noisy, gameable).
Q2. Are points retroactive for existing lesson_progress records?
My recommendation: yes, backfill once on migration, then live going forward.
Q3. Streak granularity? My recommendation: daily, with a 1-day grace period.
Q4. Level thresholds: linear, quadratic, or hand-tuned?
My recommendation: quadratic, capped at level 50.
...
Las sesiones terminan entre quince y cien preguntas. La sesión termina cuando el agente informa que no quedan decisiones sin resolver y tienes un design concept estable. Sin código, sin PRD. La salida del grilling es un historial de chat; el historial de chat es el design concept hecho explícito.
6.2 Etapa 2: de conversación a PRD
Cuando el design concept se estabiliza, invoca to-prd. La Skill no te entrevista de nuevo; sintetiza lo que ya dijiste en un Product Requirements Document.
> /to-prd
La salida es un documento markdown que sigue una plantilla fija:
# PRD: Course Platform Gamification
## Problem Statement
Students drop off after a handful of lessons. Retention metrics
indicate completion rates ... [synthesised from the brief]
## Solution
Add a points/streaks/levels gamification layer ...
## User Stories
1. As a student, I earn 10 points when I complete a lesson.
2. As a student, I earn 25 points when I pass a quiz.
3. As a student, I see my current streak on the dashboard.
4. As a student, I see my level on my profile.
5. As an admin, I can see aggregate engagement metrics.
... [12-20 more, each independently verifiable]
## Modules Touched
- NEW: gamification_service (deep module, owns points + streaks + levels)
- MODIFIED: lesson_progress_service (emits events on completion)
- MODIFIED: dashboard route (reads from gamification_service)
- NEW DB: point_events table, streak_state table
## Implementation Decisions
- Level formula: floor(sqrt(total_points / 50))
- Streak grace: 1 missed day allowed
- Backfill: one-time job at deploy
## Out of Scope
- Leaderboards (separate PRD)
- Push notifications (separate PRD)
Qué leer en el PRD antes de aprobarlo. Busca deriva por encima, no corrijas estilo. Tú y el agente ya comparten el design concept de la sesión de grilling, y el agente es excelente resumiendo; leer línea por línea es trabajo de dumb zone. Enfoca tu atención en los cuatro lugares donde la síntesis puede derivar: las historias de usuario (¿se cayó o inventó alguna?), los módulos tocados (¿el límite sigue coincidiendo con lo que discutieron?), las decisiones de implementación (¿coinciden con las decisiones tomadas durante el grilling?) y fuera de alcance (¿se movió el límite?). Dos minutos de lectura enfocada detectan casi todas las fallas; leer todo el documento detecta las mismas fallas y cuesta diez veces la atención.
6.3 Etapa 3: de PRD a issues de vertical slice
El PRD describe el destino. La siguiente Skill describe el viaje: cómo dividir el PRD en issues que se puedan tomar de forma independiente, cortados verticalmente, con relaciones de bloqueo entre ellos.
Ejecuta to-issues. Para el PRD de gamificación produce un tablero Kanban pequeño:
┌────────────────────────────────────────────────────────────┐
│ Issue #1 - Award points for lesson completion (E2E) │
│ blocked by: nothing. Type: AFK. │
│ Touches: schema, service, lesson route, dashboard widget │
└────────────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────────────┐
│ Issue #2 - Award points for quiz pass (E2E) │
│ blocked by: #1. Type: AFK. │
└────────────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────────────┐
│ Issue #3 - Streak counter (E2E) │
│ blocked by: #1. Type: AFK. │
└────────────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────────────┐
│ Issue #4 - Level threshold + UI badge │
│ blocked by: #2. Type: AFK. │
└────────────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────────────┐
│ Issue #5 - Retroactive backfill of historical lessons │
│ blocked by: #1. Type: human-in-the-loop. │
└────────────────────────────────────────────────────────────┘
Varias propiedades no son accidentales:
- El Issue #1 entrega una slice funcional. Si el equipo fusionara solo #1 y se detuviera, la plataforma tendría una función de gamificación operativa, aunque mínima. Con corte horizontal, la "fase 1" habría producido una tabla de base de datos que no hacía nada.
- El DAG admite paralelismo. #2 y #3 pueden ejecutarse en sesiones paralelas sobre ramas paralelas cuando #1 se haya fusionado. Dos agentes AFK, dos PRs para la mañana.
- #5 está marcado como human-in-the-loop, no AFK. Los backfills tocan datos históricos; un humano observa cada paso. El campo
Typele dice al bucle AFK de §6.5 que lo omita.
6.4 Etapa 4: implementación: TDD sobre una slice
Elige la parte superior desbloqueada de la cola: Issue #1. Invoca tdd. La Skill impone red-green-refactor estricto: escribir una prueba fallida, verla fallar, escribir solo lo suficiente para hacerla pasar, verla pasar, refactorizar con todas las pruebas aún en verde, repetir.
¿Por qué TDD específicamente? Dos razones.
- Obliga a pasos pequeños. Sin TDD, un agente produce seis archivos de código y luego escribe una capa de pruebas alrededor. Esas pruebas tienden a hacer trampa; ejercitan la implementación, no el comportamiento. Con TDD, la prueba se escribe primero, antes de que exista la implementación, así que no puede moldearse para ajustarse a lo que escribió el agente.
- Aporta retroalimentación cada minuto. Cada prueba que pasa es un punto de control. Si el agente deriva, la siguiente prueba fallida lo atrapa antes de que produzca cien líneas de basura.
Aquí está la slice para Issue #1 en ambos lenguajes: un módulo profundo GamificationService con una interfaz pequeña, una implementación amplia y un archivo de pruebas enfocado. La Skill tdd asume un test runner funcional: instala uno antes de empezar, pip install pytest para la slice en Python o npm install -D vitest para la slice en TypeScript, o el primer paso rojo fallará por runner faltante en lugar de por implementación faltante.
Qué importa aquí. El ejemplo de abajo muestra dos cosas visibles sin leer la sintaxis:
- El servicio tiene una interfaz pública diminuta: solo dos métodos (
award_lesson_completionytotal_points). Todo lo demás está escondido dentro de la clase. Los llamadores no pueden tocar los internos.- La prueba llama solo esos dos métodos. La prueba no hurga en helpers internos. Comprueba el comportamiento que vería el llamador ("después de tres completions, el total es 30"), no cómo lo calcula el servicio.
Esa forma (interfaz pequeña, implementación amplia, pruebas en el límite) es lo que §7 llama un módulo profundo. Las versiones en Python y TypeScript son equivalentes línea por línea.
- Python
- TypeScript
# gamification/service.py - the deep module's interface
from dataclasses import dataclass
from datetime import datetime
from typing import Protocol
@dataclass(frozen=True)
class PointAward:
student_id: str
points: int
reason: str
awarded_at: datetime
class PointEventStore(Protocol):
def append(self, award: PointAward) -> None: ...
def total_for_student(self, student_id: str) -> int: ...
class GamificationService:
"""Awards and totals points. Streaks and levels live here too,
but in the same module, so the interface stays small."""
LESSON_COMPLETION_POINTS = 10
def __init__(self, store: PointEventStore, clock=datetime.utcnow) -> None:
self._store = store
self._clock = clock
def award_lesson_completion(self, student_id: str) -> PointAward:
award = PointAward(
student_id=student_id,
points=self.LESSON_COMPLETION_POINTS,
reason="lesson_completion",
awarded_at=self._clock(),
)
self._store.append(award)
return award
def total_points(self, student_id: str) -> int:
return self._store.total_for_student(student_id)
# gamification/test_service.py - written FIRST
from datetime import datetime
from gamification.service import GamificationService, PointAward
class InMemoryStore:
def __init__(self) -> None:
self._events: list[PointAward] = []
def append(self, award: PointAward) -> None:
self._events.append(award)
def total_for_student(self, student_id: str) -> int:
return sum(a.points for a in self._events if a.student_id == student_id)
def test_lesson_completion_awards_ten_points():
store = InMemoryStore()
fixed_clock = lambda: datetime(2026, 5, 10, 12, 0, 0)
svc = GamificationService(store, clock=fixed_clock)
award = svc.award_lesson_completion("student-42")
assert award.points == 10
assert award.reason == "lesson_completion"
assert svc.total_points("student-42") == 10
def test_multiple_completions_accumulate():
svc = GamificationService(InMemoryStore())
for _ in range(3):
svc.award_lesson_completion("student-42")
assert svc.total_points("student-42") == 30
// gamification/service.ts - the deep module's interface
export interface PointAward {
readonly studentId: string;
readonly points: number;
readonly reason: string;
readonly awardedAt: Date;
}
export interface PointEventStore {
append(award: PointAward): void;
totalForStudent(studentId: string): number;
}
export class GamificationService {
static readonly LESSON_COMPLETION_POINTS = 10;
constructor(
private readonly store: PointEventStore,
private readonly clock: () => Date = () => new Date(),
) {}
awardLessonCompletion(studentId: string): PointAward {
const award: PointAward = {
studentId,
points: GamificationService.LESSON_COMPLETION_POINTS,
reason: "lesson_completion",
awardedAt: this.clock(),
};
this.store.append(award);
return award;
}
totalPoints(studentId: string): number {
return this.store.totalForStudent(studentId);
}
}
// gamification/service.test.ts - written FIRST
import { describe, it, expect } from "vitest";
import { GamificationService, PointAward, PointEventStore } from "./service";
class InMemoryStore implements PointEventStore {
private events: PointAward[] = [];
append(a: PointAward) {
this.events.push(a);
}
totalForStudent(id: string) {
return this.events
.filter((e) => e.studentId === id)
.reduce((sum, e) => sum + e.points, 0);
}
}
describe("GamificationService", () => {
it("awards ten points on lesson completion", () => {
const fixedClock = () => new Date("2026-05-10T12:00:00Z");
const svc = new GamificationService(new InMemoryStore(), fixedClock);
const award = svc.awardLessonCompletion("student-42");
expect(award.points).toBe(10);
expect(award.reason).toBe("lesson_completion");
expect(svc.totalPoints("student-42")).toBe(10);
});
it("accumulates across multiple completions", () => {
const svc = new GamificationService(new InMemoryStore());
for (let i = 0; i < 3; i++) svc.awardLessonCompletion("student-42");
expect(svc.totalPoints("student-42")).toBe(30);
});
});
Eso es un módulo profundo en acción: una interfaz pública de dos métodos (awardLessonCompletion, totalPoints) sobre una implementación libre de crecer hasta miles de líneas. Para demostrar la afirmación en lugar de declararla, mira qué pasa cuando llega el Issue #3 (contador de streak).
Qué importa aquí. Observa la interfaz pública, no las líneas. Antes de esta slice, el servicio tenía dos métodos (
awardLessonCompletion,totalPoints). Después de esta slice tiene tres (los mismos dos máscurrentStreak). La implementación creció bastante, con un store de streaks, un registro de actividad y un helper de fecha, pero nada de eso se filtra hacia fuera. Los llamadores ven un método nuevo. Los llamadores existentes no hacen nada distinto. Las pruebas existentes siguen en verde. La nueva prueba llama solo al nuevo método. Eso es lo que "profundo" significa en la práctica: el comportamiento crece; la superficie apenas se mueve.
- Python
- TypeScript
# gamification/service.py - interface gains ONE method, nothing else changes
class GamificationService:
LESSON_COMPLETION_POINTS = 10
def __init__(self, store, streaks=None, clock=datetime.utcnow):
self._store = store
self._streaks = streaks or InMemoryStreakStore() # internal detail
self._clock = clock
def award_lesson_completion(self, student_id: str) -> PointAward:
# unchanged signature; internally also updates streak state
award = PointAward(...)
self._store.append(award)
self._streaks.record_activity(student_id, self._clock().date())
return award
def total_points(self, student_id: str) -> int: # unchanged
return self._store.total_for_student(student_id)
def current_streak(self, student_id: str) -> int: # NEW - only addition
return self._streaks.streak_length(student_id, today=self._clock().date())
# gamification/test_service.py - existing tests untouched; ONE new test added
def test_streak_grows_with_consecutive_daily_completions():
days = [date(2026, 5, 8), date(2026, 5, 9), date(2026, 5, 10)]
clock = iter(datetime.combine(d, time()) for d in days)
svc = GamificationService(InMemoryStore(), clock=lambda: next(clock))
for _ in days:
svc.award_lesson_completion("student-42")
assert svc.current_streak("student-42") == 3
// gamification/service.ts - interface gains ONE method, nothing else changes
export class GamificationService {
static readonly LESSON_COMPLETION_POINTS = 10;
constructor(
private readonly store: PointEventStore,
private readonly streaks: StreakStore = new InMemoryStreakStore(),
private readonly clock: () => Date = () => new Date(),
) {}
awardLessonCompletion(studentId: string): PointAward {
// unchanged signature; internally also updates streak state
const award: PointAward = {
/* ... */
};
this.store.append(award);
this.streaks.recordActivity(studentId, this.clock());
return award;
}
totalPoints(studentId: string): number {
// unchanged
return this.store.totalForStudent(studentId);
}
currentStreak(studentId: string): number {
// NEW - only addition
return this.streaks.streakLength(studentId, this.clock());
}
}
// gamification/service.test.ts - existing tests untouched; ONE new test added
it("grows the streak across consecutive daily completions", () => {
const days = [
new Date("2026-05-08T12:00:00Z"),
new Date("2026-05-09T12:00:00Z"),
new Date("2026-05-10T12:00:00Z"),
];
let i = 0;
const svc = new GamificationService(
new InMemoryStore(),
undefined,
() => days[i],
);
for (i = 0; i < days.length; i++) svc.awardLessonCompletion("student-42");
expect(svc.currentStreak("student-42")).toBe(3);
});
Pasaron tres cosas, todas diagnósticas de un módulo profundo sano:
- La interfaz creció en un método, no en cinco. Una alternativa superficial habría expuesto
recordActivity,streakLength,streakStore,setActivityCalendar: mecánica interna filtrándose al límite. La versión profunda da a los llamadores exactamente lo que necesitan (currentStreak) y nada más. - Las pruebas existentes no cambiaron. El comportamiento que fijan sigue vigente; el archivo de pruebas es puramente aditivo. Eso es lo que compras al probar en la interfaz.
- El nuevo comportamiento recibió una prueba en el mismo límite. El store de streaks, el registro de actividad y el helper de fecha no se prueban directamente; se prueban indirectamente mediante el contrato de
currentStreak, que es el nivel correcto.
La siguiente slice (Issue #4, umbral de nivel) sigue el mismo patrón: un método añadido, pruebas existentes intactas, una nueva prueba de comportamiento en el límite.
6.5 Etapa 5: el bucle AFK
Tienes cinco issues en el backlog y una Skill tdd instalada. No quieres sentarte al teclado mientras el agente avanza por ellos. Quieres empujar cinco tracer bullets por el sistema en paralelo, cenar y revisar cinco PRs por la mañana.
El bucle AFK es un script de shell: recopilar los issues AFK desbloqueados, entregárselos al agente con un prompt claro, ejecutar dentro de un contenedor sandboxed, repetir hasta vaciar la cola. Siguen dos implementaciones: una versión mínima en bash (funciona con cualquier harness) y un orquestador TypeScript estructurado que ejecuta slices en paralelo.
6.5.1 Bucle AFK mínimo (bash)
Qué importa aquí. El script hace cinco cosas en un bucle hasta que no queda nada por hacer: (1) lee todos los issues abiertos de una carpeta; (2) lee el historial reciente de commits; (3) entrega ambos al agente con un prompt claro; (4) el agente elige un issue y lo implementa; (5) comprueba si la cola está vacía y, si lo está, se detiene. El humano no está en el teclado durante nada de esto. El script arranca y camina solo.
#!/usr/bin/env bash
# ralph.sh - the simplest AFK loop. Works with either harness.
# Loops over /issues/*.md, picks the highest-priority AFK issue,
# implements it inside a sandbox, commits, repeats until done.
set -euo pipefail # bash safety: exit on any error, undefined var, or failed pipe
PROMPT_FILE="${1:-prompts/implement.md}"
ISSUES_DIR="${2:-issues}"
# Two env vars carry the harness difference. AGENT_CMD is the binary;
# AGENT_PERM_FLAG is its skip-approvals flag, which is NOT the same
# string in both harnesses (see the tool-tabs below). Everything else
# in this script is byte-identical across Claude Code and OpenCode.
CMD="${AGENT_CMD:-claude}"
PERM_FLAG="${AGENT_PERM_FLAG:---permission-mode acceptEdits}"
while :; do
ISSUES=$(cat "$ISSUES_DIR"/*.md 2>/dev/null || true)
COMMITS=$(git log --oneline -5)
PROMPT=$(cat "$PROMPT_FILE")
RESULT=$($CMD $PERM_FLAG <<EOF
$PROMPT
## Open issues
$ISSUES
## Recent commits
$COMMITS
EOF
)
# Exit only on a line that is *exactly* the sentinel, so the loop
# does not stop if the agent merely quotes the token in prose.
if echo "$RESULT" | grep -qx "NO_MORE_TASKS"; then
echo "queue drained - exiting"
break
fi
done
<!-- prompts/implement.md - fed to the agent on every iteration -->
You are operating AFK on the gamification project.
1. From the open issues, pick the highest-priority issue whose
`Type:` is `AFK` and whose blockers are all closed.
If none, reply with a line containing only `NO_MORE_TASKS` and stop.
2. Read the PRD it references.
3. Use the `tdd` skill to implement one vertical slice.
4. Run the project feedback loops (typecheck, tests, lint).
Do not commit if any fail.
5. Commit referencing the issue number and close the issue.
Las Skills, el prompt y los issues son byte-identical en los dos harnesses. Lo que difiere es el binario del harness y su flag para omitir aprobaciones: Claude Code usa --permission-mode acceptEdits, OpenCode usa --dangerously-skip-permissions para el mismo efecto. Las dos variables de entorno de abajo llevan esa diferencia; el heredoc por stdin funciona para ambos.
AGENT_CMD="claude" \
AGENT_PERM_FLAG="--permission-mode acceptEdits" ./ralph.sh
6.5.2 Orquestador AFK paralelo (TypeScript)
La versión bash ejecuta slices de forma secuencial. Cuando confías en el bucle, el siguiente punto de apalancamiento es la ejecución paralela: elegir todos los issues desbloqueados, levantar un worktree sandboxed por issue, ejecutarlos concurrentemente y fusionar. El orquestador de abajo bosqueja el patrón; existen implementaciones de producción como bibliotecas dedicadas de sandboxing tanto en el ecosistema de Claude Code como en el de OpenCode.
Qué importa aquí. Tres ideas; todo lo demás es mecánica de implementación:
- Paralelo, no secuencial. En lugar de hacer la slice 1, luego la slice 2 y luego la slice 3, el orquestador hace las tres al mismo tiempo, cada una en su propio espacio de trabajo aislado. Por la mañana tienes tres pull requests en lugar de una.
- Cada ejecución paralela está en sandbox. Un "sandboxed worktree" es una copia separada del codebase (un
git worktreees la forma incorporada de git de tener varias copias checked-out) ejecutándose dentro de un contenedor que no puede dañar tu portátil. Si el agente hace algo mal, el radio de daño es un worktree.- El revisor es un agente separado en una sesión fresca. Un agente distinto, con un modelo distinto (más barato), mira solo el diff y lo compara con los estándares de codificación del proyecto. Revisar en el mismo chat que escribió el código sería revisar dentro de la dumb zone.
El código en sí es un script Node.js de nivel medio; la línea
Promise.alles donde ocurre el paralelismo.
// orchestrator.ts - parallel AFK loop with sandboxed worktrees
import { spawn } from "node:child_process";
import { readdir, readFile } from "node:fs/promises";
interface Issue {
id: string; // e.g. "issue-001"
title: string;
type: "AFK" | "human-in-the-loop";
blockedBy: string[]; // ids of blocking issues
closed: boolean;
}
const HARNESS = process.env.AGENT_CMD ?? "claude"; // "claude" or "opencode run"
async function loadIssues(dir: string): Promise<Issue[]> {
const files = await readdir(dir);
return Promise.all(
files.map(async (f) => {
const raw = await readFile(`${dir}/${f}`, "utf8");
return parseIssue(f, raw); // omitted for brevity
}),
);
}
function unblocked(issues: Issue[]): Issue[] {
const closed = new Set(issues.filter((i) => i.closed).map((i) => i.id));
return issues.filter(
(i) =>
!i.closed && i.type === "AFK" && i.blockedBy.every((b) => closed.has(b)),
);
}
function runInSandbox(issue: Issue): Promise<{ ok: boolean; branch: string }> {
return new Promise((resolve) => {
const branch = `afk/${issue.id}`;
// 1. create a git worktree on a fresh branch
// 2. start a docker container with that worktree mounted r/w
// 3. run the harness inside, with the implement.md prompt
const proc = spawn("scripts/run-sandbox.sh", [HARNESS, branch, issue.id], {
stdio: "inherit",
});
proc.on("exit", (code) => resolve({ ok: code === 0, branch }));
});
}
async function main() {
let issues = await loadIssues("./issues");
while (true) {
const ready = unblocked(issues);
if (ready.length === 0) {
console.log("backlog drained or fully blocked - exiting");
break;
}
// run all unblocked issues in parallel, one sandbox each
const results = await Promise.all(ready.map(runInSandbox));
// automated review on each successful branch BEFORE merge
// (in a fresh session - smart-zone reviewer)
for (const r of results.filter((r) => r.ok)) {
await reviewBranch(r.branch);
}
// reload issues from disk; agents may have closed some and opened others
issues = await loadIssues("./issues");
}
}
async function reviewBranch(branch: string): Promise<void> {
// spawn a *separate* agent session, smaller model, with the
// diff and the coding-standards skill as input. Open a comment
// on the PR. Do NOT auto-merge.
}
main();
Tres principios están incrustados en el orquestador e importan más que el código:
- Los sandboxes son obligatorios. AFK con
--permission-mode bypassPermissionsy sin sandbox es la forma en que se destruyen repositorios. Cada slice recibe un contenedor fresco, un worktree fresco, sin credenciales de producción y sin salida de red más allá de lo que necesita. - El revisor es un agente separado. Un revisor en la misma sesión que el implementador revisa dentro de la dumb zone. Un revisor en una sesión fresca, con solo el diff y los estándares, ve el trabajo con claridad. Un modelo más pequeño está bien para revisión (a menudo es más crítico); usa el grande para implementación.
- El bucle recarga los issues desde disco en cada iteración. Cuando QA genera nuevos issues en §6.6, aparecen automáticamente en la cola.
6.5.3 Bucles persistentes y agentes ambientales
Los bucles anteriores se ejecutan una vez por backlog. Arrancan, vacían la cola y se detienen. La siguiente evolución es mantenerlos en ejecución.
Un bucle, en el sentido de Boris Cherny, es una invocación de agente programada con cron para ejecutarse cada minuto, cada cinco minutos o cada treinta minutos contra un trabajo permanente pequeño. Cada invocación es una sesión fresca, así que empieza en la smart zone cada vez y nunca acumula deriva de dumb zone. El agente no permanece vivo; el trabajo permanece vivo, y nace un nuevo agente para atender cada tick.
Un conjunto de bucles funcional en un proyecto podría incluir:
- Un PR janitor: vuelve a ejecutar CI flaky, hace rebase contra
main, corrige typos y comentarios de lint dejados por revisores. - Un CI healer: cuando una prueba flaky empieza a fallar de forma intermitente, investiga y la corrige.
- Un feedback clusterer: recoge feedback entrante de usuarios cada treinta minutos, lo agrupa por tema y publica un resumen en Slack.
No son herramientas. Son agentes ambientales: una fuerza laboral de IA persistente y de baja intensidad que se ejecuta junto al proyecto y maneja el impuesto de fondo que históricamente consumía horas de ingeniería, como limpieza de PRs, higiene de CI, triage de tickets, mantenimiento de dependencias, digestión de logs y resúmenes de monitoreo. Ninguna tarea individual justifica una ejecución AFK completa; juntas consumen tiempo real. Ejecútalas como bucles y desaparecen del día del ingeniero.
Un bucle persistente mínimo es una línea de cron sobre un archivo de prompt:
Qué importa aquí. Un job de
cronejecuta un comando según una agenda: cada martes a las 9am, por ejemplo, o cada 30 minutos. Los cinco caracteres*/30 * * * *significan "cada 30 minutos, cada hora, todos los días" (crontab.guru decodifica cualquier agenda). La línea de abajo le dice al sistema operativo: "cada media hora, ve a la carpeta de mi proyecto y ejecuta el agente PR-janitor por un tick." Cada tick es una sesión fresca de agente que dura lo que tarda en atender las PRs que requieran atención y luego sale. El trabajo vive para siempre; los agentes son desechables.
# crontab -e
# every 30 minutes, run the PR-janitor agent in the project
*/30 * * * * cd /home/me/project && \
AGENT_CMD="claude" ./scripts/run-once.sh prompts/pr-janitor.md
<!-- prompts/pr-janitor.md -->
You are the PR janitor for this project.
1. List my open PRs (`gh pr list --author @me`). # gh = GitHub's CLI
2. For each PR:
- If CI failed on a known-flaky test, retrigger only that job.
- If the PR has merge conflicts with main, attempt a clean rebase.
If the rebase is non-trivial, leave a comment and stop.
- If a reviewer left a typo / lint comment, fix it and push.
3. Commit only changes you can explain in one sentence.
4. Do nothing else. Output a one-line summary.
Un patrón más pesado es la routine: el mismo bucle ejecutado del lado del servidor en lugar de desde el cron de tu portátil, para que sobreviva a suspensión, reinicios y viajes. Están surgiendo funciones de agentes programados del lado del servidor en productos de agentes de codificación; trata la versión con cron local como la forma de desarrollo y la versión del lado del servidor como la forma de producción. El prompt es el mismo; solo cambia el scheduler.
Dos reglas de diseño gobiernan los bucles persistentes:
- Cada tick es una sesión fresca. Ningún estado sobrevive entre ticks excepto lo escrito en el entorno (las PRs, los logs de CI, un pequeño archivo de estado). El bucle es stateless a propósito; el prompt lleva el rol.
- Cada bucle tiene un trabajo. Un bucle que hace trabajo de PR-janitor y CI healing y feedback clustering se degradará en una sesión que no hace bien ninguno. Un bucle por rol, como una Skill por rol.
El patrón AFK ya es de extremo a extremo: §6.5.1 ejecuta una slice secuencialmente; §6.5.2 ejecuta muchas slices en paralelo; §6.5.3 mantiene la fuerza laboral en ejecución indefinida sobre los ritmos que genera el propio proyecto. Cada paso añade throughput sin sumar a nadie al equipo: la forma operativa de una fuerza laboral de Digital FTEs.
6.6 Etapa 6: revisión humana y QA
La mañana después de ejecutar el bucle tienes N pull requests. Lee los diffs, no el resumen del agente sobre los diffs. El resumen es la palabra del agente sobre lo que hizo; el diff es lo que hizo realmente. A menudo difieren de formas sutiles que solo importan a escala de producción.
Un ejemplo concreto, de la slice de gamificación de §6.4. El resumen de PR del agente decía: "Añadidos puntos por completar lecciones. Las pruebas pasan. El widget del dashboard muestra el total actual." El diff decía lo mismo, salvo que la pasada de QA encontró que abrir el dashboard antes de completar cualquier lección fallaba con TypeError: Cannot read property 'awarded_at' of null. El agente había manejado el estado vacío en el servicio (devolviendo 0 desde total_points), pero el widget React asumía que existía un timestamp last_award_at. Un null check, arreglo fácil; pero las pruebas del agente no cubrían el render de UI en estado vacío, porque la historia de usuario de la slice asumía implícitamente que había al menos un award. Esa observación vuelve al backlog como un nuevo issue ("añadir estado vacío al widget del dashboard; cubrirlo con una prueba") bloqueado por nada, tipo AFK. La PR se fusiona; el turno de noche toma el nuevo issue mañana. Este bucle, donde el humano encuentra la brecha, el ticket vuelve a la cola y el agente lo corrige AFK, es lo que hace que el pipeline mejore por sí mismo.
QA produce el artefacto más valioso del pipeline: nuevos issues. Cada bug encontrado, cada preocupación de UX, cada edge case que el PRD original no contempló se convierte en un nuevo ticket del tablero Kanban con relaciones de bloqueo adecuadas. El tablero nunca se vacía; sigue produciendo slices.
Esta también es la etapa donde vive el gusto. Automatizar QA es una tentación que conviene resistir: un agente que revisa la UI de otro agente llega a una opinión que no sostiene nadie en particular, y el resultado es la pasta suavemente derivativa y sin asperezas que caracteriza a la salida de IA sin supervisión. Un humano decidiendo "este padding está mal" y "esta etiqueta es demasiado larga" es un paso irreducible. El agente entrega a cinco veces la velocidad normal; tu trabajo es asegurarte de que entregue tu gusto a cinco veces la velocidad normal, no el de cualquiera.
7. Principios de arquitectura para codebases compatibles con IA
El flujo de trabajo y el codebase son inseparables: cuanto más limpia es la arquitectura, mejor se desempeña el agente dentro de ella. La arquitectura ya no es solo un fin en sí mismo; es una entrada para tu fuerza laboral de IA.
7.1 Módulos profundos sobre módulos superficiales
Un módulo es profundo cuando tiene una interfaz pequeña y mucho comportamiento detrás; superficial cuando la interfaz y la implementación tienen aproximadamente el mismo tamaño.
flowchart TB
subgraph S["Shallow modules - bad"]
direction LR
s1[ ] ~~~ s2[ ] ~~~ s3[ ] ~~~ s4[ ] ~~~ s5[ ]
s6[ ] ~~~ s7[ ] ~~~ s8[ ] ~~~ s9[ ] ~~~ s10[ ]
SLABEL["many small pieces<br/>callers thread through<br/>implicit dependencies"]
end
subgraph D["Deep module - good"]
direction TB
DI["small interface<br/>━━━━━━━━━━━"]
DBODY["large internal<br/>implementation<br/>(hidden from callers)"]
DI --> DBODY
end
classDef bad fill:#fde8e8,stroke:#a83838,color:#5a0d0d
classDef good fill:#e8f5e8,stroke:#3b8a3b,color:#0d3a0d
classDef shallowCell fill:#f0d0d0,stroke:#a83838,color:#5a0d0d
class S bad
class D good
class s1,s2,s3,s4,s5,s6,s7,s8,s9,s10 shallowCell
Para un agente, la diferencia es decisiva. En un codebase superficial, el agente rastrea muchas dependencias par a par entre muchos archivos pequeños; la relación señal-ruido por token se degrada; las pruebas se desparraman entre límites de módulos porque ningún límite contiene suficiente comportamiento para valer la pena probarlo en aislamiento. En un codebase profundo, el agente lee una interfaz y confía en el límite. Las pruebas se ubican en la interfaz. El comportamiento puede añadirse internamente sin perturbar a los llamadores y sin volver a probarlos.
Para hacer concreta la diferencia, así se habría visto la versión superficial de GamificationService: la forma en que un agente sin guía arquitectónica tiende a escribir la misma función.
Qué importa aquí. Cuenta el número de elementos exportados en cada bloque. La versión superficial expone nueve funciones top-level que los llamadores deben recordar llamar en el orden y combinación correctos. La versión profunda expone tres métodos en una sola clase; lo que deba ocurrir entre bastidores ocurre entre bastidores. El bug que hay que evitar: en la versión superficial, un llamador puede olvidar invocar
validateAntiCheaty corromper el sistema en silencio. En la versión profunda, el llamador no puede tocarvalidateAntiCheat; está escondido dentro deawardLessonCompletion, que lo llama automáticamente. Ocultar las cosas correctas es todo el trabajo de un módulo profundo.
// gamification/index.ts - SHALLOW: the interface IS the implementation
export function awardPoints(studentId: string, reason: string, n: number): void;
export function totalPoints(studentId: string): number;
export function recordStreakActivity(studentId: string, day: Date): void;
export function streakLength(studentId: string, today: Date): number;
export function computeLevel(totalPoints: number): number;
export function validateAntiCheat(
studentId: string,
event: PointEvent,
): boolean;
export function backfillHistorical(studentId: string, since: Date): void;
export function pointsForLessonCompletion(): number;
export function pointsForQuizPass(): number;
// ... + the data classes each function depends on
Nueve funciones top-level, cada una llamable desde cualquier lugar, cada una silenciosamente dependiente de las demás (awardPoints debe llamar a validateAntiCheat; el dashboard debe llamar a awardPoints y recordStreakActivity y computeLevel para una completion de lección; si cualquier llamador olvida una, el sistema deriva en silencio fuera de consistencia).
Compáralo con la versión profunda de §6.4:
// gamification/service.ts - DEEP: small interface, large hidden body
export class GamificationService {
awardLessonCompletion(studentId: string): PointAward; // does ALL of the above internally
totalPoints(studentId: string): number;
currentStreak(studentId: string): number;
// streak recording, anti-cheat, level calc, point amounts → all hidden
}
Tres métodos. Internamente existen las mismas nueve preocupaciones, pero no son la interfaz. Los llamadores no pueden olvidar llamar a validateAntiCheat, porque no pueden llamarlo en absoluto. Las pruebas se apoyan en tres métodos, no en nueve. El nuevo comportamiento (recordStreak, umbral de nivel, backfill) se añade dentro sin cambiar el contrato: exactamente la propiedad que demuestra §6.4.
Heurística. Si la vista Outline de tu IDE para un módulo es más larga que su interfaz pública, el módulo es superficial. Profundízalo.
7.2 Probar en la interfaz
Un corolario de §7.1. Las pruebas se ubican sobre interfaces de módulo, no sobre funciones internas. Una prueba sobre una función interna fija la implementación; refactorizar los internos rompe la prueba aunque el comportamiento visible externamente sea correcto. Una prueba sobre la interfaz fija el comportamiento; los internos cambian libremente mientras el contrato se mantenga.
Esto es lo que la Skill tdd impone por defecto: las pruebas apuntan a la interfaz; el agente refactoriza internos entre pasos verdes; la suite da cobertura completa desde una superficie pequeña.
7.3 Diseña la interfaz, delega la implementación
El hábito más importante para un ingeniero senior que trabaja con agentes.
Tú decides qué expone el módulo: el contrato, los nombres, los invariantes. Estas decisiones afectan a cada llamador; dan forma a la arquitectura; requieren gusto y tener todo el sistema en mente.
El agente decide cómo se satisface el contrato: estructuras de datos internas, ubicación de helpers, orden de operaciones. Esto afecta solo el interior de un módulo; los errores son recuperables; no hace falta el mapa arquitectónico.
Este es el principio de caja gris. Desde fuera, el módulo está completamente especificado: interfaz visible, internos invisibles por diseño. Desde dentro, el agente es libre de hacer trabajo excelente, limitado solo por el contrato de la interfaz. Un ingeniero senior puede sostener en la cabeza el mapa arquitectónico de un codebase de un millón de líneas porque el mapa contiene solo interfaces.
Esto hace manejable el problema de saturación mental de la Falla 5. No puedes leer cada línea que escribe el agente; ese camino lleva al agotamiento. Sí puedes mantener en la cabeza el mapa de módulos y leer con cuidado cada cambio de interfaz. El conjunto de cambios en interfaces es pequeño; el conjunto de cambios dentro de módulos es grande. Concentrar la atención en el conjunto pequeño es lo que escala.
7.4 La Skill improve-codebase-architecture
Los codebases derivan hacia lo superficial con el tiempo, especialmente con agentes dentro. La corrección es una pasada periódica de profundización.
Incluso Karpathy, trabajando en la frontera con los modelos más recientes, describe la experiencia con claridad: "A veces casi me da un infarto porque el código está muy inflado, hay mucho copiar y pegar, y abstracciones torpes que son frágiles. Funciona, pero es realmente desagradable." No es que un modelo profundo esté fallando; es que el modelo opera dentro del circuito verificable de "¿el código se ejecuta?" sin una recompensa correspondiente por "¿el código está bien diseñado?". La pasada de profundización suministra la recompensa que los laboratorios no dieron.
---
name: improve-codebase-architecture
description: Find shallow-module candidates in the codebase and propose deepenings. Run weekly, or after a burst of feature work.
---
You are an architecture reviewer. Walk the codebase and find places
where understanding one concept requires bouncing between many small
files; where pure functions have been extracted only for testability,
not behaviour; where modules are tightly coupled at the seams.
Surface a numbered list of deepening candidates. For each, briefly:
- which existing files would collapse into the new deep module
- what the new interface would be (3-5 method signatures, no more)
- what behaviour would move inside, freeing callers from knowing it
Do NOT make changes. Open a markdown RFC describing the highest-value
candidate as an issue, blocked by nothing, type AFK.
Una ejecución semanal produce un RFC de profundización. Entra al mismo tablero Kanban por el que fluye el trabajo de features. Se implementa con el mismo bucle de TDD sobre vertical slices. El codebase mejora según una agenda, no por accidente.
8. El vocabulario de trabajo
El vocabulario preciso acelera el razonamiento. La referencia completa es el Dictionary of AI Coding; el subconjunto de abajo es el mínimo necesario para leer y escribir el resto de este libro.
| Término | Significado |
|---|---|
| Model | Los parámetros. Sin estado. Predice el siguiente token; nada más. |
| Harness | Todo lo que rodea al modelo y lo convierte en agente: herramientas, system prompt, gestión de ventana de contexto, permisos. Claude Code es un harness; OpenCode es un harness. |
| Agent | Un modelo + harness operando en una ventana de contexto con herramientas. Aquello con lo que realmente hablas. |
| Context window | La vista de bytes de tamaño fijo que el modelo ve en cada solicitud. Finita. La única superficie por la que el modelo percibe algo. |
| Smart zone / dumb zone | La región temprana de la sesión donde la atención es aguda / la región tardía donde la atención se diluye por tokens competidores. |
| Hallucination | Salida confiadamente incorrecta. Las alucinaciones de factuality vienen de huecos en el conocimiento paramétrico; las de faithfulness vienen de deriva en la dumb zone. Las soluciones difieren. |
| Clearing | Terminar la sesión y empezar una fresca. El reinicio duro. Devuelve al agente a un estado conocido. |
| Compaction | Resumir la sesión en memoria para sembrar una nueva. Con pérdida; preserva parte del razonamiento de dumb zone. |
| Handoff | Transferir contexto de una sesión a otra mediante un artefacto (PRD, ticket, CONTEXT.md). |
| AFK | "Away from keyboard." El usuario inicia una sesión y la deja ejecutándose sin supervisión en un sandbox. |
| Skill | Una capacidad enseñable empaquetada como archivo SKILL.md. Cargada bajo demanda. La unidad de divulgación progresiva. |
| Tracer bullet / vertical slice | Un issue que entrega un camino delgado por cada capa del sistema, de extremo a extremo. |
| Deep module | Un módulo con una interfaz pequeña y una implementación interna grande. La forma que hace escalables los codebases con IA. |
| Design concept | La idea compartida y efímera de lo que se está construyendo, sostenida en común entre usuario y agente. No es un artefacto. |
| Grilling | Una técnica para formar un design concept: el agente entrevista al usuario socráticamente, una decisión a la vez. |
| Vibe coding | Aceptar código del agente sin revisión humana. Distinto de "codificación de baja calidad"; el término nombra la postura de revisión, no la salida. |
| Agentic engineering | La disciplina de usar agentes en trabajo de producción mientras se conserva el estándar de calidad del software profesional. La postura opuesta a vibe coding: piso elevado, techo sostenido. |
| Jagged intelligence | El hecho empírico de que la capacidad de los LLM alcanza picos abruptos en tareas para las que los laboratorios entrenaron mediante RL verificable (matemáticas, código) y se estanca fuera de esos circuitos. El agente que refactoriza 100k líneas también puede decirte que camines a un autolavado a 50 m. |
| On distribution | La propiedad de estar bien representado en los datos de entrenamiento del modelo y, por lo tanto, ser manejado competentemente por él. Al empezar desde cero, elige stacks donde el modelo ya sea fuerte. |
| Loop / Routine | Un agente ambiental persistente: una sesión fresca invocada según una agenda (cron localmente; "routine" del lado del servidor) contra un pequeño trabajo permanente. Cada tick es stateless; el rol persiste en el prompt. |
Un coder de trabajo debería usar cualquiera de estos sin dudar. "Voy a hacer clear y luego ejecutar tdd en la siguiente vertical slice desbloqueada" y "eso es una alucinación de faithfulness; la documentación sigue en contexto, simplemente dejó de leerla alrededor del turno cuarenta" son el tipo de frases que separan una conversación vaga de una que realmente avanza.
9. Ejercicios prácticos
Tres ejercicios. Hazlos en orden. Cada uno toma de treinta minutos a dos horas.
Ejercicio 1: instala y ejecuta grill-me sobre una idea real.
Elige una función cuyo alcance hayas estado posponiendo. Instala el skill pack en un repo limpio siguiendo §5.2 (lectores de Claude Code: primero mkdir -p .claude/skills, luego npx skills@latest add mattpocock/skills). Abre Claude Code (u OpenCode), invoca /grill-me y responde preguntas hasta que el agente se detenga. No tomes atajos. Cuenta las preguntas. Anota qué decisiones no habrías sacado a la superficie por tu cuenta.
Cómo se ve algo "bueno". Una sesión de grilling sobre una función no trivial tiende a moverse en el orden de 15-40 preguntas y 30-90 minutos antes de que el agente reporte alineación. Menos de unas 10 preguntas suele significar que la idea era demasiado pequeña o que respondiste con demasiada generosidad; más de 60 suele significar que el agente está pescando, así que interrúmpelo y pídele que se comprometa con una recomendación por pregunta. Al final deberías poder parafrasear al menos tres decisiones que surgieron y que no habías considerado al entrar. Si no puedes, fue una encuesta, no grilling. Una proporción diagnóstica útil: aproximadamente una de cada cinco preguntas debería sacar a la superficie una decisión que no tenías resuelta de antemano.
Ejercicio 2: escribe una vertical slice como tracer bullet.
Toma cualquier feature inacabada de tu codebase. Escribe una sola historia de usuario que trace el camino de extremo a extremo más pequeño posible. Impleméntala bajo la Skill tdd. Observa lo corta que es la slice. Observa cuánto antes emergen los bugs de integración frente a lo que habría pasado con corte horizontal.
Cómo se ve algo "bueno". La slice aterriza en menos de una sesión con la prueba, la implementación y un diff revisable en una PR. Si no ocurre, la slice era demasiado gruesa; divídela. La fricción de integración que encuentras durante la slice es el valor del ejercicio; captúrala como nuevos issues, no expandas la slice actual para absorberla.
Ejercicio 3: profundiza un módulo.
Ejecuta improve-codebase-architecture en un codebase que conozcas bien. Elige el candidato de mayor valor. No lo implementes todavía; bosqueja en papel la nueva interfaz (3-5 firmas de métodos, no más). Compara el área de superficie de la nueva interfaz con la anterior (suma de símbolos públicos entre los archivos que colapsarían). La proporción es tu medida concreta de qué tan superficial se había vuelto el codebase.
Cómo se ve algo "bueno". Una profundización genuina suele colapsar varios módulos pequeños (del orden de 5 a 15) en uno profundo, con una proporción de símbolos públicos (antiguo : nuevo) del orden de 3:1 o mayor. Si la proporción está más cerca de 1:1, el candidato no era realmente superficial; elige otro.
Una checklist corta para el trabajo diario:
- ¿Ejecuté
/clearantes de empezar la sesión de hoy? - ¿Usé
grill-mepara cualquier cambio no trivial? - ¿Mis issues son vertical slices, no fases horizontales?
- ¿Cada slice de implementación pasa por
tdd? - ¿Las ejecuciones AFK están en un sandbox?
- ¿El revisor es una sesión separada del implementador?
- ¿Leí el diff, no el resumen?
10. Cierre: el programador estratégico
Esta es la imagen que conviene llevarse.
Tu agente es un excelente programador táctico: un sargento en el terreno que puede tomar cualquier colina bien especificada, en cualquier lenguaje, en cualquier framework, en mitad de la noche, y traer una slice funcional por la mañana. No necesitas enseñarle a escribir una función o una prueba. El harness, el modelo y las herramientas ya resolvieron eso.
Lo que el sargento no puede hacer es decidir qué colina. No puede decirte si el sistema que se está construyendo es el sistema que necesita el negocio. No puede decirte si el tercer módulo que estás a punto de pedir debería existir como módulo separado o integrarse en uno profundo existente. No puede decirte que el código que pediste viola una restricción de dominio que no se ha escrito en ninguna parte. No puede mantener en mente el mapa arquitectónico del sistema durante meses y años; no tiene meses ni años; tiene la sesión actual y unos pocos archivos en disco.
Todo lo que está por encima del sargento es el rol del programador estratégico, que es tu rol. Alinearte con el stakeholder. Formar el design concept. Elegir la slice. Diseñar la interfaz. Leer el diff. Sostener el mapa. Invertir todos los días en el diseño del sistema, como escribió Kent Beck hace treinta años para humanos, y como ahora aplica a la fuerza laboral híbrida de ingenieros humanos y Digital FTEs que construirá la próxima década de software.
Las herramientas del programador estratégico se describen en este capítulo. El pipeline (§4). Las seis fallas (§3) y sus curas. Las Skills (§5) que codifican las curas. La arquitectura (§7) que hace bueno al agente. El vocabulario (§8) que te permite razonar sobre todo ello. Entre Claude Code y OpenCode, la disciplina es la misma. Entre Python y TypeScript, la disciplina es la misma. Entre cualquier modelo y harness que existan dentro de cinco años, la disciplina seguirá siendo la misma.
La narrativa del inicio de este capítulo, que la IA reemplaza los fundamentos del software, es falsa porque confunde quién escribe el código con cómo se ve el buen código. El autor cambió; el estándar no. Los codebases que eran buenos para humanos son buenos para agentes. Los codebases que eran malos para humanos son malos para agentes, y peor, porque los agentes amplifican lo malo.
Lee los libros antiguos. The Pragmatic Programmer. A Philosophy of Software Design. Domain-Driven Design. Extreme Programming Explained. The Design of Design. Cada página es anterior a esta tecnología, y cada página aplica con más fuerza ahora que cuando se escribió. Son la forma en que el programador estratégico aprende a pensar en las escalas temporales que el sargento no puede alcanzar.
Vale la pena llevarse una frase de Karpathy: "Puedes subcontratar tu pensamiento, pero no puedes subcontratar tu comprensión." El agente hará el tipeo, la búsqueda, el boilerplate, el recuerdo de detalles de API, el refactor tedioso. Cada vez más hará también el pensamiento: generar opciones, pesarlas, redactar soluciones, ejecutar experimentos. Lo que sigue siendo exclusivamente tuyo es la comprensión: por qué se construye este sistema, para qué sirve, quién depende de él, qué nunca debe hacer. La comprensión es lo que te permite dirigir al agente. Sin ella, el agente no tiene destino, y un agente rápido sin destino es solo una forma cara de perderse.
El corolario, de Boris Cherny: cuando la codificación está resuelta y el conocimiento de dominio es el cuello de botella, la mejor persona para escribir el software es quien mejor entiende el dominio, no quien históricamente ha escrito el software. El mejor autor de software contable es un contador realmente bueno. La analogía histórica es la imprenta: antes de Gutenberg, la lectura era un oficio especializado practicado por una minoría alfabetizada; pocas décadas después de su prensa, la producción impresa explotó; durante los siglos siguientes, la alfabetización se convirtió en una habilidad de mayoría amplia mientras dejaba de ser una profesión. El mismo arco empieza ahora para el software. En una generación, construir software será algo que profesionales de cada dominio hagan como parte natural de su trabajo (contadores que escriben sus propios libros contables, médicos que escriben sus propios flujos clínicos, abogados que escriben sus propios analizadores de contratos, docentes que escriben sus propias herramientas curriculares) y el rol que llamamos "ingeniero" significará algo más estrecho y más profundo: la persona que diseña el sustrato sobre el que construye el resto de la fuerza laboral.
Esta es la forma de fuerza laboral de la que trata este libro. El Digital FTE que fabricarás en los capítulos siguientes es una herramienta del experto de dominio: construida por un ingeniero agentic, pero especificada, gobernada y usada por el contador, el underwriter, el analista o el case manager que posee el trabajo. Los principios y flujos de trabajo de este capítulo son lo que vuelve a esos Digital FTEs lo bastante confiables para merecer esa propiedad. Pipeline, Skills, módulos profundos, bucles persistentes, sandboxes, disciplina de smart zone, conciencia de jagged intelligence: todo al servicio de software en el que un experto de dominio pueda confiar sin leer una línea de código. Ese es el contrato de la ingeniería agentic con las personas a las que sirve.
Ese es el trabajo. Ese es el capítulo.
Lecturas adicionales
- Matt Pocock, Software Fundamentals Matter More Than Ever: la keynote que informa la tesis de este capítulo.
- Matt Pocock, Full Walkthrough: Workflow for AI Coding: un walkthrough en vivo de dos horas del pipeline de §4 y §5.
- Matt Pocock, 5 Claude Code Skills I Use Every Single Day: la referencia diaria de Skills.
- Matt Pocock, Dictionary of AI Coding: el glosario canónico; la fuente de §8.
- Matt Pocock, Skills for Real Engineers: el skill pack instalable usado en todo el capítulo.
- Andrej Karpathy, From Vibe Coding to Agentic Engineering: la charla que nombra la disciplina, articula el marco Software 1.0/2.0/3.0 e introduce jagged intelligence y la lente animals vs. ghosts usada en §1 y §2.
- Boris Cherny (Anthropic), Why Coding Is Solved, and What Comes Next: el creador de Claude Code sobre su flujo de trabajo personal, el argumento "on-distribution" para elegir stack, bucles persistentes y routines, y la analogía de la imprenta usada en §1.2, §2.3, §6.5.3 y §10.
- John Ousterhout, A Philosophy of Software Design: módulos profundos, módulos superficiales.
- David Thomas & Andrew Hunt, The Pragmatic Programmer: tracer bullets, headlights.
- Eric Evans, Domain-Driven Design: lenguaje ubicuo.
- Kent Beck, Extreme Programming Explained: invertir en el diseño todos los días.
- Frederick P. Brooks, The Design of Design: el árbol de diseño, el design concept.
Skills complementarias (este capítulo)
El pipeline del capítulo pasa por seis Skills del pack de Matt Pocock, todas enlazadas aquí para lectura directa:
grill-me: la entrevista socrática que produce el design concept.grill-with-docs: grilling que también escribeCONTEXT.mdy ADRs en línea (el linaje de "lenguaje ubicuo" de §3 Falla 2).to-prd: sintetiza la conversación en un PRD.to-issues: divide el PRD en tickets tracer-bullet.tdd: red-green-refactor, una slice a la vez.improve-codebase-architecture: encuentra módulos superficiales, propone profundizaciones y abre un RFC.
Un bootstrap de una sola vez, setup-matt-pocock-skills, se ejecuta primero por repo y crea la configuración del issue tracker y el layout docs/agents/ del que dependen las skills de ingeniería.
El pack de Matt incluye catorce skills en total (repo completo). Además del pipeline de siete etapas y setup-matt-pocock-skills, también incluye diagnose (depuración disciplinada de bugs), triage (triage de tickets como máquina de estados), zoom-out (reencuadre de contexto más amplio), prototype (prototipos de diseño desechables), write-a-skill (la meta-Skill para crear nuevas), handoff (la disciplina de artefactos de handoff entre sesiones de §4.1) y caveman (modo de prompt terse). Están fuera del pipeline de siete etapas, pero componen con él, y cada una se ejecuta igual en Claude Code y OpenCode. Consulta Part 5: Building OpenClaw Apps para la referencia de Agent Factory Skillpack y Skills adicionales específicas del libro.