Un agente no es confiable si sus herramientas no tienen límites
La confiabilidad de un agente no depende sólo del modelo. Depende de que sus herramientas tengan contratos, permisos, validación, idempotencia, límites de ejecución y mecanismos de aprobación.
Un agente puede tomar una buena decisión y aun así causar daño. Basta con que la herramienta que ejecuta esa decisión tenga permisos excesivos, acepte argumentos ambiguos, reintente sin control o no pueda distinguir entre una operación nueva y una duplicada.
Por eso la confiabilidad de un agente no depende únicamente de su modelo, su prompt o su capacidad de razonamiento. También depende del runtime que limita sus acciones y de las herramientas que conectan ese razonamiento con el mundo externo.
La tesis de este artículo es simple: la autonomía de un agente debe estar limitada por el runtime, no por la buena voluntad del modelo.
Esto conecta con los problemas que he explorado en artículos anteriores. El estado define en qué condiciones puede actuar. Un agente persistente necesita coordinar operaciones que pueden durar más que una inferencia. La voz introduce comandos ambiguos e interrupciones. La evaluación debe medir el uso de herramientas como una capacidad independiente, no sólo el resultado final.
El modelo no debería tener acceso directo al mundo
El patrón inicial de muchos agentes se ve así:
Usuario → Modelo → Tool → Sistema externoEs una simplificación útil para una demo, pero oculta una frontera importante. El modelo produce una propuesta; la herramienta produce un efecto. Entre ambos debería existir una capa que valide la propuesta, compruebe permisos, aplique límites y registre lo que ocurrió.
Usuario / Evento
↓
Agent Runtime
↓
Modelo propone
↓
Policy + Authorization + Validation
↓
Tool Runtime
↓
Sistema externo
↓
Resultado verificado y auditadoEl modelo no debería decidir por sí solo si una acción está autorizada. Tampoco debería recibir credenciales amplias y construir libremente una llamada HTTP. El runtime debe ser dueño de la ejecución y tratar la salida del modelo como una entrada no confiable, aunque provenga de un modelo excelente.
Esta separación no elimina los errores del modelo. Reduce cuánto puede afectar un error. Esa es una diferencia fundamental entre intentar que el modelo “se comporte bien” y diseñar el sistema para que una mala propuesta tenga un radio de impacto limitado.
Una tool no es una función: es una frontera de seguridad
En código local, una función puede recibir argumentos y devolver un valor. En un agente, una tool puede leer datos privados, enviar mensajes, cambiar una configuración, ejecutar código, crear una compra o eliminar información.
Su contrato debe describir más que el nombre y los parámetros. Como mínimo, conviene conocer:
- qué capacidad expone;
- qué recursos puede leer o modificar;
- qué identidad y tenant utiliza;
- qué permisos requiere;
- si tiene efectos secundarios;
- si es reversible o compensable;
- si es idempotente;
- cuánto tiempo y recursos puede consumir;
- qué evidencia confirma que terminó correctamente.
Una herramienta llamada send_email no tiene el mismo riesgo que search_documents. Una herramienta que busca datos puede tener problemas de confidencialidad; una que envía correo puede generar un efecto externo irreversible; una que ejecuta comandos puede combinar ambos riesgos.
El diseño debería clasificar las tools por capacidad y riesgo, no sólo por la forma de su API:
| Tipo | Ejemplo | Control típico |
|---|---|---|
| Lectura | Consultar disponibilidad | Scope de lectura, filtro por tenant y límites de resultados. |
| Escritura reversible | Crear un borrador | Autorización, validación y posibilidad de cancelar. |
| Comunicación externa | Enviar un correo | Vista previa, destinatarios limitados y aprobación. |
| Acción sensible | Transferir dinero o borrar datos | Confirmación explícita, límites estrictos y auditoría. |
| Ejecución arbitraria | Ejecutar shell o código | Sandbox, allowlist, aislamiento y presupuesto. |
Los contratos deben ser estrictos
Un esquema JSON ayuda a validar tipos, campos requeridos y valores permitidos. No basta por sí solo, pero es una primera barrera contra argumentos incompletos o demasiado amplios.
const createDraftEmail = {
name: "create_draft_email",
description: "Crea un borrador. No envía el mensaje.",
inputSchema: {
type: "object",
additionalProperties: false,
required: ["to", "subject", "body"],
properties: {
to: {
type: "array",
minItems: 1,
maxItems: 10,
items: { type: "string", format: "email" }
},
subject: { type: "string", minLength: 1, maxLength: 200 },
body: { type: "string", minLength: 1, maxLength: 20_000 }
}
}
};El esquema protege la forma de la entrada. El runtime todavía debe validar reglas de negocio: si los destinatarios pertenecen al tenant correcto, si el usuario puede comunicarse con ellos, si el mensaje contiene información sensible y si crear el borrador es una acción permitida en el estado actual.
También importa el nombre y la descripción. Una descripción ambigua aumenta la probabilidad de que el modelo elija una herramienta incorrecta o la use con una interpretación peligrosa. Las tools deben expresar su alcance y sus efectos, incluyendo lo que no hacen.
Una buena práctica es separar acciones de preparación y acciones de ejecución: create_draft, preview_change o calculate_transfer no deben producir el mismo efecto que send, apply_change o execute_transfer.
Validar la propuesta, no sólo el JSON
La validación debe ocurrir en varias capas:
- Schema validation: tipos, campos, formatos, tamaños y valores permitidos.
- Authentication: quién está solicitando la operación.
- Authorization: qué puede hacer esa identidad sobre ese recurso.
- Policy validation: qué reglas adicionales aplican a la acción.
- State validation: si la transición es válida en el estado actual.
- Domain validation: si la operación es coherente con el sistema externo.
- Result validation: si el efecto realmente ocurrió como se esperaba.
Un agente puede tener permiso para crear una cita, pero no para crearla en cualquier calendario. Puede tener acceso a una base de datos, pero no a todas las filas. Puede enviar mensajes, pero no a destinatarios fuera de una lista aprobada.
El scope debe ser específico para el usuario, el agente, el tenant, el recurso y la tarea. Validar sólo al inicio de la sesión es insuficiente: los permisos pueden cambiar mientras un workflow espera, y cada ejecución puede operar sobre recursos distintos.
Least privilege y capabilities
Un agente debería recibir únicamente las capacidades necesarias para la tarea actual. No todas las herramientas disponibles en la plataforma tienen que estar disponibles en cada ejecución.
Esto puede modelarse como un conjunto de capabilities:
{
"agentId": "assistant-42",
"tenantId": "tenant-7",
"taskId": "task-1842",
"capabilities": [
{
"tool": "calendar.find_availability",
"scope": "calendar:read",
"resources": ["calendar:primary"]
},
{
"tool": "calendar.create_event",
"scope": "calendar:write",
"resources": ["calendar:primary"],
"requiresApproval": true
}
],
"expiresAt": "2026-10-01T18:00:00Z"
}Una capability no debe convertirse en una credencial permanente que el modelo pueda reutilizar sin restricciones. Debe tener alcance, duración, propietario y contexto. En sistemas multi-tenant, el tenant no puede inferirse sólo de argumentos generados por el modelo; debe provenir de la identidad autenticada y ser aplicado por el runtime.
El principio de mínimo privilegio reduce el impacto de prompt injection, selección equivocada de herramientas, errores de autorización y fallos de configuración. No es una solución exclusiva para IA. Es una propiedad básica de cualquier sistema que ejecuta acciones, pero los agentes hacen más importante aplicarla porque la selección de acciones es probabilística.
Acciones reversibles e irreversibles
No todas las acciones necesitan el mismo nivel de autonomía. Un agente puede consultar disponibilidad o preparar un borrador sin confirmación. Eso no implica que deba enviar un correo, borrar registros o efectuar una compra automáticamente.
Conviene clasificar las acciones por reversibilidad:
- Reversible: puede deshacerse sin consecuencias relevantes.
- Compensable: no puede deshacerse técnicamente, pero existe una operación de compensación.
- Irreversible: produce un efecto que no puede eliminarse de manera confiable.
- Externa: afecta a personas o sistemas fuera del control del runtime.
La política debería considerar también el monto, la sensibilidad de los datos, el número de afectados y la confianza de la evidencia. Una acción de bajo riesgo puede ejecutarse de forma automática. Una operación de alto impacto debería requerir una aprobación explícita y contextual.
La aprobación debe referirse a una acción concreta, no a una autorización genérica como “puedes manejar mi calendario”. El usuario debería poder ver qué se hará, sobre qué recurso, con qué parámetros y con qué consecuencias previsibles.
Human-in-the-loop no es un botón de confirmar
La aprobación humana es parte del estado del workflow:
PROPOSED
↓
WAITING_FOR_APPROVAL
↓ aprobado
AUTHORIZED
↓
EXECUTING
↓
SUCCEEDED / FAILED / UNKNOWNEl runtime debe persistir la propuesta, la versión de los argumentos, quién autorizó, cuándo expiraba la autorización y qué se ejecutó finalmente. Si el estado externo cambió mientras se esperaba, la aprobación puede dejar de ser válida y requerir una nueva confirmación.
Esto es especialmente importante en voz. Una frase ambigua como “sí, hazlo” puede referirse a la última opción mencionada, no necesariamente a la acción que el sistema cree tener pendiente. El agente debe resumir la operación y pedir confirmación contextual cuando el riesgo lo justifique.
La aprobación tampoco debe ocultar los controles del runtime. Un usuario puede aprobar una acción, pero el servidor todavía debe comprobar límites, ownership, expiración y consistencia antes de ejecutarla.
Idempotencia, retries y efectos duplicados
Una de las situaciones más peligrosas ocurre cuando una tool produce un efecto externo y la respuesta se pierde. El runtime no sabe si la operación falló o si terminó pero no alcanzó a registrar el resultado.
Runtime → API externa: crear reserva
API externa: crea la reserva
Red: timeout antes de devolver respuesta
Runtime: no sabe si debe reintentarUn retry ciego puede crear una segunda reserva. El problema no se resuelve con un prompt mejor. Requiere un contrato operacional.
Para operaciones con efectos secundarios, conviene usar una idempotencyKey asociada a la intención de negocio:
type ToolExecution = {
executionId: string;
idempotencyKey: string;
toolName: string;
argumentsHash: string;
status: "running" | "succeeded" | "failed" | "unknown";
};
async function executeCreateReservation(input: Input) {
const key = `reservation:${input.taskId}:${input.restaurantId}:${input.slot}`;
const previous = await executions.findByKey(key);
if (previous?.status === "succeeded") return previous.result;
if (previous?.status === "running") return { status: "pending" };
await executions.start({ key, argumentsHash: hash(input) });
return reservations.create(input, { idempotencyKey: key });
}La idempotencia no significa que cualquier operación pueda repetirse sin riesgo. Significa que el sistema puede reconocer repeticiones de la misma intención y producir un resultado consistente. Si el proveedor externo no soporta claves de idempotencia, el runtime puede necesitar una consulta posterior, un registro de correlación o una política de compensación.
También conviene distinguir entre errores reintentables y errores permanentes. Un timeout puede justificar una estrategia de recuperación; un error de autorización no debería provocar diez intentos idénticos. Los límites de retries, backoff y jitter son responsabilidades del runtime, no del modelo.
Timeouts, cancelación y presupuestos
Una tool sin timeout puede detener un workflow indefinidamente. Una tool con un timeout demasiado agresivo puede marcar como fallida una operación que continúa ejecutándose externamente. Ambas situaciones requieren una política explícita.
Los límites deberían existir en varios niveles:
- timeout de conexión;
- timeout de la operación de la tool;
- deadline de la tarea completa;
- máximo de llamadas por herramienta;
- máximo de retries;
- presupuesto de tokens y costo;
- límite de datos leídos o escritos;
- límite de acciones externas por turno.
Cuando una ejecución se cancela, no siempre es posible cancelar el efecto externo. Por eso el estado debe soportar UNKNOWN o PENDING_RECONCILIATION, en lugar de afirmar que la operación falló sin evidencia.
En una interfaz de voz, la cancelación puede detener el audio y la generación, pero no necesariamente una transferencia ya enviada. La UX debe comunicar esa diferencia. “Dejé de hablar” no significa “deshice la operación”.
Sandboxing y aislamiento
Las herramientas que ejecutan código, shell, navegador o acceso a archivos requieren una frontera más fuerte que una validación de argumentos.
El aislamiento puede incluir:
- contenedores o sandboxes con filesystem limitado;
- allowlist de comandos y dominios;
- credenciales efímeras;
- red restringida;
- límites de CPU, memoria y tiempo;
- procesos separados por tenant o tarea;
- eliminación del entorno al terminar;
- registro de comandos y artefactos producidos.
Una descripción de herramienta que dice “ejecuta cualquier comando” ya es una decisión arquitectónica de alto riesgo. Es preferible exponer operaciones de dominio más estrechas: run_tests, read_file sobre un workspace específico o create_pull_request con una rama limitada.
El aislamiento no sustituye autorización. Un proceso dentro de un contenedor puede seguir teniendo acceso indebido a secretos o APIs. La seguridad debe componerse desde identidad, red, filesystem, permisos y runtime.
El runtime debe separar propuesta, autorización y ejecución
Una traza operativa debería diferenciar tres momentos:
- Propuesta: el modelo seleccionó una tool y produjo argumentos.
- Autorización: una política o una persona permitió la acción.
- Ejecución: la infraestructura llamó al sistema externo y verificó el resultado.
Si estos momentos se mezclan, es difícil saber si una acción fue bloqueada, si falló una policy o si el proveedor externo produjo un error. También se vuelve difícil evaluar al agente: no es lo mismo proponer una acción peligrosa que ejecutarla después de que el runtime la bloqueó, aunque ambas situaciones merecen observación.
{
"taskId": "task-1842",
"tool": "calendar.create_event",
"proposal": {
"arguments": { "title": "Revisión", "start": "..." },
"model": "model-version",
"createdAt": "..."
},
"authorization": {
"decision": "approved",
"policyVersion": "policy-12",
"approvedBy": "user-7"
},
"execution": {
"status": "succeeded",
"externalId": "event-99",
"idempotencyKey": "..."
}
}Esta separación también permite implementar dry-run, previews, simulaciones y revisiones humanas sin ejecutar efectos reales.
Observabilidad y auditoría
Un log de la respuesta final no basta. Para investigar una acción se necesita correlacionar al menos:
- usuario, agente y tenant;
- task ID y execution ID;
- modelo, prompt o política versionados;
- tool y versión del contrato;
- argumentos validados, con secretos redactados;
- decisión de autorización;
- retries, timeouts y cancelaciones;
- resultado externo y evidencia de verificación;
- costo, latencia y estado final.
La auditoría no significa guardar indiscriminadamente todo el contenido. Los argumentos pueden incluir datos personales, tokens o información financiera. La observabilidad debe aplicar redacción, control de acceso, retención y separación entre datos necesarios para depurar y datos que no deberían conservarse.
También deben registrarse las negativas. Una acción bloqueada por falta de permisos es información útil para detectar prompts mal diseñados, abuso, intentos de escalamiento o una experiencia de producto confusa.
Una arquitectura de tool runtime
Una arquitectura conceptual puede organizarse así:
Agent Runtime
↓
Tool Planner / Proposal
↓
Policy Engine ─── Identity & Authorization
↓
Schema + Domain Validator
↓
Approval Manager (cuando aplica)
↓
Execution Manager
├── timeouts
├── retries
├── idempotency
├── cancellation
└── budgets
↓
Tool Adapter / Sandbox
↓
External System
↓
Result Verifier + State Update + AuditNo es necesario construir todos estos módulos como servicios independientes. En una aplicación pequeña pueden ser componentes dentro del mismo proceso. La separación importante es de responsabilidades y de autoridad.
El modelo puede sugerir la siguiente acción. El Policy Engine decide si esa clase de acción es posible. El validador revisa argumentos y estado. El gestor de ejecución controla el ciclo de vida. El verificador confirma el efecto. El estado registra qué ocurrió y qué debe pasar después.
MCP resuelve interoperabilidad, no confianza
Los protocolos estandarizados para exponer herramientas pueden hacer más fácil conectar un agente con fuentes de datos y capacidades externas. Eso es valioso para la interoperabilidad y la composición de sistemas.
Pero un protocolo de tools no convierte automáticamente una herramienta en segura. La seguridad sigue dependiendo de quién puede invocarla, qué recursos alcanza, cómo se autentica, qué datos devuelve, qué efectos produce y qué controles aplica el cliente o el servidor.
Una herramienta conectada por un estándar sigue necesitando mínimo privilegio, validación, aislamiento, límites, autorización y auditoría. La estandarización reduce fricción de integración; no sustituye el diseño de seguridad ni la política del runtime.
Evaluar el uso de tools como capacidad independiente
Un agente puede completar una tarea y aun así haber usado una estrategia peligrosa. Por eso la evaluación debe observar:
- si eligió la herramienta adecuada;
- si sus argumentos fueron válidos y suficientemente restringidos;
- si respetó el orden de dependencias;
- si actuó con el scope correcto;
- si pidió aprobación cuando correspondía;
- si distinguió errores reintentables de errores permanentes;
- si verificó el resultado externo;
- si evitó duplicar efectos;
- si terminó dentro del presupuesto;
- si se detuvo ante evidencia insuficiente.
Una suite de pruebas debería incluir casos normales y fallos deliberados: respuestas inválidas, timeouts después de un efecto, cambios de permisos, recursos pertenecientes a otro tenant, duplicación de eventos, herramientas indisponibles, prompt injection en datos recuperados y aprobaciones expiradas.
El resultado final no debe ser la única métrica. Conviene registrar la diferencia entre una acción propuesta, una acción autorizada y una acción ejecutada. También deben medirse los peores casos: operaciones no autorizadas, exposición de datos, duplicados, loops, costo máximo y severidad de acciones irreversibles.
Lo que creo actualmente
Un agente confiable no es el que siempre intenta actuar. Es el que puede explicar operacionalmente qué propuso, dentro de qué límites, con qué autorización, qué ocurrió realmente y desde dónde puede recuperarse si la respuesta fue ambigua.
La autonomía no debería medirse por cuántas decisiones delegamos al modelo. Debería medirse por cuántas tareas puede completar dentro de un espacio de acciones seguro, observable y recuperable.
Un modelo puede equivocarse al escoger una herramienta. Un prompt puede ser manipulado por contenido externo. Una API puede responder tarde. Una red puede perder la respuesta después de un efecto. Un usuario puede interrumpir una operación de voz. Ninguno de estos eventos debería convertir automáticamente un error local en un incidente sistémico.
La arquitectura correcta no intenta eliminar toda incertidumbre. Construye límites alrededor de ella.
Las capacidades de IA se vuelven útiles cuando pueden operar dentro de un sistema que controla sus efectos.
Referencias
- OWASP GenAI Security Project, Excessive Agency. Riesgos de funciones, permisos y autonomía excesivos en aplicaciones con LLM.
- OWASP, Top 10 for Large Language Model Applications. Riesgos de seguridad relevantes para aplicaciones basadas en modelos.
- NIST, AI Risk Management Framework. Marco para gestionar riesgos de sistemas de IA.
- Model Context Protocol, Specification. Protocolo para conectar aplicaciones de IA con herramientas y fuentes externas.
- Amazon Web Services, Timeouts, retries, and backoff with jitter. Diseño de timeouts y reintentos en sistemas distribuidos.
- Stripe, Idempotent requests. Uso de claves de idempotencia para evitar efectos duplicados en operaciones repetidas.
- Temporal, Workflows. Ejecución durable, recuperación y coordinación de procesos largos.
- Google, Handling overload, Site Reliability Engineering. Límites, overload y comportamiento de sistemas bajo presión.