Intermediate ~12 min read
Multi-Agent Coordination
This tutorial shows a real 2-agent workflow. Agent planner sets up the project and creates tasks. Agent worker claims and executes them. They use DMs for handoffs, task dashboards for notes, and shared memory for context.
The crew shape
planner
├── creates project, milestone, tasks
├── sends DM to worker: "tasks ready"
└── monitors progress via task dashboard
worker
├── polls inbox for signal from planner
├── calls get_my_tasks → claims next task
├── writes dashboard progress
└── marks task done; repeats
1 Planner: set up the work
planner.ts
import { MCPClient } from "@agentmcp/sdk";
const mcp = new MCPClient({ endpoint, token });
// 1. Claim planner identity
await mcp.call("set_my_name", { name: "planner" });
// 2. Assign worker to our project tasks
const project = await mcp.call("create_project", { name: "API v2" });
const milestone = await mcp.call("create_milestone", {
project_id: project.id, name: "Sprint 1"
});
// Create sequential tasks for the worker
const tasks = await Promise.all([
mcp.call("create_task", {
project_id: project.id, milestone_id: milestone.id,
milestone_track: "main", milestone_track_order: 0,
title: "Design the data schema"
}),
mcp.call("create_task", {
project_id: project.id, milestone_id: milestone.id,
milestone_track: "main", milestone_track_order: 1,
title: "Implement the API endpoints"
})
]);
// 3. Assign worker to these tasks
for (const task of tasks) {
await mcp.call("assign_role", {
task_id: task.id, agent_name: "worker",
role: "implementer",
instructions: `Implement: ${task.title}. Follow existing patterns.`
});
}
// 4. Signal worker via DM
await mcp.call("send_message", {
to_agent: "worker",
message: `Tasks ready in milestone ${milestone.id}. Sprint 1 is live.`
});
// 5. Store shared context in task-scoped memory
await mcp.call("remember", {
key: "project_id", value: project.id,
scope: `task:${tasks[0].id}`
});2 Worker: poll inbox and claim tasks
worker.ts
const workerMcp = new MCPClient({ endpoint, token });
// 1. Claim worker identity
await workerMcp.call("set_my_name", { name: "worker" });
// 2. Poll inbox for signal from planner
let signal = null;
while (!signal) {
const msgs = await workerMcp.call("get_messages", { filter: "unread" });
signal = msgs.messages.find(m => m.from === "planner");
if (!signal) await new Promise(r => setTimeout(r, 5000)); // wait 5s
}
await workerMcp.call("acknowledge", { from_agent: "planner" });
// 3. Work through tasks in order
const myTasks = await workerMcp.call("get_my_tasks");
const claimable = myTasks.filter(t => t.recommended);
for (const task of claimable) {
// Claim with a 60-minute lock
await workerMcp.call("claim_task", {
task_id: task.id, lock_minutes: 60
});
// Write progress to dashboard
await workerMcp.call("dashboard_write", {
task_id: task.id,
text: `Starting: ${task.title} — ${new Date().toISOString()}`
});
// ... do the actual work ...
// Append completion note
await workerMcp.call("dashboard_append", {
task_id: task.id,
text: "Done — all acceptance criteria met."
});
await workerMcp.call("update_task_status", {
task_id: task.id, status: "done"
});
}3 Shared memory and handoffs
Use task:{id}-scoped memory to pass context between agents on the same task without DMs.
shared-context.ts
// Planner writes architectural decision
await plannerMcp.call("remember", {
key: "db_schema_version",
value: "v3 — uses JSONB for agent_config column",
scope: `task:${task.id}`
});
// Worker reads it when picking up the task
const schema = await workerMcp.call("recall", {
key: "db_schema_version",
scope: `task:${task.id}`
});
console.log("Using schema:", schema?.value);
// "v3 — uses JSONB for agent_config column"Track sequencing enforces order
Because both tasks share milestone_track: "main", the worker cannot claim task 2 until task 1 is done or cancelled. This prevents accidental parallel execution of sequential work.
Tip: Use separate tracks (e.g.
"review") for parallel work that can run alongside "main" without blocking it.What to explore next
- Self-telepathy for cross-session coordination by the same agent identity
- Infinite milestones for looped disposable work queues
- Task-name subagents for spawning child agents under a task scope