I can build a small AI product where the interesting work is the plumbing: getting data in cleanly, watching what it costs, and swapping models without a rewrite.
Shipit News
An AI and coding news feed that groups the last 72 hours into stories and records what every run cost.
Feed, topic, and digest.
A pipeline on a cron will happily re-post the same story every half hour, and the bill for it turns up a month later.
It runs every 30 minutes without repeating a story, asks RSS feeds only for what changed, routes every model call through one swappable function, and writes a cost row per run.
What runs every thirty minutes.
- 01 fetch parallel · per-source
- 02 dedupe by url · idempotent
- 03 cluster 72h → topics haiku 4.5 cached
- 04 summarize topic → 2-3 graf haiku 4.5 cached
- 05 score engagement × recency
A personal AI/coding news feed across Hacker News, Reddit, RSS, Bluesky, and YouTube. Claude clusters the last 72 hours into stories, writes a short summary, and logs the cost of every run. The useful part is the plumbing: it runs every 30 minutes without duplicating items or hiding spend.
Why it exists
There are plenty of AI/coding news feeds. None of them surface the long tail I actually read: researchers on Bluesky, niche YouTube channels, and the subreddits where threads start before they hit Hacker News.
It also shows the production habits I care about: routing between models, caching prompts, ingestion that never counts a story twice, and a cost figure attached to every run. Small enough to inspect. Big enough that the instrumentation matters.
What made it hard
Summarizing the day's headlines in one model call works exactly once. Put it on a 30-minute cron and the same stories start arriving twice while the bill climbs somewhere nobody is looking.
Most of the work went into the unglamorous parts: throwing out URLs it has already seen, asking RSS feeds only for what changed, caching the system prompts, and writing a cost row for every run. One code path covers both local SQLite and Turso. The Claude calls are the small part.
What the plumbing has to guarantee.
One callLLM() over Anthropic, Gemini, and Cerebras
One call shape covers Anthropic, Gemini, and Cerebras: system prompt, user message, optional JSON tool. Anthropic gets tool_use and cache_control; Gemini and Cerebras use OpenAI-compatible chat/completions.
I can swap models or A/B runs without rewriting the pipeline. The LLM surface stays small enough to reason about.
Only Anthropic gets prompt caching today. Switching providers still works, but the bill can stop getting cheaper. New models also need prices added by hand.
Every system prompt is cached, and every run writes what it cost
Anthropic system prompts use ephemeral cache_control. Each run writes input, output, cache_creation, cache_read, and estimated USD to the database.
A 30-minute cron needs unglamorous economics. Cache hit rate should be a SQL query.
The OpenAI-compatible path returns zero cache fields. Cost estimates also depend on a static price table.
Re-running the pipeline never duplicates a story
Items dedupe by URL. RSS sources store ETag and Last-Modified, then send If-None-Match / If-Modified-Since on the next pull. No new items means the run only refreshes scores.
The schedule only works if a rerun is cheap in compute and dollars.
URL dedupe misses late momentum. A story that jumps from 12 comments to 400 after first ingest is undercounted.
The stack.
- Next.js 15 App Router
- TypeScript 5
- Tailwind 4
- @anthropic-ai/sdk
- Claude Haiku 4.5
- Gemini 2.5 Flash
- Cerebras Llama 3.3
- Drizzle ORM
- libSQL · Turso
- SQLite · local dev
- RSS · Atom · ETag
- Bluesky public API
- YouTube channel feeds
- launchd · macOS
- Vercel Cron
- Claude Code
Want the walkthrough, or the parts that aren’t written up yet?
jakerosow@gmail.com