The agent's job is to process an order after the payment is confirmed. The flow is: payment webhook fires → agent starts → agent processes order.
Except sometimes the sequence is: payment webhook fires → agent starts → agent polls for payment confirmation → times out → reports failure.
The payment was confirmed before the agent started polling. The agent missed it. It's looking for a future event that is already in the past.
This is the event-ordering failure. It's not exotic. It happens whenever an agent waits for an external event that might have already occurred. And because agents are typically stateless between invocations, they start each session with no memory of what already happened.
The four shapes of this problem
The missed webhook. The agent is triggered by event A and needs to wait for event B before proceeding. Event B fires before the agent starts listening for it. The agent polls for event B indefinitely and never finds it.
The replay gap. An agent processes a queue of events. The agent crashes and restarts. It resumes from the current position in the queue, not from where it left off. Events that arrived during the crash window are missed.
The stale poll. The agent polls an API for a status change. The status changed and changed back (briefly unavailable then available again) before the first poll. The agent sees the current state and never detects the transition it was looking for.
The race condition. Two agents are processing related events. Agent A is waiting for Agent B to complete before starting its own step. Agent B completed while Agent A was starting up. Agent A never gets the signal. Both agents are now blocked, each waiting for a handshake that already happened.
Why agents are particularly vulnerable
Traditional software listening for events uses subscriptions: it registers with a queue or event bus and receives events that arrive after registration. Anything that arrived before registration is handled by offset management or replay.
Most LLM agents don't use subscriptions. They use polling: periodically check whether a condition is true. Polling works when the agent knows exactly what state to look for and can reliably detect it at any point in time. It breaks when the state change is transient — when the thing the agent is looking for happened and is no longer visible.
Agents are also typically stateless between sessions. When an agent session starts, it doesn't know what events occurred before it was invoked. It starts from nothing and tries to figure out the current state of the world through its tools.
If the current state of the world is "the payment is confirmed," the polling agent will eventually find that. If the current state is "payment confirmed and order already fulfilled by a different process," the polling agent might detect a conflict state it wasn't designed to handle.
What to build instead
Check for past events before waiting for future ones. When the agent needs an external event to proceed, its first action should be to check whether that event already occurred. Query the event log, the audit trail, the status endpoint. If the event is already there, proceed. Only start waiting if the event is genuinely in the future.
Use event sourcing. Store events in an append-only log with timestamps. When an agent session starts, give it access to the event log from a point before it started. The agent can query "did event X happen in the last N minutes?" and get a real answer.
Make waiting explicit with timeouts and fallbacks. If the agent must wait for an external event, give it an explicit timeout and a fallback behavior. Don't let it poll indefinitely. At timeout, it should surface the situation rather than silently fail.
Use message queues with durable subscriptions. Where possible, replace polling with durable queue subscriptions. The queue holds messages until a consumer reads them. Messages that arrive before the consumer starts are held and delivered when the consumer connects. No events are missed.
Build idempotent handlers. If the same event might be processed twice (because of at-least-once delivery), design the handler so running it twice produces the same result as running it once. This makes event-ordering errors recoverable rather than catastrophic.
Faultmap maps the async event dependencies in your agent's execution graph. It identifies which steps wait for external events, which events could arrive before the agent starts listening, and whether your polling logic handles the "already happened" case. You see the timing gaps before they become support tickets.
The event happened. Your agent just wasn't there yet. Build for the possibility that the event you're waiting for is already in the past.

