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

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:

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 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:

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, 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:

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:

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:

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
  • 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
  • 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.

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.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →