Blog
Saved0

Event-driven AI agents: how to wait, correlate and resume without mistakes

A manager approved a $100,000 credit increase, and the approval event resumed a different $250,000 request. Why long-running AI work needs typed events, exact correlation, durable waits and a check before every resume.

LinkedIn
On this page
  1. The approval arrived. It resumed the wrong request.
  2. Is an event-driven AI workflow just messaging?
  3. Not every message makes the same claim
  4. Which IDs does an event need?
  5. A wait is durable state, not a sleeping thread
  6. What should happen before a paused AI workflow resumes?
  7. Delivery guarantees stop at the broker
  8. Order is a business rule
  9. Authority can expire while you wait
  10. Common anti-patterns
  11. Ten rules for event-driven AI agents
  12. The result: an event is a claim, not a command
  13. Frequently asked questions
  14. How Frozion waits and resumes long-running AI work
  15. Sources
The short answer

An event-driven AI workflow must treat every incoming event as an untrusted claim. Bind it to the exact run, wait and proposal it may advance, not just a customer or account. Before resuming, check duplicates, timing, order, current authority and whether the proposal changed. Delivery guarantees stop at the broker; business effects need their own operation ID.

The approval arrived. It resumed the wrong request.

A distributor's credit application handles a relationship manager's request for a $100,000 credit-limit increase. It gathers account history, contract terms and payment behavior. A model recommends approval, rules check the financial limits, and the workflow pauses while a regional credit manager reviews the exact proposal. That's run E-104, proposal P-17.

Before the manager responds, a second request arrives for the same customer: a $250,000 increase with a different justification. It becomes run E-109, proposal P-22, and it pauses too.

The manager approves P-17. The approval service publishes an event with the customer account, the approver, a timestamp and approved=true. It doesn't include the run, the wait, the proposal or a digest, because the original design assumed only one review per account at a time.

The consumer matches on account and event type, and picks the most recently registered wait. That's E-109. Proposal P-22 resumes and the $250,000 limit is committed.

The event was authentic. Its schema was valid. The approver was authorized. The broker delivered it. The wrong business decision moved forward. No single component failed; the failure lived in how event, correlation, waiting state and proposal related to each other.

Two paused credit requests for the same customer; an approval event carrying only the account ID is matched to the newer request instead of the approved one
Matching by account alone picked the most recent wait. The approval was for the other one.

Is an event-driven AI workflow just messaging?

No. Messaging moves data. An event-driven workflow also decides what a message means, which waiting case it may affect, whose authority it carries, whether it's current or duplicated, and what evidence must remain.

Standards give a good base. CloudEvents defines a portable event context (ID, source, type, version) and lets a duplicate keep the same source and ID. AsyncAPI describes message-driven APIs, including payloads, headers and correlation IDs. Neither is meant to define the business meaning of your application.

A broker can deliver a message. It can't decide whether that message authorizes a credit increase, invalidates an earlier proposal, satisfies a waiting case, or should be ignored, rejected, escalated or kept only as evidence.

Not every message makes the same claim

The same channel often carries very different kinds of message. Treating them as interchangeable makes control flow ambiguous.

KindWhat it claimsExampleWhat the runtime must do
FactSomething already happenedinvoice.receivedDon't treat it as a request
CommandPlease try an actionissue.refundIt can be refused; it isn't proof the action happened
SignalSomething may now be availabledocument.availableFetch, validate and check current state
DecisionA person or policy decidedapproval.grantedBind to the exact proposal, evidence, authority and version
TimerA deadline or duration passedreview.expiredTime passing doesn't make the next step valid
OutcomeAn attempted action returnedpayment.committedDistinguish committed, not committed and uncertain

AI adds another kind: proposals. A model's risk assessment is a proposal or observation, not a verified fact. Name it riskAssessmentProposed, not riskCleared, which falsely implies that policy, evidence, authority and any human review are already settled. The model may propose an event or plan; the runtime decides whether it changes anything.

Which IDs does an event need?

Several, each with one job: event ID, trace ID, correlation ID, causation ID, run ID, wait ID, proposal ID and digest, operation ID and resource ID. One overloaded correlationId can't answer all their questions.

IDQuestion it answers
Event IDHave I already received this event?
Trace IDWhich technical path carried it?
Correlation IDWhich broader case or conversation is it part of?
Causation IDWhat directly caused it?
Run IDWhich running workflow owns it?
Wait IDWhich exact pause may resume?
Proposal ID and digestWhat exactly was approved or rejected?
Operation IDIs this the same payment or update across retries?
Resource IDWhich account, claim, order or supplier?

Trace context keeps a technical request path connected across services. That's valuable for diagnosis, but a trace ID isn't the business identity of a case, the cause of an approval or the idempotency key for an external action. Values can coincide sometimes; don't design as if they're interchangeable.

A wait is durable state, not a sleeping thread

A long-running workflow doesn't hold a thread open for three days while a manager decides. It saves its state, releases compute, and later rebuilds the conditions under which it may continue. Process standards have long treated waiting as an explicit part of a process. AI adds things that can change during the wait: models, prompts, context, policy, authority and proposal status.

A durable wait should preserve:

  • the workflow version and dependencies that created it;
  • the exact run, step and branch that paused;
  • the expected event type, source, kind and validation rules;
  • the precise correlation rule;
  • the proposal ID, digest and evidence;
  • the authority and policy that applied, and what to recheck;
  • deadline, expiry, timeout path and late-event rule;
  • the allowed record revision, sequence and competing waits;
  • what happens on resume, and what evidence to keep.

What should happen before a paused AI workflow resumes?

A fixed sequence of checks: validate the event, bind it to the exact wait, rule out duplicates, check timing and order, recheck authority and policy, confirm the proposal hasn't changed, then choose a typed outcome and record it.

  1. Normalize the message into the expected event contract.
  2. Authenticate the producer and check integrity where needed.
  3. Validate shape, kind, source, tenant, resource and classification.
  4. Correlate to the exact run and wait, not just the business object.
  5. Decide whether it's a duplicate of something already accepted.
  6. Check event time, expiry, sequence and record revision.
  7. Recheck the initiating authority, current actor, delegated scope and policy.
  8. Confirm the proposal and material facts still match what was approved or awaited.
  9. Choose an outcome: resume, ignore duplicate, reject, expire, ask for evidence, require re-approval or escalate.
  10. Record the event, checks, decisions and state change.
  11. Only then let the next step or change run.

This stops a common shortcut: resuming the same code path when the meaning of the wait has changed. A changed proposal, expired approval, different legal entity, new policy obligation or incompatible workflow version shouldn't silently inherit the earlier event.

An incoming event passing through a sequence of gates before a paused workflow is allowed to resume, with typed exits at each gate
An event can wake the runtime. Only these checks decide whether it may advance the business.

Delivery guarantees stop at the broker

Messaging protocols offer different delivery levels. MQTT 5.0, for example, defines at-most-once, at-least-once and exactly-once delivery. Those guarantees cover transfer within the protocol. The application has more layers where zero, one or several outcomes can happen:

LayerQuestionWhat can still go wrong
DeliveryDid the message arrive?Loss, duplicates, redelivery, reordering
ProcessingDid a consumer handle it?Crash before acknowledgement, poison message, partial processing
State changeDid exactly one eligible wait advance?Wrong match, double resume, stale proposal
Business effectDid the external action commit once?Commit then lost acknowledgement, double payment
EvidenceCan we prove what happened?Missing event snapshot, receipt or match decision

A transport can deliver once while a consumer creates two effects. It can deliver twice while the application creates one effect, thanks to a stable operation ID and reconciliation.

De-duplication and idempotency protect different things. The event ID stays the same when the same event is redelivered. The operation ID stays the same whenever the same business intent is retried, even under a new message. A genuinely new action gets a new operation ID even if it's for the same account. When you can't tell whether an effect committed, the state is uncertain, not failed: reconcile before retrying (see retries and idempotency).

Order is a business rule

Events arrive late, out of order, or after a newer state has committed. "Latest received" isn't "latest effective". Keep event time, receipt time, processing time, effective time and expiry separate. Where order matters, carry a record revision, sequence or expected prior state, and let the resume check decide whether a late event is accepted, rejected as stale, re-evaluated or sent to a person.

Authority can expire while you wait

Authenticating the producer doesn't prove the event may cause the change. A service may be trusted to publish approvals while a given approval has expired, sits outside the approver's legal entity, belongs to another tenant or refers to an earlier proposal. Zero-trust guidance rejects implicit trust from prior access; in a long-running case, conditions need rechecking when the event arrives.

That's why an approval can't be a boolean. It should bind the exact proposal and digest, the evidence shown, the approver's role and delegation, conditions and expiry, the governing versions, and the specific change it permits. If the amount, beneficiary, entity or another material fact changes, the approval no longer covers it (see human-in-the-loop approval).

Redelivery, replay and re-execution also stay distinct. Replay reconstructs a past case for inspection or simulation; it should never republish old events into production channels (see behavior replay).

Common anti-patterns

Anti-patternWhy it failsWhat to do instead
Correlate only by business objectOne account can have several open waitsBind to the exact run, wait and proposal
Topic name as the contractA route says nothing about meaning, authority or timingDeclare a typed, versioned event contract
Delivery guarantee as business guaranteeEffects can still repeat or go missingSeparate delivery, processing, state change and effect
Resume on the latest workflow versionContracts and approvals change meaningKeep waits on their version; migrate deliberately
Approval as approved=trueLoses proposal, evidence, scope and expiryBind approval to the exact proposal and change
Event treated as authorityAuthentic isn't the same as authorizedAuthorize each event-triggered change separately
One ID for tracing, correlation and idempotencyQuestions get conflatedGive each ID one job
Arrival order as business orderLate events overwrite newer truthUse effective time, revision and a stale-event rule

Ten rules for event-driven AI agents

  1. Declare whether each message is a fact, command, signal, decision, timer, proposal or outcome.
  2. Use a typed, versioned event contract for every consequential event.
  3. Keep event, trace, case, cause, run, wait, proposal, resource and operation IDs separate.
  4. Persist every wait as durable state tied to the version that created it.
  5. Bind approvals to the exact proposal and evidence reviewed.
  6. Recheck authority, policy, timing, order and material change before resuming.
  7. Treat duplicate, late, reordered and uncertain events as normal, with typed outcomes.
  8. Don't confuse delivery guarantees with business-effect guarantees.
  9. Use stable operation IDs and reconciliation for every external change.
  10. Record why each case woke up, and keep replay away from live effects.

The result: an event is a claim, not a command

Event-driven design is usually sold as decoupling: producers and consumers don't call each other directly. That's useful, but it isn't the core requirement for enterprise AI. The application has to preserve what each event claims, which exact state it may touch, whose authority applies, whether the proposal is still current, how duplicates and late arrivals are handled, and what proves the result.

A durable AI workflow isn't a long-running thread. It's a version-bound state machine that can show why it waited, what woke it, what changed while it slept, and why it was safe, or unsafe, to continue.


Frequently asked questions

How should an AI workflow correlate an incoming event?

Correlate it to the exact run, wait and proposal it may advance, not only to the account or order it mentions. One customer can have several open waits at once, so matching by business object alone can resume the wrong case.

Does exactly-once delivery prevent duplicate actions?

Exactly-once delivery covers moving a message between protocol participants, not the business effect. A consumer can still process it twice or commit an action whose acknowledgement is lost. Use a stable operation ID, de-duplication and reconciliation for the effect itself.

What should happen when an event arrives late or out of order?

The workflow should apply a declared rule, not take the latest arrival as the latest truth. Compare event time, effective time, record revision and sequence, then accept, reject as stale, re-evaluate or send it to a person, and record which.

How does this work for agents we already run?

Route the agent's tool calls through a governed runtime over MCP or HTTPS. Long-running steps become durable waits with deadlines and fallbacks, events resume only the exact case they belong to, and authority is rechecked before any change.

With Frozion

How Frozion waits and resumes long-running AI work

Durable Execution keeps every wait as durable state. Work survives a crash or restart of Frozion itself, deploys don't interrupt running cases, and every wait has a timer, deadline and fallback. Authority is rechecked when paused work resumes, right before the change, and uncertain writes are reconciled before any retry. In the HITL Inbox, approval is bound to the exact revision reviewed, and work resumes on its own once a person decides. Provenance & Evidence records what woke each case and what followed.


Sources

  1. CloudEventsPortable event context; a duplicate may keep the same source and ID.
  2. AsyncAPIMachine-readable contracts for message-driven APIs, including correlation IDs.
  3. OMG BPMN 2.0.2Events and waiting as explicit parts of a process model.
  4. W3C Trace ContextTechnical trace propagation, distinct from business identity.
  5. W3C PROV-ORelating events, activities and responsible agents.
  6. OASIS MQTT Version 5.0Delivery levels scoped to the protocol interaction.
  7. RFC 9110, HTTP SemanticsIdempotency defined by intended effect, not by delivery.
  8. NIST SP 800-207, Zero Trust ArchitectureAuthorize on current conditions, not prior access.
More in Work that finishes

Work that finishes

Retries & side effects 2
Durable & event-driven 1
Contracts & routing 3
APIs & integration 1

Other topics

Pick up where you left off: