Laboratorio de infraestructura en GitLab usando Antigravity

El pipeline de CI/CD como único Hard Guardrail determinista remoto al trabajar con agentes de IA en GitLab.

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:

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:

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:

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í:

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

En Settings → Merge Requests:

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.

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 en AGENTS.md. El archivo GEMINI.md solo 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