How to Use Copilot SDK for Fleet Mode Parallel Execution with Todo Coordination
Fleet mode enables parallel execution of sub-agents using a SQLite-backed todo table for coordination, where the parent session starts workers via session.rpc.fleet.start() and monitors completion through session.todos_changed events.
The GitHub Copilot SDK provides a built-in pattern for orchestrating multiple AI sub-agents simultaneously through fleet mode. By leveraging the session's internal SQLite database and event-driven architecture, developers can build scalable parallel workflows without shared memory concerns. This guide demonstrates the exact implementation details found in the github/copilot-sdk repository, including specific RPC methods and file locations from the source analysis.
Understanding Fleet Mode Architecture
Fleet mode distributes work across independent sub-agents while maintaining coordination through a central todo system. According to the source code in nodejs/src/generated/rpc.ts, the fleet namespace provides the entry point for launching parallel executions.
Core Components
The architecture consists of four primary elements working together:
- Parent session: Calls
session.rpc.fleet.start()(defined at lines 90-99 innodejs/src/generated/rpc.ts) to initiate parallel execution with a high-level prompt - Sub-agents: Independent workers spawned by the runtime, each processing a single todo row identified by
id - Todo tables: SQLite tables (
todosandtodo_deps) storing task state and dependency edges - Event system: The
session.todos_changedsignal (documented innodejs/src/generated/session-events.tsat lines 1816-1823) notifies the parent when sub-agents update database rows
Coordination Flow
The execution follows a strict six-step pattern implemented in the runtime:
- The parent creates todo rows and optional dependency edges in the session SQLite database
- The parent calls
session.rpc.fleet.start()with a high-level prompt; the runtime injects its own orchestration instructions - The runtime spawns sub-agents for each todo whose dependencies are satisfied (no pending
depends_onentries intodo_deps) - Workers execute their assigned tasks and update their row
statustodone - The runtime emits
session.todos_changedevents whenever thetodosortodo_depstables are written - The parent calls
session.rpc.plan.readSqlTodosWithDependencies()(lines 19617-19625 innodejs/src/generated/rpc.ts) to refresh state and determine whether to summarize results or continue execution
Implementing Todo Coordination
The todo table serves as the single source of truth for parallel execution, eliminating the need for shared memory between agents. All communication occurs through database writes and SDK-provided events.
Creating Todos and Dependencies
Before starting fleet mode, populate the todos table and optionally define dependencies in todo_deps:
// Insert a pending todo
await session.rpc.plan.executeSql({
sql: `INSERT INTO todos (id, title, status)
VALUES ('analysis-task-1', 'Run code analysis', 'pending');`
});
// Add dependency if needed (analysis-task-1 depends on setup-task)
await session.rpc.plan.executeSql({
sql: `INSERT INTO todo_deps (todo_id, depends_on)
VALUES ('analysis-task-1', 'setup-task');`
});
Starting Fleet Execution
Launch the parallel workers by invoking the fleet RPC. The runtime automatically manages worker creation based on the current todo state:
const result = await session.rpc.fleet.start({
prompt: "Execute all pending todos."
});
if (!result.started) {
throw new Error("Fleet mode failed to start");
}
Monitoring Completion Through Events
Set up an event listener to track progress through the session.todos_changed event. As noted in nodejs/src/generated/session-events.ts, this is a signal-only event requiring the parent to explicitly fetch updated state:
session.on("session.todos_changed", async () => {
// Fetch complete state including dependencies
const { rows, edges } = await session.rpc.plan.readSqlTodosWithDependencies();
console.log("Current todo state:", rows);
console.log("Dependency graph:", edges);
// Check if all tasks completed
const allDone = rows.every(row => row.status === 'done');
if (allDone) {
console.log("Fleet execution complete");
}
});
This pattern, demonstrated in nodejs/test/e2e/session_todos_changed.e2e.test.ts, ensures the parent always maintains the latest coordination state.
Complete Working Example
The end-to-end test in nodejs/test/e2e/rpc_shell_and_fleet.e2e.test.ts demonstrates the full implementation pattern. Below is an adapted version showing custom tool integration with proper error handling:
import * as fs from "fs";
import * as path from "path";
import { z } from "zod";
import { defineTool, approveAll } from "copilot-sdk";
// Define a tool for sub-agents to report completion via filesystem markers
const recordFleetCompletion = defineTool("record_fleet_completion", {
description: "Write a marker file when a fleet sub-agent finishes its todo",
parameters: z.object({
markerPath: z.string(),
content: z.string()
}),
handler: ({ markerPath, content }) => {
fs.writeFileSync(markerPath, content);
return { content };
},
});
async function runFleetWorkflow() {
const { copilotClient: client, workDir } = await createSdkTestContext();
// Create session with custom tool available to sub-agents
const session = await client.createSession({
onPermissionRequest: approveAll,
tools: [recordFleetCompletion],
});
// Initialize todo for the fleet task
await session.rpc.plan.executeSql({
sql: `INSERT INTO todos (id, title, status)
VALUES ('fleet-task-1', 'Execute parallel analysis', 'pending');`
});
// Start fleet mode - runtime creates sub-agents for each pending todo
await session.rpc.fleet.start({ prompt: "Execute all pending todos." });
// Monitor changes through the event system
session.on("session.todos_changed", async () => {
const { rows } = await session.rpc.plan.readSqlTodosWithDependencies();
console.log("Updated todos:", rows);
});
// Wait for completion marker written by sub-agent via custom tool
const markerPath = path.join(workDir, "fleet-marker.txt");
await waitForFile(markerPath, "complete", 30000);
await session.disconnect();
}
// Utility to poll for file creation by sub-agents
async function waitForFile(file: string, content: string, timeout = 30000) {
const deadline = Date.now() + timeout;
while (Date.now() < deadline) {
if (fs.existsSync(file) && fs.readFileSync(file, "utf8").includes(content)) {
return;
}
await new Promise(r => setTimeout(r, 100));
}
throw new Error(`Timeout waiting for ${content} in ${file}`);
}
This example illustrates how sub-agents communicate completion back to the parent process through filesystem markers while the SDK handles the parallel orchestration internally.
Summary
- Fleet mode enables parallel sub-agent execution through
session.rpc.fleet.start()as defined innodejs/src/generated/rpc.ts(lines 90-99) - The todo table (
todosandtodo_depsin SQLite) serves as the coordination mechanism, requiring no shared memory between agents - Event-driven updates via
session.todos_changed(documented innodejs/src/generated/session-events.tsat lines 1816-1823) allow the parent to monitor progress - Use
session.rpc.plan.readSqlTodosWithDependencies()(lines 19617-19625 innodejs/src/generated/rpc.ts) to refresh the complete task state and dependency graph after each change - Custom tools enable sub-agents to report progress through side effects while the runtime manages parallel dispatch automatically
Frequently Asked Questions
How does the Copilot SDK handle todo dependencies in fleet mode?
The runtime evaluates the todo_deps table before spawning sub-agents. A sub-agent is only created for a todo when all rows referencing it as todo_id have their depends_on entries satisfied by completed (status done) todos. This dependency resolution happens automatically when session.rpc.fleet.start() is called, as the runtime queries both tables to determine eligible work. The parent can verify dependency satisfaction by calling session.rpc.plan.readSqlTodosWithDependencies() to inspect the current edges array.
What is the difference between readSqlTodos and readSqlTodosWithDependencies?
readSqlTodos returns only the todo rows from SQLite, while readSqlTodosWithDependencies (recommended for fleet coordination) returns both the rows and edges needed to reconstruct the dependency graph. According to nodejs/src/generated/rpc.ts (lines 19617-19625), the latter provides the complete state required to determine which tasks are ready to execute or have completed, including the dependency relationships stored in todo_deps.
Can sub-agents create new todos during fleet execution?
Yes. Sub-agents can execute SQL against the session database using available tools, inserting new rows into todos and todo_deps. Because session.todos_changed events trigger on any write to these tables, the parent receives notifications about dynamically added work items and can extend fleet execution accordingly. This pattern allows for dynamic workflow generation where initial tasks spawn dependent sub-tasks.
How do I debug fleet mode when sub-agents fail to update todos?
Verify that your custom tools properly execute SQL updates to the todos table, setting the status column to a terminal state like done. Listen for session.todos_changed events and log the output of readSqlTodosWithDependencies() to confirm writes are persisting. The end-to-end test in nodejs/test/e2e/rpc_shell_and_fleet.e2e.test.ts demonstrates verification patterns including filesystem markers and timeout handling for detecting stuck workers.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →