Fleet mode is Copilot's parallel orchestration pattern for work that can be split across independent sub-agents. In the runtime research notes, fleet mode is described as "the runtime's built-in pattern for dispatching multiple sub-agents in parallel via the task tool, with SQL todos as the shared coordination state." Use it when one parent session should coordinate several workers, collect their results, and continue the conversation with the combined context.
Fleet mode is useful when the work can be decomposed before execution and each unit can run without waiting for the others.
Good fits include:
Avoid fleet mode for:
Fleet mode works best when the parent session can create clear units of work, assign one owner per unit, and define what each worker must return.
The SDK exposes fleet mode through the session RPC namespace in several languages. The binding is experimental in the generated RPC surface; pin both the SDK and the Copilot CLI runtime if your application depends on it.
The wire method is session.fleet.start. The optional prompt is combined with the runtime's fleet orchestration instructions.
const result = await session.rpc.fleet.start({
prompt: "Refactor each SDK package independently, then summarize the changes.",
});
if (result.started) {
console.log("Fleet mode started");
}
from copilot.rpc import FleetStartRequest
result = await session.rpc.fleet.start(
FleetStartRequest(
prompt="Review each service independently, then summarize the risks."
)
)
if result.started:
print("Fleet mode started")
package main
import (
"context"
"fmt"
copilot "github.com/github/copilot-sdk/go"
"github.com/github/copilot-sdk/go/rpc"
)
func main() {
ctx := context.Background()
client := copilot.NewClient(nil)
session, err := client.CreateSession(ctx, &copilot.SessionConfig{})
if err != nil {
return
}
prompt := "Update each package independently, then report validation results."
result, err := session.RPC.Fleet.Start(ctx, &rpc.FleetStartRequest{
Prompt: &prompt,
})
if err != nil {
return
}
if result.Started {
fmt.Println("Fleet mode started")
}
}
prompt := "Update each package independently, then report validation results."
result, err := session.RPC.Fleet.Start(ctx, &rpc.FleetStartRequest{
Prompt: &prompt,
})
if err != nil {
return err
}
if result.Started {
fmt.Println("Fleet mode started")
}
using GitHub.Copilot;
await using var client = new CopilotClient();
await using var session = await client.CreateSessionAsync(new SessionConfig());
var result = await session.Rpc.Fleet.StartAsync(
"Audit each project independently, then summarize the findings.");
if (result.Started)
{
Console.WriteLine("Fleet mode started");
}
var result = await session.Rpc.Fleet.StartAsync(
"Audit each project independently, then summarize the findings.");
if (result.Started)
{
Console.WriteLine("Fleet mode started");
}
use github_copilot_sdk::rpc::FleetStartRequest;
let result = session
.rpc()
.fleet()
.start(FleetStartRequest {
prompt: Some("Research each crate independently, then summarize the plan.".into()),
})
.await?;
if result.started {
println!("Fleet mode started");
}
Native typed bindings for fleet mode were verified in Node.js/TypeScript, Python, Go, .NET, and Rust. A Java binding was not found in java/src/main/java on this branch, so Java examples are omitted until that surface is available.
Plan-mode UIs can start fleet deployment by returning the autopilot_fleet exit action. The generated session event types describe it as:
type PlanModeExitAction =
| "exit_only"
| "interactive"
| "autopilot"
/** Exit plan mode and continue with parallel autonomous workers. */
| "autopilot_fleet";
Use this when a user approves a plan that already contains independent work items. Use autopilot for a single autonomous worker and interactive when the user should stay in the loop.
Fleet mode relies on explicit coordination state instead of implicit shared memory. The parent agent decomposes the work into todos, each sub-agent owns one todo, and the orchestrator dispatches workers whose dependencies are already complete.
The canonical schema is:
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 moves through a small state machine:
pending -> in_progress -> done
\-> blocked
A sub-agent should:
status = 'in_progress'.status = 'done' when complete.status = 'blocked' when it cannot proceed, and include the reason.The orchestrator can find work whose dependencies are satisfied with a query like:
SELECT t.*
FROM todos t
WHERE t.status = 'pending'
AND NOT EXISTS (
SELECT 1
FROM todo_deps td
JOIN todos dep ON td.depends_on = dep.id
WHERE td.todo_id = t.id
AND dep.status != 'done'
);
This pattern gives every worker a clear owner and lets the parent session reason about what is ready, running, complete, or blocked.
Fleet mode invokes sub-agents through the runtime's task mechanism. The runtime emits hook activity for sub-agent tool calls: the runtime 1.0.52 changelog notes that preToolUse, postToolUse, subagentStart, and subagentStop fire correctly for sub-agent tool calls.
A dedicated SDK hook callback for subagentStart or subagentStop was not found in the public SDK surface on this branch. SDK consumers can observe sub-agent activity through the generic session event stream, which includes events such as subagent.started, subagent.completed, subagent.failed, subagent.selected, and subagent.deselected.
session.on((event) => {
if (event.type === "subagent.started") {
console.log(`Started ${event.data.agentDisplayName}`);
}
if (event.type === "subagent.completed") {
console.log(`Completed ${event.data.agentDisplayName}`);
}
});
import asyncio
from copilot import CopilotClient
from copilot.session import PermissionHandler
async def main():
client = CopilotClient()
await client.start()
session = await client.create_session(
on_permission_request=PermissionHandler.approve_all,
)
def handle_event(event):
if event.type == "subagent.started":
print(f"Started {event.data.agent_display_name}")
elif event.type == "subagent.completed":
print(f"Completed {event.data.agent_display_name}")
unsubscribe = session.on(handle_event)
asyncio.run(main())
def handle_event(event):
if event.type == "subagent.started":
print(f"Started {event.data.agent_display_name}")
elif event.type == "subagent.completed":
print(f"Completed {event.data.agent_display_name}")
unsubscribe = session.on(handle_event)
For hook configuration that is already exposed at the SDK layer, see Hooks. For sub-agent event payloads, see Custom agents and sub-agent orchestration.
The runtime can load plugins with --plugin-dir. Plugins loaded this way can register their agents as available task(agent_type=...) sub-agent types in prompt mode, which means fleet mode can dispatch to those plugin-provided worker types.
This is currently a runtime-level configuration pattern rather than a documented SDK-level registration API. Configure the Copilot CLI runtime with the plugin directory, then connect the SDK client to that runtime. Native SDK helpers for registering plugin sub-agent types may be added in the future.
Conceptually, a fleet prompt can then ask for a specific worker type:
Use task(agent_type="security-review") for each independent package.
Run the workers in parallel and summarize only high-confidence findings.
Keep plugin-provided sub-agent types narrow and descriptive so the orchestrator can choose them reliably.
subagentStart and subagentStop are runtime hook names; this branch exposes sub-agent lifecycle to SDK consumers through the generic session event stream, not dedicated hook callbacks.--plugin-dir; no SDK-level plugin registration helper was verified on this branch.session.fleet.start were not found in the Java SDK source on this branch.Can you improve this documentation? These fine people already did:
Steve Sanderson & Patrick NikoletichEdit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |