Módulo 6: La cáscara determinista

Proyecto: construye la cáscara determinista del agente de Mercado

Descripción

Este es el capstone del módulo. En las siete lecciones anteriores construiste, pieza por pieza, la cáscara determinista que contiene las acciones de un componente de IA: el modelo propone en vez de ejecutar (L2), en un formato estructurado (L3), de un menú acotado de capabilities (L4), validado contra las reglas de negocio (L5), con un núcleo mantenido pequeño (L6), en un pipeline ordenado y auditado (L7). Ahora las juntas todas en un solo entregable: la cáscara determinista completa del agente de soporte de Mercado, corriendo sobre un lote realista de propuestas del modelo, medida de punta a punta.

El entregable tiene cuatro partes, como todo capstone de arquitectura: el diagrama de la cáscara (dónde vive el componente de IA y qué compuertas rodean sus acciones), el código ejecutado (el pipeline completo corriendo sobre doce propuestas del modelo, con su salida literal), un ADR (el registro de la decisión arquitectónica de contener las acciones del modelo, con su contexto, sus alternativas y sus consecuencias), y la justificación de por qué, en este diseño, el LLM nunca toca el dinero directamente y la no-determinación queda contenida. Al terminar, tienes un artefacto que puedes llevarte y aplicar a cualquier feature de IA que tome acciones.

Conexión con el módulo. Esta lección cierra el módulo 6 juntando todo lo anterior en un caso real y medido, y lo conecta con lo que sigue. Hacia el módulo 7 (el lazo de datos): el log de auditoría que la cáscara produce es la materia prima del flywheel de datos que verás a continuación. Hacia el módulo 8 (el capstone de la guía): la cáscara determinista es una de las piezas que arquitectarás en la feature completa de Mercado, junto con el presupuesto, el eval gate, los guardrails y el fallback. Y las fronteras de siempre: la lógica de negocio en sí es de las guías de dominio; cómo construir el agente es de AI Engineering; aquí, el patrón de contención de sus acciones.

El caso: el agente de soporte que quiere reembolsar

Recapitulemos el caso que ha acompañado todo el módulo, ahora completo. El agente de soporte de Mercado atiende mensajes de clientes: "quiero mi dinero de vuelta", "el pedido llegó roto", "reembólsame". El LLM lee cada mensaje y propone una acción —casi siempre un refund, a veces un escalate_to_human o un send_message—. El agente es útil porque el modelo entiende el lenguaje del cliente; es seguro porque nunca ejecuta un reembolso directamente. Cada propuesta pasa por la cáscara determinista, que la valida y ejecuta solo si cumple todas las reglas.

El lote de doce propuestas que vamos a correr es una muestra realista de lo que un modelo produce en producción: la mayoría son intentos legítimos, pero hay de todo lo que puede salir mal —montos absurdos, pedidos alucinados, reembolsos fuera de política, dobles reembolsos, acciones fuera del menú del agente, y texto libre en vez de un comando—. La cáscara tiene que dejar pasar lo bueno y contener todo lo demás, y tenemos que poder medir cuánto contuvo.

Antes del código, el diagrama de la cáscara —el artefacto que muestra dónde vive el componente de IA y qué lo rodea—:

flowchart TD
    C["mensaje del cliente"] --> LLM
    subgraph nucleo["NUCLEO PROBABILISTICO"]
        LLM["el LLM (simulado)<br/>PROPONE una accion estructurada"]
    end
    LLM -->|"{action, order_id, amount}"| S1
    subgraph shell["CASCARA DETERMINISTA"]
        S1["1 · ESTRUCTURA<br/>¿comando del menu, con campos?"]
        S2["2 · CAPABILITY<br/>¿accion de ESTE agente?"]
        S3["3 · POLITICA<br/>¿cumple las reglas de negocio?"]
        S1 -->|pasa| S2 -->|pasa| S3
    end
    S1 -->|falla| B["BLOQUEA<br/>+ log de auditoria"]
    S2 -->|falla| B
    S3 -->|falla| B
    S3 -->|pasa| EX["EJECUTAR<br/>el dinero se mueve SOLO aqui"]
    EX --> LOG["log de auditoria<br/>(materia prima del M7)"]
    B --> LOG

Léelo así: el componente de IA —el núcleo probabilístico— vive dentro de la cáscara, y su única responsabilidad es proponer. Su salida no va al dinero: va a la primera compuerta. Las tres compuertas en serie deciden; el dinero se mueve solo al final, y solo para lo que pasó las tres. Todo —lo ejecutado y lo bloqueado— queda en el log de auditoría. Esa es la forma de un sistema AI-native que toma acciones: una gota de incertidumbre (el núcleo) rodeada de un pipeline determinista (la cáscara) que garantiza que nada peligroso llegue al dinero.

El código: la cáscara completa, ejecutada

Aquí está la cáscara determinista completa del agente de soporte, corriendo sobre las doce propuestas. Nota que la ejecución muta el estado: cuando un reembolso se ejecuta, el pedido se marca como reembolsado, de modo que una segunda propuesta de reembolso del mismo pedido —como la última del lote— se bloquea por doble reembolso. Es el pipeline de la lección 7, con el lote ampliado y las métricas de contención.

# Modulo 6, Leccion 8 (proyecto): la cascara determinista del agente de soporte.
# Pipeline completo -estructura + capabilities + reglas de negocio + auditoria-
# sobre un lote de 12 propuestas del LLM (SIMULADO): legitimas, alucinadas,
# fuera de politica, montos absurdos y capabilities no otorgadas.
# Solo se EJECUTA lo que pasa las tres compuertas. Sin red, sin API. Datos fijos.

ORDERS = {
    "A-1001": {"total": 50.00,  "days_since_delivery": 3,  "refunded": False},
    "A-1002": {"total": 120.00, "days_since_delivery": 45, "refunded": False},
    "A-1003": {"total": 30.00,  "days_since_delivery": 5,  "refunded": True},
    "A-1005": {"total": 75.00,  "days_since_delivery": 8,  "refunded": False},
}
MAX_REFUND = 100.00
RETURN_WINDOW_DAYS = 30

ACTION_MENU = {
    "refund":             {"order_id", "amount"},
    "escalate_to_human":  {"reason"},
    "send_message":       {"text"},
    "issue_store_credit": {"amount"},
    "change_price":       {"product_id", "price"},
}
GRANTED = {"refund", "escalate_to_human", "send_message"}


def stage_structure(p):
    if not isinstance(p, dict):
        return (False, "texto libre, no es un comando")
    if p.get("action") not in ACTION_MENU:
        return (False, f"accion desconocida: {p.get('action')!r}")
    if ACTION_MENU[p["action"]] - p.keys():
        return (False, "faltan campos requeridos")
    return (True, "")


def stage_capability(p):
    if p["action"] not in GRANTED:
        return (False, f"{p['action']} no esta en las capabilities del agente")
    return (True, "")


def stage_policy(p):
    if p["action"] != "refund":
        return (True, "")
    oid, amount = p["order_id"], p["amount"]
    if oid not in ORDERS:
        return (False, f"pedido {oid} no existe")
    o = ORDERS[oid]
    if o["refunded"]:
        return (False, f"pedido {oid} ya reembolsado")
    if o["days_since_delivery"] > RETURN_WINDOW_DAYS:
        return (False, "fuera de la ventana de devolucion")
    if amount > o["total"]:
        return (False, f"monto {amount:.2f} > total {o['total']:.2f}")
    if amount > MAX_REFUND:
        return (False, f"monto {amount:.2f} > limite {MAX_REFUND:.2f}")
    return (True, "")


def execute(p):
    if p["action"] == "refund":
        ORDERS[p["order_id"]]["refunded"] = True
        return p["amount"]
    return 0.0


PIPELINE = [("ESTRUCTURA", stage_structure),
            ("CAPABILITY", stage_capability),
            ("POLITICA",   stage_policy)]

# Lote de 12 propuestas del nucleo probabilistico (SIMULADO).
PROPOSALS = [
    {"action": "refund", "order_id": "A-1001", "amount": 50.00},
    {"action": "refund", "order_id": "A-1002", "amount": 120.00},
    {"action": "refund", "order_id": "A-1003", "amount": 30.00},
    {"action": "refund", "order_id": "A-9999", "amount": 40.00},
    {"action": "refund", "order_id": "A-1005", "amount": 5000.00},
    {"action": "refund", "order_id": "A-1005", "amount": 75.00},
    "te devuelvo el dinero enseguida, no te preocupes",
    {"action": "issue_store_credit", "amount": 200.00},
    {"action": "change_price", "product_id": "P-1", "price": 0.01},
    {"action": "delete_account", "user_id": "U-7"},
    {"action": "escalate_to_human", "reason": "cliente pide un supervisor"},
    {"action": "refund", "order_id": "A-1001", "amount": 50.00},
]

print(f"{'#':<3}{'accion':<20}{'resultado':<12}{'etapa':<12}razon")
print("-" * 96)
executed = 0
money_moved = money_blocked = 0.0
stopped = {"ESTRUCTURA": 0, "CAPABILITY": 0, "POLITICA": 0}
for i, p in enumerate(PROPOSALS):
    name = p["action"] if isinstance(p, dict) and "action" in p else "(texto libre)"
    blocked_at = reason = None
    for stage_name, stage_fn in PIPELINE:
        ok, why = stage_fn(p)
        if not ok:
            blocked_at, reason = stage_name, why
            stopped[stage_name] += 1
            break
    if blocked_at is None:
        money_moved += execute(p)
        executed += 1
        print(f"{i:<3}{name:<20}{'EJECUTA':<12}{'-':<12}aprobada")
    else:
        if isinstance(p, dict) and p.get("action") == "refund":
            money_blocked += p.get("amount", 0.0)
        print(f"{i:<3}{name:<20}{'BLOQUEA':<12}{blocked_at:<12}{reason}")

blocked = len(PROPOSALS) - executed
print()
print(f"Propuestas del LLM      : {len(PROPOSALS)}")
print(f"Ejecutadas              : {executed}")
print(f"Bloqueadas (contenidas) : {blocked}")
print(f"  por ESTRUCTURA : {stopped['ESTRUCTURA']}   "
      f"por CAPABILITY : {stopped['CAPABILITY']}   por POLITICA : {stopped['POLITICA']}")
print(f"Tasa de contencion      : {blocked / len(PROPOSALS) * 100:.1f}%")
print(f"Dinero movido (aprobado): {money_moved:.2f}")
print(f"Dinero contenido        : {money_blocked:.2f}  (reembolsos que el LLM propuso y no salieron)")

Qué esperar. Al correr el archivo, la salida es exactamente esta:

#  accion              resultado   etapa       razon
------------------------------------------------------------------------------------------------
0  refund              EJECUTA     -           aprobada
1  refund              BLOQUEA     POLITICA    fuera de la ventana de devolucion
2  refund              BLOQUEA     POLITICA    pedido A-1003 ya reembolsado
3  refund              BLOQUEA     POLITICA    pedido A-9999 no existe
4  refund              BLOQUEA     POLITICA    monto 5000.00 > total 75.00
5  refund              EJECUTA     -           aprobada
6  (texto libre)       BLOQUEA     ESTRUCTURA  texto libre, no es un comando
7  issue_store_credit  BLOQUEA     CAPABILITY  issue_store_credit no esta en las capabilities del agente
8  change_price        BLOQUEA     CAPABILITY  change_price no esta en las capabilities del agente
9  delete_account      BLOQUEA     ESTRUCTURA  accion desconocida: 'delete_account'
10 escalate_to_human   EJECUTA     -           aprobada
11 refund              BLOQUEA     POLITICA    pedido A-1001 ya reembolsado

Propuestas del LLM      : 12
Ejecutadas              : 3
Bloqueadas (contenidas) : 9
  por ESTRUCTURA : 2   por CAPABILITY : 2   por POLITICA : 5
Tasa de contencion      : 75.0%
Dinero movido (aprobado): 125.00
Dinero contenido        : 5240.00  (reembolsos que el LLM propuso y no salieron)

Lee la salida completa, porque es la cáscara del módulo entero funcionando sobre un caso realista.

Tres de doce se ejecutaron; la cáscara contuvo nueve. Las ejecutadas son las legítimas: dos reembolsos válidos (A-1001 por $50, A-1005 por $75) y un escalamiento a un humano. Todo lo demás —el 75% del lote— se contuvo, cada bloqueo en su compuerta. Ese número, 75% de contención, es la medida del módulo: de cada cuatro propuestas del modelo, la cáscara dejó pasar una y detuvo tres, sin que el modelo fuera "malo" —simplemente propuso el rango normal de acciones que un LLM propone, bueno y malo, y la cáscara filtró—.

Los bloqueos se reparten por las tres compuertas, cada una atrapando su tipo de problema. Dos en ESTRUCTURA: el texto libre (fila 6, que no es un comando) y el delete_account (fila 9, una acción alucinada que ni existe en el menú de la plataforma). Dos en CAPABILITY: issue_store_credit y change_price (filas 7 y 8, comandos reales de la plataforma pero fuera de las capabilities del agente de soporte). Cinco en POLITICA: los refund bien formados y dentro del menú que violan las reglas —fuera de ventana (fila 1), ya reembolsado (fila 2), pedido inexistente (fila 3), monto absurdo (fila 4), y doble reembolso (fila 11)—. Cada compuerta hizo su trabajo, y el reparto te dice qué tipo de error contuvo cada una.

La fila 11 muestra la cáscara protegiendo el estado, no solo el dinero. Fíjate en la última propuesta: refund de A-1001 por $50 —la misma acción que la fila 0, que sí se ejecutó—. Pero esta vez se bloquea, con la razón "pedido A-1001 ya reembolsado". ¿Por qué? Porque la fila 0 ejecutó ese reembolso y mutó el estado (marcó A-1001 como reembolsado), así que cuando la fila 11 propone reembolsarlo otra vez, la regla not_refunded lo atrapa. Sin esta protección, un modelo que propone dos veces el mismo reembolso —algo que pasa, por reintentos o confusión— pagaría dos veces. La cáscara no solo valida contra reglas fijas: valida contra el estado actual del sistema, que cambia con cada acción que ejecuta.

El número final: $5240 contenidos. El modelo propuso, en refund, un total de $5365; la cáscara ejecutó $125 (los legítimos) y contuvo $5240 que nunca tocaron la cuenta de ningún cliente. Como en la lección 1, el monto absurdo de $5000 domina esa cifra, y ese es justo el punto: la cáscara no protege solo contra los errores chicos y frecuentes, sino contra el error grande y raro que, ejecutado una sola vez, sería un desastre. Junta las tres métricas —75% de contención, $125 movidos con criterio, $5240 protegidos— y tienes la justificación cuantitativa de todo el módulo: la no-determinación del modelo quedó contenida, y el dinero solo se movió para lo que cumplió cada regla.

El ADR: registrar la decisión de contener las acciones

Un capstone de arquitectura entrega un ADR (Architecture Decision Record): el registro de una decisión de diseño, con su contexto, la decisión, las alternativas consideradas y sus consecuencias. Aquí está el ADR de la cáscara determinista del agente de soporte.

# ADR-006: Cascara determinista para las acciones del agente de soporte

## Estado
Aceptada

## Contexto
El agente de soporte de Mercado usa un LLM para entender los mensajes de los
clientes y resolver casos, muchos de los cuales terminan en un reembolso —una
accion que mueve dinero real e irreversible—. El LLM es no determinista: puede
alucinar un pedido, proponer un monto absurdo, reembolsar fuera de politica o
dos veces el mismo pedido. Necesitamos que el agente sea util (entienda al
cliente) sin que su no-determinacion pueda comprometer el dinero ni el estado
del sistema.

## Decision
El LLM NUNCA ejecuta una accion sobre dinero o estado directamente. PROPONE una
accion estructurada -un comando con nombre y campos tipados- y una cascara
determinista la valida en un pipeline de tres compuertas en orden antes de
ejecutar:
  1. ESTRUCTURA  - la propuesta es un comando conocido del menu, con sus campos.
  2. CAPABILITY  - la accion esta entre las otorgadas a ESTE agente (menu minimo).
  3. POLITICA    - la accion cumple TODAS las reglas de negocio vigentes.
Solo las propuestas que pasan las tres compuertas se ejecutan. Todo -aprobado y
bloqueado- queda en un log de auditoria. La politica vive en CODIGO, no en el
prompt.

## Alternativas consideradas
- Ejecutar directo la salida del LLM (ingenuo): descartada; una alucinacion
  mueve dinero sin freno. Medido: pagaria 5315 vs 125 con cascara.
- Poner la politica en el prompt: descartada; da una probabilidad, no una
  garantia. El modelo propone fuera de politica igual cada tanto.
- Validar el contenido con un guardrail de formato (M4) y ejecutar: insuficiente;
  un JSON impecable puede ser una accion que viola la politica.

## Consecuencias
+ El LLM no puede tocar el dinero directamente; la garantia la da el codigo.
+ Radio de dano acotado: capabilities minimas -> operaciones peligrosas fuera
  del alcance por definicion.
+ Sistema auditable: cada decision queda registrada (base del lazo de datos, M7).
+ Contencion medida: 75% de propuestas contenidas, 5240 protegidos en el lote.
- La cascara hay que mantenerla cuando la politica de negocio cambia (pero es un
  cambio de parametros, no de arquitectura).
- La politica concreta (limites, ventana) es contenido de dominio, no de esta
  cascara; hay que coordinarla con el equipo de negocio.

Este ADR es el artefacto que justifica la decisión ante el equipo y ante quien la revise en el futuro. Nota que no documenta cómo se implementó cada compuerta (eso está en el código), sino por qué se decidió contener las acciones del modelo, qué se descartó y qué consecuencias trae. Un ADR bien escrito permite que alguien que llega en seis meses entienda la decisión sin tener que reconstruirla.

La justificación: por qué la no-determinación queda contenida

Cierra el capstone con el argumento que junta todo el módulo: por qué, en este diseño, el LLM nunca toca el dinero y la no-determinación queda contenida.

El argumento tiene tres patas. La primera: el LLM solo propone; no ejecuta. Su salida no está conectada a ninguna API que mueva dinero —está conectada a la primera compuerta de la cáscara—. Por más que el modelo "quiera" reembolsar $5000, lo único que puede hacer es proponerlo; la ejecución ocurre al final de un pipeline que esa propuesta tiene que pasar entera, y $5000 no pasa la compuerta de política. La garantía "ningún reembolso fuera de política sale" no depende de que el modelo se porte bien: la dan las condiciones duras del código, que se cumplen el 100% de las veces.

La segunda: la superficie del modelo sobre lo irreversible es mínima. El agente tiene un menú de tres capabilities, de las cuales solo una (refund) toca dinero, y esa está envuelta en cinco reglas de política más la validación contra el estado actual. El modelo no puede cambiar precios, dar de baja cuentas ni emitir créditos —esas operaciones no están en su menú, así que no existen para él—. El radio de daño de cualquier alucinación está acotado a "proponer un reembolso que la política rechazará", que es exactamente lo que vimos contenerse nueve veces.

La tercera: el sistema es observable y rinde cuentas. Cada decisión —ejecutada o bloqueada, y por qué— queda en el log de auditoría. No hay acciones fantasma: si el dinero se movió, hay un registro de qué propuesta lo movió y por qué pasó las compuertas; si no se movió, hay un registro de qué compuerta lo detuvo. Esto no solo permite operar el sistema; es la base del lazo de datos del módulo 7, donde esas mismas decisiones registradas alimentan la mejora continua del sistema.

Junta las tres patas y tienes la tesis del módulo demostrada sobre un caso real: el modelo propone, el sistema dispone. El modelo aporta la inteligencia de entender al cliente; la cáscara aporta la garantía de que ninguna acción irreversible ocurra sin cumplir las reglas. La no-determinación no se eliminó —el modelo sigue siendo tan falible como siempre—; se contuvo, de modo que su falibilidad no puede escapar al dinero. Esa es la forma de un sistema AI-native que toma acciones, y ahora la construiste, la corriste y la mediste.

Ejercicios

Ejercicio 1 — Extiende la cáscara. El equipo de negocio de Mercado quiere que el agente de soporte pueda también reenviar la factura de un pedido al cliente (una acción sin riesgo, solo manda un correo con un documento existente). Describe qué cambios harías en cada parte de la cáscara —ACTION_MENU, GRANTED, y si necesita reglas de política— y justifica por qué esta acción necesita menos contención que un reembolso.

Ver solución

Cambios en la cáscara para agregar resend_invoice:

  • ACTION_MENU: agregar "resend_invoice": {"order_id"} —es un comando conocido de la plataforma, con un campo requerido (el pedido cuya factura reenviar)—.
  • GRANTED: agregar "resend_invoice" al menú del agente de soporte, porque reenviar facturas es parte de su trabajo (a diferencia de change_price, que no lo es).
  • Reglas de política (stage_policy): una regla mínima —que el pedido exista (order_id in ORDERS)—, y nada más. No necesita chequear montos, ventanas ni estado de reembolso, porque la acción no toca dinero ni cambia estado: solo reenvía un documento que ya existe.

Por qué necesita menos contención que un reembolso: resend_invoice es reversible y sin efecto sobre el dinero o el estado crítico. Lo peor que puede pasar si el modelo alucina es que se reenvíe una factura de más (un correo redundante), que es un inconveniente menor y recuperable —muy distinto de reembolsar $5000, que es dinero irreversible—. La cantidad de contención que una acción necesita es proporcional a su radio de daño: refund toca dinero irreversible y necesita cinco reglas + validación de estado; resend_invoice toca solo un correo y necesita una regla (que el pedido exista). Esto ilustra un principio de diseño: no toda acción del menú necesita el mismo peso de cáscara; la política se calibra al riesgo de cada acción. Aun así, resend_invoice pasa por las mismas tres compuertas —estructura, capability, política—; solo que su compuerta de política es más ligera.

Ejercicio 2 — El intento de manipulación. Un cliente malicioso descubre que si escribe mensajes muy insistentes, a veces logra que el modelo proponga un reembolso de un monto mayor al del pedido. Explica, usando la salida del capstone, por qué este ataque no funciona contra la cáscara, y qué compuerta lo detiene. Luego di por qué poner la política en el prompt del modelo sería vulnerable a este ataque.

Ver solución

El ataque no funciona contra la cáscara porque, aunque el cliente logre que el modelo proponga un reembolso inflado, la propuesta tiene que pasar la compuerta de POLITICA antes de ejecutarse, y ahí la regla amount > o["total"] (o amount > MAX_REFUND) lo detiene. Lo vemos en la fila 4 del capstone: el refund de A-1005 por $5000 —un monto muy superior al del pedido, exactamente el resultado que un atacante buscaría— se bloquea con "monto 5000.00 > total 75.00". No importa cómo el atacante convenció al modelo de proponerlo (insistencia, manipulación, prompt injection); la cáscara valida la acción resultante contra las reglas, no las razones del modelo para proponerla. El atacante puede manipular al modelo (el núcleo probabilístico), pero no puede manipular la condición dura if amount > total de la cáscara determinista.

Poner la política en el prompt sería vulnerable porque el prompt es parte del núcleo probabilístico —el mismo componente que el atacante está manipulando—. Si la única barrera contra un reembolso inflado es una instrucción en el prompt ("no reembolses más del total"), entonces convencer al modelo de ignorar esa instrucción es el ataque, y los modelos pueden ser convencidos (esa es la esencia del prompt injection del módulo 4). La política en el prompt es una barrera dentro de la cosa que el atacante controla; la política en la cáscara determinista es una barrera fuera de su alcance. Por eso el módulo insiste: la garantía vive en el código determinista, no en el prompt. El atacante puede tener la última palabra sobre lo que el modelo propone; nunca sobre lo que la cáscara ejecuta.

Ejercicio 3 — Aplícalo a otra feature. Elige otra feature de IA de Mercado que tome acciones —por ejemplo, un agente que ayude a los vendedores a gestionar su inventario (podría proponer bajar el precio de un producto, marcarlo como agotado, o crear una promoción)—. Diseña su cáscara determinista: el menú de capabilities, un ejemplo de acción estructurada, dos reglas de política, y una operación peligrosa que no le darías. Justifica cada decisión con un principio del módulo.

Ver solución

Cáscara determinista para el agente de gestión de inventario del vendedor:

  • Menú de capabilities (GRANTED): {update_stock, mark_out_of_stock, propose_discount}. Cada una es parte del trabajo de gestionar inventario. Principio: capabilities acotadas (L4) —el menú se deriva del trabajo del agente, no de "lo que podría ser útil"—.
  • Ejemplo de acción estructurada: {action: "propose_discount", product_id: "P-42", percent: 15}. Un comando con nombre y campos tipados, no texto libre. Principio: la acción estructurada (L3) —el modelo nombra un comando del menú, no ejecuta código ni prosa—.
  • Dos reglas de política (para propose_discount): (1) el descuento no puede superar el 30% sin autorización (percent <= 30), para proteger el margen; (2) el producto debe pertenecer a este vendedor (product.seller_id == agent.seller_id), para que un agente no toque productos de otro. Principio: validar contra reglas de negocio (L5) —la validación es conjuntiva y protege contra riesgos distintos: margen y pertenencia—.
  • Operación peligrosa que NO le daría: delete_product (borrar un producto del catálogo). No es reversible con facilidad y no es necesaria para la gestión rutinaria de inventario —si un producto debe eliminarse, es una decisión que pasa por un humano vía escalamiento—. Principio: least privilege (L4) y núcleo pequeño (L6) —lo irreversible y raro sale del menú; el radio de daño se mantiene chico—.

Nota de diseño: igual que en el agente de soporte, este agente propone (bajar precio, marcar agotado) y la cáscara dispone (valida el descuento contra el margen y la pertenencia antes de aplicarlo). El precio no lo fija el modelo libremente; el modelo propone un porcentaje que la regla acota. Y decisiones como "¿este producto califica para promoción destacada?" serían reglas deterministas (sobre ventas, stock), no juicios del modelo —encoger el núcleo, L6—. La misma forma del módulo, aplicada a un caso distinto: el modelo aporta la inteligencia de gestionar; la cáscara garantiza que ninguna acción viole las reglas del negocio.

Resumen y siguiente paso

En este capstone construiste la cáscara determinista completa del agente de soporte de Mercado y la mediste de punta a punta. Entregaste las cuatro partes: el diagrama (el núcleo probabilístico dentro de las tres compuertas, con el dinero moviéndose solo al final), el código ejecutado (el pipeline sobre doce propuestas: 3 ejecutadas, 9 contenidas, 75% de contención, $5240 protegidos, incluyendo un doble reembolso atrapado por la validación contra el estado actual), el ADR (la decisión de contener las acciones del modelo, con su contexto, alternativas y consecuencias), y la justificación de por qué la no-determinación queda contenida —el LLM solo propone, su superficie sobre lo irreversible es mínima, y el sistema es observable y rinde cuentas—. Demostraste, sobre un caso real, la tesis del módulo: el modelo propone, el sistema dispone.

Con esto cierras la cáscara determinista de las acciones. Ya sabes contener lo que el modelo produce (M4, guardrails de salida) y lo que el modelo hace (M6, este módulo). La guía tiene una pieza más antes del capstone final: el módulo 7, el lazo de datos y de retroalimentación. Y hay un puente directo desde aquí: el log de auditoría que tu cáscara produjo —cada propuesta, cada aprobación, cada bloqueo con su razón— no es solo un registro para operar; es la materia prima del flywheel de datos. Cada decisión que la cáscara tomó es un dato sobre cómo se comporta el modelo, y el módulo 7 te enseñará a cerrar el lazo: usar → datos → mejor sistema. La contención que construiste aquí genera, como subproducto, exactamente los datos con los que el sistema aprende.

Y las fronteras que respetaste todo el módulo siguen marcando hacia dónde seguir: para construir el agente que propone las acciones —mejor prompt, tool use, function calling—, el ecosistema de AI Engineering; para decidir cuáles son las reglas de la política de reembolsos, las guías de dominio de Mercado; para la resiliencia de la cáscara cuando el modelo cae, el módulo 5 y la resilience-and-reliability-patterns-guide. Aquí construiste el patrón de contención; esas guías construyen las piezas que contiene.

Recursos

  • Anthropic, "Building Effective Agents" (2024) — anthropic.com/engineering/building-effective-agents. La guía de referencia para diseñar agentes donde el modelo propone acciones acotadas y verificadas, en vez de ejecutar libremente. Es el respaldo directo de la cáscara de este capstone. En inglés.
  • Documentación de Claude, tool usedocs.anthropic.com. El mecanismo por el que un modelo devuelve acciones estructuradas (llamadas a herramientas con argumentos tipados) que tu sistema decide ejecutar o no. La base técnica de la acción estructurada. Sin fijarte en una versión de modelo específica. En inglés.
  • Michael Nygard, "Documenting Architecture Decisions" (2011) — el formato ADR que usamos en este capstone. Registrar el contexto, la decisión, las alternativas y las consecuencias de una decisión de diseño es una práctica estándar de arquitectura, y aquí la aplicamos a la decisión de contener las acciones del modelo. En inglés.
  • Martin Fowler y Bharani Subramaniam, "Emerging Patterns in Building GenAI Apps" — martinfowler.com/articles/gen-ai-patterns. El mapa completo de patrones de contención para apps GenAI, donde esta cáscara de acciones es una pieza entre varias. En inglés.
  • Chip Huyen, AI Engineering (O'Reilly, 2024). Para la visión completa de la arquitectura de aplicaciones con modelos —el componente de IA rodeado de lógica de aplicación, con sus controles y su observabilidad—. En inglés.