← Writing

July 9, 2026

My stdlib-only plugin grew a podcast, a reply channel, and a requirements.txt

A month ago I deleted my 17,000-line research app and replaced it with a Claude Code plugin (the writeup). The bet was that four slash commands and a nightly Slack digest cover everything the web app did. The bet mostly held, and two gaps showed up by week two.

Papers longer than about fifteen pages sat in my inbox unread. And the digest was a one-way broadcast: when Slack showed me something interesting at 7am, there was nothing to do about it until I was back at a desk.

v2.1 shipped on July 9 to close both: 820 lines across 24 files, built from a written plan that subagents on cheaper models executed task by task while a larger model reviewed each diff. The four commands are now six. The diagram above shows where the new pieces plug in.

A podcast instead of a backlog

/scribe:episode turns the day's top paper into a two-host audio episode, NotebookLM-style: a skeptic and an expert arguing through the paper for 40 to 60 turns. The script is JSONL, one turn per line, and every factual claim carries an inline citation like [§4.2] pointing at the section of the paper it came from. If the model can't cite it, the rule is it doesn't get said. The markers are stripped before audio and kept in the show notes.

Everything after the script is a 183-line Python file with no model in it: edge-tts renders each turn (free Microsoft neural voices, one per host), ffmpeg concatenates with 300ms gaps and normalizes loudness, feedgen regenerates an RSS feed. The feed sits behind an unguessable URL path because podcast apps can't do authentication. Subscribe once in any podcast app and a 12 to 15 minute episode about yesterday's best paper shows up like any other show.

The quality lives entirely in the dialogue prompt. The TTS and stitching were an evening; I expect to be iterating on the script instructions for months.

Replying to the digest

A 158-line daemon (bridge.py, a systemd unit on the same Proxmox LXC that runs the nightly scan) long-polls Telegram, so there's no public endpoint to secure. Three commands do the things I kept wishing I could do from my phone:

Anything else is treated as a question. The bridge fetches the referenced paper's sections and spawns a headless claude -p run to answer, grounded in the actual text. Ask "why is #2 relevant to me?" and the reply quotes the paper's sections, not a paraphrase of the abstract. Those question runs and the nightly dialogue script are the only model calls in the whole v2.1 surface; the bill scales with the two calls a day that actually require judgment.

The original spec called for a Haiku call to classify each inbound message as command, question, or feedback. I replaced it with a regex over three verbs. It covers the whole verb set, costs nothing, and the classifier can come back when the verbs outgrow a regex.

Two things bit me here. First, my reference regex matched digits inside arXiv IDs: "why is 2606.11111 relevant" matched 06 and silently grounded the answer in row 6 of the digest. A lookahead excluding dots fixed it, and that case is now in the test file. Second, the headless claude -p call inside the bridge consumes paper text fetched from the internet, which makes it a prompt-injection target: a crafted abstract could tell the model to do something with tools. I assumed --disallowedTools "*" would lock it down. Tested it; the model happily ran Bash anyway. Only an explicit list of tool names actually denies them. The Q&A run now gets every tool banned by name, and the bot answers nobody outside an allowlist of Telegram user IDs.

Interest packs

interests.md turned out to be the highest-leverage file in the system, so v2.1 makes it shareable. A pack is one markdown file with the same schema plus frontmatter; /scribe:interests install agentic-ai merges one into your active interests with semantic dedupe. Five seed packs ship in the repo (ml-systems, agentic-ai, security, distributed-systems, quant), and CONTRIBUTING.md exists mostly to invite pack PRs. It's the cheapest feature in the release and probably the one most useful to anyone who isn't me.

What deploying it broke

The deploy step in deploy.sh ran pip3 install -r requirements.txt with errors suppressed, because a notification failure should never block a scan. Debian 12 refuses system-wide pip installs (PEP 668), so the install failed silently and the first episode run would have crashed at import. The fix falls back to pip3 install --user --break-system-packages. ffmpeg also wasn't on the container; that one was just an apt install, but nothing in the pipeline would have told me before the 06:15 cron did.

The quieter cost: v2 shipped with a stdlib-only guarantee, no installs anywhere. v2.1 ends that. The core scan path is still pure stdlib, but the podcast and bridge pull in five pip packages and a systemd unit. That's a real step back toward the machinery I deleted in June, and I took it with open eyes, because "listen to papers while walking the dog" was worth a requirements.txt. The line I'm holding is that judgment stays in prompts, plumbing stays in Python, and state stays in files a human can read.

Trying it

The repo is at github.com/mukitmomin/scribe:

/plugin marketplace add mukitmomin/scribe
/plugin install scribe@scribe
# write ~/.scribe/interests.md, or: /scribe:interests install ml-systems
/scribe:scan

The podcast and bridge are opt-in extras that need requirements.txt and ffmpeg on whatever box runs your cron; the README covers both, and docs/superpowers/plans/ has the implementation plan the subagents executed. A month ago I traded features for a tool I actually use. This month I traded a little purity for a tool that talks back.