fleet — AgentTeamsModule¶
Safely run --update --merge across every agent-infrastructure workspace under a parent directory (and its subfolders) in one command — covering both .github/agents/ (copilot-vscode) and .claude/ infrastructures — with a git-commit snapshot before each apply and a git diff content audit after.
Source:
agentteams/fleet.py
Command-line usage¶
The fleet runner is driven entirely from the CLI via the --fleet option (there is no separate executable):
# Dry-run preview — discovers workspaces and reports what WOULD change. No writes.
agentteams --fleet /path/to/parent --update --merge
# Apply — snapshot each git workspace, run the merge, then analyse the diff.
agentteams --fleet /path/to/parent --update --merge --yes
# Restrict to one infrastructure, or change the report location.
agentteams --fleet /path/to/parent --update --merge --yes --fleet-frameworks github
agentteams --fleet /path/to/parent --update --merge --yes --fleet-report /tmp/fleet-out
--fleet requires --update and --merge. The default (without --yes) is a dry-run preview; pass --yes to apply. See the CLI Reference → Fleet Update for the option table and the full exclusivity rules.
What one run does, per discovered workspace¶
- Discover — find directories containing
.github/agents/and/or.claude/, pruningnode_modules,.git,.agentteams-backups,__pycache__,.venv,venv,.goose,tmp,.worktrees, andarchive, and never recursing into.github/.claudeinternals. - Snapshot (git commit) — before applying, commit the workspace's agent-infra state as
chore(fleet): pre-update snapshot(or stay atHEADwhen already clean). This is the recoverable rollback point and the diff base. Non-git workspaces fall back to the automatic<output_dir>/.agentteams-backups/<ts>/snapshot. - Update in-process — re-enter the standard update path per target (
--update --mergefor.github/agents/and native.claude/agents/,.goose/recipes/and.codex/agents/teams, each with--project <workspace>so the project scan runs;--bridge-mergefor bridge consumers). A bridge refreshes from the source framework its entry file records inside theAGENTTEAMS-BRIDGEfence ("Use source frameworkgoose…":.goose/recipes;claude:.claude/agents; otherwise.github/agents). A recorded source fleet can't use is aSKIP, never a re-bridge from.github/agents. No subprocess is spawned, so a successful merge is never misreported because of an interpreter/exit-code quirk, and a failure in one target is isolated. - Diff analysis — after applying,
git diff <snapshot>is classified by the authoritative content signals — shrink Notices and deletions insideUSER-EDITABLEregions — not the process exit code. AUSER-EDITABLEregion opens only at a real marker (a blockquote notice naming it, such as## Project-Specific Notes'> ⚙️ **USER-EDITABLE**or the orchestrator's project-rules notice; a heading naming it; or an explicitBEGIN USER-EDITABLEcomment). It runs to the next real<!-- AGENTTEAMS:BEGIN … -->fence, an end marker, or the end of the file, so a user's own##heading inside it stays covered. A fenced line is never user-editable, and prose that merely mentions the phrase (or quotes a fence marker) opens nothing. Per-workspace.difffiles plusreport.jsonandsummary.mdare written under<DIR>/.agentteams-fleet/<run-id>/.
Per-target status¶
Each (workspace, target) row is one of:
| Status | Meaning |
|---|---|
OK |
Merge succeeded; only fenced/generated regeneration and intel churn. |
REVIEW |
A shrink Notice or a USER-EDITABLE-region deletion was detected — inspect the saved diff before committing. Also a kept pre-fencing twin, a dropped twin that retired a rule, a duplicate section (duplicates: a heading present twice, including both copies inside fences; whole_body_migration), or a fence-twin acknowledgement that no earlier applied run has shown (see below). Twin and duplicate classes are matched at the start of the notice, so a quoted heading cannot reclassify one. |
FAIL |
The merge itself errored (e.g. invalid/absent descriptor, bridge source missing). |
SKIP |
Ambiguous .claude/ (no bridge signal and no resolvable descriptor), left for manual review. Also a bridge whose recorded source framework fleet can't bridge from: an unknown framework, a missing source directory, an unreadable entry file, or a source equal to the bridge's own framework. Also used for an interop-projected .codex/agents team (origin: "interop" marker): the row's detail carries the refresh command (agentteams --interop-from <source> --framework codex --output . --overwrite), which fleet never runs. |
WOULD-UPDATE |
Dry-run preview only. |
Note on the
targetfield when parsingreport.json: updated rows use one oftarget = "github","claude-direct","claude-bridge","goose-direct","goose-bridge", or"codex-direct"(a native.codex/agentsteam,--update --merge --framework codex,allonly). ASKIProw for an ambiguous.claude/carriestarget = "claude"(it could not be classified); an interop-projected Codex team'sSKIProw carriestarget = "codex". Filter for these explicitly so manual-review rows are not dropped.
Fence-twin acknowledgements are reviewed once¶
A row in a team's references/fence-twin-acks.csv (fence_twin_acks) turns a kept
twin's REVIEW into a twin kept (acknowledged) notice. Nothing ties the row to the operator, so fleet
(agentteams/_fleet_twin_acks.py, integrity-pinned) keeps it a REVIEW reason until an applied run has shown
it. Each applied run writes the acknowledged-twin notices it showed, per workspace and target, to
<run dir>/fence-twin-acks-seen.json beside report.json. The next applied run reads the record of the
latest earlier run directory that has one. A notice missing from it (a new row, a changed reason or digest,
a row removed and restored) is listed in the target's twins_ack_unreviewed and makes the row REVIEW:
"fence-twin acknowledgement new or changed since the last fleet run (N) — needs operator review".
The key is the sha256 of the whole notice line, so only an identical acknowledgement counts as seen.
A new agentteams release that rewords a kept-twin reason re-flags those acknowledgements once (safe).
It fails safe: a missing, unreadable or malformed record, or a --fleet-report directory with no
earlier run, flags every acknowledgement. So does a record that is, or sits in, a symlink, one whose
location git cannot check, or one inside a git work tree: when the fleet parent is itself a repository, its agents could plant a record in the
default report dir, so pass --fleet-report outside every repository. A target with unflagged
acknowledgements is recorded only when its row is REVIEW with no fence imbalance and that reason in
its detail. A FAIL, a fence-imbalance REVIEW or a target that did not run keeps its old record, so a
flag that was not shown is shown next time. Dry runs neither read nor write the record.
What it does not prove. The record means "fleet showed this to the operator", not "the operator
approved it": the flag is shown once, and an operator who rejects the acknowledgement deletes the row.
An agent that can run agentteams --fleet --yes itself, or write the operator's fleet output directory,
can still clear the flag; tying a row to the operator would take an operator signature. A non-git
workspace is not classified at all, so neither this check nor the kept-twin one applies there.
Safety guarantees¶
Fleet mode is non-destructive by construction: it is merge-only, and --overwrite, --prune, --migrate, --bridge-refresh, --shrink-policy=allow, and every single-target flag (--description, --project, --output, --self, …) are rejected — see the full exclusivity list. .claude/ is only ever bridge-merged, never bridge-refreshed. Descriptor resolution prefers .agentteams/brief.json over the thin _build-description.json stub. The exit code is 1 only when a target genuinely FAILed; a REVIEW exits 0 with a warning so the report can be inspected.
Classes¶
TargetResult¶
Source:
agentteams/fleet.py
Outcome of updating one target ("github", "claude-direct", "claude-bridge", "goose-direct", or "goose-bridge") within a workspace.
Attributes:
workspace(str) — Absolute path of the workspace.target(str) — Which infrastructure was updated.status(str) —OK|REVIEW|FAIL|SKIP|WOULD-UPDATE.detail(str) — Human-readable explanation (e.g. the first error, or the review reason).descriptor(str) — The descriptor file used.files_changed,added_lines,removed_lines(int) — Diff magnitude vs the snapshot.shrink_notices(list[str]) — Shrink-guard Notices emitted during the merge.user_editable_deletions(list[str]) — Deleted lines that fell inside aUSER-EDITABLEregion (the real content-loss signal).shrink_grant_warnings(list[str]) — The run'sWARNING: --shrink-allowlines: a grant skipped as unappliable, refused for want of a backup dir, or one that released nothing. Recorded on every path, dry runs included.summary.mdcounts them per target and adds a Shrink grants section: skipped or refused grants per target, and grants that released nothing on any target that announced them (an exportedAGENTTEAMS_SHRINK_ALLOWis read by every target, so a grant meant for one workspace is not reported as unused because the others ignored it). A skipped entry is echoed as typed, so whatever is inAGENTTEAMS_SHRINK_ALLOWlands in the report as well as on stderr: keep only grant entries in it.shrink_grants_active(list[str]) — Grant entries the run announced as active (the input to that cross-target check).rc(int | None) — Raw return code of the in-process update (diagnostic only — status is derived from content, not this).
WorkspaceResult¶
Source:
agentteams/fleet.py
Aggregate result for one workspace.
Attributes:
path(str) — Absolute path of the workspace.is_git(bool) — Whether the workspace is a git repository.snapshot_ref(str | None) — The snapshot commit/HEAD used as the diff base and rollback point.snapshot_committed(bool) —Trueif a snapshot commit was created (workspace was dirty),FalseifHEADwas already clean.diff_file(str | None) — Path to the persisted per-workspace.diff.targets(list[TargetResult]) — One entry per updated target.
Functions¶
discover_workspaces(parent, frameworks="both", *, skipped_worktrees=None)¶
Source:
agentteams/fleet.py
Return the sorted list of workspace directories under parent (recursively) that contain .github/agents/ and/or .claude/ (and, for goose/all, a Goose-bridged workspace). frameworks ("github" | "claude" | "goose" | "both" | "all") filters which infrastructure qualifies a directory: "both" is the legacy default (copilot + claude only); "all" adds Goose and Codex. A Codex team qualifies only with a regular, non-symlink .codex/agents/references/build-log.json marker, native or origin: "interop". Prunes node_modules, .git, .agentteams-backups, __pycache__, .venv, venv, .goose, .codex, tmp, .worktrees, archive, and the fleet's own .agentteams-fleet output/backup tree, never recurses into .github/.claude internals, and skips git linked worktrees (and submodules) — their agent infra is covered via the main working tree. If skipped_worktrees (a keyword-only list[Path]) is passed, each excluded linked worktree that carries agent infra is appended to it so the caller can surface the exclusion as a visible report row.
Returns: list[Path]
run_fleet(args, parser)¶
Source:
agentteams/fleet.py
Entry point dispatched from build_team.main() when --fleet is set. Discovers workspaces, snapshots and updates each target in-process, classifies the resulting diff, writes the report under <DIR>/.agentteams-fleet/<run-id>/, and returns an exit code (1 if any target FAILed, else 0).
Parameters:
args(argparse.Namespace) — Parsed CLI arguments (readsfleet,fleet_frameworks,fleet_report,yes,dry_run,shrink_policy,fleet_allow_no_verify).parser(argparse.ArgumentParser) — The CLI parser (reserved for error reporting).
Returns: int — Process exit code.
Rollback¶
The pre-update snapshot is the recoverable point. To undo a workspace's update:
Non-git workspaces restore from their <output_dir>/.agentteams-backups/<ts>/ snapshot (see emit.restore_backup).
See also¶
update_report— the durable per-run record each in-process--updatetarget leaves.drift— the template-to-instance drift detection the per-target updates rely on.- Cross-Repository Coordination Guide — coordinating agent-infrastructure changes across many workspaces.
- Update Lifecycle Guide — where a fleet run sits in the broader update lifecycle.