Approvals and leasing
How a remote agent takes work without two agents taking the same work, and how a human gate stops a destructive action
Not built yet. There is no Approval table, no lease, and no endpoint to claim against. The Prisma
schema still ships the template's stub model. This page describes the intended shape, taken from
spec/01-domain-model.md and spec/03-agent-experience.md.
Two problems share one page here because they share one answer: work that leaves the record has to be able to come back. A lease is how work comes back when a machine dies. An approval is how work comes back when a human has to look at it first.
Leasing
A lease is a time-bounded right to work one Task. It is held by one Run, and it expires on its own.
- A runner calls
tasks_claimwith its agent id and a capability filter. The server picks the highest-priority ready task, setsstatus=in_progress, creates a Run, and returns a lease with a TTL alongside the full working context. - The runner renews the lease by heartbeating while it works.
- Heartbeats stop past the TTL. The lease expires, the Run is marked
abandoned, the Task returns totodo, andattemptCountincrements. attemptCountpast a threshold moves the Task toblocked, with an Event recording why.
A runner that stops heartbeating is assumed dead, not done. That assumption is the whole point: the alternative is a task that looks claimed forever because the workstation it was claimed on went to sleep.
The claim is atomic
Claiming is designed as a single conditional update:
UPDATE tasks
SET status = 'in_progress', lease_id = $1, lease_expires_at = $2
WHERE id = $3 AND status = 'todo' AND lease_id IS NULL
RETURNING *;Two agents claiming at the same moment cannot both win. The one whose update matches no row gets nothing back and asks again. There is no read-then-write window to lose.
Heartbeat cadence
The write-back contract asks for a heartbeat at least every leaseTtl / 3. Three chances to report
before the lease is judged dead means one slow network round trip does not cost a runner its work.
A heartbeat carries a progress percentage and a one-line human-readable status, so the lease is also the mechanism by which a human watching the board can see that something is still happening.
What leasing buys
- Nothing double-runs. One Task, one live lease, one running Run.
- Nothing is lost. An abandoned attempt requeues rather than disappearing.
- Failure is visible.
attemptCountrising is the signal that a task is not merely slow.
Approvals
An Approval is a human-in-the-loop gate. An agent requests one before a destructive or
outward-facing action, and the Task sits in blocked until it resolves.
| Field | Notes |
|---|---|
question | What the agent is asking, in plain language. |
options[] | The choices offered. A free-text answer is not a decision the state machine can read. |
decision | Which option was chosen. |
decidedByUserId | Who chose it. Recorded, like every other write in the system. |
expiresAt | An unanswered approval does not hold a lease open forever. |
When an approval is required
The workspace carries policy flags, requireApprovalForDestructiveTools among them. Beyond policy,
the rule from the agent experience spec is that destructive and outward-facing actions require one,
and that the approval is recorded rather than merely respected.
Outward-facing matters as much as destructive. Publishing, sending, deploying and posting are all things that cannot be taken back once done, even though none of them deletes anything.
Why options, not free text
An agent has to be able to act on the answer without interpreting it. options[] plus a decision
naming one of them is machine-readable. A paragraph of guidance is a
Comment, which is a different mechanism for a
different job.
Blocking questions
A Comment posted with blocking: true and left unanswered also moves the Task to blocked. The
difference from an Approval is what is being asked for:
- Approval — permission to do something the agent already knows how to do.
- Blocking comment — information the agent does not have and cannot obtain.
Both stop the work honestly rather than letting a runner guess and report success. Any feature that makes it easier to report success than to report a blocker is treated as a design defect.
Credentials are never the answer
An agent that needs a secret does not get one from an approval. No table in this model stores a
secret value; only secretRef pointers to the real store. The agent receives the reference and
resolves it on its own machine, which is the only place the value ever exists.