9cd962625d
Phase 2. On every poker-context turn, detect players named or described in the message and slide their structured history into her prompt as a SCOUTING DESK note (cite-don't-invent). Fail-safe: any desk error is swallowed, never breaks the turn; silence is the default. - poker.villain_recall(id): episodic brief — times/where seen, last seen, notable hands (linkable ids), reads, stats (Gap 1: the when/where/which-hand narrative). - scouting.scout(): named hits (deterministic word match) + descriptor spans (regex → resolve_villain). High → surface + "confirm it's the same guy"; ambiguous → file needs_clarification to the queue instead of interrupting; generic → stay silent. - mind.build_messages wires it in, gated to poker modes (poker_cash/study). 5 tests. Full suite 146 green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
74 lines
2.7 KiB
Python
74 lines
2.7 KiB
Python
"""The scouting desk: named + descriptor recall, ambiguous→queue, generic→silence."""
|
|
from __future__ import annotations
|
|
|
|
import importlib
|
|
|
|
import numpy as np
|
|
import pytest
|
|
|
|
|
|
def _fake_embed(texts):
|
|
out = []
|
|
for t in texts:
|
|
v = np.zeros(64, dtype=np.float32)
|
|
for w in t.lower().split():
|
|
v[hash(w) % 64] += 1.0
|
|
out.append((v if v.any() else np.full(64, 1e-6, dtype=np.float32)).tolist())
|
|
return out
|
|
|
|
|
|
@pytest.fixture
|
|
def mods(tmp_path, monkeypatch):
|
|
monkeypatch.setenv("LYRA_DB_PATH", str(tmp_path / "test.db"))
|
|
from lyra import llm
|
|
monkeypatch.setattr(llm, "embed", _fake_embed)
|
|
import lyra.memory as memory
|
|
importlib.reload(memory)
|
|
import lyra.poker as poker
|
|
importlib.reload(poker)
|
|
import lyra.scouting as scouting
|
|
importlib.reload(scouting)
|
|
return poker, scouting
|
|
|
|
|
|
def test_named_player_surfaces_a_brief(mods):
|
|
poker, scouting = mods
|
|
sid = poker.start_session(venue="Meadows", stakes="1/3", buy_in=300)
|
|
pid = poker.upsert_player("Sleepy John", venue="Meadows", category="reg")
|
|
poker._c().execute(
|
|
"INSERT INTO player_observations (player_id, session_id, cards, created_at) VALUES (?,?,?,?)",
|
|
(pid, sid, "As Ks", poker._now()))
|
|
poker._c().commit()
|
|
note = scouting.scout("sleepy john just sat down on my left", venue="Meadows")
|
|
assert note and "Sleepy John" in note and "SCOUTING DESK" in note
|
|
|
|
|
|
def test_descriptor_high_match_surfaces_with_confirm(mods):
|
|
poker, scouting = mods
|
|
poker.create_descriptor_villain("neck tattoo sleeve arm", venue="Meadows", category="reg")
|
|
note = scouting.scout("the neck tattoo sleeve guy just 3bet me again", venue="Meadows")
|
|
assert note and "confirm it's the same guy" in note
|
|
|
|
|
|
def test_ambiguous_descriptor_queues_instead_of_interrupting(mods):
|
|
poker, scouting = mods
|
|
poker.create_descriptor_villain("neck tattoo sleeve arm", venue="Meadows")
|
|
note = scouting.scout("the neck tattoo guy raised", venue="Meadows")
|
|
assert note is None # didn't interrupt
|
|
q = poker.list_identity_queue()
|
|
assert q and q[0]["kind"] == "needs_clarification"
|
|
|
|
|
|
def test_generic_descriptor_stays_silent(mods):
|
|
poker, scouting = mods
|
|
poker.create_descriptor_villain("neck tattoo sleeve arm", venue="Meadows")
|
|
note = scouting.scout("the mid aged white guy with glasses raised", venue="Meadows")
|
|
assert note is None
|
|
assert poker.list_identity_queue() == [] # no queue spam for a non-identifier
|
|
|
|
|
|
def test_no_player_reference_returns_nothing(mods):
|
|
poker, scouting = mods
|
|
poker.upsert_player("Sleepy John", venue="Meadows")
|
|
assert scouting.scout("i folded pocket kings to a 4bet", venue="Meadows") is None
|