"""Reliable hand logging: hero-hand guard, tool-visible history (B), forced log (A). Root cause these guard: mid-session, memory.history() rebuilt past turns as 'hand -> narration' with tool calls stripped, few-shot-conditioning the model to stop logging (clean history logged 4/4, the stripped history 0/4).""" from __future__ import annotations from types import SimpleNamespace from lyra import poker_prompts as pp # --- the hero-hand guard (who gets force-logged) -------------------------- def test_looks_like_hero_hand_true_for_brians_own_hand(): assert pp.looks_like_hero_hand("im utg with 2d2s. i raise to $15, btn calls") assert pp.looks_like_hero_hand("300eff. i call btn w AsQs, flop Qh7c2s, i bet 20 he calls") def test_looks_like_hero_hand_false_for_observed_and_chatter(): # A villain the actor (no I/me/my) must never be force-logged as Brian's hand. assert not pp.looks_like_hero_hand("TAG limped A4o in the SB") assert not pp.looks_like_hero_hand("how's the table looking tonight?") assert not pp.looks_like_hero_hand("") # --- Fix B: tool calls made visible in reconstructed history -------------- def _ex(role, content, at): return SimpleNamespace(role=role, content=content, created_at=at, id=hash(at)) def test_history_marks_the_assistant_turn_that_logged(monkeypatch): from lyra import mind, memory recent = [ _ex("user", "i have 2d2s utg, flop 2c2hKs, quads", "2026-07-10T18:00:00.000000+00:00"), _ex("assistant", "Sick cooler.", "2026-07-10T18:00:05.000000+00:00"), _ex("user", "how am i doing", "2026-07-10T18:01:00.000000+00:00"), _ex("assistant", "Up a grand.", "2026-07-10T18:01:03.000000+00:00"), ] monkeypatch.setattr(memory, "tool_events", lambda sid: [ {"tool": "record_hand", "result": "Hand #62 logged — UTG 2d2s.", "created_at": "2026-07-10T18:00:03.000000+00:00"}, {"tool": "session_state", "result": "net +1000", "created_at": "2026-07-10T18:01:02.000000+00:00"}, ]) msgs = mind._history_with_tools("s1", recent) # each event is attributed to the assistant turn whose window it falls in assert "record_hand → Hand #62 logged" in msgs[1]["content"] assert msgs[1]["content"].endswith("Sick cooler.") assert "session_state" in msgs[3]["content"] # user turns are untouched assert msgs[0]["content"] == recent[0].content def test_history_no_marker_when_no_tools(monkeypatch): from lyra import mind, memory monkeypatch.setattr(memory, "tool_events", lambda sid: []) recent = [_ex("assistant", "just talking", "2026-07-10T18:00:05.000000+00:00")] assert mind._history_with_tools("s1", recent)[0]["content"] == "just talking" # --- Fix A: force the log when the model skipped a hero hand --------------- def _force_setup(monkeypatch, tool_calls): from lyra import chat monkeypatch.setattr(chat.llm, "chat_call", lambda *a, **k: ({"role": "assistant"}, tool_calls)) dispatched = [] monkeypatch.setattr(chat.toolkit, "dispatch", lambda name, args, ctx=None: dispatched.append(name) or "Hand #71 logged.") monkeypatch.setattr(chat.memory, "add_tool_event", lambda *a, **k: 1) return chat, dispatched def test_forces_log_on_unlogged_hero_hand(monkeypatch): chat, dispatched = _force_setup(monkeypatch, [{"id": "1", "name": "record_hand", "arguments": '{"shorthand":"AsQs..."}'}]) forced = chat._ensure_hand_logged([], "300eff i call btn w AsQs, i bet 20", "HAND", [], "cloud", None, {}, "s1") assert forced == ["record_hand"] and dispatched == ["record_hand"] def test_does_not_force_when_already_logged(monkeypatch): chat, dispatched = _force_setup(monkeypatch, []) forced = chat._ensure_hand_logged([], "i have AsQs, i bet", "HAND", ["record_hand"], "cloud", None, {}, "s1") assert forced == [] and dispatched == [] def test_does_not_force_non_hand_or_observed(monkeypatch): chat, dispatched = _force_setup(monkeypatch, []) # not a HAND turn assert chat._ensure_hand_logged([], "down to 220", "LOG", [], "cloud", None, {}, "s1") == [] # HAND-classified but observed (no first person) → never force-logged as his assert chat._ensure_hand_logged([], "TAG shoved AKo", "HAND", [], "cloud", None, {}, "s1") == [] assert dispatched == []