AI Agent Stale Writes: Stop Updates to Changed Records
Prevent an AI agent from overwriting a support ticket changed after it read it. Use conditional writes, conflict handling, and a release test.
If an agent reads a support ticket at version 12 and a person changes it to version 13, the agent should not write its version-12 decision over the newer record. Carry the version from the read into the same atomic write: use an API's If-Match condition or its native version check. If the condition fails, fetch the current ticket and make a fresh decision, or ask a person to resolve it. Do not resend the stale update.
This protects against a lost update during normal processing, even when no timeout or retry occurred.
Quick take: Read the record and its version together. Send a narrow update that succeeds only if the version is still current. Treat a version conflict as a request to re-evaluate from fresh state, not as a transient error to retry unchanged.
| If you need to decide | Start here |
|---|---|
| What prevents a stale update? | Make the write conditional |
| Is custom coordination required? | Use native concurrency controls before custom coordination |
| How do we prove it works? | Run these release tests |
Make the write conditional
Imagine an agent reads a ticket and prepares to set its priority to normal and assign it to team A. While the agent is deciding, a support engineer marks the ticket urgent and assigns it to team B. If the agent then sends an unconditional update based on its old read, it can replace the engineer's newer values.
The write needs a precondition checked by the system that owns the record. For an HTTP API that supports entity tags, send the tag returned by the read in an If-Match header. RFC 9110 defines this as a way to prevent lost updates: when the condition does not match, the origin server must not perform the requested method and may report 412 Precondition Failed (RFC 9110, section 13.1.1). The endpoint must actually implement the condition; the presence of an ETag alone does not prove that a write checks it.
For a database-backed record, the same idea is an atomic update with a filter on the expected version or current value. A separate “read, compare, then write” sequence is not enough: another change can land after the comparison but before the update.
This is different from retry safety. A version conflict means the record changed since the agent read it. A timeout after dispatch means the agent may not know whether the write completed. Use the separate tool retry guide for uncertain completion and duplicate side effects.
Give the agent a conflict path
For a support-ticket workflow, make the application—not the model—own this sequence:
- Read the ticket and its concurrency token. Preserve the ETag, version number, or equivalent value supplied by the source system.
- Prepare a narrow patch. Identify the exact fields the approved action may change. Avoid replacing the full ticket when the agent only needs to update priority or assignment.
- Submit a conditional update. The source system must check the expected token as part of the update. A successful precondition means the agent acted on the version it read.
- On conflict, stop that attempt. Fetch the new ticket, reconsider the action against the changed fields, and create a new update only if the current workflow rules still permit it. If the change makes the intent unclear, leave the record unchanged and route it for review.
- Bound repeated conflicts. If another update arrives during re-evaluation, stop after the workflow's configured limit instead of looping against active human work.
Keep the previous version, attempted version, result, and next status in the operational record so an owner can tell whether the agent applied its update or yielded to a newer change. This is a recommendation for traceability, not a claim that version checks validate the agent's judgment.
Use native concurrency controls before custom coordination
If the destination already offers an atomic conditional update, use that feature before building custom coordination. MongoDB's manual says single-document writes are atomic and recommends including the expected current value in the update filter so a concurrent change causes the second update not to match (MongoDB: Atomicity and Transactions). DynamoDB documents the equivalent pattern with a version attribute and condition expression; when the version differs, the conditional write fails, and the client must re-read before trying again (DynamoDB: Optimistic locking with version number).
For a single-record update, a target-native ETag, version field, or conditional expression is usually enough. A no-code step is also sufficient if it can pass the version read and the destination checks that version atomically. A workflow that only reads the record, compares a value in its own step, and then sends an unconditional write still has a race window.
Build a custom write path only when the target's current first-party documentation confirms there is no usable conditional update and the workflow must protect concurrent changes. That path must enforce the version check where the record is written; an external wrapper cannot guarantee it by doing another pre-read. If the target cannot enforce the condition, keep the action as a draft or require a person to apply it. Also check the destination's scope: MongoDB's atomicity statement is per document, while DynamoDB's guidance notes that global tables use last-writer-wins reconciliation across Regions, where version-based optimistic locking does not work as expected (DynamoDB: Concurrent update best practices).
Run these release tests
Use a staging ticket with a known version. The release gate should check the actual destination endpoint and observe the saved record, not only the agent's explanation.
| Scenario | Expected result |
|---|---|
| No change lands after the agent reads | The conditional update succeeds and changes only the allowed fields. |
| A person changes the ticket before the agent writes | The stale update is rejected; the person's newer values remain intact. |
| The agent receives a conflict and re-reads | Its next decision uses the new ticket state and a new concurrency token. |
| Another conflict happens during re-evaluation | The run stops at its configured limit and hands the case to an owner. |
| The endpoint ignores or cannot enforce the supplied token | The test fails; keep autonomous writes disabled for that path. |
| The write commits but its response is lost | Run the separate idempotency and reconciliation test in the retry guide. |
A blank or unobserved result is not a pass. The test should show the persisted fields and version after every attempted write.
For a broader review of tool boundaries, conflicts, and release evidence, see Zenovae's AI agent reliability audit. The production-ready agent checklist covers other pre-release failure paths, and the production agent build describes a bounded build with an evaluation gate. For ticket permissions and untrusted ticket text, see Can an AI Support Agent Trust Instructions in a Ticket?.
Sources
Need Help with Your AI Project?
At Zenovae, we build production-ready AI systems that scale. From OpenClaw setup to custom integrations, Mission Control workflows, and full-stack delivery, we can help you ship faster and avoid costly mistakes.
Let's Talk