Files
project-lyra/tests/test_chat.py
T
serversdown 904eda3388 refactor(P1): extract the turn pipeline into lyra/mind.py (behavior-preserving)
First step of the cognition control plane (docs/COGNITION.md). The chat turn is now
an explicit society of parts over a shared TurnContext blackboard:
  perceive (stub) -> route (session mode) -> compose (tiered prompt) -> deliberate.

- lyra/mind.py (new): TurnContext + the pipeline + assemble(); moved build_messages
  and the deliberation helpers here (the assembly belongs in the control plane).
- lyra/chat.py: slimmed to "speak + persist" — calls mind.assemble(), runs the
  tool/generation loop, persists. No behavior change (same prompt, same output).
- tests: point test_time/test_chat at mind; add an assemble() structure test;
  make test_chat/test_tools hermetic (CHAT_DELIBERATE off so respond() doesn't make
  a real LLM call). Suite 86 green in ~5s, ruff clean, no import cycle.

This is the frame; perceive/route/learn get filled in next phases — each opt-in.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 05:19:39 +00:00

64 lines
2.5 KiB
Python

"""The mind pipeline: the deliberation pass (think privately before answering)."""
from __future__ import annotations
import importlib
import pytest
@pytest.fixture
def lyra(tmp_path, monkeypatch):
monkeypatch.setenv("LYRA_DB_PATH", str(tmp_path / "test.db"))
from lyra import llm
monkeypatch.setattr(llm, "embed", lambda texts: [[0.1, 0.2, 0.3] for _ in texts])
import lyra.memory as memory
importlib.reload(memory)
import lyra.mind as mind
importlib.reload(mind)
return memory, mind
def test_should_deliberate_skips_trivial(lyra):
_, mind = lyra
assert mind._should_deliberate("How would we actually start building this?")
assert mind._should_deliberate("I disagree, that seems risky")
for trivial in ("ok", "lol", "thanks", "yeah", "nice", "👍", "k"):
assert not mind._should_deliberate(trivial)
assert not mind._should_deliberate("ok!") # punctuation stripped
assert not mind._should_deliberate("hey") # too short
def test_deliberation_note_runs_and_appends(lyra, monkeypatch):
_, mind = lyra
calls = []
def fake_complete(messages, backend=None, model=None):
calls.append(messages)
return "I actually think the first move is the smallest end-to-end slice."
monkeypatch.setattr(mind.llm, "complete", fake_complete)
note = mind._deliberation_note("s1", "How would we start on this?", "cloud", None, [])
assert note and note["role"] == "system"
assert "first move is the smallest" in note["content"] # her thinking carried in
assert "numbered list" in note["content"].lower() # voice enforcement attached
assert len(calls) == 1
def test_deliberation_skipped_when_disabled(lyra, monkeypatch):
_, mind = lyra
monkeypatch.setenv("CHAT_DELIBERATE", "false")
called = []
monkeypatch.setattr(mind.llm, "complete", lambda *a, **k: called.append(1) or "x")
assert mind._deliberation_note("s1", "a real substantive question here", "cloud", None, []) is None
assert called == [] # no LLM call when off
def test_assemble_runs_the_pipeline(lyra, monkeypatch):
memory, mind = lyra
monkeypatch.setenv("CHAT_DELIBERATE", "false") # keep it offline for the structure test
memory.ensure_session("s1")
turn = mind.assemble("s1", "hey what's up", "cloud", None)
assert turn.mode is not None # route ran
assert turn.messages and turn.messages[-1]["role"] == "user" # compose ran
assert turn.messages[-1]["content"] == "hey what's up"