La primera versión de nuestro revisor con IA señaló 14 problemas "críticos" en un cambio de configuración de una línea. Doce eran imaginarios. Uno era una errata real. Y uno era un bug genuino que nadie más había detectado. Esa proporción —una señal enterrada bajo doce invenciones— es exactamente la razón por la que la mayoría de los equipos apaga el bot en una semana y no lo vuelve a encender.
No queríamos ese bot. Queríamos un segundo revisor que nunca se canse pero que tampoco invente un problema de la nada. Un revisor que ejecute nuestra suite de tests real en lugar de adivinarla, que comente una sola vez con lo que encontró, y que cueste dinero predecible. Esta es la configuración que, tras unas cuantas iteraciones vergonzosas, de verdad aguantó en nuestro CI.
Todo aquí corre sobre código abierto: Octomind, el agente basado en sesiones, y octomind-action, la GitHub Action que lo envuelve. Sin revisor SaaS, sin precios por asiento, sin que el código salga de tu runner salvo hacia el proveedor de modelos que ya pagas.
Por qué un "revisor de IA autoalojado" en vez de un bot SaaS
Hoy existe un mercado saludable de productos de revisión de código con IA alojados. Funcionan. Pero para la revisión de código con IA en CI, tres cosas nos empujaron hacia un revisor de IA autoalojado corriendo dentro de nuestras propias GitHub Actions:
- El revisor debe ejecutar nuestros comandos, no su idea de nuestros comandos. Un bot alojado lee el diff. No ejecuta
cargo clippy --all-targetscon nuestra configuración exacta de lint, ni nuestra suite de integración tras los feature flags correctos. Un revisor que no puede ejecutar las comprobaciones reales del proyecto queda reducido a hacer pattern-matching sobre texto, que es precisamente de donde salen las alucinaciones. - El agente corre en el runner que ya confiamos. Mismo checkout, mismo límite de secretos, mismas reglas de egress de red. Nada del diff va a ningún sitio que no hayamos autorizado ya.
- Es un archivo de configuración en el repo, revisado como cualquier otro. El comportamiento del revisor es un YAML de workflow y unos pocos scripts en
.agents/tools/bajo control de versiones. Cuando se vuelve ruidoso, lo arreglas en un PR. Cuando alguien nuevo pregunta "¿qué comprueba el bot en realidad?", la respuesta esgit log.
Si leíste nuestro artículo anterior sobre por qué los MCP personalizados pertenecen a tu repo, esta es la misma filosofía apuntada al CI: las herramientas que dirigen a tu agente viven junto al código sobre el que operan.
Cómo se comporta octomind run de forma no interactiva
Todo empieza con un hecho sobre el agente: octomind run cambia a modo no interactivo en cuanto le das un --format o le pasas stdin por tubería. Según la definición de la CLI:
--format <FORMAT> Output format: plain or jsonl. When set, runs
non-interactively (reads input from stdin).
Por dentro, el runtime resuelve un modo de salida a partir del flag y de si stdin es una terminal:
| Invocación | Modo |
|---|---|
octomind run en una terminal |
Interactive (prompts, colores, animaciones) |
echo "..." | octomind run (por tubería) |
NonInteractive |
octomind run --format plain |
NonInteractive |
octomind run --format jsonl |
Jsonl (un objeto JSON por línea) |
El CI es el caso de la tubería, y --format jsonl es el que quieres ahí. Suprime el adorno interactivo y emite un flujo de eventos estructurados —mensajes del asistente, un evento final de coste— que un workflow puede parsear sin raspar prosa. Esa es la razón entera de que el agente sea automatizable: no hay ningún prompt oculto esperando a que un humano pulse y.
Puedes probarlo a mano para verlo:
echo "Review the staged diff and report only real defects." \
| octomind run developer:rust --format jsonl
El último evento assistant lleva el texto de la revisión; un evento cost final lleva los totales de tokens y dólares. Ese es el mismo flujo que parsea la GitHub Action.
La Action y sus inputs reales
octomind-action ofrece dos puntos de entrada. Usamos ambos para trabajos distintos:
muvon/octomind-action@v1(alias demuvon/octomind-action/run@v1) ejecuta una única sesión deoctomind runa partir de un prompt.muvon/octomind-action/workflow@v1ejecuta unoctomind workflowde varios pasos desde un archivo TOML.
Para la revisión de código, la sesión única run es suficiente. Estos son sus inputs reales —copiados de action.yml, no inventados—:
| Input | Por defecto | Qué hace |
|---|---|---|
prompt |
(requerido) | La tarea/mensaje enviado a octomind |
role |
el del config | Tag de rol/agente, p. ej. developer:rust |
model |
— | Override de modelo, p. ej. openrouter:anthropic/claude-sonnet-5 |
name |
— | Sesión con nombre (crear o reanudar) |
resume |
— | Reanudar una sesión con nombre |
resume_recent |
false |
Reanudar la sesión más reciente del cwd |
sandbox |
false |
Restringir las escrituras al directorio de trabajo |
version |
latest |
Versión de Octomind a instalar |
tap |
— | Añadir un tap antes de ejecutar |
config |
— | Ruta a un archivo de config de octomind |
comment |
none |
Modo de comentario en PR: full, compact o none |
github_token |
${{ github.token }} |
Token usado para publicar el comentario en el PR |
Y los outputs que puedes leer en pasos posteriores:
| Output | Qué lleva |
|---|---|
result |
Último mensaje del asistente (la revisión en sí) |
session_id |
ID de sesión, para reanudar en un paso posterior |
cost |
{"tokens": N, "cost": N} |
raw_output |
Flujo JSONL completo |
exit_code |
Código de salida del proceso |
Dos de estos son decisivos para un revisor. comment: compact publica la revisión como un bloque plegable <details> en el PR —un comentario, plegable, con el coste en un pie <sub>— en vez de un muro de texto. Y cost te deja poner el precio de cada revisión en los logs, lo cual importa más de lo que parece.
Conectar el lint y test reales del equipo mediante .agents/tools/
Esta es la parte que separó "de verdad aguantó" de "apagado en una semana". Un revisor que solo lee texto inventa problemas porque el texto es todo lo que tiene. Dale la capacidad de ejecutar tus comprobaciones reales y dejará de adivinar.
Octomind descubre herramientas locales del proyecto en <workdir>/.agents/tools/<name> —scripts con shebang y una cabecera de comentario que se convierte en el esquema de la herramienta—. Aparecen ante el modelo como un servidor MCP local, sin proceso de servidor aparte, sin registro. Cubrimos la mecánica en los MCP personalizados pertenecen a tu repo; aquí va el punto relevante para CI: los mismos scripts que usan localmente los agentes de tus desarrolladores son los que ejecuta el agente revisor en CI, porque están commiteados en el repo.
Así que pusimos los comandos exactos del equipo detrás de herramientas con nombre.
.agents/tools/lint:
#!/usr/bin/env bash
# @description Run the project linter exactly as CI does. Returns warnings and errors.
set -euo pipefail
cd "$OCTOMIND_WORKDIR"
cargo clippy --all-targets --all-features -- -D warnings
.agents/tools/test:
#!/usr/bin/env bash
# @description Run the test suite. Defaults to the fast unit set.
# @param scope string One of: unit, all (default: unit)
set -euo pipefail
cd "$OCTOMIND_WORKDIR"
scope="${OCTOMIND_PARAM_SCOPE:-unit}"
case "$scope" in
unit) cargo test --lib ;;
all) cargo test --all-targets ;;
*) echo "Unknown scope: $scope" >&2; exit 2 ;;
esac
Ahora el prompt del revisor puede decir "ejecuta lint y test, y reporta solo defectos que puedas sustentar". El agente llama a la herramienta con nombre, obtiene un código de salida real y una salida real, y ancla su revisión en lo que el proyecto hace de verdad. Una afirmación como "esto fallará clippy" deja de ser una adivinanza: el agente ejecutó clippy. Si clippy pasó, la afirmación nunca se escribe.
Esta es la mayor palanca contra el modo de fallo del problema-alucinado. El modelo es malo prediciendo si tu código compila. Es perfectamente bueno leyendo la salida del comando que lo compiló.
Acotar qué puede tocar el revisor
Un revisor debe leer, ejecutar comprobaciones y reportar. No debe editar archivos, hacer push ni abrir PRs. Los roles de Octomind controlan esto con server_refs (qué servidores de herramientas ve el rol) y allowed_tools (qué herramientas dentro de ellos). La forma, según las plantillas de rol que vienen incluidas:
[[roles]]
name = "reviewer:strict"
system = """
You are a code reviewer. Run the project's lint and test tools to ground
every claim. Report only defects you can substantiate with tool output or a
specific line. If you find nothing, say so in one sentence. Never edit files.
"""
temperature = 0.1
[roles.mcp]
server_refs = ["filesystem"]
allowed_tools = ["view", "lint", "test"]
Unas cuantas decisiones deliberadas ahí. temperature = 0.1 porque un revisor debe ser aburrido y repetible, no creativo. allowed_tools lista solo herramientas de lectura y comprobación —ni escritura por shell, ni edición— para que aunque el modelo decidiera "arreglar" algo, no tenga herramienta para hacerlo. Y las últimas dos frases del system prompt son el control de ruido: reporta solo lo que puedas sustentar; si no encuentras nada, dilo en una frase. Esa única instrucción llevó nuestro comentario medio de catorce "problemas" a, en un PR limpio, "No se encontraron defectos".
Como cinturón y tirantes extra, el input sandbox: true de la Action restringe cualquier escritura al directorio de trabajo, de modo que una herramienta que sí necesite escribir un archivo temporal puede hacerlo, pero nada escapa del checkout.
Permisos y seguridad en una ejecución no interactiva
Lo que la gente teme de un agente en CI son las aprobaciones. En una sesión interactiva se te pediría confirmación antes de ejecutar una herramienta. En CI no hay nadie a quien preguntar, y ese es el punto: en modo no interactivo no hay puerta y/n que cuelgue el job. La seguridad no viene de un humano en el bucle, viene de que el bucle sea estrecho:
- El
allowed_toolsdel rol es la lista blanca para las herramientas de servidores MCP. El revisor no puede llamar a una herramienta de servidor que no esté listada — un límite estático, no un juicio en tiempo de ejecución del que se pudiera convencer al modelo. La excepción son los scripts locales de.agents/tools/, que siempre quedan expuestos: su límite es lo que confirmas en ese directorio, y una revisión de PR ve cada cambio allí. sandbox: truemantiene las escrituras dentro del checkout.- Los secretos quedan en el entorno del runner, nunca en el repo. Los scripts de
.agents/tools/leenACME_TOKENy compañía del entorno; el script está commiteado, el secreto es un secret de GitHub Actions. Si falta un secreto, la herramienta sale con código distinto de cero y un mensaje claro en lugar de inventar un resultado. github_tokenestá acotado a comentar. El${{ github.token }}por defecto publica el comentario del PR; no se le entrega al modelo como herramienta.
El radio de impacto del revisor es: leer archivos, ejecutar dos comandos en lista blanca, escribir un comentario en el PR. Ese es un límite sobre el que puedes razonar, que es más de lo que puedes decir de "el modelo prometió que no tocaría nada".
El workflow que aguantó
Este es el job de revisión real, anclado por completo en los inputs de arriba:
name: AI Code Review
on:
pull_request:
types: [opened, synchronize]
permissions:
contents: read
pull-requests: write # needed to post the review comment
jobs:
review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # so the agent can diff against the base
- uses: muvon/octomind-action@v1
with:
role: reviewer:strict
model: openrouter:anthropic/claude-sonnet-5
sandbox: true
comment: compact
prompt: |
Review the changes on this branch against the base.
Run the `lint` and `test` tools to ground your findings.
Report ONLY defects you can substantiate with tool output or a
specific changed line. Group by severity. If nothing is wrong,
reply in one sentence. Do not restate the diff.
env:
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
Notas sobre las decisiones:
role: reviewer:strictaporta la temperatura baja, la lista blanca de herramientas y el system prompt de "sustentar o callar". El input del prompt es la tarea; el rol es la personalidad y las barandillas. Mantenlos separados para ajustarlos por separado.model:está fijado, no se deja a la deriva. Fijar el modelo es la mitad de tu control de coste y casi toda tu reproducibilidad. Lo fijamos a un modelo de clase Sonnet y solo lo cambiamos a propósito.comment: compactda un comentario plegable por ejecución. Ensynchronize(cada push al PR) obtienes una revisión nueva sin que el hilo se convierta en un scroll.OPENROUTER_API_KEYes el único secreto que necesita el job. Octomind habla con OpenRouter, Anthropic, OpenAI, DeepSeek, Google Vertex, AWS Bedrock y Cloudflare; la variable de entorno que definas elige el proveedor.
Ese es el revisor entero. Un job, una Action, dos scripts de herramienta commiteados, una definición de rol.
Mantener el coste acotado
Un agente que puede ejecutar herramientas puede, en principio, hacer bucles para siempre y facturar para siempre. En la práctica lo acotamos con unas cuantas palancas sosas y efectivas:
- Fija el modelo. Un modelo de clase Sonnet fijado tiene un coste por token predecible. "Lo último y lo mejor" es como consigues una factura sorpresa.
- Dispara en los eventos correctos. Solo
openedysynchronize. No en cada comentario, no en PRs en borrador (filtra conif: github.event.pull_request.draft == falsecuando quieras). - Lee el output
costy registralo. Cada ejecución emitecostcomo{"tokens": N, "cost": N}. Vuélcalo al resumen del job. Una vez que el precio de cada revisión está en tus logs de Actions, el coste deja de ser una sensación y se convierte en un número al que puedes ponerle un umbral.
- run: echo "Review cost: ${{ steps.review.outputs.cost }}" >> "$GITHUB_STEP_SUMMARY"
El mayor motor de coste son las herramientas, no el prompt. Un revisor que ejecuta tu suite de integración completa en cada push es caro porque la suite es cara, no porque el agente lo sea. Nuestra herramienta test usa por defecto el set rápido de unidad exactamente por esto; el agente solo recurre a scope: all cuando el diff toca algo que lo amerita, y aun así podemos limitarlo a nivel de rol.
Los problemas, con honestidad
No determinismo. Dos ejecuciones sobre el mismo diff pueden redactar la revisión de forma distinta, y de vez en cuando una encuentra algo que la otra no. temperature = 0.1 reduce esto mucho pero no lo elimina. El arreglo no es perseguir el determinismo, es tratar la revisión de IA como una comprobación consultiva entre varias. Nunca bloquea el merge por sí sola. Eso lo hacen un humano y las comprobaciones reales de CI.
El ruido es un problema de prompt, no de modelo. Nuestro primer revisor era ruidoso porque le pedimos "revisa el código", que un modelo entusiasta lee como "encuentra catorce cosas". La cura fueron las dos frases del system prompt: sustenta cada afirmación; si no encuentras nada, dilo en una frase. Más las herramientas: la mitad del ruido era el modelo adivinando resultados que podría simplemente haber ejecutado. Si tu revisor es ruidoso, arregla el prompt y dale la capacidad de comprobar, antes de culpar al modelo.
Secretos en la salida de las herramientas. Una herramienta que vuelca el entorno o logs verbosos puede filtrar un secreto directo a un comentario del PR. Mantén la salida de las herramientas ajustada: devuelve el resultado de lint/test, no el mundo. Lo aprendimos cuando un runner de tests verboso hizo echo de un connection string al cuerpo de la revisión. Ahora las herramientas imprimen resultados, no el entorno.
El diff contra la base. Usa fetch-depth: 0 en el checkout o el agente no puede comparar contra la rama base y acaba "revisando" el archivo entero. Error barato, síntoma molesto.
Es un segundo revisor, no el revisor. El día que empieces a confiar en que lo atrape todo es el día que se le escapa el que importa. Es una primera pasada incansable. Los humanos cansados siguen siendo dueños del merge.
Preguntas frecuentes
¿El agente necesita acceso de escritura a mi repo?
No. El rol del revisor lista solo herramientas de lectura y comprobación en allowed_tools, sandbox: true confina cualquier escritura al checkout, y github_token se usa solo para publicar el comentario. Al modelo nunca se le entrega una herramienta de push o edición.
¿Puedo ejecutar el lint/test real del equipo en vez de uno genérico?
Ese es el punto entero. Pon tus comandos exactos en .agents/tools/lint y .agents/tools/test, commitéalos, y el agente los ejecuta en CI. Los mismos scripts que usan los agentes locales de tus desarrolladores. Ver los MCP personalizados pertenecen a tu repo.
¿Cómo leo el coste de una revisión?
La Action expone un output cost ({"tokens": N, "cost": N}) y el JSONL completo vía raw_output. Haz echo de cost a $GITHUB_STEP_SUMMARY y estará en el log de cada ejecución.
¿Y si quiero un pipeline de varios pasos, no una sola revisión?
Usa muvon/octomind-action/workflow@v1 con un archivo TOML: reúne contexto en un paso, revisa en el siguiente, resume en un tercero. Mismo entorno de proveedor, mismo plumbing de comentario en PR, más un input dry_run para validar el pipeline sin gastar tokens.
¿Qué proveedores de modelos funcionan?
El que ya pagues: OpenRouter, Anthropic, OpenAI, DeepSeek, Google Vertex, AWS Bedrock, Cloudflare. Define la API key correspondiente en env y fija el modelo con el input model.
El cambio
El instinto con un revisor de IA es hacerlo más listo —mejor modelo, prompt más largo, más ingenioso—. Lo que de verdad aguantó para nosotros fue hacerlo más estrecho y más anclado. Menos herramientas, todas nuestras. Una temperatura baja. Un system prompt cuyo trabajo entero es mantener al modelo callado salvo que pueda demostrar el punto. Y la capacidad de ejecutar las comprobaciones reales, para que razone desde la salida de cargo clippy en vez de desde una corazonada sobre lo que cargo clippy podría decir.
El resultado no es un revisor genio. Es uno fiable: nunca se cansa, ejecuta las mismas comprobaciones cada vez y —la parte que más costó ganar— dejó de alucinar problemas de la nada. Para el conjunto más amplio de cosas que salieron alrededor de esto, el resumen de junio de 2026 tiene el resto.
Cablearlo primero en un repo de bajo riesgo. Mira unas cuantas revisiones. Ajusta el prompt dos veces. Luego déjalo sentado tranquilo en tus PRs siendo el segundo par de ojos que nunca parpadea.
— Don
Octomind y octomind-action son código abierto bajo Apache-2.0. Si tu revisor es ruidoso, el arreglo casi siempre es el prompt y las herramientas; y si no lo es, abre un issue con el JSONL y le echaremos un vistazo.



