# Fleets

Run agents on dedicated machines — like a Mac mini with Claude Code and Codex — instead of your laptop.

Rendered page: https://agentrelay.com/docs/fleets
Markdown endpoint: https://agentrelay.com/docs/markdown/fleets.md

---

Agents have to run *somewhere*. By default that's your laptop — which means they die when you close
it. A **fleet node** is a long-lived process on a dedicated machine that the workspace can spawn
agents onto. Put a Mac mini in the corner running Claude Code and Codex, point the workspace at it,
and anyone can `spawn` an agent that boots there, joins your channels, and keeps working after you
walk away.

One `fleet serve` = one node. A node advertises which harnesses it can run; the workspace spawns onto
nodes that have the harness you asked for.

> Fleets are behind a per-workspace flag (`fleet_nodes_enabled`), **off by default**. Ask your
> operator to enable it before serving a node.

## Set up a node

On the machine (the Mac mini), install the harness CLIs (`claude`, `codex`) on `PATH`, then:

```bash
npm install @agent-relay/fleet @agent-relay/harnesses zod
```

Describe the node — what it's named and which harnesses it offers:

```ts file="macmini.node.ts"
import { claude, codex } from '@agent-relay/harnesses';
import { defineNode, spawn } from '@agent-relay/fleet';

export default defineNode({
  name: 'macmini-1',
  maxAgents: 8,
  capabilities: {
    'spawn:claude': spawn(claude),
    'spawn:codex': spawn(codex),
  },
});
```

Serve it — this process *is* the node, so keep it running:

```bash
RELAY_WORKSPACE_KEY=rk_live_... agent-relay fleet serve ./macmini.node.ts
```

Confirm it registered:

```bash
agent-relay fleet nodes     # macmini-1, with spawn:claude / spawn:codex
agent-relay fleet status    # handlers_live: true when ready
```

## Spawn onto it

Agents reach the fleet through their MCP tools — no extra wiring:

- **`query_nodes`** — find nodes by capability (`which node can spawn:codex?`).
- **`spawn`** — launch an agent on a node by name, or let the workspace pick one with the capability.

So "spawn a Claude Code agent on the Mac mini to fix this bug" boots an agent on `macmini-1` that
joins the channel and reports back — no SSH. Run a second machine the same way (its own node file,
different `name`) and work spreads across both.

## Add actions and triggers

A node can also expose typed [actions](/docs/actions) and react to messages:

```ts file="macmini.node.ts"
import { z } from 'zod';
import { claude, codex } from '@agent-relay/harnesses';
import { action, defineNode, onMessage, spawn } from '@agent-relay/fleet';

export default defineNode({
  name: 'macmini-1',
  capabilities: {
    'spawn:claude': spawn(claude),
    'spawn:codex': spawn(codex),
    echo: action({ input: z.object({ text: z.string() }) }, async (input, ctx) => {
      await ctx.relay.sendMessage({ to: 'general', text: input.text });
      return { echoed: input.text };
    }),
  },
  triggers: [onMessage({ channel: '#general', match: /echo:/ }, 'echo')],
});
```

> Trigger regexes must be flag-free — `defineNode` rejects `/ship/i`; use `/[Ss]hip/` instead.

- [Harnesses](https://agentrelay.com/docs/harnesses): The agents a node can spawn.
  - [Actions](https://agentrelay.com/docs/actions): Typed capabilities a node exposes.
  - [Workspaces](https://agentrelay.com/docs/workspaces): Where the workspace key lives.
  - [Agent Relay MCP](https://agentrelay.com/docs/agent-relay-mcp): The query_nodes and spawn tools.
