Resolving Agents
Every agent reference is a string, and one resolver turns it into a folder on disk.
await Fleet({
barry: "../barry", // a path, relative to the script
dave: "~/agents/dave", // a path, absolute
scout: "scout", // a name — resolved from your watch paths
zeno: "@axon/zeno", // a registry package
})
Whatever the form, the result is the same: a directory containing an agent, which
Axon() boots. Nothing else about the runtime varies by where the agent came from.
The resolution order
A reference containing a / or starting with ., ~, or / is a path. It is
resolved relative to the script and used directly. If nothing is there, it throws — a
path is a statement about where the agent is, so a missing one is a mistake, not an
invitation to go looking.
Anything else is a name, resolved in order:
- Watch paths. Directories you have registered with
axon watchare scanned for an agent of that name. This is where your own agents live, so your own work always wins. - The local install directory. Agents already fetched from the registry are cached machine-wide. A second script asking for the same agent gets the cached copy.
- The registry. Not found locally, so it is fetched, installed to the cache, and booted from there.
The first match wins and resolution stops. An agent named zeno in a watch path shadows
@axon/zeno from the registry, deliberately — a local copy is the one you are working
on.
Watch paths
axon watch tells the CLI where you keep agents:
axon watch ~/agents
axon watch ~/work/team-agents
Every agent folder under a watched directory becomes addressable by its bare name from any script on the machine:
const { barry, scout } = await Fleet({ barry: "barry", scout: "scout" })
This is what makes short fleet scripts practical. Without watch paths, every reference is a relative path that breaks the moment you move the file.
Run axon watch with no argument to list what is registered, and
axon unwatch to remove one.
Registry references
A scoped name is a registry package:
zeno: "@axon/zeno"
The first script that asks for it fetches and installs it. That run is slower — a network fetch and an install, seconds rather than milliseconds. Every run after that reads the cache and boots at local speed.
Pin a version when you need the agent to stay put:
zeno: "@axon/zeno@1.4.0"
Unpinned resolves to the latest version already installed, and fetches the latest published one if none is. Pin anything whose behaviour you are relying on; a fleet script that produces different output next week because an agent moved underneath it is an unpleasant surprise.
Installing ahead of time
You do not have to let the first run pay for the fetch:
axon install @axon/zeno
This resolves and caches the agent without booting it — useful in CI, or before a demo, or any time you would rather not discover the network is down at the moment your script runs.
When resolution fails
The failure is loud and names what it tried. A path that does not exist, a name found in no watch path and no registry, a pinned version that was never published — each throws before anything boots, so a fleet either comes up whole or not at all.
A partially-booted fleet would be the worse outcome: a script holding two working agents
and one broken one, discovering the third only when it calls it. Fleet() resolves
everything first.
Next: Lifecycle — boot, shutdown, and failure.