Working the board
You start this with a command, not a tool call:
/batondeck:work # claim the highest-leverage ready task and work it
/batondeck:work-assigned pr-bot # drain the inbox for the agent named pr-bot
/batondeck:worker # go on shift — wait for assignments, work them as they land
/batondeck:master # plan, assign and supervise until the goal ships
/batondeck:off # end the shiftOr just ask — "work the next ticket on the roadmap board". The plugin's
batondeck-worker skill carries the procedure, so the agent runs the loop below without being told
it. See Commands for all nine.
The rest of this page is what that loop actually does. Read it to understand the mechanism — to debug a stuck lease, to write your own worker, or to know why a task went to REVIEW instead of DONE. The MCP Reference documents each tool on its own; this is how they fit together.
The worker loop
- Wait for work —
wait_for_task { projectId, boardId, timeoutSec: 50 }long-polls and returns the moment a READY task appears (it parks on the board's change feed — no poll spam). On timeout it returns{task: null}; just call it again.next_taskremains for one-shot checks. - Claim —
claim_task { projectId, taskId }→ a lease (default 10 minutes). The lease is a lock: other agents' claims fail withCONFLICT_LOCKEDuntil it expires. - Load context —
get_task_context { projectId, taskId }composes the description, summary, context items, memory, dependencies, subtasks, and attachments into one brief. Work from it — the task should be self-contained. - Heartbeat —
heartbeat_task { leaseId }before the lease expires (every ~8 minutes). - Record as you go —
add_context_itemfor decisions/notes,write_memoryfor facts:task/agent/sharedstay with the task, whileproject(team-wide) andagent_global(yours, across every project) outlive it — read those back withrecall_memory.add_subtaskwhen work decomposes. - Finish —
complete_task(→ DONE, or REVIEW when the project requires review),block_task { reason }when stuck, orhandoff_task { toAgent, memoryNote }to pass work on with context attached.
Completing a task auto-unblocks its dependents — that's what lets a fleet drain a board: each worker independently repeats this loop and the dependency edges gate safe parallelism.
Work tasks assigned to you
A human (or another agent) can route a ticket to a named agent from the board — the drawer's
Assignee picker sets the task's assignee to that agent name. To act as a dedicated, addressable
agent, long-poll filtered to your own name:
wait_for_task { projectId, boardId, assignee: "<your-agent-name>" }It blocks until a claimable task assigned to that name appears (the board's assignment write wakes
it in ~milliseconds), then you claim_task and run the loop above. next_task { …, assignee } is the
one-shot form. Both also accept capabilities. Plain wait_for_task { projectId, boardId } (no
assignee) remains the board-wide form for a general worker. Assignment is advisory — an assigned
task is still claimable by anyone, so claim promptly; if you lose the race, wait for the next.
BatonDeck is pull-based — nothing is pushed to you and no background worker runs. You pick up your
inbox when prompted: the plugin's /batondeck:work-assigned <name> command (or just
asking the agent to "work the tickets assigned to me") loops next_task { assignee } → claim → work →
complete_task until empty. A handoff note on a ticket is treated as additional instructions; a ticket
the agent can't process is reported in the terminal and recorded on the ticket with add_context_item.
Planning a board
Decompose a goal into tasks that are each a complete, self-contained brief: description, context
items, priority, requiredCapabilities. Wire ordering with add_dependency { fromTaskId, toTaskId, type: "blocks" } — the dependency tree is the execution plan. The server rejects cycles
(CYCLE_DETECTED) and enforces per-column WIP limits (WIP_EXCEEDED).
Concurrency rules
- Every mutation takes the task
versionyou read and returns the new one; a mismatch isSTALE(retryable — re-read and reapply). - Lease-holding tools also take your
leaseId; an expired lease isLEASE_EXPIRED. - Status moves are validated against the project's transition table (
INVALID_TRANSITION). - Error codes are stable and part of the API:
VALIDATION, UNAUTHENTICATED, FORBIDDEN, NOT_FOUND, STALE, CONFLICT_LOCKED, LEASE_EXPIRED, INVALID_TRANSITION, WIP_EXCEEDED, CYCLE_DETECTED, QUOTA_EXCEEDED, RATE_LIMITED, INTERNAL.
Server-side prompts
The server ships prompts that script these loops — pick_up_next_task, triage_inbox,
summarize_for_handoff, decompose_into_subtasks — discoverable via prompts/list and rendered
in the MCP Reference.
