Every podcast had to be worth somebody's time to make, so shows get made about topics broad enough to pay back the hours. The NFL gets forty podcasts, and nobody makes one about the four open-source projects you actually depend on. The topics I care about are too narrow to be anybody's job.
An episode of Nauracast costs about twenty-five cents to produce. At that price an audience of one works.
The name was a guess. I picked it thinking aura meant sound; it actually means a breeze, from the Greek for a breath of moving air. Close enough, since sound is moving air. The word for ear is auris, where aural comes from, and the two aren't related at all. Names are hard.
The proof of concept
I started with a town, because everything a local news show says can be checked against somebody's published reporting. Grand Rapids Daily has published 58 episodes:
- Nine to sixteen minutes against a fifteen-minute target, median a bit over twelve.
- Monday, Wednesday and Friday at 6:06am Eastern. The first week it went out daily.
- Recent titles include DASH Bus Expansion, West Nile Warnings, and Rapid Daylight Loss and Severe Storms, Casino Milestones, and the Tart Cherry Crisis. GRPS Cell Phone Ban, a local school district story, would never clear the bar for a national feed.
The length doesn't consume a whole commute, and I can listen in quick while I'm going to grab something from the grocery store, or picking a kid up from an activity.
I do listen to it. Not instead of the shows I follow; when those run out, this is what's left in the queue. The episodes that earn it are about something I'd half-heard about and was never going to chase down. On April 23rd it spent a full segment on replacing the Sixth Street Dam with an adjustable sea lamprey barrier, part of the Grand River restoration. I now know why the lamprey matter without reading six articles.
Architecture
- Twelve pnpm workspace packages, ten deployed as Workers, on seven Cloudflare primitives: Workers, Workflows, D1, R2, Durable Objects, Containers and cron triggers.
- The scheduler ticks every fifteen minutes and asks each active feed whether its cron expression matches in that feed's own IANA timezone, so a Detroit show fires on Detroit time with no daylight saving math.
- Each stage is a durable
step.do()with three retries and exponential backoff. Transitions write to arun_stagestable, which gives the admin UI a live timeline. - Workflow step outputs have to be JSON-serializable, which I'd underestimated. It makes D1 the only real source of truth, so every stage re-queries what it needs instead of trusting the previous step.
- A show is a row in a YAML file. A slug, a category, source kinds, topic strings, a cron expression and two voice names. A new show costs one config entry and a quarter per episode.
Editorial rules
The planner prompt decides whether this is a show or slop with a microphone.
- Never invent a story. Every item has to map to a real fetched candidate by exact title and URL. A drifted URL fails validation and the plan is retried. A salvage pass repairs it when the title still matches something real and drops it when it doesn't. The prompt says a short, honest episode beats a fabricated one.
- Routine weather isn't news. Local outlets publish forecast stories constantly, so a popularity ranker turns a local news show into a weather show. A weather story now earns a roundup slot only with something concrete to report, like damage, a broken record, closures or injuries. Live forecast data still gets a few sentences of cold-open banter. Severe weather outranks everything and has to name the time window, the hazard, and what to do.
- Don't recap a recap. If a story ran last episode and nothing new surfaced, drop it. Revisit a previewed event after it happens.
- Mind the gap. The planner knows the publishing cadence and flags anything worth planning around before the next episode. A council meeting on Thursday is worth thirty seconds on Wednesday.
Everything was massive
Every renovation was massive, every figure staggering, every result incredible. A May 4th episode called the 22 million dollar redo of the Stafford's Hospitality portfolio massive. Two segments later something else was massive.
The planner prompt now caps superlatives at roughly one an episode and says a
number from the research brief beats an adjective ("up 40%" over "a massive
jump"). Models comply for a while and drift back, so audio-slop.ts runs on
every accepted script, retries and salvaged plans included, and enforces a
budget rather than a ban:
A host saying "massive" once is fine. Saying it in three consecutive segments is the thing you notice and cannot un-notice.
massivegets one use per episode,crazyandwildtwo,reallythree.utilize,leverage,delveandat the end of the dayget zero.- The counter is shared across the whole script, not per turn. Longer phrases
match first, so
at the end of the dayis handled before anything inside it. - Excess uses cycle through replacements, so a capped word doesn't become four
copies of "big". Pure amplifiers like
veryandabsolutelyare deleted, and a tidy pass cleans up the double spaces and orphaned commas. - Every run logs what it caught, as
massive×3, very×2.
Very little of this overlaps with the checklist for this site. Em-dashes and paragraph rhythm are invisible to somebody driving; repetition is what grates in audio. Where they agree is structure. The prompt tells the hosts to vary sentence length and sometimes leave a loose end, a question the other host can't answer or a disagreement nobody resolves. This site's linter measures the same two things.
Costs and small cruelties
About twenty-five cents an episode. TTS is roughly seventy percent of it, billed per audio output token, which prompt tuning can't shrink. The planner call is six and a half cents. Fetching and ranking are free.
Things that cost me an evening each:
- Google's TTS API caps a request at 4000 bytes of markup, so segments chunk at 3500 to leave room for per-turn overhead. Oversized single turns split on sentence boundaries, decode to PCM, concatenate, and encode to MP3 once per segment.
- Cloud TTS rejects speaker ids that aren't alphanumeric, so
host_aandhost_bbecomeAandBon the wire and nowhere else. - Chapter timings are recorded as an anchor index before the segment that starts the chapter is pushed onto the list. The other order shifts every chapter by one segment.
- Google shut down two of the Gemini model ids this launched on a few months
later.
pnpm check:modelsexists so I don't find out from a silent 404 at 6am.
Ideas I want next
- A podcast about the projects I starred. The
githubsource reads my received-events feed (activity from accounts I follow, filtered by Gemini Flash Lite down to real features rather than dependency bumps and CI tweaks). Stars are a different list, one I curated on purpose, and a weekly rundown of what shipped across them is a show nobody will make for me. - A podcast that only publishes when there's news. Every feed runs on a cron and produces an episode whether or not anything happened. For a company or project I follow, it should fetch on a schedule and publish only when the candidates clear some bar. That's a deep change, since publish is currently the last step of a run rather than a decision the run makes.
Loose ends
It doesn't credit anybody. The planner carries a source_url for every item
and the database stores it per segment, but it never reaches the show notes or
the RSS feed. The episode description is just a summary, so the outlet that did
the reporting gets nothing, and the byline never gets said. Should the fix be a
source list in the show notes, or the hosts naming the outlet out loud? Probably
both, and I haven't done either. The planner only sees that a story's source
kind was news, not which outlet, so it couldn't credit anyone by name yet.
Forward-looking coverage only knows what retrospective news copy happens to mention. Structured context feeds (a civic calendar for Grand Rapids, game schedules for the sports show) are sketched in the roadmap, and none of it is built.
And I have no idea whether anyone besides me has ever subscribed.