# How to Dispatch Multiple Sub-Agents in Parallel Using Fleet Mode in the Copilot SDK

> Learn to dispatch multiple sub-agents in parallel with Copilot SDK's Fleet Mode. This pattern orchestrates workers efficiently using shared state and automatic task dispatching for faster processing.

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

---

**Fleet Mode is the Copilot runtime's built-in pattern for orchestrating many independent sub-agents (workers) in parallel through a shared SQLite coordination state and automatic task dispatching.**

The Copilot SDK (`github/copilot-sdk`) enables complex multi-agent workflows through Fleet Mode, a runtime pattern that manages concurrent execution without explicit thread management. Instead of chaining sub-agents sequentially, you can dispatch multiple sub-agents in parallel using Fleet Mode to process independent work units simultaneously. This approach leverages durable SQL tables for coordination and the runtime's automatic task distribution to handle complex batches of work across your codebase.

## Understanding Fleet Mode Architecture

Fleet Mode replaces implicit shared memory with explicit coordination via SQL tables, making parallel execution deterministic and recoverable across restarts. The architecture centers on a parent session that defines work units called *todos*, which persist in a SQLite database that survives process interruptions.

The coordination schema consists of two core tables defined in the SDK documentation at [`docs/features/fleet-mode.md`](https://github.com/github/copilot-sdk/blob/main/docs/features/fleet-mode.md):

```sql
CREATE TABLE todos (
    id TEXT PRIMARY KEY,
    title TEXT NOT NULL,
    description TEXT,
    status TEXT DEFAULT 'pending'
);

CREATE TABLE todo_deps (
    todo_id TEXT,
    depends_on TEXT,
    PRIMARY KEY (todo_id, depends_on)
);

```

Each todo transitions through a strict state machine: **`pending → in_progress → done`** (or `blocked` when dependencies fail). The runtime automatically dispatches sub-agents only for todos with `status = 'pending'` and all dependencies marked `done`.

## Starting Fleet Mode

To begin parallel execution, invoke the generated RPC call `session.rpc.fleet.start` with an optional prompt that supplies high-level instructions for the fleet. The RPC interfaces are defined in [`nodejs/src/generated/rpc.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/src/generated/rpc.ts) as `FleetStartRequest` and `FleetStartResult`.

**Key parameters:**
- **`prompt`**: High-level instruction passed to all sub-agents in the fleet
- **`started`**: Boolean returned in the result confirming fleet activation

When the orchestrator successfully initializes, the runtime returns `started: true` and begins monitoring the coordination tables for ready work units.

## Creating the Coordination State

After starting Fleet Mode, the parent agent initializes the durable state tables. Each row in the `todos` table represents an independent unit of work—such as a file to refactor or a microservice to analyze—while `todo_deps` defines any execution prerequisites.

Insert work items using standard SQL. For example, to create independent tasks for multiple packages:

```typescript
const packages = ["core", "java", "python"];
const stmt = db.prepare(
  "INSERT INTO todos (id, title, description) VALUES (?, ?, ?)"
);
for (const pkg of packages) {
  stmt.run(`todo-${pkg}`, `Refactor ${pkg}`, `Refactor the ${pkg} SDK package`);
}

```

Sub-agents claim work by atomically updating the status from `pending` to `in_progress`, preventing duplicate processing across concurrent workers.

## Dispatching and Monitoring Sub-Agents

The Copilot runtime automatically invokes the **`task`** tool for each ready todo, launching sub-agents with `agent_type` specifications without blocking the parent session. While the SDK does not expose dedicated `subagentStart` or `subagentStop` hooks, the generic session event stream emits lifecycle events that you can observe via `session.on` listeners.

**Available event types:**
- **`subagent.started`**: Emitted when a worker claims a todo and begins execution
- **`subagent.completed`**: Emitted when the worker sets status to `done`
- **`subagent.failed`**: Emitted when processing errors occur

These events are validated in [`nodejs/test/e2e/subagent_hooks.e2e.test.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/test/e2e/subagent_hooks.e2e.test.ts), which demonstrates reliable monitoring of parallel worker lifecycles without explicit polling loops.

## Aggregating Results

Once all todos reach `done` status (or after implementing timeout logic), the parent session queries the coordination tables to collect outputs. Sub-agents typically write results back to the session context or files before marking completion, enabling the parent to generate combined summaries or proceed with aggregated data.

## Implementation Examples

The following examples demonstrate dispatching multiple sub-agents in parallel using Fleet Mode across TypeScript, Python, and Go.

### Node.js / TypeScript

This example uses an in-memory SQLite database to coordinate refactoring tasks across SDK packages:

```typescript
import { CopilotClient } from "copilot-sdk";
import { sql } from "better-sqlite3";

async function runFleet() {
  const client = new CopilotClient();
  const session = await client.createSession();

  // 1️⃣ Start fleet mode
  const fleet = await session.rpc.fleet.start({
    prompt: "Refactor each SDK package independently, then summarize the changes."
  });
  if (!fleet.started) throw new Error("Fleet failed to start");

  // 2️⃣ Create coordination tables
  const db = sql(":memory:");
  db.exec(`
    CREATE TABLE todos (id TEXT PRIMARY KEY, title TEXT, description TEXT, status TEXT DEFAULT 'pending');
    CREATE TABLE todo_deps (todo_id TEXT, depends_on TEXT, PRIMARY KEY (todo_id, depends_on));
  `);

  // 3️⃣ Insert independent work items
  const packages = ["core", "java", "python"];
  const stmt = db.prepare(
    "INSERT INTO todos (id, title, description) VALUES (?, ?, ?)"
  );
  for (const pkg of packages) {
    stmt.run(`todo-${pkg}`, `Refactor ${pkg}`, `Refactor the ${pkg} SDK package`);
  }

  // 4️⃣ Listen to sub-agent lifecycle events
  session.on((e) => {
    if (e.type === "subagent.started") console.log(`🟢 ${e.data.agentDisplayName} started`);
    if (e.type === "subagent.completed") console.log(`✅ ${e.data.agentDisplayName} finished`);
  });

  // Sub-agents automatically claim and process todos via the task tool
  // Parent aggregates results after completion
  const all = db.prepare("SELECT * FROM todos").all();
  console.log("All todos:", all);
}

```

### Python

The Python SDK mirrors the RPC surface with `FleetStartRequest` and automatic task dispatching:

```python
import asyncio
from copilot import CopilotClient
from copilot.rpc import FleetStartRequest

async def main():
    client = CopilotClient()
    await client.start()
    session = await client.create_session()

    # 1️⃣ Start fleet mode

    result = await session.rpc.fleet.start(
        FleetStartRequest(prompt="Review each microservice independently, then summarize risks.")
    )
    if not result.started:
        raise RuntimeError("Fleet start failed")

    # 2️⃣ Create coordination tables

    await session.exec_sql("""
        CREATE TABLE todos (id TEXT PRIMARY KEY, title TEXT, description TEXT, status TEXT DEFAULT 'pending');
        CREATE TABLE todo_deps (todo_id TEXT, depends_on TEXT, PRIMARY KEY (todo_id, depends_on));
    """)

    # 3️⃣ Insert work items

    services = ["auth", "billing", "notifications"]
    for svc in services:
        await session.exec_sql(
            "INSERT INTO todos (id, title, description) VALUES (?, ?, ?)",
            (f"todo-{svc}", f"Review {svc}", f"Security review of the {svc} service")
        )

    # 4️⃣ Subscribe to sub-agent events

    def handle(event):
        if event.type == "subagent.started":
            print(f"🟢 {event.data.agent_display_name} started")
        if event.type == "subagent.completed":
            print(f"✅ {event.data.agent_display_name} completed")
    session.on(handle)

    # 5️⃣ Wait for completion

    while True:
        rows = await session.query_sql("SELECT * FROM todos WHERE status != 'done'")
        if not rows:
            break
        await asyncio.sleep(1)

    # 6️⃣ Gather results

    all_todos = await session.query_sql("SELECT * FROM todos")
    print("All todos:", all_todos)

asyncio.run(main())

```

### Go

The Go bindings in `github.com/github/copilot-sdk/go` provide the same RPC interfaces:

```go
package main

import (
	"context"
	"fmt"
	"log"
	"time"

	copilot "github.com/github/copilot-sdk/go"
	"github.com/github/copilot-sdk/go/rpc"
)

func main() {
	ctx := context.Background()
	client := copilot.NewClient(nil)

	// 1️⃣ Start fleet mode
	fleetResult, err := client.Session().RPC.Fleet.Start(ctx, &rpc.FleetStartRequest{
		Prompt: stringPtr("Refactor each package independently, then report validation results."),
	})
	if err != nil || !fleetResult.Started {
		log.Fatalf("failed to start fleet: %v", err)
	}

	// 2️⃣ Create coordination tables
	if _, err := client.Session().ExecSQL(ctx, `
		CREATE TABLE todos (id TEXT PRIMARY KEY, title TEXT, description TEXT, status TEXT DEFAULT 'pending');
		CREATE TABLE todo_deps (todo_id TEXT, depends_on TEXT, PRIMARY KEY (todo_id, depends_on));
	`); err != nil {
		log.Fatalf("sql error: %v", err)
	}

	// 3️⃣ Insert work items
	pkgs := []string{"core", "java", "python"}
	for _, p := range pkgs {
		_, err := client.Session().ExecSQL(ctx, `
			INSERT INTO todos (id, title, description) VALUES (?, ?, ?);
		`, fmt.Sprintf("todo-%s", p), fmt.Sprintf("Refactor %s", p), fmt.Sprintf("Refactor the %s SDK package", p))
		if err != nil {
			log.Fatalf("insert error: %v", err)
		}
	}

	// 4️⃣ Subscribe to sub-agent events
	client.Session().On(func(e copilot.Event) {
		switch e.Type {
		case "subagent.started":
			fmt.Printf("🟢 %s started\n", e.Data["agentDisplayName"])
		case "subagent.completed":
			fmt.Printf("✅ %s completed\n", e.Data["agentDisplayName"])
		}
	})

	// 5️⃣ Wait for completion
	for {
		rows, _ := client.Session().QuerySQL(ctx, "SELECT * FROM todos WHERE status != 'done'")
		if len(rows) == 0 {
			break
		}
		time.Sleep(time.Second)
	}

	// 6️⃣ Final aggregation
	all, _ := client.Session().QuerySQL(ctx, "SELECT * FROM todos")
	fmt.Println("All todos:", all)
}

func stringPtr(s string) *string {
	return &s
}

```

## Summary

- **Fleet Mode** enables parallel sub-agent execution through the `session.rpc.fleet.start` RPC call defined in [`nodejs/src/generated/rpc.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/src/generated/rpc.ts)
- **Coordination** relies on explicit SQL state management via `todos` and `todo_deps` tables rather than implicit shared memory
- **Automatic dispatch** occurs when the runtime's `task` tool picks up ready todos (status `pending` with satisfied dependencies)
- **Lifecycle monitoring** uses the generic event stream (`subagent.started`, `subagent.completed`) tested in [`nodejs/test/e2e/subagent_hooks.e2e.test.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/test/e2e/subagent_hooks.e2e.test.ts)
- **Deterministic recovery** persists state across restarts, making long-running fleet operations resilient to interruptions

## Frequently Asked Questions

### What is the difference between Fleet Mode and sequential sub-agent calls?

Sequential calls execute one sub-agent at a time, blocking until each returns. Fleet Mode launches many sub-agents concurrently using the runtime's task distribution system, coordinated through the SQL `todos` table. This reduces total execution time for independent work units and is validated 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).

### Can sub-agents in Fleet Mode communicate with each other?

No. Sub-agents are intentionally isolated and communicate only through the durable coordination tables by updating their `status` and writing results to shared storage. They cannot directly share memory or call each other's functions, which prevents coupling and ensures deterministic execution.

### How does the runtime handle failed sub-agents?

When a sub-agent encounters an error, it sets its todo status to `blocked` or emits a `subagent.failed` event. The parent session can query the `todos` table to identify blocked items and implement retry logic, or proceed with partial results from successfully completed workers.

### Is Fleet Mode suitable for tasks with complex dependencies?

Yes. The `todo_deps` table allows modeling directed acyclic graphs where a todo only becomes ready when specific dependencies reach `done`. However, for tightly coupled sequential steps where each step requires the concrete output of the previous, standard sequential calls remain more appropriate than Fleet Mode's parallel coordination.