Multi-Agent Task Claims: Blocking Late Workers with Leases and Fencing
When multiple Agents compete for the same work, a TTL-based claim helps with reclamation but cannot stop an old executor from committing late. This article uses monotonic fencing, attempt identity, and side-effect records to establish provable task ownership.
Key Points
- Topic Focus: AI、AI Agent、DevOps
- Suitable for: indie hackers, full-stack engineers, AI application developers
- Core value: quickly understand background, practical paths, common pitfalls, and engineering advice
Multi-Agent runtimes often place a work item in a shared queue for an idle worker to claim and execute. The most straightforward implementation writes ownerId and expiresAt: after expiration, someone else can take over. However, a paused process does not stop automatically just because database time has passed. A's lease may expire and B may take over and finish, yet A can still return with an old result and overwrite B's work; if the task sends email, modifies a repository, or creates cloud resources, both executors may even produce side effects.
Here, a claim is the coordination layer's record of ownership over who may advance a work item, not authorization to use a tool. A short-lived capability lease limits what a particular tool call can do, while the task lease in this article determines which attempt may commit. General Agent long-running workflows handle checkpoints; this article further tightens the boundary against late writes when multiple workers contend for the same node.
Freeze the claim.v1 Contract
A task stores taskId, immutable inputDigest, stateVersion, schedulerEpoch, claimGeneration, attemptId, ownerId, leaseExpiresAt, cancelGeneration, and resultCommitId. Claiming must be completed in a single linearizable transaction: a task may be claimed only from READY or an expired CLAIMED state, claimGeneration is incremented monotonically, and a brand-new attempt is created. The generation number comes from a CAS on the task record, not from the worker's clock or a random number.
claim(task, expectedStateVersion, schedulerEpoch, owner, ttl)
-> attemptId, schedulerEpoch, claimGeneration, cancelGeneration,
leaseExpiresAt, inputDigest
commit(task, attemptId, schedulerEpoch, claimGeneration,
observedCancelGeneration, inputDigest, resultDigest)
-> COMMITTED | FENCED | STALE_INPUT | CANCELLED
Claiming, renewal, tool acceptance, and result commit must all match the current schedulerEpoch and cancelGeneration; renewal must also match the task, attempt, claim generation, and owner, and may extend the lease only by a bounded window. A worker cannot make its TTL unlimited. etcd's Lock/Election establishes ordering through leases and revisions, while Kubernetes Lease demonstrates the resource-version semantics of coordination records. The storage implementation may vary, but these two responsibilities must remain separate: expiration is used for reclamation, while monotonic values are used to reject stale owners.
Fencing Must Be Enforced at the Commit Point
Before starting each expensive step, a worker checks the remaining lease time, but this check can only reduce wasted work; it cannot prove final ownership. At result commit, the system of record must compare the generation again. An old attempt may upload data to an isolated object area, but it cannot update the task's canonical result, DAG successors, or shared memory. Concurrency control for shared Multi-Agent memory handles version merging for factual records, while task fencing removes results that no longer have an owner before they enter that write path.
Each side-effect slot has one unique durable record: effectId = H(taskId,nodeId,effectSlot), which does not include the attempt or payload. The first PREPARED transition binds payloadDigest with CAS; subsequent different digests return PAYLOAD_CONFLICT, and only a new task revision or an explicitly new slot can create another effect. Every generation reuses that effectId downstream and separately passes claimGeneration for fencing and the resource ETag. A timeout is recorded as UNKNOWN; the takeover worker may only query or reconcile it and may execute under the original key only after confirming that the effect did not occur. It must not blindly send the request under a different key.
If the downstream system supports neither conditional writes, queryable idempotent results, nor reservations, the system cannot claim exactly-once semantics; the node should be configured as a serialized, manually approved NON_REPLAYABLE node. The invocation semantics in MCP timeouts, retries, and idempotency should be a prerequisite for scheduling.
Duplicate Execution Is Allowed; Duplicate Effects Are Not
Lease reclamation implies at-least-once attempts. The scheduler should distinguish attempt_started, the effect states PREPARED/UNKNOWN/COMMITTED, result_staged, and result_committed, rather than infer state from a single running Boolean. When A incurs inference cost but is fenced out, that cost can be counted in the discarded-attempt metric; only a canonical commit releases successor nodes. The result digest should also enter Multi-Agent result provenance and conflict arbitration, preventing the last text returned from automatically becoming the answer.
A negative test has A execute payload X and then lose the response while B generates Y for the same slot. B must receive PAYLOAD_CONFLICT and reconcile X, with zero second external calls. Also cover a stale owner resuming, input changes, cancellation races, and increases to schedulerEpoch, asserting that the generation increases and each task has at most one canonical commit.
Metrics, Rollout, and Safe Rollback
Core metrics include claim contention rate, expired-claim reclamation latency, renewal failure rate, fenced commit count, attempts per task, unknown side-effect backlog, discarded compute cost, and p95 latency from READY to canonical commit. A fenced count of zero is not necessarily healthy; validation may not be running at all. The release gate must also measure coverage of successful epoch and generation validation at every acceptance point, with a target of 100%.
Begin rollout by dual-writing the legacy claim and claim.v1, comparing winners in shadow mode, and then expanding from idempotent reads to conditional writes. If anomalies occur, first stop new claims and increase schedulerEpoch; claim, renew, tool acceptance points, and commit must all reject the old epoch, and old binaries that do not recognize this field must not return to the pool. Switch pools only after confirming that active leases, UNKNOWN effects, and results awaiting commit have all reached zero. Agent failure recovery order can determine whether to retry or hand control to a human, but no recovery path may bypass the epoch and generation comparisons.
Related
Related Content
Article
What Is Vibe Coding? Will It Change How Programmers Work?
Technical trends, engineering practices, and an indie developer perspective on Vibe Coding, AI, and independent development.
Article
From Copilot to Codex: AI Coding Tools Enter the Agent Era
Technical trends, engineering practices, and indie-developer perspectives around AI, AI Agents, and Vibe Coding.
Article
Why Does MCP Matter? Understanding the AI Tool Protocol
Technical trends, engineering practices, and an independent developer perspective on MCP and AI Agents.
Article
Core Architecture of AI Agents: Planning, Memory, Tools, and Execution
Technical trends, engineering practices, and independent developer perspectives around AI Agents and large language models.