Multi-Agent Concurrency Control: Six Mechanisms Compared
Multi-agent concurrency control compared: locks, version checks, conditional writes, serializable transactions, queues and leases, and where each breaks.
Multi-agent concurrency control compared: locks, version checks, conditional writes, serializable transactions, queues and leases, and where each breaks.
TL;DR: Multi-agent concurrency control is the set of mechanisms that keep agents from corrupting state they share: locks, version checks, conditional writes, serializable transactions, single-writer queues and leases. Every one of them assumes the gap between read and write is short. Agents read, then think for seconds, then write, so locks block for too long and optimistic schemes throw away expensive work. The pattern that holds up is to let the agent reason on a plain read, then commit through a short transaction that re-checks what it relied on.
Multi-agent frameworks put most of their machinery into deciding who runs next and passing messages between agents. Much less of it covers what happens when two agents write the same thing; the position paper below describes existing workflows as ones that “typically assume benign interleavings.” The orchestration graph is clean, every agent’s trace looks reasonable, and the shared state at the end is wrong.
The fix isn’t new. Databases solved concurrent writers decades ago, and the vocabulary carries over almost unchanged: stale reads, lost updates, isolation levels, optimistic and pessimistic control. What doesn’t carry over is the timing. A database transaction reads and writes inside a few lines of code. An agent reads, builds a prompt, waits for a model, maybe calls two more tools, then writes.
This post surveys the options: what each does, where it breaks under agent timing, and how to pick one.
Multi-agent concurrency control is the set of rules and mechanisms that make concurrent AI agents’ reads and writes to shared state produce a correct result, as if the agents had run one at a time. It covers conflict detection, isolation guarantees and controlled access to shared resources, adapted to agents whose reasoning step lasts seconds.
The definition matters because of what it excludes. Deciding which agent runs next is orchestration, covered in Agent Coordination. Passing messages is communication. Concurrency control is about the state underneath both, and it only applies when two agents can touch the same state inside the same window. That condition is multi-writer contention, and it is narrower than it sounds.
The clearest statement of the argument is Position: Multi-Agent Systems Should Prioritize Concurrency Control by Xin Yang, Letian Li, Zimo Ji, Terry Jingchen Zhang and Wenyuan Jiang, submitted to arXiv in June 2026. Its abstract states the thesis directly: “many MAS failures are fundamentally concurrency control problems: agents concurrently read and write shared state, and long LLM inference windows amplify the risk of stale reads, lost updates, and inconsistent outcomes.”
Three parts of the paper are worth knowing before you design anything.
It maps familiar failures onto classical anomalies. The full paper walks through four scenarios usually blamed on coordination or communication: a stale read (an agent writes code against a config file another agent just changed), a lost update (two agents edit different functions in one file and the later write discards the earlier one), a stale correction (an agent executes a plan before a correction reaches it) and action-message desynchronization (an agent acts on a message describing a world state another agent already changed).
It names the timing problem. The authors call it a temporal asymmetry: “inference spans seconds to minutes while tool execution completes in milliseconds.” Their summary of what that does to the two classic strategies is blunt: “Pessimistic locking blocks unacceptably at these timescales; optimistic control wastes expensive computation on aborts.”
It scopes its own claim. The paper says its claim “targets MAS in which agents read or modify shared mutable state during long inference windows,” and that “Systems with disjoint inputs face lower concurrency risk.” It is a position paper, and its limitations section says the trade-offs are discussed qualitatively, with empirical validation left for future work.
It also answers the obvious objection that eventual consistency is enough: that view “conflates convergence with semantic correctness.”
One note on the paper’s examples: its opening example and two of its four failure scenarios involve coding agents on a shared file system; the other two involve message-based agents and agents in embodied environments such as Minecraft-based benchmarks. Code that goes through branches and reviewed pull requests already has a merge step. The anomalies cost the most where there is none: money, quotas, inventory, schemas, live account state. That is unmergeable state, and it is where the rest of this post focuses.
Every classic mechanism bets that the window between read and write is short. Locks bet that holding is cheap. Optimistic control bets that conflicts are rare and retries are cheap. An agent breaks all of those bets at once: locks block other agents for a whole model call, conflicts get likelier as the window grows, and each abort throws away a reasoning step that costs money and may not come out the same way twice. The mechanisms still work. What changes is where you put the transaction boundary.
The running example is illustrative: two sourcing agents allocating units from one warehouse’s stock of a single SKU to different customer orders. Stock is a number both agents read and both decrement.
Pessimistic locks. The agent takes SELECT ... FOR UPDATE on the stock row, reasons, writes and commits. Correct, and the worst fit for agents: per the PostgreSQL docs, “a transaction seeking either a table-level or row-level lock will wait indefinitely for conflicting locks to be released,” unless a deadlock is detected. Every other agent that needs the row waits out the first agent’s whole model call.
Leases. A lease is a lock with an expiry, so a stalled agent doesn’t hold the resource forever. The catch is the one Martin Kleppmann describes in How to do distributed locking: a client can pause past its lease and then write as if it still held it. His fix is a fencing token, “a number that increases (e.g. incremented by the lock service) every time a client acquires the lock,” sent with every write so storage can reject writes from stale holders. A slow model response looks exactly like a long pause, so agents need the fencing.
Optimistic version checks. The agent reads the row and its version number, reasons without a lock, then writes only if the version hasn’t changed. Nobody blocks during the think. The cost is wasted reasoning under contention: if ten agents race for one row, nine throw away a model call.
Conditional writes. Optimistic control where the condition is the business rule itself. UPDATE stock SET qty = qty - 3 WHERE sku = ’A-1’ AND qty >= 3 checks and writes in one statement. Under PostgreSQL’s Read Committed level, a second concurrent updater waits for the first to commit, then the docs say “the search condition of the command (the WHERE clause) is re-evaluated to see if the updated version of the row still matches the search condition.” If the stock is gone, it updates nothing. This is the cheapest correct fix when the rule fits on one row.
Serializable transactions. When the rule spans rows, such as “allocated plus reserved across all bins stays at or under on-hand,” put the reads and the write in one transaction at Serializable isolation. PostgreSQL’s implementation monitors for conflicts and, per the docs, “does not introduce any blocking beyond that present in repeatable read.” When it detects a conflict, one transaction fails with a serialization error, and “applications using this level must be prepared to retry transactions due to serialization failures.” The retry should re-run the short transaction, not the model.
Single-writer queues. Route every write for a given key through one consumer. Often the simplest answer for per-key state. It caps each key at one writer’s speed, and one tool that writes directly breaks the guarantee.
| Mechanism | Blocks other agents during the think? | Cost when two agents conflict | Breaks when |
|---|---|---|---|
| Pessimistic lock | Yes, for the whole model call | Waiting; possible deadlock | Think time is long or lock order varies |
| Lease without fencing | Yes, until expiry | Stale holder writes after expiry | An agent pauses past its lease |
| Version check | No | One wasted reasoning step per loser | Contention is high on one key |
| Conditional write | No | Loser’s write affects zero rows | The rule spans more than one row |
| Serializable transaction | No | Serialization error, retry | The transaction is held open across the model call |
| Single-writer queue | Queued agents wait their turn | None; writes are serialized | An agent bypasses the queue |
A common design for multi-agent systems is event-driven: agents publish and consume events on a log such as Kafka instead of calling each other. That gives you one of the mechanisms above for free, and only one.
Kafka’s ordering is per key. “Events with the same event key (e.g., a customer or vehicle ID) are written to the same partition, and Kafka guarantees that any consumer of a given topic-partition will always read that partition’s events in exactly the same order as they were written” (Kafka introduction). Within a consumer group, “each partition is assigned to exactly one consumer in the group” (KafkaConsumer docs). Key the allocation events by SKU and you have a single-writer queue per SKU without building one.
It leaves two gaps. Delivery is at-least-once when a consumer commits its offset after processing; the same consumer docs say “each record will likely be delivered one time but in failure cases could be duplicated.” So the consumer’s write still needs an idempotency key or a conditional check. And the ordering covers one key. Two consumers handling different SKUs that both draw on one shared budget still race on the budget, and a consumer that reads open orders and a forecast from other systems still decides on several moments.
An illustrative scenario with constructed numbers. Warehouse stock for SKU A-1 is 5 units. Two sourcing agents work different orders.
t = 0.0 s Agent 1 reads qty = 5, order needs 3
t = 0.4 s Agent 2 reads qty = 5, order needs 4
t = 7.1 s Agent 1 decides to allocate 3, writes qty = 2
t = 9.8 s Agent 2 decides to allocate 4, writes qty = 1
7 units promised, 5 on the shelf; stored qty says 1
Both anomalies from the paper show up: Agent 2 decided on a stale read, and its write overwrote Agent 1’s, so the stored number is wrong as well as the allocation. The fix that respects agent timing keeps the model call outside any transaction and makes the commit conditional:
-- after the agent has decided, outside the model call
UPDATE stock
SET qty = qty - 4
WHERE sku = 'A-1'
AND qty >= 4
RETURNING qty;
-- zero rows: stock moved under you; give the agent the new qty and let it re-plan
Agent 2’s update sees qty = 2 (if Agent 1’s transaction were still open, it would wait for it, then re-evaluate the condition), fails qty >= 4 and returns nothing. The agent learns exactly why. The paper notes that this kind of semantic feedback lets agents adapt, where an opaque “retry” gives no guidance.
Work through four questions in order.
The first two exits need no concurrency control at all. Only the last two do.
Do two agents ever write the same state in the same window? If every agent owns its slice, such as one agent per customer with no shared counters, you have partitioning and need capacity, not concurrency control.
Does the state have a native merge? Code in git, text in a CRDT document and append-only logs all merge. Use the merge. Stop here.
Does the rule fit on one row? Use a conditional write or a version check. Expose it as a single tool, such as allocate(sku, qty), so the agent never holds a check and a write as two steps.
Does the rule span rows or tables? Use a short serializable transaction that re-reads and writes, and retry the transaction on serialization failure. If the write lands in a system you don’t control, write a reservation in a store you do control first, then make the external call with an idempotency key, then confirm or release.
Every mechanism above coordinates writes inside the store that runs it. If stock is owned by an external warehouse system, your database can serialize your agents’ reservations but not that system’s own writes. And the read side stays open: an agent that reads stock from one system, open orders from another and a forecast from a pipeline decides on three different moments, however clean its commit.
For the deeper treatment of each failure shape, see Race Conditions Between AI Agents and Agentic TOCTOU.
The mechanisms above need a store that holds the contended state and runs real transactions over it, and a read that sees all the context a decision depends on at one moment.
The Tacnode Context Lake™ holds the decision context agents share: what they read before acting and what they decided. For state Tacnode holds, such as reservations, allocations your agents make, per-agent limits, task claims and agent actions, conditional writes and serializable transactions run with full ACID semantics, and serializable isolation is the default. Two agents committing conflicting allocations against the same rows can’t both commit; one gets the conflict and re-plans.
For data your own systems hold, such as stock in a warehouse system or orders in an order system that arrive by change data capture, Tacnode doesn’t enforce anything inside those systems. It gives each agent’s decision an internally coherent view: raw rows and derived values, such as available-to-promise across bins, computed against the same set of ingested events. Each decision’s output is written back and becomes context for the next agent’s check.
If your agents only retrieve documents and never contend on state, none of this applies; any retrieval store, pgvector included, covers that.

Nearly two decades at Microsoft, including the SQL Server engine. Leads engineering for the Tacnode Context Lake™.
Fresh Context Newsletter
The argument behind the posts — what breaks when AI acts on stale data, from the team building the Context Lake. About twice a month.
Book a demo and discover how Tacnode can power your AI-native applications.
Book a Demo