¿Qué pongo en el AGENTS.md y qué es mejor que compruebe una máquina?
🟠 Intermedio · Truco · 8 min de lectura
Mi AGENTS.md tiene ochenta líneas. El agente sigue usando console.log en
vez de mi módulo de logs. Añado una línea más, en mayúsculas. Sigue usándolo.
El problema
Un fichero de instrucciones es una petición, no una garantía. Funciona la
mayoría de las veces, y esa es justamente la trampa: como funciona casi
siempre, cuando falla no te enteras hasta que lo tienes mezclado en la rama
principal.
Geoffrey Huntley lo ha dicho esta semana mejor de lo que yo sabría:
Si el agente no está usando mi biblioteca de diagnóstico, podría
introspeccionar y actualizar elAGENTS.md, pero sigue habiendo una
diferencia, y va a derivar. En vez de eso escribo un gancho de pre-commit
que comprueba en el diff si hay llamadas directas a módulos de
observabilidad que se saltan mi módulo de diagnóstico. Zas: falla la
confirmación, inyecta una instrucción diciéndole al agente que use la
biblioteca de diagnóstico. De golpe, diez de diez.
Ahí está todo. «Sigue habiendo una diferencia, y va a derivar» describe lo
que le pasa a cualquier fichero de instrucciones con el tiempo. «De golpe,
diez de diez» describe lo que pasa cuando dejas de pedirlo y empiezas a
comprobarlo.
El fichero no sobra. Lo que sobra es pedirle lo que una máquina puede
verificar.
Cómo se resuelve hoy
La regla que a mí me ha servido para repartir: si se puede comprobar con un
script, no va en el fichero de instrucciones.
Al AGENTS.md le toca lo que un script no puede saber:
- El porqué. «Los importes se guardan en céntimos enteros porque tuvimos
un error de redondeo en 2024.» Ningún linter te cuenta eso, y es lo que
evita que alguien lo «arregle». - Lo que no es obvio desde el código. Qué módulo está a punto de morir,
cuál toca el cobro de verdad, dónde hay un apaño que aguanta algo que no se
ve desde ahí. - Las preferencias de forma. Cómo quieres los mensajes de commit, en qué
idioma, qué tono. - Los límites de criterio. «Si una migración borra datos, pregunta antes.»
Un script puede detectar unDROP, pero no puede decidir si en este caso
está bien.
A la máquina le toca todo lo que tenga una respuesta de sí o no: qué
bibliotecas se usan, qué formato tiene el código, qué no se importa desde
dónde, qué no se sube nunca.
La diferencia práctica entre las dos listas es quién se entera del fallo. Una
instrucción incumplida no avisa a nadie. Una comprobación que falla para la
confirmación y, si está bien escrita, le dice al agente exactamente qué hacer.
Las herramientas están empezando a empujar en la misma dirección por otro
lado, el del contexto. Claude Code 2.1.299 ha añadido la compactación en
reposo (una conversación de más de 200.000 tokens que lleva unos 54 minutos
parada se compacta antes de que caduque su caché de una hora) y
CLAUDE_CODE_DISABLE_FILE_MENTIONS, con la que las menciones con arroba
llegan como rutas para que el modelo las lea con sus herramientas en vez de
volcarle el contenido entero. Las dos atacan el mismo error de bulto: meter
en el contexto cosas que el agente puede ir a buscar cuando las necesite.
Tu AGENTS.md de ochenta líneas es un caso de eso.
Ejemplo
El gancho que describe Huntley, escrito para un proyecto de Python. Mira el
diff que está a punto de confirmarse, no el repositorio entero, así que es
rápido y solo se queja de lo que acabas de tocar.
.git/hooks/pre-commit:
#!/bin/sh
# Comprueba lo que el AGENTS.md pedia por favor.
# Instalar con: chmod +x .git/hooks/pre-commit
fallos=""
# 1. Logs: se usa el modulo propio, no print ni logging directo.
colados=$(git diff --cached -U0 -- '*.py' \
| grep '^+' | grep -v '^+++' \
| grep -nE '^\+\s*(print\(|logging\.(debug|info|warning|error))' || true)
if [ -n "$colados" ]; then
fallos="${fallos}
LOGS: hay llamadas directas a print() o logging. En este proyecto se usa
src/diagnostico.py. Sustituyelas por diagnostico.info(), .aviso() o .error().
$colados"
fi
# 2. Importes: la capa de API no habla con la base de datos.
cruzados=$(git diff --cached -U0 -- 'src/api/*.py' \
| grep '^+' | grep -v '^+++' \
| grep -nE '^\+\s*(from|import)\s+src\.db' || true)
if [ -n "$cruzados" ]; then
fallos="${fallos}
CAPAS: src/api/ importa src/db directamente. La API solo enruta; el acceso a
datos va por src/dominio/. Mueve la consulta a un caso de uso del dominio.
$cruzados"
fi
if [ -n "$fallos" ]; then
echo "----- La confirmacion no pasa -----"
echo "$fallos"
echo
echo "Arregla lo de arriba y vuelve a confirmar."
exit 1
fi
Fíjate en cómo están escritos los mensajes. No dicen «error de estilo»: dicen
qué está mal, cuál es la regla y qué hacer en su lugar. El agente lee esa
salida y actúa; un «commit rejected» a secas le deja adivinando.
Y entonces el AGENTS.md adelgaza y se queda con lo que nadie puede
comprobar:
# Proyecto X
## Lo que un script no puede saber
- Los importes van en céntimos enteros, nunca en coma flotante. Viene de un
error de redondeo que nos costó una conciliación entera en 2024.
- `src/legado/facturacion.py` se va a borrar en enero. No lo mejores, no le
añadas tests: lo que toques ahí es trabajo tirado.
- `src/api/` solo enruta. La lógica vive en `src/dominio/`.
(El pre-commit comprueba que no se importe `src.db` desde la API.)
## Cuándo parar y preguntar
- Si una migración borra o renombra una columna con datos.
- Si hay que tocar algo del cobro.
- Si la solución te pide añadir una dependencia nueva.
## Forma
- Commits en español, imperativo, explicando el porqué y no el qué.
Las reglas comprobables siguen mencionadas, en una línea y entre paréntesis,
porque al agente le ahorra un ciclo de fallo. La diferencia es que ahora,
si no las cumple, no pasa.
Errores habituales
Escribir en mayúsculas lo que ya no funcionó en minúsculas. Si una
instrucción se incumple, subirle el tono no la arregla. Es la señal de que esa
regla necesita una comprobación, no una línea más.
Un AGENTS.md que crece y nunca encoge. Cada vez que algo sale mal se
añade un párrafo y no se quita ninguno. A las ochenta líneas, el fichero
compite consigo mismo por la atención del modelo y además te cuesta dinero en
cada turno. Cuando pases una regla a un gancho, bórrala del fichero.
Ganchos que dicen que no sin decir por qué. Un exit 1 mudo convierte al
agente en alguien probando a ciegas. El mensaje de error es la mitad del
trabajo: es el único momento en que puedes darle una instrucción justo cuando
le hace falta.
Comprobar el repositorio entero en vez del diff. Un gancho que tarda
treinta segundos acaba desactivado con --no-verify en dos días. git diff mira lo que de verdad va a entrar.
--cached
Qué vigilar
🟢 Los ganchos de pre-commit funcionan hoy, no dependen de ningún modelo y
sirven igual cuando el que teclea eres tú. Pasar dos reglas del fichero a un
gancho es trabajo de una tarde.
🟡 Los ganchos propios de las herramientas de agentes, que se disparan antes
de que el cambio llegue siquiera a git. Son más inmediatos que un pre-commit
y dependen de la herramienta que uses, así que tu gancho de git sigue siendo
la red que funciona para todos.
🟡 La gestión del contexto que no tienes que hacer tú. La compactación en
reposo de Claude Code y las menciones que llegan como rutas van en esa
dirección. Cuanto mejor lo hagan las herramientas, menos razones habrá para
tener un fichero de instrucciones enorme.
🔴 Que los modelos cumplan instrucciones de forma fiable y esto deje de hacer
falta. Si algún día un fichero de instrucciones se cumple el cien por cien de
las veces, este artículo sobra. No hay señal de que esté cerca, y mientras
tanto la diferencia entre pedir y comprobar sigue siendo la diferencia entre
enterarte y no enterarte.
