Módulo 6: La cáscara determinista
Capabilities acotadas
Descripción
La lección 3 mostró que el modelo propone un comando de un menú, y que las acciones fuera del menú se rechazan por definición —delete_database no existía para la cáscara porque no estaba en ACTION_MENU—. Esa lección lo trató como una consecuencia del formato. Esta lección lo convierte en un principio de diseño con nombre propio: capabilities acotadas, la versión AI-native del principio de least privilege (mínimo privilegio) de la seguridad clásica. La idea es simple y poderosa: el modelo solo puede proponer de un menú cerrado y mínimo de acciones permitidas, y ese menú se diseña dándole lo menos posible —solo lo que necesita para su trabajo, y nada más—.
Lo que hace esta lección distinta de la anterior es la pregunta que responde: no cómo se rechaza lo que está fuera del menú (eso es el chequeo de canal de L3), sino qué tan grande debe ser el menú. Y la respuesta se mide: el radio de daño de un agente —cuántas operaciones irreversibles y peligrosas puede alcanzar una alucinación— es exactamente el número de esas operaciones que caen dentro de su menú. Con un menú acotado, ese número es cero. Con un menú amplio, "para que el agente sea útil", cada operación peligrosa que agregas es una que una alucinación puede ahora alcanzar. Vas a ver, ejecutado, cómo el mismo lote de propuestas del modelo —que incluye acciones destructivas alucinadas— deja 0 operaciones peligrosas al alcance con un menú chico y 3 con un menú amplio.
Conexión con el módulo. Esta lección define qué puede proponer el modelo, entre la acción estructurada (L3, el formato) y la validación de reglas (L5, la política). Es una compuerta distinta de las reglas: las capabilities dicen "esta acción no existe para este agente" (un refund de $5000 y un delete_account fallan por razones diferentes —el primero por política, el segundo por capability—). La frontera con las guías de dominio: qué capabilities necesita cada agente es una decisión de diseño del sistema; aquí tratamos el principio (dar el mínimo) y cómo medir su efecto. La frontera con la guía de seguridad: el least privilege a fondo —modelos de permisos, roles, escalamiento— es tema de seguridad; aquí lo aplicamos al menú de acciones de un LLM.
Una analogía: las llaves que le das al empleado nuevo
Cuando contratas a un empleado nuevo en una tienda, decides qué llaves le das. Podrías darle el llavero completo del gerente —la caja fuerte, la bodega, el sistema de nómina, la puerta trasera, todo— "para que pueda ayudar en lo que sea". O podrías darle solo las llaves que su trabajo necesita: la caja registradora y la puerta del almacén de productos, digamos. Las dos opciones tienen un empleado igual de capaz y de bien intencionado. La diferencia está en qué puede llegar a hacer cuando se equivoca —o cuando alguien lo engaña—.
Con el llavero completo, un empleado nuevo confundido puede, sin mala intención, abrir la caja fuerte y dejarla abierta, mover algo en la nómina, o dar acceso a la bodega a quien no debe. El radio de daño de su error es enorme, porque tiene llaves para todo. Con las llaves mínimas, ese mismo empleado, igual de confundido, no puede tocar la caja fuerte ni la nómina —no tiene la llave—; su error, en el peor caso, es un problema en la caja registradora, acotado y recuperable. No le diste menos llaves porque desconfíes de él; se las diste porque su error (inevitable, humano) hace menos daño cuando su acceso es mínimo.
El principio de seguridad tiene un nombre: least privilege, dar a cada quien el mínimo acceso que su función necesita. Y aplica idéntico a un LLM. El "llavero" del agente es su menú de capabilities. Darle capabilities amplias "para que sea útil" es darle el llavero del gerente: cada acción irreversible que agregas es una llave que una alucinación puede usar. Darle un menú mínimo es darle las llaves justas: cuando el modelo se equivoque —y se va a equivocar—, no tendrá la llave de la caja fuerte, porque nunca se la diste. El agente de soporte de Mercado necesita reembolsar, escalar y mandar mensajes; no necesita cambiar precios, banear vendedores ni dar de baja cuentas. Esas llaves, simplemente, no van en su llavero.
Ejemplo trabajado: el radio de daño crece con el menú
Vamos a medir el radio de daño. Definimos las capabilities otorgadas al agente de soporte —un menú acotado: refund, escalate_to_human, send_message— y un conjunto de operaciones irreversibles y peligrosas que existen en la plataforma pero que este agente no debería tocar jamás: delete_account, change_price, ban_user. Luego corremos un lote de acciones que el modelo propuso, que mezcla acciones dentro de sus capabilities con operaciones peligrosas que "alucinó" poder hacer. Medimos cuántas peligrosas quedan al alcance con el menú acotado, y comparamos contra un menú amplio que se las dio todas "para que el agente sea útil".
# Modulo 6, Leccion 4: capabilities acotadas (least privilege).
# El agente de soporte solo puede PROPONER de un MENU de acciones permitidas.
# Todo lo que caiga fuera del menu se rechaza, por mas que el LLM lo proponga.
# Sin red, sin API, sin claves. Datos fijos.
# Capabilities GRANTED al agente de soporte: un menu chico y acotado.
GRANTED = {"refund", "escalate_to_human", "send_message"}
# Operaciones IRREVERSIBLES que existen en la plataforma pero que este agente
# NO deberia poder tocar jamas.
DANGEROUS = {"delete_account", "change_price", "ban_user"}
def capability_check(action, granted):
return action in granted
# Lote que el nucleo probabilistico (SIMULADO) propuso. Mezcla acciones dentro
# de sus capabilities con operaciones peligrosas que "alucino" poder hacer.
PROPOSED = [
"refund",
"delete_account",
"change_price",
"escalate_to_human",
"ban_user",
"send_message",
]
print("=== Menu ACOTADO (narrow): el agente solo tiene 3 capabilities ===")
print(f" GRANTED = {sorted(GRANTED)}")
print()
print(f"{'accion propuesta':<22}{'verdicto'}")
print("-" * 42)
within = outside = 0
reached_dangerous_narrow = []
for action in PROPOSED:
ok = capability_check(action, GRANTED)
if ok:
within += 1
verdict = "PERMITIDA (en capabilities)"
else:
outside += 1
verdict = "RECHAZADA (fuera del menu)"
if action in DANGEROUS:
pass # con el menu acotado, ninguna peligrosa quedo al alcance
print(f"{action:<22}{verdict}")
print()
print(f" dentro de capabilities : {within}/{len(PROPOSED)}")
print(f" fuera del menu : {outside}/{len(PROPOSED)}")
print(f" operaciones peligrosas alcanzables : {len(reached_dangerous_narrow)}")
# --- Contraste: un menu AMPLIO "para que el agente sea util". ---
GRANTED_BROAD = GRANTED | DANGEROUS
reached_dangerous_broad = [a for a in PROPOSED
if a in DANGEROUS and capability_check(a, GRANTED_BROAD)]
print()
print("=== Menu AMPLIO (broad): se le dieron TODAS las capabilities ===")
print(f" GRANTED_BROAD = {sorted(GRANTED_BROAD)}")
print(f" operaciones peligrosas ahora alcanzables : {len(reached_dangerous_broad)}")
print(f" -> {sorted(reached_dangerous_broad)}")
print()
print("Regla: el radio de dano = cuantas operaciones irreversibles caen dentro")
print("del menu. Menu chico -> radio de dano chico.")
Qué esperar. Al correr el archivo, la salida es exactamente esta:
=== Menu ACOTADO (narrow): el agente solo tiene 3 capabilities ===
GRANTED = ['escalate_to_human', 'refund', 'send_message']
accion propuesta verdicto
------------------------------------------
refund PERMITIDA (en capabilities)
delete_account RECHAZADA (fuera del menu)
change_price RECHAZADA (fuera del menu)
escalate_to_human PERMITIDA (en capabilities)
ban_user RECHAZADA (fuera del menu)
send_message PERMITIDA (en capabilities)
dentro de capabilities : 3/6
fuera del menu : 3/6
operaciones peligrosas alcanzables : 0
=== Menu AMPLIO (broad): se le dieron TODAS las capabilities ===
GRANTED_BROAD = ['ban_user', 'change_price', 'delete_account', 'escalate_to_human', 'refund', 'send_message']
operaciones peligrosas ahora alcanzables : 3
-> ['ban_user', 'change_price', 'delete_account']
Regla: el radio de dano = cuantas operaciones irreversibles caen dentro
del menu. Menu chico -> radio de dano chico.
Compara los dos menús, porque la diferencia entre ellos es el radio de daño.
Con el menú acotado, las tres operaciones peligrosas se rechazan por definición. El modelo propuso delete_account, change_price y ban_user —tres alucinaciones destructivas—, y las tres se rechazaron con la misma razón: "fuera del menú". No porque una regla de negocio las evaluara y decidiera que estaban mal, sino porque no existen para este agente: no están en GRANTED. La cuenta final es la que importa: operaciones peligrosas alcanzables: 0. Por más que el modelo alucinara acciones destructivas, ninguna quedó al alcance, porque el llavero del agente no tiene esas llaves. Las tres acciones dentro de sus capabilities (refund, escalate_to_human, send_message) sí pasaron el chequeo —y seguirán a la validación de reglas de la lección 5—, pero las peligrosas se detuvieron aquí, en la puerta de las capabilities.
Con el menú amplio, las mismas tres alucinaciones ahora sí quedan al alcance. El único cambio fue el llavero: GRANTED_BROAD incluye delete_account, change_price y ban_user "para que el agente sea útil". Y de golpe, las operaciones peligrosas alcanzables suben de 0 a 3. El modelo es idéntico, propuso lo mismo; lo que cambió es que ahora esas acciones existen para el agente, así que una alucinación de change_price a $0.01, o de delete_account, ya no se rechaza en la puerta —tiene la llave—. Esta es la lección medida: el radio de daño no depende de cuán bueno sea el modelo, sino de cuántas operaciones peligrosas caen dentro de su menú. Ampliar el menú "por si acaso" no hace al agente más seguro ni más listo; solo le da más llaves a sus errores.
Cero y tres es toda la moraleja. El menú acotado deja cero operaciones peligrosas al alcance; el amplio, tres. Ninguna regla de negocio, ningún prompt mejor, ninguna validación adicional produjo esa diferencia: la produjo únicamente el tamaño del menú. Por eso las capabilities acotadas son la defensa más barata y más fuerte del módulo: no requieren entender la política ni verificar contra una fuente de verdad —requieren simplemente no darle al agente llaves que no necesita—. Una operación que no está en el menú no puede fallar, no puede alucinarse, no puede explotarse: para el agente, no existe.
Profundización: least privilege aplicado a lo que un LLM puede proponer
El ejemplo mostró el efecto; vale la pena entender el principio y sus consecuencias de diseño.
Las capabilities son una compuerta distinta de las reglas de negocio. Es tentador pensar "total, la validación de reglas (L5) va a atrapar las acciones malas de todos modos, ¿para qué acotar el menú?". Pero las dos compuertas responden preguntas diferentes y protegen contra cosas diferentes. Las reglas de negocio evalúan una acción que el agente puede tomar —"este reembolso, que sí es una acción del agente, ¿cumple la política?"—. Las capabilities deciden si la acción es siquiera del agente —"¿delete_account es algo que este agente puede proponer?"—. Un refund de $5000 y un delete_account son peligrosos por razones distintas: el primero es una acción legítima con un valor ilegítimo (lo atrapan las reglas); el segundo es una acción que el agente nunca debió poder proponer (lo atrapa la capability). Si solo tuvieras reglas de negocio, tendrías que escribir una regla para cada forma en que cada operación peligrosa podría estar mal —una tarea infinita—. Con capabilities acotadas, las operaciones que no otorgas simplemente no llegan a necesitar reglas: se rechazan antes.
El menú mínimo reduce la superficie que hay que asegurar. Cada capability que otorgas es una operación para la que tienes que pensar todos los modos en que podría salir mal, escribir sus reglas de validación, probar sus casos límite, y monitorear su uso. Un menú de tres acciones es tres operaciones que asegurar; un menú de diez es diez. El least privilege no solo reduce el radio de daño de las alucinaciones: reduce el trabajo de contención que tienes que hacer. Menos llaves, menos cerraduras que diseñar. Por eso "dale menos" no es una postura paranoica: es la que hace el sistema más simple y más seguro a la vez, que es una combinación rara y valiosa.
El radio de dano = operaciones IRREVERSIBLES dentro del menu del agente
Menu ACOTADO (least privilege): Menu AMPLIO ("por si acaso"):
GRANTED = { GRANTED = {
refund, ◄ necesarias refund,
escalate_to_human, para su escalate_to_human,
send_message, trabajo send_message,
} delete_account, ◄ una alucinacion
change_price, ahora tiene la
peligrosas al alcance: 0 ban_user, llave de estas
}
una alucinacion de peligrosas al alcance: 3
delete_account NO tiene
la llave -> rechazada el radio de dano crece con
cada llave de mas
Otorgar capabilities es una decisión de diseño, no un default. El error más común no es dar capabilities peligrosas a propósito, sino darlas por defecto —conectar el agente a una API que expone diez operaciones y no restringir cuáles puede usar—. El agente hereda todo lo que la API ofrece, y su menú termina siendo "lo que sea que el sistema pueda hacer" en vez de "lo que este agente necesita". La disciplina AI-native invierte el default: el menú empieza vacío, y agregas una capability solo cuando puedes justificar que este agente la necesita para su trabajo. Es la diferencia entre "quito lo peligroso de una lista larga" (frágil: siempre se te escapa algo) y "agrego solo lo necesario a una lista vacía" (robusto: lo que no justificaste, no está).
Acotar el menú no limita al agente en lo que importa. La objeción natural: "pero si le doy menos capabilities, el agente resuelve menos casos". En la práctica, casi nunca. El agente de soporte resuelve el 99% de sus casos con reembolsar, escalar y mandar mensajes; los casos raros que necesitarían change_price o delete_account son precisamente los que deben pasar por un humano —vía escalate_to_human—, no por una alucinación del modelo. Acotar el menú no le quita al agente su trabajo; le quita el poder de causar daños que ni siquiera son parte de su trabajo. Y para lo que sí necesita y no puede hacer, la capability correcta es escalate_to_human: la salida de emergencia que convierte "no tengo la llave" en "se lo paso a quien la tiene".
Errores comunes
Darle al agente capabilities amplias "para que sea útil". Qué pasa: para que el agente "pueda resolver cualquier cosa", se le da un menú enorme —reembolsar, dar crédito, cambiar precios, cancelar, dar de baja cuentas—. Cada operación irreversible extra es una llave que una alucinación puede usar; el día que el modelo propone change_price a $0.01 o delete_account, esa acción estaba en el menú, así que la capability la deja pasar. Por qué pasa: se confunde "útil" con "poderoso", y se teme que un menú chico limite al agente. Cómo detectarlo: cuenta las operaciones irreversibles en el menú de tu agente; ese número es tu radio de daño. Cómo corregirlo: aplica least privilege —empieza con el menú vacío y agrega solo lo que el agente necesita para su trabajo, y usa escalate_to_human para lo que no—. El ejemplo lo mide: 3 capabilities dejan 0 operaciones peligrosas al alcance; el menú amplio deja 3.
Heredar capabilities por defecto de la API a la que conectas el agente. Qué pasa: conectas el agente a un servicio que expone muchas operaciones y no restringes cuáles puede invocar, así que hereda todo el catálogo. Su menú termina siendo "lo que sea que el sistema pueda hacer". Por qué pasa: es el default —no restringir es más fácil que restringir— y no se ve el problema hasta que el modelo alucina una operación que nunca pensaste que podría alcanzar. Cómo detectarlo: si no puedes enumerar el menú exacto de tu agente en una lista corta y explícita, probablemente heredó de más. Cómo corregirlo: define un GRANTED explícito y mínimo, y rechaza todo lo que no esté en él, sin importar qué exponga la API subyacente. El menú del agente lo defines tú, no la API.
Confiar en el prompt para acotar lo que el modelo puede hacer. Qué pasa: en vez de un menú cerrado en código, se le dice al modelo en el prompt "solo puedes reembolsar, escalar y mandar mensajes; nunca cambies precios ni des de baja cuentas". La mayoría de las veces obedece; pero el prompt es una sugerencia a un componente probabilístico, y el día que el modelo propone delete_account igual —porque un usuario lo manipuló, o simplemente por no determinación—, si la capability existe en el sistema, se ejecuta. Por qué pasa: poner el límite en el prompt es fácil y parece funcionar. Cómo detectarlo: si tu única barrera contra una acción peligrosa es una instrucción en el prompt, tienes una probabilidad, no una garantía. Cómo corregirlo: el menú vive en código —if action not in GRANTED: reject—, que se cumple el 100% de las veces. El prompt puede pedirle al modelo que se mantenga en su menú (ayuda a que proponga bien), pero la garantía la da el chequeo de capability, no el prompt. Es el mismo principio que en L1: la política va en código, no en el prompt.
Ejercicios
Ejercicio 1 — Qué llaves darle. Estás diseñando el menú de capabilities de tres agentes de Mercado. Para cada uno, propón un menú mínimo y di qué operación peligrosa no le darías y por qué: (a) el agente de soporte al cliente; (b) el generador "describe tu producto" para vendedores; (c) el asistente de búsqueda semántica.
Ver solución
- (a) Agente de soporte: menú mínimo
{refund, escalate_to_human, send_message}. No le daríaschange_pricenidelete_accountniban_user: no son parte de resolver un caso de soporte, y si alguna vez un caso los necesitara, debe ir a un humano víaescalate_to_human. Radio de daño acotado a reembolsos (que además las reglas de L5 validan). - (b) Generador "describe tu producto": menú mínimo
{propose_description}(o{save_draft}). No le daríaspublishdirecto niset_price: el generador propone un texto; publicarlo o fijar el precio son decisiones que pasan por la validación de contenido (M4) y por reglas/humano, no por el modelo. Menos aún tocaría cuentas o pedidos, que no tienen nada que ver con su trabajo. - (c) Asistente de búsqueda semántica: menú mínimo
{search}—o incluso ninguna capability de acción, porque su trabajo es leer y devolver resultados, no hacer nada—. No le darías ninguna operación que toque estado (nada derefund,change_price,add_to_cartautomático): una búsqueda no debe poder modificar nada. Su radio de daño ideal es cero operaciones de estado.
El patrón: el menú se deriva del trabajo del agente, no de "lo que podría llegar a ser útil". Lo que el agente no necesita para su trabajo, no va en su llavero; y para lo raro que sí necesitaría, la capability correcta suele ser escalar a un humano.
Ejercicio 2 — El radio de daño. En el ejemplo, el menú acotado dejó 0 operaciones peligrosas al alcance y el amplio dejó 3. Supón que el modelo mejora y alucina acciones destructivas la mitad de seguido que antes. ¿Cómo cambia el radio de daño de cada menú? Usa la respuesta para argumentar por qué acotar el menú es mejor defensa que mejorar el modelo.
Ver solución
El radio de daño no cambia con la calidad del modelo: sigue siendo 0 para el menú acotado y 3 para el amplio. El radio de daño es cuántas operaciones peligrosas caen dentro del menú, y eso depende del menú, no de con qué frecuencia el modelo alucina. Mejorar el modelo reduce cuántas veces propone una acción destructiva, pero no cambia cuáles puede alcanzar: si delete_account está en el menú, una sola alucinación —por rara que sea— la alcanza; si no está, ninguna la alcanza, por seguido que el modelo alucine.
El argumento: mejorar el modelo baja la frecuencia del error, pero a escala cualquier frecuencia positiva termina ocurriendo, y una sola ejecución de delete_account es un desastre. Acotar el menú, en cambio, lleva el radio de daño a cero de forma dura, independiente de la frecuencia: la operación peligrosa no existe para el agente. Por eso las capabilities acotadas son una defensa categórica (elimina la posibilidad) y "mejor modelo" es una defensa estadística (reduce la probabilidad). Cerca de operaciones irreversibles, quieres la categórica. Es el mismo argumento que la cáscara del módulo 1: la garantía la da la contención, no la calidad del núcleo.
Ejercicio 3 — Capability o regla. Para cada uno de estos casos, di si la acción debe bloquearse por capability (no está en el menú del agente) o por regla de negocio (está en el menú pero viola la política), y explica la diferencia: (a) el agente de soporte propone delete_account; (b) el agente de soporte propone refund de $5000; (c) el agente de soporte propone refund de un pedido ya reembolsado; (d) el agente de soporte propone change_price.
Ver solución
- (a)
delete_account→ capability. Dar de baja cuentas no es parte del trabajo del agente de soporte; no está en su menú. Se rechaza porque la acción no existe para este agente, no porque una regla la evalúe. Compuerta de capabilities (esta lección). - (b)
refundde $5000 → regla de negocio. Reembolsar sí es una acción del agente (está en el menú); lo que está mal es el valor —$5000 excede el límite—. La acción es legítima; el monto no. Compuerta de reglas de negocio (lección 5). - (c)
refundde un pedido ya reembolsado → regla de negocio. Otra vez,refundestá en el menú; lo que viola la política es el estado del pedido (ya reembolsado). La acción es del agente; las circunstancias la bloquean. Compuerta de reglas (lección 5). - (d)
change_price→ capability. Cambiar precios no es parte del trabajo del agente de soporte; no está en su menú. Se rechaza como capability, igual que (a).
La diferencia clave: las capabilities preguntan "¿es esta acción del agente?" (compuerta de L4); las reglas preguntan "¿esta acción del agente cumple la política?" (compuerta de L5). (a) y (d) fallan porque la acción no es del agente; (b) y (c) fallan porque, siendo del agente, violan la política. Un buen diseño tiene las dos compuertas: las capabilities recortan qué puede proponer, las reglas validan cómo.
Resumen y siguiente paso
En esta lección convertiste el menú de la lección 3 en un principio de diseño: las capabilities acotadas, el least privilege aplicado a lo que un LLM puede proponer. El modelo solo puede proponer de un menú cerrado y mínimo; lo que no está en él se rechaza por definición. Lo mediste: el mismo lote de propuestas del modelo —con tres alucinaciones destructivas— dejó 0 operaciones peligrosas al alcance con un menú acotado y 3 con un menú amplio "para que el agente sea útil". Viste que el radio de daño depende del tamaño del menú, no de la calidad del modelo; que las capabilities son una compuerta distinta de las reglas de negocio (una acción no está en el menú vs una acción del menú viola la política); que el menú se diseña empezando vacío y agregando solo lo necesario; y que acotar no limita al agente en lo que importa, porque lo raro que necesitaría pasa por escalate_to_human.
Antes de avanzar deberías poder: enunciar el principio de least privilege y aplicarlo al menú de un agente; explicar por qué el radio de daño es el número de operaciones peligrosas dentro del menú; distinguir un bloqueo por capability de uno por regla de negocio; y argumentar por qué el menú va en código y no en el prompt.
La lección 5 llega al corazón del módulo: validar la propuesta contra las reglas de negocio. Ya sabes que el modelo propone un comando bien formado (L3) de un menú acotado (L4); ahora, para las acciones que sí son del agente —un refund—, hay que decidir si cumplen la política antes de ejecutar. Vas a ver, ejecutado con una matriz regla por regla, cómo cada propuesta de reembolso se valida contra las cinco reglas de la política —pedido existe, no reembolsado, dentro de la ventana, monto ≤ total, monto ≤ límite— y se ejecuta solo si las pasa todas. Es la compuerta que atrapó el refund de $5000 del principio de la guía, ahora abierta regla por regla.
Recursos
- El principio de least privilege (mínimo privilegio), pilar de la seguridad clásica (Saltzer y Schroeder, 1975): dar a cada componente el conjunto mínimo de permisos que necesita para su función, y nada más. Esta lección lo aplica al menú de acciones de un LLM. Cualquier referencia introductoria de seguridad o el material de OWASP lo cubre. En inglés.
- Anthropic, "Building Effective Agents" (2024) — anthropic.com/engineering/building-effective-agents. Su recomendación de dar al agente un conjunto acotado y bien definido de herramientas, en vez de acceso amplio, es exactamente el principio de capabilities acotadas de esta lección. En inglés.
- Documentación de Claude, tool use — docs.anthropic.com. Al definir las herramientas que un modelo puede invocar, defines su menú de capabilities; la práctica de exponer solo las herramientas necesarias es el least privilege en acción. Sin fijarte en una versión de modelo específica. En inglés.
- Martin Fowler y Bharani Subramaniam, "Emerging Patterns in Building GenAI Apps" — martinfowler.com/articles/gen-ai-patterns. El tratamiento de los límites y permisos de un agente ubica las capabilities acotadas dentro del mapa de patrones de contención. En inglés.