From 338c44361ff93cbeda5fd6e8b9161a1570a299da Mon Sep 17 00:00:00 2001 From: serversdown Date: Sun, 5 Jul 2026 02:57:30 +0000 Subject: [PATCH] =?UTF-8?q?feat(prompting):=20Phase=20B=20step=202=20?= =?UTF-8?q?=E2=80=94=20wire=20the=20sharded=20poker=20prompt=20into=20the?= =?UTF-8?q?=20pipeline?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- lyra/mind.py | 20 ++++++++++++++++---- lyra/modes.py | 5 ++++- tests/test_perceive.py | 30 +++++++++++++++++++++++++++++- 3 files changed, 49 insertions(+), 6 deletions(-) diff --git a/lyra/mind.py b/lyra/mind.py index 8c0b90b..91b0261 100644 --- a/lyra/mind.py +++ b/lyra/mind.py @@ -17,8 +17,8 @@ from __future__ import annotations from dataclasses import dataclass, field from lyra import ( - clock, config, llm, logbus, memory, modes, perceive, persona, scouting, - self_state, thoughts, + clock, config, llm, logbus, memory, modes, perceive, persona, poker, poker_prompts, + scouting, self_state, thoughts, ) from lyra.llm import Backend, Message @@ -153,8 +153,20 @@ def build_messages(session_id: str, user_msg: str, if inner: messages.append(inner) - # Mode card: how to behave *right now*. Talk mode has no card (persona is Talk). - if mode and mode.card: + # Mode framing: how to behave *right now*. Poker (poker_cash) is SHARDED — a lean + # always-on BASE plus ONE response-shape fragment chosen by classifying this message + # (replaces the old ~100-line monolithic card). Roster handles make the READ-vs-HAND + # split reliable; fetched fail-safe. Other modes use their single card. + if mode and mode.key == "poker_cash": + messages.append({"role": "system", "content": poker_prompts.BASE}) + try: + handles = [r["name"] for r in poker.session_roster()] + except Exception: + handles = [] + msg_type = poker_prompts.classify(user_msg, handles) + messages.append({"role": "system", "content": poker_prompts.fragment_for(msg_type)}) + logbus.log("info", "poker turn classified", type=msg_type) + elif mode and mode.card: messages.append({"role": "system", "content": mode.card}) # Mode awareness: she can offer to switch when the work clearly shifts (she decides diff --git a/lyra/modes.py b/lyra/modes.py index 134bbcd..ec52b6d 100644 --- a/lyra/modes.py +++ b/lyra/modes.py @@ -240,7 +240,10 @@ TALK = Mode( CASH = Mode( key="poker_cash", label="Poker", - card=_CASH_CARD, + # Poker mode is SHARDED at the pipeline (lyra.poker_prompts: BASE + a per-message + # fragment), so no monolithic card here. _CASH_CARD is kept below only as the source + # material the fragments were distilled from — superseded, safe to delete later. + card="", tools=_CASH_TOOLS, ) diff --git a/tests/test_perceive.py b/tests/test_perceive.py index 0a6f4b9..4719f73 100644 --- a/tests/test_perceive.py +++ b/tests/test_perceive.py @@ -74,4 +74,32 @@ def test_mode_menu_note_suppressed_in_poker(mind): 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 \ No newline at end of file + 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 \ No newline at end of file