feat(service): the voice brain follows a spoken ask's turn on Neon Realtime instead of polling
Third of three small PRs that remove Luke's last transcript and voice loops. The goal is realtime with no fallbacks. On the coordinator's call, this is stacked on #3295 (luke/neon-realtime-planning-hold), beside #3757, and not on #3931: it touches only the service. It needs #3266's lookedUntil/LiveWake and #3267's taskConversationLiveStatements, and #3295 carries both.
What changed
voice/live-brain.ts: a spoken ask's follow no longer reads the record every 250 ms (POLL), and no longer wakes on eve's session stream with a 60 msSETTLE.- It now calls
lookedUntilon the task conversation's mark. The record is read at once, then again each time the mark moves over Neon Realtime. The mark moves when a message lands, when the journal revision goes up, when the newest turn or its status changes, and when a worker starts or ends. - The hold is
PARKED_FOLLOW, the widest bound. The look itself still says when a turn is past the plain bound or the parked one, as before. LiveWakeis taken where the brain is built, on the socket runtime thatrunWebprovides withliveWakeFromEnvironment, and passed to each follow's fiber.- Retired:
POLL,SETTLE,STREAM_RETRY,EVE_DELTA_EVENTS, and the per-session stream listener.
- It now calls
voice/live-exchange.tsandexchange-attachment.ts: the exchange gets the session'staskIdand passes the braintaskConversationLiveStatements(conversation.userId, taskId).eve-sessions.ts:EveStreamEventis no longer exported, so knip passes.EveSessions.streamhas no caller left. Removing that seam means editing eight test fakes, so I left it for a follow-up.
Worth knowing
lookedUntil on this base still looks every LIVE_SAFETY_POLL (5 s) beside the watch. That's #3266's primitive, and I didn't change it. A worker's own journal moving between its start and its end doesn't move the mark. So a worker step shown on the Work tab can arrive up to 5 s late, until the turn's own journal or a worker's start or end moves the mark. I didn't chase that edge case.
Tests
live-brain.test.ts:- Its real-clock suite runs on
tickingLiveWake, a new helper intesting/live-wake.tswhose watch hears a change every 5 ms, standing in for Realtime hearing each write. - The test that a turn is heard the moment eve's stream says something is replaced by one where the turn is heard the moment Realtime says the mark moved. It checks that the follow watches the mark it was handed. It asserts nothing about the time before the mark moves, because on CI's shared Postgres the 5 s safety beat can look first. That race failed the first CI run.
- Its real-clock suite runs on
live-exchange.test.tsandvoice-service-exchange.test.tsprovide the ticking wake to the exchange they build.
Checks run locally
pnpm check:affected gets OOM-killed in the sandbox, so I ran its pieces one at a time:
repository-checks.sh,biome check .,oxlint,knipdiscarded-effect(with a larger heap)tscfor service and web- vitest for
src/voice,src/agents/brain-host, andsrc/live(114 tests)
What to check by hand
- In a call on a task, ask Luke something that takes a few steps. His activity line and spoken reply should come at least as quickly as before, without the steady 250 ms reads.
- Ask him to hand work to a worker. Its findings should still be spoken when they land.
Made with Luke