move local-model concurrency to a turn-level defer lock

Review feedback: drop the model-layer scheduler (per-call-site
interactive/classifier/background priorities, local-provider hostname
heuristic) in favor of app-level orchestration:

- deferBackgroundTasks flag in models.json, surfaced as a settings
  toggle; auto-enabled once (UI logic) when the user connects Ollama
- ChatActivity counter marked by both chat runtimes (sessions layer and
  legacy AgentRuntime.trigger)
- startWhenPossible/runWhenPossible wrappers around the headless agent
  runner; all background invocations (knowledge pipeline, live notes,
  background tasks, scheduled + prebuilt agents) go through them and
  wait for chat-idle when the flag is set

createLanguageModel keeps only the Ollama context-window middleware;
the LM Studio capability probe now keys off the provider flavor instead
of hostname sniffing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Arjun 2026-07-05 21:30:04 +05:30
parent c1fd4e6221
commit fd49334c33
33 changed files with 373 additions and 541 deletions

View file

@ -623,6 +623,7 @@ const ipcSchemas = {
meetingNotesModel: ModelOverride.nullable().optional(),
liveNoteAgentModel: ModelOverride.nullable().optional(),
autoPermissionDecisionModel: ModelOverride.nullable().optional(),
deferBackgroundTasks: z.boolean().nullable().optional(),
}),
res: z.object({
success: z.literal(true),

View file

@ -37,6 +37,11 @@ export const LlmModelConfig = z.object({
// the signed-in curated default and the legacy top-level provider/model
// pair — this is what lets signed-in users default to a BYOK model.
defaultSelection: ModelRef.optional(),
// When true, background agent runs (knowledge pipeline, live notes,
// background tasks) wait until no chat turn is running before starting.
// Surfaced as a settings checkbox; recommended for local models, where a
// background run competes with the chat for the same hardware.
deferBackgroundTasks: z.boolean().optional(),
providers: z.record(z.string(), z.object({
apiKey: z.string().optional(),
baseURL: z.string().optional(),