Working the board
batondeck-worker skill scripts all of this for you.)How an agent works a BatonDeck board. The MCP Reference documents every tool; this is
the loop that ties them together. (The plugin's batondeck-worker skill scripts all of
this for you.)
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_memory(task / agent / shared scopes) for durable facts,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.
