Reframed the v1 self-wallet consensus watchers as first-mover OVERLAP detection on the pmv2 pipeline: the cohort watcher emits an append-only ENTER/EXIT event feed, and a cursor-based join against the operator’s own book raises CONFIRM / FOLLOW_OUT / AGAINST alerts annotated with the trader’s confidence score and avg won/lost bet sizes. Shipped 2026-06-02 on main as commit 4794a76 (+932/−2381 lines); migration 20260603000000_pmv2_overlap applied to prod.
For Agents
- Source of truth for cohort ENTER/EXIT:
run_pmv2_positions→ append-onlypmv2_position_events.- Detection/logging is DECOUPLED from the copy-enabled gate; only the paper-copy ACTION is gated on
pmv2_copy_config.enabled. Events populate even with the copy lane OFF.- New module:
overlap_watcher.rs. Cursor key'overlap_watcher'inpm_detection_cursors.- GO-LIVE: overlap / new-position / self-trade alerts are NOT default-off — they fire the moment the binary deploys. Set
pmv2_copy_config.score_threshold > 0(e.g. 0.03–0.05) to avoid noise; default 0 qualifies ALL scored cohort traders.
What Shipped
The v1 self-wallet consensus watchers (change_detection.rs, consensus_participant_changes.rs) were retired and replaced by first-mover overlap detection layered on the pmv2 pipeline. Rather than asking “do my watched wallets agree on a market” (consensus, previously rejected as an edge), the system now asks “did a high-confidence cohort trader just take or leave a position that overlaps my own book, and on which side?”
- Commit
4794a76onmain,+932 / −2381lines (net deletion — large consensus surface removed). - Migration
20260603000000_pmv2_overlapapplied to production.
Architecture — Approach A (event-log join)
The design is a decoupled producer/consumer split: the cohort watcher produces a denormalized event feed; the overlap watcher consumes it via a cursor and joins against the operator’s book.
1. Cohort watcher as source of truth
run_pmv2_positions is now authoritative for cohort ENTER/EXIT and writes an append-only pmv2_position_events feed.
Detection decoupled from the copy gate
A new predicate
should_detect(prior_len) = prior_len > 0controls whether detection + event logging run. This is independent ofpmv2_copy_config.enabled— only the paper-copy ACTION stays gated. Consequence: the event feed populates (and overlap alerts fire) even while the copy lane is OFF.
2. EXIT detection + current-state snapshot mirror
- New
detect_exitedadds cohort EXIT detection. - The cohort snapshot became a true current-state mirror: each cycle DELETEs cohort positions absent from the fresh fetch.
- CRITICAL safety scoping: the delete is restricted to wallets that successfully fetched.
fetch_positionsnow returns(rows, succeeded: HashSet<...>). A timed-out wallet therefore cannot fire false EXITs or mass-delete the snapshot.
Why the
succeededset mattersWithout scoping the delete to wallets that actually returned data, a single timed-out fetch would look like “this wallet exited every position”, firing bogus FOLLOW_OUT alerts and wiping the snapshot. Relates to the proxied-client silent-hang class of bug — timeouts must never be read as “empty result”.
3. Denormalized events
Events are denormalized at log time with run_id, trader_score, avg_won_bet_size, avg_lost_bet_size. This avoids a fragile “which run’s score applies” join and lets events outlive cohort membership (a trader can drop out of the cohort and the historical event still carries its score context).
4. overlap_watcher.rs — the join
Cursor-based (key 'overlap_watcher' in pm_detection_cursors) join of the event feed against the operator’s own book:
| Cohort event | Operator side | Alert |
|---|---|---|
| ENTER | same side | CONFIRM |
| EXIT | same side | FOLLOW_OUT |
| ENTER | opposite side | AGAINST |
| EXIT | opposite side | (intentionally NOT alerted) |
- Dedup via
pm_overlap_alertsUNIQUE(event_id, self_wallet). - Alerts include the trader’s confidence score + avg won/lost bet sizes (explicit user requirement).
Operator Side
- New-position alert: prior-vs-current self-snapshot diff via
repo::self_new_positions, scoped topm_self_wallets+ the latest twoself_snapshotruns, with the first run suppressed (no spurious “everything is new” on cold start). Routed to the ACTIVE notifier. - Notifier routing: self-sync start/complete summaries stay on the SILENT notifier;
run_self_sync_for_schedulernow takes BOTH notifiers so the new-position alert can go active while housekeeping stays quiet. self_trade_changesrepointed off the stalepm_tradestable to the liveDataApiClient::tradesper self-wallet.
Removals
change_detection.rsandconsensus_participant_changes.rsdeleted.- Consensus-only repo/notifier surface removed.
- Pipeline + bot daemon repointed: the overlap watcher replaces change-detection + participant-changes everywhere.
Migration 20260603000000_pmv2_overlap
Forward ALTERs on the LIVE pmv2_position_events table:
- Dropped the per-key UNIQUE constraint — the table is now an append-only lifecycle log (enter → exit → re-enter must all coexist).
kindCHECK changed to('ENTERED', 'EXITED').- Added columns
run_id,trader_score,avg_won_bet_size,avg_lost_bet_size. - Created
pm_overlap_alerts(withUNIQUE(event_id, self_wallet)).
Gotcha — truncated Postgres constraint name
The auto-generated unique constraint name was TRUNCATED to
..._outcome_inde_key(NOT..._index_key). TheDROP CONSTRAINThad to use the real truncated name. Always look up actual constraint names viapg_constraintbefore issuing a DROP — do not assume the name from the column list.
Go-Live Behavior
Not default-off
Unlike the paper-copy lane, the overlap / new-position / self-trade alerts go LIVE the moment the binary deploys. The overlap gate uses
pmv2_copy_config.score_threshold, which defaults to 0 → ALL scored cohort traders qualify → very noisy.Recommendation: set
score_threshold > 0(e.g.0.03–0.05) before or right after deploy. The paper-copy lane stays independentlyenabled = false.
Known Loose End / Follow-up
The self-status CLI and the bot /wallets command still render v1 consensus alignment from the frozen pm_selected_wallets (safe-but-stale; no runtime error). The consensus chain — fetch_consensus_alignment, fetch_raw_watched_consensus, ConsensusAlignment, classify_consensus, fetch_safe_bets_by_market — was intentionally KEPT because deleting it breaks self-status. Reframe or drop that display in a future pass.
Verification
- Built via subagent-driven development (12 tasks), with per-task gates, spec/quality reviews, and a final integration review.
- 0 test failures.
- clippy: only the 3 pre-existing
scoring.rswarnings. - fmt clean.
Related
- pmv2-two-tier-trader-scoring
- pmv2-pipeline-bug-audit
- xref-consensus-rejected-fresh-copy-wins
- copy-trade-lane
- watcher-onchain-gap-backfill — 30s eth_getLogs sweep recovering OrderFilled logs the WSS feed dropped during disconnects
- polymarket-fetch