Skip to content

The Adaptive Tuner

The adaptive tuner is the older, experimental optimizer. For deciding single settings use setting trials.

Using it

$ beebo settings repeater.routing.tuning.adaptive.enabled on      # start observe-only logging
$ beebo monitor capture tuning on                # make sure tuning records are captured
$ beebo monitor decode -k adaptive --limit 20      # see recent proposals
$ beebo monitor pull                           # pull a full capture for offline analysis
$ beebo monitor decode --save                  # convert the pulled capture for `analyze`
$ beebo analyze tuning indicators --window 5  # per-window diagnostics (delivery proxy, TX success, ...)
$ beebo settings repeater.routing.tuning.adaptive.applied 1        # promote rx_delay_base (bit 0) to live actuation

How it works

AdaptiveController (src/helpers/AdaptiveController.h) runs one small multi-choice bandit (UCB1) per parameter, round-robin — every measurement window it advances exactly one parameter's bandit by one tick, trying a neighboring value (-step/stay/+step) and recording the objective score observed just before the change. A full cycle through all 6 parameters (rx_delay_base, tx_delay_factor, direct_tx_delay_factor, agc_reset_interval, interference_threshold, airtime_factor) takes six windows.

Decision windows

The optimizer judges each change by the measurement window that follows it. A window closes once the node has heard at least one packet and has run at least tuning.adaptive.window.min_s seconds (default 300); if it heard nothing at all by tuning.adaptive.window.max_s (default 3600) it counts as insufficient data and nothing is changed. A window with real reception but few or no actual forwards still closes and counts — for a repeater, failing to convert reception into forwards is itself the service quality being measured, not noise to discard. Busy nodes therefore close windows in minutes and quiet ones take longer. A change is rolled back if the next window's confirm ratio falls well below the one before it or the packet pool ran close to full; each window is logged as a MON_EVAL record (beebo monitor decode -k eval). These settings are RAM-only and reset on reboot.

Observe-only by default

Every decision is logged as a MON_ADAPTIVE MonRing record (beebo monitor decode -k adaptive) regardless of whether anything was actually changed — old_value, proposed_value, the reward that preceded the decision, and whether it was applied. This is deliberate: the reward signal and bandit behavior can (and should) be validated against real traffic before trusting the optimizer to change anything live. Turn it on with beebo settings repeater.routing.tuning.adaptive.enabled on.

Live actuation, per parameter

A separate bitmask (repeater.routing.tuning.adaptive.applied) promotes individual parameters from observe-only to actually writing the value — through the exact same setter path a human changing that setting by hand would use, so a live-applied change can't drift from manual behavior. Every other parameter stays observe-only regardless. Every current parameter, including interference_threshold, can be live-actuated this way — interference_threshold's getter is not hardcoded to 0 as it is upstream (a stale leftover from before hardware CAD superseded it, which silently discards any value written to it): beebo reads the stored value.

Automatic rollback

After a live change, the parameter's next visit compares the freshly-observed confirm ratio against the one recorded just before that change. A drop of more than 15 percentage points reverts to the most recently confirmed-good value (not the very first value the parameter ever had) instead of trying a new step — a live-tuned parameter can regress, but it won't be left in a state that measurably hurt delivery.