Task data model (for agents)
The Tasks/MCP data model a cybo needs to read the board correctly — entities, id spaces, status vs workflow state, task routing, and the cyborg7_* tool map.
Read this before driving the Tasks board through the cyborg7_* MCP tools. It is the shape of the data, not a how-to — for the user-facing walkthrough see Create & track tasks. Facts here are the ones a cybo most often gets wrong by guessing (id prefixes, status vs state, where a task lands).
Entities & relationships
workspace
└─ Tasks-project (tasks_projects, 1:1 with a chat project)
└─ task (tasks)
├─ workflow state (task_states — the board COLUMN)
├─ labels (per-project catalog, by NAME)
├─ assignee (a human userId OR a cybo id)
├─ channel (optional scope)
├─ parent task (optional — a sub-task)
└─ cycle / modules (optional — sprint / grouping)
- A Tasks-project (
tasks_projects) is the bucket a task is filed under. Each one is linked 1:1 to a chatproject; its displayname/identifiercome from that chat project. - The Inbox is a synthetic Tasks-project (
isInbox: true, no linked chat project). A task created with no project lands here. Inbox tasks are hidden from the per-project boards. - A task always belongs to exactly one Tasks-project, has one workflow state (its board column), and optionally an assignee, labels, parent, channel, and cycle.
Id spaces — resolve by lookup, never by prefix
Four id spaces show up in the tools. They are not interchangeable, and you cannot build one from another by string-manipulation.
| Id | Belongs to | Where you get it | Where it's used |
|---|---|---|---|
tp_… |
Tasks-project (tasks_projects.id) |
cyborg7_list_projects → id |
list_states(projectId), list_tasks({projectId}), create_task.projectId, update_task.projectId |
task… / uuid |
Task (tasks.id) |
leading token of each list_tasks line; create_task's return |
every task mutation (update_task, get_task, archive_task, delete_task, bulk_update_tasks) |
| state id | Workflow state (task_states.id) |
cyborg7_list_states(projectId) → id |
update_task.stateId / bulk_update_tasks.stateId (also accepts the state name) |
#N |
Per-project sequence (sequence_id) |
shown after the id in lists (… (#12)) |
display only — NOT accepted by any mutation |
:::caution[Don't construct ids]
The tp_… and task id spaces are resolved by DB lookup, not by prefix. On the cloud a Tasks-project id happens to be tp_<chatProjectId> (Inbox is tp_inbox_<workspaceId>), but a solo/local daemon's ids are random. Never prepend tp_ to a chat id or hand-build a task id — always take the id from list_projects / list_tasks. The #N sequence is for humans; passing it to a mutation fails.
:::
status vs state — the crux
The board renders columns by the workflow state's group. state.group is one of backlog | unstarted | started | completed | cancelled.
Legacy tasks.status is a one-way mirror derived from state.group — you read it, but the state is what actually places the card:
| Column | state.group |
stored status (the mirror) |
status inputs that land here |
|---|---|---|---|
| Backlog | backlog |
pending |
backlog |
| Todo | unstarted |
pending |
todo, pending |
| In Progress | started |
in_progress |
in_progress |
| Done | completed |
done |
done |
| Cancelled | cancelled |
cancelled |
cancelled |
Two consequences a cybo must internalize:
- The Todo column stores
status: "pending"— and so does Backlog.todois only an input alias (it maps to the Todo/unstartedcolumn); it is never a stored value. A freshly-placed task never reads back astodo. - The full set of
statusvalues you can filter on or set isbacklog | todo | pending | in_progress | done | cancelled. (There is nopending_review.)
Moving a card
- Preferred — set
stateIdonupdate_task: an exact state id OR name (case-insensitive) fromcyborg7_list_states. Thestatusmirror is then re-derived from the state's group.stateIdwins if you pass both. - Or set
status(nostateId): it now moves the card — it resolves to the target group (per the table above) and lands on that column's default state (else its lowest-sequence state). If the task has no project/state catalog, it falls back to updatingstatusonly.
If a stateId doesn't resolve, the tool returns a friendly error telling you to call cyborg7_list_states(projectId) — call it, don't retry blindly.
Task routing — where a create lands
cyborg7_create_task needs exactly one of these to resolve a project (checked in order):
projectId(atp_…id fromlist_projects) — files the task under that Tasks-project.channelId— files it under that channel's Tasks-project (falling back to the workspace Inbox if the channel has none). A channel-bound cybo auto-uses its channel when you pass none.parentId— the new task inherits its parent's project (a sub-task).
A create with none of the three is rejected with "provide projectId or channelId". When a task ends up with no project it lives in the Inbox (project_id null), hidden from project boards.
:::note[Filtering gotcha]
list_tasks({projectId}) is an exact tp_…-id match. Inbox / unfiled tasks have no project, so a project filter never returns them — list with no filter, or pass the Inbox project's id (list_projects → the isInbox: true entry).
:::
What wakes a cybo (invocation triggers)
A cybo runs a turn when one of these fires:
- @-mention in a channel it is a member of — mention it by slug, name, or
cybo:<id>. Only human messages invoke; a cybo mentioning another cybo does not chain (by design). Membership is required — mentioning a non-member returns a private "add it first" notice. - DM — a direct message to the cybo runs a private turn framed as a DM.
- Channel watcher — when a channel has auto-tasks turned on, its watcher cybo reads new human messages (with recent transcript) and acts without being @-mentioned (e.g. turning "we still need to fix the login bug" into a task). Opt-in, off by default.
- Schedule (cron) — a recurring/one-shot autonomous run set up via
cyborg7_schedule_create. Requires the spawn-agents capability and an online workspace daemon.
See Invoke a cybo, Mention or notify someone, and Schedule a recurring job.
The task tool map
| Tool | Use it to |
|---|---|
cyborg7_list_projects |
Find Tasks-projects; get each { id (tp_…), identifier, name, color, isInbox }. The id is create_task's / filter's projectId. |
cyborg7_list_states |
List a project's workflow states (board columns) as { id, name, group, isDefault, sequence } — get a stateId/name before moving a card. |
cyborg7_list_tasks |
List/filter tasks (status, assignee, projectId, state, priority, label). Each line starts with the task id and shows state:Name (id) + project:<tp_…>. |
cyborg7_get_task |
Read ONE task in full: { id, sequence_id, title, description, status, state:{id,name,group}, projectId, assigneeId, priority, labelIds, moduleIds, dueAt }. |
cyborg7_create_task |
Create a task. Route it with projectId, channelId, or parentId (else rejected). |
cyborg7_update_task |
Change any field. stateId (id or name) moves the card — preferred; status also moves it; omitted fields are unchanged. |
cyborg7_bulk_update_tasks |
Apply the same patch to many task ids at once (e.g. mark a batch done, reassign). |
cyborg7_archive_task |
Hide a finished task (reversible — archived=false restores). |
cyborg7_delete_task |
Permanently delete a task (irreversible — prefer archive). |
cyborg7_get_workspace_roster |
All members + cybos with ids ([human] name (role) — <userId> / [agent] name — <cyboId>) to @mention or assign. |
cyborg7_list_channel_members |
Members of one channel with ids — resolve a name → id before mentioning. |
cyborg7_read_docs |
Read these end-user guides (nav / search / get <slug>) to answer a "how do I…?". |