How it works
Everything runs on your own home mesh. No accounts, no cloud, no trust required. Here is how the pieces fit together.
Federated learning
Federated learning lets the devices on your home mesh learn from devices on other meshes without sharing raw data. Only model gradients travel out -- your records stay on your home mesh.
Train locally
Each device trains a model on its own data. Your watch data, journal entries, and spending records never leave your home mesh. Training happens in PyTorch using only what is already in the local DuckDB database on each device.
Share only gradients
After training, each device sends its updated model weights -- not data -- to a coordination server. These are just numbers: the direction the model moved, stripped of anything that could identify individual records.
Aggregate and improve
The server averages the weights from all participating devices using Federated Averaging. The result is a smarter model that has learned from everyone's patterns without seeing anyone's data. Each device then downloads the improved model and the cycle repeats.
What this means for you
A single device can only learn from its own history. With federated learning, your model benefits from patterns discovered across many people -- better sleep predictions, sharper spending alerts, more relevant suggestions -- without a single row of your data leaving your home mesh. The coordination server never sees your records. It only sees the mathematical summary of what each device learned.
The system is built on Flower, an open-source federated learning framework. Model plugins can be added through the same entry point system as everything else.
Your devices, in sync
Your laptop syncs a fitness tracker. Your phone has a language app. A mesh daemon running on each device keeps them all in sync -- peer to peer, centrally orchestrated, and through any NAT.
Each device generates an Ed25519 keypair on first run and registers with the coordination server. The server stores only the public key and a device ID -- the private key never leaves your machine.
Devices discover each other through the server's registry. Each one reports its reachable endpoints -- local IPs for same-network connections, and public addresses resolved via STUN for NAT traversal.
A background daemon wakes up every 60 seconds, finds peers with unseen events, and exchanges changes directly over UDP. If the peer is unreachable, events route through an encrypted server relay instead.
Append-only sync log
Every data mutation -- a source sync, a transform run -- appends
an immutable event to a local log. Each event carries the
originating device ID, the affected table, and a timestamp.
When two devices connect, they compare cursors: "I last saw
your event evt-abc, send me everything after
that." Only new events travel. The log is never rewritten.
NAT piercing with STUN
Most home networks sit behind NAT, making direct connections between devices difficult. Each device sends a lightweight UDP probe to a public STUN server, which reflects back the device's externally visible IP and port. These endpoints are published to the coordination server so peers can attempt direct connections. When both devices are on the same local network, the local address takes priority for the fastest possible path.
Relay fallback
When direct connections fail -- symmetric NAT, firewalls, devices on different schedules -- events are pushed to the coordination server as opaque encrypted payloads. The receiving device polls and consumes them on its next sync cycle. The server cannot read the contents. It only knows which device sent a message and which device should receive it. Messages are deleted after delivery.
Data provenance
Every row produced by a transform is tagged with the device that created it. When your laptop syncs fitness tracker data and your phone syncs language-app data, each device's contributions are clearly attributed. This enables deduplication, conflict awareness, and a clear audit trail of where every piece of data came from.
One app, every device
The same interface runs on your desktop, your phone, and your browser. Your data and your surface, wherever you are.
Desktop
A Tauri 2 app wraps the web frontend in a native window. Three PyInstaller-built binaries are bundled as sidecars: the main server, the scheduler daemon, and the CLI. The app starts the server on launch, and the frontend talks to it over localhost. Everything runs in-process -- no external services, no Docker, no containers.
Mobile
On mobile, Python is replaced entirely by Rust. An axum HTTP server runs in-process alongside an embedded DuckDB instance, exposing the same API contract as the Python server. The frontend is identical -- same Lit components, same queries, same surface. The only difference is the engine underneath.
Web
The FastAPI server can also run standalone on any machine. Point a browser at it and you get the full surface with plugin discovery, telemetry, and the complete API. Useful for headless setups, remote access, or development.
Same frontend, different engines
The frontend is a set of Lit web components that communicate with the backend over HTTP and Arrow IPC. They do not care whether the server behind localhost is Python or Rust. This means the desktop app, mobile app, and web server all share the exact same UI code. Features built for one platform automatically work on all of them.
Everything is a plugin
Sources, datasets, surfaces, themes, and frontends are all independent Python packages. You install only what you need, and nothing runs unless you put it there.
Discovery, not configuration
Each plugin declares a standard Python
entry point
in its package metadata. When shenas starts, it calls
importlib.metadata.entry_points() and finds every
installed plugin automatically. No config files, no hardcoded
imports, no plugin registry to maintain.
Five plugin kinds exist, each with its own entry point group:
Signed and verified
Every plugin is distributed as a standard Python wheel served
from a PEP 503 package repository. Before a wheel reaches
that repository, it is signed with an Ed25519 key. When you
run shenasctl source add gcalendar, the CLI downloads the
wheel and its .whl.sig signature file,
verifies the signature against the known public key, and only
then installs it. If the signature does not match, nothing gets
installed.
You always know exactly what is running on your home mesh and where it came from. Trust by design, not by promise.
Write your own
A source is a Python class with two methods:
build_client() to create an API client from stored
credentials, and resources() to return
dlt resource objects that
know how to fetch and yield data. Add a
pyproject.toml with a hatchling build backend,
declare your entry point, and
shenasctl source add your-source picks it up.