An agent’s plan is not a transaction. Once tools change external state, the system needs to know which effects happened, which remain uncertain and which operations may safely be retried. Fluent narration does not provide that bookkeeping.
Distinguish deciding from doing
A model can propose a sequence of actions without providing any guarantee that the sequence will execute atomically. Suppose a hypothetical assistant updates a project record, creates a deployment and posts a status message. Those operations may cross three services with different failure modes. The model’s explanation can describe them as one coherent task, but the infrastructure still sees separate requests. I would make the proposal an explicit object before execution, with named operations, dependencies, preconditions and the scope of authority under which each operation is allowed.
Anthropic’s guidance on building agents distinguishes predefined workflows from systems where the model dynamically directs its process and tool use. That distinction is useful, but neither approach eliminates the need for execution semantics. My view is that increasing planning flexibility should make the surrounding action boundary more explicit, not less. A model may choose among permitted operations; the executor should still decide whether the proposal is well formed, whether its preconditions hold and whether it can be applied under the caller’s actual permissions.
References: [1] Anthropic: Building effective agents
Give each external effect an identity
A timeout leaves an uncomfortable possibility: the remote service may have completed the operation even though the caller never received the response. Retrying with a fresh identity can create a duplicate. I would assign an operation identifier before the first attempt and preserve it across retries of that same intended effect. The identifier should belong to the execution layer, not be regenerated whenever the language model rephrases its plan. Two descriptions of the same action are not necessarily two authorized actions.
AWS’s discussion of idempotent APIs explains why caller-provided request identifiers help make retries safe and why the service must handle repeated requests consistently. In an agent system, I would additionally bind the identifier to a canonical payload digest. Reusing the same identifier with different arguments should be rejected rather than silently interpreted as a retry. That rule catches a subtle failure mode: a repair loop changes a parameter while retaining an old operation key, making it unclear whether the system intends to repeat an action or perform a different one.
References: [2] AWS Builders’ Library: Making retries safe with idempotent APIs
Record intent before attempting the effect
An execution ledger can record the operation identifier, payload digest, expected resource version and status before making the external request. Useful states include prepared, in flight, succeeded, rejected and outcome unknown. I would not collapse outcome unknown into failed, because the next safe step differs. A rejected request may be corrected and resubmitted under a new intent. An uncertain request should first be reconciled using its operation identity or the remote system’s observable state. Otherwise the retry policy can manufacture duplicate effects while claiming to recover from failure.
The ledger itself should be updated transactionally where possible. PostgreSQL documents transactions as grouping database operations into an all-or-nothing unit, with intermediate changes not visible as committed state. That is useful for recording an internal state change and an outbox entry together. It does not automatically include a remote HTTP call in the database transaction. I would keep the boundary honest: the database can atomically record the intention to perform an effect, while a worker still needs retry and reconciliation rules for delivering that effect to another service.
BEGIN;
UPDATE tasks SET state = 'approved', version = version + 1
WHERE id = :task_id AND version = :expected_version;
-- The application must require exactly one updated row.
INSERT INTO outbox(operation_id, payload_digest, payload, status)
VALUES (:operation_id, :digest, :payload, 'prepared');
COMMIT;
-- A separate worker delivers the recorded intent using the same operation_id.References: [3] PostgreSQL documentation: Transactions
Validate against the state you actually change
An agent may inspect a resource, reason for several seconds and then propose an update based on the earlier state. Another actor can change the resource during that interval. I would include an expected version or another suitable precondition in the mutation request. If the condition no longer holds, the operation should stop and obtain a fresh view. Re-reading immediately before writing is insufficient if the read and write are not protected by a consistency mechanism; the resource can change between those two operations as well.
Consider an illustrative task at version 7 with two remaining prerequisites. The agent prepares a completion update, but another process adds a prerequisite and increments the version to 8. An unconditional update would erase the significance of that change. A conditional update against version 7 fails visibly. The executor can then ask the planner to reconsider using the new state. This is a useful division of labor: the model handles interpretation, while the resource service enforces the state transition’s concurrency rule with information current at the point of mutation.
Compensation is not rollback
A database rollback can prevent uncommitted changes from becoming visible. An external message that has already been read cannot be made unread. A deployment that ran briefly may have served traffic even if the previous version is restored. I would distinguish compensation from rollback in both the implementation and the user-facing explanation. Compensation is another operation intended to reduce the consequences of an earlier effect. It can fail, require authorization or leave residual consequences. Calling it undo can promise more than the system can deliver.
For a multi-step workflow, write down which operations are reversible, compensatable or effectively irreversible before allowing the planner to compose them. An irreversible step may belong after validation and preparation of the reversible parts. That ordering is not universally possible, and some services offer stronger transactional facilities than others. The important point is to expose the dependency rather than letting a generated plan imply atomicity. When partial completion occurs, the system should report the actual completed effects and remaining uncertainty instead of describing the whole task with one success or failure flag.
Test crashes at the awkward moments
A happy-path test does not exercise the main reason for durable execution state. I would inject failures immediately before sending a request, after the remote effect but before receiving its response, and after receiving success but before recording it locally. Each case should lead to a defined recovery path. Replaying the same intended operation should either recover its existing result or be rejected consistently, rather than create an additional effect. These tests need a controllable fake service with explicit operation identities and observable side effects.
Also test the planner’s behavior after recovery. If the executor reports outcome unknown, the model should not invent a success narrative or create a replacement action with a new identifier. The orchestration should constrain the next step to reconciliation or a clearly authorized alternative. I would measure duplicate effects, unresolved intents and incorrect success reports separately from ordinary task completion. A high completion rate can hide a small but consequential rate of duplicate changes, especially when evaluation checks only the final state and ignores how many times the action occurred.
Use the smallest boundary that is sufficient
The counterargument is that a durable workflow engine is excessive for a simple assistant. That can be true. A read-only research helper may need cancellation and provenance without any mutation ledger. A single idempotent API operation may need only a stable operation key and a stored result. I would scale the machinery to the consequences and recovery requirements, not to the fashionable label agent. The essential requirement is a truthful account of effects; the implementation can remain small when the effect space is small.
Once a system coordinates several external mutations, however, the bookkeeping cannot be replaced by model intelligence. Better reasoning may choose a better plan, but it does not resolve whether a timed-out request committed remotely. Transaction boundaries make that uncertainty representable and recoverable. I want the planner to be flexible about how it solves a task while the executor remains precise about what happened. That division lets the system explain partial progress honestly, retry deliberately and stop when the available state does not justify another action.
Sources and further reading
- Anthropic: Building effective agents
Primary distinction between predetermined workflows and dynamically directed agent processes.
- AWS Builders’ Library: Making retries safe with idempotent APIs
Primary engineering guidance on retry identity, repeated requests and ambiguous outcomes.
- PostgreSQL documentation: Transactions
Primary description of database transaction boundaries; remote effects require separate coordination.