Update main. #6
@@ -0,0 +1,115 @@
|
||||
# Poker mode: message-type-specific prompts + dumb capture
|
||||
|
||||
- **Date:** 2026-06-28
|
||||
- **Status:** Draft for review
|
||||
- **Scope:** Poker (`poker_cash`) mode only. This is the template; the same pattern will later extend to Build/Explore/Study/Decide in a separate pass.
|
||||
|
||||
## Problem
|
||||
|
||||
In poker mode Lyra routes correctly but her replies are generic — "stringing poker words together." Evidence from the 2026-06-27 (Meadows) and 2026-06-28 (Wheeling) sessions in chat `sess-dff2s91c`:
|
||||
|
||||
- **Every turn is a coaching essay, including pure data.** A stack update (`Stack=$685`) draws 4–6 sentences of momentum filler ("keep that momentum rolling," "you've got this"). Bare facts don't need a brain.
|
||||
- **She projects tilt/fatigue onto neutral facts.** "Table broke, it's 11:50pm" → three replies about "late-night fatigue-driven decisions… mental reset." Brian had to say *"I'm not upset… you seem to be reading me as tilted."* Cause: the `_route` mood nudge (`lyra/mind.py:328`) firing on non-mood messages.
|
||||
- **She doesn't reason about bet intent.** On a hand where Brian flopped bottom set, turned a full house, value bet $40 and got 88 to fold, she said *"his fold… shows the power of representing something stronger… well executed."* That was a **value bet that got no value** — money left on the table — not a successful rep. She pattern-matched "bet → fold → good."
|
||||
- **She never calls `analyze_spot`; she eyeballs.** The 77 multiway spot got *"a disciplined fold might have been the better path"* — pure hedge, no math, violating the persona's "never eyeball poker math" rule.
|
||||
- **Even her best reply leaks bad poker logic.** The sharp Connie read included "limp-checking in position" — contradictory nonsense — proving "sounds sharp" ≠ "is correct."
|
||||
|
||||
Root cause: one large per-turn card (`_CASH_CARD`, `lyra/modes.py:66`) describes **traits** ("be decisive," "be present"). The model satisfies trait language with safe, flattering abstraction. Pure-data turns shouldn't reach the model at all; judgment turns need concrete response contracts, not adjectives.
|
||||
|
||||
## Goals
|
||||
|
||||
1. Pure data entry (stack, buy-in, cash-out) never touches the LLM.
|
||||
2. Conversational turns get a **message-type-specific** prompt instead of one broad card.
|
||||
3. Kill the false tilt/fatigue reads.
|
||||
4. On hands, reason about **bet intent** and lean on `analyze_spot` — flag missed value, value-owning, and sizing rather than reflexively praising.
|
||||
5. Fix the iOS-PWA bottom safe-area gap surfaced in testing.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Rewriting the other five modes (separate pass).
|
||||
- Changing the tool handlers themselves (`poker.log_stack`, etc. already exist and are LLM-independent).
|
||||
- An LLM-based classifier in v1 (the design leaves the seam for it; v1 ships heuristics).
|
||||
- Itemized buy-in history (buy-ins remain a single `buy_in_total`).
|
||||
|
||||
## Architecture
|
||||
|
||||
Split poker input by whether it needs judgment.
|
||||
|
||||
- **Track 1 — dumb capture:** pure data → direct HTTP endpoint → tool handler. No prompt, no reply.
|
||||
- **Track 2 — conversational:** anything needing a brain → a classifier tags the message type → a type-specific prompt fragment is injected in place of the giant card.
|
||||
- **PWA fix:** correct the bottom safe-area inset.
|
||||
|
||||
### Deliverable 1 — Track 1: dumb capture (no LLM)
|
||||
|
||||
**Backend (new endpoints in `lyra/web/server.py`).** Today the *only* write path for a stack/buy-in is chat → tool-calling; no direct endpoint exists. Add:
|
||||
|
||||
- `POST /session/stack` → `poker.log_stack(amount)` → returns updated `stack_state()`. Server stamps the time.
|
||||
- `POST /session/buyin` → `poker.add_buyin(amount)` → returns new `buy_in_total`.
|
||||
- Cash-out reuses the existing `PATCH /session/{id}` with `{cash_out}` (already recomputes `net`).
|
||||
|
||||
Both resolve the live session via `poker._resolve(None)` and return `{"ok": ..., "stack": ...}`. On "no live session" return a clear error the UI can show.
|
||||
|
||||
**Frontend — the 2nd input box (`lyra/web/static/index.html`).** A slim quick-capture row **below the message input, above the bottom nav icons**. Behavior:
|
||||
|
||||
- Type a number (e.g. `685`) → `POST /session/stack` → on success, append a one-line confirmation to the **Live Log** (`$685 logged · 11:34pm`) and update the HUD. **No chat message, no LLM call, main message box untouched.**
|
||||
- Scope: **stack only** in v1. Buy-in and cash-out live on the Session HUD widget below.
|
||||
- Empty/non-numeric input is ignored (or shows inline hint). Decimal and `$`-prefixed input tolerated (`$685`, `685`).
|
||||
|
||||
**Frontend — HUD widget (`lyra/web/static/session.html`).** In the Stack card (`session.html:280`), mirroring the existing `saveEdit()` PATCH pattern (`session.html:192`): a stack field (`POST /session/stack`), a buy-in field (`POST /session/buyin`), and a cash-out field (existing PATCH). Each updates the sparkline on success.
|
||||
|
||||
### Deliverable 2 — Track 2: classifier + type fragments
|
||||
|
||||
**Classifier (`lyra/poker_classify.py`, new).** A function `classify(message: str) -> str` returning one of `HAND | STATUS | MENTAL | LOG | CHAT`. v1 is heuristic; the signature is the swappable seam — an LLM/MI50 implementation can replace the body later with no other changes.
|
||||
|
||||
Heuristic signals (first match wins, in priority order):
|
||||
|
||||
1. **HAND** — card-token regex (`\b[2-9TJQKA][shdc]\b` appearing ≥2×), or position tokens (UTG/MP/HJ/CO/BTN/SB/BB/"button"/"hijack"), or street words (flop/turn/river) combined with betting verbs (bet/raise/call/fold/check/shove/limp).
|
||||
2. **MENTAL** — first-person feeling: "I feel", "I'm tilted/steaming/fried/tired/frustrated/confident", "in my head", "mental", "leak", "on tilt".
|
||||
3. **STATUS** — time/venue/logistics with no cards: "table broke", "new table", "waiting for a seat", "seat opened", clock times, venue names.
|
||||
4. **LOG** — prose money/result that slipped past the 2nd box: "I'm at 350", "stack is", "out for", "cashed", "rebought" with a number.
|
||||
5. **CHAT** — default fallback (questions, open talk).
|
||||
|
||||
Ambiguous → `CHAT` (safe full-voice default).
|
||||
|
||||
**Pipeline integration (`lyra/mind.py`).** Add a `_classify` step to `PIPELINE` (after `_route`, before `_compose`) that sets `ctx.msg_type` when `ctx.mode.key == "poker_cash"`. In `build_messages` (`mind.py:137`), when in poker mode, inject the **type fragment** in the seat currently held by `mode.card` (`mind.py:152`) instead of the whole `_CASH_CARD`. Non-poker modes are unchanged.
|
||||
|
||||
**Fragment registry (new `lyra/poker_prompts.py`).** `POKER_FRAGMENTS: dict[str, str]` keyed by type. Each fragment is short and concrete (rules + response shape), not trait language. Persona guardrails ("never eyeball math," "don't invent reads") stay sourced from `personas/lyra.md` via `_persona_block`.
|
||||
|
||||
Response contracts:
|
||||
|
||||
- **HAND.** Steps, in order: (1) log it (`record_hand` preferred, or `log_hand`); (2) if the decision is close or equity matters, call `analyze_spot` — do **not** eyeball; (3) reason about **bet intent** — for each meaningful bet, name what it was for (value / bluff / protection) and whether it achieved that (a fold to a value bet = value lost — flag it; a call of a bluff = bluff failed); (4) name leaks plainly (value-owning, missed value, bad sizing); (5) one clear opinion. **No reflexive praise** — do not open with "nice hand." If a named villain is referenced, call `player_profile`/`get_villain_file` first; never invent a read. Talk in prose, not a listicle. Seed with 2 exemplars (below).
|
||||
- **STATUS.** 1–2 sentences. Acknowledge, log a stack only if a bare number is present, then stop. **No coaching. No tilt/fatigue reads.**
|
||||
- **MENTAL.** Full presence, real voice, an actual point of view. (This register already works — preserve it.)
|
||||
- **LOG.** One line. `$317 logged.` Nothing else.
|
||||
- **CHAT.** Full voice, a real opinion, no filler sign-offs.
|
||||
|
||||
**HAND exemplars** (embedded in the fragment):
|
||||
|
||||
> *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; ~$20–25 gets called by the over-pair and the worse two-pairs. The hand's a monster, the leak is the sizing."
|
||||
|
||||
> *Brian:* "77 in a multiway pot, 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 logic (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."
|
||||
|
||||
**Pipeline fixes:**
|
||||
|
||||
- Remove/gate the `_route` mood nudge (`mind.py:328`) so a steady→"steady" / up→"hype" note is **not** appended on non-MENTAL messages. Mental-game register comes only from the MENTAL fragment.
|
||||
- Trim the always-on mode-menu note (`mind.py:77`) inside poker mode — it dilutes every turn.
|
||||
|
||||
### Deliverable 3 — PWA bottom safe-area fix
|
||||
|
||||
The band below the nav icons is an iOS-PWA safe-area issue (likely `100vh` not accounting for `env(safe-area-inset-bottom)` / the home indicator). Fix the layout container and bottom nav in the web 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
|
||||
|
||||
- **Classifier unit tests:** feed the real user turns from both transcripts and assert the expected type (e.g. "Stack is at $373" → LOG/handled by box, "Button straddle… I limp UTG with 22…" → HAND, "table broke, it's 11:50pm" → STATUS, "I feel like I'm being mean when I raise" → MENTAL).
|
||||
- **Endpoint tests:** `POST /session/stack` / `/session/buyin` against a live test session; assert the row is written, time stamped, and `stack_state()` updates; assert a clean error when no live session.
|
||||
- **Transcript replay (eyeball eval):** run yesterday's + today's user turns through the new classifier + fragments and compare replies to the logged mush — confirm stack updates produce no essay, the $40 hand is flagged as missed value, and STATUS turns carry no tilt-reading.
|
||||
- **PWA:** verify on the iOS PWA that the gap is gone and the new input box sits above the nav with the keyboard open.
|
||||
|
||||
## Out of scope / future
|
||||
|
||||
- LLM classifier behind the same interface (if heuristics prove brittle).
|
||||
- Extending message-type fragments to the other five modes.
|
||||
- Itemized buy-in history / per-table stack series.
|
||||
- Buy-in/cash-out in the chat-page 2nd box (HUD only for v1).
|
||||
Reference in New Issue
Block a user