+ Used for journal chat (no tools — just conversation) and lightweight one-shot tasks like note-title generation and tag suggestions.
+ Pick a small fast model (e.g. qwen3:8b, llama3.2:3b) — speed matters more than depth here.
+ Ideally runs on GPU.
+
-
+
- Model used for background tasks: title generation, tag suggestions, and project summaries.
- Using a small dedicated model (e.g. qwen2.5:0.5b) keeps the chat model's KV cache warm between messages, significantly reducing response time.
+ Used for heavy async work: the journal curator (capture / propose updates), daily prep generation, end-of-day closeout, task body consolidation, project summaries, and profile observation processing.
+ Pick a smart model — latency doesn't matter, quality does. Often runs on CPU with system RAM (e.g. qwen3:32b, qwen3:30b-a3b).
- ⚠ Setting this to the same model as Chat Model will wipe the KV cache after every background task, increasing response latency.
+ ⚠ Using the same model for both means worker tasks compete for the chat model's resources. Pick different models so they can be loaded simultaneously (OLLAMA_MAX_LOADED_MODELS = 2+).
diff --git a/src/fabledassistant/services/chat.py b/src/fabledassistant/services/chat.py
index 2adabad..e6df2bc 100644
--- a/src/fabledassistant/services/chat.py
+++ b/src/fabledassistant/services/chat.py
@@ -243,8 +243,15 @@ async def save_response_as_note(user_id: int, message_id: int) -> dict:
},
{"role": "user", "content": msg.content[:2000]},
]
- bg_model = await get_setting(user_id, "background_model", Config.OLLAMA_BACKGROUND_MODEL)
- title = await generate_completion(prompt_messages, bg_model)
+ # 3-8 word title generation is the kind of trivial small-input
+ # / small-output task the chat model (default_model) handles in
+ # ~1s. Routing here so the worker (background_model) isn't
+ # interrupted from heavier curator / prep / closeout passes.
+ chat_model = (
+ await get_setting(user_id, "default_model", "")
+ or Config.OLLAMA_MODEL
+ )
+ title = await generate_completion(prompt_messages, chat_model)
title = title.strip().strip('"\'').strip()[:100]
except Exception:
logger.warning("Failed to generate note title, using fallback", exc_info=True)
diff --git a/src/fabledassistant/services/journal_prep.py b/src/fabledassistant/services/journal_prep.py
index c661dcf..c427bc9 100644
--- a/src/fabledassistant/services/journal_prep.py
+++ b/src/fabledassistant/services/journal_prep.py
@@ -446,7 +446,16 @@ async def _generate_prep_prose(
"""Ask the LLM for a direct conversational journal opener built from the sections."""
from fabledassistant.services.llm import generate_completion
- model = (await get_setting(user_id, "default_model", "")) or Config.OLLAMA_MODEL
+ # Daily prep is a deliberate, multi-section generation — runs once a day,
+ # latency-tolerant, benefits from a smarter model. Route to the worker
+ # (background_model) rather than the chat model. The chat model in the
+ # conversation+curator architecture is small/fast/no-tools; prep needs
+ # the heavier reasoning the worker provides.
+ model = (
+ await get_setting(user_id, "background_model", "")
+ or Config.OLLAMA_BACKGROUND_MODEL
+ or Config.OLLAMA_MODEL
+ )
if not model:
logger.warning("No LLM model configured for daily prep — using fallback text")
return _fallback_prep_text(day_date)
diff --git a/src/fabledassistant/services/tag_suggestions.py b/src/fabledassistant/services/tag_suggestions.py
index b39a622..90fce85 100644
--- a/src/fabledassistant/services/tag_suggestions.py
+++ b/src/fabledassistant/services/tag_suggestions.py
@@ -22,7 +22,14 @@ async def suggest_tags(user_id: int, title: str, body: str, current_tags: list[s
return []
existing_tags = await get_all_tags(user_id)
- model = await get_setting(user_id, "background_model", Config.OLLAMA_BACKGROUND_MODEL)
+ # Tag suggestion is a tiny single-shot task (3-5 tags from a note's
+ # title + body) — route to the chat model (small/fast) rather than
+ # tying up the worker on a trivial call. The chat model handles this
+ # in well under a second.
+ model = (
+ await get_setting(user_id, "default_model", "")
+ or Config.OLLAMA_MODEL
+ )
existing_list = ", ".join(f"#{t}" for t in existing_tags) if existing_tags else "(none yet)"
diff --git a/src/fabledassistant/services/user_profile.py b/src/fabledassistant/services/user_profile.py
index bdb4fdb..8b776b8 100644
--- a/src/fabledassistant/services/user_profile.py
+++ b/src/fabledassistant/services/user_profile.py
@@ -144,7 +144,15 @@ async def _consolidate_observations(user_id: int) -> str:
user_prompt += f"Existing summary:\n{existing_summary}\n\n"
user_prompt += f"New observations:\n{obs_text}"
- model = await get_setting(user_id, "default_model", Config.OLLAMA_MODEL)
+ # Profile observation consolidation reasons over multiple documents to
+ # produce a coherent summary — closer to curator-shaped work than chat.
+ # Route to worker (background_model). Falls back to OLLAMA_MODEL only if
+ # neither setting nor BACKGROUND default is available.
+ model = (
+ await get_setting(user_id, "background_model", "")
+ or Config.OLLAMA_BACKGROUND_MODEL
+ or Config.OLLAMA_MODEL
+ )
try:
new_summary = (await generate_completion(
[