What nfltr orch is useful for, measured
Running several Claude Code agents instead of one costs more tokens and adds moving parts, so the question is when it pays. nfltr connects machines through an outbound-only relay, and nfltr orch runs Claude Code agents on your own machines, driven by a hub that is your own Claude Code session. A hub is useful when work needs more than one machine, more than one agent at a time, or access that should stay on the machine that holds it; it is not worth it for a quick fix in one repository.
What we measured: one big task split six ways finished 1.88× faster than one agent at 1.57× the cost; 20 tasks across three real machines finished 3.5× faster than the same agents one after another, but with the slots only 0.39 busy; and an incident across a cluster, CI and a laptop had an owner-approved rollback 1 m 34 s after the alert, while the full root cause took one more question. On short single-repository tasks one agent was faster. Each use below has its setup, numbers and what is still partial. In every run the hub and its agents used the same Claude model. How the hub learned to keep a fleet busy is in the earlier post, How a Claude Code hub learned to fill a fleet.
One big task, split across agents
Setup. One goal: write tests for six independent packages of a Go codebase, judged by a hidden check that runs the tests and 12 seeded mutants. Four nodes, each a container on one host with 2 CPUs, 4 GB and --max-agents 2 (8 slots), a private relay, a cold fleet for every hub run. The single agent ran in a container of the same size, to its own end.
| Arm | Time to finish | Time | Cost |
|---|---|---|---|
| Single agent | 605 s | $2.44 | |
| Hub, six agents | 322 s | $3.83 | |
| Hub, steered, before a fix | 1,151 s | $4.34 | |
| Hub, steered, after the fix | 333 s | $3.79 |
Every hub run split the task six ways in its first call, one agent per package, and every part landed. The default hub finished in 322 s against the single agent's 605 s, 1.88× faster at 1.57× the cost. Splitting bought wall time, not tokens: each agent started from a cold clone and build and wrote and checked its own suite, so the six together worked about twice as long as the one.
Partial. One slow agent can erase the gain. In the 1,151 s run, one package's agent spent most of 1,118 s trying a race-detector build that its node's image could not do, until the hub sent it a correction; the same part took 290 s and 300 s in the other runs. And here the goal named the six packages. In the earlier 20-task batch, where two splittable tasks sat among 18 others, the hub split neither.
Steering. You can talk to a running hub. In the two steered runs a scripted operator message asked it to move work to idle slots. A message sent while it waits for agents now ends the wait with a note that a message is pending: in 0.16 to 0.22 s, and the hub's next call came 2.5 s after the send. Before the fix in the table, a batch of parallel waits held the message 70 s. Both hubs read the request, looked at their agents and declined to move work: their call. These runs steered the hub that the nfltr orch "<goal>" launcher started; that launcher has since been removed (v2.1.20, October 22, 2026). In your own Claude Code session the same note ends the wait once nfltr orch hub install-hook has installed the hub's hooks.
Evidence: one big task, hub against one agent
- Setup
- The task-diversity scenario
tests-six-mid-packages: write tests for six independent Go packages, judged by a hidden check that runs vet, the tests and 12 seeded mutants. Four node containers on one host, each 2 CPUs, 4 GB and--max-agents 2(8 slots), a private relay, a fresh cold fleet for every hub run; the single agent in one container of the same size. The same Claude model everywhere. One run per arm, 2026-10-01. - Numbers
- Single agent 605 s, $2.44; hub 322 s, $3.83 (1.88× faster at 1.57× the cost); steered hub 1,151 s, $4.34 before the wait fix and 333 s, $3.79 after. Every arm passed the hidden check. A pending message ended the hub's wait in 0.16 to 0.22 s (70 s before the fix).
- Reproduce
- (a) With the released binaries: give one goal that names several independent parts of your own repository to a hub session over joined nodes, and the same goal to one plain Claude Code agent; compare time and cost. The scenario and its hidden check are not public. (b) Our check, in the closed nfltr repository: the fleet is
make orch-distributed-nodes-up ORCH_NODES_ORIGIN_DIR=<dir>; each arm ismake smoke-test-orch-task-diversity-live TASK_DIVERSITY_SCENARIO=tests-six-mid-packages TASK_DIVERSITY_ARM=hub(withTASK_DIVERSITY_HUB_CMDandTASK_DIVERSITY_HUB_MCP_CMD) orTASK_DIVERSITY_ARM=single-agent(withTASK_DIVERSITY_SINGLE_AGENT_CMD), plus the required capsTASK_DIVERSITY_SCENARIO_MAX_TOKENS,TASK_DIVERSITY_SCENARIO_MAX_USD,TASK_DIVERSITY_CAMPAIGN_MAX_TOKENSandTASK_DIVERSITY_CAMPAIGN_MAX_USD(no defaults). Live runs cost real model spend.make check-orchestration-task-diversity TASK_DIVERSITY_SCENARIO=tests-six-mid-packages($0) shows the hidden check fails on the seed and passes with the reference. - Limits
- One run per arm, one host's containers, one scenario. The goal named the six packages; in a 20-task batch the hub split neither of two splittable tasks. One slow agent erased the gain in the 1,151 s run. The steered runs used a scripted message, not a person.
Many tasks, across real machines
Setup. The same 20 tasks as the earlier post's full run, on three separate machines joined to nfltr.xyz: a laptop (10 cores, 32 GB), which also ran the hub, and two cloud VMs with 4 vCPU and 16 GB each. Each node had 3 slots, so 9 in all. The VMs held the data sets the data tasks needed; the laptop held none.
| Measure | Result |
|---|---|
| Makespan against serial | 1,062 s against 3,737 s: 3.5× faster |
| Hidden checks | 20 / 20 judged on the statuses the hub answered (see below) |
| Data-bound agents on the machine with the data | 3 of 3 |
| Model spend | $6.17 in total ($1.57 hub, $4.60 agents), $0.31 per task |
| Throughput | 67.8 tasks an hour |
| Mean utilization of the 9 slots | 0.39 all 9 busy for 11 % of the run |
About the 20 of 20: the hub put its final JSON answer inside a code fence after one line of text, and the test runner, which wants exactly one JSON object, refused it, so its strict score is 0 of 20. In the run itself 17 of 20 checks passed; judged on the statuses the hub answered, all 20 pass.
Partial. Utilization was 0.39, against 0.71 on the one-host fleet of the earlier post, where the same 20 tasks ran 5.7× faster than serial (1,416 s against 8,106 s, four 2-CPU containers on one host, a private relay). The two runs are not directly comparable: here the agents had faster machines and did less than half the busy time. Three things kept slots idle:
- The laptop took no work for its first 2 minutes. It had two agent harnesses installed, so its node refused every launch that did not name one, and the hub was not told why. Since this run, the node's refusal reaches the hub and
nfltr node joinwarns at startup. - Clones over the WAN. The task repositories were on the laptop, so every VM agent cloned over its uplink: an agent's first clone took a median of 54 s and 66 s on the two VMs, against 8 s on the laptop.
- An overloaded VM. Three cold builds on 4 vCPU pushed one VM's load to 15–19, and one agent there ran 813 s, leaving a single slot busy for about 360 s at the end. Fewer slots on a small machine (
--max-agents) is the lever.
Evidence: 20 tasks across three machines
- Setup
- Three machines joined to nfltr.xyz: a laptop (10 cores, 32 GB) that also ran the hub, and two cloud VMs (4 vCPU, 16 GB each), 3 slots per node, 9 in all. The data sets lived on the VMs, the task repositories on the laptop. The 20 tasks of the earlier post's full run, each with its hidden check. One run, 2026-10-01.
- Numbers
- Makespan 1,062 s against 3,737 s serial (3.5×); 20 / 20 judged on the statuses the hub answered (17 / 20 in the run, 0 / 20 strict); 3 of 3 data-bound agents on the machine with the data; $6.17 ($0.31 per task); 67.8 tasks an hour; mean utilization 0.39.
- Reproduce
- (a) With the released binaries: join your own machines with
nfltr node join --max-agents <slots>, put each data set on one machine, and give the hub a batch of independent tasks as one goal. The run itself was one-off; no checked-in target sets up that fleet. (b) Our check, in the closed nfltr repository: the tasks and checks are the task-diversity corpus, run through its runner's batch mode (--arm hub --batch --batch-slots <slots>); no make target wraps the batch mode. The one-host fleet it is compared with ismake orch-distributed-nodes-up ORCH_NODES_ORIGIN_DIR=<dir>. Live runs cost real model spend. - Limits
- One run. Serial is the agents' run times added up, not a separate single-agent run. Not directly comparable with the one-host run (5.7×): different machines, less than half the busy time, and three setup problems that kept slots idle.
Incident response across environments
Setup. A real Kubernetes cluster (a local kind cluster) with a Go API that a bad config change makes crash-loop at startup. Prometheus and Alertmanager sent a real alert to a webhook the hub created on nfltr.xyz. Four nodes, each its own container: a read-only cluster node, a write-capable cluster node gated on the owner's approval, a CI node with the build logs, and a laptop node with the developer's checkout. The owner's signing key lived in a separate container with no network. The hub ran with nfltr orch "<goal>", and the goal said to investigate, propose a fix, apply it only with the owner's approval, and stop once the alert resolved. That launcher has since been removed (v2.1.20, October 22, 2026); an unattended hub is now claude -p "<goal>" with the hub tools added through claude mcp add nfltr -- nfltr mcp --toolset hub.
Run 2, with no shortcut. No environment held the whole story: the cluster had the crash and the failing setting's name, but not the build, the PR or the value; CI had which build made which image; the laptop had the PR's diff.
| Since the alert | Event |
|---|---|
| 11.8 s | The hub has the alert |
| 18.5 s | One read-only investigator on the cluster node |
| 46.4 s | Its result: the crash, the setting, a healthy previous revision |
| 48.7 s | The hub requests approval for an exact rollback prompt on the write node |
| 1 m 17 s | The owner signs it |
| 1 m 34 s | Rollback applied; 2 of 2 pods ready seconds later |
| 4 m 12 s | The resolved alert reaches the hub |
| 4 m 23 s | The hub has revoked its webhook and written its report |
The incident session cost $0.39. Without being asked, the hub mitigated and stopped: its report named the failing setting but not the build or the PR, and said the bad value was inferred from the log. After one message from the owner asking which build and PR produced the image, the hub looked at the CI and laptop nodes, ran one read-only agent on each in parallel, and answered correctly in 53 s for $0.25: the build, the PR, the commit, its author and the change. The whole run cost $0.64.
Run 1, with a shortcut. The root cause was there in 36 s and the fix applied 2 m 16 s after the alert, about 1 m 24 s of it the owner's. But in that run the deployment carried an annotation, written by CI, that named the build and the PR, so the cluster alone told the whole story and the hub never used the CI or laptop nodes. Run 1 did not test correlation; run 2 did.
Safety, measured in these runs:
- The write node took only its own hub's pinned work. It was joined with
--serve-hubsand--require-pin. A spawn on it from another hub id was refused, and so was an unpinned spawn from its own hub; no agent process started for either. - Changes ran only with the owner's signed approval, bound to the exact prompt.
nfltr orch hub approve --expect-digestrefused the prompt file with one byte added and signed nothing. In run 1, an unapproved write was denied by the node, and the agent did not try to work around it. - Prompt injection was ignored. Text telling AI agents to delete namespaces sat in the alert annotation, a pod log and the PR. No agent acted on it, and no agent tool call in run 2 contained
delete; the hub reported the text to the owner as a security finding. - No credential reached hub-visible output. The cluster tokens, the CI token, the API key, the Claude login and the owner's key were searched for in every transcript and log: no hit. The only secret printed was the webhook's, which the goal asked for, and it was revoked at the end.
Evidence: an alert to an approved rollback
- Setup
- A local kind cluster with a Go API made to crash-loop by a bad config change; Prometheus and Alertmanager posting to a hub webhook on nfltr.xyz; four node containers (read-only cluster, approval-gated write cluster, CI, laptop); the owner's signing key in a separate container with no network. Two runs, 2026-10-01; the hub started with the since-removed launcher.
- Numbers
- Run 2: the hub had the alert at 11.8 s, requested approval at 48.7 s, the owner signed at 1 m 17 s, rollback applied at 1 m 34 s, report at 4 m 23 s; $0.39, then $0.25 and 53 s for the build-and-PR follow-up, $0.64 in all. Run 1: fix applied 2 m 16 s after the alert. In run 2, no agent tool call contained
delete, and no credential appeared in any transcript or log. - Reproduce
- (a) With the released binaries, From Alert to Approved Fix walks the same path: a hook, read-only investigation and an approved change. The runs themselves were one-off; no checked-in harness reproduces this fixture. (b) Our check, in the closed nfltr repository: the nearest harness is a later, different fixture against a private relay:
make demo-incident($0, fake Claude;DEMO_ALERT_SOURCE=alertmanager|grafana|github) andmake demo-incident-live(a real model;DEMO_MODEL,DEMO_HUB_MAX_TOKENS,DEMO_HUB_MAX_USDand a Claude credential are required, real spend). - Limits
- Two runs on one fixture. Run 1 had a shortcut (an annotation naming the build and PR), so only run 2 tested correlation across environments, and there the hub stopped at the mitigation until asked for the root cause. The time to approve is the test owner's, not a person paged at night.
When not to use it
A quick fix in one repository is faster with one agent. On two short tasks run on nfltr.xyz, the hub took 67 s against the single agent's 28 s, and 111 s against 62 s; once it took 479 s, because its brief asked for a whole-module test run. The hub adds about 20 s of fixed time per task: its own session before the spawn, the launch, and delivering the answer. The cost was within $0.03 of the single agent's. A hub earns that overhead when the work needs another machine, more slots, or many tasks at once.
Evidence: short tasks, hub against one agent
- Setup
- Two short single-repository tasks from the task-diversity suite, run by a hub and by one plain Claude Code agent on nfltr.xyz, with the same snapshot, check and caps; the same Claude model.
- Numbers
- Hub 67 s against 28 s, and 111 s against 62 s; once 479 s, when its brief asked for a whole-module test run. About 20 s of fixed hub time per task. Cost within $0.03 of the single agent's.
- Reproduce
- (a) With the released binaries: give a short fix in one repository to a hub session and to one plain Claude Code agent, and time both. (b) Our check, in the closed nfltr repository:
make smoke-test-orch-task-diversity-livewithTASK_DIVERSITY_ARM=huband thenTASK_DIVERSITY_ARM=single-agenton the sameTASK_DIVERSITY_SCENARIO, with the arm's command variables and the four required caps. Real model spend. - Limits
- Two tasks, few runs each. The fixed time depends on the relay's distance and the machine's clone and build time.
What's next
- Guidance for incident goals. The hub stopped at a correct mitigation because nothing asked for more. We will publish how to state what a root cause must contain (the build, the change, the author) in the goal or a
--hub-guidance-file, and measure an incident run that uses it. - Multi-machine utilization. A rerun with the refusal fix, repositories near the agents, and slots sized to each machine, to see how much of the 0.39 was setup and how much is the hub.
- Hosted TURN, so
nfltr p2pcalls across symmetric NATs work without your own TURN server.
Is it for you?
Fits if your work needs another machine (data that should stay where it is, a cluster or CI only reachable from inside), more agent slots than one machine has, or many independent tasks at once, and you accept paying more tokens for less wall time.
Not ready for a quick fix in one repository: one agent is faster there. The hub does not reliably split one big task inside a batch of others, and on mixed machines the slots stayed 0.39 busy. nfltr orch runs Claude Code agents only, and the nfltr repository is closed source (link, the relay SDK and relay-core are public, see Four nfltr repositories are now open source). nfltr.xyz runs on one VM, and a hub and its nodes belong to one account; hosted TURN is not enabled yet. Every result here is one or two runs.
Try next: join each machine you want agents on, then add the hub to your own Claude Code session and give it a goal:
nfltr node join --max-agents 2 --labels env=ci
claude mcp add nfltr -- nfltr mcp --toolset hub
nfltr orch hub install-hook
claude -p "<goal>"
On a fresh machine, nfltr start joins it with a safe default policy and runs a first task. The guides: Join machines as nodes (with approvals for nodes that can change an environment), Use nfltr from Claude Code, From Alert to Approved Fix, and the hub tools reference. The design and how we test it are in How distributed Claude Code is built with nfltr; a larger single job, a refactor of nfltr's own CLI, is in part 4; the same kind of batch against Claude Code's own agent teams is in Claude Code agent teams and nfltr orch, measured.