On this page
- The approval arrived. It resumed the wrong request.
- Is an event-driven AI workflow just messaging?
- Not every message makes the same claim
- Which IDs does an event need?
- A wait is durable state, not a sleeping thread
- What should happen before a paused AI workflow resumes?
- Delivery guarantees stop at the broker
- Order is a business rule
- Authority can expire while you wait
- Common anti-patterns
- Ten rules for event-driven AI agents
- The result: an event is a claim, not a command
- Frequently asked questions
- How Frozion waits and resumes long-running AI work
- Sources
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.

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.
| Kind | What it claims | Example | What the runtime must do |
|---|---|---|---|
| Fact | Something already happened | invoice.received | Don't treat it as a request |
| Command | Please try an action | issue.refund | It can be refused; it isn't proof the action happened |
| Signal | Something may now be available | document.available | Fetch, validate and check current state |
| Decision | A person or policy decided | approval.granted | Bind to the exact proposal, evidence, authority and version |
| Timer | A deadline or duration passed | review.expired | Time passing doesn't make the next step valid |
| Outcome | An attempted action returned | payment.committed | Distinguish 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.
| ID | Question it answers |
|---|---|
| Event ID | Have I already received this event? |
| Trace ID | Which technical path carried it? |
| Correlation ID | Which broader case or conversation is it part of? |
| Causation ID | What directly caused it? |
| Run ID | Which running workflow owns it? |
| Wait ID | Which exact pause may resume? |
| Proposal ID and digest | What exactly was approved or rejected? |
| Operation ID | Is this the same payment or update across retries? |
| Resource ID | Which 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.
- Normalize the message into the expected event contract.
- Authenticate the producer and check integrity where needed.
- Validate shape, kind, source, tenant, resource and classification.
- Correlate to the exact run and wait, not just the business object.
- Decide whether it's a duplicate of something already accepted.
- Check event time, expiry, sequence and record revision.
- Recheck the initiating authority, current actor, delegated scope and policy.
- Confirm the proposal and material facts still match what was approved or awaited.
- Choose an outcome: resume, ignore duplicate, reject, expire, ask for evidence, require re-approval or escalate.
- Record the event, checks, decisions and state change.
- 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.

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:
| Layer | Question | What can still go wrong |
|---|---|---|
| Delivery | Did the message arrive? | Loss, duplicates, redelivery, reordering |
| Processing | Did a consumer handle it? | Crash before acknowledgement, poison message, partial processing |
| State change | Did exactly one eligible wait advance? | Wrong match, double resume, stale proposal |
| Business effect | Did the external action commit once? | Commit then lost acknowledgement, double payment |
| Evidence | Can 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-pattern | Why it fails | What to do instead |
|---|---|---|
| Correlate only by business object | One account can have several open waits | Bind to the exact run, wait and proposal |
| Topic name as the contract | A route says nothing about meaning, authority or timing | Declare a typed, versioned event contract |
| Delivery guarantee as business guarantee | Effects can still repeat or go missing | Separate delivery, processing, state change and effect |
| Resume on the latest workflow version | Contracts and approvals change meaning | Keep waits on their version; migrate deliberately |
Approval as approved=true | Loses proposal, evidence, scope and expiry | Bind approval to the exact proposal and change |
| Event treated as authority | Authentic isn't the same as authorized | Authorize each event-triggered change separately |
| One ID for tracing, correlation and idempotency | Questions get conflated | Give each ID one job |
| Arrival order as business order | Late events overwrite newer truth | Use effective time, revision and a stale-event rule |
Ten rules for event-driven AI agents
- Declare whether each message is a fact, command, signal, decision, timer, proposal or outcome.
- Use a typed, versioned event contract for every consequential event.
- Keep event, trace, case, cause, run, wait, proposal, resource and operation IDs separate.
- Persist every wait as durable state tied to the version that created it.
- Bind approvals to the exact proposal and evidence reviewed.
- Recheck authority, policy, timing, order and material change before resuming.
- Treat duplicate, late, reordered and uncertain events as normal, with typed outcomes.
- Don't confuse delivery guarantees with business-effect guarantees.
- Use stable operation IDs and reconciliation for every external change.
- 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.
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
- CloudEventsPortable event context; a duplicate may keep the same source and ID.
- AsyncAPIMachine-readable contracts for message-driven APIs, including correlation IDs.
- OMG BPMN 2.0.2Events and waiting as explicit parts of a process model.
- W3C Trace ContextTechnical trace propagation, distinct from business identity.
- W3C PROV-ORelating events, activities and responsible agents.
- OASIS MQTT Version 5.0Delivery levels scoped to the protocol interaction.
- RFC 9110, HTTP SemanticsIdempotency defined by intended effect, not by delivery.
- NIST SP 800-207, Zero Trust ArchitectureAuthorize on current conditions, not prior access.
More in Work that finishes
Retries & side effects 2
Durable & event-driven 1
Contracts & routing 3
APIs & integration 1
Other topics



