Architecture
The same Rust daemon runs on every device as a client and on the home server as the server. Same code, same database schema.
flowchart LR
src["Files · Mail · Notes"] --> client
subgraph client["Device — client"]
db[("SQLite<br/>full text · vectors")]
ui["CLI · Emacs · Web"]
end
subgraph server["Home server"]
api["API + job queue"]
blobs["Blob store"]
end
worker["ML worker<br/>OCR · embeddings"]
client <-->|"sync: operation log"| api
api --- blobs
api --> worker
Key ideas
- Files stay files. Indexed files remain in the file system. Moved or renamed files are recognised by their hash.
- The server holds everything. Clients subscribe per folder: metadata only, plus extracted text and thumbnails, or fully offline.
- Derived data is computed once — text, OCR, thumbnails, embeddings — on the server, never on weak devices.
- Sync is an operation log with hybrid logical clocks; the last writer wins per field.
- Private by default. All traffic stays inside a private network (Tailscale/Headscale).
Stack
| Layer | Choice |
|---|---|
| Core | Rust (tokio, axum) |
| Storage | SQLite with FTS5 and sqlite-vec, statically linked |
| Blobs | File system, keyed by BLAKE3 hash |
| ML | Python worker for OCR and embeddings |
| Deployment | One Debian package; Docker Compose on the server |