Skip to contentAbhinav Modi
← Projects

Watcher

RSS feeds → deduplicated stories → one summary each

An AI news feed that groups articles about the same story, so you read it once, with one summary per story.

Live, updated weekly

Live site ↗

Python · FastAPI · Postgres + pgvector · PydanticAI · OpenAI · Next.js

Watcher's feed, one card per story, each with its momentum score and how many sources covered it

Input

Long-form engineering blogs (AWS, Meta Engineering, Google Research, BAIR, GitHub, Julia Evans, Simon Willison, The Pragmatic Engineer), AI news feeds and Hacker News. A GitHub Actions cron runs the pipeline.

Constraints

A personal budget: under ₹1,000 a month for every model call. Each model in the pipeline costs a different amount per token, so every "send more text" change gets priced stage by stage.

In September, giving the embedder 8,000 characters instead of 2,000 cost about three cents for the whole corpus. Sending the classifier the full text would have added about $0.75 a run, for no gain I could measure. So the embedder got the wider window, and the classifier kept its 4,000 characters.

How it works

StepWhat it does
ingestReads each feed, keeps the full post body, and drops duplicate URLs.
classifyTags each article with its topics, a relevance score, a noise flag and its key entities, in one structured call.
embedTurns each article into a vector and stores it in pgvector.
clusterJoins an article to a story when the vectors are very close, or close and they share a key entity. A story closes a week after its first article.
summarizeWrites one summary per story, and rewrites it when the story gains articles.
rankOrders stories by momentum. Recent coverage counts more, and older coverage fades.

Read it from the bottom up. One summary per story needs every article about that story in one place. That needs a way to tell "same story" from "same topic", which a single similarity threshold can't do. So the cluster step also checks for a shared key entity, and the entities come free from the classifier call that was already there.

Output

One short summary per story on the live site: what happened, and what it means.

EvalLabelled by handResult
Classifier50 articles, blindNoise precision 0.91, recall 0.83
Clustering13 article pairsSame story ≥ 0.61, different ≤ 0.32

Both agents moved from Claude to OpenAI's GPT-6 Luna in October. On Claude Haiku the classifier scored 0.96 and 0.92. Luna scores lower but passes every target, at about an eighteenth of the cost per article. The clustering numbers are from June, on news feeds.

If I rebuilt it

I tuned the clustering rule on news, where ten outlets cover one story. Then I moved the feeds to engineering blogs for the retrieval work, and a shared entity stopped meaning "same story" and started meaning "same publisher". AWS and Google Research turned into cluster magnets. I'd re-run the clustering eval before changing the corpus, not after.

Next

Grounding. Every claim on a card will cite the article text it came from, and a checker will verify its numbers and dates against that text.