# Errores de herramientas de agentes que ayudan sin exponer secretos

Los errores de las herramientas de un agente necesitan suficiente estructura para que el agente pueda recuperarse, pero no deben convertir cada solicitud fallida en una exportación de credenciales. El límite es fácil de describir y se viola constantemente en la práctica: un envoltorio captura una excepción, devuelve el mensaje de la biblioteca y entrega discretamente al modelo una URL, un encabezado Authorization, el sujeto de un certificado, un destino SSH o un fragmento de token.

He visto que el problema empieza con un campo de depuración bien intencionado. Alguien añade `request_headers` porque un agente no dejaba de recibir respuestas 401. El agente copia el error en sus notas de trabajo. Esas notas terminan pegadas en una solicitud de cambios, enviadas a un sistema de soporte o conservadas en un registro de evaluación. El fallo original ya pasó. El secreto sigue ahí.

El diseño adecuado ofrece al agente una explicación limitada de lo ocurrido, de lo que puede hacer de forma segura a continuación y un identificador de correlación para un operador humano. Mantiene las pruebas sin procesar detrás de la frontera de la acción. No hay que elegir entre errores útiles y errores seguros para los secretos. Es un trabajo de diseño de interfaces.

## Una respuesta de error forma parte de la frontera de seguridad

Un agente trata la salida de una herramienta como memoria de trabajo. Puede citarla a un usuario, colocarla en un archivo, enviarla a otra herramienta o usarla para decidir si reintenta. Por eso, cualquier campo que devuelva una puerta de enlace de acciones debe tratarse como una divulgación a un interlocutor no confiable, incluso cuando ese interlocutor sea un agente iniciado por un desarrollador que controla la máquina.

Una llamada a una herramienta tiene dos públicos. El agente necesita datos operativos: si la acción se ejecutó, si puede reintentarse, si necesita consentimiento y qué entrada debe cambiar. El operador necesita datos forenses: qué credencial se seleccionó, qué ruta exacta se llamó, qué resolvedor DNS falló y qué respondió el par remoto. No satisfagas al segundo público volcando sus pruebas al primero.

La diferencia importa sobre todo cuando un agente puede invocar herramientas repetidamente. Una persona que ve un error detallado una vez quizá detecte un token bearer. Un agente puede conservarlo durante decenas de turnos y luego incluirlo en código generado o en un accesorio de prueba. No hace falta que el modelo tenga malas intenciones para que ocurra. 

Construye la interfaz en torno a una regla explícita: el interlocutor solo recibe campos que seguirían siendo seguros si se copiaran en una incidencia pública. Si un campo no supera esa prueba, guárdalo en diagnósticos protegidos u omítelo.

## Da decisiones a los agentes, no cadenas de excepciones

Una respuesta útil indica al agente qué tipo de decisión tiene delante. Las excepciones sin procesar no lo hacen de forma fiable ni segura. Su redacción cambia entre versiones del sistema operativo y de las bibliotecas cliente, y a menudo mezclan una causa técnica con material que debe permanecer privado.

Usa un vocabulario pequeño y documentado de códigos. Cada código debe corresponder a un comportamiento concreto del interlocutor. No hagas los códigos tan detallados que acabes recreando cada posible fallo de los sistemas ascendentes.

Una forma práctica de respuesta sería esta:

```json
{
  "ok": false,
  "code": "AUTH_FAILED",
  "message": "The remote service rejected the stored credential.",
  "action": "stop_and_report",
  "retryable": false,
  "request_id": "act_7f3c2a91",
  "http_status": 401
}
```

El agente puede detenerse, informar al usuario de que la autenticación requiere atención e incluir el ID de solicitud. No necesita el token bearer, el esquema de autorización, el correo de la cuenta ni una copia del cuerpo de la respuesta para tomar esa decisión.

`action` es más útil que un simple booleano ambiguo. Un booleano indica si el tiempo podría resolver el problema. Una acción indica al agente qué debe hacer. Mantén los valores permitidos bajo control:

- `retry_after_delay` para operaciones temporales que se pueden repetir de forma segura.
- `repair_input` para una solicitud que el agente puede corregir sin nueva autoridad.
- `request_approval` cuando una persona debe autorizar la acción.
- `stop_and_report` para fallos que requieren trabajo del operador.
- `inspect_outcome` cuando una escritura puede haber llegado al servicio remoto.

El último caso merece un tratamiento especial. Un tiempo de espera después de enviar una escritura no es igual que un tiempo de espera antes de enviarla. Si conviertes ambos casos en `NETWORK_ERROR`, el agente volverá a intentar una acción que quizá ya se completó. Así se producen tickets, despliegues y registros duplicados, además de comandos destructivos repetidos.

Expón `http_status` solo cuando sea significativo y seguro. Suele aportar contexto útil en llamadas HTTP, pero no finjas que ofrece la respuesta completa. Un 403 puede significar un fallo de autorización ascendente, una restricción a nivel de recurso o una decisión de la puerta de enlace. El código de la herramienta debe indicar el comportamiento que el agente debe seguir.

## Mantén una taxonomía de códigos estable y suficientemente pequeña para probarla

Una taxonomía de códigos debe describir quién tiene que actuar y cómo recuperarse, no cada capa de la pila de red. Si tu lista tiene cincuenta códigos al cabo de la primera semana, probablemente estés exportando detalles de implementación con otro nombre.

Empieza con categorías que permitan al interlocutor tomar una acción diferente:

| Código | Significado | Comportamiento del agente |
| --- | --- | --- |
| `INVALID_INPUT` | La herramienta rechazó los campos proporcionados antes de realizar una acción externa. | Corrige los datos. |
| `VAULT_LOCKED` | La puerta de enlace no puede usar ningún secreto almacenado. | Pide a una persona que la desbloquee. |
| `USER_DENIED` | Una persona rechazó esta acción. | Detente. No la reformules ni la vuelvas a enviar. |
| `AUTH_FAILED` | El servicio remoto rechazó la credencial seleccionada. | Detente e informa. |
| `REMOTE_FORBIDDEN` | La solicitud se autenticó, pero no tiene permiso remoto. | Detente e informa. |
| `RATE_LIMITED` | El servicio remoto pidió a los clientes que redujeran el ritmo. | Espera si existe un retraso seguro. |
| `TEMPORARY_FAILURE` | Una solicitud repetible falló temporalmente. | Reintenta dentro de un límite establecido. |
| `OUTCOME_UNKNOWN` | Una escritura puede haberse completado antes del fallo. | Comprueba el resultado antes de reintentar. |
| `NETWORK_UNREACHABLE` | La puerta de enlace no pudo alcanzar un endpoint remoto. | Reintenta solo si la acción se puede repetir de forma segura. |
| `INTERNAL_FAILURE` | La puerta de enlace falló sin una solución segura para el interlocutor. | Detente e informa del ID de solicitud. |

No uses `ERROR`, `FAILED` o `EXCEPTION` como contrato principal. Esas etiquetas trasladan al agente la carga de interpretar el problema, que tendrá que deducir una solución a partir de la prosa. Puede deducirla mal.

Mantén estables los significados de los códigos. Puedes mejorar el mensaje para humanos, añadir un campo `retry_after_seconds` o incluir un nuevo valor de estado seguro. No cambies `AUTH_FAILED` para que signifique tanto credenciales incorrectas como rechazo de una aprobación local. Con el tiempo, los agentes y la orquestación que los rodea crearán ramas basadas en ese código.

RFC 9457, «Problem Details for HTTP APIs», ofrece una base útil: las respuestas pueden incluir un tipo de problema estable, un título, el estado, los detalles y una referencia de instancia. La advertencia del estándar es la parte que los equipos suelen omitir. RFC 9457 indica que `detail` debe ayudar a corregir el problema y que los detalles del problema pueden exponer información sensible. En las herramientas de agentes, convierte el tipo o código estable en el contrato, mantén los detalles breves y usa la instancia o el ID de solicitud para conectar al operador con las pruebas protegidas.

## Los encabezados de solicitud y los errores de conexión son pruebas, no contexto

Los equipos suelen llamar «contexto» a los encabezados sin procesar y a los mensajes de transporte. Son pruebas. Las pruebas deben estar en un registro de auditoría con controles de acceso, no en una respuesta para el agente.

Piensa en una solicitud API fallida. Una excepción típica de una biblioteca HTTP puede incluir la URL completa solicitada, la ubicación de redirección, la dirección del proxy, los encabezados de respuesta y parte del cuerpo de la respuesta. Cualquiera de esos elementos puede contener secretos. Los parámetros de consulta todavía transportan claves de API en algunas API antiguas. Los encabezados `Location` suelen contener URL firmadas de descarga. Las cookies y los encabezados de autenticación personalizados son filtraciones evidentes. Otros campos son menos obvios: `X-Request-Id` puede ser aceptable, mientras que `X-Forwarded-Host` o un encabezado de servicio interno puede revelar infraestructura que el agente nunca necesitó conocer.

Los errores SSH requieren la misma disciplina. No devuelvas una línea de comandos que contenga un destino privado, una ruta de known_hosts, un archivo de identidad ofrecido o el texto sin procesar de un conflicto de claves de host. Un conflicto de claves de host tiene un significado relevante para el interlocutor: la conexión está bloqueada porque no se puede verificar la identidad remota. Devuelve ese significado. Conserva para el operador la comparación de huellas, las rutas y los diagnósticos de la biblioteca.

OAuth 2.0 deja este punto especialmente claro. RFC 6750 indica que un token bearer da acceso a quien lo posee y pide a los clientes que protejan los tokens frente a la divulgación durante el almacenamiento y el transporte. Un gestor de errores que copia un token bearer en una traza ha incumplido ese requisito, aunque la solicitud original usara TLS correctamente.

Sanea los datos antes de serializarlos, no después de que los registros se hayan dispersado. Un filtro de ocultación en un destino general de registros sirve como red de seguridad, pero no es la frontera. Para entonces, una excepción puede haberse adjuntado ya a un resultado de herramienta, a un evento de telemetría o a un informe de fallo.

Usa una lista permitida para los campos que cruzan hacia el agente. Una lista de exclusión acaba olvidando `x-api-token`, un parámetro de consulta firmado, un campo de sesión específico del proveedor o una nueva propiedad de la biblioteca. Una lista permitida parte de cero y solo añade campos cuyo uso por parte del interlocutor esté identificado.

## Separa lo que falló de si la acción llegó a ejecutarse

Un diseño seguro de errores debe indicar al agente si el lado remoto pudo haber actuado. Aquí es donde la mayoría de las instrucciones de reintento se vuelven peligrosas.

Supón que un agente envía `POST /deployments` y la conexión agota el tiempo de espera. La puerta de enlace sabe que intentó realizar la llamada. No sabe si el sistema ascendente la recibió, si creó un despliegue o si la respuesta desapareció durante el trayecto de vuelta. Devolver `TEMPORARY_FAILURE` invita al agente a enviar otra solicitud de despliegue. Devolver `AUTH_FAILED` sería sencillamente falso. El estado correcto es `OUTCOME_UNKNOWN`.

La respuesta debe decirlo claramente:

```json
{
  "ok": false,
  "code": "OUTCOME_UNKNOWN",
  "message": "The connection ended after the request started. The remote action may have completed.",
  "action": "inspect_outcome",
  "retryable": false,
  "request_id": "act_9b18d4e0",
  "operation": "create_deployment"
}
```

`operation` nombra una clase de acción genérica, no la ruta ni el contenido completos. Ahora el agente puede usar una llamada de estado independiente y de solo lectura si la integración ofrece una. Si la API admite referencias de idempotencia, la puerta de enlace puede asociar la referencia segura con la operación y consultarla internamente. No expongas un valor de idempotencia si en el sistema de destino también sirve como material de acceso.

Las operaciones de lectura tampoco son automáticamente seguras para reintentar. Una lectura puede activar una facturación, actualizar un estado remoto o ejecutar un comando con efectos secundarios detrás de un nombre aparentemente inocente. El autor de la integración debe indicar si una operación se puede repetir. No pidas a un modelo de lenguaje que lo decida a partir del nombre del método.

Establece un presupuesto de reintentos en la puerta de enlace. Una respuesta puede incluir un retraso limitado, como `retry_after_seconds: 30`, pero solo cuando el sistema ascendente haya dado un valor seguro o la propia puerta de enlace controle el límite. No permitas que un agente reintente indefinidamente porque un mensaje diga «temporal». Los fallos repetidos generan ruido, consumen límites de frecuencia y dificultan la investigación posterior.

## Una denegación humana necesita su propio significado

Que una persona rechace una aprobación no es un error de autenticación remota. Significa que la acción solicitada no se ejecutó. Esa distinción protege la seguridad y la facilidad de uso.

Si una herramienta asigna una aprobación denegada a `AUTH_FAILED`, el agente puede probar credenciales alternativas, pedir que se rote un secreto o intentar una solicitud ligeramente modificada. Ninguna de esas acciones respeta a la persona que dijo que no. Si asigna la denegación a un fallo interno genérico, el usuario no puede saber si la puerta de enlace funcionó mal.

Devuelve `USER_DENIED` con un mensaje breve como «La acción solicitada no fue aprobada y no se ejecutó». Evita mencionar el nombre del secreto, la cuenta seleccionada o el destino exacto si esos detalles no forman parte de la entrada segura definida para la herramienta. El interlocutor debe detenerse. Una persona puede decidir si inicia una nueva solicitud visible.

Una bóveda de secretos bloqueada es algo distinto. `VAULT_LOCKED` significa que la puerta de enlace rechazó la acción antes de poder seleccionar o usar una credencial. La solución segura es pedir a una persona que desbloquee la puerta de enlace, no pedir al agente que proporcione un token. Así se evita un fallo habitual en el que un modelo compensa la falta de credenciales administradas buscando otro secreto en su contexto.

Sallyport aplica directamente esta separación: mientras la puerta de la bóveda está bloqueada, deniega todas las acciones, y su autorización por sesión puede distinguir entre un proceso nuevo que necesita aprobación y un servicio remoto que rechazó una llamada autenticada. El agente recibe el resultado de la acción, mientras la credencial permanece en la bóveda cifrada de la aplicación.

No intentes compensar la fatiga de aprobación devolviendo más detalles de diagnóstico. Si los usuarios aprueban habitualmente llamadas que no han revisado, corrige la agrupación de acciones, el alcance y la identidad del proceso que se muestra para la aprobación. Unas denegaciones más extensas no vuelven más seguro un consentimiento apresurado.

## Construye una ruta de diagnóstico con dos registros

Un registro debe ser seguro para el agente y otro debe ser lo bastante completo para un operador autorizado. Intentar que un único registro cumpla ambas funciones produce una experiencia de soporte opaca o una filtración de secretos.

El registro seguro para el interlocutor necesita un código, un mensaje, una acción, indicaciones de reintento, un ID de solicitud y quizá un estado del protocolo. El registro protegido puede contener el identificador del registro de credenciales seleccionado, el destino normalizado, el método, los tiempos, los metadatos de la respuesta ascendente, la huella saneada del contenido enviado, la excepción sin procesar y un rastro de la decisión de la puerta de enlace. Guarda el registro protegido en un lugar que los agentes no puedan consultar mediante las herramientas habituales.

Un ID de solicitud debe ser opaco. Genéralo de forma independiente de las credenciales y los destinos. No codifiques un nombre de host, un nombre de usuario, una marca temporal que revele patrones de actividad ni un ID de base de datos incremental si esos detalles son importantes en tu entorno. El ID permite que un usuario diga «inspecciona act_9b18d4e0» sin entregarle los diagnósticos subyacentes.

Para las acciones de alto valor, registra si la puerta de enlace alcanzó cada frontera: validación de la entrada, selección de credenciales, autorización del usuario, inicio de la conexión, envío de los bytes de la solicitud, recepción de la respuesta y devolución del resultado. Esta secuencia ofrece al operador una explicación defendible de `OUTCOME_UNKNOWN` sin mostrar al agente la solicitud sin procesar.

La evidencia contra manipulaciones también importa en esta ruta interna. Si alguien puede eliminar en silencio intentos de autorización fallidos o cambiar el motivo por el que se bloqueó una acción, el registro de auditoría se convierte en un simple registro de conveniencia. Sallyport proyecta las vistas de sesiones y llamadas desde un registro de auditoría cifrado y encadenado mediante hashes, y `sp audit verify` comprueba la cadena sin conexión sobre texto cifrado. Es útil cuando un operador necesita confiar en el registro sin conceder al agente acceso a su contenido.

Un registro protegido de ejemplo podría tener este aspecto. Intencionadamente, no es una respuesta para el agente:

```json
{
  "request_id": "act_9b18d4e0",
  "event": "http_call_failed",
  "credential_record": "cred_42",
  "destination": "api.internal.example",
  "method": "POST",
  "path_template": "/deployments",
  "bytes_sent": true,
  "response_received": false,
  "exception_class": "ReadTimeout",
  "result_code": "OUTCOME_UNKNOWN"
}
```

Incluso aquí hay que revisar los campos cuidadosamente. Una ruta completa puede exponer identificadores de recursos. El cuerpo de una solicitud normalmente debería convertirse en una huella unidireccional, un nombre de esquema o una representación estrictamente redactada. Los operadores suelen necesitar comparar dos intentos, no leer cada valor enviado.

## Los mensajes de error necesitan una política de ocultación deliberada

Ocultar datos no consiste en reemplazar un token por ocho asteriscos. Eso solo gestiona los formatos de secreto que ya reconoces. Una política adecuada clasifica los campos antes de que entren en mensajes, registros, métricas y resultados de herramientas.

Clasifica primero los secretos directos: tokens bearer, contraseñas, claves privadas, cookies, URL firmadas, encabezados de autorización y certificados de cliente. Después clasifica el contexto sensible: nombres DNS internos, rutas locales, nombres de usuario, nombres de repositorios, ID de recursos, cuerpos de solicitudes y encabezados que revelen la topología del despliegue. La segunda clase puede ser aceptable en un registro protegido, pero rara vez pertenece a un error visible para el agente.

No devuelvas «Se rechazó la credencial que termina en 7KQ2». Los equipos añaden esto porque existen varias credenciales y los operadores quieren saber cuál falló. Crea un identificador duradero que puede correlacionarse entre trazas. Devuelve `AUTH_FAILED` al agente. Permite que el operador consulte el registro de credenciales mediante el ID de solicitud protegido.

No repitas la entrada de la herramienta por defecto. El agente ya sabe lo que intentó, pero la puerta de enlace no puede suponer que esa entrada sea segura para repetirla. Una URL puede contener una cadena de consulta firmada. Un comando puede incluir una asignación de entorno. Un contenido JSON puede contener una credencial temporal que el agente recibió de otro sistema. Devuelve un error de validación por campo, como `invalid_fields: ["repository"]`, no el valor no válido copiado.

Prueba la ocultación con accesorios hostiles. Incluye secretos con mayúsculas y minúsculas inusuales, encabezados duplicados, información de usuario en URL, valores de consulta codificados con porcentajes, JSON anidado, causas de excepciones y argumentos de comandos SSH. Después comprueba que ningún secreto de prueba aparezca en la respuesta serializada de la herramienta, en los registros generales, en las etiquetas de métricas o en los datos de fallos. Una prueba que solo comprueba la ruta correcta no demuestra nada sobre el tratamiento de errores.

## Trata los mensajes remotos como entradas no confiables

Una API remota puede devolver un cuerpo de error útil, una página HTML de inicio de sesión o una cadena diseñada para influir en quien la lea. La puerta de enlace no debe pasar ese contenido directamente al contexto de un agente.

Esto es, en parte, un problema de secretos. Los servidores a veces reflejan valores de la solicitud en las páginas de error. Un encabezado Authorization, una cookie, un parámetro de consulta o un campo JSON no válido pueden regresar en una respuesta de diagnóstico ascendente. Pasarlo sin cambios convierte un eco remoto en una filtración de credenciales.

También es un problema de integridad de las instrucciones. Si un error ascendente dice «Ejecuta este comando para reparar tus credenciales», el agente puede tratarlo como una instrucción operativa. El servicio remoto no puede decidir la política de recuperación de la puerta de enlace.

Mapea los campos seguros conocidos de una respuesta de protocolo ascendente. Por ejemplo, un estado HTTP numérico y un retraso documentado por límite de frecuencia pueden ser útiles. Trata los cuerpos de texto libre como pruebas protegidas, salvo que tengas un analizador específico para el formato y una lista permitida clara. Si una integración necesita un motivo remoto legible para humanos, normalízalo con lenguaje propio de la puerta de enlace, como «El servicio rechazó el recurso solicitado», en lugar de copiar su prosa.

RFC 9110 define la semántica de los estados HTTP, pero sus estados no autorizan a divulgar el cuerpo de respuesta de un origen. Mantén clara esa separación. La información del protocolo puede ayudar a recuperarse; el texto ascendente arbitrario no es un contrato de diagnóstico seguro.

## Somete el contrato a pruebas centradas en los fallos

La mayoría de los equipos comprueba que una solicitud válida devuelva datos útiles. Comprueba también que cada solicitud no válida o interrumpida devuelva únicamente los datos que el agente debe ver.

Convierte el esquema de errores en un contrato versionado. Valídalo en las pruebas y, durante el desarrollo, rechaza los campos desconocidos en la frontera de serialización. Es más fácil auditar una lista permitida cuando el objeto tiene una forma estricta.

Usa un secreto de prueba lo bastante extraño como para detectar transformaciones accidentales y fuerza fallos en cada etapa. La matriz de pruebas debe cubrir la validación previa a las credenciales, una bóveda bloqueada, el consentimiento rechazado, respuestas remotas 401 y 403, límites de frecuencia, fallos de DNS, fallos de validación TLS, un tiempo de espera antes de que salgan bytes, un tiempo de espera después de que salgan bytes, JSON ascendente mal formado y una excepción lanzada por la propia puerta de enlace.

Para cada caso, comprueba cuatro cosas:

- La respuesta de la herramienta contiene el código esperado y la acción permitida.
- El secreto de prueba no aparece en ningún campo serializado visible para el interlocutor.
- La respuesta no contiene encabezados sin procesar, texto del cuerpo ascendente sin procesar ni detalles de conexión local.
- El registro de diagnóstico protegido contiene el ID de solicitud y suficiente información de estado para que un operador investigue.

No te conformes solo con comprobaciones mediante expresiones regulares. Busca el secreto exacto de prueba, su forma codificada en URL, su forma en base64 cuando corresponda y sus prefijos o sufijos habituales. Después inspecciona manualmente una respuesta de error capturada cada vez que actualices una dependencia HTTP, SSH, de telemetría o de generación de informes de fallos. Las bibliotecas cambian el formato de las excepciones sin pedir permiso.

Lo difícil es resistirse a la tentación de hacer que el error del agente se parezca a la consola de depuración del operador. Mantén pequeño, estable y orientado a la acción el contrato para el interlocutor. Mantén las pruebas protegidas y correlacionadas. Cuando llegue la próxima interrupción, esa separación permitirá que el agente actúe de forma segura y que la persona tenga lo necesario para corregir el fallo real.
