MESH ONLINECODENAME: Circus Maximus

Claiming a Contended Resource

Some resources can't be shared. A GPU NVLink domain, an accelerator slot, a licensed seat — one holder at a time, and two nodes that both believe they hold it is a corruption bug, not a performance problem.

The gang-claim scheduler is the surface for that: publish what you have, match what you need, and claim it atomically under contention. It is deliberately not a job scheduler — it decides who holds a resource, and stops there. What runs once you hold it is the task lifecycle.

Islands and units

An island is a co-located pool of exclusive units. The motivating case is a GPU NVLink domain — eight H100s that are only useful together — but nothing in the model is GPU-specific. GPU-ness rides on ordinary capability tags (gpu:h100, model:<hex>), the same tags capability discovery already uses.

The exclusivity boundary is the island, not the unit. You claim the whole island or you don't claim it.

Publish what you have

A node advertises its islands into the island fold:

code
use net_sdk::gang::IslandRecord;
 
let published = mesh.publish_island_topology(record).await?;

Publishing is how a resource becomes claimable by anyone. A node that never publishes is invisible to matching, including its own.

Match what you need

Matching is read-only — it tells you what could be claimed, in preference order, and takes no lock:

code
use net_sdk::gang::{
    CapabilityFilter, CapabilityQuery, MatchCriteria, NumericFilter, SelectionPolicy,
};
 
let criteria = MatchCriteria {
    capability: CapabilityQuery::Composite(CapabilityFilter {
        tags_all: vec!["gpu:h100".into()],
        ..Default::default()
    }),
    numeric: NumericFilter {
        min_units: 8,                      // the whole NVLink domain
        max_load: Some(0.7),               // live load, 0.0..=1.0
        max_p50_latency_us: Some(2_000),
        ..Default::default()
    },
    selection: SelectionPolicy::LeastLoaded,
    prefer_capability: None,
};
 
let candidates = mesh.match_islands(&criteria);

The numeric axes are live, not static inventory: max_load and max_p50_latency_us filter on what the island is doing right now, and tags_all on the island side can require resident capabilities — a warm model:<hex> that lets you skip a cold load.

Selection policy is a scheduling decision

SelectionPolicy is the knob that decides what your cluster looks like under load, and the three options pull in genuinely different directions:

  • LeastLoaded (default) — spread. Lowest-load island first, island id ascending as a deterministic tie-break.
  • Pack — consolidate. Most-loaded-but-still-passing island first, so whole islands stay idle and claimable by a future large gang. Pick this when big gangs matter more than tail latency.
  • LoadBand { target } — aim for a load level. Avoids both stone-cold islands (cold-start cost) and near-saturated ones (tail-latency cliff).

Claim it

claim_island runs the match and reserves the first available result in one call. This is the operation that has to be atomic:

code
let held = mesh.claim_island(&criteria, until_unix_us).await?;
 
match held {
    Some(island) => { /* we hold it until `until_unix_us` */ }
    None => { /* nothing matched, or every match was contended */ }
}

The reservation is time-bounded on purpose. A holder that crashes does not strand the island forever — the claim expires. Renew before until_unix_us if you need it longer, and release explicitly when you're done:

code
use net_sdk::gang::ClaimOutcome;
 
match mesh.release_island(island).await? {
    ClaimOutcome::Won => { /* released */ }
    ClaimOutcome::Lost => { /* we weren't the holder — someone else took it */ }
}

ClaimOutcome::Lost is not an error. It's the compare-and-swap telling you your view was stale: someone else holds the island, and the correct response is to re-run the match pipeline and try elsewhere, not to retry the same island.

What this does and does not guarantee

Atomic, not consensus. The claim is a compare-and-swap against the reservation fold. Under a partition, two sides can each believe they hold an island that the other also claims — the fold converges when the partition heals, and one side learns it lost. If double-booking across a partition is unacceptable for your resource, you need a quorum, and a bare gang claim is the wrong primitive.

Local view, converging. match_islands and claim_island read this node's folds. A node sees its own announced capabilities and published islands immediately; peer-hosted islands appear only once their announcements converge over the mesh. On an isolated node, only self-hosted islands match — which is also why an empty match result on a freshly-started node usually means "not converged yet", not "nothing exists".

Advisory discovery, binding claim. Matching tells you who can. Only the claim is exclusive. Nothing stops a peer that ignores the scheduler entirely from using its own hardware.

Where to go next