Start Debugging

Copilot Dynamic Workflows vs /fleet vs GitHub Agentic Workflows: Which Multi-Agent Option to Reach For

Use /fleet for a one-off parallel task in your terminal, a dynamic workflow when the same multi-agent process has to run the same way twice, and GitHub Agentic Workflows when that process runs unattended on repository events. Compared on Copilot CLI 1.0.103 and gh-aw v0.93.6: who plans the split, limits, resume, permissions, write safety and billing.

Short answer: pick by who writes the plan and where it runs. /fleet lets Copilot split a task into parallel subagents on the fly, which is right for a one-off job you are watching. A dynamic workflow puts the split in code (defineWorkflow in .github/extensions/<name>/extension.mjs), so it is the winner for anything you will run more than once. GitHub Agentic Workflows (gh-aw) is not a third orchestrator. It is the guardrail layer you wrap around either of the other two when the run fires from an issue, a PR or a schedule with nobody watching. Everything below is checked against Copilot CLI 1.0.103 (October 22, 2026) and gh-aw v0.93.6 (October 26, 2026). Dynamic workflows are in public preview and gh-aw is a technical preview. /fleet has been available to all users since Copilot CLI 0.0.411 in February.

Three answers to the same “use more than one agent” question

GitHub now ships three ways to put several agents on one job, and their names overlap enough to confuse anyone. GitHub’s own dynamic workflows concepts page says the main difference is “how work is planned and controlled, not simply how many agents are used.” That is the right lens, and it extends cleanly to gh-aw:

So the first two are orchestration models, and the third is a runtime and a security boundary. Comparing all three as peers is what makes the choice look harder than it is.

Feature matrix

/fleet (CLI 1.0.103)Dynamic workflow (CLI 1.0.103, preview)gh-aw (v0.93.6, technical preview)
Who decides the splitCopilot, per runYour codeWhatever runs inside: a prompt, or a dynamic workflow
Same steps every runNoYes (agent answers can still vary)Yes for the shell, depends on the body
Where it runsCopilot CLI or the Copilot appCLI, app, SDK host, any CI stepGitHub Actions only
How you start it/fleet <prompt>, copilot --fleet -pAsk by name, copilot workflow run <name>, slash command, SDKon: triggers, schedule: daily, on: /command
Structured outputProse summary per subtaskJSON validated against a schemaTyped safe-outputs requests
LimitsSession AI credit limit onlyConcurrency, total agents, timeout, AI credits per runtimeout-minutes, max-ai-credits, safe-output caps
Pause and resumeNoYes, journaled steps replayNo, one shot per job
Write accessWhatever the session grantsSession grants, plus the extension’s own Node.js codeAgent job has no write token
BillingAI creditsAI credits, token-based plans onlyAI credits plus Actions minutes

If you read one row, read the first. Every other row follows from who owns the plan.

When /fleet is the right call

/fleet is the fastest path from “this is obviously parallel” to agents working on it. There is nothing to write. The typical flow from the /fleet how-to is plan mode first, then fleet:

# Copilot CLI 1.0.103, interactive session
# Shift+Tab into plan mode, iterate on the plan, then:
/fleet Implement the plan. Use the @test-writer agent to add tests for each component.

Then /tasks lists the subagents. Enter shows a subtask’s details and summary, x kills one, r clears finished ones.

The same thing works without an interactive session:

# Copilot CLI 1.0.103
copilot --fleet -p "Refactor the utils package and update everything that calls it" --allow-tool write

Reach for it when:

What you give up is predictability. Two runs of the same /fleet prompt can divide the work differently, launch a different number of subagents and spend a very different number of credits. GitHub’s docs warn that fleet “may cause more GitHub AI Credits to be consumed” and that inherently sequential work gets no benefit. The only ceiling is the per-session AI credit limit, covered in setting AI credit session limits in Copilot CLI and the SDK.

When a dynamic workflow is the right call

The moment you catch yourself pasting the same /fleet prompt a second time, write a workflow instead. The decomposition becomes code you can review, diff and version, and the agents get a narrower job each. A small sweep that finds call sites of a deprecated API, one agent per directory, looks like this:

// .github/extensions/api-sweep/extension.mjs
// Copilot CLI 1.0.103, @github/copilot-sdk resolved by the CLI (not installed)
import { readdir } from "node:fs/promises";
import { defineWorkflow, joinSession } from "@github/copilot-sdk/extension";

const HITS = {
  type: "object",
  required: ["hits"],
  properties: {
    hits: {
      type: "array",
      items: {
        type: "object",
        required: ["file", "line", "replacement"],
        properties: {
          file: { type: "string" },
          line: { type: "integer" },
          replacement: { type: "string" },
        },
      },
    },
  },
};

const sweep = defineWorkflow({
  meta: {
    name: "api-sweep",
    description: "Find calls to a deprecated API per top-level directory. args: { api: string }",
    argsSchema: { type: "object", required: ["api"], properties: { api: { type: "string" } } },
    limits: { maxConcurrentSubagents: 4, maxTotalSubagents: 40, timeoutSeconds: 1800 },
  },
  run: async (ctx) => {
    const { api } = ctx.args;
    // Deterministic step, journaled so a resume does not redo it.
    const dirs = await ctx.step("dirs-v1", async () =>
      (await readdir("src", { withFileTypes: true }))
        .filter((d) => d.isDirectory())
        .map((d) => `src/${d.name}`),
    );
    const results = await ctx.parallel(
      dirs.map((dir) => () =>
        ctx.agent(
          `List every call to ${api} under ${dir} and the replacement it needs. ` +
          `Return an empty list if there are none.`,
          { label: `sweep:${dir}`, schema: HITS },
        ),
      ),
    );
    // ctx.agent resolves to null on ordinary failure instead of throwing.
    return { hits: results.filter(Boolean).flatMap((r) => r.hits) };
  },
});

await joinSession({ workflows: [sweep] });

Run it by name from a session (“run the api-sweep dynamic workflow for HttpClientFactory.Legacy”) or straight from the shell:

# Copilot CLI 1.0.103; global flags go before the subcommand
copilot --experimental --allow-tool=read workflow run api-sweep \
  --args '{"api":"HttpClientFactory.Legacy"}' --result-file hits.json

Pick a dynamic workflow when:

The costs are real but small: you write and maintain code, the API is @experimental, and workflow execution needs token-based billing. Copilot Pro and Pro+ subscribers still on legacy annual request-based plans cannot run workflows at all. The full CI walkthrough, including exit codes, the --experimental flag and project-extension trust, is in how to define a Copilot CLI dynamic workflow and run it from CI.

When GitHub Agentic Workflows is the right call

A dynamic workflow in a raw copilot workflow run step has one weakness: everything around it is your responsibility. The step holds whatever token you gave it, the network is open, and extension.mjs runs with the runner’s full rights. That is fine for a maintainer-triggered job. It is not fine for a job that reads an issue body written by a stranger.

gh-aw fixes exactly that layer, and since v0.91.0 (PR #65937) it can host a dynamic workflow inside its agent job. It is off by default, and with current CLI releases you need both settings, per the gh-aw Copilot engine reference:

---
# .github/workflows/weekly-api-sweep.md
# gh-aw v0.93.6, compile with `gh aw compile weekly-api-sweep`
on:
  schedule: weekly on monday
permissions:
  contents: read
  copilot-requests: write
engine:
  id: copilot
  dynamic-workflows: true
  args: ["--experimental"]
timeout-minutes: 30
safe-outputs:
  create-issue:
    labels: [tech-debt]
    max: 1
---

# Weekly deprecated API sweep

Run the registered dynamic workflow `api-sweep` once with arguments
`{"api":"HttpClientFactory.Legacy"}`. Use the dynamic workflow run tool,
not a task agent or a newly authored workflow.

If it returns any hits, open one issue listing each file, line and
replacement. If it returns none, do nothing.

gh-aw then turns on the CLI’s EXTENSIONS feature, loads project extensions and pre-approves workflow runs. The agent job stays read-only. The issue is created by a separate job that checks the create-issue request against max: 1 and the label list. The compiler also restores .github/ from a trusted activation snapshot after checkout, so a pull request cannot swap in its own extension.mjs and have the agent run it. That last point is the one you would most likely get wrong in a hand-written workflow.

Pick gh-aw when:

The gotchas that make the choice for you

A few constraints override preference entirely:

Which one I would reach for

Default to a dynamic workflow. It is the only option of the three that is both repeatable and portable: the same extension.mjs runs from your terminal, the Copilot app, an SDK host and a CI step. Use /fleet as a sketchpad for one-off parallel work, and treat a fleet prompt you keep reusing as a draft of a workflow you have not written yet. You can even ask Copilot to author the workflow from the plan that fleet just executed. Then, as soon as that workflow needs to run on repository events or on a schedule, wrap it in a gh-aw Markdown file rather than a raw Actions step, because the read-only agent job and the trusted .github/ restore are exactly the parts nobody remembers to hand-write.

This is the same split as the difference between an AI agent and an AI workflow: /fleet is an agent deciding how to delegate, a dynamic workflow is a workflow that calls agents, and gh-aw is the sandbox you run that workflow in. Claude Code users will find the middle option familiar from Claude Code’s dynamic workflows, where the orchestration also lives in a script.

Sources

Comments

Sign in with GitHub to comment. Reactions and replies thread back to the comments repo.

< Back