Styrene Architecture
Document Type: Executive summary and decision record Last Updated: 2026-02-19 Status: Active
Styrene is a Reticulum-native fleet provisioning and management system for edge devices. It replaces fragile bash installer scripts with a robust, mesh-connected architecture enabling SSH-free device management through encrypted LXMF messaging.
Document Hierarchy
| Document | Scope | When to Reference |
|---|---|---|
| architecture-decisions.md (this) | Decisions, integration points | Architecture questions, new contributor onboarding |
| mesh-vpn-architecture | WireGuard tunnels over LXMF signaling, transport tiers | Tunnel implementation, mesh VPN work |
| rust-migration | Rust parallel implementation, crate architecture | Rust crate work, interop testing |
| PROVISIONING-VISION | Provisioning flow, Hub, fleet communication | Backend/protocol implementation |
| STYRENE-TUI-VISION | TUI screens, UX, implementation phases | Frontend/UI implementation |
| Published Docs | User-facing documentation | End-user reference |
| Research: Organic Docs | Molecular metaphor, polymer architectures | Conceptual understanding |
Distribution Model
Key Decision: Isolated Package Dependencies
Styrene is distributed as independent Python packages with explicit dependency relationships. This keeps coupling at the package boundary (dependency declaration + IPC/wire protocol) rather than deep library entanglement, enabling future rewrites of individual components in other languages (e.g., Rust).
styrened (pip install styrened)
├── Standalone daemon + CLI + library
├── LXMF messaging core (NomadNet/MeshChat wire-compatible)
├── Styrene protocols (RPC, discovery, terminal)
├── IPC control server (Unix socket)
├── Optional: [tui] extra — Textual-based operator interface (styrened.tui subpackage)
├── Optional: [web] extra — HTTP API endpoints
├── Optional: [metrics] extra — Prometheus metrics
└── Installable alone for headless/edge use; pre-installed in Styrix NixOS
styrene (pip install styrene)
├── PyPI meta-package — installs styrened[tui]
├── Extras: [web], [metrics], [yubikey]
└── User-facing install target (pip install styrene = full operator stack)
styrene-edge (git clone, not pip — device support repo)
├── sbc/ NixOS configurations + flake (Styrix)
├── Device profiles YAML (consumed by TUI at runtime via remote fetch)
├── Polymerize scripts
└── Contributors add device support here; operators never clone it
styrene community hub (styrened + nomadnet, deployed on K8s)
├── RNS transport node (rns.styrene.io:4242) — relays announces between peers
├── LXMF propagation node — store-and-forward message delivery
├── NomadNet BBS / hosted pages
└── Fleet coordination
Installation scenarios:
- Headless edge device:
pip install styrenedor pre-installed via Styrix NixOS - Operator workstation:
pipx install styrene - Community Hub:
styrened[web,metrics]+nomadnetin container - Device contributor:
git clone styrene-edge, work locally, submit PR
Key Decision: TUI as Subpackage
The TUI was originally a separate repo (styrene-tui), now archived. It lives as styrened.tui, installed via the [tui] extra. This keeps the entire operator stack in one package with one version number. The styrene PyPI meta-package provides a user-friendly install alias.
Key Decision: TUI Manages Daemon Lifecycle
On Styrix edge devices, styrened runs as a permanent systemd service. On macOS, a launchd plist manages the daemon. On an operator’s workstation, the TUI manages styrened’s lifecycle — starts it on launch, tears it down on exit. No systemctl enable required to use the TUI. The IPC contract (Unix socket) is the same in both cases; the TUI just owns the other end of the socket when there’s no system daemon.
Key Decision: styrene-edge as Upstream Device Support
styrene-edge is a device support repository, not an application. It’s the equivalent of a firmware tree or board support package. Contributors work there to add new device patterns (NixOS configs, device profiles, polymerize scripts). Operators never clone it.
The TUI’s provisioning flow fetches device profiles and NixOS configs from styrene-edge at runtime (GitHub raw, release artifact, or Cachix), the same way it fetches NixOS base layers from nixpkgs or the Cachix binary cache. When a contributor merges a new device PR, it appears in the TUI’s catalog on next fetch.
System Components

Component Details:
- TUI (styrened.tui) → styrene-tui-vision.md
- styrened (Device Daemon) → provisioning-vision.md#styrened
- Styrene Hub → provisioning-vision.md#styrene-hub
Network Architecture
Layer Model

In OSI terms, Reticulum replaces layers 3–6 (Network through Presentation) with cryptographic identity-based routing and end-to-end encryption. BATMAN-adv handles layer 2, and physical transports (WiFi, LoRa, Ethernet) provide layer 1. Standard internet protocols can coexist on the same hardware — the stack is backward-compatible, not a replacement.
Key Decision: BATMAN-adv + Reticulum (Dual Layer)
Why two layers? They solve different problems:
-
BATMAN-adv (L2): “How do I get an Ethernet frame from radio A to radio B across 3 hops?”
- Uses MAC addresses, measures real throughput, adapts to interference
- Creates virtual switch across mesh nodes
-
Reticulum (Overlay): “How do I send an encrypted message to cryptographic identity X?”
- Uses 128-bit destination hashes, transport-agnostic
- End-to-end encryption, identity-based routing
Reticulum sees BATMAN-adv’s bat0 interface as just another network interface. The layers complement each other.
→ Details: provisioning-vision.md#layer-2-mesh-batman-adv
Key Decision: Graceful Degradation
The network stack is designed to degrade gracefully as connectivity drops:
| Level | Available | Styrene Behavior |
|---|---|---|
| Full | Internet + BATMAN-adv + RNS | Standard ops, packages from cache.nixos.org + styrene.cachix.org, full fleet management |
| LAN only | BATMAN-adv + RNS | Fleet management continues, no upstream packages, Hub serves cached content |
| Mesh only | RNS (WiFi or LoRa) | RPC, status, terminal sessions — all core fleet ops work without IP connectivity |
| Offline | Nothing | Device runs autonomously on last-known config |
BATMAN-adv provides the IP-compatible mesh for conventional traffic. RNS maintains mesh topology and path establishment. If proper backbones drop out, operations degrade to RNS-only — all fleet management commands, status checks, and terminal sessions continue to function because they ride LXMF, not IP.
When high-bandwidth transports are available (802.11s WiFi, Ethernet), nodes can automatically promote to WireGuard tunnels negotiated via LXMF — providing full IP connectivity at line speed while maintaining LXMF signaling as the control plane. See mesh-vpn-architecture for the 4-tier transport model and tunnel negotiation protocol.
Key Decision: Styrix (NixOS Edge Flavor)
Decision: The NixOS configuration for edge fleet devices is named Styrix (Styrax + Nix). Styrix is NixOS with styrened, RNS, BATMAN-adv, and the Imperial CRT console theme baked in.
Rationale:
- From the Styrax tree (etymological origin of “styrene”) + Nix — places it in the styr- naming family (styrene, styryl, styrax, storax, styrix)
- Not a fork — Styrix is opinionated NixOS configuration, not a separate distribution
- Cross-platform polymerization doesn’t need per-platform names (“Styrene for Android”, “Styrene for Mac”), but the purpose-built NixOS edge substrate earns its own identity
Scope: Styrix applies to edge fleet devices running NixOS. Operator workstations, containers, and non-NixOS platforms simply run styrened.
Key Decision: NetworkManager for NixOS Fleet
Decision: Standardize on NetworkManager with 802.11s mesh for all Styrix fleet devices.
Rationale:
- Consistent interface across RPi4B, Zero 2W, T100TA, x86 (MiniGMK, Q502L)
- Robust 802.11s mesh support with automatic reconnection
- ~15 MB RAM overhead acceptable even on 512 MB Zero 2W
- nmcli/nmtui available for debugging
Implementation: batman-mesh.nix module in styrene-edge/sbc/common/
Configuration Decisions
Mesh Key: Fleet-Wide Shared
Decision: Single PSK shared across all devices in a fleet.
Rationale:
- Reticulum provides end-to-end encryption for all application traffic
- Mesh key only controls L2 access (who can join the mesh)
- Small trusted fleet doesn’t benefit from per-device key complexity
- Easy rotation: change in Vault, re-provision or push via Polymerize RPC
Storage: Vault secret, injected at provision time.
Gateway Mode: Per-Instance Configuration
Decision: Gateway mode is deployment-specific, not device-type specific.
| Role | Gateway Mode | Purpose |
|---|---|---|
| Hub Node | server | Bridges mesh to LAN, provides DHCP |
| Mesh Routers | off | Pure L2 mesh coordination |
| Fleet Devices | client | Uses gateway when available |
Offline Operation: Set all nodes to gw_mode=off. Mesh works for internal communication without internet.
Configuration: Set in mesh.conf (OpenWrt) or NixOS module option at provision time.
Styrix Module: batman-mesh.nix
All Styrix fleet devices import a common module providing:
batman-advkernel modulebatctlpackage- NetworkManager 802.11s mesh profile
- systemd service to attach interface to
bat0
Options:
styrene.mesh = {
enable = true;
meshId = "styrene";
meshKey = "fleet-key-from-vault";
channel = 6;
gwMode = "client"; # or "server" or "off"
};
TUI Integration Points
| Component | TUI Surface | Phase | Reference |
|---|---|---|---|
| Storage detection | Provision screen device picker | 1 ✓ | Absorbed from Forge |
| Device specs | Config validation, partition sizes | 1 ✓ | Fetched from styrene-edge at runtime |
| Media preparation | Progress bar (Forge, absorbed) | 1 ✓ | TUI provisioning module |
| LXMF inbox/chat | Inbox, conversation screens | 2 ✓ | styrened ConversationService via IPC |
| Mesh status | Dashboard panel (bat0 neighbors) | 2 | batctl n data |
| Fleet devices | Device list with live status | 2 ✓ | styrened RPCClient via IPC |
| Hub content | Browser screen [TODO] | 3 | NomadNet pages |
| Device actions | Shell, Config, Logs, Reboot | 2 ✓ | styrened RPCClient via IPC |
Implementation Phases
| Phase | Focus | Status | Key Deliverables |
|---|---|---|---|
| 1 | Local Provisioning | ✅ Largely Complete | Forge (media writer, bundle builder), polymerize.sh, SD images |
| 2 | Mesh Integration | ✅ Largely Complete | styrened daemon, wire protocol v2, RPC |
| 3 | Fleet Operations | Pending | Batch ops, Hub, config versioning, audit |
→ Details: styrene-tui-vision.md#implementation-phases
Wire Protocol Summary
| Aspect | Choice |
|---|---|
| Transport | LXMF over Reticulum (FIELD_CUSTOM_TYPE/DATA) |
| Serialization | msgpack (NOT Protobuf) |
| Wire Format | [styrene.io:][version:1][type:1][request_id:16][payload] |
| Architecture | Python only (styrened daemon, no Go component) |
Message Type Ranges (StyreneMessageType enum):
- 0x01-0x0F: Control (PING, PONG, HEARTBEAT)
- 0x10-0x1F: Status (STATUS_REQUEST, STATUS_RESPONSE)
- 0x40-0x5F: RPC Commands (EXEC, REBOOT, CONFIG_UPDATE)
- 0x60-0x7F: RPC Responses (EXEC_RESULT, REBOOT_RESULT)
- 0xC0-0xCF: Terminal Sessions (TERMINAL_REQUEST, TERMINAL_ACCEPT)
- 0xD8-0xDE: Tunnel Negotiation (TUNNEL_OFFER, TUNNEL_ACCEPT, TUNNEL_REKEY)
→ Details: provisioning-vision.md#fleet-communication-protocol → Tunnel protocol: mesh-vpn-architecture
Key Decision: Rust Parallel Implementation
Decision: Fork FreeTAKTeam/LXMF-rs as styrene-lab/styrene-rs and develop a parallel Rust implementation of the RNS/LXMF stack. Python remains primary until the Rust stack passes the interop gate.
Rationale:
- The distribution model already specifies “coupling at the package boundary (dependency declaration + IPC/wire protocol) rather than deep library entanglement, enabling future rewrites of individual components in other languages”
- Rust provides native performance, static binaries, and lower memory for constrained edge devices (Pi Zero 2W)
- LXMF-rs is the most complete non-Python RNS implementation (TCP/UDP transport, identity, destinations, links, LXMF router/propagation)
- Wire protocol is the contract — no FFI, no shared memory, implementations communicate over LXMF
Scope: Rust replaces Python on resource-constrained edge devices (Phase 5). Python keeps TUI, Hub, and operator workstation roles. Both implementations coexist on the same mesh.
Details: rust-migration
Key Decision: Community Hub as Default Infrastructure
Decision: Every new install connects to the Styrene Community Hub by default for both RNS transport and LXMF propagation.
Implementation:
COMMUNITY_HUB_PROPAGATION_HASHconstant inmodels/config.py— the stable LXMF propagation destination hash, derived from PVC-backed identity on the hubget_profile_defaults(Profile.OPERATOR)setslxmf.propagation_destinationto this hashrns.styrene.io:4242included as default peer for all profiles- Hub runs
autopeer: false(it IS the propagation node, not a client seeking one) - Hub runs
enable_propagation()vialxmf.propagation_node.enabled: true - TUI Network tab exposes a single STYRENE COMMUNITY HUB toggle (default ON) that controls both the transport peer and the propagation destination
Rationale: Zero-config mesh connectivity. New users get store-and-forward messaging immediately without understanding transport vs. propagation. Power users can disable the community hub and point to custom infrastructure.
Open Questions
| Question | Status | Notes |
|---|---|---|
| Hub hosting (K8s vs dedicated) | Pending | K8s on brutus for now; revisit for off-grid scenarios |
| Identity lifecycle/revocation | Pending | Need revocation list specification |
| Offline provisioning | Pending | Pre-cache everything on USB? Separate offline mode? |
Document Maintenance
Keeping Documents in Sync
When updating Styrene architecture:
- Decision changes → Update architecture-decisions.md first, then update detail docs
- Implementation details → Update vision docs, verify architecture-decisions.md links still valid
- New components → Add to architecture-decisions.md with link to new/existing detail doc
Drift Prevention Checklist
Before committing changes to any Styrene document:
- architecture-decisions.md reflects current decisions
- Cross-document links are valid
- No contradictions between documents
- Revision history updated in modified docs
Document Ownership
| Document | Scope | Update Trigger |
|---|---|---|
| architecture-decisions.md | Decisions, integration | Any architectural change |
| provisioning-vision.md | Provisioning, Hub, wire protocol | Backend/protocol changes |
| styrene-tui-vision.md | TUI screens, UX | Frontend/UI changes |
| styrene-edge/router/README.md | OpenWrt mesh routers | Router config changes |
| styrene-edge/sbc/README.md | NixOS fleet devices | SBC config changes |
Claude Code Directive
When working on Styrene-related code:
- Read architecture-decisions.md first for current decisions
- Follow links to relevant detail docs for implementation specifics
- After making architectural changes, update architecture-decisions.md
- Verify cross-document consistency before committing
References
- provisioning-vision.md - Full provisioning architecture
- styrene-tui-vision.md - TUI design and phases
- Reticulum Documentation
- BATMAN-adv Documentation
- NetworkManager 802.11s
Revision History
| Date | Change |
|---|---|
| 2025-01-20 | Initial architecture document created |
| 2026-02-01 | MAJOR CORRECTIONS: Wire protocol is msgpack (not Protobuf), Python only (no Go); “Polymerize” (formerly “Bond”) renamed to “styrened”; Phase status updated; component diagram updated; fixed broken anchor links |
| 2026-02-01 | styrened v0.3.6: TerminalService implemented, added ConversationService, ReadReceiptProtocol, NodeStore |
| 2026-02-13 | Added OSI layer mapping, graceful degradation decision, updated Layer Model table |
| 2026-02-18 | Phase 1 status updated (Forge replaces planned scripts); edge-fleet→styrene-edge naming; added Cachix to graceful degradation; updated device list and document ownership paths |
| 2026-02-18 | Named NixOS edge flavor Styrix (Styrax + Nix); renamed “Bond” to “Polymerize” across all docs; added Monomer to glossary |
| 2026-02-24 | Added Rust Parallel Implementation key decision; linked to rust-migration |
| 2026-02-19 | MAJOR UPDATE: Distribution architecture and TUI scope |
| — Added Distribution Model section: isolated package dependencies, dependency graph | |
| — TUI absorbs Forge provisioning (no standalone Forge binary) | |
| — TUI replaces NomadNet/MeshChat as operator’s LXMF client | |
| — TUI manages styrened lifecycle on operator workstations (no systemd required) | |
| — TUI communicates with styrened via IPC (not direct library initialization) | |
| — styrene-edge becomes upstream device support repo (fetched at runtime, not cloned) | |
| — Added gaming handheld devices (RG35XX H, R36S) to styrene-edge | |
| — Updated system component diagram with new architecture | |
| 2026-03-01 | TUI merged into styrened as subpackage (styrened.tui, [tui] extra) |
— styrene-tui repo archived; styrene PyPI meta-package wraps styrened[tui] | |
| — Added Community Hub as Default Infrastructure decision | |
| — v0.10.77: Community hub propagation hash, Network tab 7-panel redesign | |
| — Updated distribution model and installation scenarios |