Fleets

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

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:

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

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

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:

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

Confirm it registered:

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 and react to messages:

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.