feat: split introspection backend from consolidation (trial Dolphin for her voice)

reflect()/think() can now run on a different model than memory consolidation:
INTROSPECTION_BACKEND / INTROSPECTION_MODEL (default to SUMMARY_BACKEND, so unset =
unchanged). Consolidation (summaries/profile/narrative) keeps the capable model;
her *voice* (reflections, thoughts) can run a steerable tune. dream.py lets
reflect()/think() self-resolve to the introspection backend; both now thread a
`model` override into llm.complete.

Trial live: introspection -> dolphin3:8b on the 3090; consolidation -> Qwen-32B
on the MI50. Suite 73 green, ruff clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-22 06:09:12 +00:00
parent c2cee3be4d
commit 05ae98abdb
6 changed files with 47 additions and 12 deletions
+4 -3
View File
@@ -473,11 +473,12 @@ def _weighted_choice(threads: list[dict]) -> dict:
def think(backend: Backend | None = None, force_mode: str | None = None,
source: str = "dream") -> dict | None:
source: str = "dream", model: str | None = None) -> dict | None:
"""Advance the thought loop by one step. Returns a small report, or None on a
parse miss. `force_mode` ('new'|'continue'|'respond') is mainly for tests."""
cfg = config.load()
backend = backend or cfg.summary_backend
backend = backend or cfg.introspection_backend # her voice (may differ from consolidation)
model = model or cfg.introspection_model
mode, thread = _pick("new" if force_mode == "react" else force_mode)
state = self_state.load()
react_item = None
@@ -546,7 +547,7 @@ def think(backend: Backend | None = None, force_mode: str | None = None,
body = f"{time_line}\n\n{inner}{norestate}\n\n{task}"
out = _safe_json(llm.complete(
[{"role": "system", "content": _THINK_PROMPT}, {"role": "user", "content": body}],
backend=backend,
backend=backend, model=model,
))
if not out or not (out.get("content") or "").strip():
logbus.log("info", "thought loop", mode=mode, result="no parse")