Un arnés para producción en Claude Code: seis capas desde Slack hasta el MR fusionado.
Cómo un arnés de seis capas convierte los tickets de Slack en código fusionado: recepción de eventos, bucles de agentes con control de acceso, estado duradero, autoreparación, capacidad de observabilidad y control por parte del operador.
Fuentes: Building a Production Agent Harness: Turning Claude Code Into a Multi-Agent Engineering Pipeline
Si pones un modelo de codificación por sí solo, obtienes algo similar a un cerebro en un frasco. Es capaz de escribir código y llamar a funciones sin problemas, pero no podrá ver tus mensajes privados en Slack a las 3 de la mañana, intentar de nuevo una tarea de CI que ha fallado, resolver el conflicto de fusión que acaba de aparecer en su solicitud abierta, ni recordar que un revisor preguntó algo ayer y nadie respondió.
Lo que convierte al modelo en algo útil es el entorno de ejecución: la estructura que proporciona “sentidos”, capacidad de acción y memoria. Sus componentes son la recepción de eventos, la orquestación de agentes, el estado persistente, bucles que se reparan solos, la capacidad de observación y una forma para que el operador humano dirija el proceso. El sistema descrito a continuación es uno en producción, no una demostración de investigación. Monitorea un canal de Slack, abre solicitudes de fusión en cinco repositorios internos, responde a los comentarios de los revisores durante la noche y repara las tareas de CI que han fallado sin que se le pida.
Se trata de un pipeline interno con múltiples agentes desarrollado sobre Claude Code. Ha estado funcionando sin interrupciones durante varios meses, tomando tareas de Slack y asignándolas a trabajos reales de ingeniería. A continuación se presenta una visión general: qué hace, cómo se conectan sus componentes y las limitaciones que dieron forma a su diseño.
Las secciones se presentan en este orden: por qué es necesario un sistema de integración, qué hace este específico, las tres brechas que resuelve, los seis niveles (ingestión de eventos, orquestación de agentes con un análisis más detallado del bucle autónomo y la capa de auto-mejora, estado persistente, capacidad de autocuración, observabilidad y control humano), algunos elementos adicionales más allá de esos niveles, un recorrido completo end-to-end y una comparación con otros sistemas similares.
Por qué una ventana de chat y algunas herramientas no son suficientes
Cuando aparecieron los plugins para asistentes de chat en 2023, muchos equipos probaron el atajo obvio: colocar una interfaz de chat frente a un LLM, darle algunas herramientas para realizar llamadas a funciones y llamar al resultado “el agente de ingeniería”. Esa configuración casi funciona en una demostración, pero nunca sobrevive en entornos de producción. En menos de una semana surgieron tres modos de fallo.
- El contexto desaparece cuando el operador cierra la pestaña. La memoria debe ser suficiente para más de una sesión de chat. Una tarea real de ingeniería dura días: abrir un ticket, crear una rama, redactar, solicitar revisiones, responder a comentarios, esperar que el sistema de integración funcione correctamente y finalmente fusionar cambios. Ninguna sola llamada al modelo puede contener todo eso.
- Nada reacciona a los cambios externos. Un revisor comenta a las 4 p.m. El sistema de integración falla con el tercer commit. Un compañero responde en la conversación. El agente debe detectar esos eventos, en lugar de hacer consultas continuamente o esperar a que alguien lo active nuevamente.
Arreglar estos problemas es la función del harness. También es lo que diferencia una simple pestaña de chat de Claude Code en sí, de los agentes en segundo plano de Cursor, de Devin o del sistema descrito aquí. El modelo subyacente es prácticamente intercambiable en todos ellos; lo que difiere es el harness que lo rodea.
El modelo solo accede al mundo a través del harness, y este se encarga de casi toda la ingeniería necesaria para que un LLM funcione como un sistema.
La tarea para la que se creó el sistema
El problema inicial era concreto. Un canal de Slack, llamémoslo #support, recibe alrededor de 30 tickets por semana. Cada uno requiere una investigación y, por lo general, un cambio de código en uno de cinco repositorios posteriormente. Un ticket puede extenderse durante días. Los revisores comentan en las propuestas de cambio, surgen problemas en los tests automatizados y algunas investigaciones resultan ser repeticiones de las del mes anterior. El equipo encargado de manejar esta carga seguía recibiendo notificaciones por la noche y los fines de semana, y nada mejoraba porque los mismos tipos de tickets reaparecían en cada ciclo.
El sistema tiene tres responsabilidades, ordenadas según el grado de intervención necesario:
- Investigar. Cuando llega un nuevo ticket a
#support, seguir el hilo de conversación, recopilar información de varios repositorios y publicar un análisis estructurado en ese mismo hilo.
Esa tercera función es lo que lo diferencia de un simple bot de gestión de tickets. La materia prima del sistema es su propio código fuente, y el LLM puede modificarlo a través de solicitudes de pull que pasan por revisión humana.
El sistema está compuesto por seis capas lógicas, y cada una tiene su propia sección a continuación.
Las tres deficiencias que un modelo básico no puede resolver
Hay una idea fundamental detrás de toda esta estructura: un LLM sin estado y puramente reactivo no constituye un sistema de producción. Las seis capas existen para subsanar tres deficiencias que el modelo no puede resolver por sí solo.
La primera deficiencia es la reactividad. Un modelo no puede activarse por sí mismo. La Capa 1 (ingestión de eventos) se encarga de eso, ya que Slack, los resultados de GitLab CI y las páginas de PagerDuty fluyen todos hacia una misma cola de distribución. El LLM nunca realiza consultas; es llamado directamente.
La persistencia es la segunda deficiencia. Los modelos olvidan todo entre sesiones y pierden el contexto cuando el trabajo se traslada entre máquinas; además, no pueden distinguir entre haber abordado un problema y un commit que realmente llegó al repositorio. La Capa 3 (estado persistente) soluciona esto: las estructuras en memoria eliminan duplicados dentro de un mismo proceso, los archivos JSON locales almacenan los mapas operativos, y los espacios de trabajo sincronizados con Git mantienen un estado duradero del caso. La continuidad de sesiones entre máquinas, descrita más adelante, se basa en la misma idea: el estado debe estar en Git y nunca dentro de un proceso en ejecución.
La tercera brecha es la calidad. Si no se estructura adecuadamente, un bucle de razonamiento de LLM llega a conclusiones seguras basadas en evidencia insuficiente, declara terminadas las investigaciones sin verificar todas las fuentes accesibles y genera descripciones de MR que parecen escritas por una máquina desde la primera oración. La capa 2 (orquestación de agentes) y el bucle de investigación abordan este problema mediante contratos de salida estructurados, una verificación de calidad en cada iteración, una revisión adversaria al finalizar el bucle y bucles de autoreparación dentro del ciclo externo de cambios de código, pruebas continuas e integración.
Lo que une todas estas capas es la acumulación de resultados, ya que cada caso resuelto acelera el siguiente. Las investigaciones aprobadas se convierten en entradas en la base de conocimientos, y los MR aprobados se transforman en patrones que el analizador de brechas puede identificar. Un caso finalizado da lugar a un MR de mejora dirigido al propio sistema. Con cada ciclo, menos tipos de tickets se manejan de manera deficiente.
Capa 1: cómo los eventos activan el sistema
Tres fuentes de señales externas pueden activar el harness:
- Mensajes de Slack, es decir, menciones en canales, respuestas en hilos y comandos de administrador enviados como mensaje privado a uno mismo.
- Actividad en GitLab, es decir, comentarios en revisiones de MR, resultados de pipelines y fallos en pipelines.
- Alertas de PagerDuty, utilizadas cuando un equipo de atención menciona un ticket que el harness ya ha visto, para poder recuperar el contexto anterior.
Las tres difieren en latencia y fiabilidad, por lo que cada una cuenta con su propio camino de entrada.
Hay dos detalles prácticos que nunca aparecen en la versión textual estándar de este diagrama.
Consistencia eventual. El endpoint conversations.history de Slack puede presentar retrasos que van desde 1 hasta 30 segundos. Un mecanismo de monitoreo que vea un resultado vacío y avance ingenuamente su cursor omitirá, de forma permanente, cualquier mensaje cuyo ts caiga dentro de ese intervalo. Por lo tanto, el cursor queda bloqueado en las consultas vacías y el siguiente intento comienza desde la misma marca de tiempo más antigua, repitiéndose hasta que aparezca al menos un mensaje. Un conjunto de deduplicación basado en el ts de cada mensaje filtra los duplicados que generan esas repeticiones.
Modo Socket junto con un monitorizador, como red de seguridad adicional. El Modo Socket proporciona respuestas de hilos con una latencia inferior a un segundo, pero las conexiones WebSocket se interrumpen. El monitorizador funciona como red de seguridad, capturando todo lo que el Modo Socket omita, y ambos comparten un archivo socket-dedup para que ningún mensaje sea enviado dos veces. Si se elimina este mecanismo, cualquier mensaje que llegue durante una interrupción de conexión desaparecerá silenciosamente, sin que el operador reciba señal alguna de que fue omitido.
El bucle cron de mr-monitor se comporta de manera diferente. GitLab no envía los comentarios de revisión al pipeline, por lo que la consulta periódica es la única opción, y escalar esa misma consulta resultó ser la parte más difícil. Cada MR de larga duración adicional que maneja el sistema prolonga un bucle de 10 minutos, lo que genera acumulación de cursores para hilos inactivos, y cada uno requiere una llamada a la API de Slack por ciclo. Dejados sin control, causaron tormentas continuas de límite de tasa 429 que obstaculizaron las consultas realmente importantes. La solución consiste en eliminar automáticamente un cursor cuando la API upstream responde con thread_not_found, de modo que deja de formar parte del bucle del siguiente ciclo. Antes de la solución, un ciclo realizaba 12 intentos repetidos durante horas seguidas. Después de aplicarla, ya no hay intentos.
Nivel 2: cómo se orquestan los agentes
El LLM en realidad se ejecuta aquí. El harness lanza procesos como unidades temporales de systemd, y a cada uno se le proporciona un payload en JSON que indica el flujo de trabajo a ejecutar, el directorio case_dir a utilizar y el hilo al que pertenece.
Cada pipeline equivale a una única ejecución prolongada de Claude Code (el “cerebro”), que recibe un prompt cuidadosamente seleccionado y un directorio case_dir escribible. Ese directorio funciona como espacio de trabajo temporal del agente, albergando el archivo TASK.md del caso, una acumulación creciente de transcripciones en formato followup_transcript_*.md, cualquier informe generado por el agente en formato gap_report*.md, además de artefactos JSON intermedios.
Cómo se secuencian los agentes forma parte del diseño. En lugar de que un solo agente haga todo, varios agentes especializados se conectan en cadena, y cada uno transfiere la tarea en un punto determinado:
oncall_runrealiza la primera investigación, con hasta 20 iteraciones de razonamiento y una verificación de calidad, finalizando con una autocrítica explícita. Su salida consiste en unANALYSIS.mdque describe qué está mal y unTASK.mdque indica qué se debe hacer.case_followupes el agente de chat de larga duración. Un operador puede responder mediante mensaje privado en el hilo del caso durante días, y cada respuesta vuelve a invocar acase_followupcon toda la conversación realizada hasta ese momento. También llama agap_analyzercomo subagente cada N respuestas, y ese bucle genera los archivosgap_report*.mdque señalan al propio código del sistema (el bucle de auto-mejora se explica más adelante).
finalize_case es el consolidador. Una vez que un caso ha estado inactivo durante cuatro horas, o cuando un operador ejecuta admin: finalize <case>, reúne todos los informes de brechas, descarta las falsas positivas y abre un único MR con las correcciones que sobrevivieron. Ese MR se enfoca en el códigobase del harness y no en el repositorio original de la solicitud, lo cual constituye el ciclo de auto-mejora en forma concreta.dev-agent es el que realiza el trabajo práctico. Dentro de su propio árbol de trabajo git por caso, edita el código, hace push, monitorea CI, repara CI cuando falla y responde a los revisores. Funciona en tres modos: address_review, fix_ci y task (la implementación original).Este gráfico presenta un par de detalles que son fáciles de pasar por alto.
En primer lugar, nada afecta al proceso principal de verificación, ya que todo el trabajo se realiza en árboles de trabajo. Esto permite que varios casos se ejecuten simultáneamente sin entrar en conflicto. El recolector de tareas y el despachador del agente de desarrollo incluyen la ruta del árbol de trabajo en la carga útil, por lo que el modelo de lenguaje nunca llega a ver el proceso principal de verificación.
En segundo lugar, case_dir sirve como punto de referencia para todo. Los archivos de estado en ~/.harness/ (ver la sección sobre persistencia) vinculan cada hilo de Slack a un caso_dir, y el agente resuelve todas las rutas en relación con él. Como resultado, todo lo demás se vuelve más sencillo: basta mover el directorio, actualizar el mapa y cada flujo de trabajo seguirá funcionando correctamente.
El gráfico muestra qué se ejecuta. La siguiente sección explica cómo se lleva a cabo, es decir, las mecánicas que permiten que una orden de Slack se convierta en una investigación completada.
El bucle autónomo, paso a paso
Si alguien pregunta qué hace realmente este sistema, la respuesta más breve y honesta es: un operador escribe una orden de Slack, y a partir de ahí se activa un ciclo cerrado de razonamiento repetido, autocrítica y uso de herramientas que funciona sin supervisión hasta que el trabajo se completa o el sistema admite que no puede continuar. Casi toda decisión de diseño interesante está oculta dentro de esa frase, y el resto de esta sección la explica en detalle.
Dentro del sistema hay cuatro ciclos de agentes distintos, y cada uno está diseñado para abordar un tipo específico de problema:
Esa imagen muestra sus formas. A continuación se explica su funcionamiento, con cada mecanismo presentado en el orden en que se ejecuta. El diagrama abarca a todos ellos, y sus nodos adquieren sentido una vez leídas las secciones siguientes. Los códigos de control que utiliza son G1 a G4, N1 a N3, N5, A1 a A3 y D1 a D15.
Antes del ciclo: el bloqueo de pre-compromiso
Existen dos medidas de protección que se aplican antes de la primera llamada a Claude, y la primera es estructural. Antes de que tenga lugar cualquier iteración de investigación, el proceso en curso exige que Claude se comprometa con una hypothesis_slate. Dicha hoja de trabajo enumera al menos seis explicaciones candidatas además de cuatro o cinco criterios para elegir entre ellas, y queda guardada en kickoff_precommitment.json, cuyo contenido no puede modificarse posteriormente.
De todo lo que existe en el sistema, este mecanismo de control es la protección con mayor peso metodológico y la menor visibilidad. Un bucle de razonamiento de un LLM sin restricciones tiende a optar por lo que favorezcan sus primeras pruebas y luego interpreta todas las pruebas posteriores como confirmación. Este mecanismo no puede detener esa tendencia. Lo que sí hace es obligar a cada investigación a partir de todo el espacio de hipótesis y a registrar cómo llega desde allí a su conclusión. El revisor adversario (Fase B) plantea entonces una pregunta más difícil que la de consistencia interna: ¿es esta la conclusión que habría predicho el modelo original, dadas las pruebas?
La segunda medida de protección es el anclaje empírico (Fase C). Solo en la primera iteración se ejecutan algunos comandos a nivel del sistema operativo sin que participe un LLM en el proceso: registros de errores de journalctl, capturas del disco y la memoria, así como una búsqueda de archivos de registro. Los resultados se incluyen en el prompt inicial de investigación como evidencia en bruto, proporcionando a Claude una imagen factual de la máquina antes de que se establezca cualquier hipótesis. Este paso nunca se bloquea, absorbe silenciosamente los errores y dura menos de un segundo. Se justifica porque el siguiente paso de razonamiento parte entonces de la verdad objetiva y no únicamente de la descripción del problema.
Salida estructurada: el informe de finalización
Cada iteración termina con un contrato en JSON que se puede analizar, el completion_report, y el sistema lo lee para decidir qué sucederá a continuación. Su estructura es fija:
{
"status": "IN_PROGRESS" | "BLOCKED" | "COMPLETE",
"confidence": 78,
"open_questions": ["..."],
"unchecked_sources": [{"name": "...", "access_status": "..."}],
"contradiction_register": ["..."],
"assumption_register": ["..."],
"adjacent_problems": [{"summary": "...", "status": "...", "blocker": "..."}],
"draft_response": "the user-facing Slack message"
}
Puede haber prosa en formato libre alrededor del bloque, pero las puertas de control evalúan el informe analizado y los archivos de estado lo conservan. Comprar algo de libertad en la prosa a cambio de un contrato analizable fue el factor más importante encontrado para mantener las salidas de los LLM útiles dentro de un pipeline. Si se omite el esquema, cada paso posterior debe extraer los datos de la prosa. Si se mantiene, las puertas de control se convierten en código Python determinista ordinario.
Puertas de control de calidad que dan significado a la confianza
Una puntuación de confianza sería solo ruido publicitario si Claude pudiera escribir confidence: 95 y seguir adelante. Por eso, la salida de cada iteración pasa por puertas de control de calidad que se niegan a permitir que la investigación avance a menos que las pruebas y la confianza declarada coincidan.
Tres familias de puertas de control se ejecutan después de cada turno de Claude:
- G-gates verifican la consistencia lógica, para que el razonamiento no se contradiga entre rondas. El caso más claro es G1, que se activa cuando
open_questionsaumenta durante la ronda peroconfidencetambién sube, ya que un denominador creciente anula cualquier aumento. Esta familia también asegura de que el lenguaje de tensión en el texto se registre como una contradicción, y de que los problemas adyacentes se traten como objetos de estado válidos en lugar de cadenas de marcador. Hay cuatro de ellos. - N-gates comprueban la completitud estructural, asegurándose de que los elementos resultantes estén completamente formados y no sean incompletos. N1 exige que cada acción de corrección ejecutable vaya acompañada de una
verify_actionque proporcione una descripción, un resultado esperado y un plazo. Una corrección propuesta sin forma de verificarla se considera trabajo inacabado. Hay tres de ellos.
COMPLETE mientras los problemas adyacentes sigan abiertos, exigen un registro de suposiciones no vacío e insisten en que al menos una acción propuesta se relacione directamente con la causa raíz. Hay siete en total.Existen dos verificaciones adicionales. EQ1 obliga a los nodos de causa raíz a citar evidencia concreta (cualquier cosa distinta a INFERRED) o aceptar un tope del 60%, mientras que P7 reduce ese límite en un 7% por cada dimensión adversarial marcada como FAIL, hasta llegar a un mínimo del 40%.
El marco completo consta de 19 funciones. Una forma práctica de recordarlas es: las G-gates reaccionan a lo que escribe Claude, las N-gates a lo que omite y las A-gates a lo que afirma.
Una violación se convierte en una entrada pending_guard_notes, la cual se coloca al principio del prompt de la siguiente iteración bajo el encabezado ## NOTAS DE PROTECCIÓN DE CALIDAD DEL SISTEMA (de la iteración anterior), con una instrucción explícita de abordar primero cada nota. Ese es el mecanismo de obligación. Las barreras no bloquean al modelo; en lugar de eso, hacen que sus violaciones anteriores sean lo primero que lea en la ronda siguiente. La cortesía por parte del modelo no tiene importancia, y es la aplicación estructural la que impulsa la investigación.
Un paso de fijación contextual (A7) también se ejecuta al final de cada generador de prompts, independientemente de si se ha violado alguna regla o no. Vuelve a adjuntar, palabra por palabra, las contradicciones sin resolver y las suposiciones no verificadas del informe actual al final del siguiente prompt. Esto contrarresta el conocido fenómeno de desvanecimiento de la atención, en el que un modelo detecta una complicación en la ronda 3 y la descarta silenciosamente para la ronda 7.
La confianza como número concreto
La elección de diseño más importante es que el agente se asigna un valor confidence: <int> en una escala de 0 a 100 en la salida estructurada de cada iteración. Este número no es meramente decorativo, ya que controla el comportamiento real:
- Cuando es inferior a 70, no se puede abandonar el bucle. El proceso fuerza la realización de hasta tres rondas adicionales e incluye una nota de advertencia indicando que el agente afirmó haber terminado cuando en realidad su valor estaba por debajo del umbral establecido.
status: COMPLETE y sin open_questions, se pasa a la fase de revisión por adversarios (equipo rojo contra equipo azul).degrading. El agente está retrocediendo, por lo que se detiene y lo informa al operador.La trayectoria completa de confianza de cada caso se registra en ANALYSIS.md como un gráfico de líneas, por ejemplo 45% → 62% → 78% → 80%. Los operadores lo consultan rápidamente para determinar si el resultado merece confianza, y el propio ciclo de auto-mejora del sistema lo lee al elegir qué casos anteriores reejecutar con un prompt diferente.
Tres formas en que el bucle se niega a detenerse temprano
En el diagrama, tres ramas llegan a ready_for_adversarial o blocked sin que el bucle finalice. Cada una representa una regla de continuidad forzosa: el sistema ignora la señal de salida e inserta una nota de protección en su lugar.
- CONFIDENCE_FORCE se aplica cuando
status=COMPLETEyopen_questions=[], peroconfidencees inferior al 70. El modelo declara victoria mientras expresa dudas a través de ese valor numérico. Se fuerzan hasta 3 iteraciones adicionales, las cuales se contabilizan dentro del límite de 20 iteraciones en lugar de sumarse a él.
status=COMPLETE y confidence es al menos 70, pero causal_chain_complete no es verdadero. Se trata de un hallazgo a nivel de síntoma sin causa raíz identificada. Esto obliga a realizar hasta 3 iteraciones más, siempre dentro del límite de 20 iteraciones.status=BLOCKED, mientras que fuentes técnicas accesibles fueron omitidas o declaradas como no disponibles por inferencia y no debido a un error real. Una investigación no puede finalizar como bloqueada sin haber explorado completamente todas las posibilidades. Permite hasta 3 continuaciones, contadas dentro del mismo límite.Juntos, estos mecanismos evitan las tres salidas prematuras más comunes: la incertidumbre que parece estar respaldada por datos fiables, la finalización a nivel de síntomas y la supuesta indisponibilidad.
El revisor adversario
Cuando el pipeline de monitoreo declara haber finalizado con un nivel de confianza de 70 o superior, el control pasa a una invocación separada de Claude que actúa como revisor adversarial. Este revisor no ve la solicitud del investigador; recibe el texto del análisis junto con una lista de verificación de 15 dimensiones de evaluación, de D1 a D15, que abarcan la validez de las suposiciones, la cadena de pruebas, el razonamiento causal, las hipótesis alternativas y más. Devuelve un veredicto estructurado:
Existen tres formas de finalizar la revisión:
- Los problemas críticos se devuelven al investigador. Sus notas se incorporan en la iteración siguiente como
pending_guard_notesy el ciclo se reinicia, ahora con la confirmación explícita de que el revisor cree que se pasó por alto algo. - Si en la primera pasada solo hay problemas menores, el ciclo se repite una vez más con esas críticas menores incluidas.
complete, y la investigación se publica.Se hace intencionadamente que el paso de adversarialidad sea una llamada separada con un prompt diferente. El mismo modelo en un rol distinto detecta una parte significativa de los halagos a sí mismo que la iteración con un solo prompt permite pasar. En un caso, la afirmación de confianza bajó de 90 a 65 entre la investigación y su revisión, y el revisor señaló precisamente la suposición que el investigador había pasado por alto.
Existe una segunda revisión que se lleva a cabo de manera distinta: la revisión del equipo rojo (G5). El revisor adversario lee toda la narrativa de la investigación, mientras que al revisor del equipo rojo solo se le muestra la descripción original del problema y la conclusión final. No recibe información sobre los razonamientos intermedios, ni sobre la cadena de pruebas, ni sobre las rondas anteriores. A partir de esos dos elementos, forma una hipótesis independiente y señala los casos en que los hallazgos de la investigación difieren de lo que un externo habría deducido.
El objetivo es el aislamiento cognitivo, no una cobertura adicional. La revisión adversaria verifica si la investigación está estructuralmente completa (de D1 a D15). El equipo rojo pregunta si se podría haber llegado a la conclusión sin el enfoque de la investigación: ¿está el razonamiento realmente basado en el problema, o la investigación se apoyó en un hallazgo inicial y permitió que la conclusión siguiera ese punto de apoyo en lugar de las pruebas? Ambas se ejecutan como llamadas separadas a Claude, y ambas deben superarse antes de que un caso sea marcado como COMPLETE.
Cinco maneras honestas de finalizar
El ciclo de investigación puede terminar por cinco razones. Cualquiera que se active se registra y se muestra al operador, como en líneas similares a estas:
exit=complete confidence=89% iterations=12 elapsed=13min
exit=blocked confidence=62% iterations=4 elapsed=4min (cannot proceed without info)
exit=stalled confidence=58% iterations=7 elapsed=22min (two rounds, zero new facts)
exit=degrading confidence=42% iterations=5 elapsed=8min (dropped >15% twice)
exit=timeout confidence=73% iterations=18 elapsed=50min
De los cinco, blocked es el que un operador puede actuar de manera más directa. El agente ha registrado bloqueadores explícitos en unchecked_sources o adjacent_problems, y es necesario que una persona realice una llamada para eliminarlos. Los otros cuatro son modos de fallo técnico que el sistema detecta y no intenta resolver automáticamente.
Qué puede acceder Claude desde adentro
Aquí son importantes los límites de las herramientas, ya que las barreras de confianza dependen en parte de lo que Claude puede acceder realmente. Una instrucción que pide al agente verificar sus fuentes no verificadas no sirve de nada si no existen las herramientas para hacerlo. Cada pipeline inicia un subproceso de Claude Code con --permission-mode bypassPermissions. No aparece ninguna lista de herramientas permitidas en el prompt. El límite está determinado por lo que esté integrado en el catálogo del Model Context Protocol (MCP), que abarca cinco sistemas externos:
Algunas restricciones merecen mención, ya que influyen en el comportamiento más que cualquier otro elemento de la ficha del modelo:
- El pipeline de investigación prohíbe explícitamente
Bash(--disallowedTools Bash), lo que evita las condiciones de carrera en git, ya que dos procesos de Claude editando al mismo tiempo un árbol de trabajo corromperían el estado. El pipeline dev-agent sí permite usar Bash, pero este se ejecuta en un árbol de trabajo aislado. - Las herramientas de escritura en Slack están bloqueadas en cada llamada al pipeline.
send_message_to_selfyedit_message_to_selfestán prohibidos, por lo que Claude no puede publicar en Slack por sí mismo. Toda comunicación saliente pasa a través del campodraft_responsedel informe de completación estructurado, el cual el sistema solo publica después de haber validado las condiciones de finalización.
bypassPermissions no significa libertad total en la práctica. El prompt de estructuración de cada pipeline incluye reglas estrictas. Se puede editar ANALYSIS.md, pero no message.json. Las escrituras fuera de case_dir se revierten automáticamente mediante git diff después de la ejecución. Las escrituras en la base de datos de producción requieren confirmación explícita del operador, según la instrucción dada. La seguridad reside en la capa de estructuración y en una verificación de límites después de la ejecución, y no en la capa de llamadas a las herramientas.search_datadog_services devuelve un rechazo de permisos, el prompt indica al agente que cambie a mcp__glean_default__search, utilizando palabras clave de Jira como sustituto. La cadena de soluciones alternativas forma parte del modo en que se aplica el procedimiento estándar y no tiene cabida durante la ejecución.tool_use a medida que llegan y envía hasta 10 operaciones de lectura concurrentes (consultas a Datadog, búsquedas en Glean, lecturas de archivos). Todo lo que realiza escrituras, ya sea Bash con efectos secundarios, ediciones, comentarios en MR o escrituras en la base de datos, se realiza uno por uno. Por lo tanto, una sola iteración de investigación puede ejecutar seis consultas a registros en paralelo y aún así mantener el orden de las escrituras.Un esqueleto, cuatro tamaños
Cada pipeline del conjunto ejecuta alguna variante del mismo esqueleto:
El pipeline de investigación en modo de guardia realiza hasta 20 iteraciones. La fase de análisis de tareas alcanza hasta 8 iteraciones. La fase de implementación ejecuta el esqueleto una vez, pero repite su paso de construcción tres veces. El dev-agent no tiene bucle interno y se ejecuta una vez por cada inicio, pero mr-monitor traslada efectivamente la iteración al exterior: genera un dev-agent nuevo cada vez que el CI falla, hasta tres veces consecutivas, antes de pasarle el caso a un humano.
La uniformidad es intencional. Los cuatro bucles existen porque los problemas son diferentes, pero todos utilizan el mismo lenguaje: la confianza se expresa como un número, una salida tiene un nombre, la autocrítica corresponde a una llamada separada a Claude, y la escalación implica un mensaje directo en Slack dentro del hilo que inició el caso. Un operador aprende esta estructura una vez y luego la reconoce en cualquier situación.
La capa de auto-mejora
Esto cierra el bucle descrito anteriormente, en el cual el sistema modifica su propio código bajo revisión humana. Dos bucles realizan la tarea, en diferentes escalas de tiempo.
El bucle 1 repara la estructura caso por caso, desde las deficiencias hasta las propuestas de cambio. Una vez finalizada una investigación, gap_analyzer busca lugares donde el sistema no funcionó correctamente: fuentes de datos omitidas, elecciones erróneas de herramientas, errores en el flujo de trabajo, pruebas faltantes y descripciones de las propuestas de cambio mal formateadas. Los filtros por categoría y nivel de confianza reducen esas deficiencias antes de que finalize_case las reciba. Cuando un caso permanece inactivo durante cuatro horas, finalize_case activa patch_suggester en un árbol de trabajo dedicado y genera una propuesta de cambio contra la propia base de código del sistema, modificando archivos de código, plantillas de mensajes o secciones de procedimientos operativos. Fusionarla requiere la intervención humana; escribirla, no.
Loop 2 refuerza el comportamiento en todos los casos, desde los modos de fallo hasta las sugerencias para procedimientos operativos estándar. Al final de cada caso, el paso auto_retrospective registra qué modos de fallo ocurrieron, clasificados en tres categorías: fallas en la recopilación de datos (¿se omitieron fuentes accesibles?), violaciones de los controles de calidad (¿contradecía una afirmación de confianza las pruebas disponibles?) y fallas en la completitud del análisis (¿se interrumpió el rastreo de las causas raíz?). Los registros se acumulan por proyecto en el registro de casos. Cada semana, un trabajo cron llamado knowledge-updater cuenta los diez últimos casos y, cuando un modo supera un umbral, elabora una sugerencia para procedimientos operativos estándar: 3 de cada 10 casos generan una sugerencia y 6 de cada 10 generan una alerta. Las sugerencias son concretas. Por ejemplo, “se omitió la consulta directa a la base de datos” se produjo 3 veces este mes, por lo que ahora los procedimientos operativos estándar exigen realizar la consulta directa a la tabla antes de cualquier inferencia sobre el esquema. Los humanos revisan y aplican dichas sugerencias.
Bueno, y por diseño nada se aplica automáticamente aquí, ya que las decisiones automatizadas que agravan sus propios errores son un peligro conocido en los sistemas autoreguladores.
De esto surge la personalización. El SOP codifica la visión del equipo sobre cómo se llevan a cabo las investigaciones, y al principio es genérico. Con el tiempo, el kb-updater crea un perfil de los patrones de fallo de un proyecto a partir de los casos y repeticiones. Con 20 a 30 casos, el SOP de ese proyecto ya no se parece al modelo genérico: registra qué fuentes de datos se siguen omitiendo, qué herramientas se usan incorrectamente y qué enfoques de investigación fallan con frecuencia en ese código específico. Cada proyecto mantiene sus propios SOP, por lo que los errores del Equipo A solo influyen en las reglas de ese equipo y nunca afectan al Equipo B.
La durabilidad es otra ventaja. Una ventana de contexto dura solo una sesión, mientras que un cambio de código fusionado se versiona, revisa, prueba y queda permanente. Una actualización de los procedimientos operativos, una vez aprobada por el operador, aparece en todas las investigaciones posteriores. El sustrato sigue mejorando, por lo que cada nuevo caso se ejecuta con un sistema más avanzado que su predecesor.
Nivel 3: estado que sobrevive a los reinicios
La orquestación es lo que se ejecuta por evento. El estado persistente es lo que queda cuando un proceso se reinicia, una máquina se reinicia o el operador pasa de una computadora portátil a una VM en la nube. El sistema mantiene su estado en tres niveles con propiedades muy diferentes:
Cachés en memoria son pequeños y se pueden reconstruir. Se encuentran en el proceso de monitoreo y mantenimiento activo, y se reconstruyen a partir del estado local después de un reinicio.
Estado local en ~/.harness/*.json es el mapa operativo. Los archivos clave son:
task-thread-map.jsonrelaciona un Slack thread_ts con un ID de JIRA y una ruta absoluta detask_dir.oncall_thread_map.jsontiene la misma estructura para los casos de investigación en modo on-call.monitored-mrs.jsonenumera los MRs que harness abrió junto con su estado de CI.patch-pr-state.jsonregistra el estado final por caso (merged,no_gaps,failedy así sucesivamente).oncall-state.jsoncontiene la bandera de interruptor de emergencia, cubierta en Capa 6.socket-dedupalmacena las marcas de tiempo de los mensajes recientes ya procesados por el Modo Socket.
Esta capa pertenece a una sola máquina y no se comparte. Sobrevive a un reinicio, pero si la máquina falla, desaparece.
Estado sincronizado con Git es la capa duradera. Los espacios de trabajo por caso ubicados en case-workspaces// se guardan y se envían a un repositorio Git privado cada vez que hay un cambio significativo en el estado. Otra máquina que clone dicho repositorio puede retomar el mismo contexto de la conversación, lo cual permite la continuidad de la sesión entre máquinas. Un operador puede chatear desde una computadora portátil o una VM de desarrollo remota sin perder ninguna información.
La consolidación de la memoria se convirtió posteriormente en una tercera función para el estado sincronizado con git. Una conversación larga genera docenas de archivos followup_transcript_*.md. Cuando se acumula suficiente actividad, un subagente los lee todos, reescribe TASK.md para incluir el estado condensado, y el flujo de trabajo deja de enviar transcripciones en bruto para las etapas siguientes. Omitir la consolidación hace que los prompts superen el límite de contexto del modelo. Hacerlo implica el riesgo de comprimir en exceso y perder información, lo cual realmente ocurrió: un documento de 14 KB se redujo a 587 bytes. De ese incidente surgieron tres medidas de protección: un modelo de prompt que indica “ACTUALIZAR, no REESCRIBIR”, una restricción de tamaño de salida que impide escrituras inferiores al 50% del tamaño de la entrada, y un archivo de bloqueo por caso para evitar que dos procesos de consolidación compitan entre sí.
La ruta de acceso desde un único hilo de Slack hasta el archivo TASK.md del caso es breve, pero merece ser mostrada:
Los dos archivos de estado contienen el mismo tipo de valor: una ruta absoluta al espacio de trabajo del caso. Cualquier flujo de trabajo terminará por referirse a case_dir tarde o temprano y luego funcionará en relación con él. Cuando la raíz del espacio de trabajo fue renombrada a mitad del proyecto, un script de migración lo gestionó reescribiendo las rutas absolutas en ambos mapas al mismo tiempo que se movía el directorio.
El estado duradero es la base. La siguiente pregunta es qué sucede cuando las operaciones basadas en él fallan, y la respuesta es que el sistema se recupera de ellas sin transmitirlas al operador.
Capa 4: bucles que se reparan solos
Esta es la capa en la que el sistema deja de ser un notificador y se vuelve autónomo. Cuenta con tres bucles, cada uno cerrado sin la intervención de ningún operador.
Los dos primeros son completamente autónomos. El tercero no lo es intencionadamente. Para actualizar la sesión del proveedor de identidad se necesita una solicitud de MFA vinculada al hardware, por lo que el agente se detiene y solicita acción humana, lo que genera un procedimiento de 30 segundos en el canal privado. Esto representa la intervención humana por diseño y no existe brecha de capacidades.
Estos bucles comparten una característica sutil: cada uno tiene un presupuesto de intentos y puede reconocer lo que ya ha hecho. La corrección automática en CI está limitada por solicitud, por lo que tres fallos consecutivos derivan al operador. La respuesta automática del revisor elimina duplicados por autor, lo que significa que cuando la última nota del boto en una discusión es más reciente que la del revisor, el bucle permanece en silencio porque ya existe una respuesta. La herramienta de proveedor de identidades permite un plazo de aprobación de 15 minutos. Si se eliminan esas limitaciones, a veces los bucles entran en espiral. Al principio, una solicitud con una prueba de integración inestable generó 80 commits innecesarios del tipo “corregir CI” en una sola tarde antes de que alguien se diera cuenta.
Recuperación tras operaciones individuales
Los ciclos de corrección automática de CI y respuesta del revisor descritos anteriormente permiten recuperar flujos de trabajo completos desde el inicio hasta el final. Debajo de ellos, cada operación primitiva que puede fallar (un push, un rebase, un comentario en una solicitud de fusión, un restablecimiento de rama) se basa en un diseño separado y más pequeño de tres capas para la autorecuperación por operación. Este difiere del modelo de seguridad de tres capas de la Capa 6, que se refiere a las restricciones, es decir, a lo que el agente puede hacer. Este modelo, en cambio, se centra en la recuperación, es decir, en lo que ocurre cuando una operación permitida falla. Es fácil confundirlos, así que manténgalos separados.
L1 es una protección determinista. Basada en reglas y siempre activa, responde en menos de un segundo. Para las actualizaciones se utiliza _pre_push_rebase(repo_path, branch): justo antes de realizar la actualización, el sistema ejecuta git fetch origin y git rebase origin/. Un rebase sin conflictos permite continuar con la actualización. Si hay un conflicto o la consulta falla, L1 interrumpe de forma segura, enumera los archivos en conflicto y transfiere el caso a L2. L1 no puede ser creativa, intencionadamente. La regla se mantiene lo suficientemente sencilla como para ser comprendida y lo bastante económica, sin gastar tokens, como para aplicarse a cada operación.
L2 es un bucle de autoreparación basado en agentes. Cuando L1 no logra solucionar la situación, el sistema inicia una sesión restringida de Claude (_self_heal_push_loop) y le proporciona toda la información sobre el fallo: el estado de git, los archivos en conflicto, la tarea que motivó el envío y algunas restricciones estrictas. Nunca realice un push forzado en una rama protegida, nunca descarte commits que no se hayan enviado y nunca elija ciegamente un lado en caso de conflicto. Tres restricciones garantizan la seguridad:
- Reintentos limitados. Cada incidente permite un máximo de 3 intentos, y cada turno de Claude tiene un tiempo de espera de 4 minutos. Después de cada acción de Claude, el sistema ejecuta nuevamente el verdadero
git push. La opinión de Claude sobre si el conflicto se ha resuelto no tiene valor alguno; solo un código de salida 0 degit pushconstituye la verdad absoluta. Si el siguiente intento de envío devuelve un valor distinto a 0, el bucle registra que el intento falló, independientemente de cuán seguro esté Claude de haber tenido éxito. - Umbral de confianza de 70 o más. Este requisito se toma tal cual del parámetro
oncall_runde la pipeline de investigación para situaciones de emergencia. Claude califica su propio desempeño del 0 al 100 en cada turno, y si la puntuación es inferior a 70 debe abandonar la tarea en lugar de actuar. Una autoevaluación por debajo de este umbral no le permite modificar el árbol de trabajo.
git push y nunca a nada que Claude diga sobre sí mismo. L2 no puede eludir esta puerta; solo puede proporcionarle un push real.L3 corresponde a la escalada al operador. Si L2 abandona el proceso por baja confianza o agota sus intentos, el sistema envía al operador un mensaje privado con toda la información del proceso L2, es decir, cada intento, cada acción realizada por Claude, cada valor de confianza asignado y el estado final obtenido con git status. El operador parte de un estado completamente documentado y no desde la suposición de que "algo salió mal". La escala de tiempo es de horas, no segundos.
Destacan dos principios:
- L2 funciona por incidente y no está siempre activo. L1 se ejecuta en cada envío porque es económico, mientras que L2 consume tokens y solo se activa después de que L1 haya fallado; por lo tanto, el costo está determinado por los casos raros y no por los comunes.
- La verdad objetiva proviene de la red. Sea cual sea lo que Claude considere acerca de su propio trabajo, la validación repite la operación real: el código de salida de
git pushpara los envíos, una ejecución real de prueba para las correcciones y la verificación del código de salida del envío por parte del mecanismo de respuestas para las solicitudes de revisión. El autoinforme del agente nunca se considera como verdad objetiva, ya que el sistema de pruebas lo vuelve a verificar contra el sistema real.
Ese mismo contenedor es adecuado para mucho más que solo los envíos. Una operación cumple los requisitos cuando existen tres elementos para ella: una solución sencilla basada en reglas (L1), un intento de reintentar limitado estrictamente por el agente (L2) y una forma clara de transferirla a un humano (L3). Esta estructura de tres partes separa un bucle autónomo que se recupera de fallos que ya conoce de uno que necesita a un humano tan pronto como las circunstancias cambian.
Nivel 5: cuatro lugares para ver qué sucedió
El sistema escribe en cuatro superficies de observabilidad, y cada una está ajustada para un tipo diferente de usuario:
- Actualizaciones en tiempo real en Slack. Mientras un agente está en ejecución, un mensaje temporal en el hilo del caso muestra diferentes estados (
Reading TASK.md, luegoSearching codebase, despuésDrafting patch) y finalmente pasa aDoneoFailed: <reason>. Este informador en streaming modifica un mensaje directamente, de modo que los hilos no se llenan de información irrelevante. - Registros estructurados en
~/.harness/logs/agent.logy~/.harness/logs/error.log. Cada inicio, envío en cadena e intento de reintentar se convierte en un evento estructurado, y son estos archivos los que se consultan cuando es necesario diagnosticar algún problema.
~/.harness/logs/system.log para eventos transversales como la finalización de despliegues, acciones del watchdog y el estado de sincronización.Se omitió intencionalmente un panel de control. Slack y grep sirven como consolas. Un panel de control se convierte en una tercera fuente de información, y es lo primero que los ingenieros dejan de leer una vez que se vuelve obsoleto. El patrón de mensajes en tiempo real proporciona suficientes señales operativas como para que nadie desee añadir una interfaz de usuario encima.
Monitoreo de las conexiones MCP
Cinco servidores MCP externos (Slack, Datadog, Jira y Confluence, Glean, GitLab) sustentan este sistema. Cada uno cuenta con su propio token OAuth, frecuencia de actualización y formas opacas de fallar. Cuando un token vence sin que se detecte, una sesión de Claude comienza a responder “No tengo acceso a esa herramienta”, y el operador no observa ningún error en absoluto.
Un servicio pequeño llamado mcp-watchdog cierra esa brecha. Cada 10 minutos consulta el punto de extremo de estado de cada MCP e intenta realizar una llamada a una herramienta que no realiza acción alguna. Cuando eso falla, realiza lo siguiente:
- Intenta actualizar el token de forma silenciosa.
- Si la actualización falla, envía un mensaje directo a Slack al operador con un texto similar a “El MCP de Atlassian necesita reautenticación, haga clic aquí.”
- Después de dos desconexiones consecutivas (
DISCONNECT_CONFIRM_THRESHOLD), eleva el nivel de alerta con una reacción:warning:en el ticket más reciente.
Si falta este bucle, un token vencido se confunde con la idea de “el agente está torpe hoy”, un tipo de fallo muy difícil de diagnosticar porque agent.log nunca indica “token vencido”; en su lugar dice “la herramienta devolvió un resultado vacío”. Tratar la salud de la autenticación como un elemento de observabilidad de primera categoría es algo que debería haberse implementado antes.
Capa 6: la interfaz de control del operador
Esta es la interfaz más utilizada y también la que inicialmente estuvo subdesarrollada. Toda interacción del operador pasa por un único canal, el self-DM, y los comandos se dividen en seis categorías:
- Tarea:
admin: task <jira>ejecuta todo el proceso de análisis, implementación y MR, y al añadir--autose hace automático.
admin: oncall-run <url> inicia una investigación a partir de un hilo de Slack, mientras que oncall-toggle activa o desactiva la supervisión automática.admin: mr rescan vuelve a escanear un MR en particular, y mr pause/resume activa o desactiva el bucle de respuesta automática en ese MR.admin: oncall pause [duration] silencia el envío automático por un tiempo, y oncall resume/status termina la pausa o informa sobre el estado actual.admin: finalize <case> recopila los informes de discrepancias y crea una solicitud de mejora relacionada con el propio sistema.admin: register-thread vincula manualmente un hilo de Slack a un caso, mientras que close-case marca uno como finalizado.El interruptor de pausa fue lo que más tiempo tomó configurar correctamente. La versión uno pausaba todo, incluidas las órdenes del administrador, lo que impedía que un operador que hubiera pausado el sistema pudiera reanudarlo. La versión dos pausaba de manera demasiado restrictiva, por lo que la opción de “detener el ruido” no se cumplía. El diseño actual se basa en una distinción precisa: la pausa silencia el envío automático desencadenado por la actividad de otras personas, pero nunca las órdenes de administrador del propio operador ni sus acciones dentro del hilo. En el sistema de monitoreo, los sitios de envío se dividen en dos grupos. Un grupo corresponde a la actividad automática generada por otros, y se bloquea durante la pausa. El otro grupo corresponde a la intención del propio operador, y permanece activo.
La calibración se logró de la manera más difícil. Poco después de que se enviara la primera pausa, una respuesta automática a los comentarios de los revisores se activó silenciosamente en otro caso y pasó desapercibida, ya que parecía que la pausa estaba en efecto. El diseño actual trata la pausa como un silencio controlado: silencia el sistema mientras mantiene la capacidad de acción del operador.
Una imagen muestra esta división:
La regla es que las acciones propias del operador nunca se silencian, mientras que la actividad de otras personas se bloquea durante la pausa. Eso es todo lo que controla admin: oncall pause.
Dos capas adicionales de seguridad
El interruptor de apagado de la pausa es la capa de seguridad más visible, aunque no la única. Hay otras dos que funcionan sin necesidad de configuración en tiempo de ejecución: una a través del código y otra mediante convenciones de despliegue:
La lista de canales permitidos en Slack merece más atención de la que parece. Un incidente casi ocurrido al principio del proyecto se debió a un token de Slack mal configurado que podía escribir en los canales internos del equipo. El agente podría haber publicado directamente a sus compañeros de equipo, omitiendo al operador, y nadie habría podido distinguir esas publicaciones de las hechas por un humano. Ahora, cada llamada chat.postMessage verifica su destino contra una lista de permisos antes de ser enviada. El único destino permitido es el mensaje directo del operador, así como un ID de canal codificado de antemano para la conversación del operador con el bot. Si el agente quiere contactar a un compañero de equipo, devuelve el contenido al operador, quien decide si lo reenvía. Esta regla arquitectónica sigue demostrando su eficacia: el agente solo puede comunicarse hacia afuera a través del buzón de correo del operador.
Más allá de las seis capas: qué lo hace apto para producción
Las seis capas constituyen la estructura portante. Varios mecanismos se encuentran fuera de esa taxonomía y son igualmente importantes. Ellos distinguen a un sistema que funciona bien de uno que opera sin interrupciones.
Ruteo de varios proyectos desde un archivo YAML
Hoy mismo hay dos proyectos en funcionamiento, cada uno con sus propias reglas operativas, y agregar un tercero no requiere ningún cambio de código, solo una nueva entrada en agent-config/projects.yaml:
projects:
team-a:
sop: "team-a/SOP.md"
kb: "team-a/KNOWLEDGE-BASE.md"
dfr_threads_dir: "team-a/threads"
task_dir: "task-workspaces"
slack_channels: ["support-channel-a", "support-channel-b"]
jira_prefixes: ["AAA", "BBB", "CCC"]
team-b:
sop: "team-b/SOP.md"
kb: "team-b/knowledge-base/kb.md"
dfr_threads_dir: "team-b/threads"
task_dir: "task-workspaces"
slack_channels: []
jira_prefixes: ["XX", "YY"]
La función resolve_project() dirige cada señal recibida según su prioridad: primero el prefijo de Jira, luego el canal de Slack y, finalmente, la opción predeterminada. Cada proyecto cuenta con su propio archivo SOP, archivo de conocimiento y estructura de directorios, y el mecanismo de generación automática de solicitudes para investigaciones selecciona de forma automática el SOP y el archivo de conocimiento adecuados según el proyecto al que se dirige la señal. Eso permite que una instancia de harness sirva a dos equipos con dos conjuntos de reglas diferentes.
Este patrón es generalizable. Cualquier organización que cuente con varios equipos de producto bajo un mismo harness puede utilizar el mismo enfoque: configuración declarativa y sin ramas de código por proyecto. Además, impone una disciplina clara, ya que todo lo específico de un proyecto debe encontrarse en su archivo SOP y nunca en el código, dado que este es compartido.
Reanudar una sesión en cualquiera de las máquinas
El operador trabaja con dos máquinas: una laptop y una VM en la nube de larga duración en el centro de datos, y una sesión de Claude debe poder reanudarse en ambas. Eso es difícil porque Claude Code almacena el estado de la sesión en el disco local por defecto, por lo que después de un reinicio en otro lugar la sesión no se puede ver.
El sistema lo resuelve con un almacén de sesiones basado en git:
El mecanismo consta de tres partes:
- Las sesiones se registran en git. Los archivos
.jsonly.metade cada sesión se guardan y se envían a un repositorio privado cada vez que hay un cambio significativo en su estado, y ese repositorio es la fuente de verdad. - Un proceso en segundo plano se ejecuta cada 5 segundos en cada máquina para mantener la copia local actualizada. Pre-carga el contexto, de modo que al reanudar una sesión de Claude no se paga la latencia de red con cada tecla pulsada.
session-start.sh examina los archivos locales .meta en busca de la entrada registrada para ese directorio de trabajo y obtiene su UUID. De este modo, un único UUID sigue al operador entre diferentes máquinas.El complemento de sincronización, user-prompt-submit.sh, se ejecuta después de cada pregunta de Claude y realiza tres operaciones en orden estricto, serializadas con flock:
- Obtener y restablecer. Al ejecutar
git fetch origin main && git reset --hard, el archivo localactive.jsonlqueda con la transcripción de la otra máquina. - Extraiga el delta antes de sobrescribir. Lee la marca de agua, que indica cuántas líneas ya se han insertado desde la otra máquina, extrae solo las líneas más recientes y las guarda en un archivo temporal. Esto debe realizarse antes del paso 3. Un error anterior intercambiaba estos dos pasos, por lo que la inserción seguía leyendo el propio registro de la máquina.
- Envíe su propio registro. Copia el registro de la sesión local de Claude a
active.jsonl, lo guarda y lo envía, y luego establece la marca de agua en la cantidad de líneas locales.
Después de que el grupo libere los cambios, el delta guardado en el paso 2 llega a Claude como un mensaje del sistema que indica que la otra máquina agregó N mensajes desde la última sincronización. El siguiente comando entonces tiene en cuenta el contexto de ambas máquinas.
Un archivo separado .linecount contiene el verdadero recuento total de líneas, ya que wc -l da información errónea cuando una transcripción se ha truncado para su envío. Las transcripciones se cortan a 1,000 líneas antes de ser enviadas para mantenerse por debajo del límite de 25 MiB de git. Sin un .linecount, cada truncación restablecería el marcador de agua y la inyección volvería a entregar miles de líneas que ya se habían procesado.
La operación de git serializada con flock evita la corrupción de index.lock debido a envíos concurrentes, lo cual ocurrió una vez cuando el inicio de una sesión atascada se superpuso con la ejecución de un enriquecedor de metadatos. Un mecanismo de limitación, configurable y con un valor por defecto de 30 segundos, omite la sincronización completa ante pulsaciones rápidas de teclas, evitando así demoras en git por cada carácter.
La idea más amplia que vale la pena adoptar es utilizar git como almacén de estado. En este contexto, git funciona como algo más que un repositorio de código; actúa como un registro duradero similar a CRDT del estado del agente, con las dos máquinas del operador sirviendo como lectores eventualmente consistentes.
La consolidación de la memoria está estrechamente relacionada con la continuidad entre máquinas. El paso memory_consolidate, que reescribe las transcripciones en archivos TASK.md y ANALYSIS.md limpios, no se ejecuta según un temporizador. Un servicio separado, consolidate_trigger, verifica en cada turno la presencia de cuatro señales: un SHA de commit o una palabra que indique decisión en la transcripción, un hilo de revisión que acaba de cerrarse, un espacio de trabajo con más de 5 KB de transcripciones acumuladas, o al menos una hora desde la última consolidación. Cualquiera de estas señales lo activa. La lógica detrás es que la información deja de acumularse después de una decisión, no tras un tiempo determinado, y juntas estas cuatro señales permiten estimar si la conversación ha alcanzado una pausa natural, de modo que el operador nunca necesita indicarlo.
Los worktrees son más que un detalle de implementación
Anteriormente se dijo que todo ocurre en los worktrees. Eso merece una explicación más detallada, ya que la decisión detrás de ello tiene un peso real.
El diseño original preveía que el agente generara un diff y que el harness ejecutara git apply en la copia del repositorio principal. Esto generó dos modos de fallo que se repetían constantemente:
- Encabezados de diff frágiles. Claude generaba un diff cuyos números de línea estaban correctos en el momento de su creación, pero se volvían incorrectos después de que se aplicara un commit concurrente, lo que hacía que
git applyrechazara todo el parche. - Un árbol principal atascado. Si
git apply --checkfallaba a mitad de proceso, el repositorio principal quedaba parcialmente aplicado, y el siguiente caso que se ejecutaba en ese mismo repositorio heredaba ese estado corrupto.
worktree_manager es la solución. Antes de que cualquier agente modifique código, crea un árbol de trabajo aislado en ~/.worktrees/case-<case_id>-<stamp>/, obtenido de una rama limpia. Claude trabaja allí con sus herramientas nativas de edición y escritura, lo que elimina la necesidad de usar git apply y los pasos adicionales mediante texto de diferencias. Funciona como un ingeniero humano: crea una nueva rama, la descarga, luego edita, hace commit y sube los cambios.
Una vez que la solicitud de fusión se fusiona o cierra, el árbol de trabajo se elimina. Un fallo parcial afecta únicamente a ese árbol de trabajo, dejando intactas la rama principal y los otros casos en ejecución.
Eso es lo que hace seguros a los agentes concurrentes en una misma máquina. Sin árboles de trabajo por caso, dos casos en el mismo repositorio competirían por el estado del sistema de archivos. Con ellos, los casos están aislados por diseño.
The harness soluciona sus propias solicitudes de pull request
El código fuente del arnés se encuentra en un repositorio privado de GitHub. Cuando el proceso de gap-report, finalización y ciclo MR genera una solicitud de mejora para el propio arnés, dicha solicitud pasa por su propio proceso CI que incluye pytest, herramientas de linting y una verificación de sintaxis en Python. Un servicio separado llamado pr-ci-fixer se encarga de cualquier fallo que surja, de la misma manera en que mr-monitor maneja los fallos en las solicitudes MR relacionadas con tickets.
El resultado es una capacidad de autoreparación tanto para el código escrito para los tickets como para el código relacionado con el arnés. Supongamos que una solicitud MR agrega un nuevo presupuesto de intentos y rompe una prueba existente, lo que provoca un fallo en el CI. pr-ci-fixer lo detecta, inicia una sesión de Claude en un worktree, corrige el problema y vuelve a subir los cambios, de modo que el revisor humano encuentra una solicitud MR con estado positivo lista para fusionarse. Este ciclo ya ha funcionado en más de una ocasión con cambios reales en el arnés.
Dirigir el bucle hacia la fuente propia del harness es la mejor forma de verificar si realmente merece el nombre de producción. Un agente que puede mantener sus propias solicitudes de pull request en estado aprobado probablemente sea digno de confianza para manejar las de otros.
Cinco niveles de pruebas para cinco categorías de fallos
El harness cuenta con 4,656 funciones de prueba distribuidas en 126 archivos. Esa cifra carece de significado sin un marco que indique qué detecta cada prueba, ya que las pruebas no son todas iguales.
El marco de L0 a L4 asigna cada prueba a uno de cinco niveles según lo que isola:
- L0, límite puro. No se simulan componentes externos y no hay sistema de archivos ni operaciones de entrada/salida. Cubre la lógica pura como funciones de control, extracción de JSON y cálculos relacionados con los límites de confianza; todo lo que pase en el nivel L0 funcionará en cualquier entorno de despliegue.
Las pruebas L4g cuestan más de escribir y son las más valiosas de mantener. Codifican la memoria institucional sobre los modos de fallo que permanecen invisibles hasta que vuelven a producirse.
Una guía de estilo que evita que el texto parezca escrito por una máquina
Cada solicitud de cambio que pasa por el sistema de arneses se somete a otro filtro antes de ser procesada, siguiendo un procedimiento operativo estandarizado diseñado específicamente para que el resultado no parezca generado por IA. Las reglas son claras:
- Ninguna mención a IA en absoluto. No se permite
Co-Authored-By: Claude, ni “Generated with Claude Code”, ni el sufijo[bot]en los mensajes de commit. La solicitud pertenece al equipo y no al modelo. - Ningún encabezado con espacios en blanco que deban completarse. Están prohibidos templates como “What this MR does / why we need it”. La descripción debe ser un texto continuo con una lista de viñetas concreta titulada
Changes:, redactada como lo haría un ingeniero a mano. - Ningún formato en negrita dentro del texto. No se permite usar etiquetas como
Root cause:seguidas de un párrafo, al igual queFix:yRisk:. El uso de negrita en formato de etiqueta es el indicio más fiable de que fue escrito por una máquina.
pytest tests/test_foo.py::test_bar -v con la orden real y las 3 líneas de salida relevantes sí cuenta.En el momento en que se redacta la descripción de la solicitud de cambio, las SOP se insertan en el prompt para generar parches. Están separadas del template del prompt porque los operadores las gestionan: púdrseles editar cuando se desee, y la siguiente solicitud de cambio seguirá las nuevas reglas, sin necesidad de reiniciar ningún daemon ni modificar el código. Incluso el bucle de revisión puede proponer ediciones a las SOP, por lo que las reglas meta sobre cómo el agente redacta las solicitudes de cambio se transmiten como tales dentro del mismo archivo que las contiene.
Esto es más importante de lo que parece. Nada destruye la confianza en un proceso automatizado de MR más rápido que cuando el equipo se da cuenta de que todas las descripciones parecen haber sido generadas por una máquina. Una vez que se forma esa impresión, aumenta la barrera de fusión, las señales reales del agente quedan ocultas y los operadores comienzan a querer redactar las descripciones a mano, lo cual anula el propósito del sistema. Las normas de estilo aseguran que las descripciones se integren con las escritas por humanos, lo que mantiene el ciclo funcionando.
Un ticket, un MR, de principio a fin
Esto es lo que sucede cuando llega un ticket real. Los tiempos son aproximados.
En total, este ciclo suele completarse en 30 a 90 minutos, tiempo real, para un ticket pequeño o mediano. La participación activa total del operador suele ser de menos de 5 minutos: confirmar el análisis, orientar decisiones ambiguas si es necesario y, opcionalmente, hacer clic en fusionar.
Las investigaciones se van acumulando. Después de 30 casos, hay 30 archivos gap_report*.md, cada uno señalando algo que el LLM consideró que podría mejorarse. Una ejecución de admin: gap-patterns cada dos semanas los agrupa en patrones de fallo recurrentes, y la pasada final convierte a los grupos más grandes en solicitudes de mejora dirigidas al repositorio del harness, lo que significa que el harness se mejora por sí mismo. Este es el ciclo de auto-mejora puesto en práctica.
Comparación con otros harnesses
Varios otros sistemas comparten la estructura del harness. La tabla muestra en qué puntos este se superpone con ellos y en cuáles difiere:
Áreas donde este harness va excepcionalmente en profundidad:
- Bloqueo de la hipótesis antes del compromiso. Claude se compromete con al menos seis hipótesis candidatas antes de que se ejecute una sola iteración de investigación, lo que evita la racionalización posterior. Ninguno de los sistemas comerciales comparados ofrece algo equivalente.
- Un marco de control de calidad con 19 funciones y límites máximos de confianza mecánica. La confianza está limitada por una fórmula fija que comienza en 1.0 y disminuye 0.08 por cada pregunta pendiente y 0.05 por cada fuente sin verificar, por lo que se trata de algo más que una cifra autoinformada. Existen penalizaciones adicionales por dimensión sobre tres categorías de control que abarcan la consistencia lógica, la completitud estructural y los límites de las afirmaciones. Ninguno de los sistemas comerciales comparados cuenta con esto.
oncall_run hasta case_followup, gap_analyzer, finalize_case y dev-agent. Cada enlace cuenta con un esquema de transferencia definido y un árbol de trabajo aislado, por lo que el trabajo se distribuye entre los agentes en lugar de concentrarse en uno solo.Donde se mantiene intencionadamente simple:
- No existe un sistema formal de evaluación. El LLM no se califica mediante pruebas de referencia, y los comentarios recibidos en producción sirven como evaluación.
- No hay panel de control, solo Slack y registros.
- Tampoco hay medidor de costos. El uso de tokens se monitorea de forma aproximada y no se muestra en ningún panel; los costos están limitados por los pasos de confirmación realizados por un humano.
Qué queda y el bucle meta
El sistema sigue en desarrollo, con tareas pendientes relacionadas con la cobertura del bucle autónomo, el medidor de costos y un sistema formal de evaluación, pero ninguna de estas es un obstáculo. Ya cumple con su propósito original: convertir las señales de Slack en código integrado, responder a los comentarios de los revisores durante la noche, autorepararse en los procesos CI y incorporar sus propias mejoras en la siguiente versión.
La verdadera lección es el bucle meta. Cada cicatriz en el sistema de soporte existe porque un ticket real ejerció presión sobre ella. La construcción y el funcionamiento constituyen un único bucle que opera a dos velocidades diferentes. La señal de producción fluye de vuelta a la fuente original del sistema, el agente elabora una solución, un humano la revisa y la integra, y así otro tipo de error se acerca más a ser eliminado.
El sistema aún no está terminado. Un sistema de producción que cree estar listo ha dejado de acumular “cicatrices”, lo cual es otra forma de decir que ya no funciona bajo carga real.
Primero construya el bucle y luego hágalo autónomo. Un sistema autónomo sin una vía de retroalimentación hacia su propio subsistema simplemente genera los mismos errores, pero más rápido y en mayor cantidad; por eso el orden es importante.
Lecturas relacionadas
- Agentes paralelos de LangGraph que revisan solicitudes de pull request mediante webhooks de FastAPI — Cómo un webhook, una cola de Redis y cuatro agentes de LangGraph convierten una solicitud de pull request en una revisión estructurada y puntuada por IA en aproximadamente 30 segundos, con ngrok para pruebas locales.
- Conceptos de LangGraph en lenguaje sencillo: estado, memoria y orquestación — Antes de escribir código: qué significan la orquestación, el tiempo de ejecución, los agentes con estado, la memoria a corto y largo plazo, así como los sistemas multiagente y con intervención humana en LangGraph.