Repository navigation
Things v1 did that this tree will eventually want back #16
Replies: 2 comments
|
Status check, 2026-09-22 (main at 23b983e) Back since this was written: TTS (Kokoro, Chatterbox, the render module), shows (productions), Still missing: the monitoring SSE feed ( |
|
Closing, 2026-10-10 (main at be84ebc). Everything this map pointed at has either come back or has a discussion of its own, so the map itself has nothing left to hold. Back since this was written (the 2026-09-22 check already listed the first five):
Where the rest lives now:
The SSE feed was the one item with no other home: The "port the reasoning, verify every path" advice stands for whoever opens any of those. v1's notes on |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Written: 2026-08-08, from the memory notes accumulated while v1 was built.
Source:
/Users/robert/Projects/deadair_v1, the pre-re-scaffold station. It is a finished,working station; this repo was re-scaffolded down to a chassis and is rebuilding a subset of it.
Everything below worked there and does not exist here. This file is a map of what is worth
porting, not a plan: the module paths, settings keys and route names in v1 mostly do not survive the
re-scaffold (no
director, noengine.config, no Navidrome,musicis nowcatalog, and there isa plugin architecture now that v1 never had). Port the reasoning and the comments, verify every
path against the current tree, and expect the shape to change where a capability becomes a plugin.
For work designed against this tree and then deferred, see the other files in this directory.
The render pipeline
apps/api/src/modules/render/in v1: TTS rendering, ffmpeg, the harbor pusher that gets renderedaudio onto the mount, a take cache, per-episode transcripts, and
llm.gate.ts.This is the single biggest absent subsystem, and several deferred items here depend on it.
Partly closed since this was written. deadair now has a segment that airs, a station that plants
its own, and a voice that can be cued over a record with the bed ducked under it — so the harbor
pusher does not need porting at all:
radio.liqfetches its own voice over HTTP from a secondrequest.queue, and no audio passes through the app. What is still absent is TTS and the take cache,which is to say anything that produces the audio in the first place. See dj-voice.
Non-obvious things v1 learned that are worth keeping:
LlmGateholds the single model slot until a streaming body drains. Releasing onfetch()resolve lets two generations overlap under
stream: true.LlmGate.fetch(..., budgetMs)composes thetimeout after
acquire(). Before that, queue wait counted against the budget, so a long run's owncalls starved each other and every beat aborted into its stub.
smooth_addwas tried and abandoned.stream/in this repo came across from v1verbatim, so that part is already here.
Breaks: the DJ that talks
packages/core/src/breaks/in v1: sign-ons, talk breaks, DJ sets, news bulletins, a rotation policyand a set of deterministic quality checks on generated dialogue. Fed by
packages/core/src/feeds/(RSS news, Open-Meteo weather).
The seam this tree would hang it on is already described in
director-and-lineups under "Segments in a lineup" and "A DJ that
talks". v1 did break timing against a persisted rundown, which is why persistence appears in
rundown as a prerequisite rather than a nice-to-have.
Shows and episodes
packages/core/src/fountain/andpodcast/, plusrender/shows.*andepisodes.*. Multi-voicetalk shows written as a real back-and-forth discussion rather than read-aloud headlines. What v1
ended up with, after several passes:
re-draft → per-beat polish.
writingModeper show (quick|outlined|polished).the outline only supplies content, and coverage is repaired so a re-order cannot drop a story.
mean a single beat asked to carry ~1300 spoken words, which is unwritable. Beats are sized to a
150–260 word band and items spread across them.
and per-show overrides, mirrored in the console form.
ideas below it were folded in: a caller here is a PERSONA (kind
caller) rather than a castentry, so it recurs by construction and carries a sheet, a voice slot, a notebook and a past — which
is "caller and persona memory" from the wishlist further down, arriving with the feature rather than
after it.
[SFX: …]cue markers, non-spoken, from an allow-list, with arenderSfxseam left for realmixing.
preview that shows the script as it is written.
generation-checkpoints.repository.tsandgeneration-control.ts.Two gotchas paid for live: cancel must be terminal (or the job broker resurrects a cancelled run
minutes later), and "no control entry" has to count as still current, or a cancelled run thinks
it was superseded and skips deleting its own partial script.
LLM plumbing
packages/core/src/llm/chat.tsandjson.ts: one sharedchatCompletethat streams when asked andotherwise does the blocking call, tolerant JSON parsing for model output, multiple providers and
models selectable per generator, and an opt-in per-provider
reasoning_effort(sign-ons and talkbreaks pinned low, because thinking is wasted on them and costs wall-clock).
Worth knowing before designing against a local model: the LLM host is remote Ollama, and a context
window that spills VRAM drops it to a couple of tokens a second, which stubs everything downstream.
The monitoring feed
v1 has a realtime pub/sub bus streamed to the console over SSE, with a JSON polling fallback:
progress, status, errors and logs from the render pipeline, jobs, the director, health probes and the
LLM gate.
This one is cheap to bring back. The generic core was lifted out into
@maroonedsoftware/serverfeed, which this repo already declares as a dependency and does not use.v1 keeps only thin adapters:
apps/api/src/modules/monitor/(aprovideMonitor/getMonitorpair mirroringprovideAppConfigStore, because the feed is constructed insetup.server.tsbefore the container exists so boot-time logs already reach the bus), the SSE andJSON routers, and a logger bridge.
Three fixes that came with it, and that any SSE endpoint here will need:
DB transaction.
closeIdleConnections()+closeAllConnections()after start). Otherwiseserver.close()waits forever on an open SSEconnection, the old process never exits, it keeps the port, and the restart looks like a hung boot.
ctx.respond = false) so a client disconnect does not make Koa logERR_STREAM_PREMATURE_CLOSEas a spurious error event in the feed.SSE auth is fetch()-based, keeping the Bearer header, because
EventSourcecannot send one.More music sources
v1 had Navidrome/Subsonic working end to end alongside Spotify, with live switching between them at
runtime and no restart: one always-built Liquidsoap
fallback([...])where the active bed iswhichever the app feeds, a mode endpoint the go-librespot launcher polls so it only runs in Spotify
mode, and a director that rebinds its source dependencies mid-run.
In this tree a second source is a plugin implementing the catalog/playout capabilities, so the
DI-token half of that design is superseded. What ports is the Liquidsoap side and the switching
behaviour. One gotcha to keep: DJ-mode picks do not exist in a demo Navidrome library; use a real
Subsonic playlist id when testing.
Now-playing sinks
apps/api/src/modules/nowplaying/in v1: a reactor fanning out to sinks, an Icecast metadata writer,and a TuneIn AIR publisher (config-gated no-op unless partner credentials are set).
Two of these are already accounted for here: mount metadata is listed in rundown,
and push destinations in director-and-lineups (as a plugin capability
fed by a
station.airedevent, rather than v1's hard-wired sink list). TuneIn's phase 0 is manualand out of band: expose Icecast publicly, register the station, obtain AIR credentials.
Selection intelligence
v1's
director/playHistory.repository.tsandenrichment/musicGraph.service.ts: durable playhistory driving a repeat window and artist cooldown, per-artist caps, and a palette derived from what
actually aired rather than from the pool. Artist similarity comes from Last.fm
(
artist.getSimilar/artist.getTopTracks); MusicBrainz has no artist-similarity endpoint.The rotation-rules and palette-steering entries in
director-and-lineups are the current-tree version of this.
Station state
packages/core/src/station/: a reducer over station events with coalescing and staleness rules,plus
modules/station/station.log.repository.tsfor the activity log behind the console.Things v1 wanted and never built either
From v1's own todo and the deferred notes around it. These have no implementation to port, so
they are ideas rather than ports:
Caller and persona memory.Built, in two halves and neither of them a new table:personas accumulating history landed 2026-08-22 (
deadair.persona_notes), and callers recurringlanded 2026-08-25 by making a caller a persona in the first place. What a caller remembers comes out
of the same store as what the host remembers, which is the whole argument for not having built it
twice.
[SFX: …]markers into mixed audio through an asset library and ffmpeg,then duck it under speech.
programmecapability andRundown.insertNextin director-and-lineups.station:requestpermission indirector-and-lineups is the piece that would gate this.
metadata.
impossible.
Moved from
docs/todo/from-v1.md. The file as it last stood: https://github.com/robert-dean/deadair/blob/71e431d45ce7b35a189c7d226832ec4a6247e9dc/docs/todo/from-v1.mdAll reactions