Leases
Observe and control the running work a pool has leased out.
When a pool staffs a stage, it leases a task to a worker; that lease is the unit of running work. A lease answers for a subject — most often a task, but a lease minted on a schedule (see Scheduled rules) can answer for a whole workflow or a whole space, with no task at all. These surfaces let you, the operator, watch and control leases whatever their subject.
Over the API, listing leases, reading one, reading its runtime coordinates, and
cancelling one are operator-only: a lease's own token is refused there with 403
and lease_token_mismatch. Over MCP a worker can list and read leases with
leases_list and leases_get, but only the leases whose subject its scope covers;
cancelling stays operator-only on both surfaces. Reading a lease's runtime
coordinates has no MCP tool — it is available over the API only. Reading a lease's
logs and attachments is open to anyone who can see the lease, the lease itself
included.
Listing and reading leases needs read; cancelling one needs member. See
Authentication. Replace https://cadence.example.com
with your instance's address.
Lease states
A lease is in one of these states. The active ones are pending, dispatching,
dispatched, running, paused, and cancelling. The terminal ones are
completed, failed, cancelled, lost, and killed.
How a queued lease reads on its board card
A lease that has not started running yet — pending, dispatching, or
dispatched — shows a chip on its task's card in the board view (the web interface).
That chip no longer collapses the whole queued family to one word: it names the
real condition the lease is waiting on, so you can read why a lease is queued
without opening it. The chip leads with the pool's name and follows it with the
condition in brackets — for example, Delivery workers (pool full).
The condition is one of:
| Chip | What it means |
|---|---|
(pool full) | The pool is at its size — every slot is running or booting. Frees as its leases finish. |
(space full) | The space has no free vCPU for this lease's whole size. Frees as running work in the space finishes. |
(instance full) | The instance has no free vCPU for this lease's whole size. Frees as running work across the instance finishes. |
(pool paused) | The pool is paused. Nothing dispatches from it until you resume it. |
(space archived) | The space is archived. Nothing dispatches until you unarchive it. |
(runtime refusing) | The runtime is refusing to bring up a container. |
(token pool unreachable) | The pool's credential source cannot be reached, so no container can be started with it. |
(awaiting agent) | The container is up and the agent has not claimed the lease yet. |
(starting) | A slot is held and the container is being brought up. |
(waiting until HH:MM) | The lease deferred and will retry at the time shown. |
The chip's colour and motion tell the two kinds of wait apart at a glance:
- Amber and still means the lease needs a person. A paused pool to resume, an
archived space to unarchive, the runtime refusing containers, or an unreachable
credential source will not clear on their own —
(pool paused),(space archived),(runtime refusing), and(token pool unreachable)read this way. - Neutral and spinning means the lease is working and waiting, and will move on
its own. A ceiling that frees as siblings finish, a container booting, an agent
about to claim, or a deferred wake read this way —
(pool full),(space full),(instance full),(awaiting agent),(starting), and(waiting until HH:MM).
Under reduced-motion settings the spin is suppressed, but the colour and the still
dot still read. A running, paused, or cancelling lease keeps the chip it has
always had.
List leases
GET /api/v1/leases; MCP leases_list. Scope the list to a space — over the API
with ?space=<key>, over MCP with space_key. Without a space, the API refuses
with 422 and missing_scope (space is required).
Filter by state and cap with limit (up to 100 over the API; over MCP 50 by default, with
an advertised maximum of 200; newest first). Over the API you can also page with cursor; over MCP the list
comes back as a single page with no cursor. Each lease carries its id, state,
attempt, pool, and subject_kind. Over MCP each lease additionally carries a
subject reference to what it answers for, which the API does not; the API instead
carries a workflow field, an object holding the workflow's slug. The API list
also includes timing and the lease's worker version, both of which the MCP list omits. task is null on a workflow- or
space-subject lease, so such a lease lists rather than failing. Over the API,
workflow is set on a task-subject lease, where it is the workflow the task
was on when the lease was created, and on a workflow-subject lease, and is null
only on a space-subject lease. It does not follow a task moved to another
workflow afterwards. The list is scoped by each lease's own space, so workflow-
and space-subject leases appear alongside task leases.
curl "https://cadence.example.com/api/v1/leases?space=OPS&state=running" \
-H "Authorization: Bearer smc_your_token_here"Read one lease
GET /api/v1/leases/:id; MCP leases_get. Both return the lease's state,
attempt, timing, pool, and subject_kind. The API response also includes the
lease's worker version and a workflow field, both of which the MCP result omits.
workflow is an object holding the workflow's slug — on a task-subject lease,
the workflow the task was on when the lease was created, which does not change if
the task later moves to another workflow; the workflow itself on a
workflow-subject lease — and is null on a space-subject lease. The MCP result instead carries a subject reference the
API does not. task is present on both surfaces but null
on a workflow- or space-subject lease, so such a lease reads rather than failing.
subject_kind is one of task, workflow, or space. An unknown lease returns
404 and lease_not_found over the API, or unknown lease id over MCP.
GET /api/v1/leases/:id/exec_target returns the runtime coordinates for reaching
a running lease. It carries no payload, environment, or secrets.
Cancel a lease
POST /api/v1/leases/:id/cancel; MCP leases_cancel. A running or paused lease
moves to cancelling while the worker is asked to stop; a lease still queued is
cancelled at once. Cancelling an already-terminal lease is a no-op.
Read a worker's logs
GET /api/v1/leases/:id/logs; MCP leases_fetch_logs. Anyone who can see the
lease can read its logs, whether it is still running or finished. Pass container
to pick a container, tail to cap the number of lines, since for an ISO-8601
lower bound, and cursor to page.
{
"events": [
{ "timestamp": "2026-05-01T10:00:00Z", "container": "agent", "stream": "stdout", "message": "…" }
],
"truncated": false,
"next_cursor": "…"
}When the pool's runtime keeps no logs, the API returns 501 with
unsupported_runtime; over MCP the same case comes back as an invalid-arguments
error.
Read a lease's attachments
GET /api/v1/leases/:id/attachments and .../attachments/:attachment_id; MCP
leases_list_attachments. Listing returns each attachment's metadata — id,
kind, filename, content_type, byte_size, and created_at; the MCP tool
adds a download_path for each, which the API response does not. Fetching one by
id streams its bytes. A worker uploads its own attachments; that upload is not an
operator surface.
An old lease's detail is reclaimed
After a retention window, Cadence reclaims the detail of a finished lease to recover storage (see An old lease's detail is reclaimed for what it clears and what it keeps). This changes what a couple of these surfaces return for such a lease:
- Read a lease's attachments returns an empty list — the sweep purges a reclaimed lease's attachments.
- The lease still lists and reads: its state, attempt, timing, pool, and
subject_kindare kept, soleases_listandleases_getresolve as they always do — a reclaimed lease is not missing. - Its activity timeline is left intact, including the entry recording how the lease ended, so the account of what the lease did survives on the task even after its detail is reclaimed.
What a worker reads from its lease
This section is for the worker running under a lease, reading its own payload —
not for the operator. Over MCP, under its lease token, a worker calls
lease_fetch_payload to get the bundle it works from. What the bundle carries
depends on the lease's subject.
The subject context
-
A task subject carries the task's fields — its key, title, body, properties, dependencies, the summaries of prior attempts, and the task's recent activity.
-
A workflow subject carries no task fields. In their place it carries a
subjectblock and the workflow's own recent activity:"subject": { "kind": "workflow", "slug": "nightly-sweep", "name": "Nightly sweep", "description": "…" } -
A space subject likewise carries a
subjectblock — with the space'skeyandname, and no slug or description — and the space's recent activity:"subject": { "kind": "space", "key": "OPS", "name": "Operations" }
A workflow- or space-subject lease has no task, no dependencies, and no prior task summaries: there is no task to carry them.
The trigger
A lease minted by an inbound rule additionally carries a
trigger block naming the event that started it:
"trigger": {
"topic": "email.received",
"occurred_at": "2026-09-11T13:59:00Z",
"data": { "from": "…", "subject": "…", "body": "…", "attachments": [ … ] }
}trigger.data is the event's whole data bag — for an email, the sender,
recipient, subject, body, and attachment list described under
the email.received event. The
trigger block is absent on a lease minted any other way — a lease from a
stage entry or from a scheduled rule carries no
trigger.
What an agent reads about its model
A lease run by an agent pool reads kind: "agent" and the pinned agent
version's model. When the
version sets an effort, the payload carries it too:
"kind": "agent",
"model": "claude-sonnet-5",
"effort": "medium"When the version sets no effort, the payload has no effort key at all; a
worker that finds none should leave the model at its own default. Cadence only
hands the value over: what the worker does with it is up to the worker's image.
What a classifier reads
A lease run by a classifier pool carries a payload body
of its own, alongside the subject context above. It reads kind: "classifier"
and the pinned classifier version's input
document, copied through verbatim:
"kind": "classifier",
"input": {
"attachments": [ { "name": "screenshot.png" } ],
"questions": [ … ]
}Cadence copies the input document through untouched — it renders nothing,
interpolates nothing, and does not inspect a question. A classifier payload
carries none of the LLM fields, skills, or MCP configuration an agent lease
carries. The worker composes its request to the question-answering provider from
the task body in the subject context, the task attachments named in
input.attachments, and the questions list.
The worker ends the lease by reporting its answers as the details of its
completion — a JSON object, one field per answer — with cadence worker complete --outcome success --details <json> (equivalently the details object on
POST /api/v1/leases/:id/complete). What that reported result then does to the
task — the checks and properties it sets — is decided by the version's output
document, which the worker never sees: its expressions render over the reported
result, so the field names the worker reports are the names those expressions read.
Cadence applies the output when the lease reports success; see How an output
expression is rendered.