feat: web switch for her inner voice (Dolphin/3090 | Qwen-32B/MI50 | Off)

Her introspection (reflect/think) voice is now switchable live from the web
settings, read each cycle by the dream loop — so Brian can flip it off the 3090
before gaming without touching config or restarting.

- memory: runtime key/value settings table + get_setting/set_setting.
- self_state: INTROSPECTION_MODES (dolphin=local/dolphin3:8b, mi50=Qwen-32B,
  off=paused) + introspection_target()/set_introspection_mode(); default "dolphin".
  reflect() resolves from the live setting and SKIPS entirely when off.
- thoughts.think(): same resolution + skip-when-off.
- server: GET/POST /settings/introspection.
- index.html: "Inner Voice (introspection)" selector in Settings, applies instantly.
- tests: routing (dolphin/mi50), off-skip for think + reflect. Suite 77, ruff clean.

Default = Dolphin on the 3090 (richer voice). Flip to MI50 or Off in Settings
before gaming — that was the GPU-contention culprit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-22 19:16:35 +00:00
parent a705e573a9
commit a7966e4bab
7 changed files with 139 additions and 8 deletions
+8 -2
View File
@@ -477,8 +477,14 @@ def think(backend: Backend | None = None, force_mode: str | None = 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.introspection_backend # her voice (may differ from consolidation)
model = model or cfg.introspection_model
# Resolve her introspection voice from the live (web-switchable) setting unless a
# backend was passed explicitly; skip entirely if introspection is switched off.
if backend is None and model is None:
tgt = self_state.introspection_target()
if not tgt["enabled"]:
logbus.log("info", "thought skipped — introspection off")
return None
backend, model = tgt["backend"], tgt["model"]
mode, thread = _pick("new" if force_mode == "react" else force_mode)
state = self_state.load()
react_item = None