All field notes

The router is pure on purpose.

Uber and Google treat placement as an optimization over a graph of supply, demand, time, and cost. We do the same for AI workloads, with one hard rule: the router never touches a provider. It reads immutable snapshots and returns a plan. Reservation is someone else’s job.

Match, cost, completion

Profiling and routing exist to honor three product pillars:

  • Match: infrastructure that fits the work (compute class, memory, interconnect, runtime, locality), not the largest machine in the catalog.
  • Cost: optimize expected cost to completion, not the cheapest hourly offer.
  • Completion: attach a fallback graph so reclaim does not end the workload.

A B200 that the work does not need is a routing failure, even if it is available and bid-able.

Two stage handoffs

Profile. Workload revision + inputs become a versioned execution_envelope (Temporal activity / gRPC). Durable ack: envelope row + outbox.

Route. Envelope + policy + market/health snapshots + remaining budget/deadline become primary route + fallback graph + bid_ceiling + score breakdown. Durable ack: route decision and explanation in the same Postgres transaction.

Inputs are immutable IDs and snapshots. Outputs are a plan. That is the entire interface.

Pilot today Profile and Route run as in-process activities rather than Temporal activities over gRPC, and both are idempotent by operation key, so a replayed step returns the stored decision instead of re-scoring. The router itself is already the pure function described here: envelope and snapshots in, decision plus score breakdown out, no provider client anywhere in the package.

Signals → envelope

Customers describe work and outcome. The profiler compiles that into something the router can score:

  • Memory / model size → peak host memory, optional device memory, footprint, topology
  • Runtime / throughput → expected duration, parallelism, deadline slack
  • Data / resilience → locality, restartability, checkpoint cost, interruption tolerance
  • Outcome → remaining max_cost_usd, policy, continuity mode

Confidence is recorded on the envelope and scored as a routing input rather than hidden. v1 may derive the envelope from static image metadata and declared requirements. Short probe runs remain an open decision.

Continuity modes

Not every AI workload is a long checkpointable training job. The envelope carries honesty about recovery:

checkpointed

Training / fine-tune. Restore gen N+1 from the latest verified manifest.

reclaim → fence → restore → re-route

restartable

Idempotent batch / many inference jobs. Re-queue unfinished units via cursor.

reclaim → cursor → requeue

ephemeral

Short best-effort. Fail fast or retry from origin per policy.

interrupt → end or origin retry

The router must not invent checkpoint restore for work that cannot resume.

The objective function

Maximize probability of finishing on time within budget, subject to policy. Score components persisted with every decision:

nodus · route score pure
fit              0.35  envelope vs offer (compute class, resources,
                       interconnect, locality)
cost_to_complete 0.30  expected run cost + expected recovery under
                       the interruption model
time_to_result   0.20  expected runtime + provision latency vs
                       deadline slack
recovery_value   0.10  quality of fallback graph (alternates, restore RTO)
health           0.05  supplier health facts
policy             --  hard constraints; violations are rejects,
                       not soft scores

total = weighted sum, scaled by snapshot freshness

Those weights are constants, not per-request tuning, because a decision has to be replayable years later from its stored snapshot IDs. Two thresholds are not scores at all: supplier health below the eligibility floor rejects an offer outright, and policy violations reject. Staleness applies as a penalty that grows as a snapshot nears its TTL. Excess capacity relative to the envelope is an explicit penalty, which is how “bigger is available” loses to “right-fit finishes”.

bid_ceiling derives from remaining budget after non-compute costs, a minimum recovery reserve, and margin for viable fallback. Cheap capacity wins only when it can deliver the outcome.

Pilot today The shipping ceiling implements the first three terms: remaining budget, a flat non-compute reserve, and a percentage recovery reserve held back from every bid. The explicit margin for fallback routes is specified but not yet computed, so today’s ceiling is conservative in the safe direction — it reserves recovery money before it reserves a machine.

Fallback is a graph

The decision stores a graph, not one backup provider. Branches are typed by failure class: reclaim, provisioning failure, restore incompatibility, deadline or budget risk. Each carries feasibility conditions, expected recovery time, expected cost, and confidence. When the graph is exhausted, recovery re-enters routing against fresh snapshots with the remaining envelope: progress consumed, budget spent, deadline slack left, last verified manifest. It never invents capacity.

Exhausted graph does not invent capacity. It re-scores against fresh snapshots with what is left.

Each branch is emitted with the facts that justified it: whether the alternate fits the envelope’s memory, whether its expected cost fits the remaining budget, and the health score it carried at decision time. A recovery can then be audited without re-deriving the world as it looked. Branch ordering is deterministic, with ties breaking on offer ID rather than map iteration.

Market price changes alone never preempt a healthy run. Explanations replay from stored snapshot IDs, and identical snapshot IDs must produce identical scores. Audit and customer trust both depend on it.

Why purity matters

If the router also reserved, every scoring bug would be a financial bug. If it called providers, latency and rate limits would poison every decision. Purity keeps side effects in the bidding service, keeps decisions append-only and explainable, and lets reclaim call the same function with a thinner envelope.

Inputs are immutable IDs and snapshots. Outputs are a plan. That is the entire interface.

All field notes