I Brought Google Reader and Yahoo Pipes Back From the Dead with AI at it's core!
This is a submission for the Hacktoberfest Open-Source AI Challenge Week 4: Ghost in the Machine Google Reader closed in 2013. Yahoo Pipes closed in 2015. Nothing has replaced either of them, and I have been quietly annoyed about it ever since. Reader was the calm inbox: unread counts, stars, j and

Posted on Nov 1 • Edited on Nov 1 • AI-assisted
Hacktoberfest Open-Source AI Challenge: Ghost in the Machine Submission 👻🌼
This is a submission for the Hacktoberfest Open-Source AI Challenge Week 4: Ghost in the Machine
Google Reader closed in 2013. Yahoo Pipes closed in 2015. Nothing has replaced either of them, and I have been quietly annoyed about it ever since.
Reader was the calm inbox: unread counts, stars, j and k, no algorithm deciding what I saw. Pipes was the clever one: drag a few boxes onto a canvas, wire them together, and get a brand-new feed out the other end. Both were killed because someone else owned the server.
So for "Ghost in the Machine" I put the two ghosts in one body. Tributary is a self-hosted feed reader whose feeds you shape with a visual pipe builder, and a small local Gemma sits inside as one of the boxes. It runs on my 2013 laptop: 8 GB of RAM, no GPU, Ubuntu.
Two dead products, one app. Here is what I kept from each:
The first ghost is the lost product: the thing you loved that vanished. The second is the one in the title, the model inside the machine, doing a small job quietly on hardware that should have been retired years ago.
A pipe is an ordered list of boxes, and every box takes a list of items and returns a list of items. That one rule is the whole engine.
A pipe I actually use looks like this on the canvas:
fetch (Hacker News + 3 blogs) -> dedupe -> fulltext -> meaning ("practical engineering, no hype") -> summarize -> sort -> limit Enter fullscreen mode Exit fullscreen mode Run it and it comes back as a feed in the reader, in order, with one-sentence summaries. That is the Pipes idea finally sitting inside the Reader idea.
VIDEO DEMO COMING SOON There is no public demo link, and that is deliberate. The self-hosted build has no login, so putting it on the open web would hand everyone a free reader and a free model on my bill. The hosted version needs accounts and per-user limits first. That is the cloud tier, and it is coming soon.
Run it yourself in about ten minutes. You need Python 3.11+, uv, Node with pnpm, and Ollama.
git clone https://github.com/Ekomjah/tributary cd tributary ollama pull gemma3:1b cp.env.example.env cd backend uv sync uv run tributary init-db uv run tributary serve Enter fullscreen mode Exit fullscreen mode In a second terminal:
cd frontend pnpm install pnpm dev Enter fullscreen mode Exit fullscreen mode Or you can simply run make setup, then make dev to spin it all up quickly
Open http://localhost:3000, click Add feed, and paste https://news.ycombinator.com/rss. Paste a blog's homepage instead and Tributary will look for its feed. Then run uv run tributary doctor to see what is wired up.
Two things are optional, and both are off until you set a key in.env:
A free-tier key for either is enough to try the features
A box is a small list transform. This is the shape of every one of them:
class WhyCareBox: box = " whycare " def run ( self, items: list [ ItemOut ], context: RunContext ) -> list [ ItemOut ]:... Enter fullscreen mode Exit fullscreen mode Because every box has the same signature, boxes compose in any order, and each one is easy to test. The backend validates a pipe's JSON with strict Pydantic models before a run starts, so unknown keys and bad values are rejected up front.
The model's job is small and the failure mode is boring. I did not want a 1B model making decisions it cannot be trusted with, so the boxes lean toward keeping things:
Answers are cached by (item, box, params hash, model). The hash includes a prompt version and a SHA-256 of the text, so an edited article or a changed criterion misses the cache, and repeating a run is free. That matters on a CPU-only laptop. Model errors report the host and failure type, never the prompt or the article.
Search starts with a local model. You write "AI regulation this week" and Gemma rewrites it into a structured query before SerpApi sees anything:
"AI regulation this week" | v Ollama refinement (local) -> q, engine, gl, hl, freshness, max_results | v SerpApi (cloud, opt-in) | v normalize -> items -> six-hour cache -> pipe / reader Enter fullscreen mode Exit fullscreen mode The refinement uses a Pydantic schema at temperature 0, an allowlisted set of engines, clamped result counts, and a fallback to your raw query if Ollama is down or returns bad JSON. The CLI prints the query it sent, so you can see what the model did to your words.
RSS feeds don't deliver data in the same structure There is no way to guess the structure in which an RSS feed gives us it's data. While one may simply dump the article's text in the XML, others may just include a link to it with a brief summary of the article. This was a challenge since m RSS file had to properly display it's reader data without making it's users go somewhere else to read more on it. I had to use BeautifulSoup and trafilatura python packages to fetch and transform inconsistent RSS feeds into well formatted HTML to be parsed by the reader
My first summaries summarized nothing. The Hacker News feed does not contain the articles. Each entry is a title and a line that says Article URL: https://.... So my first Summarize box was faithfully summarizing a link. The fix was the fulltext box, which fetches the page and extracts the body with trafilatura, and a rule that Summarize only reads real text.
A blog URL "worked" and gave me nothing. I pasted a blog's homepage, got a 200 response, zero items, and a healthy-looking feed. I asked a coding agent to audit my fetcher and it returned 34 findings. The worst one was exactly this: an HTML page was being counted as a successful fetch. The fetcher now checks that the response really is a feed, rejects empty bodies and HTML pages, follows redirects one hop at a time and re-checks every hop so a public feed cannot bounce into a private address, caps responses at 2 MB, and has a discovery step that finds a feed from a blog URL.
Apostrophes arrived as ’. Feed text carries HTML entities, and my cleanup skipped short snippets. Titles and summaries are now normalized and feed HTML is sanitized before it is stored.
Google Reader and Yahoo Pipes are not gone because they were bad. They are gone because they lived on someone else's server and that someone changed their mind. That is the whole argument for open, and it shaped Tributary:
Where a closed model would be better. A frontier model would almost certainly judge "is this practical engineering without hype?" better than a 1B model, and would write sharper summaries. I have not measured the gap, so I will not put a number on it. The honest trade is quality against ownership, and for a tool I want to still have in ten years I chose ownership. There is also an opt-in cloud model ( gemma4:3b through Ollama Cloud) for people who want a stronger model. When it is on, article text and prompts do leave your machine, and the docs say so.
The tools that quietly let you read what you wanted, in the order you wanted, until someone shut the server off.
The cloud tier. The code is built around the same service layer, with PostgreSQL and Auth0 accounts, kept as an adapter rather than a fork so the self-hosted build never grows a login screen. I am not opening signups until per-user limits and abuse protections are in. After that: a built-in scheduler, durable pipe runs, OPML, and an evaluation set from my Keep/Drop labels so I can finally try a fine-tune and report numbers, whether or not they flatter me.