Una reserva, dos registros, una respuesta que no llega
Un agente de IA debe reservar un repuesto. Anota que atendió la solicitud y después llama al almacén. El proceso se interrumpe entre ambos pasos. Al reiniciar encuentra «hecho» y no reintenta: no hay reserva. Si invertimos el orden, el almacén reserva pero se pierde la respuesta antes de actualizar el registro. Reintentar puede reservar dos piezas. La misma ausencia de mensaje conduce a errores opuestos.
Resumen. ¿Cómo distinguir intención registrada, efecto remoto y confirmación observada? Construimos un modelo ejecutable con dos bases SQLite independientes, tres protocolos y cinco puntos de interrupción. La outbox protege la intención, pero no evita por sí sola duplicados: necesita un contrato del receptor. Son quince casos deterministas, no mediciones de fiabilidad industrial. No se realizan pedidos, reservas ni llamadas externas reales.
El límite que una frase convincente no elimina
Una transacción local hace indivisibles varias modificaciones del mismo sistema: se confirman juntas o no se aplican. No incluye automáticamente una escritura en otro servicio. El modelo puede elegir el repuesto correcto y producir JSON perfecto sin ampliar ese límite. Es un problema del protocolo de ejecución, no de lo convincente que resulte «operación completada».
Llamamos I a la intención persistente y N al número de efectos en el almacén simulado. Para una solicitud aceptada que puede completarse, buscamos N=1. N=0 indica ausencia y N=2 duplicación. Son conteos sin unidades físicas, no probabilidades. «Done» solo describe lo registrado; sin definir cómo se obtiene, no demuestra ninguno de esos valores.
La outbox conserva trabajo pendiente, no prueba de ejecución
Primero registramos la decisión y un mensaje pendiente en la misma transacción local. Esa cola persistente es la outbox. Un proceso de envío, relay, lee intenciones confirmadas y las transmite. Si se detiene tras confirmar localmente, la fila pending sobrevive al reinicio. «¿Recordamos qué intentar?» ya no depende de la memoria volátil del agente.
No convierte ambas bases en una sola transacción. Tras aplicar el efecto, el relay puede detenerse antes de registrar la entrega: la fila sigue pending y se reenvía. Nuestro contador remoto aumenta dos veces si cada llegada se interpreta como nueva. La outbox cierra un hueco entre decisión y mensaje, pero deja la ambigüedad de la respuesta perdida.
La identidad pertenece a la intención, no al intento
Asignamos a la reserva una clave estable, tenantA:reserve42. Cada intento usa esa misma clave. El receptor guarda un recibo con clave, parámetros y resultado. Si existe y los parámetros coinciden, devuelve el resultado anterior sin repetir el efecto. Si cambian, debe rechazar la reutilización ambigua: reservar dos piezas no es reservar una.
La última línea es decisiva. Escribir el recibo y aplicar el efecto en transacciones separadas reproduce el fallo en el receptor. Invertirlas deja un intervalo con efecto pero sin recibo. El modelo confirma ambos juntos. Un servicio concurrente necesita además aislamiento y unicidad bien gestionados: dos workers no deben observar a la vez «clave ausente» y ejecutar ambos.
Quince casos: qué interrumpimos realmente
El paquete Python crea dos archivos SQLite nuevos por caso, uno para el orquestador y otro para el almacén. Permanecen en runs para inspeccionar el estado. Solo hay un contador, no existencias reales. Una excepción interrumpe el control en un punto elegido y la recuperación abre nuevas conexiones. No apagamos el equipo ni medimos resistencia del disco a fallos eléctricos.
Los puntos son: 0 antes del registro local; 1 después, antes de enviar; 2 tras el efecto remoto, sin observar respuesta; 3 tras la respuesta, antes de confirmar localmente; 4 después de confirmar. En 0 suponemos que el solicitante presenta otra vez la misma intención. Sin ello ninguna outbox recuerda una petición nunca registrada. En los demás casos se retoma pending y no se repite done.
| Protocolo / punto | 0 | 1 | 2 | 3 | 4 |
|---|---|---|---|---|---|
| mark-first | 1 | 0 | 1 | 1 | 1 |
| outbox-no-dedup | 1 | 1 | 2 | 2 | 1 |
| outbox-dedup | 1 | 1 | 1 | 1 | 1 |
Cada celda cuenta efectos tras recuperar. Mark-first escribe done antes del envío y en 1 deja cero: el registro impide el intento necesario. Outbox sin deduplicación produce dos efectos en 2 y 3: el receptor actuó antes de que el relay lo registrara. Outbox más recibo atómico mantiene uno en los cinco casos modelados.

El gráfico no es un porcentaje de éxito: elegimos cinco puntos, no una muestra representativa de averías. Buscamos contraejemplos y comprobamos una propiedad del modelo. Aserciones verifican las tres secuencias. El fragmento final llama al mismo simulador e imprime la tabla; el paquete dibuja las barras desde esos datos, no desde una ilustración manual.
La propiedad y sus supuestos
Para cada clave k buscamos N(k)≤1: ningún segundo efecto de la misma intención. La transacción receptora lo sostiene: efecto y recibo existen juntos o no existe ninguno; el recibo retenido evita otro incremento. Para lograr además N(k)≥1 hacen falta intención persistente, intentos continuados, receptor disponible y petición válida. Lo primero es seguridad del protocolo; lo segundo, progreso. Un sistema puede evitar duplicados y quedar bloqueado indefinidamente.
«Exactamente una vez» requiere un límite explícito: un efecto lógico por clave bajo ciertas condiciones. No significa un paquete de red, un intento ni ausencia de errores de negocio. Dos intenciones con claves diferentes pueden reservar la misma pieza dos veces, correctamente para el protocolo y mal para el usuario. La identidad debe fijarse antes de reintentar; regenerar la clave con el modelo en cada recuperación anula la protección.
Dónde deja de bastar el modelo didáctico
Los recibos tienen duración. Si se borran antes de llegar un intento antiguo, este puede parecer nuevo. La retención debe concordar con retrasos, colas y reintentos; otra opción es rechazar solicitudes de épocas cerradas. No implementamos esa política. El tenant también integra la identidad: claves iguales de clientes distintos no deben confundir intenciones independientes.
Un sistema real trata workers concurrentes, leases caducadas, orden de cambios del mismo objeto, respuestas parciales y rechazos permanentes. Aceptar remotamente puede iniciar solo un trabajo asíncrono: aceptado y completado son distintos. Sin identidad estable ni consulta del resultado, un timeout puede exigir reconciliar en vez de afirmar éxito. Ningún prompt aporta por sí solo esa información ausente.
Conclusión: qué puede afirmar realmente el agente
El registro local acredita intención conservada; un recibo remoto verificado acredita el resultado definido por su contrato. En el modelo, outbox, identidad estable y deduplicación atómica eliminan tanto la pérdida de 1 como los duplicados de 2 y 3. Esa cadena de evidencia justifica «hecho», no la palabra en un log. El experimento no acredita una función disponible ni un resultado operativo de EL-AI.
Las fuentes primarias explican outbox y contratos de API idempotentes; también leímos implementaciones, solicitudes tardías y cambios de parámetros. Simulador, contraejemplos y tabla son nuestro análisis didáctico ejecutado, no benchmarks de sus autores. Cada caso maneja una intención y transiciones constantes; el trabajo de control crece linealmente con los casos, sin estimar costes de base de datos, red ni grandes colas. Inyectar fallos en el servicio concreto, con concurrencia y caducidad, sería el siguiente experimento propuesto, no realizado.
Fuentes y reproducibilidad
AWS Prescriptive Guidance — Transactional outbox pattern.
Malcolm Featonby — Making retries safe with idempotent APIs, Amazon Builders’ Library.
# Run beside experiment.py; all effects are local simulated counters.
from experiment import run
for protocol in ("mark-first", "outbox-no-dedup", "outbox-dedup"):
print(protocol, [run(protocol, cut)["effects"] for cut in range(5)])
Código, datos e instrucciones · JSON. Cálculos didácticos ejecutados con Python 3.14.0; figuras con Matplotlib 3.11.2. Análisis asistido por IA, sin afirmar revisión por pares ni humana. Portada original ImageGen ilustrativa: no documenta personas, sedes ni instalaciones de EL-AI. Fuentes consultadas el 3 de octubre de 2026.

