Módulo 6: La cáscara determinista

Validar la propuesta contra las reglas de negocio

Descripción

Ya tienes las dos primeras compuertas de la cáscara. La lección 3 verificó que la propuesta sea un comando bien formado (canal); la lección 4 verificó que la acción sea del agente (capability). Esta lección llega a la tercera y central: para las acciones que sí pasaron las dos anteriores —un refund bien formado, dentro del menú del agente—, ¿la acción cumple la política? Esta es la compuerta que atrapó el refund de $5000 del principio de la guía, y es el corazón del módulo, porque es donde "el sistema dispone" se vuelve concreto: un conjunto de reglas de negocio deterministas que la propuesta debe pasar todas antes de ejecutarse.

Vas a ver la validación abierta regla por regla. La política de reembolsos de Mercado tiene cinco reglas: el pedido existe, no fue reembolsado ya, está dentro de la ventana de devolución, el monto no excede el total del pedido, y el monto no excede el límite del agente. Para cada propuesta del modelo, la cáscara evalúa las cinco y muestra cuáles pasa y cuál falla, y ejecuta solo si pasa todas. El resultado es una matriz que hace visible, propuesta por propuesta, exactamente qué la contuvo —o por qué se aprobó—.

Conexión con el módulo. Esta es la lección-corazón: la validación de la acción propuesta contra las reglas de negocio, que es lo que da la garantía de "el sistema dispone". La frontera con las guías de dominio es aquí más importante que nunca y hay que trazarla con cuidado: no enseñamos cuál debe ser la política de reembolsos de Mercado —qué monto máximo, qué ventana, qué excepciones— porque eso es una decisión de negocio, contenido de dominio. Enseñamos el patrón: que exista una capa determinista que valide la acción contra la política antes de ejecutarla, cualquiera que sea la política. Y la frontera con el módulo 4, que esta lección deja definitivamente clara: el guardrail de M4 validaba el contenido/formato de la salida; aquí validamos la acción contra reglas de negocio. Una salida puede ser un JSON impecable (pasa M4) y una acción que viola la política (la bloquea M6).

Una analogía: el cajero del banco y la lista de verificación del crédito

Vuelve al cajero de la lección 1, el que recomienda pero no aprueba. Cuando su recomendación de crédito llega al sistema del banco, el sistema no la aprueba "a ojo" ni confía en que el cajero haya hecho bien su trabajo. Corre una lista de verificación: ¿el solicitante existe en nuestros registros? ¿su score está por encima del mínimo? ¿su deuda actual está por debajo del tope? ¿el monto pedido está dentro de su límite de perfil? ¿no tiene ya un crédito activo del mismo tipo? Cada punto de la lista es una condición dura, evaluada por código, y el crédito se aprueba solo si pasa todos. Basta que uno falle —el score bajo, la deuda alta— para que la solicitud se rechace, con una razón concreta.

Fíjate en tres cosas de esa lista. Primero, es exhaustiva y determinista: no es una impresión general de "se ve bien", son puntos específicos con respuestas de sí/no. Segundo, es conjuntiva: no basta pasar la mayoría; hay que pasar todos, porque cada regla protege contra un riesgo distinto y saltarse una deja ese riesgo abierto. Y tercero, la lista vive en el banco, no en el cajero: el cajero puede recomendar cuanto quiera, pero la lista la corre el sistema, que es quien conoce las reglas y quien da la garantía de que ninguna aprobación las viole.

Esa lista de verificación es la validación de reglas de negocio. La propuesta del modelo (el refund) es la recomendación del cajero; la lista de verificación es la política de reembolsos; el sistema que la corre es la cáscara determinista. El modelo puede proponer con toda convicción, igual que el cajero puede recomendar con entusiasmo; la aprobación depende de pasar cada punto de la lista, no de cuán convencido estaba quien propuso. Y —clave para la frontera con las guías de dominio— cuáles son los puntos de la lista (qué score mínimo, qué tope de deuda) es una decisión del banco; lo que esta lección enseña es que la lista existe, se corre antes de aprobar, y hay que pasarla entera.

Ejemplo trabajado: la validación, regla por regla

Vamos a correr la lista de verificación. La política de reembolsos tiene cinco reglas, y la función check_rules evalúa cada una para cada propuesta, devolviendo una fila con el resultado de las cinco más el veredicto final. Usamos tres marcas: ok (la regla pasó), x (la regla falló), - (no aplica, no se pudo evaluar —por ejemplo, si el pedido no existe, no tiene sentido evaluar su ventana—). El veredicto es EJECUTA solo si las cinco son ok. Corremos las mismas seis propuestas de la lección 1 para ver la matriz completa.

# Modulo 6, Leccion 5: validar la propuesta contra las reglas de negocio.
# El corazon del patron: antes de EJECUTAR, la cascara determinista corre la
# propuesta del LLM contra CADA regla de la politica de reembolsos y solo
# ejecuta si TODAS pasan. Sin red, sin API, sin claves. 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


def check_rules(proposal):
    # Devuelve una fila con el resultado de cada regla y el veredicto final.
    # 'ok' = paso; 'x' = fallo; '-' = no aplica (no se pudo evaluar).
    oid, amount = proposal["order_id"], proposal["amount"]
    row = {"exists": "-", "not_refunded": "-", "in_window": "-",
           "amt<=total": "-", "amt<=limit": "-"}
    if oid not in ORDERS:
        row["exists"] = "x"
        return row, False
    row["exists"] = "ok"
    order = ORDERS[oid]
    row["not_refunded"] = "ok" if not order["refunded"] else "x"
    row["in_window"] = "ok" if order["days_since_delivery"] <= RETURN_WINDOW_DAYS else "x"
    row["amt<=total"] = "ok" if amount <= order["total"] else "x"
    row["amt<=limit"] = "ok" if amount <= MAX_REFUND else "x"
    passed = all(v == "ok" for v in row.values())
    return row, passed


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},
]

cols = ["exists", "not_refunded", "in_window", "amt<=total", "amt<=limit"]
header = f"{'pedido':<8}{'monto':>9}  " + "".join(f"{c:<14}" for c in cols) + "veredicto"
print(header)
print("-" * len(header))
executed = blocked = 0
for p in PROPOSALS:
    row, passed = check_rules(p)
    verdict = "EJECUTA" if passed else "BLOQUEA"
    executed += passed
    blocked += (not passed)
    cells = "".join(f"{row[c]:<14}" for c in cols)
    print(f"{p['order_id']:<8}{p['amount']:>9.2f}  {cells}{verdict}")

print()
print(f"Ejecutadas : {executed}/{len(PROPOSALS)}")
print(f"Bloqueadas : {blocked}/{len(PROPOSALS)}  (contenidas por al menos una regla)")

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

pedido      monto  exists        not_refunded  in_window     amt<=total    amt<=limit    veredicto
--------------------------------------------------------------------------------------------------
A-1001      50.00  ok            ok            ok            ok            ok            EJECUTA
A-1002     120.00  ok            ok            x             ok            x             BLOQUEA
A-1003      30.00  ok            x             ok            ok            ok            BLOQUEA
A-9999      40.00  x             -             -             -             -             BLOQUEA
A-1005    5000.00  ok            ok            ok            x             x             BLOQUEA
A-1005      75.00  ok            ok            ok            ok            ok            EJECUTA

Ejecutadas : 2/6
Bloqueadas : 4/6  (contenidas por al menos una regla)

Lee la matriz fila por fila, porque cada una cuenta una historia distinta de cómo una propuesta puede violar la política.

A-1001 ($50): todas ok → EJECUTA. El pedido existe, no fue reembolsado, está dentro de la ventana (3 días ≤ 30), el monto no excede el total ($50 ≤ $50) ni el límite ($50 ≤ $100). Cinco ok, veredicto EJECUTA. Es la comanda que pasa la lista de verificación entera: un reembolso legítimo que la cáscara aprueba con confianza, porque cumple cada regla.

A-1002 ($120): falla in_window y amt<=limit → BLOQUEA. Aquí la matriz muestra algo valioso: una sola propuesta puede violar varias reglas a la vez. El pedido se entregó hace 45 días (fuera de la ventana de 30 → in_window es x) y el monto de $120 excede el límite del agente de $100 (amt<=limit es x). Dos razones independientes para bloquear. La cáscara no necesita elegir "la" razón: basta con que alguna regla falle para que el veredicto sea BLOQUEA. Ver las dos x es útil para el diagnóstico —dice todo lo que está mal, no solo lo primero—.

A-1003 ($30): falla not_refunded → BLOQUEA. El pedido existe, está dentro de la ventana, el monto es razonable... pero ya fue reembolsado (not_refunded es x). Sin esta regla, sería un doble reembolso: pagar dos veces por el mismo pedido. Es una violación que no tiene nada que ver con el monto ni con la ventana; es puramente de estado. Muestra por qué la lista tiene que ser exhaustiva: cada regla protege contra un riesgo que las otras no ven.

A-9999 ($40): falla exists, el resto - → BLOQUEA. El pedido no existe en la fuente de verdad —una alucinación del modelo, que inventó un order_id—. Fíjate en las marcas: exists es x, y las otras cuatro son - (no aplica). Esto es deliberado y correcto: si el pedido no existe, no tiene sentido preguntar por su ventana o su total —no hay pedido cuyo total consultar—. La validación corta en seco cuando falla una precondición fundamental, en vez de inventar respuestas para reglas que no se pueden evaluar. Preguntar "¿el monto excede el total?" de un pedido inexistente no tiene respuesta; marcar - lo dice honestamente.

A-1005 ($5000): falla amt<=total y amt<=limit → BLOQUEA. El pedido existe, no fue reembolsado, está dentro de la ventana... pero el monto de $5000 excede tanto el total del pedido ($75) como el límite del agente ($100). Dos x, otra vez varias reglas fallando juntas. Este es el refund de $5000 que ha aparecido en toda la guía: un monto absurdo que un solo error del modelo desembolsaría, contenido aquí por dos reglas independientes de la política.

A-1005 ($75): todas ok → EJECUTA. Mismo pedido que la fila anterior, monto distinto. $75 no excede el total ($75 ≤ $75) ni el límite ($75 ≤ $100), y las otras reglas pasan. Veredicto EJECUTA. Muestra que la cáscara no bloquea pedidos ni clientes: valida acciones. El mismo pedido A-1005 produce un bloqueo con $5000 y una ejecución con $75, porque lo que la política evalúa es la acción concreta, no el pedido en abstracto.

Dos ejecutadas, cuatro contenidas. El resultado final es el mismo que la lección 1, pero ahora sabes exactamente por qué: la matriz muestra, regla por regla, qué contuvo cada propuesta. Y la lección de diseño es la naturaleza conjuntiva de la validación: una acción se ejecuta solo si pasa todas las reglas; basta que una falle para bloquear. Cada regla es una pared independiente, y el reembolso tiene que pasar por todas para tocar el dinero.

Profundización: la lista de verificación como garantía

El ejemplo mostró la matriz; vale la pena entender las propiedades que hacen de esta validación una garantía y no una sugerencia.

La validación es conjuntiva: se pasan todas las reglas o ninguna ejecución. El veredicto es all(v == "ok" for v in row.values()) —una conjunción—. Esto no es un detalle de implementación: es la esencia de la garantía. Si la validación fuera "pasa la mayoría" o "pasa la más importante", cada regla que no se exige se vuelve un riesgo abierto. La política protege solo si cada una de sus reglas es obligatoria, porque cada una tapa un agujero distinto: exists tapa las alucinaciones de pedidos, not_refunded tapa los dobles reembolsos, in_window tapa los reembolsos fuera de plazo, amt<=total y amt<=limit tapan los montos absurdos. Saltarse una es dejar su agujero abierto. Por eso la lista se pasa entera o no se ejecuta.

El corte en seco evita evaluar lo inevaluable. Cuando exists falla, la función devuelve de inmediato, marcando el resto como -. Esto es correcto por dos razones. La técnica: no puedes consultar order["total"] de un pedido que no está en ORDERS —intentarlo lanzaría un error—. Y la conceptual: preguntar "¿el monto excede el total?" de un pedido inexistente no tiene una respuesta con sentido; - (no aplica) es más honesto que forzar un ok o un x. Hay un orden natural en las reglas: primero las precondiciones (¿existe el objeto sobre el que actúo?), luego las reglas que dependen de él (¿su estado?, ¿su monto?). El corte en seco respeta ese orden.

   La validacion es una LISTA DE VERIFICACION conjuntiva (pasa TODAS o bloquea):

   propuesta: {refund, order_id, amount}
        │
        ▼
   ┌──────────────┐  no → BLOQUEA (y no evalua el resto: '-')
   │ existe?       │──────────────────────────────────────────┐
   └──────────────┘                                            │
        │ si                                                   │
        ▼                                                       │
   ┌──────────────┐  no → BLOQUEA                               │
   │ no reembolsado?│─────────────────────────┐                │
   └──────────────┘                           │                │
        │ si                                   │                │
        ▼                                       ▼                ▼
   ┌──────────────┐  no → BLOQUEA        cualquier 'x' en la lista
   │ en ventana?   │────────────►        significa: NO se ejecuta
   └──────────────┘
        │ si          (amt<=total y amt<=limit siguen igual)
        ▼
   todas 'ok' ──► EJECUTA  (el dinero se mueve solo aqui)

La distinción con el módulo 4, ahora definitiva. Toda propuesta de esta matriz es un JSON impecable —{action: "refund", order_id: "A-1005", amount: 5000.00} está perfectamente bien formado—. Un guardrail de esquema del módulo 4 las aprobaría todas: son salidas válidas, bien tipadas, sin contenido prohibido. Y sin embargo cuatro de las seis violan la política. Esto es lo que M4 no puede ver y M6 sí: M4 valida que la salida sea una salida válida; M6 valida que la acción sea ejecutable según las reglas del negocio. Un monto de $5000 es un número perfectamente válido en cualquier esquema; solo la regla amt<=limit, que conoce la política, lo rechaza. Las dos compuertas van en serie: el guardrail de M4 primero (¿es una salida bien formada?), la validación de M6 después (¿es una acción que cumple la política?). Ninguna reemplaza a la otra.

Dónde termina esta lección y empieza el dominio. Es crucial no cruzar la frontera. Esta lección enseña que la lista de verificación existe, se corre antes de ejecutar, y se pasa entera. Lo que no enseña —y lo que pertenece a las guías de dominio de Mercado— es cuáles deben ser las reglas: si el límite es $100 o $200, si la ventana es 30 o 60 días, si hay excepciones para clientes premium, cómo se calculan los días desde la entrega considerando zonas horarias, qué pasa con reembolsos parciales. Esas son decisiones de negocio, y cambiarlas no cambia el patrón: el patrón es que haya una capa determinista que valide la acción contra la política vigente, sea cual sea. Si mañana Mercado sube el límite a $200, cambias MAX_REFUND y el patrón sigue idéntico. La cáscara es la arquitectura; las reglas concretas son el dominio.

Errores comunes

Validar "la mayoría" de las reglas o "la más importante", no todas. Qué pasa: por simplicidad o por prisa, la validación exige el monto pero no chequea si el pedido ya se reembolsó, o valida la ventana pero no verifica que el pedido exista. Cada regla que no se exige es un agujero: el día que llega una propuesta que viola exactamente esa regla, pasa. Por qué pasa: cada regla parece cubrir un caso raro, y se subestima. Cómo detectarlo: si tu veredicto no es una conjunción de todas las reglas de la política, tienes agujeros. Cómo corregirlo: la validación es conjuntiva —se pasan todas o se bloquea—. Cada regla tapa un riesgo que las otras no ven; ninguna es opcional.

Evaluar reglas sobre un objeto que no existe. Qué pasa: la validación asume que el pedido existe y va directo a chequear su monto o su ventana, sin verificar primero que esté en la base de datos. Cuando el modelo alucina un order_id, el código intenta leer los datos de un pedido inexistente y o lanza un error (y se cae) o —peor— usa un valor por defecto y valida contra basura. Por qué pasa: se olvida que el order_id viene de un componente que puede alucinar. Cómo detectarlo: ¿tu validación chequea la existencia del objeto antes de leer sus propiedades? Si no, un identificador alucinado la rompe. Cómo corregirlo: ordena las reglas con las precondiciones primero (¿existe?) y corta en seco si fallan, marcando el resto como no aplicable, como en el ejemplo.

Meter las reglas en el prompt en vez de en la cáscara. Qué pasa: en lugar de la lista de verificación en código, la política vive en el prompt —"reembolsa solo dentro de 30 días, nunca más de $100, nunca un pedido ya reembolsado"— y se confía en que el modelo la respete. La mayoría de las veces lo hace, pero el prompt es una sugerencia a un componente probabilístico: tarde o temprano propone $5000 o un pedido ya reembolsado igual. Por qué pasa: poner la regla en el prompt es rápido y parece funcionar en las pruebas. Cómo detectarlo: si tu única barrera contra un reembolso fuera de política es una instrucción en el prompt, tienes una probabilidad, no una garantía. Cómo corregirlo: la política vive en código determinista —la lista de verificación de esta lección—, que se cumple el 100% de las veces. El prompt puede pedirle al modelo que proponga dentro de política; la garantía la da el if, no el prompt.

Confundir validar el contenido (M4) con validar la acción (M6). Qué pasa: el equipo puso un guardrail de esquema que verifica que la salida sea un JSON bien formado con los campos correctos, y cree que con eso valida el reembolso. Pero {action: "refund", amount: 5000, order_id: "A-9999"} pasa el guardrail —es JSON perfecto— y aun así es una acción que reembolsa un monto absurdo de un pedido inexistente. Por qué pasa: se confunde "salida válida" con "acción válida". Cómo detectarlo: pregúntate si una salida perfectamente bien formada podría violar la política de negocio; si la respuesta es sí, te falta la validación de reglas de M6. Cómo corregirlo: después del guardrail de esquema (M4), corre la lista de verificación de reglas de negocio (M6). Son compuertas en serie, no alternativas.

Ejercicios

Ejercicio 1 — Lee la matriz. Sin correr el código, para una propuesta {refund, order_id: "A-1002", amount: 60.00} (recuerda: A-1002 tiene total $120, 45 días desde la entrega, no reembolsado), predice el resultado de cada una de las cinco reglas y el veredicto final. Explica por qué.

Ver solución
  • exists → ok: A-1002 está en ORDERS.
  • not_refunded → ok: A-1002 tiene refunded: False.
  • in_window → x: se entregó hace 45 días, y 45 > 30 (RETURN_WINDOW_DAYS). Falla.
  • amt<=total → ok: $60 ≤ $120 (el total del pedido). Pasa.
  • amt<=limit → ok: $60 ≤ $100 (el límite del agente). Pasa.
  • Veredicto → BLOQUEA. Cuatro reglas pasan, pero in_window falla, y la validación es conjuntiva: basta una x para bloquear.

La lección de esta fila: un reembolso puede tener un monto perfectamente razonable ($60, dentro del total y del límite) y aun así bloquearse por una razón que no tiene nada que ver con el monto —estar fuera de la ventana—. Por eso la lista tiene que ser exhaustiva: el monto correcto no compensa la ventana vencida. Cada regla protege contra un riesgo distinto, y todas son obligatorias.

Ejercicio 2 — La frontera con el dominio. El equipo de negocio de Mercado decide cambiar la política: subir el límite del agente de $100 a $250 y acortar la ventana de 30 a 15 días. ¿Qué cambia en el código de la cáscara y qué no cambia? Usa la respuesta para explicar la frontera entre esta guía y las guías de dominio.

Ver solución

Lo que cambia son dos valores: MAX_REFUND = 250.00 y RETURN_WINDOW_DAYS = 15. Nada más. La estructura de la validación —que corre las cinco reglas, que es conjuntiva, que corta en seco si el pedido no existe, que ejecuta solo si pasa todas— queda idéntica. El patrón no se toca; solo se ajustan los parámetros de la política.

Esto ilustra la frontera exactamente. Las guías de dominio deciden cuáles son los valores y las reglas de la política: cuánto es el límite, cuántos días la ventana, si hay excepciones para clientes premium, cómo se cuentan los días. Son decisiones de negocio que cambian con el negocio. Esta guía (arquitectura) enseña el patrón de contención: que exista una capa determinista que valide la acción contra la política vigente, antes de ejecutar, pasándola entera. El patrón es estable aunque la política cambie: subir el límite a $250 o bajar la ventana a 15 días no cambia que haya una lista de verificación conjuntiva corriendo antes de mover el dinero. Por eso decimos que la cáscara es la arquitectura y las reglas concretas son el dominio: lo primero lo enseñamos aquí, lo segundo lo deciden las guías de dominio y el negocio.

Ejercicio 3 — Una regla nueva. Mercado quiere agregar una regla: un mismo cliente no puede recibir más de 3 reembolsos en 30 días (para frenar el abuso). Explica por qué esta regla no se puede validar solo con los datos del pedido, qué necesitaría la cáscara para evaluarla, y a qué compuerta del módulo pertenece. Bonus: ¿por qué es peligroso dejar esta regla en el prompt?

Ver solución

Esta regla no se puede validar solo con los datos del pedido porque depende del historial del cliente, no del pedido en cuestión: hay que saber cuántos reembolsos recibió ese cliente en los últimos 30 días. La cáscara necesitaría acceso a una fuente de verdad adicional —un registro de reembolsos por cliente— para contar los reembolsos recientes y compararlos con el límite de 3. Es una regla de negocio más, y pertenece a la compuerta de validación de reglas (esta lección, L5): se evalúa sobre la acción propuesta, contra la política, antes de ejecutar. Se agregaría a la lista de verificación como una sexta regla (refunds_last_30d < 3), y como toda la lista es conjuntiva, un reembolso solo se ejecutaría si además pasa esta.

Es peligroso dejarla en el prompt porque el modelo no tiene forma confiable de saber cuántos reembolsos recibió el cliente —no lleva esa cuenta, y aunque se la pusieras en el contexto, es un componente probabilístico que podría contar mal o ignorar la instrucción—. Una regla anti-abuso que depende de que el modelo "recuerde" y "respete" un conteo es una regla sin garantía: exactamente el tipo de control que un atacante que quiere abusar del sistema buscaría saltarse. La cuenta la lleva el código (una consulta determinista al registro de reembolsos), y el if refunds_last_30d >= 3: block da la garantía. Otra vez el principio del módulo: la regla que protege el dinero vive en la cáscara determinista, no en el prompt.

Resumen y siguiente paso

En esta lección llegaste al corazón del módulo: validar la propuesta contra las reglas de negocio. Para las acciones que pasaron el canal (L3) y la capability (L4), la cáscara corre una lista de verificación de reglas deterministas —pedido existe, no reembolsado, dentro de la ventana, monto ≤ total, monto ≤ límite— y ejecuta solo si pasa todas. Lo viste regla por regla en una matriz: dos propuestas pasaron las cinco y se ejecutaron; cuatro fallaron al menos una y se bloquearon, cada una con su razón visible. Aprendiste que la validación es conjuntiva (todas o ninguna, porque cada regla tapa un riesgo distinto), que corta en seco cuando falla una precondición (el pedido inexistente no tiene ventana que evaluar), y que se distingue definitivamente del guardrail de M4 (una salida puede ser un JSON impecable y una acción que viola la política). Y marcaste la frontera con el dominio: aquí el patrón de la lista de verificación; cuáles son las reglas es decisión de negocio.

Antes de avanzar deberías poder: explicar por qué la validación es conjuntiva; ordenar las reglas con las precondiciones primero y cortar en seco; distinguir validar el contenido (M4) de validar la acción (M6); y ubicar la frontera entre el patrón (esta guía) y las reglas concretas (dominio).

La lección 6 da un paso atrás y hace una pregunta de diseño más amplia: ¿cuánto debe decidir el modelo en primer lugar? Ya sabes contener lo que el modelo propone; pero cuanta menos superficie tenga el LLM sobre acciones que tocan estado, menos hay que contener. Vas a ver, medido, la diferencia entre un fat core —donde el modelo decide muchas cosas que tocan dinero— y un thin core —donde el modelo propone una sola cosa y el resto son reglas deterministas—: sacar decisiones del núcleo reduce la superficie que puede alucinar, y esa reducción se cuenta. La disciplina de mantener el núcleo probabilístico pequeño, que da nombre a toda la guía.

Recursos

  • Martin Fowler y Bharani Subramaniam, "Emerging Patterns in Building GenAI Apps" — martinfowler.com/articles/gen-ai-patterns. El patrón de validar la acción de un modelo contra reglas deterministas antes de ejecutarla es una pieza central del mapa de contención que describe el artículo. En inglés.
  • Anthropic, "Building Effective Agents" (2024) — anthropic.com/engineering/building-effective-agents. Su énfasis en poner controles deterministas alrededor de las acciones que un agente propone, en vez de confiar en el modelo, es la base de la validación de reglas de esta lección. En inglés.
  • Chip Huyen, AI Engineering (O'Reilly, 2024). Su tratamiento de la validación y los controles sobre las salidas de un modelo con efectos secundarios cubre por qué la política vive en código y no en el prompt. En inglés.
  • Documentación de Claude, tool usedocs.anthropic.com. Cuando el modelo propone una llamada a herramienta, tu código decide si ejecutarla; las reglas de negocio que corres antes de ejecutar son la lista de verificación de esta lección. Sin fijarte en una versión de modelo específica. En inglés.