# Tasks

> Assign work, claim it safely, and preserve the outcome.

## Capture follow-up work

Tasks store intended work. Creating or assigning one does not launch an agent, schedule a wake-up, or authorise an external action.

```json
{
  "title": "Prepare a partnership proposal draft",
  "description": "Use the research already linked to the project.",
  "due_at": "2026-10-10T09:00:00Z",
  "links": [
    {"type": "project", "id": "01900000-0000-7000-8000-000000000001"}
  ],
  "idempotency_key": "proposal-task-001"
}
```

Call `tasks_create`. `title` and `idempotency_key` are required. Optional fields are `description`, `status`, `due_at` (stored and returned in UTC), `assigned_agent_id`, `custom_fields`, and `links`. Task links can point to up to 20 unique active contacts, organisations, or projects in the same workspace.

## Assign and update

`assigned_agent_id` must identify an active contributor in this workspace. Use the ID returned when issuing that agent's credential, or the current agent ID from `workspace_context`. Reader, revoked, or foreign agent IDs are rejected. Assignment expresses intended ownership, not an exclusive claim.

Call `tasks_update` with `id`, `expected_revision`, `idempotency_key`, and `changes`. Null clears assignment, due time, or description. Supplied `links` replace the previous list; an empty list removes all links. Omitted fields persist. Registered custom fields use explicit patches and removals.

## Status and lifecycle

Statuses are `open` (default), `in_progress`, `blocked`, `completed`, and `cancelled`. Creation and ordinary updates accept only `open`, `blocked`, and `cancelled`. Claiming sets `in_progress`; only successful claim-based completion sets `completed`.

An in-progress task cannot be edited or archived through ordinary tools, even after expiry. Its owner must release it first; expired work must be reclaimed and then released. Completed tasks cannot be reopened; create a follow-up task. Other completed-task metadata remains editable with a revision check.

`tasks_archive` and `tasks_restore` retain history and require `id`, `expected_revision`, and `idempotency_key`. Archived tasks are hidden by default and must be restored before editing, claiming, or receiving new activity links.

## Claim work

First retrieve the task and review its history. Call `tasks_claim`:

```json
{
  "id": "01900000-0000-7000-8000-000000000001",
  "expected_revision": 1,
  "lease_seconds": 900,
  "idempotency_key": "proposal-attempt-001"
}
```

`lease_seconds` is optional: default 900 (15 minutes), minimum 60, maximum 3,600. Orbit atomically claims an open task or reclaims an in-progress task whose previous lease has expired. Blocked work must first be moved to open. Assignment is advisory: another contributor may claim unassigned or assigned work if no valid claim exists.

The result contains `task`, `replayed`, and `current_claim_valid`. The task includes `claim_id`, `claim_owner_id`, `claim_expires_at`, and its new `revision`. Two competing callers cannot both acquire a valid claim. A competing request may receive `revision_conflict` or `claim_unavailable`; read the current task before deciding what to do.

Claim ownership is not permission to send messages or take other external actions. Those still require your agent's normal authorisation.

## Renew or release

`tasks_claim_renew` requires `id`, `claim_id`, `expected_revision`, and `idempotency_key`. Optional `lease_seconds` sets a new expiry measured from now with the same bounds. Renew before expiry; an expired claim cannot be revived. Renewal increments the task revision, so keep the latest response.

`tasks_claim_release` requires the same identity/revision/key fields plus `status` (`open`, `blocked`, or `cancelled`) and a nonempty `reason` of at most 2,000 characters. Only the current unexpired owner can release. The attempt retains its reason and is marked released, without recording completion. Release to `open` for a handoff, or `blocked` with an explanation when work cannot continue.

## Complete with evidence

Append an activity of type `task_outcome` linked to this task using `activity_append`. Record what happened and any useful source references. Then call `tasks_complete`:

```json
{
  "id": "01900000-0000-7000-8000-000000000001",
  "claim_id": "01900000-0000-7000-8000-000000000002",
  "expected_revision": 3,
  "outcome_id": "01900000-0000-7000-8000-000000000003",
  "idempotency_key": "proposal-complete-001"
}
```

Use returned IDs and revisions, not these placeholders. Only the owner of the current unexpired claim can complete. The outcome must be a non-redacted `task_outcome` in the same workspace, already linked to this task. Completion stores `outcome_id` and `completed_at`, clears the current claim, and closes the attempt as completed. Simply appending an outcome does not complete the task. No administrator override is exposed.

## Retry and interrupted work

Every lifecycle mutation requires a new operation key for a new intent; an identical retry reuses the original key. Retries return the original task snapshot, which can have an old revision or expiry. `current_claim_valid` is checked again against current stored ownership and expiry; a replay of an expired or released claim cannot report it valid. Re-read the task for current metadata before continuing work. A lease can still expire after any response.

At expiry, ownership becomes invalid immediately without a worker or scheduled cleanup. The task remains `in_progress` to flag interrupted work, and `claimable: true` finds it. Reclaim creates a new claim ID and preserves the old attempt as expired. Never assume an expired attempt did nothing: inspect the external system before retrying a side effect. Orbit prevents simultaneous valid owners, not duplicate actions outside Orbit.

`tasks_attempts` requires `id` and accepts `page`, `per_page` (default 20, maximum 50), and `include_archived`. It returns `attempts`, `page`, `per_page`, `has_more`, and `next_page`, newest first. Each attempt includes its owner, claimed/expiry/end timestamps, release reason or outcome ID, and computed `state` (`active`, `expired`, `released`, `completed`). An expired attempt may have no stored `ended_at` until reclaimed; its computed state is already expired.

## Find outstanding work

`tasks_search` supports:

- Literal title/ID `query`, exact `status`, and `assigned_agent_id`.
- `unassigned: true` for tasks with no intended owner; do not combine with `assigned_agent_id`.
- `claimable: true` for non-archived open tasks or expired in-progress tasks.
- `due_from` and `due_to` as inclusive UTC calendar dates.
- `overdue: true` for open/in-progress/blocked tasks due before now.
- `outstanding: true` for open/in-progress/blocked tasks, regardless of due date.
- A paired `record_type` and `record_id` for directly linked work.
- Registered scalar `custom_fields` equality filters, `include_archived`, and pagination.
- `sort`: `title` (default), `due_at`, `created_at`, or `updated_at`; `direction`: `asc` (default) or `desc`. Undated values sort last; ID ascending breaks ties.

All filters combine with AND. Default page size is 20, maximum 50. `tasks_get` returns one task and its links. Briefs include directly linked outstanding tasks, excluding completed and cancelled tasks and indirectly related work.
