Optimistic UI for AI agents is safe only where undo is real

Optimistic UI for AI agent actions is a feedback pattern that shows the result of an agent's work before the server confirms it, and it stays honest only when the product can take that result back. A like button can flip to "liked" in 50 milliseconds because the request almost never fails and a failure costs nothing. An agent that refunds an order, emails the customer and closes the ticket does none of that in 50 milliseconds. It fails more often, and it leaves effects outside your product that no state rollback can reach.
This piece is for product teams putting agents inside a SaaS who want the screen to move the moment the user asks, without ever showing "Done" for something that has not happened. The rule we work by fits in one line: show intent at once, and show an effect only after it happened.
Why does an agent break optimistic UI?
Classic optimistic UI rests on three assumptions. The server answers fast. The request almost always succeeds. A failure is cheap to undo, because undoing it means putting the old value back on screen. Agent actions break all three.
Speed first. Nielsen Norman Group's three response time limits still frame the problem: 0.1 seconds feels like the user caused the result, 1 second keeps the flow of thought, and 10 seconds is about the limit for keeping attention on the task. Past that, the same article says users need feedback on when the work will finish, and that feedback matters most when the response time is highly variable. An agent run is the definition of variable. The same request can take one tool call or twelve.
Success rate second. A run fails when the model misreads the request, when a tool times out, when a permission is missing. Each step is a new chance to fail, so a seven-step run fails more often than a single write to your own database.
Rollback third, and this is the one that hurts. React's useOptimistic shows a temporary value while an Action is in progress. If the Action throws, the Transition ends and React renders the real value again, so the optimistic item disappears. The documentation treats telling the user about the failure as your job. The screen goes back; the explanation does not come with it. And when the step was an email that already left, putting the old value back on screen changes nothing in the customer's inbox.
Why a spinner does not fix it
The common retreat is a single "Thinking..." indicator for the whole run. It is honest and it is useless. Forty seconds of the same animation tells the user nothing about which step is running, gives them nothing to cancel, and invites a second click, which starts a second run. Showing "Done" early is useful and false. The pattern below sits between the two.
Sort every agent action by what undo costs
Before designing any screen, sort the actions the agent can take into three classes. The class decides what the interface may show before confirmation.
| Class | Examples | What the screen shows at once | How undo works |
|---|---|---|---|
| Local and reversible | Drafting text, tagging a record, reordering a list inside the product | The result, immediately | Restore the previous state and say why |
| Deferred commit | Sending an email, posting a message, scheduling a meeting | The effect as pending, with a countdown and Undo | Cancel before the window closes; nothing has left yet |
| External and irreversible | Charging or refunding a card, deleting data in another system | The plan, then a confirmation, then live progress | None after commit; only a second, compensating action |
Most of the trouble comes from treating a class 3 action with class 1 feedback. The six practices that follow apply the table.
1. Be optimistic about the plan
Within 100 milliseconds of the request, render the agent's plan as a list of steps marked "planned". This claims something true: the request arrived and the agent has a way to handle it. It claims nothing about results. Victor Yocco's patterns for agentic AI in Smashing Magazine call this an Intent Preview and place it before the agent acts, next to an Autonomy Dial that sets how much the agent may do unasked. If the model has not produced a plan yet, echo the request itself as the first line. The screen moves, and nothing on it is false.
2. Give each step its own state
Use five states per step: planned, running, done, failed, skipped. Stream them from the server as each step changes, so the list fills in while the user watches. A run of seven steps then reads "step 3 of 7, looking up the order", which answers the NN/g question of when the work will end better than a percent bar can when nobody knows how long step 5 will take. A single spinner hides the one thing the user needs when a run fails: where it stopped.
3. Hold a commit window for anything that leaves the product
Gmail's Undo Send lets users pick a cancellation period of 5, 10, 20 or 30 seconds. Its undo is real because the message has not been sent yet: Gmail waits before delivering it. Agents need the same mechanism. Queue the outbound effect, show it as "Sending to the customer in 10 s" with an Undo button, and commit when the timer ends. The undo works because nothing has happened. A recall after delivery is a different, much weaker feature.
4. Confirm before effects you cannot reverse
OpenAI built this into Operator: the agent asks for confirmation before finalizing tasks with external effects, such as submitting an order or sending an email. Microsoft's Guidelines for Human-AI Interaction put the general case in G10, scope services when in doubt: when the system is unsure of the user's goal, it asks before it acts. An irreversible effect is where that doubt costs the most. The confirmation names the exact effect: the amount, the recipient, the account. For a batch, ask once and list every item, so a run of 40 refunds is one decision and not 40 interruptions.
5. Make the retry safe before you make the button fast
Fast feedback invites retries, and a user who is unsure clicks again. Give every agent action an idempotency key, created when the plan is created, so a retried run carries the same key. Stripe's idempotent requests show the contract: a retry with the same key within 24 hours returns the first result, and keys can be removed after that. Without a key, an optimistic screen plus a nervous user is a double refund.
6. When a step fails, reverse it on screen and say what happened
Microsoft's guideline G9 is "support efficient correction": make it easy to edit, refine or recover when the system is wrong. In an agent run that means the failed step turns red with the reason in plain words, the steps already done stay marked done (the email went, the refund did not), the dependent steps move to skipped, and one action is offered next: retry the step, edit the request, or hand it to a person. Our piece on error state UX covers the wording.
What this looks like on a real screen
Take a hypothetical support agent inside a helpdesk product. The user types "Refund order 4417 and let the customer know."
- At 0.1 seconds a plan appears: look up order 4417, refund €84.00 to the card on file, email the customer, close the ticket. All four steps read "planned".
- Step 1 runs and turns done in about two seconds. It only reads data, so nothing needs guarding.
- Step 2 stops on a confirmation that names the amount and the last four digits of the card. The user confirms. The refund goes out with the idempotency key created with the plan, and the step turns done when the payment provider confirms the refund, not when the request leaves.
- Step 3 shows "Sending to the customer in 10 s" with Undo. The email waits in a queue behind step 2.
- Step 4 closes the ticket. It lives inside the product and can be reopened, so the screen shows it closed at once.
If the refund fails, step 2 turns failed with the provider's reason, steps 3 and 4 move to skipped, and the email never leaves, because it was queued behind a step that did not finish. The user sees exactly one thing to decide.
What the pattern costs
Three costs, all worth knowing before you commit to it. A commit window adds its length to every send, so a 10-second window means every email the agent writes arrives 10 seconds later. Confirmations slow down the people who use the agent most; the fix is an autonomy setting that lets a team pre-approve a class, such as refunds under €50, and keep confirmations for the rest. And streaming step states needs a backend that emits events per step, which is more work than one request and one response. If your agent architecture is still a single prompt behind a button, our notes on embedding AI agents in SaaS cover the decisions that come first.
The pay-off is a screen that moves in 100 milliseconds and never says anything the system will have to take back.
Sources
- Nielsen Norman Group: Response Times, the 3 Important Limits
- React documentation: useOptimistic
- Smashing Magazine: Designing for Agentic AI, Practical UX Patterns for Control, Consent and Accountability (February 2026)
- Gmail Help: Send or unsend Gmail messages
- OpenAI: Introducing Operator
- Microsoft Research: Guidelines for Human-AI Interaction (CHI 2019)
- Stripe API reference: Idempotent requests
Frequently asked questions
Yes, while the text stays inside the product. A draft the agent writes is a local, reversible action: show it at once and label it as a draft until the user accepts it. Streaming tokens as the model produces them is real progress, so it needs no optimism at all. The line is crossed when the text leaves the product, as an email, a message or a post. From that moment it belongs to the deferred class and gets a countdown with Undo.
Gmail offers 5, 10, 20 or 30 seconds for its Undo Send, which is a useful range to start from. Pick the shortest window in which users still catch their mistakes, and measure how often Undo is pressed and at which second. Batch actions deserve a longer window than single sends, because one mistake repeats across every item. Payments and deletions should not rely on a window at all: they get an explicit confirmation before anything leaves.
It cannot undo it, so it offers the compensating action instead: a refund for a wrong charge, a correction for a wrong email, a restore from backup for a wrong deletion. Record the original action and the compensation in an audit log the user can open, which is the Action Audit and Undo pattern Smashing Magazine describes for agentic products. Label the second action as a correction on screen, so nobody reads it as a new, separate event. And review why the confirmation step did not catch it.
Only for the first moment. useOptimistic holds a temporary value while an Action is pending and drops it when the Action ends, so it fits the plan echo that appears in the first 100 milliseconds. A run that lasts minutes, survives a page reload or continues while the user works elsewhere needs its state stored on the server and streamed to the client step by step. Render the step list from that server state, and keep useOptimistic for the instant acknowledgement.
Related articles
May 23, 2026 · 7 min read
July 12, 2026 · 7 min read
October 11, 2026 · 9 min read