Skip to main content

Tasks API

Task management for LLM agents — create, list, update, complete, delete tasks with priorities.


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.

# 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:

{
  "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.

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).

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.

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.

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.

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.

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

# 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