Esta serie empezó con el desarrollo de Monster High. La excusa fue mi hija: tenía un catálogo de Pokémon que había hecho en un ejercicio siguiendo un curso de Next y, cuando era muy pequeña, se me ocurrió que podía servirle para que se familiarizase con el manejo del ratón. Ya pasados los años, pensé que si le hacía algo con los personajes que le gustaban de verdad, podría motivarla a leer. De ahí salió todo lo que hemos visto en los artículos anteriores.
Ahora ha descubierto Pokémon. Quiere ver evoluciones, conocer tipos, descubrir más personajes. El catálogo simple que conoce ya no le vale.
El repositorio del catálogo de Pokémon lleva años en GitLab, la excusa perfecta para aplicar lo que habíamos visto en el artículo Infraestructura de agentes autónomos, pero esta vez en una plataforma distinta a GitHub.
Eso cambia más cosas de lo que parece.
El nexo
En el artículo anterior cerré con una nota al margen:
Los principios se aplican exactamente igual si trabajas con cualquier otra plataforma.
Era cierto en teoría. Este artículo es la prueba en la práctica. Y la práctica tiene matices.
El laboratorio
El stack del laboratorio:
- Repositorio: GitLab (tier Free)
- IDE: Antigravity v2.x
- Gestor de paquetes: Bun (migrado desde Yarn)
- Deploy: Vercel, desvinculado del repositorio y controlado desde el pipeline
El proyecto tiene las dependencias bastante desactualizadas (Next.js 12, React 18, TypeScript 4.7). Eso queda para otro ciclo. El objetivo de este laboratorio era definir la capa de seguridad para el agente, trasladando a GitLab la infraestructura de gobernanza que vimos en el artículo anterior.
El único cambio respecto al repositorio original fue migrar de Yarn a Bun como gestor de
paquetes. Al retomar el
proyecto desde cero, con una instalación limpia de dependencias, era el momento natural para hacerlo. El pipeline
refleja esa decisión: usa oven/bun:1-alpine como imagen base y bun.lock como clave de
caché.
Seguridad en el deployment
El primer paso, en esto no hay diferencia, cortar la integración automática de Vercel. Si no se hace, Vercel despliega en paralelo al pipeline, completamente fuera de nuestro control.
El proceso:
- 1. Desvincular el repositorio desde el panel de Vercel (Settings → Git → Disconnect)
- 2. Generar un token en Vercel (Settings → Tokens) con nombre descriptivo, por ejemplo
GITLAB_CI_PIPELINE - 3. Obtener los IDs del proyecto. Se pueden obtener desde la interfaz de Vercel o ejecutando
vercel linken local, que genera.vercel/project.jsonconorgIdyprojectId - 4. Añadir las variables en GitLab (Settings → CI/CD → Variables):
VERCEL_TOKEN→ siempre Masked, Protected (porque solo se despliega desdemain)VERCEL_ORG_IDyVERCEL_PROJECT_ID→ sin enmascarar
Una vez desconectado Vercel, el único camino al deploy es el pipeline.
El pipeline
Para materializar la gobernanza en GitLab, diseñé un archivo .gitlab-ci.yml estructurado en
dos
fases:
- La primera valida el código en cada Merge Request (MR). Si falla, el merge queda bloqueado.
- La segunda despliega a producción, pero solo cuando un humano pulsa el botón.
stages:
- validate
- build
.bun_cache: &bun_cache
cache:
key:
files:
- bun.lock
paths:
- node_modules/
lint-and-typecheck:
stage: validate
image: oven/bun:1-alpine
<<: *bun_cache
before_script:
- bun install --frozen-lockfile
script:
- bun run lint
- bun x tsc --noEmit
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
deploy-production:
stage: build
image: oven/bun:1-alpine
<<: *bun_cache
environment:
name: production
before_script:
- bun install --frozen-lockfile
script:
- bun x vercel pull --yes --environment=production
- bun x vercel build --prod
- bun x vercel deploy --prebuilt --prod --yes
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
when: manual
Dos decisiones deliberadas aquí:
-
La caché usa el hash de
bun.lockcomo clave. Si se añade un paquete, el lockfile cambia, la clave cambia, el pipeline reinstala todo. Si no cambia nada, reutiliza losnode_modulescacheados. Automático, sin configuración adicional. -
El stage
validatesolo corre en Merge Requests. Amainno se puede hacer push directo, solo merges. Y el MR ya habrá validado el código. Ejecutar validate de nuevo al mergear sería gastar minutos de runner en algo ya comprobado.
El when: manual en deploy es la pieza central del Human in the loop
(HITL), el mismo principio que aplicamos en el artículo anterior. Sin click humano,
no hay deploy. Ningún agente puede saltarse eso, el control está definido en la infraestructura.
La protección de ramas
El pipeline solo funciona como guardrail si nadie puede saltárselo. Para eso hay que cerrar el acceso directo a
main desde la configuración de GitLab.
Settings → Repository → Branch Rules → main → View details
- Allowed to merge: Maintainers
- Allowed to push and merge: No one
- Allow force push: desactivado
En Settings → Merge Requests:
- Pipelines must succeed: activado
- All threads must be resolved: activado
Estas dos opciones son las que provocan que si el job de lint o type-check falla, el botón de merge queda desactivado y por tanto el pipeline queda bloqueado. Si el pipeline falla, el merge queda bloqueado para cualquiera.
Dónde choca GitLab con GitHub
Aquí es donde el laboratorio se vuelve interesante (y honesto).
En el artículo anterior, la infraestructura descansaba sobre tres pilares: CI/CD, CODEOWNERS con aprobación obligatoria, y protección de entornos. Los tres funcionaban juntos para hacer la gobernanza determinista.
En GitLab Free, solo queda el primero.
- CODEOWNERS requiere Premium. El archivo puede existir en el repositorio, pero el toggle que lo convierte en aprobación obligatoria solo aparece en Branch Rules si tienes Premium. En Free, el archivo es documentación.
- Las Approval Rules tampoco están en Free. En GitHub, puedes exigir revisores concretos, invalidar aprobaciones cuando hay nuevos commits, o evitar que el autor apruebe su propio código. En GitLab Free, ninguna de estas opciones existe en la interfaz.
- Los entornos protegidos con aprobaciones de despliegue son Premium. El entorno existe, pero no puedes configurar quién debe aprobar antes de que el deploy se ejecute.
Por tanto, el pipeline con when: manual es el único Hard Guardrail determinista
remoto como
HITL real.
La consecuencia práctica: la protección contra que el agente modifique sus propios archivos de gobernanza depende
de
los Soft Guardrails (las instrucciones en AGENTS.md y GEMINI.md) más
que de mecanismos técnicos infranqueables.
Exactamente la limitación que el artículo anterior describía como insuficiente. Es una limitación real. Y es el tipo de limitación que vale la pena documentar.
La gobernanza del agente: Antigravity
La configuración del agente la dividí en tres capas, cada una con un propósito distinto.
AGENTS.md en la raíz — identidad universal, legible por cualquier agente en
cualquier IDE. Define el idioma, el comportamiento y los estándares generales, pero delega el detalle metodológico
en archivos de reglas bajo .agents/rules/: el ciclo TDD, la metodología XP, los estándares de código
y de testing. Esa separación es una buena práctica en sí misma, mantiene el archivo raíz legible y permite
actualizar cada regla de forma independiente.
GEMINI.md en la raíz — overrides específicos de Antigravity, pensados para
extender
AGENTS.md cuando el comportamiento del IDE lo permite. Cubre la asignación de modelos según el tipo
de tarea, una política de ejecución que distingue entre Plan Mode y Fast Mode, una Terminal Policy con comandos
explícitamente permitidos y comandos que requieren confirmación humana, y reglas de gobernanza GitLab: no
pushear
directamente a
main, no tocar .gitlab-ci.yml, y no aprobar ni triggerear el deploy de forma autónoma.
La incógnita es si Antigravity respeta estas instrucciones cuando entran en conflicto con su configuración interna. Si no lo hace, la solución es configurar esas restricciones directamente en el IDE, especialmente la Terminal Policy, donde Antigravity ofrece modos nativos que sí actúan como Hard Guardrail local, y consolidar el resto enAGENTS.md. El archivoGEMINI.mdsolo tiene sentido si el IDE lo respeta como capa de overrides.
openspec/config.yaml — contexto para OpenSpec. Esto ya lo vimos en el artículo sobre specs.
Define el stack técnico, las
restricciones del proyecto, y las reglas por tipo de artefacto (proposal, specs, design, tasks) para gestionar
el ciclo de cambios reemplazando el flujo manual de
reasoning → planning → action.
Lo que me llevé
GitLab (tier Free) es un entorno de gobernanza incompleto comparado con GitHub. No es una
opinión,
es una
constatación
técnica: sin CODEOWNERS obligatorio y sin Approval Rules, la Frontera A que describí en el artículo anterior queda
parcialmente abierta. En GitHub puedes delegar parte de la supervisión a la infraestructura. En
GitLab, la única capa HITL que provee la infraestructura es el pipeline. Aunque el deployment
permanece protegido, queda sin cubrir el margen de maniobra que un agente puede aprovechar si alucina o decide
saltarse una restricción e introducir cambios no deseados en main.
El siguiente paso es empezar a construir. Mi hija lleva tiempo esperando las evoluciones, y a mí me permitirá,
mediante una práctica deliberada, comprobar cuánto pesa realmente trabajar con esta seguridad incompleta así como
observar qué tal funcionan los overrides de Antigravity definidos en GEMINI.md. Lo veremos
en el
siguiente artículo.
Explora el código
Ver Repositorio en GitLab →Conceptos aplicados
-
Soft Guardrails (Controles Blandos): Instrucciones y reglas de comportamiento en lenguaje natural (como en
AGENTS.mdoGEMINI.md) que orientan al modelo pero dependen de su alineación semántica. - Hard Guardrails (Controles Duros): Salvaguardas y restricciones técnicas deterministas impuestas a nivel de infraestructura (como los pipelines de CI/CD obligatorios) que el agente de IA no puede eludir.
-
HITL (Human-in-the-Loop): Punto de control en el flujo de trabajo (como la acción
when: manualpara el despliegue) que exige la intervención y aprobación explícita de un desarrollador humano antes de ejecutar acciones críticas. - CODEOWNERS: Mecanismo de asignación de propietarios de código para requerir revisiones obligatorias en cambios de archivos críticos (en GitLab Free actúa como documentación al requerir versión Premium para forzar su aprobación).
- Merge Request (MR): Solicitud de integración de cambios de rama en GitLab donde se ejecutan los pipelines de validación (linting, type-checking) antes de permitir la fusión del código.
-
Branch Protection (Protección de ramas): Configuración de seguridad a nivel de repositorio que restringe quién puede realizar push directo o merge sobre ramas protegidas como
main.