Files
project-lyra/docs/superpowers/specs/2026-06-28-poker-mode-prompts-design.md
serversdown 5c63175a3c docs: restructure spec — poker logging service first, then Lyra wiring
Decompose into two sub-projects: (1) harden poker.py into a standalone
logging service with a complete REST API, a first-class documented
tool/API contract, and a human UI to log/edit/correct everything —
usable by Brian alone, zero LLM dependency; (2) wire Lyra in as a
client (classifier + message-type prompts), parked. Contract is
first-class because it's the shared seam for the cloud model, a
fine-tuned MI50 poker model, RTO, the human UI, and a future MCP wrap.
MCP deferred until a second host app exists.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G796GsLCvJQKVN7hwV2cDx
2026-06-28 20:59:52 +00:00

12 KiB
Raw Permalink Blame History

Poker logging service + message-type prompts

  • Date: 2026-06-28
  • Status: Sub-project 1 spec ready for review; sub-project 2 parked.
  • Branch: feat/poker-mode-prompts

Origin

This started as "make Lyra's poker replies less generic" (message-type-specific prompts). During design we decided to build the logging tool first as a standalone system of record with a clean API and a human-usable UI, then wire Lyra in as a client of it. Rationale:

  • Brian can log and correct data himself, independent of whether Lyra parsed it right (she mislabeled "Dave the rock" vs "Dave the mechanic" mid-session).
  • The data stops being hostage to the agent. Lyra becomes one client among potentially several.
  • It's reusable: RTO (the solver) and a fine-tuned poker model on the MI50 could consume the same hand/session data through the same contract.

Decomposition

Two sub-projects, built and shipped in order.

Sub-project 1 — Poker logging service (this spec)

Harden lyra/poker.py into a well-bounded store, expose a complete REST API over it, define a stable, documented tool/API contract, and build the human UI to log/edit/correct everything. Fully usable by Brian alone, zero LLM dependency.

Sub-project 2 — Lyra wiring (parked; summarized at the end)

Message-type classifier + type-specific prompt fragments; Lyra's tools call the sub-project 1 service. Separately, enabling tool-calling on the MI50 backend so a fine-tuned poker model can drive the same contract.

Why the contract is first-class: in every design (in-process, REST, MCP) the model never calls the API directly — it emits a tool-call and the host app executes it. So what lets the cloud model, the MI50 fine-tune, RTO, and a human UI all interoperate is a single stable tool/API schema (operation names + JSON arg schemas). That contract is the training target for the fine-tune and the seam for every backend. MCP is deferred: it's a thin wrap over the same service, worth adding only when a second host application appears.


Sub-project 1 — Poker logging service

Goals

  1. A complete API surface over the poker data model — create/read/update/delete for every entity, not just the few edit/delete endpoints exposed today.
  2. A single documented, versioned tool/API contract that the REST API, Lyra's LLM tools, the human UI, a future MCP wrapper, and the MI50 fine-tune all share.
  3. A human UI to log (fast capture) and edit/correct (fix Lyra's mistakes) every entity.
  4. The 2nd input box (stack quick-capture) and the iOS-PWA bottom safe-area fix.
  5. Pure data capture never touches the LLM.

Non-goals

  • Lyra's classifier / prompt fragments (sub-project 2).
  • Enabling tools on the MI50 backend (sub-project 2).
  • MCP wrapper (deferred until a second host app exists).
  • Itemized buy-in history — buy-ins stay a single buy_in_total scalar.
  • Rewriting the SQLite schema; we build on the existing tables.

Current state (what exists)

  • Store & logic: lyra/poker.py — schema at poker.py:21 (tables poker_sessions, poker_hands, poker_stack_log, poker_rituals, poker_players, player_reads, player_observations). Functions: start_session (157), add_buyin (389), log_stack (407), stack_state (446), update_session (362), end_session (515), log_hand (541, flat/no-LLM), record_hand (770, LLM-parses shorthand), add_read (1010), hud (1245).
  • Exposed endpoints (lyra/web/server.py): GET /session/data (hud), PATCH /session/{id}, DELETE /session/entry/{kind}/{id}, GET/DELETE /history, GET /hand/{id}/data, POST /hand/{id}/reconstruct, GET /hands/data, GET /recap/.... No direct create endpoint for stack/buyin/hand/read/session — those are reachable only through chat → tool-calling.
  • UI: index.html (chat), session.html (live HUD: stack card + sparkline + a PATCH-based edit form via saveEdit() at session.html:192, and del(kind,id) at 211), history.html, hand.html.
  • Tool specs: lyra/tools.py already defines arg schemas for each operation (_f(...) specs, tools.py:469-658) — the embryo of the contract.

Design

1. Store layer — harden poker.py

Keep the existing functions and tables; tighten the module into a clean service boundary so both the REST layer and Lyra's tools call the same functions. Each operation: validates input, resolves the target session (_resolve), writes, returns a consistent dict. No behavior change to existing callers; this is consolidation, not a rewrite.

2. The tool/API contract (first-class deliverable)

A single source-of-truth document + schema defining every operation: name, purpose, JSON arg schema, return shape, and which REST route + which LLM tool map to it. Versioned (e.g. contract_version: 1). Lives at docs/POKER_API.md (or a machine-readable poker_contract.py that both the REST routes and tools.py specs derive from — preferred, so they can't drift).

Operations (the canonical set):

Operation Args Entity
start_session venue, stakes, game, format, buy_in, mantra session
update_session venue, stakes, game, format, buy_in_total, cash_out, mantra, mood session
end_session cash_out, mood session
delete_session id session
log_stack amount, note stack entry
delete_stack id stack entry
add_buyin amount session (increments buy_in_total)
log_hand position, hole_cards, board, streets…, pot, result, tag, lesson hand
record_hand shorthand (LLM-parsed) hand
update_hand id, any hand field hand
delete_hand id hand
add_read note, name, seat, tendencies, adjustment, category, venue player/read
update_read / update_player id, fields player/read
delete_read id player/read
rituals: scar_note, confidence_bank, alligator_blood, reset_ritual ritual

3. REST API — complete the surface (lyra/web/server.py)

Add the missing create/update routes so the human UI (and any non-LLM client) can do everything:

  • POST /session/stacklog_stack(amount, note?); server-stamped time; returns stack_state().
  • POST /session/buyinadd_buyin(amount); returns buy_in_total.
  • POST /sessionstart_session(...).
  • POST /session/handlog_hand(...) (flat) and/or record_hand(shorthand).
  • PATCH /hand/{id}update_hand(...); DELETE /hand/{id}.
  • POST /session/readadd_read(...); PATCH /read/{id}; DELETE via existing entry-delete.
  • Keep existing PATCH /session/{id}, DELETE /session/entry/{kind}/{id}, GET /session/data.

All return {ok, ...} and a clear error on "no live session." Routes are thin wrappers over the store, mirroring the contract one-to-one.

4. Human UI — log + edit/correct

Fast capture:

  • 2nd input box (index.html): slim row below the message input, above the bottom nav icons. Type a number → POST /session/stack → time-stamped, sparkline updates, a one-line confirmation drops into the Live Log. No chat message, no LLM call. Stack-only in v1. Tolerates $685/685.
  • HUD widget (session.html, in the Stack card at :280, mirroring saveEdit() at :192): stack field (POST /session/stack), buy-in field (POST /session/buyin), cash-out field (existing PATCH).

Edit / correct (fix Lyra's mistakes):

  • Edit any session field (exists via the PATCH edit form — verify coverage).
  • Hands list with edit + delete (hand.html + new PATCH/DELETE) — fix mislabeled villains, wrong board, wrong result.
  • Reads/players list with edit + delete — rename "Dave the rock" ≠ "Dave the mechanic", fix tendencies.
  • Stack entries deletable (exists via del('stack', id)) — verify.

5. iOS-PWA bottom safe-area fix

The empty band below the nav icons is a safe-area issue (likely 100vh not accounting for env(safe-area-inset-bottom) / the home indicator). Fix the layout container + bottom nav CSS so the app fills the viewport with the icons seated above the home indicator. Use the building-ios-pwas skill at implementation time.

Testing / verification

  • Contract conformance: a test asserting each REST route and each tools.py spec matches the canonical contract (names, required args) — catches drift between the human API and the LLM API.
  • Endpoint round-trips: create → read → update → delete for stack, buyin, hand, read against a test session; assert rows written, time stamped, stack_state()/hud() reflect changes; assert clean error with no live session.
  • UI manual pass: log a stack via the 2nd box and confirm it lands in Live Log + sparkline without a chat reply; edit a hand's villain and confirm persistence; delete a bad read.
  • PWA: on the iOS PWA, confirm the bottom gap is gone and the 2nd input box sits above the nav with the keyboard open.

Sub-project 2 — Lyra wiring (parked)

Detail preserved here; gets its own spec → plan after sub-project 1 is MVP'd.

Why it exists (diagnosis from real sessions)

Evidence from sess-dff2s91c (2026-06-27 Meadows, 2026-06-28 Wheeling):

  • Coaching essay on every turn, including pure dataStack=$685 drew 46 sentences of "keep that momentum rolling." (Sub-project 1's dumb capture removes these from the LLM entirely.)
  • False tilt/fatigue reads — "table broke, it's 11:50pm" → repeated "late-night fatigue… mental reset"; Brian: "you seem to be reading me as tilted." Cause: the _route mood nudge (mind.py:328) firing on non-mood messages.
  • No bet-intent reasoning — a value bet ($40, full house) that folded out 88 was praised as "the power of representing something stronger." It was value lost, not a successful rep.
  • Eyeballs instead of analyze_spot — 77 multiway got "a disciplined fold might have been better," no math, violating the persona's "never eyeball poker math" rule.
  • Even her sharp reads leak bad logic — the Connie read included "limp-checking in position" (contradictory).

Root cause: one broad per-turn card (_CASH_CARD, modes.py:66) describes traits; the model satisfies trait language with safe abstraction.

Planned approach

  • Classifier (lyra/poker_classify.py): classify(message) -> HAND | STATUS | MENTAL | LOG | CHAT. Heuristic v1 (card-token regex, position/street keywords, feeling phrases, time/venue), swappable for an LLM/MI50 classifier behind the same signature. Ambiguous → CHAT.
  • Pipeline: a _classify step in mind.PIPELINE sets ctx.msg_type (poker mode only); build_messages injects the type fragment in the seat now held by mode.card (mind.py:152) instead of the whole _CASH_CARD.
  • Fragments (lyra/poker_prompts.py, POKER_FRAGMENTS): concrete rules + response shape per type.
    • HAND: log it → analyze_spot if close → reason about bet intent (value/bluff/protection — did it work? a fold to a value bet = value lost, flag it) → name leaks (value-owning, missed value, sizing) → one opinion. No reflexive praise. Pull player_profile before referencing a villain. Seeded with 2 real-hand exemplars (the $40 value bet; the 77 spot).
    • STATUS: 12 sentences, no coaching, no tilt-reading.
    • MENTAL: full presence (already works — preserve).
    • LOG: one line.
    • CHAT: full voice, real opinion.
  • Pipeline fixes: kill the misfiring _route mood nudge (mind.py:328); trim the always-on mode-menu note (mind.py:77) in poker mode.
  • MI50 tool-calling: enable tools on the MI50 backend (chat.py TOOL_BACKENDS = {"cloud"} at :21) so a fine-tuned, tool-calling poker model can drive the contract. Requires the fine-tune to emit the contract's tool-call format.

HAND exemplars (preserved for sub-project 2)

Brian: "Flopped bottom set with 22, turned a full house, bet $40 on the river, he folded 88." Lyra: "That's a value bet that got no value — 88 was never folding to a smaller stab. $40 into that pot was too much; ~$2025 gets called by the over-pair and worse two-pairs. The hand's a monster, the leak is the sizing."

Brian: "77 multiway, flop 4h5c3c, HJ bets 75, BTN shoves 74, I call. Turn/river check through, HJ wins with 64." Lyra: "Against a bet and a shove on 4-5-3 you're drawing thin — sets, two pair, and the made wheel are all ahead, and you block almost none of it. The stack-depth read (he only had ~150 behind) is real, but that's a reason to fold and wait, not to call off light. This is the value-owning spot you flagged yourself."