"""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?" or # "what do we have on Round Mike?" works even when we're just talking. _LOOKUPS = ("player_profile", "get_villain_file", "running_stats") # 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", "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. 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()]