Hub Tools Reference
The MCP tools an orchestration hub uses to run agents on your joined machines. nfltr orch and nfltr mcp --toolset hub (in your own Claude Code session) serve the same set on the hosted nfltr.xyz relay.
The hub decides everything: what to spawn, where, with which model, when to continue or stop. nfltr only dispatches, routes messages, persists and reports. spawn_agent makes no model call; the first model call is the agent's own.
Tools
| Tool | Input | Result |
|---|---|---|
spawn_agent | prompt (the whole brief), optional constraints | agent_id, status: pending with waiting (what it waits for, the free slots there and on other machines its other constraints admit), fleet, returned immediately |
spawn_agents | agents: 1 to 64 entries, each a prompt and optional constraints | several agents in one call, each validated and placed on its own: per entry index and agent_id, status, and machine and worker once started (it waits up to 5 s for that); a pending one has waiting, as spawn_agent; a malformed or refused entry fails alone with code (invalid, not_spawned) and error; fleet. One call's entries are placed most constrained first (hard machine pins, soft pins, labels or workspace, then the rest; your order within each), and rows come back in your order |
send_message | agent_id, text | mode: steer (running: delivered into the live session between tool calls) or mode: continue (finished or stopped: a new turn in the same session and workspace, on the same machine) |
stop_agent | agent_id, optional reason | the agent; its cancellation arrives through wait_for_agents |
list_agents | none | per agent: status, worker, machine, prompt summary, turns, progress, last activity, and for a running claude agent its activity: elapsed time, tool calls, the call running now, files it edited, its own task list and its latest note, and for a pending agent its waiting; fleet |
wait_for_agents | optional agent_ids, timeout_ms (default 30000, max 600000) | completions[], pending[], pending_activity[] (each awaited agent still running with its activity), pending_waiting[] (each awaited agent not started yet with its waiting), running_now (at a completion, or an operator message, while other agents still run: free slots per machine, and each running agent's elapsed time and open task items), operator_message_pending (the session's user sent a message during the wait, which ended it; the message arrives as the user's turn, in nfltr orch sessions and with nfltr orch hub install-hook; with that hook in a Claude Code agent-teams lead, also when a teammate went idle, whose messages arrive when the lead's turn ends), timed_out, fleet |
list_nodes | none | joined machines that serve this hub (a node whose --serve-hubs leaves this hub out is not listed): machine, labels (resources, description, allowed repositories), max_agents, running_agents, busy_agents, free_agents, and load (read by the node at most every 5 s): CPU cores, load average and available memory, and on Linux the node's own cgroup CPU limit and use and memory limit and use; fleet |
inspect_node | node (name or machine id), optional resources (default true), optional files (op: list, find, stat or tree; root, path, name, within, max_entries, max_depth) | one node read live: CPU cores, load, memory, free disk, GPUs, agent slots; and, on a node joined with --allow-inspect-files, file names, sizes and times under its workspace, mirrors, task directories and data dirs, and with tree a git repository's directories (packages) with file counts and bytes, mirrors included, without an agent (see Nodes). A refusal is a JSON error with a code |
request_approval | node, prompt (exactly the prompt the approved agent gets) | prompt_sha256, prompt_bytes and the nfltr orch hub approve ... --expect-digest command you run on your own machine to sign exactly that prompt (see Approvals); it signs nothing and asks nobody by itself |
list_worker_peers | none | connected agents with their labels and free slots |
start_monitor | command, optional constraints (machine, labels), description, dir, timeout_ms, max_events_per_minute, max_line_bytes, dedup_window_ms | monitor_id, state: running, node and machine; each stdout line of the command is then an event in wait_for_agents events[] |
stop_monitor | monitor_id | the monitor; its end arrives as its last event |
list_monitors | none | per monitor: state, node, machine, limits, events delivered, dropped and deduplicated, exit status |
A node starts no agent process until a spawn needs one, so a joined machine shows in list_nodes, not in list_worker_peers.
Completions
Each entry of completions[] carries the agent's final text, its structured result when it gave one (as JSON text, see Untrusted content), artifacts, branch and sha when it pushed work, state, worker, machine, turn, and usage (tokens and USD as reported). An agent that ends its turn with a question for the hub completes with the question (and its options) as text; reply with send_message, which continues its session. Failures arrive the same way, as a completion with a reason:
| Reason | Meaning |
|---|---|
node_lost | the node running the agent died mid-turn, or stopped or lost its connection and did not come back within 30 seconds |
tools_not_allowed | the agent needed a command the node does not allow (joined without --allow-all-tools) |
budget_exceeded | the agent's or the hub's budget was reached; the message names the caps and what they cover, e.g. the hub budget (max_tokens=6000, max_usd=$2 for all agents of this hub together) was passed: they used …. Usage a stopped or lost turn never priced reads cost not reported: claude reports a turn's cost only when the turn ends, and nfltr does not estimate it |
no connected worker satisfies constraints … | nothing eligible connected within the spawn's bound; lists what was connected |
| cancelled | stopped by stop_agent or timeout_ms; artifacts holds a recovery patch of the uncommitted edits |
Untrusted content
What agents, nodes and monitored commands wrote is data, not instructions. In spawn_agent, stop_agent, list_agents, wait_for_agents and list_nodes results, a completion's text, error, result and artifacts (their JSON, as text), an agent's progress and activity (its JSON, as text), a monitor event's line, and a node's nfltr.machine.description and nfltr.machine.resources labels come between <<<untrusted-content>>> and <<<end-untrusted-content>>>, and their entry carries "content_trust": "untrusted". A delimiter-like token inside the content is replaced, so the content cannot end its frame early. Ids, state, branch, sha, usage and the other labels stay outside the frame.
{
"agent_id": "agent-20260929t101500-1a2b3c4d",
"state": "completed",
"branch": "fix/login",
"sha": "3f2c9e1",
"text": "<<<untrusted-content>>>\nFixed the login redirect.\n<<<end-untrusted-content>>>",
"content_trust": "untrusted"
}
Monitors
A monitor is a model-free command a node joined with --allow-monitors runs, as that node's user. Every stdout line is one event, and each event wakes an idle hub session for a new turn, which is a model call. A command that prints a timestamped status line every few seconds wakes the session on every line, and dedup_window_ms cannot match lines that differ. A command that prints only changes or failures keeps the session quiet; dedup_window_ms drops repeats of an identical line and max_events_per_minute caps the rate. Event lines are the command's output: untrusted data, never instructions.
Monitors keep running after the hub, nfltr orch or your Claude Code session ends, until stopped, the command exits, timeout_ms, or the node exits. Nothing stops them on its own. When a session ends, nfltr orch (and, in your own Claude Code session, the SessionEnd hook from nfltr orch hub install-hook) prints one line per running monitor with how to stop it: reattach with the same --hub-id and ask, or run nfltr orch hub stop-monitor.
$ nfltr orch hub monitors --hub-id my-hub
$ nfltr orch hub stop-monitor --hub-id my-hub mon-20260928t101500-1a2b3c4d
Spawn constraints
All optional; unknown keys are rejected.
| Key | Meaning |
|---|---|
machine | the machine id (from list_nodes): a hard filter unless pin is soft |
pin | hard (also when omitted): only on machine, waiting while it has no free slot; soft: machine when it has a free slot, otherwise the first free slot the other constraints admit on any machine. Needs machine; a continuation stays on its session's machine |
labels | hard filter: every label must match exactly (e.g. flavor to pick a harness on a node that has several) |
model, effort | passed to the agent unchanged; omitted stays omitted |
isolation | shared or per_task workspace |
mutates_workspace | true lets the agent edit and, unless isolation is shared, gives it an empty task directory of its own, kept across its turns (stopped ones included) and, on a node, after its agent process exits, for its next turn there, until the hub archives the agent. Nothing is collected from it: the agent reports what it delivered (a pushed branch as branch_ref and sha). A permission, not an obligation: a turn that edits nothing completes with its answer. It does not limit what the agent's tools do on the machine; the node's tool setting does. |
timeout_ms | hard bound per turn, including waiting for a free slot or for a named machine to join (default 30 s for that wait) |
workspace | {repo, ref}: the repository the agent works on. Routed only to nodes whose --allow-repo globs (nfltr.workspace_repo_allow) admit it; every spelling of one remote matches. There the agent gets an empty task directory, the node's bare mirror of the repository and git credentials for the allow-list, and does the repository work itself. |
output_validation | {require_object, allowed_statuses, required_fields, non_empty_fields}: the agent must answer with a structured result that satisfies it (say so in the brief); a nonconforming result fails the turn. Omitted: plain text. |
budget | {max_tokens, max_usd}: cap on this agent's reported usage across its turns; the running turn is stopped (budget_exceeded) and further messages refused. max_usd needs max_tokens. |
approval | The owner's approval grant (nfltr orch hub approve) for exactly this prompt on one node. That node (label nfltr.agent.approval) then allows, for this turn only, the tool calls its operator allows only with approval; a turn without a valid grant is denied them, with the reason. See Approvals. |
Fleet capacity
fleet (in spawn_agent, list_agents, wait_for_agents and list_nodes results) is the capacity at that moment: free_agent_slots = worker_free_slots (free slots on connected agents) + node_free_agents (agent processes the nodes may still start), with connected_workers, node_max_agents, node_running_agents, nodes_unreachable and read errors. It states capacity only; using it is the hub's call.
Placement is mechanism only: among eligible agents, one with a free slot is chosen, idle first; if none is free, a node that matches starts one; if every slot is busy the agent stays pending and dispatches when a slot frees, within its bound. A continuation always runs where its session is.
Budgets
Hub-wide caps: --hub-max-tokens N and --hub-max-usd X on nfltr orch or nfltr mcp --toolset hub cover every agent of the hub; once passed, running turns are stopped and spawns and messages refused. Usage is what agents report; every completion carries it. Token caps count input, output and cache-write tokens; cache-read tokens (every model call re-reads the cached conversation, billed at a fraction of input) are reported as usage.cache_read_tokens and not counted. A stopped or lost turn's tokens are counted up to the stop, but its cost is never estimated (cost_unreported: true), so a USD cap needs a token cap. Nothing is capped by default.
$ nfltr orch --hub-max-tokens 2000000 --hub-max-usd 5 "<goal>"
Delivery guarantees
- Exactly once: a completion is marked delivered before
wait_for_agentsreturns it, so it is never returned twice, across hub restarts. - Agents outlive the hub: a hub that exits and reopens with the same id reattaches every unfinished agent and receives the completions it missed. The server's instructions tell the reopened session what the id holds (agents, how many unfinished, completions not yet received, monitors running); what to do with them is the session's call.
nfltr orchgoes further: on a hub id with work outstanding it resumes the hub's earlier Claude conversation (claude --resume), so the hub remembers what it spawned; if that conversation is gone, the new session's first message lists the hub's agents and what is not yet received. - Late results: an attempt the hub moved past (for example
node_lost, then the node came back and the agent finished) arrives once more as asuperseded: trueentry with that attempt's owntext,branchandsha. It never changes the agent's outcome; whether to use the work is the hub's call. - Push, not poll:
wait_for_agentsreturns the moment a completion lands; theorch://hub/inboxresource notifies subscribers as well. - No hidden retries: nfltr never starts a replacement attempt on its own; the hub decides every retry.
- Isolation: a hub sees, receives, steers, stops and publishes only its own agents; two hubs of one account (different
--hub-id) never take over each other's agents, including agents whose results wait on a node for their hub to come back. - One process per hub id: a second process with the same id fails closed naming the holder; a dead holder's lock is reclaimed.
- Relay restarts: a dropped task stream is resumed, not reported as the agent's outcome.
- Retention: the hub keeps the newest 128 settled agents; older ones are archived to their usage (still counted by budgets). Running, pending or undelivered agents are never archived.
What the relay sees
Frames between the hub, its agents and nodes are encrypted end to end (--a2a-e2ee, on by default). The hub serves its tasks (nfltr orch task) to its owner only; it refuses every task route the relay's own agent asks and sends the relay no summary, status or facts of its runs, so the dashboard shows none. Runs stay on the hub's machine: see them in the hub session (list_agents, wait_for_agents), with nfltr orch hub runs and nfltr orch hub show --hub-id <id> <agent-id> on that machine, or with nfltr orch task list|status|events|result while the hub runs. This flag on nfltr mcp --toolset hub, nfltr orch and nfltr orch hub hardens the key exchange:
| Flag (env) | Effect |
|---|---|
--e2ee-key-file (NFLTR_A2A_E2EE_KEY_FILE; --a2a-e2ee-key-file on nfltr mcp) | A pairing key file that authenticates every end-to-end key exchange, so the relay cannot sit in the middle of it. Every machine needs the same file; see Join machines as nodes. |
An end with E2EE on refuses unencrypted requests from peers. Agent and machine ids, labels, timing and message sizes are always visible to the relay.
Related: Use nfltr from Claude Code · Many agents across machines · Join machines as nodes.