Repository navigation
Evict stale radio contacts when the contact table is full - #5
Merged
Merged
Conversation
The radio's contact table (~350 slots) does not age out old contacts, so once full it drops new nodes and emits CONTACTS_FULL — new contacts never replace stale ones. The bot now evicts the stalest contacts FROM THE RADIO to keep free headroom: at startup, automatically on CONTACTS_FULL, and on-demand from the web UI Manage->Radio page. The bot's own sqlite contacts table remains the untouched long-term archive. Eviction operates on a fresh get_contacts(lastmod=0) dump (the DB archive may hold contacts no longer on the radio, so it can't pick victims) and never deletes DB rows. Victims are the stalest by a fused key: bot-DB last_synced_at, then radio lastmod, then a plausible last_advert. Protected: configured owners, all bot users, anyone DM'd in the last 24h, and any contact types in radio_evict_protect_types. Removal is capped per run and aborts after 3 consecutive radio errors; the lib's contact cache is kept in step so send_dm doesn't use an evicted entry. - [bot] config: radio_evict_enabled (default true), radio_evict_headroom (8), radio_evict_protect_types (default none), radio_evict_max_per_run (50), radio_evict_min_interval (120s debounce for auto runs). - mcbot.py: select_eviction_victims (pure), evict_radio_contacts, _on_contacts_full (gated + debounced), _on_contact_deleted, startup hook, subscriptions, parse_contact_types helper. - management.py: radio_contacts_status (read-only; includes the raw firmware autoadd byte for diagnostics), radio_evict_contacts (audited), radio_set_evict_policy (runtime-only; conf is the startup truth). - webapi: GET /api/radio/contacts-status, POST /api/radio/evict-contacts, POST /api/radio/contacts-policy (auth required). - web-ui: "Contact table" card in the Radio tab (status, policy toggle + headroom, dry-run-preview + confirm "Evict now"); rebuilt static assets. - tests/test_radio_eviction.py: victim selection, protection, garbage timestamps, dry-run, headroom math, per-run cap, error-abort, shortfall, archive preservation, disabled-gate. The firmware-native set_autoadd_config is intentionally NOT exposed (its flag byte is undocumented); only a read-only autoadd diagnostic is shown. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
sg
force-pushed
the
fix/contacts-rollover
branch
from
June 11, 2026 02:47
371694b to
3117e3d
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The radio's contact table (~350 slots) doesn't age out old contacts. Once full, the firmware just emits
CONTACTS_FULLand drops the new node's advert — so new contacts never replace stale ones, and the bot logsCONTACTS_FULLindefinitely. The bot's sqlitecontactstable holds 500 and acts as the long-term archive; the radio is the working set.Solution
When enabled, the bot evicts the stalest contacts from the radio to keep
radio_evict_headroomfree slots:CONTACTS_FULL(debounced);Eviction operates on a fresh
get_contacts(lastmod=0)dump (the DB archive may hold contacts no longer on the radio, so it can't choose victims) and never deletes DB rows — the archive is preserved. Victims are stalest-first by a fused key: bot-DBlast_synced_at→ radiolastmod→ a plausiblelast_advert(garbage sender-clock timestamps are treated as oldest). Protected: configured owners, all bot users, anyone the bot DM'd in the last 24h, and any contact types inradio_evict_protect_types. Removal is capped per run and aborts after 3 consecutive radio errors; the meshcore lib's in-memory contact cache is kept in step sosend_dmwon't reference an evicted entry.Config (
[bot])radio_evict_enabledtrueradio_evict_headroom8radio_evict_protect_typesradio_evict_max_per_run50radio_evict_min_interval120Manual eviction works even when disabled; the switch only governs the automatic triggers. Web policy changes are runtime-only —
mcbot.confis the startup source of truth.Web UI / API
GET /api/radio/contacts-status,POST /api/radio/evict-contacts,POST /api/radio/contacts-policy(auth required).Notes
set_autoadd_configis not exposed — its flag byte is undocumented (firmware source/web research were unavailable). Only a read-onlyautoadd_rawdiagnostic is surfaced; the setter can be added later once the semantics are verified.shortfall(e.g. when protections exceed capacity), never an error.Testing
tests/test_radio_eviction.py(run with a deps-equipped Python) — victim selection ordering/protection/garbage-timestamp handling, dry-run, headroom math, per-run cap, 3-error abort, shortfall, DB-archive preservation, and the disabled-policy handler gate. All pass; existingtests/test_repeat_tracking.pystill passes;web-uibuilds clean.🤖 Generated with Claude Code