The Armory
Every agent on every ship wants the same things: the skills you have written, the provider config you have settled on, the dotfiles you cannot work without. The Armory is where you keep them once. It is a single directory on the bridge host; every ship pulls it and installs it, and re-installs it whenever it changes.
It is deliberately one-way. You edit the armory on the bridge — by hand, or by pointing a git checkout at it — and ships converge on what you wrote. Nothing in Fleet writes to it, and there is no upload, edit, or delete affordance in the CLI or the GUI.
Layout
Section titled “Layout”The armory lives at <bridge dataDirectory>/armory/. With the default
fleet bridge -d ./.fleet-bridge, that is ./.fleet-bridge/armory/.
armory/ skills/ reviewer/ SKILL.md checklist.md plugins/ claude-code/ commands/ lint.md opencode/ plugins/ notify.js dotfiles/ gitconfig nvim/ init.lua dotfile-map.jsonOnly skills/, plugins/, and dotfiles/ are scanned. Anything else at the
armory root is ignored, except dotfile-map.json, which is read as
configuration rather than content.
A ship’s copy is not the armory — it is a cache at
~/.config/autosmith/fleet-ship/armory/files/, mirroring the tree above. Do not
edit it; the next sync overwrites it.
How a change reaches a ship
Section titled “How a change reaches a ship”- The bridge watches the armory directory recursively and collapses a burst of
writes (a
git pull, say) into one event. - It re-scans the tree into a manifest and pushes
POST /armory/syncto every online ship, carrying its own URL and the new revision. - Each ship pulls the manifest from
GET /armory, then fetches only the files whose hash it does not already hold, verifying every one against the manifest’ssha256before it lands. This is the one hop that runs ship-to-bridge, and the ship authenticates it with its ownshipToken— see authentication. - It installs the cache into place and records what it did.
The revision is a content address: it changes when and only when a file’s contents, mode, or path changes, or the dotfile map changes. Two scans of an unchanged armory produce the same revision, so a re-push costs a ship nothing.
The bridge also pushes on ship registration and on every arrival at online, so a ship that was down, restarted, or newly added catches up on its own. There is no polling and no schedule — if nothing changes, nothing happens.
A pull is all-or-nothing. One file that fails its hash check, or that the bridge will not serve, fails the whole sync: the ship keeps the revision it already had and records the reason rather than applying half an armory.
Which bridge a ship pulls from
Section titled “Which bridge a ship pulls from”The push names the bridge to pull from, and what is pulled gets installed into
the agent config of whoever runs the ship. So a ship accepts pushes from one
bridge and refuses the rest with 403, having fetched nothing:
fleet ship --bridge-url <url>pins it explicitly.fleet launchsets this for every ship it spawns, frombridge.publicUrl.- Unset, the ship pins whichever bridge pushes to it first and holds that from
then on. The pin lives in
~/.config/autosmith/fleet-ship/armory/state.json.
The URL is compared as an origin — scheme, host, port, and path — so
http://Bridge:4800/ and http://bridge:4800 are the same bridge. Query strings
and case are ignored; a different port or host is a different bridge.
Skills fan out to every provider
Section titled “Skills fan out to every provider”skills/<name>/ is a standard skill directory — a SKILL.md plus whatever else
it needs. Each one is installed into every agent provider whose config directory
already exists on that ship:
| Provider | Skills directory |
|---|---|
claude-code |
~/.claude/skills/<name>/ |
opencode |
~/.config/opencode/skills/<name>/ |
copilot |
~/.copilot/skills/<name>/ |
codex |
~/.codex/skills/<name>/ and ~/.agents/skills/<name>/ |
So skills/reviewer/SKILL.md on a host with all four providers becomes five
files, reviewer/SKILL.md under each root above. Fleet never creates a config
root — a provider you have not installed is skipped, not conjured.
Codex gets the skill twice: in its own directory and in the shared ~/.agents
location. That mirrors what a ship already does for its built-in fleet-agent
skill.
Plugins pass straight through
Section titled “Plugins pass straight through”Skills fan out because every provider understands the same skill format. Nothing
else does, so plugins/ does not try. The path after the provider name is used
verbatim, relative to that provider’s config root:
| Armory path | Lands at |
|---|---|
plugins/claude-code/commands/lint.md |
~/.claude/commands/lint.md |
plugins/opencode/plugins/notify.js |
~/.config/opencode/plugins/notify.js |
plugins/codex/config.toml |
~/.codex/config.toml |
You control the layout, which means you can install anything a provider reads,
not just the shapes Fleet knows about. It also means the path is your
responsibility: Fleet does not validate that ~/.claude/commands/ is a thing
claude-code reads.
The first segment must name a provider — claude-code, opencode, copilot,
or codex. Anything else is skipped with a warning:
ignored armory plugins/vscode: not a directory named after a known provider (claude-code, opencode, copilot, codex)A known provider that simply isn’t installed on that host is skipped silently. That is the normal case, not a problem: one armory serves hosts with different tools on them.
Dotfiles are symlinked
Section titled “Dotfiles are symlinked”dotfiles/ holds files and directories; dotfile-map.json says where each one
goes. Sources are relative to dotfiles/; destinations are ~/-rooted or
absolute:
{ "gitconfig": "~/.gitconfig", "nvim": "~/.config/nvim", "tmux.conf": "~/.tmux.conf"}Each mapping becomes a symlink at the destination pointing into the ship’s
armory cache. Nothing is copied. A directory source is one symlink to the whole
directory — nvim above produces a single ~/.config/nvim link, not a file per
entry — so adding a file to dotfiles/nvim/ on the bridge makes it visible on
every ship without re-linking anything.
Symlinks are why edits propagate at all. The trade-off is that a tool which rewrites its config in place is writing into the ship’s cache, and the next sync will overwrite it.
Conflicts
Section titled “Conflicts”A destination that already holds a real file, a real directory, or a symlink pointing anywhere other than the armory cache is a conflict. Fleet leaves it exactly as it is and reports the path:
orca: conflict: /home/you/.vimrcThe same rule covers skills and plugins: a file Fleet does not own, or one it owned and you have since edited, is preserved and reported rather than replaced.
The ship also says so on its own console at startup, once per conflicting path:
Fleet startup preserved a conflicting dotfile: /home/you/.vimrc. Move or delete it to let the armory's symlink take that path on the next sync or ship restart.A symlink that already points into the cache is not a conflict — Fleet made it, so it is re-pointed without ceremony when the source moves.
Removal never clobbers your work
Section titled “Removal never clobbers your work”Delete something from the armory and the ships uninstall it — but only where Fleet can still prove the file is the one it wrote.
For skills and plugins, proof is a content hash: Fleet records each installed
path’s sha256 and mode in ~/.config/autosmith/fleet-ship/managed-files-v1.json
and re-checks both immediately before unlinking. Edit an installed file and it no
longer matches, so it stays:
warning: left /home/you/.claude/skills/reviewer/SKILL.md in place: it no longer matches what Fleet installed thereFor dotfiles, proof is the link itself: the target must still be a symlink pointing into the ship’s armory cache. A target you have replaced with a real file, or re-pointed elsewhere, is left alone and reported the same way.
Empty directories left behind by a removal are pruned, but never above the provider’s own root.
Check what happened
Section titled “Check what happened”From the CLI, against the bridge:
fleet client armory lsfleet client armory cat skills/reviewer/SKILL.mdfleet client armory shipsls lists what the bridge holds and the revision it is serving; cat prints one
file; ships is the one that answers “did it land?”:
SHIP STATUS REVISION SYNCED STATEorca online 59a0c6b293b4 2026-07-27T14:51:20.318Z in sync
orca: conflict: /home/you/.vimrc warning: skipped dotfile bashrc: destination "/etc/bashrc" is outside /home/youSTATE is in sync when the ship holds the bridge’s current revision, behind
when it holds an older one, never when it has never synced, error when its
last attempt failed, and unknown when the bridge could not reach it at all.
error outranks the revision comparison: a ship whose sync failed is stuck on a
revision it could not replace, and that is more useful to know than “behind”.
The same view is in the GUI’s Armory page, which adds a file viewer and the dotfile map. See the CLI reference for the full flag list.
Troubleshooting
Section titled “Troubleshooting”Nothing syncs at all, and no ship reports an error. The bridge tells each
ship where to pull from, using bridge.publicUrl (or
fleet bridge --public-url). Unset, it defaults to http://localhost:<port> —
which on another host means that host. Set it to a URL your ships can reach. See
multi-host.
One ship reports bridge answered 401 for the armory manifest. That ship has
no shipToken, so its pull reached the bridge with no credential. Restart it with
FLEET_SHIP_TOKEN set to the shipToken the bridge was given for it, and check
the two match. Nothing else about the ship is affected — its workspaces keep
working, which is why this is easy to miss until you look at
fleet client armory ships. fleet launch supplies the token to every
source: local ship it spawns, so this only affects ships you start yourself.
See authentication.
fleet client armory ls returns a 400 naming dotfile-map.json. A malformed
map fails the whole manifest, so nothing is served and nothing is pushed — ships
keep the last good revision. Every bad entry is listed at once, keyed by source:
fleet: request failed (400): invalid /srv/.fleet-bridge/armory/dotfile-map.json: "vimrc": destination "relative/path" must start with "~/" or be absolute "../evil": source "../evil" must be a relative path under dotfiles/ with no "..", "." or "\" segmentsSources must be relative paths under dotfiles/ with no . or .. segments;
destinations must start with ~/ or be absolute. A missing dotfile-map.json is
not an error — it means nothing is linked.
A dotfile is reported as skipped rather than linked. Destinations are confined to the ship’s home directory:
warning: skipped dotfile bashrc: destination "/etc/bashrc" is outside /home/youThe mapping is dropped, not attempted. Use a destination under the ship user’s home.
A ship answers the push with 403 and never syncs. It is pinned to a
different bridge, so it refused the push without fetching anything:
fleet-bridge: could not push the armory to ship "orca": armory push refused: this ship is pinned to bridge http://10.0.0.2:4800 but the push named http://10.0.0.9:4800; the pin is this ship's configured --bridge-urlThe message names both URLs and where the pin came from. If the push is the
legitimate one, the two are out of step — usually bridge.publicUrl (or
fleet bridge --public-url) changed after the ship was pinned. Fix it by making
them agree: restart the ship with a matching --bridge-url, or, for a ship
pinned by first use rather than configuration, delete
~/.config/autosmith/fleet-ship/armory/state.json on that ship and let the next
push re-pin it. If the push is not one you sent, something else on the network
is pushing at your ships; see the caution above.
A large file breaks the sync. The bridge refuses to serve any single file
over 10 MiB. It still appears in the manifest, but fetching it answers 413,
which fails that ship’s whole sync and shows up as error in
fleet client armory ships. Keep binaries out of the armory.
Related
Section titled “Related”- Agent integrations — the built-in
fleet-agentskill, which is Fleet’s own and separate from anything you put here. - Running across several machines — why a multi-host fleet
needs
bridge.publicUrl. - Bridge API and ship API — the routes behind all of this.