Multi-link scheduling
The scheduler is where Atlas earns its keep: continuous measurement of every link, and a per-packet placement decision that shifts traffic away from trouble gradually — not on a binary up/down trigger that flaps. This page covers how links are measured, the eight strategies you can choose from, and the knobs that make asymmetric bearers behave.
Probes: how a link earns trust
Every (peer, link) pair is probed on its own clock — [link_monitor] probe_interval_ms, default 250 ms. Each probe updates an RTT EWMA, a windowed jitter figure and a windowed loss rate. Two principles govern the bookkeeping:
- Aliveness requires evidence. A link is never considered alive before its first completed round trip, and is declared dead after
dead_probe_count(default 5) consecutive misses. - A late probe is not proof of life. Past
unusable_rtt_ms(default 10 000; per-link override available) a probe that does return stops counting as evidence the link works — so a link buried under its own transmit backlog reads as down, not as "up at 40 s RTT with 0% probe loss". The measured RTT still reaches your dashboards.
Per-link overrides matter on mixed bonds: [[link]] probe_interval_ms lets a cellular link answer every 250 ms while a geostationary VSAT (~600 ms RTT) is probed every 1000 ms, each with timeouts scaled to its own cadence. And because sparse control probes underestimate loss on full-size packets, data_loss_probe_every_n (default 10) piggybacks loss accounting on every Nth real data fragment — the loss figure the scheduler acts on is measured on traffic shaped like yours.
The eight strategies
Set [scheduler] strategy to one of:
| Strategy | Placement rule | Reach for it when |
|---|---|---|
lowest_delivery_time | Default. Per fragment, minimise size/capacity + RTT — capacity- and size-aware. | General bonding; links of unequal speed. |
weighted_round_robin | Spread packets proportionally to [[link]] weight. | Deliberate load ratios across similar links. |
broadcast | Every fragment on every alive link; the receiver's anti-replay window deduplicates. | N× bandwidth is cheaper than any loss; survives N−1 failures. |
priority | Strict failover through priority_order, a list of link names. | A cheap primary and an expensive standby (e.g. satellite backup). |
shortest_path | SPF on hop counts; local link choice falls back to lowest_delivery_time. | Multi-hop meshes where route stability beats RTT-chasing. |
adaptive | 1→2→3-link redundancy driven by cost ratios between the best links. | Redundancy that scales with how bad things are. |
loss_adaptive | Same escalation, driven purely by measured loss (enter at 20% / 50%). | Loss-dominated bearers. |
latency_adaptive | Same escalation, driven by best-link RTT (enter at 40 ms / 100 ms). | Latency-sensitive traffic where rising RTT is the early warning. |
The three adaptive strategies share an anti-flap discipline: escalation is instant, de-escalation waits (adaptive_dwell_ms, default 5000) and the enter/exit thresholds form a hysteresis gap, so a link hovering at a threshold cannot make the bond oscillate. For the non-adaptive strategies, redundancy_factor = N tops up any placement to the best N links at once.
[scheduler]
strategy = "latency_adaptive"
latency_mirror_ms = 40.0 # ≥ this best-link RTT: send on 2 links
latency_triplicate_ms = 100.0 # ≥ this: send on 3
adaptive_dwell_ms = 5000 # de-escalation dwell; escalation is immediate
Gradual by construction
Link scores update on every probe: loss inflates a link's cost geometrically (1/(1−loss)), jitter inflates it further, and cost-based strategies shift traffic as the numbers move — long before anything is "down". The only binary events are the ones that should be binary: a dead link leaving the candidate set, and the adaptive strategies stepping their redundancy level. By the time a failing link is formally dead, it was already carrying almost nothing.
Predictive failover
[link_monitor] prediction_source — none (default), rssi, position, or both — lets the scheduler act on where a link is heading:
- rssi: the signal-strength trend from radio telemetry is projected
rssi_lookahead_secsahead againstrssi_floor_dbm; a link fading toward the floor has its perceived loss inflated (capped at 75%) so traffic drains off it before it dies. - position: peers advertise position and velocity; the projected distance is scored against each radio's range envelope (
[[link]] max_range_m, warning ramp fromrange_warn_m) and stretched links get expensive — locally and in mesh route costs.
Prediction only ever biases scheduling. It never declares a link dead — probes remain the sole authority — and dashboards keep showing measured values, never predicted ones.
Metered links
A link with a data budget declares it: quota = "20 GB" with quota_period (monthly, weekly, daily, rolling-30d), an anchor day, and a warning threshold (quota_warn_pct, default 80). Accounting is honest — wire bytes, including framing, probes, handshakes and redundancy copies — and persists across restarts. An over-budget link drops out of the scheduler's candidate set, but a quota never takes down the last usable path: when the expensive link is the only link, it carries the traffic and the journal records the crossing.
Mirrors, stripes and aggregation
In the transport-class path, each class's multipath policy compiles to a typed send plan: Mirror (a copy on every listed link — redundancy) or Stripe (each packet on exactly one link, weighted by effective goodput capacity × (1 − loss) — aggregation). Striping is what makes two 20 Mbps bearers carry materially more than one: measured 36.5 Mbps across two such links under 60 Mbps offered load, 91% of the theoretical sum, versus 19.2 Mbps when the same traffic was mirrored. Links that miss a class's latency budget are excluded from its stripe set before weights are computed, and an emptied set falls back to the single best link rather than dropping traffic.
A tuning checklist for asymmetric bonds
- Declare honest capacities per link (
capacity = "64 kbps"…"1.5 Gbps") — the default assumption is 80 Mbps, and cost functions divide by it. - Slow the probe cadence on high-RTT bearers (
probe_interval_msper link) and raise theirunusable_rtt_ms. - Budget metered links with
quotaso the scheduler, not an invoice, discovers the ceiling. - Give narrowband radios a slower mesh advertisement cadence — see route flooding on narrow bearers.
- Prefer an adaptive strategy when link quality varies faster than a human can retune; prefer
prioritywhen the economics, not the physics, should decide.