Agentic QAUPEX Galaxy · harnesses
Release · agosto 2026

Una fuente de verdad, tres harnesses que la leen

El repositorio corre igual en Claude Code, OpenCode y Codex (CLI y Desktop). Existe una sola copia de cada instrucción y de cada skill. Donde los harnesses difieren de verdad —formato de MCP, API de hook— cada uno conserva un adaptador fino, versionado y verificado. Nada más está duplicado.

1
cuerpo de instrucciones
3
harnesses soportados
1
store para todas las skills
0
archivos de comando: skill + modo
3
configs MCP en paridad
1
línea en CLAUDE.md: @AGENTS.md
Instrucciones

Ningún nombre de archivo lo descubren los tres

Claude Code carga CLAUDE.md. OpenCode y Codex cargan AGENTS.md. Mantener dos documentos completos y compararlos en CI no elimina el drift: lo detecta tarde, después de que alguien ya trabajó con la copia vieja.

Por eso la relación se invirtió. AGENTS.md es la única entrada al cuerpo de instrucciones y CLAUDE.md quedó en once bytes. Ese @ es la sintaxis de import documentada de Claude Code: trae el archivo al contexto de la sesión. Se eligió por encima de un symlink justamente porque un checkout de Windows lo trata igual que uno de macOS o Linux.

Ese cuerpo además está en capas. AGENTS.md carga en cada sesión y guarda solo lo que ata en cada turno, más un router; cada tema vive en su propio archivo bajo .agents/instructions/ y se lee cuando el router, o una línea ROUTE: que agrega el hook, lo nombra. Los tres hosts siguen leyendo el mismo archivo de entrada; lo que cambió es cuánto carga siempre. El mecanismo: Progressive disclosure.

CLAUDE CODE busca CLAUDE.md OPENCODE busca AGENTS.md CODEX CLI + DESKTOP busca AGENTS.md carga nativa CLAUDE.md una línea · shim generado import @AGENTS.md carga nativa carga nativa AGENTS.md único cuerpo de instrucciones · versionado
Claude Code OpenCode Codex artefacto generado
Dos de los tres llegan directo. El tercero llega por un salto de once bytes que nadie escribe a mano. Ese salto es toda la diferencia entre los harnesses en la superficie de instrucciones.
# CLAUDE.md — el archivo entero, sin recortar
@AGENTS.md

Escribir prosa operativa dentro de CLAUDE.md es drift estructural: bun run agents:compat:check falla si el archivo deja de ser exactamente el shim.

Las cinco superficies

Qué consume cada harness, superficie por superficie

Cinco superficies por tres harnesses. Lo marcado como nativo lo descubre el harness por su cuenta; lo marcado como generado es salida de un script y no se edita a mano.

Superficie Claude Code OpenCode Codex CLI + Desktop
Instrucciones generado
CLAUDE.md un import: @AGENTS.md
nativo
AGENTS.md
nativo
AGENTS.md
Skills generado
.claude/skills symlink en POSIX, junction en Windows
nativo
.agents/skills/
nativo
.agents/skills/
Comandos ninguno skill + modo: /project-context data ninguno skill y modo, en prosa ninguno skill y modo, en prosa
Hook .claude/settings.json UserPromptSubmit, SessionStart, PostToolUse .opencode/plugins/ plugin de proyecto .codex/hooks.json UserPromptSubmit, SessionStart + commandWindows
MCP .mcp.jsonJSON opencode.jsoncJSONC .codex/config.tomlTOML

Codex Desktop no agrega convención

CLI y Desktop consumen exactamente la misma configuración del repositorio. Desktop agrega interfaz, no un segundo contrato ni un directorio extra.

Los adaptadores son delgados a propósito

Solo dos superficies tienen adaptador real: el hook y el MCP. Ahí los harnesses difieren en la API, no en la intención. Instrucciones y skills son la misma fuente vista desde tres lugares, y archivos de comando no hay.

Skills · regla dura

Lo generado nunca se edita a mano

Hay un solo store de skills, .agents/skills/, versionado, con todas las skills adentro (el índice es .agents/skills/REGISTRY.md). Todo lo demás que un harness necesita ver son punteros hacia ahí. Si un puntero se rompe, se regenera; nunca se parchea. De lo generado, solo .claude/skills queda fuera del repositorio (está en .gitignore); CLAUDE.md sí se commitea.

FUENTE · VERSIONADA GENERADOR SALIDA · GITIGNORADA .agents/ skills/ todas las skills · el store canónico hooks/ personality-reinject.mjs se edita acá, siempre bun run agents:compat .claude/skills symlink POSIX · junction Windows .backups/shadowing-commands/ comando que tapaba una skill, movido verifica contra la fuente bun run agents:compat:check corre en repo:check · en pre-push · condicionalmente en pre-commit
La fuente sale una vez del lado izquierdo y vuelve a entrar por abajo como verificación. Una salida editada a mano no sobrevive a ese ciclo: la comparación es contra la fuente, no contra la última versión que alguien guardó.
Artefacto generado Su fuente Se repara con
.claude/skills .agents/skills/symlink o junction bun run agents:compat
CLAUDE.md AGENTS.mdun import de una línea bun run agents:compat

La reparación del alias tiene una salvaguarda: si .claude/skills es un directorio real con contenido propio, se niega a borrarlo y nombra el conflicto. La única excepción es la forma que escribe la CLI de skills —un directorio cuyas entradas son todas punteros hacia .agents/skills— porque ahí no hay nada que perder.

Comandos

Una skill, un modo

Antes cada comando llevaba el workflow entero escrito adentro. adapt-framework era el más largo. Eso era instrucción duplicada que ningún harness fuera de Claude podía descubrir, y que se desincronizaba de su skill en cuanto alguien editaba una de las dos copias. Ahora no queda ni el archivo: invocás la skill por su nombre y le decís el modo. Las mediciones de ese antes y después, con su fecha, están en .context/ADR/ADR-0006-forensic-measurements-ledger.md.

Workflow duplicado dentro de los comandos el workflow entero, copiado
Archivos de comando que quedan ninguno

La forma es la misma en los tres harnesses. Cambia solo cómo la escribís:

# Claude Code: el slash de la skill, el modo como primer argumento
/project-context data
/test-automation explain <target>

# OpenCode y Codex: lo mismo, en prosa
carga project-context, modo data

La regla del primer token: el primer token de $ARGUMENTS que coincide con un modo de la skill es el modo, y el resto se reenvía sin tocar. Si ninguno coincide, la skill pregunta antes de hacer nada.

Invocación Skill Modo
/test-automation explain <target> test-automation explainsellado, solo lectura
… los modos de cada skill están en su SKILL.md; el catálogo es .agents/skills/REGISTRY.md

Nada que mantener por host

Claude Code llega a la skill por su slash, a través de .claude/skills. OpenCode y Codex la cargan por nombre. En ninguno hay un archivo intermedio que se pueda desincronizar.

Un comando no tapa una skill

Un comando del proyecto con el mismo nombre que una skill la taparía. agents:compat:check lo rechaza y bun run agents:compat lo mueve a .backups/shadowing-commands/, donde se puede recuperar.

Hook

Un emisor, tres adaptadores

Lo que el hook agrega a cada prompt vive una sola vez, en .agents/hooks/personality-reinject.mjs. El archivo exporta funciones y además escribe a stdout cuando se lo ejecuta como script. Esas dos formas de consumirlo son las que necesitan los tres harnesses. El contrato de salida de AGENTS.md no va en el emisor: el harness lo carga una vez por sesión, así que no se reinyecta en cada prompt.

Por prompt, en este orden, el emisor escribe:

  • Una línea AGENT IDENTITY: con worktree, sesión y harness, de donde salen los trailers forenses de cada commit.
  • Una línea ORCA: que nombra /orca-orchestration, solo si el binario de orquestación está instalado.
  • Como mucho un aviso de setup: falta .env en el checkout, o es un worktree enlazado que nunca corrió bun run worktree:provision.
  • Líneas ROUTE: read <archivo> con las secciones de instrucciones que el prompt necesita y la sesión todavía no leyó, más una línea ROUTE-OPTIONAL: si sobran. Si nada nuevo coincide, no hay líneas de ruta.
personality-reinject.mjs en .agents/hooks/ · el emisor, una vez exporta sus funciones · imprime a stdout ejecuta ejecuta importa .claude/settings.json UserPromptSubmit · stdout → developer context .codex/hooks.json UserPromptSubmit · stdout → developer context .opencode/plugins/ muta output.system en el lugar
Los verbos de las flechas son la diferencia real: dos harnesses ejecutan el archivo y leen su salida, uno lo importa como módulo. El texto de esas líneas es el mismo en los tres porque nunca se copió.

El validador de contrato rechaza, con nombre y línea:

  • Rutas absolutas personales dentro de cualquier comando de hook, que romperían en la máquina de cualquier otra persona.
  • Un emisor compartido que trate CLAUDE.md como el archivo canónico, que es exactamente la inversión que este release deshizo.
  • Un plugin de OpenCode que reemplace output.system en vez de mutarlo, lo que descartaría el system prompt del harness.
  • La reaparición de los archivos de hook duplicados que se eliminaron.

El adaptador de Codex además carga commandWindows para PowerShell y resuelve la raíz del repositorio con git rev-parse --show-toplevel, en vez de asumir el directorio de trabajo.

Las rutas se rearman solas. Claude Code y Codex registran dos grupos SessionStart, con matcher compact y con clear: al compactar o limpiar el contexto, las secciones ya ruteadas dejan de contar como leídas y el emisor las vuelve a pedir en el prompt siguiente. OpenCode 1 hace lo mismo con experimental.session.compacting. Solo Claude Code corre además el emisor en PostToolUse: ahí no escribe líneas nuevas, solo una vez por prompt una línea ROUTE-PENDING: si una sección ruteada sigue sin leerse.

Hay un segundo hook, separado de este: .agents/hooks/doc-contracts.mjs. Claude Code y Codex lo corren después de cada edición de archivo (PostToolUse). Cuando la edición cae dentro de una región marcada con LINT.IfChange, agrega una línea DOCS: con las páginas que describen ese código, una vez por sesión. El que bloquea es el gate de pre-push y de CI; el hook solo adelanta el aviso al momento de editar. OpenCode no lo registra. Vale solo en el repo del boilerplate (ADR-0016).

Commits

Los trailers de commit son forenses, no atribución

Todo commit que escribe una sesión de agente termina con dos líneas copiadas de la línea AGENT IDENTITY:, en cualquier host. Dicen qué checkout y qué sesión produjeron el cambio, para encontrar la transcripción correcta en un bisect o en un post-mortem. No nombran ninguna herramienta.

docs: rewrite the harnesses page

Worktree: <nombre|primary>
Session: <etiqueta>

Obligatorio

Worktree: y después Session:, al final y sin nada debajo. Si un valor no se pudo resolver se escribe unknown, nunca se adivina ni se omite la clave.

Prohibido

Claude-Session: o cualquier trailer con marca de un harness, un Co-Authored-By: de una IA y cualquier línea "Generated with". En Claude Code, .claude/settings.json deja attribution en blanco.

Cómo se controla

.husky/commit-msg llama a .husky/framework-gates.sh, que corre scripts/check-commit-trailers.ts. Avisa y nunca bloquea: un commit humano, un merge o un arreglo de emergencia siempre pasan.

La regla es la Critical Rule #3 de AGENTS.md; el procedimiento de etiquetas y valores por harness vive en la skill /git-flow-master.

MCP

Un solo set de servidores, tres formatos, paridad semántica

Los servidores que declara .mcp.json existen en las configuraciones de los harnesses que usa el proyecto (las tres en el boilerplate). La comparación no puede ser textual: los archivos ni siquiera comparten lenguaje.

Un solo harness alcanza. La lista harnesses: de .agents/project.yaml dice cuáles usa el proyecto, y cada gate (agents:compat:check, setup:doctor, el instalador, bun sync) revisa solo esos. Sin la lista, o en null, se detectan por los archivos presentes; una lista explícita manda. El boilerplate mismo revisa siempre los tres. Decisión: .context/ADR/ADR-0012-declared-harnesses.md.

.mcp.json opencode.jsonc .codex/config.toml
.mcp.json JSON · Claude Code opencode.jsonc JSONC · OpenCode .codex/config.toml TOML · Codex normalización transporte · comando · args url · env vars · estado una forma común, sin sintaxis compara cada servidor × hosts en uso agregarlo a uno solo es un fallo, no un aviso
Sin el paso del medio, la comparación solo podría decir que los tres archivos son distintos. Con él, dice si describen los mismos servidores.

Las credenciales llegan igual en los tres hosts: cada servidor que necesita valores de .env arranca detrás del mismo loader, varlock run --inject vars --filter A,B -- <server>, que lee .env (o el gestor de secretos que nombre el esquema) al arrancar el servidor y le pasa solo los nombres de su --filter. Las configuraciones commiteadas llevan nombres, nunca valores, y ese --filter es justo el conjunto de dependencias que compara la paridad (.context/ADR/ADR-0011-mcp-env-loader.md).

Falla silenciosa. En ningún host una credencial vacía avisa al arrancar: el servidor sube sin la variable y muere en su primera llamada autenticada. Un 401, un 403 o una tool que falla sin explicación es la señal: parar, nombrar la variable, corregir .env y reiniciar la sesión (Critical Rule #10). El entorno se lee cuando el harness lanza los servidores y no se refresca a mitad de sesión.

Migración

Si venís de la versión anterior

Un proyecto creado cuando las instrucciones vivían en CLAUDE.md y las skills en .claude/skills/ recibe una migración preflight la primera vez que corre bun sync, antes de que se sincronice un solo componente, porque sincronizar primero sobre una estructura vieja es exactamente cómo se pierde trabajo. La migración se ve dentro de esa misma corrida, como un aviso en la página local o en la terminal si corrés con --headless.

bun sync en un proyecto de la era Claude migración preflight antes de sincronizar ningún componente todo resoluble sin adivinar? se evalúa el árbol entero, no archivo por archivo no sí se niega entera no toca nada · nada a medias nombra el archivo que la bloqueó migra, en este orden memoria → AGENTS.md, shim de una línea skills → .agents/skills/, propias incluidas colisión de nombre → se archiva la legacy .template/pre-agents-migration/ recién ahora se sincronizan los componentes
La rama izquierda es la que importa: una migración que no puede resolver un elemento sin adivinar aborta completa en vez de dejar el proyecto mitad viejo y mitad nuevo.

Nada se borra

Todo se mueve o se respalda. Lo que no se puede mover a su lugar canónico queda en .template/pre-agents-migration/, con su contenido intacto.

Es idempotente

Correrla dos veces no hace nada la segunda vez. Un proyecto ya migrado pasa de largo sin escribir.

Límites conocidos

Lo que todavía no está cerrado

Lo que conviene saber antes de apoyarse en esto, dicho como límites y no como notas al pie.

  • El adaptador de OpenCode cubre dos generaciones del plugin. OpenCode 1 entra por server() y usa experimental.chat.system.transform, una API oficial pero marcada como experimental por el proyecto. OpenCode 2 entra por setup(ctx) y registra un hook de sesión context; como no le llega el texto del prompt, no recibe líneas ROUTE: y se queda con la tabla del router. Claude y Codex están sobre APIs de hook estables. Hay que re-verificar al actualizar OpenCode.
  • El junction de Windows está cubierto por tests unitarios de construcción de ruta y de comando, no por una corrida en una máquina Windows.
  • Codex necesita que el repositorio esté marcado como confiable. La configuración de proyecto bajo .codex/ y sus hooks no cargan si no lo está. bun run setup:doctor reporta esa confianza por separado de la corrección de los archivos, porque es estado de runtime que no se puede verificar leyendo el disco.
  • Una variable que exportaste en tu shell gana sobre .env. El harness se abre tal cual (claude, opencode, codex o su app de escritorio) y cada MCP lee .env por su cuenta, pero un valor viejo exportado en el perfil de tu shell tapa el del archivo en todo lo que arranques desde ahí. bun run vars:env:check te dice cuál, sin mostrar el valor.