Registro de reintentos de agentes de IA que revela efectos duplicados
El registro de reintentos de agentes de IA vincula cada intento con una única acción prevista, conserva los resultados desconocidos y revela los efectos secundarios repetidos durante una investigación.

Un reintento de un agente no es una segunda copia de una acción. Es un segundo intento de completar una acción declarada, y la pista de auditoría debe conservar esa diferencia. Si tus registros solo muestran una serie de llamadas HTTP parecidas, has hecho que la recuperación y la repetición parezcan lo mismo.
La diferencia importa sobre todo cuando la acción cambia el mundo: crear un ticket, revocar un acceso, registrar un pago, desplegar una versión o ejecutar un comando mediante SSH. El agente puede recibir un tiempo de espera después de que el destino ya haya actuado. También puede reintentar tras un fallo local, antes de que salgan bytes del equipo. Esos casos requieren respuestas distintas, pero demasiados sistemas registran ambos como request failed, retrying.
He visto equipos investigar supuestas acciones duplicadas comparando manualmente las marcas de tiempo y las cargas útiles. Es un sustituto deficiente de un modelo de eventos. Construye la relación en cada registro cuando ocurra la acción. El investigador debe poder seleccionar una operación y ver su intención, todos los intentos, el motivo de cada intento posterior y el resultado que finalmente resolvió la duda.
Un reintento pertenece a una operación, no a una línea del log
Cada acción de un agente que produzca efectos secundarios necesita dos identidades: un ID de operación para el resultado previsto y un ID de intento para una ejecución concreta. El ID de operación persiste desde el momento en que el agente decide qué quiere hacer hasta que cierras o reconcilias esa intención. Cada llamada de red o ejecución SSH recibe un ID de intento nuevo.
Supón que un agente quiere desactivar una cuenta. Crea la operación op_7f2c. La primera solicitud, att_01, llega a la API de identidad, pero la conexión se cierra antes de recibir una respuesta. Una segunda solicitud, att_02, podría ser un intento de recuperación justificado. Ambos registros deben apuntar a op_7f2c; att_02 debe apuntar directamente a att_01 como el intento que lo provocó.
No uses el ID de conversación como ID de operación. Una conversación del agente puede contener muchas acciones, y una acción puede sobrevivir a una conversación cuando un supervisor retoma el trabajo. Tampoco uses solo un hash de la solicitud. El hash describe los bytes, mientras que una operación describe el efecto previsto. Dos solicitudes pueden diferir en detalles de transporte inofensivos y seguir perteneciendo a una misma operación. A la inversa, los mismos bytes pueden crear dos acciones distintas cuando el agente los envía dos veces deliberadamente.
Usa una identidad de operación cuando el agente adquiere un compromiso como «desactivar la cuenta A», «crear un incidente para la alerta B» o «ejecutar una vez esta migración en el host C». Captura esa intención en campos estructurados. Un resumen en lenguaje natural resulta útil para las personas, pero no puede ser la única identidad, porque la redacción cambia entre ejecuciones del agente.
Una jerarquía clara se ve así:
- Una sesión identifica una ejecución del proceso del agente.
- Una operación identifica un efecto externo previsto.
- Un intento identifica una ejecución concreta.
- Una observación identifica pruebas recibidas más tarde, como una devolución de llamada, una comprobación posterior a la escritura o una decisión del operador.
Esta jerarquía resuelve un caso incómodo que aparece con frecuencia: un agente envía una solicitud, agota el tiempo de espera y después pregunta a otro endpoint si la acción ocurrió. La consulta no es un reintento. Es una observación vinculada a la operación original. Tratarla como otro intento oculta las pruebas más útiles en la categoría equivocada.
Desconocido es un resultado, no un mensaje de error
Un tiempo de espera produce incertidumbre, no una prueba de fallo. Tu log necesita un estado para esa incertidumbre y debe conservarlo hasta que una evidencia posterior lo resuelva.
Muchas bibliotecas de cliente convierten varios eventos en una sola excepción: conexión rechazada, fallo de DNS, cuerpo de respuesta que llegó demasiado tarde y conexión restablecida después de que el servidor confirmara una escritura. Esa comodidad puede servir para el flujo de control de la aplicación. Es inaceptable como registro de auditoría final. Los registros deben indicar lo que observó el emisor y evitar afirmaciones sobre el destino que no pueda demostrar.
Para cada intento, separa la observación local del estado resuelto de la operación. Un intento puede ser not_sent, sent_no_response, response_received o execution_error. La operación puede ser open, succeeded, failed, unknown o cancelled. Los nombres pueden variar, pero la separación no.
not_sent significa que el cliente se detuvo antes del envío. Un fallo local al buscar credenciales puede encajar aquí. Reintentarlo no puede duplicar un efecto remoto porque no salió ninguna solicitud.
sent_no_response significa que el emisor envió la acción, pero no tiene una respuesta utilizable. Este es el estado peligroso. Un reintento automático solo puede ser seguro si el destino tiene un mecanismo fiable de deduplicación o si la acción no puede crear un efecto duplicado.
response_received no significa automáticamente que haya éxito. El servidor puede devolver un error de validación, una respuesta de conflicto o una respuesta correcta que describa un trabajo asíncrono. Guarda el estado, la huella de respuesta relevante y cualquier referencia de operación emitida por el destino. Después establece el estado de la operación según el contrato de esa API concreta.
Evita escribir failed cuando quieres decir unknown. La palabra deja los paneles más ordenados, pero indica al siguiente agente u operador que repita una acción que quizá ya ocurrió. Durante un incidente, ese campo poco fiable puede convertir una acción equivocada en una secuencia de acciones.
La semántica HTTP no hace que una acción de negocio sea segura de repetir
Los métodos HTTP describen la semántica del protocolo, no tus garantías de negocio. RFC 9110 dice que un método es idempotente cuando el efecto previsto de varias solicitudes idénticas es igual al efecto de una sola. Considera idempotentes PUT, DELETE y los métodos seguros, mientras que POST no lo es por defecto.
La orientación es útil, pero los ingenieros suelen extenderla demasiado. Una solicitud DELETE puede ser idempotente a nivel de protocolo porque borrar un recurso inexistente deja el recurso inexistente. Tu pregunta de auditoría puede ser otra: ¿el agente borró la cuenta correcta?, ¿activó dos veces la limpieza posterior?, ¿usó una autoridad distinta en la segunda llamada? La etiqueta del protocolo no responde a esas preguntas.
PUT también causa problemas. Un PUT que establece un recurso con una representación fija suele tolerar reintentos. Un endpoint PUT que envía una notificación, asigna un registro o ejecuta una integración en cada recepción no ofrece la seguridad que muchos suponen. Lee el contrato documentado del destino y prueba el comportamiento forzando la pérdida de la respuesta. Los nombres de los métodos no son pruebas.
Los endpoints POST suelen admitir un token de idempotencia. Envía un token estable derivado del ID de operación, no del ID de intento. Si att_01 y att_02 tienen tokens distintos, has inutilizado la función que debía protegerte del reintento.
Un sobre de solicitud podría ser así:
{
"operation_id": "op_7f2c9c",
"attempt_id": "att_01",
"idempotency_key": "op_7f2c9c",
"intent": {
"kind": "disable_account",
"subject_ref": "user:1842"
},
"destination": {
"method": "POST",
"route_template": "/v1/accounts/{id}/disable",
"authority_ref": "vault:identity-prod"
},
"request_fingerprint": "sha256:...",
"dispatch_state": "sent_no_response"
}
No registres el encabezado de autorización, la cookie de sesión, material SSH privado ni un cuerpo que contenga secretos. Guarda una referencia a las credenciales y una huella de la representación canónica de la solicitud. Una huella ayuda a los investigadores a comparar intentos sin convertir la pista de auditoría en otro almacén de secretos.
El destino debe respetar el token de idempotencia para evitar efectos repetidos. Cuando lo haga, registra la referencia devuelta por el destino y si la respuesta procedía de un resultado anterior almacenado. Cuando no lo haga, el token será solo un encabezado inerte y tu política de reintentos debe actuar en consecuencia.
Un reintento SSH puede repetir más de un comando
SSH dificulta el recuento de reintentos porque una conexión puede transportar sintaxis de shell, tuberías, redirecciones y comandos que se completan parcialmente. Una sesión SSH fallida no indica qué partes del comando remoto llegaron a ejecutarse.
Considera este comando:
create-user deployer && install-key deployer /tmp/new.pub && restart-service api
Si el cliente pierde la conexión después del envío, un reintento puede fallar porque el usuario ya existe, instalar una clave dos veces si el asistente la añade, o reiniciar el servicio por segunda vez. && solo controla el comportamiento dentro de una ejecución. No protege una conexión nueva que vuelva a ejecutar la cadena completa.
Registra el comando exacto solo cuando no contenga secretos. De lo contrario, guarda una versión visible con los datos ocultos y una huella canónica. Registra el alias del host o la referencia de su clave, la referencia de la cuenta remota, el directorio de trabajo si es relevante, el estado de salida si se recibió y el límite de ejecución. Ese límite debe indicar si el asistente inició el comando y si recibió un estado de salida, no solo si el emisor local informó de un error.
El patrón más seguro es un script remoto con un marcador de operación que el propio script compruebe antes de actuar. El marcador debe vivir donde el sistema de destino pueda leerlo de forma atómica. Según el entorno, puede servir una transacción de base de datos, un registro de despliegue o un archivo creado de forma exclusiva. Una caché local del agente no puede demostrar nada después de un fallo del proceso o de una segunda ejecución del agente.
Por ejemplo, un script de despliegue puede aceptar OPERATION_ID, escribirlo en un registro de versión antes de activarla y devolver el resultado existente si ese registro ya existe. El evento de auditoría captura entonces tanto el ID de operación local como el ID del registro remoto. Así, el investigador dispone de un puente entre el registro del agente y las pruebas del host.
No consideres reintentables comandos de shell arbitrarios porque sean «más o menos seguros». Clasifica las familias de comandos. La recopilación de solo lectura puede reintentarse libremente. Los comandos que establecen estados necesitan una condición de convergencia explícita. Los comandos de solo anexado, financieros, destructivos o de notificación necesitan un registro remoto de deduplicación o una decisión humana después de un resultado desconocido.
Cada intento posterior necesita un motivo declarado y un padre
Un evento de reintento debe nombrar el intento que lo provocó y la condición que justificó volver a probar. retry_count: 2 es demasiado débil. Indica que hubo llamadas anteriores, pero no qué llamada falló, si el agente cambió algo o si una persona aprobó la continuación.
Usa un conjunto controlado de motivos y adjunta los detalles de apoyo por separado. Algunos motivos útiles son connection_not_established, rate_limited, destination_5xx, response_lost_after_dispatch, credential_refreshed y operator_requested. No permitas que el agente invente un motivo en prosa que parezca tranquilizador, pero que no pueda agruparse ni revisarse.
Para cada reintento, conserva estos vínculos:
{
"operation_id": "op_7f2c9c",
"attempt_id": "att_02",
"retry_of_attempt_id": "att_01",
"retry_reason": "response_lost_after_dispatch",
"retry_decision": "destination_idempotency_confirmed",
"attempt_budget_remaining": 1,
"request_fingerprint": "sha256:...",
"prior_request_fingerprint": "sha256:..."
}
Las dos huellas normalmente deberían coincidir. Si difieren, registra el motivo. Un encabezado de marca de tiempo cambiado puede ser normal. Un identificador de cuenta, importe, nombre de host, ruta o autoridad diferente no es un reintento en el sentido habitual. Es una operación nueva o una intención modificada manualmente, y la pista de auditoría debe decirlo.
Aquí es donde los sistemas de agentes suelen engañarse. El modelo lee un error, cambia un parámetro para «arreglarlo» y llama reintento a la siguiente solicitud. Esa es una decisión nueva con un efecto posible nuevo. Vincularla como reintento oculta el cambio de plan y hace casi imposible revisarlo.
Limita el presupuesto de intentos por operación y registra la decisión sobre ese presupuesto. Reintentar una respuesta de límite de velocidad después del tiempo indicado por el destino no es igual que reintentar una escritura desconocida tras un tiempo de espera. En el primer caso suele haber una respuesta remota inequívoca. En el segundo necesitas pruebas de idempotencia o reconciliación antes de repetirla.
Los tokens de idempotencia y las identidades de auditoría cumplen funciones distintas
Un token de idempotencia indica al destino que trate varios envíos como una sola operación. Un ID de operación de auditoría indica a tus investigadores qué intentos pertenecen a una misma intención. Usa ambos cuando puedas, pero no supongas que uno sustituye al otro.
El token puede estar limitado a una ruta, un comercio, un periodo o una cuenta concreta. Algunas API conservan los tokens solo durante un tiempo limitado. Algunas devuelven la respuesta original para un duplicado; otras devuelven un conflicto. Algunas rechazan un token reutilizado cuando cambia el cuerpo de la solicitud. Esos detalles deben formar parte del contrato del conector y de tu conjunto de pruebas.
El ID de operación tiene una función más amplia. Conecta la sesión del agente, las pruebas de aprobación, la construcción de la solicitud, los intentos de transporte, la respuesta remota y la reconciliación posterior. Debe seguir siendo válido aunque una API de proveedor no ofrezca idempotencia, aunque la acción use SSH o aunque un operador realice manualmente la recuperación final.
No generes un ID de operación nuevo después de un reinicio solo porque haya desaparecido la memoria del proceso. Persiste las operaciones pendientes antes del envío. Durante la recuperación, revisa cada operación sin resolver y elige una de tres vías: reconciliar usando pruebas remotas, reintentar bajo una garantía de idempotencia documentada o escalar el caso a una persona. Un reinicio es un evento de ingeniería, no un permiso para olvidar la incertidumbre.
Una recomendación popular, pero equivocada, es reintentar cada escritura fallida con espera progresiva. La espera progresiva reduce la presión sobre un servicio que tiene problemas. No convierte una escritura desconocida en una escritura segura. La repetibilidad de la acción y el comportamiento de deduplicación del destino determinan si otro intento es aceptable.
La reconciliación cierra la incertidumbre sin reescribir la historia
Reconciliar significa reunir pruebas posteriores sobre una operación sin resolver. No significa editar el primer intento hasta que parezca correcto.
Supón que un agente crea un incidente con una referencia proporcionada por el cliente en el cuerpo de la solicitud. El POST inicial termina como sent_no_response. Antes de reintentar, el agente consulta los incidentes usando esa referencia. Si encuentra un registro coincidente, añade un evento de observación que cite el ID de operación original, la huella de la consulta, el identificador remoto devuelto y los criterios de coincidencia. Después cierra la operación como correcta mediante reconciliación.
Si la consulta no encuentra nada, ten cuidado. La ausencia demuestra poco cuando la API tiene retrasos de replicación, demora en la indexación de búsquedas o filtros poco precisos. Registra la observación negativa con la hora y el endpoint utilizado. Reintenta solo si el contrato del destino indica que el token de idempotencia sigue siendo efectivo, o espera y solicita una decisión.
Si la consulta encuentra dos registros coincidentes, no marques la operación como correcta y sigas adelante. Ciérrala como duplicate_effect_confirmed, conserva ambos identificadores remotos y crea una operación de corrección separada. La corrección no debe compartir el ID de operación original porque tiene un efecto previsto diferente.
Conserva eventos de solo anexado en lugar de filas de estado mutables como fuente de verdad. Puedes proyectar un estado actual cómodo para una interfaz, pero las pruebas deben conservar las transiciones: intención creada, intento enviado, respuesta perdida, consulta realizada, registro remoto encontrado y operación resuelta. El investigador necesita la secuencia completa, incluido el reintento equivocado si lo hubo.
Un log que evidencie manipulaciones añade otra propiedad: permite verificar que un proceso posterior no eliminó en silencio el primer intento desconocido. Sallyport registra sesiones de agentes y acciones individuales en un registro de auditoría cifrado, encadenado mediante hashes y ciego a la escritura, y sp audit verify comprueba la cadena sin conexión sobre el texto cifrado. Esto ayuda a conservar la línea temporal, pero el esquema de eventos sigue necesitando los vínculos entre operación e intento descritos aquí.
La delegación del agente necesita un único responsable de la operación
Los subagentes facilitan los efectos duplicados porque cada proceso puede creer que es el dueño de la tarea. Asigna a un solo proceso la propiedad del ID de operación y exige que cada trabajador delegado lleve ese ID en su contexto de acción.
Un planificador puede pedir a un trabajador que recopile información y a otro que ejecute una acción. Las llamadas de recopilación deben recibir sus propias operaciones porque son intenciones distintas. El trabajador de ejecución debe recibir el ID de operación original solo cuando actúe sobre el mismo efecto declarado. Sus llamadas individuales usan ID de intento nuevos e identifican el proceso del trabajador que las realizó.
No permitas que los trabajadores reintenten de forma independiente una escritura desconocida mientras el padre también la reintenta. El padre debe recibir el estado de envío del trabajador antes de decidir qué hacer. Si el trabajador termina inesperadamente, marca la operación como no resuelta y reconcíliala. La ausencia del resultado de un hijo no es una invitación para que un supervisor reproduzca el comando.
Los registros de aprobación por sesión también forman parte de la cadena de pruebas. Cuando un proceso del agente obtiene autoridad para realizar un grupo de llamadas, registra por separado la identidad del proceso, la hora de aprobación y la hora de revocación, sin mezclarlas con el resultado de la acción. La aprobación explica quién tenía permiso para intentarlo. No demuestra que el destino ejecutara la acción.
Para las operaciones sensibles, exige una aprobación por llamada para el propio reintento cuando el primer intento haya tenido un resultado desconocido. Las personas toman mejores decisiones cuando la tarjeta de aprobación muestra la intención original, el estado de envío anterior y el método de recuperación previsto. Un aviso genérico que diga «permitir llamada de API» oculta precisamente el dato que debería hacerles detenerse.
La vista de investigación debe mostrar una línea temporal, no un montón de solicitudes
El investigador necesita una página o resultado de consulta de una sola operación, que empiece con la intención y termine con el resultado mejor respaldado. Los logs de solicitudes ordenados por hora obligan a reconstruir la relación entre padres e hijos bajo presión, a menudo entre varios sistemas con relojes ligeramente distintos.
Muestra arriba el estado de la operación, pero deja las pruebas disponibles debajo. Cada intento debe mostrar su número, estado de envío, padre del reintento, motivo, referencia de autoridad, destino, huella de solicitud, resumen de respuesta y duración. Cada observación debe indicar qué comprobó y por qué esa evidencia cambió o no cambió el estado.
No ocultes las solicitudes duplicadas porque el efecto final haya sido inofensivo. La duplicación inofensiva de hoy puede convertirse en un efecto costoso después de que cambie una API o una integración añada un webhook. El registro debe permitir distinguir «el agente reintentó correctamente y el destino deduplicó» de «el agente envió la solicitud dos veces y tuvo suerte».
Trata el cambio del cuerpo de la solicitud durante la recuperación como una bifurcación explícita. La operación original permanece abierta o recibe su resultado resuelto. La acción modificada obtiene un ID de operación nuevo y un vínculo como supersedes_operation_id. Ese registro dice la verdad: el agente no se limitó a reintentar, sino que cambió lo que pretendía hacer.
Haz una prueba de fallo forzado antes de confiar en el diseño. Haz que el destino confirme una acción de prueba idempotente y después elimina la respuesta que debía recibir el emisor. Comprueba que la siguiente ejecución del agente conserve el ID de operación original, use el mismo token del destino, registre el primer intento como desconocido, realice la reconciliación o el reintento documentado y cierre la operación con pruebas. Si tu prueba no puede responder a esos puntos, tampoco podrá hacerlo un incidente.
El primer campo que busco en el log de acciones de un agente no es un estado HTTP. Es el ID de operación estable. Sin él, cada investigación de un reintento empieza como una conjetura. Con él, puedes hacer las preguntas importantes: qué pretendía el agente, qué salió del equipo, qué cambió entre los intentos y qué pruebas respaldan el resultado final.
FAQ
¿Los reintentos de los agentes de IA siempre son un problema de seguridad?
Pueden ser intentos legítimos de recuperación, pero el registro debe vincularlos a un intento anterior y dejar constancia de lo ocurrido antes del reintento. Sin esa relación, el investigador no puede saber si el agente se recuperó de un tiempo de espera o produjo dos veces el mismo efecto secundario.
¿Qué identificador debe mantenerse igual durante un reintento del agente?
Usa un identificador de operación estable para la acción de negocio prevista y asigna un identificador de intento nuevo a cada intento de transporte. El identificador de operación se mantiene igual durante los reintentos; el identificador de intento nunca debe repetirse.
¿Un tiempo de espera significa que una solicitud de API falló?
No. Un tiempo de espera solo indica que el emisor no recibió una respuesta utilizable. El servicio remoto pudo rechazar la solicitud, completarla una vez o incluso completarla más de una vez si el emisor cambió la solicitud entre intentos.
¿Las claves de idempotencia eliminan la necesidad de registrar los reintentos?
La idempotencia reduce los efectos duplicados en un destino que coopera, pero no documenta la decisión del agente de reintentar ni demuestra que el destino respetara la solicitud. Conserva un registro de acciones aunque la API acepte un token de idempotencia.
¿Cómo deben registrar los logs un resultado desconocido?
Registra el resultado real como desconocido hasta reconciliarlo. No escribas «fallido» solo porque el agente perdió la respuesta, ni «correcto» si el destino no confirma la finalización o una evidencia posterior no la demuestra.
¿Cuándo debe dejar de reintentar una acción un agente?
Reintenta solo cuando la acción tenga una identidad de operación estable, un motivo explícito para el reintento y un presupuesto de intentos limitado. Para acciones irreversibles sin soporte de idempotencia, detente tras un resultado desconocido y exige una decisión humana o una comprobación de reconciliación.
¿Basta un código de estado HTTP para el registro de auditoría de un agente de IA?
No. Un código de estado describe un intercambio HTTP, mientras que el registro de la acción necesita el efecto previsto, la referencia de credenciales, el destino, la relación entre intentos, las pruebas de respuesta y el estado final resuelto. Los logs HTTP por sí solos suelen omitir los datos que necesita un investigador.
¿Cómo funcionan los reintentos cuando un agente delega tareas en subagentes?
El proceso padre es dueño del identificador de operación y lo proporciona al trabajo hijo. Un hijo puede crear sus propios identificadores de intento, pero debe conservar el identificador de operación del padre y declarar su función de ejecución.
¿Puedo editar un registro de reintento después de conocer el resultado?
Conserva el evento original y añade un evento de corrección o reconciliación que lo referencie. Los logs mutables permiten reescribir la historia en silencio justo cuando un incidente exige una línea temporal fiable.
¿Qué pruebas necesita un investigador después de producirse efectos duplicados en una API?
Necesitan la intención original, todos los intentos en orden, las credenciales o la autoridad utilizada, las huellas de las solicitudes, las respuestas observadas, los motivos de los reintentos y el resultado final reconciliado. También necesitan pruebas de que el software posterior no pudo alterar esos registros en silencio.