Summary
A user reports that Parakeet cold start works reliably when Auto-unload model is set to Never, but the same setup starts showing No model loaded again when Auto-unload is set back to the default timed policy.
This points to the restore path after an auto-unload, not initial setup/download.
User report
yes, after setting the auto-unload model to "never", the problem is solved. It is now performing cold start perfectly with no issues. However, as soon as I set the auto-unload model back to its default—which I believe is a few minutes—it starts exhibiting the same behavior. I think we need a fix for this.
Current behavior
When Parakeet has been unloaded by the auto-unload timer, the next dictation can fail with:
No model loaded. Please download and select a model first.
Setting auto-unload to Never avoids the issue.
Expected behavior
Auto-unload should release the local model from memory, but the next dictation should restore the selected/persisted Parakeet model before transcription starts. Users should not need to disable auto-unload to make cold starts reliable.
Investigation notes
Relevant code paths:
ModelAutoUnloadPolicy.defaultSeconds is 600, so the default policy is 10 minutes.
ModelManagerService.prepareEngineForTranscription(...) calls triggerRestoreModel(...) when the selected transcription plugin is not configured.
ModelManagerService.triggerRestoreModel(...) dispatches triggerRestoreModel through ObjC and polls plugin.isConfigured for up to ~30 seconds.
ParakeetPlugin.triggerAutoUnload() calls unloadModel(clearPersistence: false), preserving loadedModel.
ParakeetPlugin.triggerRestoreModel() starts Task { await restoreLoadedModel(allowDownloads: true) }.
Local checks performed:
xcodebuild test ...
Executed 3 focused auto-unload policy/timer tests, 0 failures
I also ran a temporary Parakeet plugin-level check locally: load Parakeet v3, unload with clearPersistence: false, then call triggerRestoreModel(). That passed, so the plugin-only reload path can work. However, the first Parakeet/CoreML load took about 29.7 seconds locally, which is very close to the host polling timeout. A slower machine, cold CoreML compile, or cleanup contention could plausibly hit the timeout and surface as No model loaded.
Possible root cause
The host restore wait may be too tight or too opaque for local Parakeet reload after auto-unload. If the async restore takes longer than the current polling window, ModelManagerService still sees plugin.isConfigured == false and throws modelNotLoaded, even though the persisted model selection is valid.
Acceptance criteria
- Add regression coverage for: selected Parakeet/local transcription engine is auto-unloaded, next dictation restores it and proceeds instead of throwing
modelNotLoaded.
- Make the restore path resilient to slow first CoreML load/compile after auto-unload.
- If restore fails for a real reason, surface the underlying load error instead of the generic
No model loaded message.
- Keep the Never behavior unchanged.
Workaround
Set Auto-unload model to Never until this path is fixed.
Summary
A user reports that Parakeet cold start works reliably when Auto-unload model is set to Never, but the same setup starts showing
No model loadedagain when Auto-unload is set back to the default timed policy.This points to the restore path after an auto-unload, not initial setup/download.
User report
Current behavior
When Parakeet has been unloaded by the auto-unload timer, the next dictation can fail with:
Setting auto-unload to Never avoids the issue.
Expected behavior
Auto-unload should release the local model from memory, but the next dictation should restore the selected/persisted Parakeet model before transcription starts. Users should not need to disable auto-unload to make cold starts reliable.
Investigation notes
Relevant code paths:
ModelAutoUnloadPolicy.defaultSecondsis600, so the default policy is 10 minutes.ModelManagerService.prepareEngineForTranscription(...)callstriggerRestoreModel(...)when the selected transcription plugin is not configured.ModelManagerService.triggerRestoreModel(...)dispatchestriggerRestoreModelthrough ObjC and pollsplugin.isConfiguredfor up to ~30 seconds.ParakeetPlugin.triggerAutoUnload()callsunloadModel(clearPersistence: false), preservingloadedModel.ParakeetPlugin.triggerRestoreModel()startsTask { await restoreLoadedModel(allowDownloads: true) }.Local checks performed:
I also ran a temporary Parakeet plugin-level check locally: load Parakeet v3, unload with
clearPersistence: false, then calltriggerRestoreModel(). That passed, so the plugin-only reload path can work. However, the first Parakeet/CoreML load took about 29.7 seconds locally, which is very close to the host polling timeout. A slower machine, cold CoreML compile, or cleanup contention could plausibly hit the timeout and surface asNo model loaded.Possible root cause
The host restore wait may be too tight or too opaque for local Parakeet reload after auto-unload. If the async restore takes longer than the current polling window,
ModelManagerServicestill seesplugin.isConfigured == falseand throwsmodelNotLoaded, even though the persisted model selection is valid.Acceptance criteria
modelNotLoaded.No model loadedmessage.Workaround
Set Auto-unload model to Never until this path is fixed.