Agent reference: Reticulum

September 11, 2026 · View on GitHub

Deep subsystem reference for AI assistants. Open this when a task touches the Reticulum sidecar, LXMF, propagation, Remote (rnsh/rncp), Nomad, RRC, voice, or games. Hard rules live in AGENTS.md; this file holds the file-level detail.

  • Ownership (do not reimplement transport): Reticulum automation has three layers — (1) RNS transport (path table, announce flood, Auto beacons, Links/proofs) stays in rsReticulum (+ overlays under reticulum-sidecar/patches/); (2) LXMF client/PN loops (delivery announces, Direct→Propagated driver, identity-from-announce, PN /offer//get) are intentional lxmd/Ratspeak parity owned by the sidecar because rsLXMF is not a daemon; (3) mesh-client policy (Auto→private demotion, multi-PN cascade, path-medium preference, DM auto-probe, Nomad force_path_refresh) is product behavior above RNS — document it, do not mistake it for transport. Renderer mirrors WS/HTTP and configures policy; it must not invent pathfinding or announce flooding. Extra sidecar send/path/PN code is usually lxmd glue or product policy, not accidental Ratspeak duplication; Chat outbox/badge receipt correlation stays in the renderer. Full write-up: ../reticulum.md#ownership-rns-vs-lxmf-client-vs-mesh-client-policy.
  • Sidecar: reticulum-sidecar/ (AGPL Rust binary mesh-client-reticulum; path deps under repo-local .rsstack/ via scripts/clone-ratspeak-stack.shrsReticulum/rsLXMF/rsNomad/rsLXST/lrgp-rs); dev: pnpm run reticulum:sidecar:dev. Listen-first: HTTP binds before attach_live; /api/v1/status status: ok = listening; rns_ready/lxmf_ready false until live. PN messagestore load deferred; local-prop serve waits for load. LXMF send/reaction fail closed with live-required errors until live.
  • IPC: reticulum:* main handlers — start / stop / getStatus / syncInterfaceIssueScope, proxyGet / proxyPost / proxyPut / proxyDelete, factoryReset (blocked on generic proxy), config file read/import dialog, showNomadContentSourceDialog, setNomadContentSource, Remote rncpSend / rncpFetch / setRncpListener / showRncpOpenFileDialog / showRncpSaveDirectoryDialog / revealInFolder. Also media:ensureCameraAccess, gps:exportGpx, db:setReticulumDestinationVerified, Remote DB db:listReticulumRemoteAddresses / upsert / delete and db:listReticulumInboundPolicy / upsert / delete (src/main/ipc/reticulum-db-handlers.ts), mesh-client:openUrl / electronAPI.deepLink.onOpenUrl. Renderer uses electronAPI.reticulum proxy (no direct localhost). Proxy rate-limit soft-envelopes live inside the handler try (settleReticulumProxyFailure). ReticulumStackPanel + useReticulumInterfaceSnapshot sync enabled interface names after hydrate so TCP/TX issue banners clear when hubs are disabled — do not refresh interfaces on announce.received / stats_update (that flooded GET /interfaces after wake); poll + shared proxy backoff only. reticulumSidecarIssueTracker keeps that enabled set sticky while reading sidecar logs; latches named TCP RST/EOF for Connection alerts. TCP hub fast-flap (RNS 1.4.0+): reticulumStackSessionTracker counts stack starts (five in 12h, persisted; testers with long sessions still qualify); Connection amber banner uses tcp_fast_flap / unreachable+restart hint; auto stack restart is skipped; host TCP probes run only before sidecar ready. troubleshooting.
  • Panels: ReticulumStackPanel (Connection — stack lifecycle, interfaces, issue banner), ReticulumNetworkPanel (Network — identity slots + QR share/ingest, stack/announce settings, Propagation mode Off/Auto/Manual + rename/delete, config import), ChatDmPaperControls (Chat DM Share as paper + Scan paper), ReticulumMapPanel (Map — RMAP v4 discovery), ReticulumRmapDiscoveryControls / ReticulumRmapConnectionStatus (RMAP publish: Network enable-all eligible interfaces; Connection X of Y status), ReticulumAdminPanel (Admin — RNode flasher, factory reset), ReticulumPeerListPanel (Peers — Peers / History / Contacts / Favorites sub-tabs; path request + probe + verified badge; LXMFace avatars; History = messaged last_heard, Contacts = explicit is_contact / Save as contact only), NomadNetworkPanel (Nomad — browse + My Pages watched-folder static host via NomadPageServerPanel/rsNomad; nomad_serving_enabled + nomad_serving_content_source restore hosting after live stack start; lazy-mount keep-alive, dual-axis page scroll; fit-width default and open-width toggle), ReticulumRemotePanel (Remote — rnsh multi-session shell + rncp send/receive/fetch; Saved addresses + inbound policy; Chat DM send-file via ChatDmRncpControl), RrcPanel (RRC — multi-hub relay chat)
  • Deep links / QR: OS scheme is lxm:// (not mesh-client://); MeshClientDeepLinkHost, meshClientDeepLink.ts (lxmPaperMessage kind + looksLikeLxmPaperBlob; Games lxm://game/<session> / Ratspeak lrgp:<session>lxmGameSession), handleReticulumQrIngest.ts (shared Network/Chat/OS paper + in-app contact ingest), applyLxmPaperIngestPOST /api/v1/lxmf/paper/ingest, QrIngestControl / QrCodeImage. OS contact / MeshCore imports confirm before upsert; paper OS deep links ingest without confirm; Games session links open Reticulum Games tab via openReticulumGameSession.
  • Decommissioned hubs: src/shared/reticulumDecommissionedHubs.ts (Amsterdam only) — stack-start auto-disable + Add default backbones disables matching enabled TCP rows; UI badge + enable-block in ReticulumInterfacesPanel.tsx (isDecommissionedReticulumTcpInterfaceRow); keep TS↔Rust synced via pnpm run check:reticulum-decommissioned-hubs. Default backbone picker + region-grouped interface list (Primary & Global / North America / Europe / Asia & Oceania / Specialty / User Defined) in reticulumDefaultHubPresets.ts + ReticulumDefaultHubsPickerModal.tsx; muted disabled rows + checkbox bulk delete; countEnabledDefaultHubPresets / >3 enable warning
  • BLE RNode RSSI: useReticulumBleRnodeRssiMap gates on sidecar running (not api-ready), burst-then-steady scans via nested acquireReticulumBleScan, clears sticky targets immediately when all BLE RNodes are disabled
  • Propagation mode / sync: Network → Propagation nodes owns Off/Auto/Manual (default Off; persisted values including legacy App-panel auto are honored). Auto one-time syncs via startPropagationSyncCascade + sidecar destination_hash sync in order: finite-hop discovered (no Add/Preferred) → configured remotesunknown-hop discovered → local-prop (skips remotes when no enabled interfaces); runtime hook useReticulumPropagationAutoSync. Sidecar start_propagation_sync is client /get-primary (inbox retrieval; UI progress from PropagationClient) — peer /offer inventory push stays on the local-host peer loop when serving (avoids AwaitingResponse hangs against non-peer remotes with a nonempty messagestore). Hard-fails with PROPAGATION_PATH_UNKNOWN when ensure_path_for_direct fails after announce settle (same path gate as offer probe). Manual uses Preferred, else picks the best configured remote for that sync only (no Preferred write), then the remaining remotes, then local-prop. Off = no PN support: startPropagationSyncCascade returns early (per-row Sync is disabled in UI), hasEffectiveReticulumPropagationTarget / hasReticulumPnCascadeCapacity are false, ReticulumPropagationNotice is hidden, and the sidecar disarms the outbound PN plus empties cascade candidates (propagation_mode in mesh_client_stack.json, POST /api/v1/propagation/mode, candidates_for_propagation_mode); renderer pushes the mode on change and on sidecar-ready. Ignore for Auto: POST/DELETE /api/v1/propagation/auto-blacklist persists propagation_auto_blacklist (32-hex, cap 256); filters Auto sync ranking and Auto deposit (auto_discovered_candidates + configured retain in Auto); Manual Prefer/Sync still allowed. Ownership: mode in renderer localStorage (+ sidecar mirror); blacklist + deposit candidates in sidecar; sync cascade orchestration in reticulumPropagationAutoApply.ts; startSync attempt stamps must be unique across same-ms supersession. reticulumPropagationStore / reticulumPropagationSync.ts — Establishing stall (~45s) + hard ceiling (~180s), auto-sync interval from last success with failure cooldown, error keys for identity / non-PN / path-unknown / peering stamp; stamps lastPropagationSyncAttemptAt / activePropagationSyncAttemptAt for WS correlation. Cancel mid-/get must call PropagationClient::abort_transfer (rsLXMF overlay) or the next Sync stays PROPAGATION_RETRIEVE_BUSY. Silent Host /get terminal clear must only drop the latch when propagation_sync_target still equals that peer. Nothing-to-sync is not a failure: when the cascade contacts no node it writes syncNoTarget / syncLocalLoading / syncRetrieveBusy (never overwriting a real error from an attempted node), the local row reports sidecar status: "loading" while the messagestore reads (local_propagation_status + PropagationBridge::messagestore_load_pending, per-row Sync disabled), and the 30 s tick calls refreshFromSidecar while hasPropagationCascadeCandidate is false so a fresh stack recovers on its own — refreshFromSidecar must not clear the active attempt while sync.active. Debug snapshot propagationClient exposes mode/preferred/autoTarget/resolvedSyncTargetId/autoBlacklist. Auto also deposits on Discovered PNs: sidecar auto_discovered_candidates (pn_cascade.rs, Auto only, cap 3, hop-sorted with MAX_PLAUSIBLE_PROPAGATION_HOPS=32, skips inactive / self / already-configured / Auto-blacklist / over max_peering_cost) appends after configured remotes and before local-prop, rebuilt from the shared rebuild_pn_cascade_candidates helper in live.rs; capacity helpers count non-blacklisted discovered rows in Auto. Chat notice dismiss: chatNoticeDismissed with Don't show again / Network toggle. Named sync target: startSync stamps syncTargetId. Attempts settle before the cascade advances: startSync returns accepted | deferred | failed. Soft-defer (PROPAGATION_SYNC_OUTBOUND_BUSY / PROPAGATION_RETRIEVE_BUSY / PROPAGATION_STACK_NOT_LIVE) advances without 15‑min backoff; all-remote soft-defer + local-only settle must not advance lastPropagationSyncAt as a full success. Remote budget PROPAGATION_CASCADE_BUDGET_MS (5 min); per-attempt ~60s; single-flight cascade. Auto /api/v1/interfaces probe fails closed (treat read/rate-limit as no interfaces → local-only settle). Retrieval vs peer sync: User Sync progress is client /get-primary. Peer /offer runs only on the local Host peer loop. Logs: propagation-retrieve (retrieve_mode=get|get_post_peer|get_periodic|local); peer-offer propagation-sync … peer_outcome=* (not retrieval). local-prop Sync uses drain_local_inbox and returns PROPAGATION_STACK_NOT_LIVE when live is absent.
  • Host PN fabric → Chat (lxmd-style glue): When local Host is enabled, mesh-client is both PN and end-client on rsLXMF (not a second lxmd). Path: outbound deposit → host peer /offer push (generation-gated; lxmd terminal bookkeeping: sync_complete / mark_offer_generation_processed / take_handled_updates + save_peer); inbound peer Resource accept → request_inbox_drain → maintenance drain_local_inboxdelivery_callback → Chat; after host peer /offer Completes for peer P, sequenced silent client /get (retrieve_mode=get_post_peer); while serving and quiet, ~90s periodic silent /get round-robin over peered remotes (Prefer/outbound first, retrieve_mode=get_periodic) for inbox catch-up — not a timed empty re-/offer. Re-/offer when offer_generation advances, offer policy changes, or a partial sync left work; maintenance polls every ~2s but only starts work when idle. Guards: coalesce drain; one internal /get; skip when user Sync target / outbound deposit owns the hash or sync_active / client_download_active. Do not re-attach peer /offer to the Sync button. Dual full-index exchange in one Link stays upstream rsLXMF; remote inventory for re-propagation arrives when peers /offer to our serve path.
  • PN hosting: Network Advanced PN hosting / ReticulumPnHostingDangerZone; shared pnHostingPolicy.ts + sidecar pn_hosting_policy.rs / pn_hosting_apply.rs; POST /api/v1/propagation/hosting-policy; rsLXMF policy-setters overlay (ratspeak/rsLXMF#6). Messagestore loads in background on live attach; enabled local-prop serve/announce waits until load completes.
  • Interface modes: rnsd mode via reticulumInterfaceMode.ts + sidecar normalize_interface_mode (keep catalogs in sync — pnpm run check:reticulum-interface-modes in pre-commit/release.sh); add defaults TCP/UDP/I2P → boundary, RNode → access_point; UI in ReticulumInterfacesPanel; default hub presets add/repair missing mode to boundary (do not overwrite valid non-boundary). Discoverable + Full/Roaming/Boundary stamps ignore_config_warnings = Yes (reconcile_ignore_config_warnings / repair_ignore_config_warnings_in_config) so RNS does not auto-correct runtime mode to Access Point; Connection shows Effective: Access Point when live runtime_mode still diverges. See ../reticulum.md#interface-modes and ../reticulum.md#rmap-publish-and-interface-mode.
  • Share instance defaults: missing keys bootstrap to share_instance = No / instance_name = mesh-client (does not overwrite explicit Yes/default); SharedInstanceClient banner + disable_share_instance repair; offline lint via reticulum:validateConfig / Network Check config / pnpm run reticulum:config:check
  • LXMF replies: sidecar stamps FIELD_REPLY_TO / capped FIELD_REPLY_QUOTE before sign; renderer ingest/Chat use reticulum_reply_to_hash + quote preview + jump-by-hash
  • RNode flasher timeouts: RNODE_COMMAND_TIMEOUT_MS (30 s serial), RNODE_BT_PAIRING_TIMEOUT_MS (90 s BLE pairing), ESP32_FLASH_STALL_TIMEOUT_MS / NRF52_DFU_STALL_TIMEOUT_MS (60 s no-progress → ESP32_FLASH_STALLED / NRF52_DFU_STALLED); humanized via flasherErrorHumanize.ts
  • Peer aliases / History vs Contacts: LXMF/Nomad announce names overlay path-table peers; SQLite reticulum_destinations.last_heard = History, is_contact = Contacts (Save as contact only — inbound/outbound LXMF does not auto-add Contacts; sidecar /contacts wire rows are History hints unless SQLite is_contact=1); default avatars via vendored LXMFace (lib/reticulum/lxmface.ts); renderer refresh + reticulumContactToNodeRecordPreservingLabel refuse hash-prefix wipes of Chat/nodeStore labels; ingest stamps History via persistReticulumHistoryFromPayload + stampHistoryPeer; SQL upsert guard preserves real names over hash-prefix aliases; destination upsert requires exact 32-hex (lowercase) and omits favorited on icon-only patches so favorites/icons survive path/probe refresh. Chat DMs are lxmf.delivery onlyresolveReticulumChatLxmfDest.ts remaps lxst.telephony (and other non-lxmf path-table rows) to that identity’s LXMF hash via identity activity; Peers Message / paste / send refuse when no LXMF announce is known (Voice badge on telephony-only rows). Existing open/active DM tabs canonicalize onto the LXMF fold when remapped (remapReticulumChatDmTabs.ts); filters/unread alias remappable aspects via reticulumChatDmNodeIdsEquivalent.
  • RRC /who: RrcPanel sends /who <room> with K_ROOM set to the joined wire room (Python rrc-web parity; slash commands are handled before forward so this is not room chat). Never /who synthetic [hub] / @dm rooms. Empty inbound K_ROOM[hub] (resolveRrcInboundChatRoom / RRC_HUB_STREAM_ROOM), never the focused room. First /who NOTICE per named room may appear in the transcript (consumeWhoTranscriptSlot / shouldShowRrcWhoTranscript); later snapshots update the nicklist only. User-initiated Refresh / composer /who bypasses that slot. Stock rrcd emit_notice for /who/actor JOINED is a single Packet.send (no chunk/resource): if the roster exceeds Link MDU (typically 431) the hub drops it silently — observed on RNS Community 28c7… with MDU 431 while Colorado Mesh who (~300 B) succeeds. Nicklist then grows from fanout JOINED + opportunistic chat; join-info NOTICE still seeds self into the roster when actor JOINED never arrives.
  • EX1-RRCD client parity: Advertise CAP_RESOURCE_ENVELOPE / CAP_ACTION / CAP_DIRECT_NOTICE in HELLO; accept T_RESOURCE_ENVELOPE (50) with SHA256 verify + notice/motd dispatch (blob ignored); parse WELCOME hub version + limits; JOINED bodies as full list or single/bare hash with advisory K_NICK on fanout (merge, never wipe on empty); peer PARTED updates nicklist via rrc.room.peer_parted. Full busy-room roster still requires a hub that chunks /who (Ratspeak push_notice_entries) or uses send_text_smart.
  • RRC stick-to-bottom: Owned by RrcChatView + shared chatScrollUtils (TanStack Virtual followOnAppend / pin ref); keep Chat/Rooms flex min-h-0 + stream [overflow-anchor:none] parity so the message stream remains a real scroller.
  • Stores/lib: reticulumIdentityStore.ts (session-global sidecar identity status shared by useReticulumSidecarApi — distinct from identity-scoped identityStore), reticulumPeerStore.ts (path-table peers + history + saved contacts; soft-TTL reads, forced ?refresh=1, incremental peers_updated route-field patches, 50ms batching, name/appearance preservation, 30s/60s large-mesh poll), reticulumDiscoveryMapStore.ts, reticulumRmapDiscovery.ts, reticulumDiscoveryMapLayout.ts, nomadNetworkStore.ts, rrcHubStore.ts / rrcSessionStore.ts (RRC hubs + multi-hub sessions; hydrate/clear room history via rrcRoomHistory.ts; persist → SQLite rrc_messages via rrcMessagePersist.ts + ipc/rrc-db-handlers.ts; prefs in rrcHubPrefs / rrcRoomPrefs / rrcRecentRooms; notifications in rrcInactiveNotifications / rrcMention (resolveRrcAlertType + App rrcUnreadAllRoomMessages, default all-room, IRC mention/DM opt-out)); Remote (rnsh/rncp): rncpTransferStore.ts, rnshSessionStore.ts, reticulumInboundPolicyStore.ts, reticulumRemoteAddressStore.ts, rncpEnableRequestStore.ts + lib remoteSettingsStorage.ts, pushRncpListenerPolicy.ts, rncpInboundPolicyLists.ts, sendRncpRequestEnable.ts, rncpRequestEnableRateLimit.ts, applyRncpReceiveDestShare.ts / rncpReceiveDestSharePending.ts (mark pending on request-enable; consume on ingest within TTL), hooks/useRemotePathCapability.ts, components/remote/*; WS events rmap.discovery, lxmf_outbound_status, nomadnetwork.node, rrc.*, rnsh.* / rncp.* in useReticulumRuntime (sidecar also emits nomad.serving_start / nomad.serving_stop; renderer polls serving status via HTTP, not those WS events)
  • LXMF outbound delivery: sidecar lxmf_delivery.rs / lxmf_outbound.rs / pn_cascade.rs (Direct-first; after Direct exhausts multi-PN cascade: preferred remote → other enabled remotes hop-sorted → in Auto only, up to 3 heard-but-not-added Discovered PNs hop-sorted → local-prop last; intermediate WS sending + delivery_method: "propagated" or "stored_locally"; terminal delivered at remote PN vs stored_locally for local hosted PN). Local-prop is a full PN (in-process cascade deposit via accept_stamped_propagated_blob; host peer /offer sync; auto Chat drain after peer ingress + post-peer silent /get; explicit local Sync via drain_local_inbox) — not an outbox; clients need not Prefer you. Propagated link establishment timeout advances the cascade when other PNs remain (avoids Prefer-hash timeout storms). Sync vs deposit: PROPAGATION_SYNC_OUTBOUND_BUSY / PN_DEPOSIT_DEFER_ADVANCE_AFTER. Renderer applyReticulumOutboundDeliveryStatus.ts (WS lxmf_outbound_status → Zustand + SQLite delivery_status + delivery_method; early-status buffer; hash/status allowlist), reticulumOutboundFailureBridge.ts (shouldApplyLinkDeliveryTimeoutFailureBridge skips the link-timeout Failed bridge when cascade capacity remains — remote or enabled local-prop; also skips propagated / stored_locally rows so cascade is not killed), markStaleReticulumOutbound.ts. Optimistic pending rows use reticulum-pending-*; send-path rekey passes replaces_message_hash on SQLite upsert to delete the prior pending hash. Remote PN Completes UI: Stored at propagation node (ReticulumMessageStatusBadge PN + green check); local-prop Completes: deposited on your hosted node (PN + amber house; peer sync may still propagate). Mode Off has no cascade capacity, so the link-timeout bridge fails the row. Paper exception: createReticulumPaperMessage / paper create Completes immediately (delivery_method: paper, ReticulumMessageStatusBadge Paper) via lxmf_message — no lxmf_outbound_status; shared reticulumMessageTransport / reticulumPaperErrors keep IPC allowlists and i18n codes aligned.
  • DM path reachability: useReticulumDmPathProbe.ts, reticulumDmPathReachability.ts, ReticulumDmPathReachabilityBadge.tsx — Chat Probe matches Peer List (sidecar running check → /probe → toast → refresh); applyProbeResult(forHash, …) applies the settle without a second /probe and ignores stale completions after DM switch; manual reprobe forces Checking… even when passive hops look reachable; Peers virtualizes above 100 rows via reticulumPeerListRows.ts; peer refresh policy in reticulumSidecarPeerRefreshEvents.ts
  • Inbound transport labels: received_via resolves the path-table interface name against local interface config type, so a TCP hub display name still renders as TCP.
  • Topology: via_hash is an immediate transport id; sidecar synthesizes missing relay nodes. ReticulumTopologyPanel uses force layout; sidecar caps graph input at 2,000 peers, renderer ingest 800, drawn graph 400 after hop filters (same as LoRa Graph). Unknown hops only when All hops + Show distant. RF only checkbox keeps RNode/KISS/BLE spokes.
  • Retention: App defaults Reticulum destination age/count pruning to 30 days / 10,000 destinations (favorites preserved; count max 50,000); Reticulum message retention independently enabled at 4,000. RRC room history retention independently enabled by default at 10,000 messages (30-day age prune) via rrcMessageRetention* settings and db:pruneRrcMessagesByCount / db:pruneRrcMessagesByAge.
  • Self label / header: reticulumSelfNodeLabel.ts (resolveReticulumSelfHeaderLabel — Network display name in app header)
  • Nomad errors: lib/nomad/nomadPageErrorHumanize.ts (sidecar error codes → i18n); LinkClient Nomad overlay in reticulum-sidecar/patches/
  • Nomad request bodies: MessagePack field_*/var_* encode/decode lives in rsNomad nomad-core (encode_request_fields / decode_request_fields); sidecar nomad_request_payload.rs only unwraps HTTP base64 JSON before calling encode
  • LXST voice: hasLxstVoice gates Call buttons (Peers + Chat DM). Session helpers in reticulumVoiceSession.ts (dial/answer/hangup + mic PCM); UI store reticulumVoiceStore.ts; overlay ReticulumVoiceOverlay (App mount). Dedicated IPC reticulum:voiceSendAudio + push channel reticulum:voiceAudio (/ws/voice; preload onVoiceAudio); control via electronAPI.reticulum.voice.*. Runtime WS: voice.update / voice.incoming / voice.stats / voice.terminated / voice.error (errors should carry link_id when known; match by link/generation/remote). Establish-only media: Answer warms AudioContext; mic capture/TX starts only after established; sidecar soft-drops pre-establish PCM (not_established). Outbound progress tones: dial → peer DTMF fold → UK double-ring (reticulumVoiceCallTones.ts / reticulumVoiceOutcome.ts / reticulumVoiceFeedback.ts); media-start coalesces by callGeneration to avoid Answer mic thrash. Terminal reasons: treat sidecar established/terminated as completed (not fail).
  • LXMF voice memos: hasReticulumVoiceMemo gates Chat DM mic (not LXST Call). Sidecar encodes Opus/AM_OPUS_OGG via /api/v1/voice/memo/{start,audio,stop,cancel} + dedicated IPC electronAPI.reticulum.voiceMemo.* (reticulum:voiceMemoStart / voiceMemoSendAudio / voiceMemoStop / voiceMemoCancel; proxy blocked). Capture resamples to 24 kHz / 60 ms frames and serializes PCM IPC before stop. Ogg capped ~240 KiB under the 256 KiB LXMF field / default PN deposit. Key files: reticulumVoiceMemo.ts, sendReticulumVoiceMemo.ts, reticulumVoiceMemoStore.ts, reticulum-attachment-audio.ts, ReticulumVoiceMemoLine.tsx. Ingest stamps FIELD_AUDIO → attachment jail + playback (chat:readReticulumAttachmentBytes). Oversize-for-PN emits message_too_large_for_propagation (notice toast + Direct-only badge — never a PN-outage toast). Tap mic to record, tap again to send; Esc/DM-switch cancels; mic stays visible while recording even if the draft is non-empty.
  • LRGP games: hasLrgpGames gates Games tab + Challenge (Peers / Chat DM). Sidecar games_session + LrgpStore; companion games_outbound.db persists last envelope + delivery_state (LXMF outbound bridge → session chips / Resend). Dedicated IPC electronAPI.reticulum.games.* / reticulum:games* (proxy rejects /api/v1/games/*); WS games.update / games.action_result. Parity: ../reticulum-games-parity.md.
  • Gating: hasReticulumDiscoveryMap (Map tab); hasReticulumRemotePanel / hasRncpTransfer (Remote tab + Chat DM rncp); hasRrcPanel (RRC tab); hasLxstVoice (LXST Call); hasReticulumVoiceMemo (LXMF voice memos); hasLrgpGames (Games); hasReticulumInterfaceConfig / hasReticulumNetworkPanel / ProtocolCapabilities
  • rnsh/rncp: sidecar stack/{rnsh_session,rncp_transfer,path_speed,link_task}.rs + HTTP /api/v1/rnsh/*, /api/v1/rncp/*, /api/v1/remote/*; typed electronAPI.reticulum.rnsh|rncp|remote; picker-gated send/fetch paths in reticulum-remote-paths.ts; LXMF enable-request sentinel mesh-client:request-rncp-receive:v1 (rncpRequestEnable.ts); peer reply mesh-client:rncp-receive-dest:v1:<hash> autofills via applyRncpReceiveDestShare (prefer pending from markRncpReceiveDestSharePending / sendRncpRequestEnable; still apply without pending for older peers); enable-request modal + dest-share side effects deduped by LXMF message_hash (rncpLxmfControlSideEffectDedup) so catch-up cannot re-fire; already-listening auto-share is once per peer per request-enable cooldown; inbound listener config persists (rncp_listener_* in mesh_client_stack.json) and restores on live stack start
  • Sidecar RRC: rrc_codec / rrc_link / rrc_session / api/rrc.rs. Idle join-ack + /who after leaving the app alone is usually a real Link death (rrc.disconnected reason=timeout|transport_error|… will_reconnect=true), not a /who poll — RNS initiator keepalive/stale (RTT-scaled, min ~5s/10s on fast TCP) tears the Link; sidecar auto-reconnects and re-JOINs. rrc_link prefers session Closed reasons over a racing resource_offers channel close (do not trust resource_offers_closed as primary). After timeout/transport/remote close, reconnect uses DropPath+RequestPath so LRPROOF can re-attach on a live iface instead of a dead via pin.
  • Runtime: useReticulumRuntime, lib/sessions/reticulumSession.ts, lib/ingest/reticulumIngest.ts; connect starts sidecar, not ConnectionDriver RF — marks configured when HTTP + identity ready (live attach may still run); RETICULUM_CONFIGURED_EVENT wakes RRC. Cancel/stop is fire-and-forget vs cargo/BLE (START_ABORTED checkpoints; next start does not rejoin a doomed promise). LXMF/RRC proxy sends: 15 s RETICULUM_IPC_SEND_TIMEOUT_MS. RRC auto-connect (useRrcStartupAutoConnect): ~500 ms while hubs pending, ~4 s steady.
  • Diagnostics: ReticulumDiagnosticEngine.ts (Reticulum-native rows; no LoRa hop-goblin semantics) — includes reticulum/sidecar-unhealthy (60s grace; HTTP health, not listen-first ready lag), reticulum/rns-not-ready / reticulum/lxmf-not-ready, reticulum/propagation-sync-stuck, reticulum/propagation-sync-failing (1h TTL)
  • No Noble/MQTT for Reticulum's own connections (sidecar owns BLE RNode via btleplug); gate UI with hasReticulumInterfaceConfig / hasReticulumNetworkPanel / ProtocolCapabilities. On macOS/Windows, connecting a Reticulum BLE RNode may still suspend/yield Noble so it does not contend with the sidecar's BLE scan — see Multi-protocol BLE in ble-serial.md.
  • Multi-protocol BLE: see ble-serial.md for the full coexistence contract (peripheral MAC registry, scan-only mutex, Reticulum BLE RNode Noble yield on macOS/Windows).
  • Docs: ../reticulum.md, ../reticulum-sidecar-ipc.md