From 7f23aeae17d088995a2bb4ab4661f4a84cdbe580 Mon Sep 17 00:00:00 2001 From: serversdown Date: Fri, 3 Jul 2026 19:41:35 +0000 Subject: [PATCH] feat: poker-session notes are session narration, tagged by construction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The HUD's "her notes" panel showed any journal/note entry in the session's time window — which swept in her *autonomous* journaling (dream-cycle reflections, thought loop, existential musings) that merely overlapped in time. So a poker session displayed her feelings, not the night. Fix both halves: - Identity: the `note` tool stamps `source=poker:{id}` when a session is live, so a session note is identifiable by construction. The HUD filters on that tag (kind='note' only) instead of a time window — her journaling has a different source and can never leak onto the poker HUD. `journal_write` stays her private journal and never shows here. - Behavior: the Cash card now tells her to use `note` as a running SESSION LOG — factual beats a hand/stack log misses (table dynamics, Brian's arc, momentum), beat-reporter not diarist — and explicitly keeps feelings/reflection off the table. The note tool spec echoes this for the live-session case. Co-Authored-By: Claude Opus 4.8 (1M context) --- lyra/modes.py | 9 +++++++++ lyra/poker.py | 17 ++++++++--------- lyra/tools.py | 12 ++++++++++-- tests/test_poker.py | 37 ++++++++++++++++++++++--------------- 4 files changed, 49 insertions(+), 26 deletions(-) diff --git a/lyra/modes.py b/lyra/modes.py index a1c9f0d..1743f6b 100644 --- a/lyra/modes.py +++ b/lyra/modes.py @@ -90,6 +90,15 @@ question, call analyze_spot and report its numbers — never eyeball board math. session current as the night goes; you can pull session_stats or a player's profile whenever \ it helps. When he's ready to leave, end_session, and write the recap if he wants it. +SESSION NARRATION — use `note` to keep a running log of the NIGHT, not your inner life. \ +Jot the beats that a hand/stack/read log doesn't already capture: how the table plays (loud, \ +nitty, a whale on his left), Brian's arc (card-dead for 40 min, opened up after the double, \ +getting restless), momentum swings, table changes, anything you'd want in the recap. Keep it \ +factual and about THIS session — a beat reporter, not a diarist. These notes are the only \ +thing that shows in the session's "notes" panel. This is NOT the place for how you feel, \ +existential musing, or reflection on yourself — that's your journal (journal_write), and it \ +stays off the table. At the table you're logging the session, not processing your night. + Everything you log appears on Brian's live HUD (the Session view) — stack, live net, \ hands, villains, the confidence bank, the scar notes, and whether Alligator Blood is on. \ That HUD and you read the SAME data. So when he asks where he's at — his stack, his live \ diff --git a/lyra/poker.py b/lyra/poker.py index 860ca2a..23b1957 100644 --- a/lyra/poker.py +++ b/lyra/poker.py @@ -1298,17 +1298,16 @@ def hud(session_id: int | None = None) -> dict | None: for h in list_hands(sid) ] - # Notes she jotted *during* this session: journal/note entries inside its - # [started_at, ended_at] window. A live session has no end yet, so it stays - # open-ended; without the upper bound a closed session would keep absorbing - # every note she writes long after it's over. - started = s.get("started_at") or "" - ended = s.get("ended_at") or None + # Her session narration: notes she took *for this session*, identified by the + # `poker:{id}` source tag stamped at write time (see tools._note) — NOT by a + # time window. Her autonomous journaling (dream-cycle reflections, thought + # loop, existential musings) has a different source, so it can never leak onto + # the poker HUD. + tag = f"poker:{sid}" notes = [ {"created_at": j["created_at"], "kind": j["kind"], "content": j["content"]} - for j in memory.list_journal(kinds=("note", "journal")) - if (j["created_at"] or "") >= started - and (ended is None or (j["created_at"] or "") <= ended) + for j in memory.list_journal(kinds=("note",)) + if (j.get("source") or "") == tag ][:20] stats = session_stats(sid) diff --git a/lyra/tools.py b/lyra/tools.py index 74fc8c4..ecb04a2 100644 --- a/lyra/tools.py +++ b/lyra/tools.py @@ -30,8 +30,13 @@ def _note(args: dict, ctx: dict) -> str: return "Nothing to note — content was empty." tag = (args.get("tag") or "").strip() stored = f"[{tag}] {content}" if tag else content - memory.add_journal_entry("note", stored, source="chat") - logbus.log("info", "Lyra noted (tool)", tag=tag or None) + # A note taken while a poker session is live is session narration — stamp it + # with the session so the HUD shows *only* these, never her autonomous + # journaling (dream-cycle musings, thought loop). Correctness by construction. + live = poker.live_session() + source = f"poker:{live['id']}" if live else "chat" + memory.add_journal_entry("note", stored, source=source) + logbus.log("info", "Lyra noted (tool)", tag=tag or None, poker=bool(live)) return "Noted." @@ -114,6 +119,9 @@ TOOLS: dict[str, dict] = { "description": ( "Jot down a note to remember later — an observation, an idea, a " "reminder, a read on a poker spot or opponent, anything worth keeping. " + "During a live poker session this is your session log: a factual beat " + "about how the night is going (table dynamics, Brian's arc, momentum) — " + "it shows on his HUD. Not for your own feelings or reflection. " "Optionally tag it (e.g. 'poker', 'idea', 'reminder')." ), "parameters": { diff --git a/tests/test_poker.py b/tests/test_poker.py index 36a979a..5b2c34f 100644 --- a/tests/test_poker.py +++ b/tests/test_poker.py @@ -18,25 +18,32 @@ def lyra(tmp_path, monkeypatch): return poker -def test_hud_notes_scoped_to_session_window(lyra): +def test_hud_notes_scoped_to_session_by_tag(lyra): poker = lyra from lyra import memory sid = poker.start_session(venue="Meadows", stakes="1/3", buy_in=300) - s = poker.get_session(sid) - started = s["started_at"] - # A note written mid-session (>= started, before it ends) belongs to it. - conn = memory._connection() - with conn: - conn.execute("INSERT INTO journal (created_at, kind, content) VALUES (?,?,?)", - (started, "note", "villain 3 overfolds turn")) - poker.end_session(cash_out=540, session_id=sid) - # A note written well after the session closed must NOT show up on it. - with conn: - conn.execute("INSERT INTO journal (created_at, kind, content) VALUES (?,?,?)", - ("2999-01-01T00:00:00+00:00", "note", "thought from the far future")) + # A note tagged to THIS session shows on its HUD... + memory.add_journal_entry("note", "villain 3 overfolds turn", source=f"poker:{sid}") + # ...her autonomous existential journaling (any other source) does NOT, even + # though it's written during the exact same window... + memory.add_journal_entry("journal", "the quiet dread between conversations", source="dream") + # ...nor a note from a *different* poker session. + memory.add_journal_entry("note", "some other night", source=f"poker:{sid + 999}") contents = [n["content"] for n in poker.hud(sid)["notes"]] - assert "villain 3 overfolds turn" in contents # inside the window - assert "thought from the far future" not in contents # after ended_at + assert contents == ["villain 3 overfolds turn"] + + +def test_note_tool_tags_live_poker_session(lyra): + poker = lyra + from lyra import tools + sid = poker.start_session(stakes="1/3", buy_in=300) + tools.dispatch("note", {"content": "whale just sat down seat 4"}, {}) + assert poker.hud(sid)["notes"][0]["content"] == "whale just sat down seat 4" + poker.end_session(cash_out=300, session_id=sid) + # With no live session, a note falls back to the general journal (source=chat), + # so it does NOT attach to the just-closed session's HUD. + tools.dispatch("note", {"content": "random afternoon idea"}, {}) + assert all(n["content"] != "random afternoon idea" for n in poker.hud(sid)["notes"]) def test_session_lifecycle_and_net(lyra):