A self-hosted personal news digest that runs on your own hardware. A local
LLM scores, summarizes, and de-duplicates your feeds, then learns what you
actually read. No cloud, no accounts, no tracking.
- Live site & docs: cruxwire.app
- Source (MIT): github.com/philoking/cruxwire
- Status: Open source · self-hosted · actively running on my homelab
What it is
Cruxwire is a news reader in a single Docker container. Point it at your RSS/Atom
feeds and your own Ollama server, and a local model turns
the firehose into a clean, ranked digest of what’s actually worth reading —
scored, summarized, and de-duplicated, all on hardware you control.

It’s deliberately small: one Python process serving the UI and an in-process
scheduler, a vanilla-JS single-file frontend, and state kept as plain JSON on a
volume. No database, no build step, no external services beyond your feeds and
your Ollama endpoint.
How it works
On a schedule (every couple of hours during your waking window, plus once on
start), each run:
- Fetches & filters your feeds, dropping anything too old or on your
blocklists. - Carries forward unread stories so a good piece doesn’t vanish when its
feed rotates it out. - Scores, summarizes & embeds each article 0–10 via your local model.
- Clusters same-story coverage across outlets into a single card.
- Retains within a floor/ceiling band by rank-weighted lifespan.
- Writes the new digest atomically — the frontend picks it up on next load.
Ranking blends four signals: the model’s relevance score, a bounded
cross-source coverage boost, an embedding-based “taste” match to what you save and
open, and a learned per-source affinity multiplier (0.5×–2.0×) that moves with
your opens, saves, and dismisses.
Retention keeps unread stories between a floor and a ceiling. Each story’s
lifespan scales with its rank — the best linger for up to a few days, weak ones
age out fast — with a hard age cap so nothing stale lingers. Read Later is
curated by hand and never expires.

Features
- LLM-ranked digest — 0–10 relevance, a one-line summary, and a category for
every article, from your local model. - Source-agnostic dedup — same story across outlets collapses into one card
with the rest of the coverage underneath. - Automatic personalization — learns from what you open, save, and dismiss.
No manual tuning. - On-demand TL;DR — summarize the real article body of a saved story, not
just the feed blurb. - Semantic search — find stories by meaning, not keywords, on local
embeddings. - Blocklists — literal keywords plus a semantic “topics to avoid” filter, on
top of a built-in spam/deal filter. - Live settings — every threshold, schedule, and retention band is editable
in-app and applies on the next run, no restart. - Multi-device state — read state syncs through the server and works offline
from local cache.
Privacy
Nothing leaves your network. No accounts, no analytics, no third-party calls
beyond fetching your feeds and talking to your Ollama box. Scoring, embeddings,
and search all run locally. Cruxwire ships with no authentication by design —
it’s built for a single trusted user on a private network, so keep it on your LAN
or a Tailscale/WireGuard tailnet, never exposed directly to the internet.
Running it
If you can run docker compose up, you can run Cruxwire. You’ll need Docker and a
reachable Ollama server with a chat model and an embedding model pulled (defaults:qwen2.5 and nomic-embed-text). The setup guide
walks through requirements, the Compose file, and tuning.

Tech: Python (standard library) · Ollama · vanilla JS · JSON-on-a-volume ·
Docker · MIT licensed.ur own hardware, and then gets out of your way. It’s effectively killed doom scrolling for me.