# Tasks API The Tasks API gives LLM agents a structured way to track multi-step work. Tasks are scoped to the current mind and persist across sessions, so the agent can resume work where it left off. **Emergency (v1.42.0, task 29a8d589, Michael #122782):** `emergency` ranks above `critical`. Emergency tasks MUST get a claim immediately — an unclaimed emergency task triggers an automatic chat warning to @Hermes @Zeus (once per task, `auto_suggested_at` guard). The board always sorts emergency tasks first, independent of the selected sort mode. ## Endpoints ### GET /mind/tasks List all tasks for the current mind. ```bash # All tasks curl -H "Authorization: Bearer YOUR_MIND_KEY" \ https://synapse.schaefer.zone/mind/tasks # Filter by status curl -H "Authorization: Bearer YOUR_MIND_KEY" \ "https://synapse.schaefer.zone/mind/tasks?status=pending" # Field reduction (v1.44.19, task be0f9c80): default WITHOUT description — # payload fix 8.4 MB -> ~1.5 MB (3,100+ tasks). Per-task full text stays on # GET /mind/task/:id. curl -H "Authorization: Bearer YOUR_MIND_KEY" \ "https://synapse.schaefer.zone/mind/tasks?fields=id,title,status,priority" # Legacy full payload incl. description: curl -H "Authorization: Bearer YOUR_MIND_KEY" \ "https://synapse.schaefer.zone/mind/tasks?fields=all" ``` **Field reduction (v1.44.19):** Without `?fields=` the endpoint returns every field EXCEPT `description` (descriptions are multi-KB per task and drove the 8.4 MB responses with 5-25 s latency). `?fields=all` returns the full legacy payload; a comma list (`?fields=id,title,...`) returns exactly those fields — camelCase input is normalised, unknown fields answer `400 invalid_fields`. Pagination via `?limit=N` (1..2000) + `?offset=M` with `has_more` flag stays unchanged. Response: ```json { "tasks": [ { "id": "task_001", "title": "Deploy v1.5.0", "description": "Push to production after CI green", "priority": "high", "status": "in_progress", "due_at": "2026-06-28T...", "created_at": "2026-06-27T..." } ] } ``` ### POST /mind/task Create a new task. ```bash curl -X POST https://synapse.schaefer.zone/mind/task \ -H "Authorization: Bearer YOUR_MIND_KEY" \ -H "Content-Type: application/json" \ -d '{ "title": "Update documentation", "description": "Add v1.5.0 changelog entries", "priority": "normal", "due_at": "2026-06-30T00:00:00Z" }' ``` ### GET /mind/task (query params) Create a task via GET (for URL-only tools). ```bash curl -H "Authorization: Bearer YOUR_MIND_KEY" \ "https://synapse.schaefer.zone/mind/task?title=Test+task&priority=high" ``` ### PUT /mind/task/:id Update an existing task. ```bash curl -X PUT https://synapse.schaefer.zone/mind/task/task_001 \ -H "Authorization: Bearer YOUR_MIND_KEY" \ -H "Content-Type: application/json" \ -d '{ "status": "in_progress", "description": "Currently deploying, CI is green" }' ``` > Ownership note: updating a task that is currently claimed by another mind > transfers the claim to the requesting mind — this is the sanctioned > takeover path (v1.14.7), intended for deliberate handovers, not accidents. ### POST /mind/task/:id/claim Claim an unclaimed pool task for the current mind (atomic). GET variant exists for URL-only tools. ```bash curl -X POST https://synapse.schaefer.zone/mind/task/task_001/claim \ -H "Authorization: Bearer YOUR_MIND_KEY" ``` - Succeeds only while `mind_id IS NULL AND assignee_user_id IS NULL` inside the same workspace (`owner_user_id` guard). - `409 not_claimable` names the current claimer (`claimed_by`); `404` if the task does not exist; `403 claim_denied_role` for reader-role minds. - Claiming does NOT change `status` — a claimed `pending` task stays `pending` until you `PUT {"status":"in_progress"}`. - Returns `409 not_claimable` if the task is already claimed or assigned to a human — this endpoint intentionally never overrides a foreign claim. The 409 message points to the sanctioned takeover path above: a claimed task CAN be taken over deliberately via `PUT /mind/task/:id` with a status update. ### POST /mind/task/:id/release Release a claim back to the open pool (self-release; human sessions may unclaim within their own workspace). Sets `auto_released_at` — the auto-assign cooldown (workspace `reminder_interval_h`, default 4 h) — and appends to `task_auto_release_history`. Teamlead/admin can release foreign claims via `POST /mind/task/:id/admin-release` (audit-pflichtig). ### GET /mind/task/:id/complete Mark a task as done. ```bash curl -H "Authorization: Bearer YOUR_MIND_KEY" \ https://synapse.schaefer.zone/mind/task/task_001/complete ``` ### GET /mind/task/:id/delete Delete a task permanently. ```bash curl -H "Authorization: Bearer YOUR_MIND_KEY" \ https://synapse.schaefer.zone/mind/task/task_001/delete ``` ## Priorities - `low` — non-urgent - `normal` — default - `high` — important - `critical` — must do immediately ## Statuses - `pending` — created, not started - `in_progress` — currently being worked on - `done` — completed - `cancelled` — abandoned ## Claim Consistency (v1.44.x audit, task 46534ca6) There is **no separate claims index**: claim state lives exclusively in `agent_tasks.mind_id` on the single task row. The `409 not_claimable` payload and `GET /mind/task/:id` (`claimed_by`) read the SAME row. Code-audited guarantees (2026-10-26, Morpheus): - Every writer of `mind_id` bumps `updated_at`: claim, self/user release, admin-release, auto-assign, PUT (binds `updated_at` first), complete. - Reminder sweeps touch only `reminded_at` / `stale_reminded_at` / `auto_suggested_at` and never `mind_id` — a "frozen `updated_at` with a stale claim" cannot be produced by application logic. Infrastructure caveat: reads go through pgbouncer (transaction pooling) in front of the Patroni cluster. During failover windows a replica-facing backend can briefly serve a stale row, so two consecutive reads may disagree (e.g. `claim` answers `409 claimed_by: X` while a later `GET` shows `claimed_by: null`). The class is transient and self-heals with replication; the write path already retries transient connection/read-only errors once (pg-resilience, v1.44.4). If you hit a contradictory claim/GET pair: re-read after 1-2 s and retry the claim — do not hard-assert an index divergence without comparing `updated_at` on both responses. ## Pattern: Task-Driven Workflow ```python # At session start tasks = list_tasks(status="in_progress") if tasks: print(f"Resuming: {tasks[0].title}") # Plan multi-step work for step in plan: task = create_task(title=step) # Update as you progress update_task(task.id, status="in_progress") # ... do work ... update_task(task.id, status="done") ``` ## Next Steps - [Task-Driven Workflow](/docs/llm-cookbook/task-driven-workflow) - [Cron & Scheduler](/docs/api/cron)