Machines
You’ll add workers, the machines that run agents, and keep them healthy. The hub’s owners and admins add, repair and remove them in the app, and log in their Claude from it too. The CLI does the same from a terminal on the hub.
A worker needs the claude CLI, and Linux on x64 or macOS (Apple silicon or Intel). fleetd has no Linux ARM build
yet.
Add a machine in the app
Section titled “Add a machine in the app”Open Settings › Hub › Machines. Add machine at the top offers three ways:
| The machine is | Pick | What happens |
|---|---|---|
| The computer the app runs on | This computer | One click. The app downloads the release of the fleetd version the hub runs, built for this computer, checks it against that release’s SHA256SUMS, and joins the hub with a one-time code that never shows on screen. The node runs as a user service. An Edge hub asks to be updated first once the edge release has moved past it. On the machine that runs the hub you’re connected to, the hub sets it up with its own fleetd instead. A computer that runs another hub, or whose node already belongs to a hub, is left as it is. |
| Reachable from the hub over SSH | A machine the hub can reach | Pick it from the hosts the hub sees (its ~/.ssh/config and its tailnet), and read the plan: the machine’s OS and CPU, each step with its command, and notes such as linger. Press Add, and watch each step run. |
| Behind NAT, or with no SSH from the hub (a laptop) | Any other machine | Give it a name and press Make the line. Copy the one line it shows and run it in a terminal on that machine: it installs fleetd and joins with a one-time code. The code works once, for 15 minutes. Under the line, “Waiting for laptop…” turns into “laptop joined.” once its node connects; Open laptop shows its checklist. |
Adding a machine the hub can reach runs fleetd machine add in the hub, step by step. A failed step shows
its output and what to fix; Retry runs it again and skips the steps that finished. The run goes on in the hub if
you leave the page, and Add machine shows it when you come back.
Only a person with Admin on every machine sees Settings’ Hub group. Agents’ keys can’t add or remove machines.
A machine id belongs to one computer. The hub knows each computer by a hash of the id its OS gives it
(/etc/machine-id on Linux, IOPlatformUUID on macOS), so it never enrolls a second computer under an id that is
already another’s, whatever the computer is called. To give an id to another computer, remove the machine
first. The app knows the hub’s own computer the same way, wherever the hub keeps its data.
Repair or remove a machine
Section titled “Repair or remove a machine”Under Add machine, This hub’s machines lists every machine with its state, each with Repair and Remove.
- Repair on a machine the hub reaches over SSH (one it set up, its own, or one in its
~/.ssh/configor tailnet) opens its plan in Add machine: adding it again repairs it and skips what is in place. On a machine that joined by itself, Repair makes a line that enrolls it again (--rejoin): run it on that machine. - Remove asks first, then does what
fleetd machine removedoes: on a machine the hub set up, it stops and removes the node’s service and revokes its key; on one that joined by itself, it only revokes the key, so stop its node there by hand.
A machine’s checklist
Section titled “A machine’s checklist”A machine’s name opens its page, with what it needs to run agents, as the hub’s own checks last found it. Check again runs them now.
| Item | Done when |
|---|---|
| Claude | claude is installed at the version fleetd was tested with, and logged in. |
| Git identity | The node’s commits carry the hub’s identity. |
| GitHub access | git could fetch each project’s remote at the last agent start there. It waits until an agent has started in one of its projects. |
| Capabilities | What screens it hosts besides browsers: Android, iOS, Linux desktop. Each one that’s set up but not ready says why. |
An item that wants a look shows its fix: a command to copy, or for Claude’s login, Log in….
Log in Claude from the app
Section titled “Log in Claude from the app”Log in… on the Claude item has the machine’s node run claude auth login, with the same environment as its
sessions, so the login lands where they read it.
- Press Open, or copy the page’s address, and sign in with the Claude account the machine should use. Any device works.
- Paste the code the page shows, and press Log in.
Only that code passes through the hub, once, to the node. claude keeps the login it gets on the machine; the token
never reaches the hub, the app or any agent’s transcript. A login nobody finishes ends after 10 minutes. A wrong or
used code ends it too: press Try again for a new page. The machine needs a fleetd recent enough to do this; with an
older one, log in on it by hand.
Add a machine from the CLI
Section titled “Add a machine from the CLI”Run these on the hub unless it says otherwise.
| The machine is | Use |
|---|---|
| Reachable from the hub over SSH | fleetd machine add <ssh-host> |
| The hub machine itself | fleetd machine add --local |
| Behind NAT, or has no SSH from the hub (a laptop) | fleetd pair --worker <name> |
Over SSH
Section titled “Over SSH”-
See the plan without changing anything:
$ fleetd machine add builder-1 --dry-rundry run: nothing runs. Planned for a fresh Linux machine at builder-1 (home /home/demo); a real run first reads the machine's state and leaves out what is already done:[probe] read the machine's state (read-only)[1/5] create fleetd's directories[2/5] install fleetd 0.0.0[3/5] write the node service[4/5] enroll the node with a one-time token (sent on stdin)[5/5] (re)start the node[hub] wait until builder-1 is online[exit 0] -
Run it for real:
fleetd machine add builder-1It copies the hub’s own fleetd to the machine, enrolls its node, runs the node as a user service, and waits until the machine is online. It prints each remote command before it runs it.
A machine of another OS or CPU than the hub’s (a Mac added from a Linux hub, say) gets the same fleetd version built for it: the hub downloads that release’s build and checks it against the release’s
SHA256SUMSfirst. A machine that has that build already is left as it is. A hub on an Edge build can do this only while the edge release is still its build; once it has moved on, the add stops and says to update the hub first.
The host name can be anything in ~/.ssh/config or on your tailnet. You can also run fleetd init again and pick the
machine from a list.
It is safe to run again: a real run reads the machine’s state first and skips what is done.
The hub machine
Section titled “The hub machine”fleetd machine add --localThe same steps, run on this machine without SSH. fleetd init does this when you pick “(this machine)”. Run it again
to repair the hub machine’s node.
A machine the hub can’t reach
Section titled “A machine the hub can’t reach”-
On the hub, print a join command for the new machine:
fleetd pair --worker laptopExpected output:
On laptop, run:curl -fsSL https://fleetd.sh | sh -s -- --join http://hub.your-tailnet.ts.net:4790 --code <code> --machine-id laptop --git-identity 'ada-lovelace <1234567+ada-lovelace@users.noreply.github.com>'The code works once, until 2026-10-18T13:35:46.366Z. laptop needs claude installed and must reach the hub at that address. -
Paste the printed command into a terminal on the new machine. It installs fleetd and joins the hub with that address and code, without asking for them.
The code works once, for 15 minutes. The new machine must reach the hub’s address.
To repair the worker later, run the same command again. --rejoin enrolls it again with a new code.
Log in Claude on a worker
Section titled “Log in Claude on a worker”Sessions use the worker’s own claude setup in ~/.claude. If claude works in your shell there, it works for
fleetd: your login, a proxy in ~/.claude/settings.json, and your MCP servers.
If it isn’t logged in, log it in from the app, or on the worker as you normally do:
claude auth loginBehind a proxy, log in through your proxy instead. To give a worker’s sessions their own claude profile, see The Claude login.
Claude Code’s version
Section titled “Claude Code’s version”Each fleetd release is tested with one Claude Code version. Updating fleetd moves every machine’s Claude Code to that version (see Update), and nothing moves it on its own:
- The node turns Claude Code’s own background update off when it starts, by setting
"DISABLE_AUTOUPDATER": "1"in theenvof thesettings.jsonits sessions read (~/.claude/settings.jsonunless the machine has its own profile). That covers aclaudeyou run yourself on that machine too.fleetd doctorwarns when it is on. - Sessions that are already running finish on the version they started with. New sessions start on the new one. Nothing waits for the machine to be idle.
- Only Claude Code’s native install (
~/.local/bin/claude) is moved. A Homebrew, npm or package install, or a machine without claude, is left as it is: the update says so in a calm line with the command that updates it (brew upgrade claude-code, say), not as a failure.
Remove a machine
Section titled “Remove a machine”In the app, press Remove beside it in Settings › Hub › Machines. From the CLI:
fleetd machine remove builder-1It stops and removes the node’s service on that machine, and revokes its key on the hub.
| Option | Also does |
|---|---|
--purge |
Deletes ~/.fleetd/node and ~/.fleetd/claude.env on that machine. Your own ~/.claude stays. |
For a machine that joined with fleetd pair --worker, only the key is revoked:
laptop was not set up by fleetd machine add from this hub, so only its key is revoked here; stop its node by hand.revoked laptop's node key; the hub refuses that node from now on.Stop its node on that machine yourself:
launchctl bootout gui/$(id -u)/sh.fleetd.nodesystemctl --user disable --now fleetd-node.serviceDisk, load, memory and usage
Section titled “Disk, load, memory and usage”A machine short of disk, CPU or memory, or whose Claude login is nearly out of a usage window, takes no new agents until it has room again. Agents already running keep going. That also means the guard can’t stop running agents from growing: it keeps new ones off a machine that is already short, it doesn’t shrink what runs. The node reads the figures every 15 seconds, with its heartbeat.
| New agents wait while | They start again at |
|---|---|
Free space where the worktrees live is below minFreeGb in node.json: 10% of the disk or 20 GB by default, whichever is larger. |
10% above it. |
| The 1-minute load is above twice the machine’s CPUs. | 10% below it. |
| 90% of the machine’s memory is in use. | 10% below it: 81%. |
| Swap is 90% used and memory 80% (Linux). Full swap alone doesn’t pause: idle pages can sit there for days. | 10% below either. |
Memory pressure: tasks stalled waiting for memory 10% of the last 10 seconds (Linux PSI some avg10). The kernel is reclaiming or swapping hard, which comes before the OOM killer. |
Below 5%: the figure swings a lot from one reading to the next. |
| A usage window of the machine’s Claude login is 90% used, or claude warned about it without a figure. API keys and gateways report no windows. | Below 85% or at the window’s reset. A reading with no reset time counts for 15 minutes. |
The project’s status says why, for example “Paused new agents: 14 GB free on builder-1” or “Paused new agents: 5-hour
limit 91% on builder-1 as of Tue 13:42 UTC, resets Tue 14:00 UTC (pauses at 90%, lifts below 85%)”, and fleetd doctor
shows each machine’s free space. A writer’s worktree, build output included, goes once its PR merges, unless it holds
uncommitted or unpushed work. A reader’s or reviewer’s goes when its task ends. When sccache is on the node’s PATH,
sessions compile through it, so a dependency builds once per machine rather than once per worktree.
When a Claude login hits its limit
Section titled “When a Claude login hits its limit”When agents on one machine hit their Claude login’s usage or rate limit, fleetd parks them and pauses new turns on that login. They go on by themselves at the reset claude’s message named, or as soon as a turn on that login completes. The project’s Status row and its chat say so, with Resume.
To go on sooner, once the login has capacity again:
- In the app, press Resume on the project’s Status row or on the chat’s blocked line. Your device key needs operate on the machine (a paired app has it).
- On the hub machine, run
fleetd usageto see each machine’s pause, andfleetd usage resume <machine>to lift one. Neither needs a key or--hub: they use the hub’s own CLI key and reach the hub at the address it listens on. From elsewhere, add--hub <url> --key-file <path>.
Resume sends every parked agent “continue”. An agent whose login is still limited parks again on its next turn, with
the real reset. Whenever the lead takes a turn during the pause (you message it, say), its wake states the pause,
when it ends and that you can press Resume. It can lift the pause on its own project’s machine with usage_resume, at
most once every 10 minutes, and that is recorded as the lead’s.
A fair share for each agent
Section titled “A fair share for each agent”Agents on one machine share its CPUs, memory and disk. Each session gets:
| What | How |
|---|---|
| Build jobs | Each agent’s share of the CPUs: the CPUs divided by the agents the machine fits, at least 2. fleetd sets CARGO_BUILD_JOBS, MAKEFLAGS=-j<n>, CMAKE_BUILD_PARALLEL_LEVEL and GOFLAGS=-p=<n>, except any you set in claude.env, and a task’s brief says what <n> is. |
| Scratch space | TMPDIR, TMP, TEMP and Claude Code’s CLAUDE_CODE_TMPDIR point at ~/.fleetd/node/tmp/<16 hex characters, one per session>, on disk. Never /tmp, which is often held in memory. It goes with the writer’s worktree; for a session without one, when the session is stopped or the node restarts. |
| Its own memory scope (Linux) | When the node can reach your systemd user manager, each session runs in its own transient scope (systemctl --user list-units 'run-*.scope', described as “fleetd agent …”). If memory runs out, the kernel’s kill lands in one agent: the build it killed fails, and the agent sees the error and goes on. The scopes and the node’s unit have OOMPolicy=continue, so an out-of-memory kill never fails an agent’s scope or the node, and stopping the node stops its agents’ scopes after its own shutdown. If a scope can’t be started, the session starts without one, and the node checks again 10 minutes later. Without a user manager (macOS, containers), sessions run as before. |
A node unit written before this has no OOMPolicy=continue; run fleetd machine add again on that machine to rewrite
it.
Machine roles
Section titled “Machine roles”Each machine runs agents, hosts screens (an agent’s browser, emulator, simulator or desktop),
or both, the default. An owner or admin sets it in Settings › Hub › Machines in the app, or with an admin key, where the screens role
is named sessions:
fleetd machine set mac1 --roles sessions --max-sessions 2fleetd machine set devbox --roles agents,sessions --max-sessions auto- Agents only. The machine hosts no screens. Its agents’ screens run on a machine that does.
- Screens only. The machine runs no agents: those running finish their work, and its projects’ agents wait until it runs agents again. It hosts screens for agents on every machine.
Screen slots are counted apart from agent slots. A machine hosts at most its screen cap at once: what it fits (one screen per two CPUs and about 2 GB of memory each, at most 16, and never fewer than its agent slots and leads), or the number you set, 1 to 64.
Each node reports what it can host, in plain words, and Settings › Hub › Machines shows it:
| Kind | Needs |
|---|---|
| Browser | Chrome: the node’s own, which it installs as it starts, or the one browser.chrome names in node.json. |
| Android emulator | fleetd’s own pinned emulator: Linux on x86_64, opted in with "device": {"enabled": true} in node.json (the first screen installs it) or installed already, and a /dev/kvm this user can open read-write. See Android screens. |
| iOS simulator | fleetd’s own golden simulator and its clones: macOS with Xcode, an iOS simulator runtime and Node.js, and opted in with "ios": {"enabled": true} in node.json (or the golden made already). While the node makes its golden (minutes), or after making it failed, the hub still sends it iOS screens: the node says it is still making it, and the next screen tries a failed one again. The doctor’s ios check says where it stands. See iOS screens. |
| Desktop | Linux with docker or Xvfb. |
When an agent starts a screen, the hub places it: on the agent’s own machine when it hosts screens, can run that kind and has a free screen slot; else on the online machine that can with the most free slots. When none can, the agent is told why in one line: none can host that kind, the ones that can are offline, or every one is full. The default cap is never lower than the machine’s agent slots and leads together, one screen each, as before screen caps.
A screen on another machine works as one on the agent’s own:
- Its secrets go sealed. Before it starts, the agent’s node seals the screen’s secrets (its project’s, its order’s and its MCP servers’) to the hosting machine’s key, as the app seals a secret to a machine. The hub passes the box on and can’t open it. The hosting machine scrubs the screen’s action log and page snapshots with them, as for a screen of its own, and forgets them once the screen has started.
- Its 1Password token stays home. A
fill_credentialis read from 1Password on the agent’s machine, with its token, and only the value goes to the hosting machine, sealed to it. That machine fills it into the item’s own sites only, refuses reads of it as for a fill of its own, and wipes it. The agent’s machine masks it in its agent’s reports. - Its relay is encrypted end to end. The agent’s
agent-browserreaches the browser through a relay on the hub. The agent’s node connects with a token only that node and the hub know (the hub keeps just its SHA-256). The two nodes encrypt every message with a key sealed along with the secrets, so the hub carries only ciphertext, and it logs none of it. - Its steps are recorded there. Each
agent-browsercommand that drove it goes into the hosting machine’s recording, with the page after it, masked on the agent’s machine first. - An iOS screen’s commands go sealed. The agent’s
agent-devicecommands, and the files they read and write, go through the hub sealed with the same key, and the Mac runs only agent-device commands that drive that screen (iOS screens from another machine). Android screens stay on their agent’s machine for now.
Only machines whose fleetd seals and opens these boxes take part. An agent on a machine with browser screens turned off can’t start one anywhere: it has no proxy to drive it through.
Check health
Section titled “Check health”fleetd doctorIt checks the hub and every machine: online, claude version and login, commit identity, disk space and the usage breaker. Each problem comes with the command that fixes it.
The app’s Machines screen shows the same checks, the ones that want a look first. Every finding and its fix: Troubleshooting.