Repository navigation
Conversation
The utterance, the feed loop with its live passes, and the warm loop move into a session on the coordinator's clock. It hands back what it ended with: the source to transcribe and the feed to wait for. The coordinator keeps the recording number and the checks after every wait. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 task
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #73, point 2 (second half: the warm and live loops). Stacked on #79 (
core/finish-pipeline).What
The engine's side of a recording leaves
DictationCoordinatorand becomesSources/PladderCore/RecordingEngineSession.swift, a@MainActorclass on the coordinator's clock. It holds the utterance, the feed loop with its live passes, the warm loop andwarmupSamples; the coordinator'sStream,warmupTask,startEngineWork,startStreamingFeedandstartWarmupLoopgo.begin() async: begins an utterance when the engine streams. A streaming engine that cannot begin is used like a batch engine, as before.startLoops(): the feed loop (with live passes in Live Transcript) and the warm loop, or only the warm loop for a batch engine.end() -> Ending: synchronous, it only cancels. It returns what the session ended with: theDictationFinish.Sourceto transcribe and the feed task to wait for.Ending.abandon() async: waits for the feed and only then drops the utterance, for a cancel's cleanup.The coordinator keeps the recording number,
isCurrent, and the race check afterawait session.begin().endRecording()callsend()where it used to cancel the feed and the warm task, still before the state flip.hotkeyReleasedwaits forending.feedand handsending.sourceto the finish.beginCancel's cleanup callsending.abandon()after the microphone stops.Deviations from the plan's sketch. None changes behaviour.
isRecordingclosure. The loops check the session's ownisEnded. Every path that ends a recording (release, cancel, drop) goes throughendRecording(), which ends the session before the state flips, soisEndedis set exactly when the oldstate.isRecordingcheck turned false.end()is handled by the session.begin()returnsVoid; if the session ended while the utterance was beginning,begin()abandons that utterance itself, where the coordinator used to.startLoops()afterend()does nothing.abandon()is onEnding, the valueend()returned, so the cleanup needs no reference to the session. It drops the utterance only if the session had one when it ended, exactly as the cleanup did withstreambefore.DictationCoordinator.swiftgoes from 644 to 571 lines (676 on main); the session is 137.Tests
The existing
CoordinatorHandOverTests,theWarmLoopRunsOnTheClockAndStopsAtRelease,noLivePassStartsAfterTheReleaseandkeyDownWarmsTheEngineare unchanged and pass. NewTests/PladderCoreTests/RecordingEngineSessionTests.swift, 7 tests onManualClock:theFeedLoopHandsOverEveryDrainedChunkOnceendCancelsTheWarmLoopBeforeReturning: the clock has no sleeper left the momentend()returnsaLivePassFinishingAfterEndPublishesNothingaBatchEngineGetsOnlyTheWarmLoopaStreamingEngineThatCannotBeginIsWarmedAndTranscribedWholeabandonWaitsForTheFeedBeforeDroppingTheUtteranceaBeginOvertakenByEndDropsItsUtteranceAndStartsNoLoopswift build(no warnings) andswift test(619) are green; the session, hand-over, regression and finish suites passed 20 runs in a row.Benchmark (critical path)
swift run -c release pladder-cli bench-coordinator --runs 101, Apple M1, 16 GB, macOS 27.0. The builds ran back to back from copies of their release products.Before,
core/finish-pipeline(5a5a80f), load 1.72 → 1.46:After, this branch (621023c), load 1.34 → 1.40:
The same run on
mainplus the harness (#77, d43924b) gave 160 ms / 0.08 / 0.10 / 0.14 ms / 0.25 ms, so the two splits together cost nothing measurable either. Engine excess, which holds the feed hand-over that moved here, is unchanged.For the developer
swift run -c release pladder-cli bench bench/fixtures --paced --runs 2 --pause 2, and the same with--live, since the live loop movedrelease-to-pastelog lines:/usr/bin/log show --last 1h --style compact --predicate 'subsystem == "de.dinooo13.pladder"'🤖 Generated with Claude Code