a7af461cdb
The control plane gains senses — cheap, deterministic, no LLM:
- lyra/perceive.py: lexicon+signal heuristic → {sentiment, intensity, tilt, kind:
emotional|strategic|meta|build|casual}. Good at the action-relevant signal,
especially tilt (the mental-game core). Word-boundary matching so 'line' doesn't
fire inside 'pipeline'.
- mind: _perceive fills ctx.moment; _route keeps the manual mode as the dominant
frame but, on a genuinely charged moment, adds a per-turn register nudge — tilt →
"meet him there, warm and steady, don't clip into logging"; up/energized → "match
his energy." Neutral turns get nothing (don't over-narrate). Injected via
build_messages(moment=...). Logged to /logs for observability.
- tests: perceive read (tilt/strategy/up/build/casual) + route nudge on/off.
Suite 92 green, ruff clean.
Complements modes (manual frame) — perceive refines register within it, doesn't
override. Model routing (mind/mouth) is P3.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
56 lines
2.0 KiB
Python
56 lines
2.0 KiB
Python
"""Perceive: cheap heuristic read of the moment, and route turning it into a nudge."""
|
|
from __future__ import annotations
|
|
|
|
import importlib
|
|
|
|
import pytest
|
|
|
|
from lyra import perceive
|
|
|
|
|
|
def test_reads_tilt():
|
|
m = perceive.read("I'm so fucking tilted, card dead all night, this is brutal!!")
|
|
assert m["tilt"] >= 0.5 and m["sentiment"] < 0 and m["kind"] == "emotional"
|
|
|
|
|
|
def test_reads_strategy_calm():
|
|
m = perceive.read("Should I fold the river here given his range and the board?")
|
|
assert m["kind"] == "strategic" and m["tilt"] < 0.4
|
|
|
|
|
|
def test_reads_up_energy():
|
|
m = perceive.read("Let's go!! crushing it tonight, feeling so good!")
|
|
assert m["sentiment"] > 0 and m["kind"] == "emotional"
|
|
|
|
|
|
def test_reads_build_and_casual():
|
|
assert perceive.read("let's refactor the cognition pipeline module").get("kind") == "build"
|
|
assert perceive.read("ok sounds good to me").get("kind") == "casual"
|
|
assert perceive.read("ok sounds good to me")["tilt"] == 0.0
|
|
|
|
|
|
@pytest.fixture
|
|
def mind(tmp_path, monkeypatch):
|
|
monkeypatch.setenv("LYRA_DB_PATH", str(tmp_path / "test.db"))
|
|
monkeypatch.setenv("CHAT_DELIBERATE", "false")
|
|
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)
|
|
memory.ensure_session("s1")
|
|
return mind
|
|
|
|
|
|
def test_route_injects_tilt_nudge(mind):
|
|
turn = mind.assemble("s1", "ugh I'm steaming, fucking coolered again!!", "cloud", None)
|
|
assert turn.register == "steady"
|
|
sys_blob = " ".join(m["content"] for m in turn.messages if m["role"] == "system")
|
|
assert "on tilt" in sys_blob.lower() or "frustrated" in sys_blob.lower()
|
|
|
|
|
|
def test_route_quiet_on_neutral_turn(mind):
|
|
turn = mind.assemble("s1", "what did we decide about the schema yesterday?", "cloud", None)
|
|
assert turn.register is None # neutral -> no nudge
|
|
assert not (turn.moment or {}).get("note") |