Here’s a failure mode every multi-agent setup eventually hits.
You have a backlog of tasks and two workers polling it. Worker A calls GET /v1/tasks, sees task #42 at the top, and starts working on it. Forty milliseconds later, Worker B runs the same query, sees the same task, and starts working on it too. Both agents burn tokens on the same job. Both try to complete it. One of them wasted its entire run.
The usual workarounds are all bad. You can shard by label so each worker has a private queue — until one worker falls behind and the others can’t help. You can have an orchestrator assign every task — now the orchestrator is a bottleneck and a single point of failure. You can have workers “mark” tasks by writing a label before starting — that’s just the same race one write later.
The fix is an atomic claim, and as of today it’s built into Delega.
One call, one winner
POST /v1/tasks/claim atomically claims the next claimable task and returns it. If ten workers call it at the same instant, ten different tasks come back — or {"task": null} for the workers that found the queue empty. There is no window between “see the task” and “take the task.”
curl -s -X POST https://api.delega.dev/v1/tasks/claim \
-H "X-Agent-Key: $DELEGA_AGENT_KEY" \
-H "Content-Type: application/json" \
-d '{"labels": ["invoices"], "lease_seconds": 600}'
{
"task": {
"id": "74d0be2b7a4b4564b6f186fb3f3769c2",
"content": "Process invoice batch 2026-06",
"priority": 1,
"status": "claimed",
"claimed_by_agent_id": "8f4c7d91d14d4cfaa80a4d6f55de4b0c",
"claimed_at": "2026-06-09 14:00:00",
"lease_expires_at": "2026-06-09 14:05:00"
}
}
A task is claimable when it’s open, unclaimed, and either unassigned or assigned to the calling agent. Claims hand out work in priority order (priority first, then oldest created). You can scope the claim with project_id and labels, so one backlog can feed several specialized worker pools.
A claim is a lease, not a lock
Agents crash. Processes get OOM-killed. A context window fills up mid-task. If a claim were a permanent lock, every crashed worker would strand a task forever.
So a claim is a lease: it expires. The default is 300 seconds, configurable from 30 to 3600 per call.
Three rules govern the lease:
- Heartbeat to keep it.
POST /v1/tasks/:id/heartbeatextends the lease while you’re still working. If you don’t hold an active claim, you get a 409 — which is your signal that you lost the task and should stop working on it. - Release to give it back.
POST /v1/tasks/:id/releaserequeues the task toopenimmediately, clearing the lease so another worker can pick it up. Use it when your worker shuts down cleanly or decides a task isn’t for it. - Expiry makes it claimable again. If the lease runs out — no heartbeat, no release — the next
claimcall simply takes the task over. A crashed worker delays a task by at most one lease window. Nobody gets paged to un-stick the queue.
The lease also protects the holder. While a claim is live, any other agent that tries to complete, update, or delegate the claimed task gets a 409. A confused orchestrator can’t yank a task out from under the worker that’s halfway through it.
The worker loop
The whole pattern is about fifteen lines of shell:
#!/bin/bash
# worker.sh — claim, work, heartbeat, complete
while true; do
TASK=$(curl -s -X POST https://api.delega.dev/v1/tasks/claim \
-H "X-Agent-Key: $DELEGA_AGENT_KEY" \
-H "Content-Type: application/json" \
-d '{"labels": ["@worker"], "lease_seconds": 300}')
TASK_ID=$(echo "$TASK" | jq -r '.task.id // empty')
if [ -z "$TASK_ID" ]; then
sleep 30 # queue empty — back off and retry
continue
fi
# Heartbeat in the background while the real work runs
( while true; do
sleep 120
curl -s -X POST "https://api.delega.dev/v1/tasks/$TASK_ID/heartbeat" \
-H "X-Agent-Key: $DELEGA_AGENT_KEY" > /dev/null
done ) &
HB_PID=$!
if process_task "$TASK"; then
curl -s -X POST "https://api.delega.dev/v1/tasks/$TASK_ID/complete" \
-H "X-Agent-Key: $DELEGA_AGENT_KEY" > /dev/null
else
curl -s -X POST "https://api.delega.dev/v1/tasks/$TASK_ID/release" \
-H "X-Agent-Key: $DELEGA_AGENT_KEY" > /dev/null
fi
kill $HB_PID
done
Run five copies of this with five different agent keys and you have a worker pool. No orchestrator, no sharding, no races. If a worker dies mid-task, its lease expires and a sibling takes the task over automatically.
If your agents speak MCP instead of curl, the same three operations ship as claim_task, heartbeat_task, and release_task tools in MCP server v1.4.0.
Claiming is not assignment
Delega already had assigned_to_agent_id and delegation chains, and claiming deliberately doesn’t touch either.
Assignment is routing: a human or an orchestrator deciding whose queue a task belongs in. It’s durable intent. Delegation is accountability: who handed work to whom, recorded as a parent/child chain.
A claim is neither — it’s a short-lived execution lease that says this worker is processing this task right now. Claiming never modifies assigned_to_agent_id, and a release doesn’t erase it. Assign a task to an agent and that agent’s workers can still claim it (tasks assigned to someone else are skipped); release it and the assignment is still there. The two systems compose instead of fighting.
Task records now carry the claim state explicitly: claimed_by_agent_id, claimed_at, and lease_expires_at, plus a new claimed status. GET /v1/tasks?claimed=true shows you everything currently being worked; ?claimed=false shows what’s actually available.
Watching the queue
Two new webhook events round it out: task.claimed fires when a worker takes a task, and task.released fires on an explicit release. Wire them into a dashboard and you can watch work flow through the pool in real time.
One deliberate exception: lease expiry fires no webhook. Expiry isn’t an action anyone took — it’s the absence of one, and it’s only observable when the next claim happens. If you want to alert on stale workers, watch for tasks where lease_expires_at is in the past.
Production status
Task claiming remains active in Ryan McMillan’s private deployment at api.delega.dev. Public plans and restricted onboarding keys are retired.
The full endpoint reference — request bodies, response shapes, and the 409 rules — is in the API docs.
Inspect the implementation:
Public hosted onboarding is retired. The source and historical documentation remain public.
→ Case study
→ Architecture and threat model
→ Historical docs
→ GitHub