Collaboration
Shared tasks
Turn a plan (a markdown spec, GitHub Issues, a Notion page or database, or a list your agent posts) into a work queue your agents and your teammates' agents share, so each one takes a different task.
On this page
Shared tasks turn a plan into a work queue for agents. You point your agent at a spec ("build SPEC.md"); Comity turns its checkboxes or sections into tasks (T-n), and every agent working from that list claims a different one, reports its progress and moves on when its work lands. Nobody types tasks into a website.
It covers two situations:
- Solo swarm: you run four agents on one spec. Each takes the next open task by itself; two agents never build the same feature, and you see who is on what.
- Team: your agent asks a teammate's agent to take some tasks. The teammate always approves first, in their own agent's approval prompt or in the dashboard. Nothing moves to a teammate without their yes.
An agent doing unrelated work ("fix the login bug") is never put on a list. Its usual Comity coordination is unchanged.
Lists and tasks
A list belongs to a repository (a Notion or agent list can also belong to the whole org) and has one source. One source is one list; lists are never mixed.
| Source | What it is | How it gets there |
|---|---|---|
file |
A markdown file in the repo, by path (SPEC.md, docs/plan.md, TASKS.md). |
Your prompt names the file (see below). |
agent |
A list an agent posted, for example from a Notion page it read with its own tools when your org hasn't connected Notion. | The agent calls task_add. |
github |
The repo's GitHub Issues, optionally filtered by labels or a milestone. | Your agent or the API creates it; the GitHub App keeps it in sync. |
notion |
A Notion page's to-dos (or headings), or a Notion database's rows. | Your agent, the dashboard or the API creates it from the page's link; the Notion integration keeps it in sync. |
A task has a number T-n (per organization), a title, an optional description, a status and a place in the queue:
| Status | Meaning |
|---|---|
open |
Waiting for an agent. An open task whose dependencies aren't done shows as Blocked and can't be claimed yet. |
claimed |
An agent session holds it: who, which agent, and its progress. |
done |
Its work landed on the default branch (or an agent or person marked it done). The result keeps the branch and commit. |
dropped |
Removed from the plan. It can be reopened. |
Working from a markdown file
Tell your agent to work from the file, the way you would anyway:
Before the prompt reaches the agent, the Comity hook reads the file on your machine, turns it into tasks and sends them with the prompt. The server adds new items, updates changed ones and makes the list your session's working list. The agent then gets a Shared tasks section in its context and claims the next open task if it holds none.
What counts:
- The prompt tells your agent to work from a markdown file inside the repo (
SPEC.md,@docs/plan.md, a quoted or absolute path;.md,.markdownor.mdx, up to 1 MB) that contains tasks: a work verb on the tasks, the list or the file (build, implement, work on/through/from, execute, follow, tackle, take, pick up, start, continue, finish, complete, …) or "next task". A file name alone (TASKS.md,PLAN.md) is not enough. - Editing, discussing or sending the document is not working from it: "fix the typo in PLAN.md", "review SPEC.md", "what do you think of the tasks in SPEC.md?" or "send SPEC.md to Mike" don't touch shared tasks.
- Work for someone else is not your agent's: "ask Mike to take 4 and 5 from SPEC.md", "can his agent also take the excerpt task from SPEC.md?", "have my teammate take the next task" or "take the auth task for Mike" claim nothing for you. In a sentence that hands work to someone else ("ask/tell/have/let him, them or a name …", "their agent", "for Mike"), only what comes before that counts: "build the tasks in SPEC.md and tell Mike when you're done" still works. To give a teammate tasks, use a work request.
- Without a file name, an instruction like "take the next task", "work on the shared tasks" or "pick something from the task list" (or board, queue, team board, work queue) re-reads the file your session last worked from, else the repo root's
TASKS.mdorTODO.md, else the repo's most recently used list. Only mentioning them ("what's left in the shared tasks?") doesn't. - "Stop working from the task list" (or "done with the shared tasks") takes your session off the list. Its claimed task stays claimed until it is done or released.
- The file is read only on such prompts, not on every prompt. Edit it and say "next task" to bring the list up to date.
How the file becomes tasks
- Checkboxes win. In a file with task-list checkboxes (
- [ ] Search,1. [ ] Search), the least indented checkboxes are the tasks. Deeper lines, nested checkboxes included, become the task's description.[x]is done,[/]is open (in progress),[-]or[~]is left out (an open task is dropped). - Otherwise sections. A file without checkboxes is read as a spec: the sections at its shallowest heading level under a single
#title are the tasks, with the section text as the description. Descriptive sections (Overview, Background, Goals, Non-goals, Notes, Open questions, Out of scope, Requirements, …) are skipped. A heading marked✅,(done),[x],DONE:or~~struck through~~is done. - The list's title is the file's first
#heading, else its file name. - Dependencies. End an item with
(depends on: Search, Filters)or put aDepends on: …line in its description, by title (or its start) or by item number (#3).after:,blocked by:andrequires:work too. A task can't be claimed until the tasks it depends on are done. - Fenced code blocks are ignored. Files over 1 MB and more than 500 items per import aren't read.
Each task keeps a stable key (its normalized title, plus an ordinal when two items share a title), so the same file imported from four agents' checkouts gives one list, and reordering or editing other items doesn't create duplicates. Importing never deletes: an open task whose item disappeared from the file becomes dropped, but only when that copy of the file is newer than the one last imported, so an agent on an older checkout can't drop newer items. Claimed and done tasks are never dropped by an import. Comity doesn't write back to the file.
Lists from GitHub Issues, Notion and agents
GitHub Issues. Ask your agent ("make the open issues labelled v2 our shared task list") and it calls task_add with source: "github" and a filter (labels and/or a milestone). Each open issue that matches becomes a task titled #12 … that links to the issue. The GitHub App's issue webhooks keep it current: new and edited issues are added or updated, an issue closed as completed makes its task done, one closed as not planned drops it, and a reopened issue reopens it. The app needs the Issues (read) permission; with read and write, Comity also comments on and closes the issue when its task is done in Comity. See GitHub Issues as shared task lists.
Posted by an agent. An agent can post any list it has: "send Mike the task list we're working on", or the tasks from a Notion page when your org hasn't connected Notion (the agent reads Notion with its own tools and posts the items). It calls task_add with a title and the items. Items taken from a markdown file in the repo that isn't a list yet ("ask Mike to take the README items from TASKS.md") are posted with the file's path instead: they become that file's list, and when someone later works from the file, its import updates those tasks instead of adding copies. When a posted item looks like a live task of another list in the repo (the same title, or it names that task's T-n), the agent is told which ones, so it can ask you to drop the duplicate before two agents build the same thing.
Notion pages and databases
Once an owner or admin has connected Notion, a Notion page or database can be a list that Comity keeps in sync. Comity reads only the pages and databases picked in Notion's page picker when it was connected.
- Tell your agent: "send Mike the task list from Notion" or "make this Notion page our task list" with the link. It calls
task_addwithsource: "notion"and the page or databaseurl(optionallyrepoandtitle), then awork_requestif the tasks are for a teammate. If Comity answers that Notion isn't connected for the org, the agent reads the page with its own Notion tools and posts the items withtask_add(title+items) instead: anagentlist that doesn't sync. - In the dashboard: Shared tasks → New list from Notion (members, once the org has a connection). Search by title or paste a link, optionally pick a repository (none: the list belongs to the whole org), and Create list. A page or database that is already a list opens that list.
- A page: its to-do blocks are the tasks, including to-dos inside toggles, columns, callouts and lists. Blocks nested under a to-do, nested to-dos included, are its description. A checked to-do is done. A page without to-dos is read as a spec: its headings (H1 to H3, the shallowest level used) are the tasks, with the blocks under each as the description; descriptive sections (Overview, Goals, Notes, Open questions, …) are skipped, and a lone H1 at the top is the title. Headings can't be checked off.
- A database: each row is a task, titled by its title property, newest 500 rows. A row is done when its Done, Complete, Completed, Finished or Closed checkbox (or its only checkbox) is ticked, when its Status is in Notion's "Complete" group, or when a select named Status reads Done, Complete, Completed, Closed, Shipped or Finished. A Description, Details, Notes or Summary text property becomes the description. Dependencies aren't imported.
- Sync: about every 5 minutes, and Sync now in the dashboard. New items are added, edited text is updated, checked items become done, an item unchecked after being checked reopens, and an open task whose item was deleted in Notion is dropped (claimed and done tasks are kept). The list shows when it last synced, or the error.
- Write-back: when a task from Notion is done in Comity (its work landed, or someone marked it done), Comity checks the to-do, ticks the checkbox or sets the Status to the first "Complete" option, once per task and within 7 days, if the connection has Notion's Update content capability. Without it, Notion is left alone and the connection shows as read only.
Each task links to its block or row in Notion.
Claiming
- One task at a time. An agent working from a list claims the next open task in queue order, and claims another only after finishing or releasing the current one. It can also claim a task by number (
task_claim {task: "T-7"}). - Atomic. When two agents go for the same task, one gets it and the other is told "already taken by …" with the next open task to take instead.
- The claim is the intent. Claiming binds the task to the session's intent (created with the task's title if the session has none), so the usual conflict detection applies to the work. The agent is told which intent that is: if it declares an intent while holding the task (say on a new feature branch), Comity updates that same intent instead of making a second one. When a task is released, or done without code changes, an intent that recorded nothing closes with it (as abandoned, never as merged: nothing landed). If the session's intent already names the next task (say the agent declared "README and CHANGELOG (T-7, T-8)"), claiming that task keeps the same intent. A task claimed again after its agent crashed continues the crashed session's intent, with the edits it recorded, instead of starting another one; once a task lands, any leftover intent of an earlier claim of it closes as superseded.
- Queued for someone. A task assigned to a person (they accepted a request for it, or assigned it to themselves) is claimed only by that person's agents.
- Waiting on a task. An agent that needs another task done first can call
wait_forwithtask: "T-3", orrequest: "WR-2"to wait for a teammate's answer.
Progress, done and stale claims
- Progress comes from the agent's own todo list: Claude Code's
TodoWrite(and itsTaskCreate/TaskUpdatetools), Codex'supdate_plan, Gemini CLI'swrite_todos, Factory Droid'sTodoWriteand omp's todo tool. Completed items count as done; cancelled or deleted ones drop out; subagents' todo lists never count. "3/7 steps" shows on the claimed task in briefings and the dashboard. Those steps are never separate tasks. - Done means landed. When the task's intent lands on the default branch, the task becomes
donewith the branch and commit. Agents calltask_doneafter committing: if the work changed code and no commit is named, the task is done but not landed yet (awaiting_landing; "done, not landed" incomity tasksand the dashboard). Tasks that depend on it stay blocked, andwait_for {task}keeps waiting, until it lands, which fills in the commit. Once the work is pushed,task_donewithcommit(andbranch) counts it at once. Work that changed no code (a question answered, a post written) is done right away. An agent that did two tasks together under the first task's intent marks the second one done with its work there: the second task waits for that intent to land too, and its own empty intent closes. If the intent of a task done but not landed is abandoned, the task opens again. - Committed work keeps its task. When the agent session holding a task ends and its intent has commits of its own (or is in review), the claim stays until that work lands, and landing marks the task done. Nobody else's agent is handed the task meanwhile; briefings show it as "session ended, committed work waiting to land". If that work will never land, release the task (or abandon the intent) from the dashboard.
- Stale claims are released when nothing works on them and nothing was committed: the session holding the task ended (after the repo's ended-intent grace period, 2 minutes by default), or the agent crashed and the session sent no heartbeat for the repo's idle time (
session_idle_minutes, 30 minutes by default) plus that grace. So a crashed agent's task opens again after about 32 minutes by default. A long tool call with no Comity hook in between (a 40-minute build) counts as quiet too. Uncommitted edits stay in that worktree. The claimer's agents are told, and a task someone accepted from a work request stays queued for them. Another live session carrying the same intent (after a handoff or adopt) takes the claim over instead. An abandoned intent releases its tasks at once.
Asking a teammate: work requests
Your agent can hand tasks to a teammate ("ask Mike to take T-4 and T-5, he knows the search code"). It calls work_request with the teammate, by name as you said it, and the tasks; Mike doesn't need to be online and the agent doesn't need his email. Comity never assigns a teammate's tasks on its own:
- Mike's agent gets the request in its Comity context: who asks, which tasks, the message. It comes first, with a line to tell Mike, at the start of every session and every prompt until he answers (once per turn while the agent is busy).
- Before accepting, it must ask Mike. With hooks, calling
task_acceptshows the agent's own approval prompt ("Accept WR-2 from Tyler: take T-4 Search, T-5 Filters?"); without hooks, the agent asks in chat and the call must say Mike confirmed (confirmed_by_human: true). An agent that can't ask at all (codex exec) can't accept: Mike answers in the dashboard. See Approval prompts. - Mike answers yes or no. On yes, his session takes the first task if it holds none and the rest are queued for him. Accepting doesn't make Tyler's list Mike's to-do list: when Mike's agent finishes a task, it goes on only with the next task queued for him, never the other tasks of Tyler's list. A queued task stays his even if his session ends before he finishes it. On no, he can give a reason.
- Tyler's agent gets the answer like a message reply, and
wait_for {request: "WR-2"}returns as soon as Mike answers.
The request also shows in Mike's Needs you on the dashboard Overview, with Accept and Decline: answering there is his own answer too. Tasks that were done, dropped or taken by someone else in the meantime are skipped.
Once Mike accepts, other open requests for the same tasks stop asking for them. If Tyler had also asked Ana to take T-4, that request is withdrawn; if he asked her for T-4 and T-6, it now asks for T-6 only. Tyler's agent is told which tasks went to Mike, and so is Ana's if it had already seen the request. The same happens when you take tasks with a request to yourself.
A request expires when Mike has no live agent session when it is sent, or nobody answers within 30 minutes. Tyler's agent is told right away that nothing failed, so it can tell Tyler, do the work itself or ask someone else; it is told not to message Mike to claim the tasks instead, which would skip his approval. An expired request stays answerable for 7 days: in Needs you, in work_requests, and at the start of Mike's next agent session. The sender can cancel a pending request.
Requests between your own sessions need no approval: they are assigned at once (solo swarm).
Approval prompts
Accepting a request always needs the human. Where the agent has a native approval prompt, Comity puts it in front of task_accept (accepting and declining), naming the request, the sender, the tasks and their message; your Yes is what lets the call through. Elsewhere the agent asks you in chat, and the server refuses an accept without confirmed_by_human: true. An agent session that can't show you any prompt can't accept at all, even with confirmed_by_human: it tells you to answer in the dashboard (Needs you) or from an interactive session. It may still decline when you told it to.
| Agent | How you approve |
|---|---|
| Claude Code, Factory Droid | The agent's permission prompt (the PreToolUse hook answers ask for mcp__comity__task_accept). In claude -p there is nobody to ask, so the call is refused. |
| Codex | Codex's approval prompt in interactive sessions: comity init sets approval_mode = "prompt" for the task_accept tool in .codex/config.toml. With approval policy never (codex exec, --dangerously-bypass-approvals-and-sandbox, approval_policy = "never") Codex runs the tool without asking, so Comity refuses accepts from those sessions: answer in the dashboard. |
| Cursor | Cursor's prompt from the beforeMCPExecution hook. |
| omp | omp's confirm dialog (Comity bridge v4). Without a UI (print mode) the agent asks in chat. |
| Gemini CLI, GitHub Copilot, Windsurf, Kiro, Cline, Roo Code, OpenCode, Amp, goose, Junie, Augment, Zed, Continue, other MCP clients | The agent asks you in chat and passes confirmed_by_human: true only after you say yes. Gemini CLI also shows its own MCP confirmation unless you always-allowed the Comity server. |
After updating the CLI, run comity init again so the hooks include the new matchers; comity doctor says when they are out of date (and checks the Codex approval setting).
In the agent's context
A session working from a list gets a compact Shared tasks section, refreshed as things change:
Work requests for you come first, at the start of every session and every prompt until you answer them, each led by the line the agent should tell you:
Notices such as "already taken" tell an agent when the task it went for was claimed by someone else, with the next one to take. Sessions that don't work from a list see only work requests addressed to their person.
In the dashboard
The Shared tasks tab shows each repository's lists and, for the selected list, its tasks in queue order: status, who claimed it with which agent, progress, the bound intent and the result. Below it are the work requests to you and the ones you sent. It updates live.
From the tab you can:
- Reorder open tasks with the arrows: agents take them from the top.
- Reassign a task (open a task, Reassign…): to yourself, it is queued for your agents; to a teammate, Comity sends them a work request, and nothing moves until they accept.
- Release a claim, so the task is open for the next agent.
- Drop a task, or reopen a dropped one.
- Accept or decline requests to you.
- New list from Notion, and Sync now on a Notion list, once your org has connected Notion.
Viewers and billing contacts can look but not change anything. Links like /dashboard/tasks?task=T-4 open a task; ?request=WR-2 highlights a request.
From a terminal, comity tasks lists the repo's lists and tasks, comity tasks show T-4 shows one with its history (claimed, released, done), and comity tasks requests your work requests. comity doctor lists the repo's lists too.
MCP tools
| Tool | Purpose |
|---|---|
tasks |
Your working list, your claimed task, or any list: {list?, status?, mine?, work_from?, page?}. list is an id, a title or a file path. work_from makes a list your session's working list. |
task_add |
Post a list or add items: {title?, items[], path?, list?, repo?, source?: "agent" | "github" | "notion", filter?, url?}. path: the repo markdown file the items come from; they join that file's list. source: "notion" with url: a Notion page or database that becomes a synced list. |
task_claim |
Claim a task: {task: "T-n" | "next", list?}. |
task_release |
Give your claimed task back: {task?}. |
task_done |
Call after committing the task's work: {task?, branch?, commit?}. Done means landed on the default branch: with code changes and no commit, the task is done but not landed (awaiting_landing; tasks depending on it stay blocked until it lands, which records the commit); pass commit (and branch) once pushed to count it at once. Work that changed no code is done at once, unless its work sits in another intent of the same session (two tasks done together): then it waits for that intent to land. |
work_request |
Ask a teammate to take tasks: {to, tasks[], message?}. to is a teammate's name, first name or email handle ("Mike", "@mike"), an email, session:<id> or intent:INT-n (its owner). A name that fits several teammates is an error listing their emails. |
task_accept |
Answer a request to you: {request_id, accept, note?, confirmed_by_human?}. Accepting needs your human's yes. |
work_requests |
Requests to you or from you: {direction?: "in" | "out" | "all", status?}. |
Write tools take session_id like the other tools. Every result starts with what happened and what to do next in plain words ("Expired (nothing failed): Mike has no live agent session, so WR-4 waits in their Needs-you for 7 days…"); the structured result repeats it as its first field, guidance, since some agents (Claude Code) read only that. See MCP tools.
API, events and Slack
Everything above is also in the REST API: lists, tasks (claim, release, progress, done, reorder, reassign, drop) and work requests (send, respond, cancel). Scopes are intents:read and intents:write. See REST API → Shared tasks and, for Notion lists, REST API → Notion.
task.added, task.claimed, task.released, task.progress, task.done, task.dropped, task.updated, task_list.updated, and work_request.sent, work_request.accepted, work_request.declined, work_request.expired, work_request.cancelled go to webhooks, the live stream and the audit log (Settings → Audit log → Shared tasks). Task payloads carry task {id, ref, title}, list {id, title} and, depending on the event, the person, session, intent, progress and result; request payloads carry request {id, ref}, from, to and tasks. A work_request.cancelled that Comity made (not the sender) carries a reason: taken (another request for its tasks was accepted; with by_request and taken_by) or account_deleted.
With Slack connected, each new work request is posted (work_request.sent is on by default). Task events can be added to the Slack notifications too; none are posted by default.