Repository navigation
feat(ios27): real token accounting and interruption-safe voice capture - #10
Merged
Merged
Conversation
ReflectKit budgets prompts against SystemLanguageModel.contextSize (exact on iOS 27, back-deployed 4096 on 26), maps LanguageModelError.contextSizeExceeded to a typed ReflectionError.contextExceeded, retries once with a budget shrunk by the reported overshoot, and records response.usage into a bounded TokenUsageLedger that only ever tightens the characters-per-token estimate. The weekly review trims each entry to fit instead of sending the week unbudgeted; entries are never dropped so citation numbering stays in lockstep. CaptureKit adds a CaptureInterruption channel opened in TranscriptionEngine .start(), a CaptureState.interrupted(draft:) that parks the words so far, and coordinator handling: resume and append when the system recommends it, hand the draft to the editor otherwise, or let the writer tap "Keep going". The real engine observes iOS 27's AVAudioSession didBecomeInactive / resumptionRecommendation messages and falls back to the legacy interruption notification on iOS 26. New copy is localized in all nine tables.
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.
Summary
First two items from the iOS 27 adoption review, both fixing real failure modes in shipped paths rather than adding surface. Deployment target stays iOS 26.0; every iOS 27 API is behind
#available.Model accounting (ReflectKit)
TokenAccounting.swift: prompt budget from the model's real context size, overflow rebudget scaled by the reported overshoot, and a boundedTokenUsageLedgeractor that calibrates the characters-per-token estimate from what the model actually counted. Counts only, never text; calibration can only make the estimate stricter.ModelAccounting.swift: readsSystemLanguageModel.default.contextSize(exact on 27, back-deployed 4096 on 26), mapsLanguageModelError.contextSizeExceededand the iOS 26 equivalent to a typedReflectionError.contextExceeded, recordsresponse.usageon 27.resolve(numbers:in:).Audio interruptions (CaptureKit)
TranscriptionEngine.interruptions()channel andCaptureInterruptionevent, opened insidestart()so an event right after start is buffered rather than missed.CaptureState.interrupted(draft:)parks the words captured so far. Coordinator resumes and appends when the system recommends it, hands the draft to the editor otherwise, and exposescontinueAfterInterruption()for the new "Keep going" stage. Re-entrancy between a tap and a system-driven resume is guarded; the interruption subscription is dropped before the engine drains on stop.SpeechTranscriberEngineobserves iOS 27AVAudioSession.DidBecomeInactiveMessage(system deactivation only) andResumptionRecommendationMessage, falling back to the legacyinterruptionNotificationon iOS 26. Observers are removed when the stream terminates.Test plan
swift test --parallelfull core packageNotes
Copyenum now sits one line over SwiftLint's 400-line type-body warning; splitting it into extensions would clear it.LocalizedOverrideTestscan flake under--parallelbecause of a process-global override; unrelated to this change and tracked separately.CaptureSummaryProviderand the InsightKit extractor still create sessions without accounting; natural next step.🤖 Generated with Claude Code