docs: spec the scouting desk — proactive recall + villain identity resolution
Design for the "she remembers" north star: a poker stats-desk that slides relevant structured context into her prompt before she replies (extends the recall already running in mind.build_messages), plus the hard part — nameless-villain identity resolution. Captures: the two retrieval channels (deterministic entity vs semantic pattern); descriptor-as-fuzzy-key identity (name becomes optional; descriptor embedding, venue scoping, distinctiveness gating so generic descriptions don't cause wrong-guy citations); seat-as-within-session-alias; the live confirm-to-merge loop; and the async review interface (/players browser + a "possible merges / needs clarification" queue that silence-at-the-table routes into, with rejected merges recorded as known-distinct and the merge scan run in the dream cycle). 6-phase sequencing, to build after the trial-by-fire logging session. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,167 @@
|
|||||||
|
# The Scouting Desk — proactive poker recall + villain identity resolution
|
||||||
|
|
||||||
|
*Design spec. Not built yet. Companion to the "she remembers" north star in the
|
||||||
|
`poker-copilot` memory. Written 2026-07-03, before the trial-by-fire session.*
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
Turn the copilot from a logbook into a copilot that **remembers across sessions,
|
||||||
|
unprompted** — the way a broadcast stats desk slides a note to the color
|
||||||
|
commentator: *"he mentioned the guy's hot streak → here are his last 10 games."*
|
||||||
|
|
||||||
|
Target moments:
|
||||||
|
- *"you had this exact leak last week too, remember?"*
|
||||||
|
- *"neck-tattoo guy just 3-bet you — last time he did that at the Meadows he had it."*
|
||||||
|
- *"Sleepy John was here two weeks ago; you stacked off AK into his set."*
|
||||||
|
|
||||||
|
The failure mode to avoid at all costs: **confident-but-wrong.** A stats desk that
|
||||||
|
guesses gets the commentator burned on air. **Silence is the default; the desk
|
||||||
|
speaks only when there's real signal.**
|
||||||
|
|
||||||
|
## What already exists (don't rebuild it)
|
||||||
|
|
||||||
|
`mind.build_messages()` already runs a recall pass on **every** message:
|
||||||
|
`memory.recall(user_msg)` over past exchanges + `memory.recall_summaries(user_msg)`
|
||||||
|
over session gists, injected as system notes before she replies. The
|
||||||
|
"slide-a-note-in-before-she-speaks" machinery is already the architecture. This
|
||||||
|
spec **adds a poker desk** to that pass — it does not build a new RAG system.
|
||||||
|
|
||||||
|
Episodic links also already exist: `link_hand_players` writes a
|
||||||
|
`player_observations` row per named villain in a recorded hand, carrying
|
||||||
|
`hand_id` AND `session_id`; `player_reads` carry `session_id`. So villain →
|
||||||
|
observation → hand → session/date is reconstructable today.
|
||||||
|
|
||||||
|
## Two retrieval channels (don't conflate them)
|
||||||
|
|
||||||
|
1. **Entity desk — deterministic.** A known **name** in the message → exact/fuzzy
|
||||||
|
SQL match on `poker_players` → pull dossier + your history vs him. ~1ms, no
|
||||||
|
hallucination. This is the "hears the name, pulls last 10 games" case.
|
||||||
|
2. **Pattern desk — semantic.** No entity to key on ("I keep punting these river
|
||||||
|
bluffs") → embed the message, retrieve similar **scar notes / hands / recap
|
||||||
|
passages** by meaning. This is where embeddings earn their keep. Also the
|
||||||
|
backbone of nameless-villain matching (below).
|
||||||
|
|
||||||
|
Both feed one injected **STATS DESK** system note, relevance-gated.
|
||||||
|
|
||||||
|
## The hard part: nameless villains
|
||||||
|
|
||||||
|
Most live villains have no name. Brian identifies them by **physical descriptor**
|
||||||
|
("guy with the lips/neck tattoo"), by **seat** ("seat 4", "two to my left"), or —
|
||||||
|
uselessly — **generically** ("mid-aged white dude with glasses").
|
||||||
|
|
||||||
|
### Current gap
|
||||||
|
`poker_players.name` is `NOT NULL` and identity is an **exact name match**
|
||||||
|
(`upsert_player` → `WHERE name = ?`). The `description` column exists but is dead
|
||||||
|
weight: not a key, not embedded, never matched. **Nameless villains can't exist
|
||||||
|
today.** This is the core schema fix.
|
||||||
|
|
||||||
|
### Identity model — descriptor as a fuzzy primary key
|
||||||
|
Store a villain as:
|
||||||
|
- `name` — now **optional**.
|
||||||
|
- `descriptors` — accumulated distinctive physical tags heard over time
|
||||||
|
("neck tattoo", "lips ink", "heavyset", "bald+beard").
|
||||||
|
- `descriptor_embedding` — embedding of the accumulated distinctive tags, for
|
||||||
|
semantic match against drifting phrasings.
|
||||||
|
- `venue` — a strong disambiguator (the neck-tattoo reg at the Meadows ≠ the one
|
||||||
|
at Wheeling, unless Brian travels).
|
||||||
|
- `distinctiveness` — a weight; distinctive features (tattoos, scars, a name)
|
||||||
|
score high, generic ones (age/race/glasses) near zero.
|
||||||
|
|
||||||
|
### Resolver — matching an incoming reference
|
||||||
|
1. **Name present** → exact/fuzzy SQL match (entity desk). Done.
|
||||||
|
2. **Descriptor present** → embed it, compare to `descriptor_embedding` of known
|
||||||
|
villains **scoped to the current venue**, weighted by distinctiveness.
|
||||||
|
3. **Confidence bands:**
|
||||||
|
- **High** (distinctive + strong match) → surface the file; if live, a light
|
||||||
|
confirm ("the neck-tattoo LAG from 3 weeks ago?").
|
||||||
|
- **Medium/ambiguous** (several candidates, or a middling score) → **do NOT
|
||||||
|
interrupt.** File a `needs_clarification` task to the review queue and stay
|
||||||
|
quiet, OR ask only if it's decision-relevant right now.
|
||||||
|
- **Generic-only** (no distinctive signal) → **refuse to guess.** Stay silent
|
||||||
|
or ask for one distinctive detail ("anything that stands out — ink, chips,
|
||||||
|
how he plays?"). Wrong-guy citation is worse than nothing.
|
||||||
|
- **No match** → new villain; open a descriptor-keyed dossier.
|
||||||
|
|
||||||
|
### Seat = within-session alias only
|
||||||
|
The live session keeps a `seat → villain` map so reads accumulate whether Brian
|
||||||
|
says "seat 4" or "the tattoo guy." Seats evaporate when the session ends — they
|
||||||
|
mean nothing next week.
|
||||||
|
|
||||||
|
## Confirmation loop (live, in chat)
|
||||||
|
|
||||||
|
Auto-merging on a fuzzy match is dangerous, so she **proposes and Brian confirms**
|
||||||
|
in natural language:
|
||||||
|
|
||||||
|
> Brian: "neck tattoo guy just 3-bet me again"
|
||||||
|
> Lyra: "The neck-tattoo LAG from the Meadows three weeks ago — the one who
|
||||||
|
> stacked you with the flush? Or new guy?"
|
||||||
|
> Brian: "yeah him" → reinforce identity · "nah different" → split, and learn
|
||||||
|
> what distinguishes them.
|
||||||
|
|
||||||
|
Handles name-arrives-later for free: catch his name off Bravo → "merge neck-tattoo
|
||||||
|
guy into 'Danny'" → history follows.
|
||||||
|
|
||||||
|
## The review interface (async, out-of-band)
|
||||||
|
|
||||||
|
Silence at the table ≠ forget it → it routes to a queue Brian clears at his pace.
|
||||||
|
|
||||||
|
### `/players` — villain file browser
|
||||||
|
List: name-or-lead-descriptor, venue, category (feeder/risky/reg), hands
|
||||||
|
observed, VPIP/PFR (when sample is real), last seen, distinctive tags. Detail
|
||||||
|
view: reads, showdowns, notable hands (link to `/hand/{id}`), sessions seen,
|
||||||
|
stats. Edit / rename / retag / delete / manual-merge.
|
||||||
|
|
||||||
|
### Resolution queue — two lanes
|
||||||
|
- **Possible merges** — two profiles likely one person (high descriptor
|
||||||
|
similarity + same venue, below auto-merge). Side-by-side → **Same guy** (merge)
|
||||||
|
/ **Different** (split).
|
||||||
|
- **Needs clarification** — a descriptor that matched several candidates, or a
|
||||||
|
nameless villain the resolver couldn't place → pick match / **New guy**.
|
||||||
|
|
||||||
|
### Two rules that keep the queue from rotting
|
||||||
|
1. **A rejected merge stays rejected** — record the pair as *known-distinct* so it
|
||||||
|
never re-surfaces; start tracking the distinguishing tell.
|
||||||
|
2. **Merge-candidate scan runs in the dream cycle**, not the hot path — nightly,
|
||||||
|
compare descriptor embeddings within each venue, file new maybes. Zero live
|
||||||
|
latency.
|
||||||
|
|
||||||
|
## Injection format & gating
|
||||||
|
|
||||||
|
A single system note, clearly marked as structured fact so she cites it (not
|
||||||
|
confabulates), e.g.:
|
||||||
|
|
||||||
|
```
|
||||||
|
STATS DESK — Neck-tattoo guy (Meadows, LAG/reg): seen 3×, last 2wk ago.
|
||||||
|
vs you: hand #38 (AK, stacked off into his set). Reads: overfolds turn,
|
||||||
|
3-bets light from the CO. Sample: 22 hands — VPIP 41 / PFR 28.
|
||||||
|
```
|
||||||
|
|
||||||
|
Gate hard: inject only on a confident entity hit or a strong semantic score.
|
||||||
|
Default to nothing. Never inject a generic-only guess.
|
||||||
|
|
||||||
|
## Honest limits
|
||||||
|
|
||||||
|
Never perfect. Some players are genuinely indistinguishable — fine. The system's
|
||||||
|
only job: **right when there's signal, quiet when there isn't.**
|
||||||
|
|
||||||
|
## New data model (sketch)
|
||||||
|
|
||||||
|
- `poker_players`: `name` → nullable; add `descriptors TEXT`,
|
||||||
|
`descriptor_embedding BLOB`, `distinctiveness REAL`.
|
||||||
|
- `player_distinct_pairs(a_id, b_id, note, created_at)` — rejected merges.
|
||||||
|
- `identity_queue(id, kind, player_ids, descriptor, context, session_id,
|
||||||
|
confidence, status, resolution, created_at)` — kind ∈ {merge_candidate,
|
||||||
|
needs_clarification}.
|
||||||
|
- Live-session `seat → player_id` alias map (in-session only).
|
||||||
|
|
||||||
|
## Sequencing (after the trial-by-fire session — recall feeds on real data)
|
||||||
|
|
||||||
|
1. **Nameless identity + resolver** — schema, descriptor embedding, venue-scoped
|
||||||
|
semantic match, distinctiveness gate. (Unblocks everything.)
|
||||||
|
2. **Scouting-desk injection** — wire entity + pattern recall into
|
||||||
|
`build_messages` as the gated STATS DESK note.
|
||||||
|
3. **Confirmation loop** — the live propose/confirm/merge/split UX in the persona.
|
||||||
|
4. **`/players` browser + resolution queue UI** — the async review interface.
|
||||||
|
5. **Dream-cycle merge scan** — nightly candidate generation.
|
||||||
|
6. **Pattern desk** — semantic recall over scars/notes/recaps for "this leak
|
||||||
|
again."
|
||||||
Reference in New Issue
Block a user