I can build a small AI product where the useful work is ingestion, observability, model routing, and boring economics.
Shipit News
An AI/coding news feed with source ingestion, LLM clustering, prompt caching, and per-run cost tracking.
A scheduled LLM pipeline can duplicate items, hide spend, miss cache behavior, or become expensive before anyone notices.
30-minute run design, URL dedupe, conditional RSS fetches, provider-swappable callLLM(), cache fields, and per-run cost rows.
- 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
Feed, topic, and digest.
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: model routing, prompt caching, idempotent ingestion, and per-run cost tracking. Small enough to inspect. Big enough that the instrumentation matters.
What made it hard
The lazy version is "hit GPT with the day's headlines and ask for a summary." That works once. It does not survive a 30-minute cron without quietly burning money or duplicating itself.
So the build starts with the boring parts: URL dedupe, conditional GETs on RSS, ephemeral cache on system prompts, cost rows in the database, and the same code path against local SQLite and Turso libSQL. The Claude calls are the small part.
Three trade-offs worth naming.
The short version: choice, reason, cost.
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.
Cache-control on every system prompt, with per-run cost written to the DB
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 boring economics. Cache hit rate should be a SQL query, not a guess.
The OpenAI-compatible path returns zero cache fields. Cost estimates also depend on a static price table.
Idempotent ingestion: URL-dedupe inserts and conditional GETs on RSS
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