Problem
Factory's dispatch depends on a fleet node that already exists and is already live. There is no path that provisions compute on demand. Today that turns any single node failure into a total dispatch outage, and it turns node saturation into a queue rather than more throughput.
The placement surface is only two values:
src/fleet/internal-fleet-client.ts:119 readonly placementLocality = 'local' as const
src/fleet/relay-fleet-client.ts:146 readonly placementLocality = 'remote' as const
local means this host. remote means some already-registered fleet node. Neither can bring capacity into existence, so when the rostered node is offline there is no third option — Factory waits.
The motivating evidence
factory-primary has been status=offline, handlers_live=0 with last_heartbeat_at frozen for hours, and Factory has dispatched nothing for days. The root cause is a genuine defect and is being worked separately.
But the outage is not only that defect — it is that the defect had nowhere to fail over to. One node stopped holding a WebSocket and the entire dispatch plane stopped. A system that could have provisioned an ephemeral node would have degraded to "slower and more expensive" rather than "stopped".
That is the argument for this issue, and it is independent of whatever fixes the WebSocket.
What is being asked for
Factory should be able to provision an on-demand sandbox node and dispatch onto it when:
- no eligible node is live — the outage case; and
- every eligible node is saturated — the throughput case, where work is queued behind
max_agents while capacity could be bought.
Why this is smaller than it looks
This is plausibly routing logic, not a provider integration. Daytona already exists as a relay configuration concept and sandbox placement has been observed at the fleet layer — a fleet spawn has previously landed on a Daytona sandbox (it ignored worker_cwd and never received its task, which is its own bug, but the placement happened). And docs/sandbox-router-product-and-build-plan.md already sets the direction: one API across providers with cross-provider failover.
So the likely gap is that Factory has no decision to make that call — not that the call cannot be made. That should be verified before any design work, because it decides whether this is a week or a day.
Design constraints, learned the expensive way
- Fail closed on the claim, not on the capacity. A dispatch that cannot record its claim must still abort. Adding a capacity tier must not add a path that dispatches without a claim.
- An ephemeral node must not become a second dispatcher. The single-dispatcher invariant is what stops duplicate work; a provisioned node is a worker host, not another Factory.
spawned: true is not running. A previous Daytona placement reported success while ignoring worker_cwd and never injecting the task, and the agent died in 75s. Any on-demand path needs an arrival proof — the agent reporting in — not a provisioning acknowledgement.
- Cost must be bounded and attributable. On-demand compute that fails open is a bill with no ceiling. State who pays, what the cap is, and what happens at the cap.
- Teardown must be guaranteed, including on the crash path. The failure mode that matters is the one where Factory dies holding a provisioned sandbox.
Definition of done
A dispatch that finds no live eligible node provisions a sandbox node, dispatches onto it, proves the agent arrived, completes the work, and tears the node down — with the claim recorded before the spawn and the cost bounded. Plus the saturation case: work that would queue behind max_agents provisions instead, under the same cap.
Not in scope
The current /v1/node/ws defect. This issue exists because that defect had no fallback, not to fix it.
Problem
Factory's dispatch depends on a fleet node that already exists and is already live. There is no path that provisions compute on demand. Today that turns any single node failure into a total dispatch outage, and it turns node saturation into a queue rather than more throughput.
The placement surface is only two values:
localmeans this host.remotemeans some already-registered fleet node. Neither can bring capacity into existence, so when the rostered node is offline there is no third option — Factory waits.The motivating evidence
factory-primaryhas beenstatus=offline, handlers_live=0withlast_heartbeat_atfrozen for hours, and Factory has dispatched nothing for days. The root cause is a genuine defect and is being worked separately.But the outage is not only that defect — it is that the defect had nowhere to fail over to. One node stopped holding a WebSocket and the entire dispatch plane stopped. A system that could have provisioned an ephemeral node would have degraded to "slower and more expensive" rather than "stopped".
That is the argument for this issue, and it is independent of whatever fixes the WebSocket.
What is being asked for
Factory should be able to provision an on-demand sandbox node and dispatch onto it when:
max_agentswhile capacity could be bought.Why this is smaller than it looks
This is plausibly routing logic, not a provider integration. Daytona already exists as a relay configuration concept and sandbox placement has been observed at the fleet layer — a fleet spawn has previously landed on a Daytona sandbox (it ignored
worker_cwdand never received its task, which is its own bug, but the placement happened). Anddocs/sandbox-router-product-and-build-plan.mdalready sets the direction: one API across providers with cross-provider failover.So the likely gap is that Factory has no decision to make that call — not that the call cannot be made. That should be verified before any design work, because it decides whether this is a week or a day.
Design constraints, learned the expensive way
spawned: trueis notrunning. A previous Daytona placement reported success while ignoringworker_cwdand never injecting the task, and the agent died in 75s. Any on-demand path needs an arrival proof — the agent reporting in — not a provisioning acknowledgement.Definition of done
A dispatch that finds no live eligible node provisions a sandbox node, dispatches onto it, proves the agent arrived, completes the work, and tears the node down — with the claim recorded before the spawn and the cost bounded. Plus the saturation case: work that would queue behind
max_agentsprovisions instead, under the same cap.Not in scope
The current
/v1/node/wsdefect. This issue exists because that defect had no fallback, not to fix it.