En la práctica: creando un proyecto Terraform con un AI Agent de principio a fin
En el post anterior, diseccionamos la anatomía de un AI Agent para Terraform: la estructura de archivos, el modelo de guides y sensors, el steering loop. Fue necesaria un poco de teoría para llegar hasta aquí. Pero ¿cómo funciona esto cuando te sientas frente a la terminal con un request real?
En este post voy a hacer un paso a paso: tomar un pedido de infraestructura, pasar por cada stage del proceso y mostrar cómo opera el agente en cada momento. El proyecto es simple a propósito (una VPC con subnets y security groups en AWS) porque el objetivo no es impresionar con complejidad de Terraform. Es mostrar cómo el proceso y los mecanismos del agente funcionan juntos en una entrega real.
Voy a simular las interacciones, mostrando qué consulta el agente, qué produce y cuándo se detiene para pedir ayuda. Al final vas a tener una idea clara de cómo funciona esto en el día a día.
El escenario
Llega el siguiente request del equipo de desarrollo:
“Necesitamos una VPC aislada para el ambiente de staging del proyecto Atlas. Tres subnets privadas, tres públicas, en AZs diferentes. Un security group para los ALB y otro para las instancias de la aplicación. Cuenta AWS: 123456789012. Región: us-east-1.”
Simple, directo, bien definido. Es el tipo de tarea que todo el mundo ya hizo decenas de veces y que, por ser “fácil”, es donde más se acumulan los descuidos.
Flujo de trabajo
Antes de sumergirnos en cada etapa, vale la pena conocer cada paso del trabajo. El proceso definido en PROCESS.md es una secuencia de seis stages, ejecutados en orden, sin saltos. Cada stage tiene una responsabilidad clara y una puerta de salida: solo se avanza al siguiente cuando el actual cumple sus criterios. Es ese encadenamiento el que garantiza que el agente entienda antes de diseñar, diseñe antes de construir y valide antes de aplicar.
Intake
La puerta de entrada. El objetivo no es escribir código, es entender el problema y restaurar contexto: leer HANDOFF.md y MEMORY.md, relevar restricciones conocidas y traducir un pedido vago en una lista clara de criterios de aceptación. Es aquí donde el agente pregunta cuando no sabe, en lugar de adivinar.
Design
Define cómo resolver antes de ponerse a escribir. El agente propone la estructura de archivos, toma decisiones arquitectónicas (registrando el porqué en MEMORY.md) y descompone el trabajo en tasks verificables en TASKS.md. Etapa de bajo costo y alto impacto: cambiar una decisión en un documento es trivial, cambiarla después de crear 20 recursos es caro.
Build
Donde nace el código, guiado por los guides y validado por sensors en cada paso. El agente trabaja una task por vez y ejecuta un micro-loop de autocorrección (escribe → formatea → valida → corrige) antes de avanzar. El feedforward y el feedback computacional (fmt, validate) se combinan para producir código que sigue las convenciones del equipo sin supervisión línea por línea.
Validate
La puerta antes de tocar la infraestructura real. Va más allá del terraform validate del Build y examina lo que va a suceder de hecho: ejecuta terraform plan y verifica contra las Pre-Apply Guards. ¿Aparece algún destroy inesperado? ¿Se recreará algún recurso? ¿El costo está dentro de lo esperado? Nada avanza sin pasar por ese filtro.
Deploy
La ejecución propiamente dicha, con una regla de oro: aplicar solo el plan que fue aprobado y verificar enseguida. Un apply que devuelve “éxito” todavía puede haber dejado drift, así que el agente ejecuta un plan inmediatamente después para confirmar que el estado real coincide con el deseado.
Close
El stage que la mayoría de los procesos olvida, y es justamente el que hace que el sistema mejore con el tiempo. El agente confirma los criterios de aceptación, deja HANDOFF.md en un estado retomable y decide qué de lo que aprendió merece convertirse en conocimiento permanente. Es el cierre del steering loop.
Con el mapa en la mano, vamos a recorrer cada stage en detalle, empezando por el Intake.
Stage 1 — Intake, entendiendo el pedido
El Intake es la puerta de entrada del proceso. El objetivo aquí no es escribir código, es entender el problema y cargar contexto antes de cualquier decisión. Es el momento en que el agente restaura la memoria de trabajo: qué ya se hizo, qué aprendió el equipo, qué restricciones existen. Saltarse esta etapa es lo que hace que un agente (o una persona) repita errores que ya se resolvieron antes. Un buen intake transforma un pedido vago en una lista clara de criterios de aceptación.
El agente empieza leyendo HANDOFF.md (vacío, proyecto nuevo) y MEMORY.md. En MEMORY.md encuentra una entrada relevante de un proyecto anterior:
| 2025-07-15 | projeto-orion | VPC com 3 AZs us-east-1 tem AZ us-east-1e com capacidade limitada para alguns instance types | Evitar us-east-1e para subnets de compute | active |
Esto es feedforward en acción. Antes de escribir una línea de código, el agente ya sabe de una restricción que alguien descubrió hace meses.
A continuación, el agente consulta FEEDFORWARD.md y encuentra en la sección “Known Pitfalls”:
| FF-003 | VPC creation | Subnets sem tags Name causam confusão no console | Sempre tagar subnets com Name incluindo AZ | medium |
Con esa información, el agente define scope y acceptance criteria:
- VPC con CIDR /16
- 3 subnets públicas + 3 privadas (evitando us-east-1e)
- Security group para ALB (puerto 443 inbound, abierto)
- Security group para app (puerto 8080 inbound, source = SG del ALB)
- Todas las subnets con tag Name incluyendo la AZ
- Tags estándar en todos los recursos
Le pregunta al humano: “¿El CIDR debe ser 10.1.0.0/16 o hay conflicto con VPCs existentes en la cuenta?”
Esto es el agente reconociendo los límites de lo que sabe. No adivina: pregunta.
El humano responde: “Usa 10.20.0.0/16, los ranges 10.0-10.19 ya están en uso.”
El agente lo registra en HANDOFF.md y avanza.
Con el pedido ya traducido en criterios de aceptación claros y sin ambigüedades pendientes, el agente tiene todo lo que necesita para salir del “qué” y entrar en el “cómo”. Es la señal para el Design.
Stage 2 — Design, definiendo la solución
Con el problema entendido, el Design es donde se decide cómo resolver antes de ponerse a escribir. Aquí el agente define la estructura de archivos, las decisiones arquitectónicas y el desglose del trabajo en tasks. Es una etapa de bajo costo y alto impacto: cambiar una decisión de diseño en un documento es trivial, cambiarla después de crear 20 recursos es caro. El punto clave es que cada decisión relevante queda registrada con su porqué en MEMORY.md, para que nadie (humano o agente) tenga que adivinar meses después por qué las cosas se hicieron de determinada forma.
Con los requisitos claros, el agente propone la estructura:
environments/staging/atlas/
├── main.tf — VPC, subnets, route tables
├── security.tf — Security groups
├── variables.tf — Inputs
├── outputs.tf — VPC ID, subnet IDs, SG IDs
├── providers.tf — AWS provider pinado
├── backend.tf — S3 state
└── terraform.tfvars — Valores para staging
Decisiones tomadas (registradas en MEMORY.md):
| 2025-08-29 | atlas | Subnets criadas com for_each sobre as AZs (chave = AZ) em vez de count | Evita recriação em cascata ao remover/reordenar AZs; segue o anti-pattern do FEEDFORWARD | active |
| 2025-08-29 | atlas | SG da app referencia SG do ALB por ID, não por CIDR | Mais seguro e resiliente a mudanças de IP | active |
El agente verifica contra FEEDFORWARD.md y no detecta ningún anti-pattern en el enfoque propuesto. La sección de design principles refuerza: “explicit over implicit, declare dependencies, don’t rely on ordering.”
Se completa TASKS.md:
| 1 | Criar VPC e subnets com routing | Build | high | terraform validate passa, plan mostra 17 recursos |
| 2 | Criar security groups com regras | Build | high | terraform validate passa, plan mostra 5 recursos |
| 3 | Configurar outputs | Build | medium | Outputs exportam vpc_id, subnet_ids, sg_ids |
| 4 | Validar plan completo | Validate | high | Plan limpo, sem destroys inesperados |
Fíjate que el diseño ya incorpora los criterios de verificación de cada task (cuántos recursos debe mostrar el plan, qué necesitan exportar los outputs). Esto no es un detalle: son esos criterios los que le dan al agente un objetivo concreto en el Build. Sin ellos, “crear la VPC” sería vago; con ellos, cada task tiene una definición de terminado que un sensor puede verificar. Con el plan de trabajo descompuesto y verificable, empieza la construcción.
Stage 3 — Build, escribiendo el Terraform
El Build es donde nace el código, pero con una diferencia importante respecto a “solo pedirle al LLM que genere”: el agente escribe guiado por los guides y validado por sensors en cada paso. Trabaja una task por vez y ejecuta un micro-loop de autocorrección (escribe → formatea → valida → corrige) antes de avanzar. No necesita acertar a la primera; necesita tener un loop rápido que capture el error temprano. Es aquí donde el feedforward (lo que ya sabe) y el feedback computacional (fmt, validate) se combinan para producir código que sigue las convenciones del equipo sin supervisión línea por línea.
Vale la pena entender por qué el agente insiste en una task por vez. Un LLM tiende a generar todo de una vez cuando le pides “crea la VPC completa”: VPC, subnets, route tables, gateways y security groups en un único bloque. El problema es que, si algo falla en el medio, se vuelve difícil aislar la causa, y el contexto del agente se llena de código que tiene que reprocesar en cada iteración. Dividir en tasks pequeñas (definidas allá en el Stage 2) mantiene cada paso verificable de forma independiente: si la task 1 pasa el validate y la task 2 falla, sabes exactamente dónde está el problema. Es la misma lógica de commits pequeños versus un commit gigante.
El agente toma la task 1 y escribe. Aquí va un fragmento de lo que produce:
locals {
azs = ["us-east-1a", "us-east-1b", "us-east-1c"]
}
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = true
enable_dns_support = true
tags = merge(var.common_tags, {
Name = "${var.project}-${var.environment}-vpc"
})
}
resource "aws_subnet" "private" {
for_each = { for idx, az in local.azs : az => idx }
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr, 8, each.value)
availability_zone = each.key
tags = merge(var.common_tags, {
Name = "${var.project}-${var.environment}-private-${each.key}"
Tier = "private"
})
}
Nota que el agente aplicó automáticamente el pitfall FF-003 (tag Name con AZ) y evitó us-east-1e (lesson del MEMORY.md). También usó for_each en lugar de count, siguiendo el anti-pattern documentado en FEEDFORWARD.md (“nunca uses count para resources con lifecycle dependencies; usa for_each”). Con for_each indexado por la AZ, remover o reordenar una AZ en el futuro no recrea las otras subnets, lo que sí ocurriría con count. No necesitó que nadie se lo recordara: la información estaba en los guides.
Las subnets públicas siguen exactamente el mismo patrón (otro aws_subnet con for_each sobre las AZs, offset diferente en el cidrsubnet() y Tier = "public"), así que omití el bloque aquí para no repetir. Lo importante es que ambas capas, 3 privadas y 3 públicas, una por AZ, se generan con la misma técnica.
Ejecuta terraform fmt y pasa. Ejecuta terraform validate:
Error: Reference to undeclared variable "common_tags" on main.tf line 9
El sensor computational detectó el problema. El agente autocorrige: declara la variable en variables.tf, ejecuta terraform validate de nuevo. Pasa.
Vale la pena desarmar este micro-loop, porque es el corazón del Build. Tiene cuatro pasos y se ejecuta hasta que el código queda limpio:
- Escribe el fragmento de la task actual.
- Formatea con
terraform fmt. Es barato y determinístico; estandariza la indentación antes que nada, evitando ruido en el diff y en el review después. - Valida con
terraform validate. Aquí el sensor detecta errores de sintaxis, referencias a variables o recursos inexistentes, tipos incompatibles. Fue lo que ocurrió arriba: una variable usada pero no declarada. - Corrige con base en el mensaje de error y vuelve al paso 2. El agente no “adivina” la corrección a ciegas; lee el error específico (
undeclared variable "common_tags"), entiende la causa y aplica el remedio exacto.
El punto importante es que este loop es rápido y local. fmt y validate corren en milisegundos, sin tocar ninguna API de AWS y sin costo. El agente puede equivocarse y corregir cinco veces seguidas sin que nadie lo note, porque nada de eso se acerca a la infraestructura real. El error se captura en el lugar más barato posible de resolver.
Pero atención a lo que el validate no detecta. Confirma que el código es sintácticamente válido e internamente coherente, no que hace lo correcto. Un cidr_block superpuesto entre dos subnets pasa el validate. Una regla de security group peligrosamente permisiva pasa el validate. Una tag faltante pasa el validate. Esos problemas semánticos solo serán capturados por otros sensors: linters de política (tfsec, checkov), la revisión del plan en el Stage 4, o el análisis inferencial del propio agente contra los guides. Por eso el Build no es la única puerta; es la primera de varias.
¿Y cuándo el agente no logra autocorregirse? El loop tiene un límite. Si después de algunos intentos el mismo error persiste, o si el error exige una decisión que no tiene contexto para tomar (por ejemplo, “ese módulo interno no existe en la versión que referenciaste, ¿cuál usar?”), se detiene y escala al humano en lugar de insistir en un callejón sin salida. Un agente que sigue intentando la misma corrección diez veces es un agente que debería haber pedido ayuda en la tercera.
Task 1 marcada como done. Toma la task 2. Mismo ciclo: escribe, formatea, valida, corrige si es necesario. Las tasks 3 y 4 siguen el mismo patrón.
Al final del Build, todas las tasks pasaron el fmt y el validate, el código está escrito e internamente coherente. Pero la coherencia sintáctica no garantiza que el resultado en la infraestructura será el esperado. Antes de aplicar cualquier cosa, el agente necesita mirar el retrato completo de lo que va a cambiar. Es exactamente lo que hace el próximo stage con el terraform plan.
Stage 4 — Validate, revisando el plan
Validate es la puerta antes de tocar la infraestructura real. El terraform validate del Build garantiza que el código es sintácticamente válido; el Validate va más allá y examina lo que va a suceder de hecho al aplicar. Es el momento de mirar el plan con desconfianza: ¿aparece algún destroy que nadie pidió? ¿Se va a recrear algún recurso (must-replace)? ¿El costo está dentro de lo esperado? El agente ejecuta un checklist explícito (las “Pre-Apply Guards”) en lugar de confiar en el “parece ok”. Este es el stage que separa una automatización madura de un apply a ciegas.
Con el build completo, el agente ejecuta terraform plan:
Plan: 22 to add, 0 to change, 0 to destroy.
Antes de presentar el resultado, verifica la sección “Pre-Apply Guards” de FEEDFORWARD.md:
- ¿State backend accesible? Sí.
- ¿Provider pinado? Sí,
~> 5.60. - ¿Sin destroys inesperados? Sí, todo es creación.
- ¿Sin hardcoded secrets? Sí.
- ¿Costo? VPC y subnets no tienen costo directo. NAT Gateway (si se agrega después) sí.
El agente presenta el plan al humano con un resumen:
## Plan Summary — atlas/staging
22 resources to create:
- 1 VPC
- 6 subnets (3 private, 3 public)
- 3 route tables
- 6 route table associations
- 1 internet gateway
- 2 security groups
- 3 security group rules
Destructive changes: none
Cost impact: ~$0/month (no NAT gateway, no EIPs)
Risks: none identified
Awaiting approval to apply.
El humano aprueba. El agente registra: “Approval received from your-company-id, 2025-08-29 14:30.”
Ese registro de aprobación no es una formalidad: marca la frontera entre lo que fue revisado y lo que será ejecutado. Solo el plan aprobado puede aplicarse, y es con él en la mano, y no con una nueva generación de código, que el agente entra en el Deploy.
Stage 5 — Deploy, aplicando el Terraform
El Deploy es la ejecución propiamente dicha, y la regla de oro es: aplicar solo el plan que fue aprobado, y verificar enseguida. Aplicar no es el fin del trabajo. Un apply que devuelve “éxito” todavía puede haber dejado drift, así que el agente ejecuta un plan inmediatamente después para confirmar que el estado real coincide con el deseado. Es un stage corto, pero es donde la operación toca el mundo real y, por lo tanto, donde la disciplina de verificar en lugar de asumir más importa.
terraform apply
Apply complete! Resources: 22 added, 0 changed, 0 destroyed.
El agente verifica inmediatamente. Ejecuta terraform plan de nuevo:
No changes. Your infrastructure matches the configuration.
Cero drift. Todo consistente.
Registra en FEEDBACK.md:
### 2025-08-29 — atlas — staging — low (success)
**Stage:** Deploy
**Action:** terraform apply — VPC, subnets, SGs for atlas staging
**Expected:** 22 resources created
**Actual:** 22 resources created successfully
**Result:** success
**Time to Resolve:** N/A
**Blast Radius:** N/A
**Lesson:** for_each com chave = AZ mantém subnets estáveis a mudanças na lista de AZs
**Propagated:** Not yet (first occurrence — observing pattern)
Con los recursos creados y el drift confirmado como cero, la parte técnica de la entrega terminó. Pero la entrega en sí todavía no: falta capturar lo que este trabajo enseñó y dejar el proyecto en un estado que cualquier persona pueda retomar. Ese es el papel del último stage, el Close.
Stage 6 — Close, propagando conocimiento
Close es el stage que la mayoría de los procesos olvida, y es justamente el que hace que el sistema mejore con el tiempo. Aquí el agente confirma que los criterios de aceptación se cumplieron, deja HANDOFF.md en un estado que permite a cualquier persona retomar el trabajo y, lo más importante, decide qué de lo que aprendió merece convertirse en conocimiento permanente. Sin esta etapa, cada entrega termina y el aprendizaje se evapora. Con ella, lo que se descubrió en un proyecto se convierte en prevención para el siguiente. Es el cierre del steering loop.
El agente verifica los acceptance criteria:
- ¿VPC creada con CIDR 10.20.0.0/16? Sí.
- ¿6 subnets en 3 AZs? Sí.
- ¿SGs con reglas correctas? Sí.
- ¿Tags en todos los recursos? Sí.
Actualiza HANDOFF.md con el estado final:
| Field | Value |
|-------|-------|
| Project | atlas |
| Environment | staging |
| Process Stage | Completed |
| Status | Delivered |
| Terraform Snapshot | 22 resources, zero drift |
Actualiza TASKS.md y marca todas las tasks como done y verificadas.
No hubo fallas en esta entrega, así que no hay un nuevo pattern para promover. Pero si el pattern del for_each indexado por AZ se confirma en más proyectos, irá a FEEDFORWARD.md como “proven pattern”.
Y con eso el ciclo se cierra: el pedido se convirtió en infraestructura, las decisiones quedaron registradas y lo poco que se aprendió ya está listo para reaprovecharse. Fue una entrega tranquila, sin sustos. Pero el valor real del proceso aparece justamente cuando algo sale mal, y es ese escenario el que vamos a ver a continuación.
Cuando las cosas salen mal
Supongamos un escenario alternativo. En el Stage 5, el apply falla:
Error: creating Security Group Rule: InvalidGroup.NotFound
El agente no reintenta a ciegas. Registra en FEEDBACK.md con severity medium, identifica que el security group todavía no existía cuando la rule intentó crearse (race condition en la API de AWS), y aplica la corrección: agrega depends_on explícito entre la SG rule y el SG.
Reaplica. Funciona.
Ahora verifica: ¿esto ya pasó antes? Busca en MEMORY.md y FEEDBACK.md. Encuentra una entry similar de hace tres meses en un proyecto diferente. Segunda ocurrencia. Regla de propagación activada:
Para MEMORY.md:
| 2025-08-29 | atlas | SG rules podem falhar com NotFound se criadas muito rápido após o SG | Usar depends_on explícito ou aws_security_group_rule com retry | E-004 |
Para FEEDFORWARD.md:
| FF-007 | SG rules creation | Race condition: rule tenta criar antes do SG propagar na API | Adicionar depends_on explícito entre rule e SG | medium | E-004 |
La próxima vez que cualquier proyecto cree security group rules, el agente leerá FF-007 en FEEDFORWARD.md y aplicará el depends_on de forma preventiva. El problema no se repetirá una tercera vez.
Lo que el agente NO hizo
Vale la pena notar lo que el agente no hizo en este paso a paso:
- No adivinó el CIDR: preguntó.
- No ignoró el checklist pre-apply: verificó ítem por ítem.
- No descartó la falla como “transient”: la registró e investigó.
- No expandió el scope: cuando percibió que un NAT Gateway podría ser necesario, lo registró como “discovered work” en TASKS.md y esperó a que el humano decidiera.
Esto es el harness funcionando. El agente es capaz, pero los guides y sensors lo mantienen sobre los rieles.
Conclusión
El proceso completo (intake, design, build, validate, deploy, close) no es burocracia. Es lo que garantiza que:
- El agente consulta conocimiento antes de actuar (feedforward)
- El agente registra resultados después de actuar (feedback)
- El agente se detiene cuando no está seguro (escalation)
- El conocimiento se acumula y previene problemas futuros (propagation)
Un proyecto simple como este lleva menos tiempo con el agente que sin él. No porque el agente teclee más rápido, sino porque no se olvida de verificar el plan, no se salta el tagging y no ignora un pattern que ya fue documentado.
Pero tal vez el punto más importante sea lo que el agente no hace. No decide solo el CIDR de una red, no aprueba su propio plan, no expande el alcance por su cuenta y no aplica nada que no haya pasado por revisión. Esos no son límites que estorban; son lo que hace confiable al agente. Un agente que decide todo solo es un agente que, tarde o temprano, va a destruir un recurso de producción con total convicción de que estaba en lo correcto. El harness existe justamente para que las decisiones de mayor impacto (qué aprobar, qué aplicar, cuándo expandir el alcance) permanezcan con quien tiene el contexto y la responsabilidad: el humano.
Ese es un punto en el que creo que vale la pena insistir: la revisión humana no es un vestigio del proceso antiguo, es una etapa de primera clase. El agente prepara el terreno, es decir, genera el código, ejecuta los sensors, arma un resumen honesto del plan destacando riesgos y cambios destructivos, y entonces se detiene y espera. Quien lee el plan, pondera el impacto en el negocio y dice “puedes aplicar” es la persona. El agente optimiza el trabajo de la revisión (todo llega formateado, validado y explicado), pero no la sustituye. La responsabilidad por lo que va a producción sigue siendo humana, porque es el humano quien responde por la auditoría, quien es despertado a las 3 de la mañana si algo se cae, y quien carga el contexto que ningún guide captura por completo. El agente ejecuta; la persona dirige y decide.
En el próximo y último post de la serie, voy a mostrar cómo transformar este sistema en algo que use todo el equipo, cómo distribuir el agente como un template, cómo evolucionar el catálogo y cómo hacer la adopción de forma incremental sin forzar a nadie.
¡Un abrazo!
¡Larga vida y prosperidad a todos!
Referencias
- Parte 1 - La anatomía de un AI Agent para proyectos de IaC
- Harness engineering: leveraging Codex in an agent-first world — OpenAI, Feb 2026
- Harness design for long-running application development — Anthropic, Mar 2026
- Harness engineering for coding agent users — Martin Fowler / Birgitta Böckeler, Abr 2026
Entre em contato:
NewsLetter - https://engineeringmanager.com.br/Linkdin - linkedin.com/in/leonardoml/
Twitter: @infraascode_br
Te convido a ver os outros posts do blog Infra-as-Code garanto que tem coisas legais lá!!
|
|
