feat: auto-accumulating villain dossiers + player lookup (poker B)

Named players in recorded hands now auto-enrich a persistent dossier, and stats
emerge once the sample is big enough — laying groundwork for A.

- poker: player_observations table (per named player per hand: vpip/pfr/saw_flop/
  showed/cards/summary); record_hand auto-links named players via link_hand_players;
  player_profile(name) returns dossier + reads + shown hands, with inferred
  VPIP/PFR/WTSD gated behind MIN_STATS_SAMPLE (12) so thin samples don't lie;
  list_players()
- player_profile tool ("what do I know about X"); thin files return a blunt
  "don't generalize" directive
- persona: she MUST call player_profile before discussing an opponent and answer
  only from it — fixes observed confabulation (she invented a whole read from one
  hand / from memory). Verified: now reports only the real logged hand.
- tests: observation linking, profile, stat-emergence at sample threshold

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-18 04:33:16 +00:00
parent 6a911423a2
commit c7d2279f8d
4 changed files with 185 additions and 1 deletions
+34
View File
@@ -116,6 +116,40 @@ def test_list_recent_hands(lyra):
assert hh and hh[0]["hole_cards"] == "QQ" and hh[0]["stakes"] == "1/3"
def test_player_observation_and_profile(lyra):
poker = lyra
sid = poker.start_session(stakes="1/3", buy_in=300)
parsed = {"hero_pos": "BB",
"players": [{"pos": "BTN", "name": "Round Mike"}, {"pos": "BB", "name": None}],
"actions": [{"street": "preflop", "pos": "BTN", "action": "raise", "amount": 12},
{"street": "preflop", "pos": "BB", "action": "call"},
{"street": "flop", "board": ["7d", "2c", "5h"]},
{"street": "flop", "pos": "BTN", "action": "bet", "amount": 15}]}
hid = poker.store_hand_history(parsed, session_id=sid)
assert poker.link_hand_players(hid, parsed, session_id=sid) == 1 # only the named player
prof = poker.player_profile("mike")
assert prof["player"]["name"] == "Round Mike"
assert prof["observations"] == 1
assert prof["stats"] is None and "small_sample" in prof # too few hands for stats
def test_player_stats_emerge_with_sample(lyra):
poker = lyra
sid = poker.start_session(stakes="1/3", buy_in=300)
raised = {"players": [{"pos": "BTN", "name": "LAG"}],
"actions": [{"street": "preflop", "pos": "BTN", "action": "raise", "amount": 10}]}
folded = {"players": [{"pos": "UTG", "name": "LAG"}],
"actions": [{"street": "preflop", "pos": "UTG", "action": "fold"}]}
for i in range(poker.MIN_STATS_SAMPLE):
p = raised if i % 2 == 0 else folded
hid = poker.store_hand_history(p, session_id=sid)
poker.link_hand_players(hid, p, session_id=sid)
prof = poker.player_profile("LAG")
assert prof["stats"] is not None
assert prof["stats"]["hands"] >= poker.MIN_STATS_SAMPLE
assert 30 <= prof["stats"]["vpip_pct"] <= 70 # ~half were voluntary
def test_poker_tools_dispatch(lyra):
from lyra import tools
assert "started" in tools.dispatch("start_session", {"stakes": "1/3", "buy_in": 300})