AI coding

Who Owns This Branch? Tracking Agent Work Across a Queue

Antonio Evans9 min read

A whiteboard-style diagram showing a queue of tasks on the left, each with a label like 'agent-7:branch-feature-xyz:env-staging'.
Key points

Key points

  1. 1Ownership is tracked by a unique label on each task that encodes agent ID, branch, and environment, so the queue has one authoritative answer to who is doing what.
  2. 2A lease with a timeout enforces exclusivity: only the holder can renew, and an expired lease returns the task to the pool.
  3. 3An agent that dies without releasing its claim is handled by lease expiry plus a check of what actually landed, never by deleting queue records by hand.
  4. 4Auditing the label, the lease and the branch state against each other is the only reliable way to catch duplicate work before two diffs reach review.

Why a queue alone does not tell you who owns a branch

A job queue answers "what is next." It does not answer "who is holding what," and those are different questions. Once you run more than one coding agent in parallel, the second question becomes the expensive one. Teams that run agents side by side report that they "stomp on each other's code or confuse each other," and that productivity gains disappear when agents share a working copy Run multiple coding agents safely with git worktrees. The standard fix is one workspace per writer, usually a git worktree with its own branch Claude Code worktree docs.

Worktrees prevent two agents from editing the same files, but they do not prevent the same task from being handed to two agents. Note the distinction stated plainly in a parallel-agent guide: worktrees "do not prevent merge conflicts or incompatible changes when you combine the branches" Superset parallel agent guide. Ownership is a scheduling problem, and it has to be solved in the queue, not in git. A multi-agent workspace only stays reliable when coordination is treated as infrastructure, with explicit task boundaries, isolated execution and evidence-based merges Augment Code multi-agent guide.

The decision rule for a practitioner is simple: if two agents could in principle receive the same task record, you need ownership tracking. If your queue already guarantees that each record is consumed exactly once, you still need it, because "consumed once" breaks the moment a worker crashes after claiming but before finishing.

A label scheme that encodes agent, branch and environment

Start with a naming contract. Every task carries a single label field that is parseable by machine and readable by a human on call. Anything free-form will drift within a week.

Conventional Branch gives you a proven structure for the branch half of the label: a type prefix, a slash, then a lowercase hyphenated description, with dedicated prefixes for agents such as claude/, codex/, copilot/, cursor/ and a generic ai/ Conventional Branch. The spec also forbids uppercase letters, underscores, spaces, and leading, trailing or consecutive hyphens, which makes the branch name safe to embed in a label without escaping.

A practical label format that fits inside those rules:

owner=<agent-id>;branch=<type>/<desc>;env=<env>;task=<ticket>;epoch=<n>

For example, owner=agent-07;branch=feature/issue-412-search-copy;env=preview-b;task=412;epoch=3. The epoch counter matters. Each time a task is re-leased, the epoch increments, and any worker holding an older epoch is stale by definition. Branch naming conventions like Conventional Branch give you the intent prefix; the epoch gives you the generation. Together they answer "who is doing this, on what branch, against which environment, and how many times has this claim changed hands."

Keep the label as data, not as a comment in a prompt. Labels written into a branch name or a commit trailer are visible to every agent and human, but a branch name can only carry so much. Environment identity is the field teams most often omit, and it is the one that causes the worst incidents. A worktree allocates a separate file tree and index, but it "does not allocate a new database, port, container name, or provider rate limit" Superset parallel agent guide. If two agents share a preview database, an environment label is the only thing telling you whose migration broke whose tests.

Label fieldExampleWhy it is requiredFailure it prevents
Owneragent-07Identifies the current lease holderTwo agents editing the same branch
Branchfeature/issue-412-search-copyTies the task to a unique refDuplicate branches for one ticket
Environmentpreview-bIsolates ports, databases, servicesCross-agent data collisions
Task412Links queue record to trackerOrphaned work with no ticket
Epoch3Distinguishes re-leasesA stale worker publishing over a new one

Two rules keep the scheme honest. First, the label is written by the queue service at claim time, never by the agent, so it cannot be faked or forgotten. Second, the branch name in the label must match a real branch that the queue can verify with git worktree list or a remote ref lookup Claude Code worktree docs. If the label says feature/issue-412-search-copy and no such branch exists, reconciliation has found a leak.

Lease and timeout rules that stop double claims

A lease is a time-bound grant of exclusive access. Martin Fowler's pattern catalogue defines it precisely: a node asks for a lease for a limited period, can renew before expiry, and the lease expires if it does not; a heartbeat is how clients refresh the time to live Lease pattern. Every rule you need follows from that definition.

The atomic claim. Acquire the lease with a single conditional write, not a read followed by a write. In Redis terms that means SET key token NX PX 30000, where NX only creates the key if absent, PX sets expiry, and the token identifies the owner Distributed locks in Go. In a relational store it means an UPDATE ... WHERE status = 'Queued' RETURNING * or an INSERT guarded by a unique constraint on the task ID. The pattern of a status column defaulting to queued and moving through running to a terminal state is the conventional job queue model YugabyteDB job queue.

Never release a lease you do not own. The classic bug is that worker A's lease expires, worker B claims the task, and then A finishes and deletes the key, wiping B's claim. The fix is to check ownership atomically, for example with a Lua script comparing the stored token before deleting Distributed locks in Go. The same rule applies at the database layer: release with DELETE ... WHERE task_id = ? AND owner = ? AND epoch = ?.

Renew on a fraction of the lease. If the lease is 60 seconds, renew every 20. Windmill's persistent volume leases use exactly this shape: an exclusive lease with a 60-second TTL auto-renewed every 10 seconds, and a second job targeting the same volume waits for release Windmill AI sandboxes. The fraction matters because a single missed heartbeat due to a slow event loop should not orphan the task.

Bound the wait, too. Temporal's worker-specific queue pattern sets a scheduleToStartTimeout so an activity cannot sit in a queue indefinitely when the target worker is gone; when it expires the activity fails and the workflow compensates rather than retrying into the same dead queue Temporal worker-specific task queues. Your queue needs the same pair of timeouts: lease duration, and a maximum time a task may wait in Queued before an alert fires.

lease_seconds  = 900   # claim validity for a long coding task
renew_every    = 300   # heartbeat at one third of the lease
queue_wait_max = 3600  # alert if no agent claims within an hour
renew_attempts = 3     # then the agent must stop and release

Set the lease from measured task duration, not from hope. A reasonable default is roughly three times the p95 runtime of the task class, because a lease shorter than the normal run guarantees a second agent will pick up work that is still in progress.

What to do when an agent dies without releasing its claim

A crashed agent leaves three artifacts in an inconsistent state: a lease that has not been renewed, a worktree with uncommitted or half-committed changes, and possibly a pushed branch. Handle them in that order.

First, let the lease expire; do not force-release it the moment you notice a missing heartbeat. A process can be paused, a network can blip, and an agent can recover. The lease is the backstop precisely because the holder might be alive but silent Lease pattern. Force-release is a manual override that requires a human to confirm the process is really gone.

Second, before re-leasing, check what actually landed. Look for the branch in the label, the diff against the recorded base commit, and any commits already pushed. Parallel-agent workflows recommend recording the base with git rev-parse HEAD before work starts and reviewing git status --short plus the full diff afterward precisely so an incomplete run is obvious Superset parallel agent guide. If the branch has commits, the recovery agent inherits the branch rather than starting over; if it has only uncommitted files, the cleanest move is often to discard and re-run with a fresh epoch.

Third, increment the epoch and re-lease with a full label rewrite. The new holder gets epoch+1 and a new owner token. Any late write from the dead agent now fails the ownership check, which is the whole point of carrying the epoch in the label Distributed locks in Go.

Fourth, clean the workspace. Worktrees that are abandoned accumulate disk. Practitioners report automatic worktree creation consuming gigabytes per workspace for a mid-sized codebase Trigger.dev on worktrees, and Claude Code itself checks for changed files, untracked files and new commits before it will remove a worktree on exit Claude Code worktree docs. Automate the same check: remove a worktree only when the branch is merged or explicitly discarded, otherwise quarantine it and alert.

Crash scenarioLease stateSignature to checkRecovery action
Killed mid-process, no commitsExpiredEmpty diff, clean git statusRemove worktree, re-lease with epoch+1
Killed after edits, before commitExpiredUntracked or modified filesQuarantine worktree, re-run from base
Killed after commit, before pushExpiredLocal commit ahead of baseRe-lease, inherit branch, continue
Killed after push, before releaseExpired or liveRemote branch existsRe-lease, open or update the change request
Hung but heartbeatingLiveLease renewing, no file changesHuman override, then force-release

The rule to carry away: an expired lease authorizes a re-claim, but it never authorizes deleting someone's work. Delete only what reconciliation has proven is empty.

Auditing and reconciling the queue against reality

Reconciliation is a scheduled job that compares three sources and reports mismatches: the queue's lease records, the git state of every labeled branch, and the environment allocation table. Run it every few minutes and treat each mismatch as a named condition, not a log line.

  • Live lease, dead agent: lease renewing but no process heartbeating to the supervisor.
  • Expired lease, task still marked running: the queue never transitioned the record.
  • Branch in label missing from the repository: the holder never created it, or someone deleted it.
  • Branch exists with no lease: orphaned work, usually a crash after release.
  • Two labels for one branch: the strongest signal that duplicate work happened.
  • Environment allocated to a task whose lease is expired: a resource leak waiting to cause a port or database collision.

Distributed queues built for scale keep terminal jobs in the table with an explicit status such as success, failed or canceled, which makes this kind of audit a query rather than a log hunt YugabyteDB job queue. Design your schema the same way: never hard-delete a task, mark it terminal and keep the owner, epoch and timestamps for the audit window.

The second audit is scoped to resources rather than tasks. If your agents run in sandboxes, remember that isolation is a bundle of boundaries, and a sandbox that shares a host filesystem or a network egress path is not isolated in any useful sense Sandboxes for coding agents. True isolation typically means hardware-level separation through microVMs rather than a shared kernel What is an agent sandbox. Your reconciliation should therefore verify that the environment label maps to a genuinely separate runtime, not just a separate directory.

Two metrics tell you whether the whole scheme is working. Lease conflict count measures how often two agents attempt the same task, and should be near zero under a correct claim. Orphan rate measures how often reconciliation finds a branch no lease owns, and should trend down as your crash handling matures. If either climbs, look at lease duration first: too short and healthy agents get preempted, too long and dead agents hold tasks that nobody can take.

For teams running many agents across many environments, the environment table is where ownership usually breaks down. Giving each agent its own isolated environment with its own credentials and its own lifecycle is the part that most orchestration layers leave to you. That is also where a managed layer helps: Quanta Inference (https://quantaintelligence.ai/inference) puts text, image, video, audio and embedding models behind one API key, which lets the label and lease logic focus on ownership instead of provider credentials. Team plans and consulting calls on the same site are the right next step if you want the reconciliation design reviewed against your own queue.

FAQ

FAQ

Where should the ownership label live?

Put it on the task record in the queue, written by the queue service at claim time, and mirror it into the branch name using a convention such as Conventional Branch Conventional Branch. The queue copy is authoritative; the branch name is for humans and for cheap verification. Do not let agents write their own label, or you lose the single source of truth.

How long should a lease be?

Set it to roughly three times the p95 runtime of that task class, and renew at one third of the lease duration. Windmill's volume leases use a 60-second TTL renewed every 10 seconds as a reference shape for short operations Windmill AI sandboxes. Long coding tasks need much longer leases; the ratio matters more than the absolute number.

Can an agent release another agent's claim?

No. Release must be conditional on the owner token and epoch, checked atomically so that a lease that expired and was re-acquired is not deleted by the old holder Distributed locks in Go. Human overrides are a separate, audited path.

Do worktrees solve duplicate work?

No. Worktrees isolate files and the index, but they do not allocate a new database, port or container Superset parallel agent guide. They stop file collisions while agents work; they do not stop the same task being scheduled twice. Ownership tracking and leases are what prevent duplicates.

What is the first thing to check when an agent dies mid-task?

Whether the lease has expired, then whether the branch in the label has any commits or uncommitted changes. If it has work, inherit the branch and bump the epoch; if it is empty, discard the worktree and re-run. Never re-lease or delete anything until you know what landed, because the diff against the recorded base commit is the only evidence you have Superset parallel agent guide.

Sources

Sources

  1. Run multiple coding agents safely with git worktrees - Medium
  2. Run parallel sessions with worktrees - Claude Code Docs
  3. Run three AI coding tasks in parallel, then review each change - Superset
  4. How to Run a Multi-Agent Coding Workspace (2026) | Augment Code
  5. Conventional Branch, A Git Branch Naming Convention
  6. Lease - Martin Fowler
  7. Distributed Locks in Go: Correctness, Failure Modes, and Production ...
  8. Distributed job queue data model | YugabyteDB Docs
  9. AI sandboxes & volumes: isolated environments for coding agents
  10. Worker-Specific Task Queues Pattern - Temporal documentation
  11. We ditched worktrees for Claude Code. Here's what we use instead
  12. Sandboxes for Coding Agents - Penligent
  13. What Is an Agent Sandbox? A Guide to Isolated Execution for AI Agents
About the author

About the author

Antonio Evans
Antonio Evans

Antonio Evans is the founder of Quanta Intelligence and a fractional CTO who builds HIPAA-ready AI systems for health and life-science companies. Before that he was technical co-founder of FriendsEAT for twenty years, CEO of Socialdraft, and a data engineer at IBM. Antonio lives with his dog Santino in Lisbon, Portugal.

All articles