338c44361f
build_messages now shards poker_cash: injects poker_prompts.BASE + exactly ONE fragment chosen by classify(user_msg, roster_handles), replacing the ~100-line _CASH_CARD monolith. Roster handles are fetched fail-safe from the live session so the READ-vs-HAND split is reliable. CASH.card set to "" (monolith kept only as the distillation source, superseded). Other modes' single-card path unchanged. Verified end-to-end: TAG-limp→READ, his-hand→HAND, table-broke→TABLE, tilt→MENTAL, bare-stack→LOG, all with BASE always present. 5 injection/pipeline tests. Full suite 196 green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
105 lines
4.3 KiB
Python
105 lines
4.3 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")
|
|
|
|
|
|
# --- Phase A pipeline fixes: poker mode suppresses the two mush sources ---
|
|
|
|
def test_poker_mode_suppresses_tilt_nudge(mind):
|
|
from lyra import memory
|
|
memory.set_session_mode("s1", "poker_cash")
|
|
turn = mind.assemble("s1", "ugh I'm steaming, fucking coolered again!!", "cloud", None)
|
|
assert turn.register is None # at the table, register comes from fragments
|
|
sys_blob = " ".join(m["content"] for m in turn.messages if m["role"] == "system")
|
|
assert "on tilt" not in sys_blob.lower() # false-positive lexicon nudge suppressed
|
|
|
|
|
|
def test_mode_menu_note_suppressed_in_poker(mind):
|
|
from lyra import modes
|
|
poker = " ".join(m["content"] for m in mind.build_messages("s1", "stack 350", mode=modes.CASH)
|
|
if m["role"] == "system")
|
|
build = " ".join(m["content"] for m in mind.build_messages("s1", "let's refactor", mode=modes.get("build"))
|
|
if m["role"] == "system")
|
|
assert "Your modes:" not in poker # no "offer to switch" note at the table
|
|
assert "Your modes:" in build # still present in a non-poker mode
|
|
|
|
|
|
# --- Phase B: sharded poker prompt (BASE + one fragment, no monolith) ---
|
|
|
|
def _poker_blob(mind, msg):
|
|
from lyra import modes
|
|
return " ".join(m["content"] for m in mind.build_messages("s1", msg, mode=modes.CASH)
|
|
if m["role"] == "system")
|
|
|
|
|
|
def test_poker_injects_base_plus_the_matching_fragment(mind):
|
|
blob = _poker_blob(mind, "I flopped a set with 99 on 9h4c2d and bet the turn")
|
|
assert "LOG FIRST" in blob # BASE is always on in poker
|
|
assert "MESSAGE TYPE: HAND" in blob # the fragment for THIS message
|
|
assert "MESSAGE TYPE: STATUS" not in blob # and not the others
|
|
assert "MESSAGE TYPE: READ" not in blob
|
|
|
|
|
|
def test_poker_fragment_changes_with_message_type(mind):
|
|
status = _poker_blob(mind, "it's 11:50pm, waiting for a seat")
|
|
assert "MESSAGE TYPE: STATUS" in status and "MESSAGE TYPE: HAND" not in status
|
|
|
|
|
|
def test_poker_monolith_no_longer_injected(mind):
|
|
from lyra import modes
|
|
blob = _poker_blob(mind, "stack 350")
|
|
assert "You move between two registers" not in blob # the old _CASH_CARD opener is gone
|
|
assert modes.CASH.card == "" # card sharded out |