@agent-relay/fleet
v14.0.0
Published
Fleet node SDK for Agent Relay.
Downloads
44,420
Readme
@agent-relay/fleet
Fleet node SDK for Agent Relay. Define a node — a named host that advertises typed
capabilities (actions and spawners) and reacts to channel messages via triggers — then
serve it with the agent-relay fleet CLI.
Use @agent-relay/fleet when you want to expose local capabilities (run a command, spawn a
harness, answer a request) to a relay workspace as a long-lived node. Use @agent-relay/sdk
for plain agent messaging and @agent-relay/harness-driver to start and supervise local
harness processes directly.
Full docs: agentrelay.com/docs.
Installation
npm install @agent-relay/fleet zodQuick start
import { defineNode, action, spawn, onMessage } from '@agent-relay/fleet';
import { z } from 'zod';
export default defineNode({
name: 'builder',
capabilities: {
'run:test': action({ input: z.object({ suite: z.string() }) }, async ({ input }) => {
// ...run the suite...
return { ok: true, suite: input.suite };
}),
'spawn:claude': spawn({ harness: 'claude' }),
},
triggers: [
// When a message matching the pattern lands in #deploys, invoke run:test.
onMessage({ channel: '#deploys', match: /[Ss]hip/ }, 'run:test'),
],
});Serve it with the CLI:
agent-relay node up --config ./builder.node.ts
# or drop the file at the project root as agent-relay.ts and run `agent-relay node up` (auto-discovered)
agent-relay fleet nodes # list registered nodes
agent-relay fleet status # show node + capability healthRepository placement
A node can declare the checkouts it can serve with an owner/repo keyed map:
export default defineNode({
name: 'builder',
repoPaths: {
'AgentWorkforce/factory': '/srv/repos/factory',
'AgentWorkforce/relay': '/srv/repos/relay',
},
capabilities: {
/* … */
},
});Each value must be an absolute path on that node. Registration derives the
placement-safe AgentWorkforce/factory and AgentWorkforce/relay keys; the
absolute values are never sent to Relaycast, exposed in the roster, or logged.
Serving a node programmatically
@agent-relay/fleet also ships the node runtime, so you can start a node in
process without the CLI:
import { defineNode, serveNode, startServeNode } from '@agent-relay/fleet';
const definition = defineNode({
name: 'builder',
capabilities: {
/* … */
},
});
const connection = { url: 'http://127.0.0.1:8787' }; // broker HTTP API base URL (apiKey optional)
// startServeNode returns a RunningNode { stop(), done } for supervised use…
const running = startServeNode({ definition, connection });
// …await running.done to block until the node stops, or call running.stop().
await running.stop();
// serveNode runs the node to completion (resolves when it stops / aborts).
await serveNode({ definition, connection });AgentWorkforce personas
Served spawn(...) handlers request verified readiness and finish only after the
delegated broker reports a ready worker. Launch failure propagates to the caller.
The engine must support node-owned spawn invocation status reads; confirmation
is bounded at 130 seconds. If confirmation is interrupted, reconcile the reported
child invocation before retrying, since the broker may still be finishing setup.
Release the compatible engine before publishing/upgrading the fleet package against
the hosted service. For legacy placement-only handlers, an operator may explicitly
set spawn(harness, { verifyReady: false }) or ctx.spawnAgent({ agent, verifyReady: false }).
This skips readiness polling and returns { placement, ready: false, readiness: 'unverified' };
it cannot be used as confirmed startup or authorize subscription creation. Verified
readiness remains the default and must stay enabled for subscription recipients.
The built-in spawn:<harness> capabilities launch raw harnesses. An
AgentWorkforce persona also carries its standing instructions, installed skills,
MCP servers, harness, model, and harness settings, so it must be resolved and
prepared as a unit on the target node.
Use defineWorkforcePersonaSpawnNode from @agentworkforce/local-surface to
advertise the spawn:persona capability:
import { serveNode } from '@agent-relay/fleet';
import { defineWorkforcePersonaSpawnNode } from '@agentworkforce/local-surface';
const definition = defineWorkforcePersonaSpawnNode({
nodeName: 'workforce-personas',
cwd: process.cwd(),
});
await serveNode({ definition, connection });Callers can then pass persona (an id or JSON path) instead of cli to the
Agent Relay spawn MCP tool. Persona spawns are SDK-backed and are reported as
successful only after node registration and the harness readiness handshake.
Logging
The node runtime emits structured events — each capability it registers and every
action that hits it (invoked / completed / failed, with a duration) — through a
logger you inject. The shape matches @agent-relay/utils' createLogger, and
every event carries a structured extra bag ({ capability, action, invocationId,
ms, … }) so file and JSON sinks can key on the fields:
import { createLogger } from '@agent-relay/utils';
await serveNode({ definition, connection, logger: createLogger('fleet') });Via the CLI, agent-relay node up surfaces this without code:
agent-relay node up --config ./builder.node.ts --log-file ./node.log
agent-relay node up --config ./builder.node.ts --log-level debug # include per-capability lines
agent-relay node up --config ./builder.node.ts --log-json # one JSON object per lineCapability registration logs at debug; action invocations at info; failures at
warn. With no logger the node is silent — pass a logger (or the older log/warn
callbacks) to receive events. The CLI wires a warn-only sink when no --log-* flag is
given, so agent-relay node up stays quiet apart from warnings until you opt in.
Concepts
- Node — a named host registered with the workspace.
defineNodevalidates the manifest up front and returns aFleetNodeDefinition. - Capability — a typed operation keyed by name. Build one with
action(...)(a handler with an optional Zod input schema) orspawn(...)(a capability that launches a harness). - Trigger — a rule that invokes a capability in response to a channel message. Create one
with
onMessage({ channel?, match?, mention? }, actionName).
Triggers and match
match accepts a string (substring/exact match) or a RegExp. The pattern is serialized to
the relay and matched broker-side.
Regex flags are not supported yet.
defineNoderejects a trigger whosematchis a flaggedRegExp(e.g./ship/i,/ship/m) rather than silently dropping the flag — a silently stripped flag would change matching semantics without warning. Until flag support lands, encode case-insensitivity with character classes:onMessage({ match: /[Ss]hip/ }, 'run:test'); // ✅ case-insensitive via character class onMessage({ match: /ship/i }, 'run:test'); // ❌ throws at defineNode validation
License
Apache-2.0
