I run a lot of overnight agent loops. Research ideas, prototypes, refactors I'd never have the patience to do by hand — I queue them up before bed and let them grind. By morning the repo has new commits, a couple of PRs, and a sprawl of agent-authored markdown notes that read like a stream-of-consciousness diary of every dead end the agent walked down at 3am.
I also have a dog. He needs walking around 7. Reading diffs with a leash in one hand and a coffee in the other is not a workflow — it's a way to spill coffee on the dog. What I wanted was a podcast: a 3-minute briefing I could listen to on the way to the field, and arrive knowing what shipped.
So I built dawncast. It's a Python CLI that turns the overnight delta on a git repo into a multilingual audio briefing.
$ dawncast podcast --since overnight --languages "English,German"
▸ collecting commits in main since 2026-05-19 18:00 … 14 commits
▸ enriching with GitHub PRs (gh) … 3 PRs
▸ picking up agent journals … 2 markdown files
▸ rendering producer brief … briefings/repo-2026-05-20.md
▸ podcastfy: writing dialogue (gemini) … ✓
▸ podcastfy: rendering audio (edge) … ✓ english (2m41s)
✓ german (2m48s)
→ out/dawncast-repo-english.mp3
→ out/dawncast-repo-german.mp3
Two stages, one pipeline
The pipeline splits cleanly in half, and that split is load-bearing — most of the design decisions fall out of it.
Stage 1 — the briefing. Collect everything the morning-self needs to know: commits in the window, file changes (with collapsed diffs), GitHub PRs pulled via gh if it's authenticated, and any agent-authored markdown notes inside the repo. Render it all into a single producer-ready markdown file. That file is useful on its own — it's a perfectly reasonable PR description, a standup note, a "what did the agent do" log. No audio, no API keys, no network. dawncast briefing stops here.
Stage 2 — the podcast. Hand the briefing to podcastfy, which has an LLM write a two-host conversation from it and a TTS engine speak it. By default the TTS is Microsoft Edge TTS — free, no API key, neural voices in 60+ languages.
The reason the split matters: the briefing is deterministic and cheap; the podcast is stochastic and (potentially) expensive. Keeping them as separate commands means I can iterate on the briefing format without burning LLM tokens, and I can re-render audio in a different language from a cached briefing without re-walking git history.
The signals beyond git log
If all you wanted was a summary of overnight commits, you don't need a tool — you need git log --since=yesterday | claude. The reason that's not enough is that agents don't only commit. They narrate.
Mine drop files like WINNER_REPORT.md (which of three competing approaches won the bake-off), FEATURE_BACKLOG.md (what's queued for the next loop), NORTH_STAR.md (the agent's current understanding of the goal), and journal.md (everything that didn't fit elsewhere). These files often contain the why behind the diff — and a diff without a why is just noise on a 7am walk.
dawncast auto-discovers them. Out of the box it picks up WINNER_REPORT*.md, FEATURE_BACKLOG.md, NORTH_STAR.md, AGENT_LOG*.md, docs/agents/*.md, and anything under .dawncast/journal*.md, provided they were touched in the window. If your agent already writes to one of those paths, you get journal pickup for free. If it doesn't, point it at .dawncast/journal.md and you're done.
GitHub PRs come through the same window filter, via gh. Title, state, author, body. If gh auth status is happy, they're in the brief. If not, --no-prs and move on.
Two modes: overnight and feature
Same engine, two framings — and the framing is what makes the audio listenable.
- Overnight (default). Window:
[--since, now], branch:HEAD. Producer brief says: "morning catch-up — what shipped while you slept." The two hosts talk like coworkers reading the previous day's standup. - Feature (
--feature BRANCH). Window: commits unique toBRANCHvs an auto-detected base (main→master→develop→origin/HEAD). Producer brief says: "feature walkthrough for a teammate doing code review." The hosts slow down, explain decisions, walk through diffs. Same files, different cadence.
The feature mode is the one I didn't expect to keep using. It turns out a 5-minute audio walkthrough of a branch is a surprisingly good PR companion — I'll generate one before I write the actual PR description, listen to it, and find that the hosts already articulated the trade-off I was about to bury in a bullet point.
Decoupling dialogue from voice
podcastfy needs two model choices: an LLM to write the script, and a TTS engine to speak it. dawncast treats these as independent picks — you can mix any LLM with any TTS — because the cost curves are completely different.
- LLM quality determines whether the hosts say something interesting. A good LLM finds the narrative thread in a messy diff. A bad one reads the changelog aloud.
- TTS quality determines whether you can stand listening for three minutes. Robotic prosody is fine for a single sentence and unbearable for a conversation.
The cheapest viable combo: Gemini for dialogue (free tier, generous), Edge for voices (free, no key, multilingual). That's the default. Upgrade either side independently when you want to: --tts elevenlabs for emotive multilingual voices, --llm-model gpt-5.2 for sharper dialogue, mix and match. The provider matrix in the README has the full menu.
Speaking of multilingual — dawncast ships voice mappings for sixteen languages on Edge: English, German, Spanish, French, Italian, Portuguese, Dutch, Polish, Swedish, Japanese, Chinese, Korean, Arabic, Hindi, Turkish, Russian. --languages "English,German" generates one MP3 per language from a shared briefing. I use both — English for technical density, German for ambient listening on the walk.
dawncast doctor
The CLI feature I wish more tools had. Run it and you get a PASS/WARN/FAIL table:
$ dawncast doctor --tts edge
PASS python 3.12.4
PASS git 2.45.1
PASS gh authenticated as nero408
PASS ffmpeg 7.0.1
PASS podcastfy importable (0.4.3)
PASS llm key GEMINI_API_KEY set
PASS tts key edge needs no key
→ briefing path: OK
→ podcast path: OK
Every row that isn't green comes with a concrete fix suggestion — the env var to set, the package to install, the command to run. It exits 0 when the briefing path is functional (warnings are non-fatal) and 1 when something is actually broken. When the pipeline misfires at 6am, dawncast doctor is faster than reading the stack trace, and it's almost always the answer.
Building this was the most boring part of the project and easily the highest-leverage. Diagnostic tools earn their keep on day 30, not day 1.
The morning ritual
The end state, after a few weeks of using it:
# Every weekday at 07:30, generate yesterday's recap.
30 7 * * 1-5 cd ~/work/my-repo && \
/opt/homebrew/bin/dawncast podcast \
--since overnight \
--languages "English,German" \
-o ~/morning-briefings/$(date +\%Y-\%m-\%d)
By the time I'm in the kitchen, today's folder has two MP3s in it. I AirDrop them to my phone, clip the leash on the dog, and walk. Most days the English one is enough; on days where the agent did something architecturally weird I'll listen to the German one too, because the slower cadence makes me re-process it.
What I didn't expect: hearing the work read out loud catches problems I'd have missed reading the diff. "The agent rewrote the retry logic" lands differently as audio than as a green-on-black +47 -23. Several times I've turned around mid-walk and gone back to revert something.
What I learned building it
A few things were surprisingly easy:
- podcastfy is the right abstraction. I started thinking I'd need to wire up an LLM client and a TTS client and a dialogue prompt and a chunker. podcastfy already had all of that, with provider plug-ins for every LLM and TTS I cared about. The job shrank from "build a podcast pipeline" to "build a producer brief that podcastfy consumes well." That reframe saved weeks.
- Edge TTS removes the friction. The single biggest barrier to "try this tool" is "go get an API key first." Defaulting to a free, no-key, neural-voice TTS means the install-to-first-audio path is one command and zero accounts.
- The producer brief is just markdown. Not JSON, not a custom DSL — a markdown document with sections the LLM can read like a human. Easy to inspect, easy to diff, easy to
--dry-runand check before paying for audio.
A few were surprisingly hard:
- Dependency resolution.
podcastfy 0.4.3caret-pinstyperto^0.12.5, which is incompatible withclick >= 8.2.uv tool installignores the[tool.uv]override inpyproject.toml, so the fix was a checked-inconstraints.txtand a documented--overridesflag. Not glamorous, but if it's not solved the tool doesn't install. Half the README's troubleshooting section exists because of this one transitive pin. - Time windows that match human intuition. "Overnight" doesn't mean "the last 24 hours." It means "yesterday at 18:00 → now." "Yesterday" means "yesterday 00:00 → now," not "the 24-hour block ending at midnight." Getting those right took a small expression parser and a lot of tests, but the alternative — making the user write ISO datetimes at 6am — was unacceptable.
- Base branch auto-detection. Feature mode needs to know what to diff against. Trying
main→master→develop→trunk→origin/main→origin/master→origin/HEADin that order covers every repo I've used in the last decade, but each one of those was a separate "oh right, this repo uses…" moment. - Matching audio length to brief length. The
--lengthpreset (short/medium/long) has to drive both podcastfy'slongform/max_num_chunksknobs and the duration hint inside the producer brief. If only one moves, the LLM writes a 12-minute script that the chunker truncates to 3 minutes mid-sentence. The fix is one line of code; finding the bug took an evening.
Install
# Briefing only — no API keys, no network
uv tool install dawncast
# Briefing + audio (the --overrides flag is load-bearing, see below)
uv tool install \
--overrides https://raw.githubusercontent.com/nero408/dawncast/main/constraints.txt \
'dawncast[podcast]'
# Then point at a free LLM and you're done
export GEMINI_API_KEY=… # https://aistudio.google.com/apikey
dawncast doctor
Python 3.11–3.13. pipx and pip work too. ffmpeg is required for some TTS providers (audio merging) — brew install ffmpeg on macOS. Everything else dawncast doctor will tell you about.
Where it's headed
- Per-author briefings. When more than one agent is touching the repo, "split the brief by author" is the next obvious knob.
- Episode chaining. Yesterday's briefing as input to today's — so the hosts can say "remember the auth rework we talked about Tuesday? It shipped."
- Non-git sources. Linear tickets, Slack threads, CI run summaries. The producer brief is just markdown; anything that can be markdown-ified can be folded in.
- A
dawncast initthat writes an example.dawncast/journal.mdtemplate and aCLAUDE.mdsnippet teaching the agent to use it.
If any of that sounds useful — or you have a use case I haven't thought of — open an issue. PRs welcome.
Try it
uv tool install 'dawncast[podcast]'
export GEMINI_API_KEY=…
cd ~/your/repo
dawncast podcast --since overnight --languages English
Three minutes later you have an MP3. Put it on your phone, clip the leash on the dog, and find out what your agents did while you slept.