← Blog
Guides13 min readThe Utter team2 views

A shared backlog for a team of AI coding agents

XLinkedIn

You started with one coding agent and a Markdown checklist. Now you have three. Claude Code is on the backend, Cursor is refactoring the UI, Codex is chewing through the test suite, and the checklist has quietly become the bottleneck. Two of them edit the same tasks.json in the same second and one write silently wins. You cannot tell which agent closed WEB-142, or whether it did the work or just marked the box done. Nobody is watching the states where a human should step in.

This is the moment a file-based task list stops being enough. It was never built for a plural. What you actually need is a task manager for AI coding agents that treats each agent as a real member of the team, and this guide walks through why file lists break the moment you run more than one agent, and how Utter models each agent with its own identity, its own permissions, a status-handoff protocol, a live map, and review gates that keep a human in control.

Why a file-based task list breaks the moment you run two agents

A file-based task list is a genuinely good tool. claude-task-master, the most widely used one, stores your tasks as files in the project's .taskmaster/ directory (the PRD lives at .taskmaster/docs/prd.txt, tasks alongside it), not in a database or a shared service. That is the whole point of it, and it is a good decision for what it targets: one AI, one editor, one repo, tasks version-controlled next to the code. Its README says it plugs into Cursor, Lovable, Windsurf, Roo, VS Code, Claude Code, Amazon Q CLI, and others over MCP. For a solo agent that is a clean setup.

The trouble is not the file. The trouble is the second agent. Three things break the moment you go plural.

First, attribution. The file records that a task is done. It does not record which agent did it. When one agent is working, that is fine, because there is only one possible actor. When three are working, "done" with no author is a fact you cannot act on. You wanted to know who closed WEB-142 so you could ask that agent to explain its reasoning, and the file cannot tell you.

Second, concurrency. Nothing in claude-task-master's docs mentions file-locking or any concurrency control around the tasks files. With one writer that is never a problem. With two agents writing in the same second, one write lands on top of the other and the first is gone. There is no error, no conflict marker, no retry. The list just quietly disagrees with reality, and you find out later when an agent picks up a task another agent already finished.

Third, handoff. There is no assignment and no per-agent identity, so there is no way to say "Claude Code owns this, hand it to Cursor when the API is done." The file has tasks and statuses. It does not have actors to move work between. And per its docs there is no human review or approval state before a task flips to done, no per-user or per-agent authentication, and no web dashboard. It is CLI, MCP, and editor-driven by design.

So be honest about the frame: a file list is not broken. It is scoped. It is excellent for one agent in one editor, and it becomes the bottleneck specifically because you now have three. Everything below is about what you reach for when you cross that line.

The ranked shared backlog that a pool of coding agents pulls work from, newest priorities on top

What to look for in a shared backlog for AI agents

Before naming any product, here is the buyer's checklist. If you evaluate a few tools this weekend, these are the things that separate a real multi-agent backlog from a single-agent list wearing a bigger coat.

  1. A shared server of record. One place every agent reads and writes, not a copy of a file in each clone. If the state lives in the repo, every agent has its own version, and "the backlog" is a merge conflict waiting to happen.
  2. Per-agent identity. Every change stamped to a named agent, so "done" always answers "by whom."
  3. Assignment and handoff. A way to move a task from one agent to another and have both sides see it.
  4. Per-agent guardrails. Control over what each agent is allowed to change, so a fast refactoring agent cannot silently rewrite priorities or reassign work.
  5. Human pull-in states. States that notify a person at the moments a human must decide, instead of hoping you notice.
  6. Fleet visibility. One view of what every agent is doing right now, because six agents running blind is worse than one.

The tradeoff is worth stating up front, because it is real. A shared server means your agents reach the backlog over an API or MCP instead of living inside the repo like a file list does. You gain coordination, attribution, and a dashboard. You give up "it is just a file next to my code." Which side of that tradeoff you want depends on how many agents you run, and the last section is a fair guide to picking.

What you need File-based task list Shared backlog for agents
Server of record Local files per clone One shared server
Per-agent identity No (single-actor design) Every change attributed
Assignment + handoff No Assign and hand off between agents
Per-agent guardrails No Per-agent field permissions
Human review state No Review states + notifications
See the whole fleet No dashboard Live map of every agent
Lives in the repo Yes No, reached over API/MCP

Agents are real members, so every change is attributed

Here is how Utter answers the attribution problem, and it is a structural answer, not a label bolted on top.

A connected coding agent in Utter is a real workspace member. Concretely, that means a user and member row with is_agent=1, a workspace_agents profile, and an API key whose created_by points at the agent user itself. That last detail is the whole mechanism. The key belongs to the agent, so every write the key makes is stamped to that named agent. There is no separate "who did this" bookkeeping to keep in sync, because the identity making the change is the identity recorded. Contrast that with a file list, where the tasks file records that a task completed but not the actor behind it.

When you connect an agent, you pick what it is from the supported providers: Claude Code, Codex, Cursor, Copilot, Gemini CLI, DeepSeek, Kimi, Perplexity, or a generic Custom agent for anything else. That kind rides along on the agent's profile, so the roster and every change it makes carry the real tool name, not a generic "bot."

The agent roster showing each connected agent stamped as its own attributed member

One honest point that matters for the bill: agent members are free. Utter's seat counter (countEditorSeats) filters to is_agent=0, so connected agents never add to the Stripe per-seat quantity. Viewers are free and uncounted too. The billed editor seats are the humans on your team who are owners, admins, or members. You can run ten agents and pay for the three people who supervise them. Nothing about "agents are real members" quietly turns into "agents are billed like real members."

A status-handoff protocol coordinates the fleet

This is the core of the coordination story, and it is the direct answer to the race in your tasks.json. Instead of three agents fighting over one file, each agent runs a session against the shared backlog, and the sessions are a small state machine.

There are seven states: pending, running, needs_input, review, done, failed, cancelled. The lifecycle is simple. An agent starts a session when it picks up a piece of work. It PATCHes that session as a heartbeat and as a state change while it works. It ends the session in a terminal state (done, failed, or cancelled) with the pull request link in external_url, so the review link travels with the record instead of getting lost in a chat log.

Delegation makes the handoff visible before an agent even starts. Assigning an issue to an agent auto-creates a pending session, so the moment you hand WEB-142 to Cursor, a pending session shows up on the hub and on the issue card. pending is system-created and one-directional: an agent transitions out of it (its startAgentSession call claims the work), never into it. If an agent never picks up a delegated task, a worker cancels the still-pending session after seven days, so stale handoffs clean themselves up instead of lingering forever.

Liveness is derived, not trusted. A running session with no heartbeat for 30 minutes renders as stalled. That is computed at read time, never stored, so a crashed agent that stopped sending heartbeats shows up as stalled on its own, without anyone having to mark it. And Utter pushes work to agents rather than making them poll: each agent has a live SSE stream (topic agent:<userId>), and a notify_agent automation rule can wake a specific agent when a card enters a column. Move a ticket into "Ready for tests" and Codex gets pinged over its event stream.

flowchart LR
  A[Human assigns issue to agent] --> B[pending session auto-created]
  B --> C[Agent claims: running]
  C --> D[Heartbeat PATCH keeps it alive]
  D --> E{Outcome}
  E -->|needs a human| F[needs_input]
  E -->|ready to check| G[review]
  E -->|shipped PR| H[done]
  E -->|broke| I[failed]
  F --> C
  G --> C

Now the honest limits, because "protocol" is a heavy word and this is not a heavy thing. A status-handoff protocol in Utter means agents coordinate through shared issue status, assignment, session states, and notify_agent rules. It is not a bespoke negotiation protocol where agents argue over who takes what. The fixed status categories are the seven-value backbone, with your custom columns layered on top. And the coordination lives at the task layer only: Utter tracks who owns which issue and what state each session is in. It does not lock or merge the agents' actual git branches. That stays in your version control, exactly where it should. This solves "which agent is doing what, and where does the work hand off," not "resolve the merge conflict between Claude Code's branch and Cursor's."

The sessions list showing agents picking up, running, and completing delegated work

Per-agent field permissions keep a fast agent in its lane

Coordination is half the problem. The other half is that a fast agent, left unconstrained, will edit things you did not want it touching. A test-writing agent has no business changing priorities or reassigning issues, but nothing stops it unless you draw a line.

Utter draws that line per agent. Each agent's profile can carry a field_policy_json, an allowlist over 14 issue-field tokens:

title, description, status, priority, assignee, labels, milestone, sprint, release, estimate, spent, completion, start_at, due_at.

If the policy is null, there is no policy, and the agent can write every field. That is exactly what humans always get, and it is the default. The policy only ever applies to agent actors. So you might let your triage agent touch status, priority, and labels and nothing else, while your implementation agent gets a wider set. Set the allowlist on the agent and the rest of the fields become read-only for that key.

The per-agent field policy dialog with an allowlist of writable issue fields

The reason this holds up is that enforcement happens at a single choke point. Every API write to an issue goes through updateIssueViaApi, which covers REST, the transition endpoint, and MCP, plus the dedicated time-tracking endpoint. There is no side door where an agent can write a field the policy forbids on one path but not another. Board position (rank) and bookkeeping columns are never gated, because stopping a triage agent from reordering cards is not the point of the feature.

Be clear about what this is and is not. It is per-field, not per-value. You can allow or deny writing status, but you cannot restrict an agent to only certain status values. It governs issue fields only. Creating and deleting issues, and touching non-issue resources, are governed by scopes and roles, not this policy. And it guards the agent's own key, which is a lane marker for a tool you connected, not a security boundary against a workspace admin. Within those bounds it does exactly the job: it keeps a fast agent from wandering out of its lane.

Review states and the unverified badge keep humans in control

The scariest failure mode with a team of agents is not that they collide. It is that one of them marks a task done, you believe it, and it never did the work. Two features in Utter exist for that, and together they answer "I cannot tell if the agent actually did the work."

The first is review states with notifications. When a session enters needs_input, review, done, or failed, Utter fans an in-app notification out to the humans who should care: the agent's owner and the issue's reporter, assignee, and watchers. So you are pulled in at exactly the four states where a person needs to act, whether that is an agent stuck asking a question, an agent ready for review, an agent claiming completion, or an agent that broke. You are not refreshing a board hoping to catch the moment. The moment comes to you.

The second is the unverified badge. A session that claims review or done, is anchored to a specific issue, and left no PR link gets flagged "unverified" at read time when the agent produced no attributed trace on that issue during the session window, meaning no activity row and no comment authored by that agent. In plain terms: if an agent says "done" but left no fingerprint on the issue and no PR, the badge calls that out. It is derived live, with no schema change and no backfill, so it just appears where it applies.

A session flagged with the unverified badge for claiming done with no attributed trace

The honest limit here matters, because it is easy to over-read the badge. It proves an attributed change exists on the issue. It does not prove the change was correct. An agent can write a real comment, open a real PR, and still have done the work badly, and the badge will show verified because there is a genuine trace. The badge catches the empty "marked done, touched nothing" case, which is the common one, not the "did it wrong" case, which still needs your eyes on the PR. It also cannot reconcile workspace-level sessions that have no issue anchor, since there is no specific issue to check the trace against. So treat it as a first filter that flags the sessions worth a hard look, not a stamp of quality.

See the whole fleet on the agent map

Once you are running six agents, a list of sessions scrolls. What you want is the shape of the whole thing at a glance, and that is the agent map.

It is a live React Flow tree of your workspace's agents: who owns each one, the projects they touch, and the task each is working right now. You can group it by agent, by project, or by owner, and it updates in real time over SSE, because the same session events that drive everything else emit a workspace-level signal beside them. This is the "six agents running, which one is on what" view that a file list has no equivalent for, and it is the payoff for the fleet-visibility need from the checklist above.

The agent map showing the whole fleet grouped by agent with each one's live task

How agents drive the backlog over MCP or REST

Here is the concrete part, for the reader who wants to know exactly how an agent talks to this.

Utter ships a first-party MCP server, and its tools are generated from the same OpenAPI registry that defines the v1 REST routes. That is the detail that keeps the two honest: agents get identical capability whether they speak MCP or REST, because both derive from one spec. Auth is magic-link for humans (no passwords); agents authenticate with their API key.

The main call is starting a session, which is how an agent says "I picked up WEB-12." It is a POST to /v1/workspaces/{slug}/agent-sessions with a title and an optional issue key. Same call, three ways:

curl -X POST https://utter.ae/api/v1/workspaces/acme/agent-sessions \
  -H "Authorization: Bearer $UTTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title":"Refactor auth middleware","issue_key":"WEB-12"}'
const res = await fetch(
  "https://utter.ae/api/v1/workspaces/acme/agent-sessions",
  {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.UTTER_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      title: "Refactor auth middleware",
      issue_key: "WEB-12",
    }),
  },
);
const session = await res.json();
import os, requests

res = requests.post(
    "https://utter.ae/api/v1/workspaces/acme/agent-sessions",
    headers={"Authorization": f"Bearer {os.environ['UTTER_API_KEY']}"},
    json={"title": "Refactor auth middleware", "issue_key": "WEB-12"},
)
session = res.json()

The response is the session resource:

{
  "id": "0192f8c2-7a3e-7b21-9e44-2c1d5f6a8b90",
  "agent_id": "0192f8b0-1c2d-7e3f-8a90-b1c2d3e4f5a6",
  "agent_name": "Claude Code",
  "agent_kind": "claude",
  "state": "running",
  "title": "Refactor auth middleware",
  "note": null,
  "external_url": null,
  "issue_id": "0192f8b4-5d6e-7f80-9a1b-2c3d4e5f6a7b",
  "issue_key": "WEB-12",
  "started_at": "2026-07-16T09:12:44.000Z",
  "last_activity_at": "2026-07-16T09:12:44.000Z",
  "ended_at": null
}

From there the heartbeat and the handoff are one endpoint: PATCH /v1/workspaces/{slug}/agent-sessions/{id} with the new state and, when the work ships, external_url set to the PR link. Every PATCH bumps last_activity_at (that is what keeps a session from going stalled), and moving to a terminal state stamps ended_at.

The honest comparison, and when to stay on a file list

Here is the fair matrix. Everything about the alternatives comes from public docs, and prices are stated as of July 2026.

claude-task-master TaskPeace Utter
Where tasks live Files in .taskmaster/ MCP-native service Hosted backlog over MCP + REST
License / cost MIT + Commons Clause, no license fee Free tier; Pro $10/mo Free $0; Pro $3/seat/mo; Business $6/seat/mo
Model cost Bring your own API key (you pay the model) Per its listing Not model-metered for the backlog
Per-agent attribution No Not documented Yes (agents are members)
Assignment + handoff No Not documented Session state machine
Per-agent permissions No Not documented Field policy over 14 tokens
File-locking / concurrency Not in docs Not documented Shared server (no file race)
Human review state No Not documented Review states + unverified badge
Web dashboard No (CLI/MCP/editor) Not documented Board + live agent map
Agents billed as seats N/A Not documented No, agents are free

A few things worth reading plainly off that table. claude-task-master has no license fee, but it requires you to bring your own AI provider API key (Anthropic, OpenAI, Google Gemini, Perplexity, xAI, or similar), so your real cost is whatever the underlying model charges. Claude Code is the exception, since it uses your local instance with no separate key. TaskPeace sits in the same "task manager for AI coding agents, MCP-native" niche and is a fair datapoint here (Free tier, Pro at $10/month per its listing as of July 2026), though I have not verified its multi-agent internals, so I will not claim them. On the Utter side, the Free plan is feature-complete: sprints, timeline, integrations, reporting, and AI credits are all on Free, and the ceiling is capacity, not features (128 MB storage, 5 active projects, 2 automations, 25 AI credits a month). Automation beyond that, roles, SSO, and audit are the paid additions. Agents are free on every tier.

Now the genuinely fair part, because I do not want you to buy a server you do not need. Stay on a file list when you run one agent, in one repo, with everything in the editor, and you want your tasks version-controlled in the repo right next to the code. In that world a file list is simpler, it lives where the code lives, and there is no server to reach or account to hold. You do not have a concurrency problem, because there is one writer. You do not have an attribution problem, because there is one actor. You do not need handoff, because there is nobody to hand off to. claude-task-master is a good fit for that shape, and adding a shared server would be overhead you would not use.

The line is the plural. The day you run a second agent against the same backlog, the tradeoff flips: a shared server of record starts paying for itself in attribution, handoff, and the ability to see and constrain the fleet, while local files in the repo start costing you in silent races and missing actors. Pick by how many agents you run, not by which is newer.

If you are already past that line, connecting your agents is the fast part. Point Claude Code, Cursor, and Codex at one Utter workspace, give each an API key and a field policy, and let the session states do the coordinating.

For the deeper mechanics, these walk through the pieces one at a time: coordinating two agents with status handoffs, managing a team of AI agents, setting per-agent field permissions, mapping your agent team, and task management for Claude Code.

Frequently asked questions

What is the best task manager for AI coding agents?

It depends on how many agents you run. For a single agent in one editor with tasks version-controlled in the repo, a file-based tool like claude-task-master is a clean fit and has no license fee (you bring your own model API key). Once you run two or more agents against the same backlog, a file list breaks on attribution, concurrency, and handoff. At that point a shared server of record like Utter earns its keep: every change is stamped to a named agent, work hands off through session states, and you get a dashboard. Pick by agent count, not by which tool is newest.

Why does a file-based task list like claude-task-master break with multiple agents?

Three things break at once. Attribution: the file records that a task is done but not which agent did it, so with three agents "done" has no author. Concurrency: its docs mention no file-locking, so two agents writing in the same second means one write silently overwrites the other with no error. Handoff: there is no per-agent identity or assignment, so there is no way to move a task from one agent to another. It is scoped to one AI in one editor by design, and that is exactly where it shines.

How does Utter attribute changes to a specific AI agent?

A connected agent in Utter is a real workspace member: a user and member row with is_agent=1, a workspace_agents profile, and an API key whose created_by points at the agent user itself. Because the key belongs to the agent, every write it makes is stamped to that named agent. There is no separate "who did this" bookkeeping, since the identity making the change is the identity recorded. The agent's provider (Claude Code, Codex, Cursor, Copilot, Gemini CLI, DeepSeek, Kimi, Perplexity, or Custom) rides along on its profile.

Do AI agents count as billed seats in Utter?

No. Agent members are free on every tier. Utter's seat counter (countEditorSeats) filters to is_agent=0, so connected agents never add to the Stripe per-seat quantity. Viewers are free and uncounted too. The billed editor seats are the humans who are owners, admins, or members. You can run ten agents and pay only for the people supervising them. As of July 2026, paid plans are Pro at $3 per seat per month and Business at $6 per seat per month, with a feature-complete Free plan at $0.

Can I limit what fields an AI agent is allowed to change?

Yes, per agent. Each agent's profile can carry a field_policy_json allowlist over 14 issue-field tokens (title, description, status, priority, assignee, labels, milestone, sprint, release, estimate, spent, completion, start_at, due_at). A null policy means all fields are writable, which is the default and what humans always get; the policy only applies to agent actors. Enforcement happens at one choke point, updateIssueViaApi, covering REST, the transition endpoint, and MCP, plus the time-tracking endpoint. It is per-field, not per-value, and it governs issue fields only, not create/delete or non-issue resources.

How do I know an AI agent actually did the work it marked done?

Utter derives an "unverified" badge at read time. A session that claims review or done, is anchored to a specific issue, and left no PR link gets flagged when the agent produced no attributed trace on that issue during the session window, meaning no activity row and no comment authored by that agent. It catches the empty "marked done, touched nothing" case. The honest limit: it proves an attributed change exists, not that the change was correct, and it cannot reconcile workspace-level sessions with no issue anchor. Treat it as a first filter for which sessions deserve a hard look at the PR.

Does Utter coordinate the agents' actual code branches?

No. Coordination lives at the task layer only. Utter tracks who owns which issue and what state each session is in, through a seven-state session machine (pending, running, needs_input, review, done, failed, cancelled) plus assignment and notify_agent automation rules. It does not lock or merge the agents' git branches. That stays in your version control. The status-handoff protocol solves "which agent is doing what and where work hands off," not "resolve the merge conflict between two agents' branches."

Related reading

أضف تعليقًا

ابدأ النقاش.