Brian's own rituals (mined from his logs) become first-class, live tools instead
of post-hoc recap sections:
- Scar Note — instructive mistakes with the punt/cooler/standard distinction.
- Confidence Bank — good process, banked regardless of result.
- Alligator Blood — invokable adversity state; she suggests it when he's
card-dead/short/stuck, and her coaching register shifts while it's on (live
state injected into context per-turn via chat._mode_state_note).
- Reset — tilt circuit-breaker; mental marker only, stats stay continuous.
poker_rituals table + log_ritual/list_rituals/set_alligator/alligator_active;
4 tools added to the Cash toolset and taught in the mode card; HUD gains a 🐊
banner + Confidence Bank + Scar Notes panels; recap grounded via _rituals_block.
tests/test_modes.py +5 ritual tests; 41 green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Seeds the tracker from Brian's real history (import/pokerlog_*.md): each session
block is LLM-extracted into structured meta + hands + villains and written as a
historical session (real date, money, net), with the original markdown stored as
that session's recap.
- lyra/backfill.py: split log -> per-session LLM extract -> seed; dry-run by
default, --commit / --reset; only-real-handle villain filter
- poker.import_session() (historical closed session), clear_all() (reseed),
prune_anonymous_players(), shared _real_handle() filter (also applied in
link_hand_players so auto-linked hand players skip anonymous descriptors + hero),
_normalize_parsed() to map unicode card suits -> letters
- result: 10 sessions, 36 hands, 17 real villain dossiers; running_stats now
reflects real net (+1057 at 1/3 over 8 sessions)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Named players in recorded hands now auto-enrich a persistent dossier, and stats
emerge once the sample is big enough — laying groundwork for A.
- poker: player_observations table (per named player per hand: vpip/pfr/saw_flop/
showed/cards/summary); record_hand auto-links named players via link_hand_players;
player_profile(name) returns dossier + reads + shown hands, with inferred
VPIP/PFR/WTSD gated behind MIN_STATS_SAMPLE (12) so thin samples don't lie;
list_players()
- player_profile tool ("what do I know about X"); thin files return a blunt
"don't generalize" directive
- persona: she MUST call player_profile before discussing an opponent and answer
only from it — fixes observed confabulation (she invented a whole read from one
hand / from memory). Verified: now reports only the real logged hand.
- tests: observation linking, profile, stat-emergence at sample threshold
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Completes the poker copilot loop: talk through a session -> structured capture
-> generated writeup in Brian's format, remembered + exportable.
- poker.generate_recap(): LLM produces Brian's .md log (Session Header, Money
Flow, Overview, Timeline, Key Hands w/ assessments, Villain Notes, Confidence
Bank, Scar Notes, Mental Game, Final Assessment) from the session's structured
data + the linked chat conversation; stored on poker_sessions.recap_md
- sessions now capture chat_session_id (via tool ctx) to pull the right convo;
list_recent_hands() for browsing
- generate_recap tool ("write up the recap")
- web: /recap/{id} (renders the md) + /recap/{id}/download (.md attachment) +
/hands browser (recent hands -> /hand/{id}); nav links added (desktop + mobile)
- tests: recap generation (stubbed), recent-hands listing
Verified live: recap for the Meadows session rendered + downloaded; all pages 200.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per Brian: never invent. Unknown suit -> 'x' (e.g. "Ax","Kx","4x"); fully
unknown card -> "x". "AA, ace of spades" -> ["As","Ax"]; "AK on A4x" -> board
["Ax","4x","x"]. Each card's suit is independent (a hole 'As' doesn't make a
board ace 'As'). Viewer renders 'x' as a muted unknown card and 'Rx' as the rank
with a neutral suit dot.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Brian's idea: vomit rough shorthand, Lyra rebuilds it into a structured,
replayable hand history.
- poker.parse_hand(): focused LLM pass turning shorthand into a canonical hand
JSON (positions, stacks, hero cards, chronological actions w/ board reveals,
result); store_hand_history() persists JSON + extracted flat fields;
record_hand() = parse+store; standalone hands attach to a 'Hand Reviews' session
- poker_hands gains a `structured` JSON column (ALTER-migrated for existing DBs)
- record_hand tool wired into chat: "log this hand: ..." -> reconstructed + a
/hand/{id} link
- web: GET /hand/{id} viewer + /hand/{id}/data — a felt table with seats placed
around the oval (hero at bottom), hole cards, progressive board reveal, and
prev/next/end step-through of the action with running pot
- tests: store/get roundtrip, record_hand tool (stubbed parse)
Verified live: parsed a real AKs hand (BTN, 14 actions, full board) end to end.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The real upgrade over the ChatGPT prose-recap workflow: structured data capture
via tools Lyra drives during a live session, with stats computed from real data.
- lyra/poker.py: domain pack (separate from core memory) — poker_sessions,
poker_hands, persistent poker_players (villain file) + player_reads; functions
for session lifecycle (start/buyin/end with net+hours), tolerant hand logging,
villain upsert/reads, and session/running stats ($/hr, by stake/venue/game)
- tools.py: 8 poker tools wired into the chat tool loop (start_session,
add_buyin, log_hand, add_read, end_session, session_stats, running_stats,
get_villain_file) — partial/terse input tolerated
- import/: Brian's real .md session-log format (reference for the phase-2 recap)
- tests: lifecycle/net math, partial hand logging, villain upsert, running
stats, tool dispatch
Verified live: a full talk-through session persisted as structured rows
(session +240, AKs hand, seat-5 read) — she drove the tools from natural chat.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>