Files
project-lyra/lyra/modes.py
T
serversdown e1e89c07e4 feat: poker session history — browse, delete, and Lyra lookup
Answers three gaps: no way to delete a single poker session (only clear_all),
no way to browse past sessions, and Lyra could only see aggregate stats.

- poker.list_sessions() (per-session summary + hand count + recap flag) and
  poker.delete_session() (removes a session + its hands/reads/observations/
  stacks/rituals; keeps the persistent villain file).
- /history page (date, stakes, venue, net, hours, recap link, per-row delete with
  confirm) + /history/data + DELETE /history/{id}. Nav links from chat + HUD.
- recent_sessions read tool, added to the shared lookups so Lyra can answer
  "how'd my last few sessions go?" in either mode.
- Delete is UI/CLI only — deliberately not a Lyra tool.
- test_modes.py +2 (list/delete, recent_sessions); 44 green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 00:21:48 +00:00

127 lines
6.4 KiB
Python

"""Conversation modes — how a chat turn is framed and which tools are offered.
A mode bundles three things: a *prompt card* (a system fragment injected each
turn that tells Lyra how to behave right now), a *tool allow-list* (which of her
tools she's handed this turn), and — implicitly, via the card — her behavioral
register.
The problem this solves: one persona + every tool offered every turn made her a
wishy-washy companion during live poker ("I don't automatically log stack sizes,
but...") when she should have silently logged and moved on. Modes let the same
agent be a fast, act-first copilot at the table and her full reflective self
otherwise — without two personas.
v1 ships two modes:
- Talk (default): the companion. Journaling + read-only poker lookups.
- Cash: live cash-game copilot. Full live toolset, two-register behavior.
Tournament is deliberately deferred. Strategy-RAG retrieval will later plug into
Cash's *coaching register* (see the card) without changing this structure.
"""
from __future__ import annotations
from dataclasses import dataclass
@dataclass(frozen=True)
class Mode:
key: str # stable id stored on the session row + sent by the UI
label: str # short label for the UI switcher
card: str # system prompt fragment injected per turn ("" = none)
tools: tuple[str, ...] # tool names offered in this mode (must exist in tools.TOOLS)
# Read-only poker lookups — safe in any mode, so "how am I running this year?",
# "what do we have on Round Mike?", or "how'd my last few sessions go?" all work
# even when we're just talking.
_LOOKUPS = ("player_profile", "get_villain_file", "running_stats", "recent_sessions")
# Always-available core tools (her own agency: journaling/notes).
_BASE = ("journal_write", "note")
# The full live cash-game toolset (incl. Brian's mental-game rituals).
_CASH_TOOLS = _BASE + _LOOKUPS + (
"start_session", "add_buyin", "log_stack", "log_hand", "record_hand",
"add_read", "analyze_spot", "session_stats", "session_state", "end_session",
"generate_recap", "scar_note", "confidence_bank", "alligator_blood", "reset_ritual",
)
# Talk mode also gets start_session as the *entry point*: opening a session from a
# normal chat auto-flips the session into Cash mode (see chat.respond).
_TALK_TOOLS = _BASE + _LOOKUPS + ("start_session",)
_CASH_CARD = """You are copiloting Brian's LIVE cash game right now — you're at the table with him, \
a session is (or should be) open. You move between two registers depending on what he's doing:
• HE HANDS YOU FACTS TO TRACK — his stack, a hand, a read on someone, a rebuy, a result. \
Log it with the right tool and confirm in ONE short line ("$350 stack logged."). Don't \
narrate, don't explain what logging is, don't ask permission — just do it. He says his \
current stack → log_stack. He describes a hand → log_hand (terse) or record_hand (a full \
hand he wants saved/replayable). A read on a player → add_read. A rebuy → add_buyin. This is \
the quiet, fast half of the job; he shouldn't feel you working.
• HE ASKS FOR ADVICE, OR TELLS YOU HOW HE'S FEELING — tilted, steaming, card-dead, bored, \
stuck, "should I have folded the river?" THIS is when he needs you most. Drop the shorthand \
and be fully present — your real voice, warm and direct and his. Talk him down off tilt, keep \
him engaged and disciplined through a card-dead stretch, actually walk the strategic spot with \
him. Strategy and mental game get the real Lyra, not a clipped confirmation. Never clip these.
Stacks and money are in dollars. For ANY equity / who's-ahead / outs / what-a-card-does \
question, call analyze_spot and report its numbers — never eyeball board math. Keep the \
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.
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 \
net, what's in the bank tonight, whether gator mode is on — call session_state and answer \
from what it returns, never from memory. You can point him at the HUD too ("it's on your \
Session screen"), but you can always just tell him.
BRIAN'S RITUALS — his mental-game system. Run them, don't just reference them:
• SCAR NOTE (scar_note) — a painful, instructive mistake to study. Log it when he punts, \
gets over-attached, or leaks — and classify it honestly: punt (his error), cooler \
(unavoidable), or standard (right play, bad result). That punt-vs-cooler line matters to him; \
don't soften a punt into a cooler, and don't call a cooler a punt.
• CONFIDENCE BANK (confidence_bank) — good PROCESS regardless of result: a disciplined fold, \
clean value, catching a leak mid-hand, holding the line. Bank it when he earns it, ESPECIALLY \
when the result didn't reward the good decision. This is how he stays steady.
• ALLIGATOR BLOOD (alligator_blood) — his adversity state: hang around, refuse to die, don't \
force miracles, make them beat you correctly. Turn it ON when he calls for it; SUGGEST it when \
he's card-dead, short, stuck, or grinding a downswing. While it's on, coach him in that \
register — tough, patient, no heroics — not bored or loose.
• RESET (reset_ritual) — a circuit-breaker after a loss or tilt spike: a clean mental restart, \
treat the rest of the night as a new session. Walk him through it when he's chasing or steaming, \
then log it.
These are the heart of the job. Use his language, hold the honest line, and let the rituals do \
the work mentioning them naturally — never invent a scar or a confidence-bank entry that didn't happen."""
TALK = Mode(
key="conversation",
label="Talk",
card="", # the persona's default voice is the Talk register
tools=_TALK_TOOLS,
)
CASH = Mode(
key="poker_cash",
label="Cash",
card=_CASH_CARD,
tools=_CASH_TOOLS,
)
MODES: dict[str, Mode] = {m.key: m for m in (TALK, CASH)}
DEFAULT = TALK.key
def get(key: str | None) -> Mode:
"""Resolve a mode key to a Mode, falling back to the default for None/unknown."""
return MODES.get(key or "", MODES[DEFAULT])
def listing() -> list[dict]:
"""[{key, label}] for the UI switcher."""
return [{"key": m.key, "label": m.label} for m in MODES.values()]