# How to Use Copilot SDK for Fleet Mode Parallel Execution with Todo Coordination

> Learn to use the Copilot SDK for fleet mode parallel execution. This guide shows how to start workers with session rpc fleet start and coordinate tasks using a todo table for seamless completion monitoring.

- Repository: [GitHub/copilot-sdk](https://github.com/github/copilot-sdk)
- Tags: how-to-guide
- Published: 2026-08-02

---

**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`](https://github.com/github/copilot-sdk/blob/main/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 in [`nodejs/src/generated/rpc.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/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 (`todos` and `todo_deps`) storing task state and dependency edges
- **Event system**: The `session.todos_changed` signal (documented in [`nodejs/src/generated/session-events.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/src/generated/session-events.ts) at 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:

1. The parent creates todo rows and optional dependency edges in the session SQLite database
2. The parent calls `session.rpc.fleet.start()` with a high-level prompt; the runtime injects its own orchestration instructions
3. The runtime spawns sub-agents for each todo whose dependencies are satisfied (no pending `depends_on` entries in `todo_deps`)
4. Workers execute their assigned tasks and update their row `status` to `done`
5. The runtime emits `session.todos_changed` events whenever the `todos` or `todo_deps` tables are written
6. The parent calls `session.rpc.plan.readSqlTodosWithDependencies()` (lines 19617-19625 in [`nodejs/src/generated/rpc.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/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`:

```typescript
// 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:

```typescript
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`](https://github.com/github/copilot-sdk/blob/main/nodejs/src/generated/session-events.ts), this is a signal-only event requiring the parent to explicitly fetch updated state:

```typescript
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`](https://github.com/github/copilot-sdk/blob/main/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`](https://github.com/github/copilot-sdk/blob/main/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:

```typescript
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 in [`nodejs/src/generated/rpc.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/src/generated/rpc.ts) (lines 90-99)
- The **todo table** (`todos` and `todo_deps` in SQLite) serves as the coordination mechanism, requiring no shared memory between agents
- **Event-driven updates** via `session.todos_changed` (documented in [`nodejs/src/generated/session-events.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/src/generated/session-events.ts) at lines 1816-1823) allow the parent to monitor progress
- Use `session.rpc.plan.readSqlTodosWithDependencies()` (lines 19617-19625 in [`nodejs/src/generated/rpc.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/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`](https://github.com/github/copilot-sdk/blob/main/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`](https://github.com/github/copilot-sdk/blob/main/nodejs/test/e2e/rpc_shell_and_fleet.e2e.test.ts) demonstrates verification patterns including filesystem markers and timeout handling for detecting stuck workers.