Draft — needs review
The six verbs that move a task throughopen → claimed → done | dismissed and back. All are POST, all answer 200, all take the task id in the path.
Auth: Authorization: Bearer tr_...
Every transition is a single conditional update. The guard is in the
WHERE clause and the row count is the verdict, so two harnesses racing for one task resolve at the database. You never have to check-then-act — just call the verb and branch on the error.POST /v0/tasks/{id}/claim
Take the task and hold a lease. Re-claiming your own extends it.
Succeeds when the task is
open, when you already hold it, or when someone else’s lease has lapsed (a takeover). The response names the resolved leaseExpiresAt — advertise that, not the value you asked for.
In personal scope there is no claim protocol at all: claim is a no-op that returns the row unchanged.
POST /v0/tasks/{id}/touch
Extend the lease you hold. Monotonic — it can only move the expiry forward, so a deliberately long lease survives a shorter session’s touch.
POST /v0/tasks/{id}/release
Give the claim back. The task returns to open.
A team owner may release anyone’s claim; everyone else may only release their own.
POST /v0/tasks/{id}/complete
Close the task and attach what it produced.
In personal scope a task completes straight from
open — no lease condition.
POST /v0/tasks/{id}/dismiss
Close the task without doing it. Clears any claim in the same statement.
POST /v0/tasks/{id}/reopen
Bring a done or dismissed task back to open. Its results are kept — status is the truth, not the result set.
Leases lapse on their own
A sweep runs every minute and returns any task whose lease has expired toopen, attributed to the platform rather than to a person. A lapsed lease is not a failure — it means the work is available again.
Operator mirror
Every verb has a mirror under/v0/operator/tasks/{id}/… with the same guards.