Decide, commit, then act
The three-step server pattern that keeps AI agents from leaving your app in a weird state.
- Problem
When a request saves data and then calls another service, a crash or retry in between leaves the app in a state nobody planned for.
- Idea
Decide with a pure function, commit the facts and the intended side effects in one transaction, then run the side effects afterwards.
Try it: crash in the middle
the same requests against a naive handler and an outbox
A naive handler saves the order and then sends the email. If it crashes in between, the email is lost and nothing remembers it was owed. With an outbox, the intent to send is committed together with the order, and a worker sends it after the commit, even after a restart.
The pattern
- Decide, with no I/OA pure function turns
(state, command)into events. It never touches the network, the filesystem or a subprocess, so you can unit-test it, replay it, and it can't hang on a slow API while holding a lock. - Commit facts and intent togetherOne database transaction writes the events, the read models the UI reads, a command receipt, and the outbox: a record of side effects that still need to run.
- Act after commitA worker picks up outbox rows and does the slow, failure-prone work (start the agent, take a git checkpoint). Results come back as new commands, through the same loop.
What the one transaction buys you
Read models can't lead
They commit in the same transaction as the events, so the UI never shows something the log doesn't have.
Retries are idempotent
A replayed command finds its receipt and returns the same result, instead of doing the work twice.
Crashes keep intent
If the process dies right after commit, "start the agent" is already saved in the outbox.
I/O can't corrupt state
The slow part runs outside the transaction, so a timeout can't leave half-written rows.
The consequence most apps get wrong
A command acknowledgement means the intent committed, not that the work finished.
If your UI treats "request accepted" as "done", you ship a lying spinner. Here, "accepted", "agent running" and "follow-up work settled" are separate states. A late checkpoint isn't even allowed to stretch the recorded duration of the agent's turn.
Two traps the docs call out
- Your schema has a past. Every stored event must still decode on replay. A schema change affects every old database on its next startup, not just the newest client.
- Not every effect can replay. Work tied to a provider process that died is retired during recovery, before new work starts.
Steal this
You don't need an event-sourcing framework. You need a pure decide step, one transaction that records facts and intent together, and a worker that does the I/O afterwards. Most "it got into a weird state" bugs come from breaking one of those three.
Use it in your code
A prompt for your coding agent
Run it from the root of your repo. It checks whether the idea fits before it changes anything.
Apply the "decide, commit, then act" pattern to this codebase.
The idea: every state change goes through three steps. Decide: a pure function turns (current state, command) into the changes to make, with no I/O. Commit: one database transaction writes those changes together with a record of the side effects still to run (an outbox). Act: a worker runs the side effects after the commit and reports results back as new commands.
1. Find one workflow where a crash, timeout or retry could leave things half-done, for example saving a record and then calling an external API or sending an email in the same request. Show me the code path.
2. Tell me whether this pattern fits it. If it doesn't, say why and stop.
3. If it fits, propose the smallest version: a pure decide function, one transaction that also inserts an outbox row, and a worker that processes outbox rows with retries. Make retries idempotent, for example with a command ID.
4. Implement it, with tests for the decide function alone, a retry of the same command, and a crash between commit and act.
Don't introduce an event-sourcing framework. Keep the change small and tell me how you verified it.Read the source
Where this comes from
T3 Code (pingdotgg/t3code), the open-source GUI for coding agents. The files behind this lesson: