The pmv2 monolith (polymarket_fetch) is being retired and split into independent services/repos with NATS JetStream as the spine and a shared pmv2-contracts crate as the seam. This is Andras’s direction (2026-06-22, via a Miro board). This note captures the multi-service design and the trade wire-contract v2 that producers (incl. weather) emit. Weather-specific current state lives in weather-bet-live-status; project overview in weather-bet.
For Agents
This is a cross-service design note (touches all
pmv2-*repos), kept in the weather vault because weather is one of the producers and signed off on the contract. The weather-relevant slice — thelimitarm of the contract, weather sizing, the dry-test gate saga — is the authoritative source; the cross-service framing (cohort/crypto/notify) is context. If you only care about weather, read the “Trade wire-contract v2 — weather’slimitarm” section + weather-bet-live-status.
Companion note (AS-lane view)
pmv2-reorg-and-weather-as-2026-06-22 covers the same reorg from the Asian-weather (
weather_as) producer side, with theas_weather_algomodule map + the validated-but-never-live AS edge. Consistent with this note. One update since it was written:pmv2-contractswas described there as “being designed — not yet built”; it is now PUBLISHED (github.com/wowjeeez/pmv2-contracts), which is what unblocks both lanes’ publisher rewrites.
The split (producers → executor → notify)
The monolith’s responsibilities are being decomposed into independent repos under /Users/levander/coding/pmv2/, with unidirectional dependencies (producers → executor → notify):
| Role | Service / repo | Owner | Responsibility |
|---|---|---|---|
| Producer | pmv2-weather-us-algo (THIS, source=weather) | weather | US lock signals (NYC/Chicago/Miami/Atlanta) |
| Producer | pmv2-weather-as-algo (source=weather_as) | weather-AS (separate session) | Asian-market lock signals (~$10–30 notional) |
| Producer | pmv2-cohort-algo (source=first_mover_core) | cohort | cohort first-mover signals |
| Producer | pmv2-crypto-algo (source=crypto_shortterm_latency_test) | crypto | crypto short-term latency signals |
| Executor | pmv2-position-manager | @deploy / @positionmanager | the trade executor (consume + gate + sign + place + settle) |
| Notify | pmv2-telegram-connector / dedicated notification service | TBD | centralizes the ~25–30 scattered Telegram send-sites |
| Seam | pmv2-contracts (PUBLISHED) | shared | the wire types + subject builders both producers + PM vendor |
New @dionysus / @shipping producer lanes were also mentioned as future additions.
Position Manager = the executor
pmv2-position-manager (handle @positionmanager, owned by @deploy) is the executor — polymarket_fetch’s consume + execution responsibilities move here. It:
- Consumes signals DIRECTLY from NATS (no separate runner process in between).
- Gates: 3-layer mode (
off/dry/live) + position caps + a breaker. - Signs EIP-712 orders and POSTs them to
clob.polymarket.com. - Records + settles positions.
- Has no
GammaClient— it does NOT do strategy/eligibility gating. Producers own ALL of that and emit only orders they have already decided to take. PM re-resolves market params (neg_risk/tick/min) authoritatively at placement and applies its global caps/breaker as the hard backstop.
Mode is currently OFF
Position Manager is not live-armed (
Mode::Off). It acks-and-skips. Going live needs the operator to flip the source’s mode (and the weather dry-rehearsal must clear first — see weather-bet-live-status).
Notification service
A dedicated service centralizing the ~25–30 Telegram send-sites that are scattered across the monolith today, sitting behind a NATS notify subject. Owner TBD.
pmv2-contracts — the shared seam (PUBLISHED)
github.com/wowjeeez/pmv2-contracts — a small, published Rust crate that producers and Position Manager both vendor so the wire can’t drift.
- Dependencies:
serde+rust_decimalonly (deliberately minimal — it is a pure types crate). - Holds: the envelope types, a
Pricingdiscriminator enum (limit|derived), aSideenum,SCHEMA_VERSION(= 2), and the subject builders. sourceis a FREEString, NOT an enum. Adding a new producer lane = publish the newsourcestring + insert an operatorpmv2_autotrade_sourcesDB row (which sets that source’s mode). It does not require a contracts release. This is deliberate — it keeps lane-onboarding a config change, not a code+publish cycle.
sourceis a load-bearing exact-match key
sourceis the EXACTpmv2_autotrade_sourcesDB key. A wrong/typo’d value silently routes toMode::Off(the order is acked-and-skipped, no error). Weather’s value is exactly"weather".
Transport (NATS subjects + consumer binding)
- Stream
PMV2_ORDERSover subjectspmv2.order.<source>.<entry|exit>. - Position Manager binds ONE durable pull consumer on
pmv2.order.>(wildcard, all sources, both entry/exit). - Explicit ack,
Nats-Msg-Id = idfor dedup, needs aduplicate_windowconfigured on the stream.
producers (weather/weather_as/cohort/crypto)
→ PMV2_ORDERS / pmv2.order.<source>.<entry|exit>
→ PM single durable pull consumer on pmv2.order.> (explicit ack, Nats-Msg-Id dedup)
→ gate (off/dry/live + caps + breaker) → sign EIP-712 → POST clob.polymarket.com → record/settle
Supersedes the old single-stream design
This replaces the old
WEATHER_SIGNALS/weather.signalssingle-stream, strategy-agnostic-envelope design (where cohort rode the same stream oncohort.signals). The new model is onePMV2_ORDERSstream, source-routed subjects, and a shared contracts crate instead of a hand-trackeddocs/signal-schema.md.
Trade wire-contract v2 — weather’s limit arm
Signed off by weather (2026-06-22)
This is the contract weather emits. It supersedes the old
docs/signal-schema.md“Directive”/v1 envelope (action/max_price/flat market fields,schema_version=1, subjectweather.signals). The old schema file is still v1 in the repo — the v2 producer code change is PENDING, see weather-bet-live-status.
Weather emits a READY BUY entry-order to subject pmv2.order.weather.entry.
Common fields (every order, every source)
| field | notes |
|---|---|
id | `uuid5(“{source} |
source | "weather" — EXACT pmv2_autotrade_sources DB key (wrong value → silently Mode::Off) |
schema_version | 2 |
produced_at | emit time |
valid_until_ts | unix seconds; past → Term (PM drops it as stale) |
market | {condition_id, outcome_index, token_id, title, event_slug, slug, + neg_risk/tick/min as HINTS} |
origin_ref | producer-defined opaque trace ref = our Supabase weather_signals row id |
side | "BUY" |
Pricing arm — pricing = "limit" (weather)
| field | notes |
|---|---|
limit_price | ≤ 0.95, hard cap (weather never overpays) |
size_shares | weather-sized (see Sizing below) |
PM placement semantics (the limit arm)
- PM places a FAK marketable-limit order at
floor_to_tick(min(limit_price, best_ask)). - Fills ≤ our cap. A near-certain bucket whose ask is > 0.95 simply won’t fill — and that is correct, we never overpay.
- PM re-resolves neg_risk/tick/min authoritatively (the values on the wire are HINTS only).
- Sized-out (
shares < min) → Skip.
Entry-only (weather has no exit)
Weather is hold-to-settlement → no exit-signal (the exit is resolution → $1). The exit-signal path (pmv2.order.<source>.exit) is cohort/crypto only. Weather emits to ...entry exclusively.
The derived arm (cohort/crypto, for contrast)
The Pricing enum’s other discriminator is derived — used by cohort/crypto, where PM derives the order price rather than being handed a fixed limit_price. Weather does not use it. (Detail owned by those producers.)
Sizing moved to producers (NEW — weather now sizes)
Sizing moved out of the executor into producers. Weather sizes its own orders:
size_shares = clamp(floor(notional / limit_price), min_size = 5, cap)
- Default notional = $50 (configurable).
min_size = 5shares (the CLOBminimum_order_sizeon weather markets).- PM’s global caps + breaker remain the hard backstop (producer sizing is advisory to PM’s limits).
What stays OFF the execution wire
Weather context — city, bucket_label, observed_high_f, lock_ts, reason — stays off the execution wire. It lives in our Supabase weather_signals row, reachable via origin_ref. PM ignores unknown fields (fail-open on extras), so this is safe, but the convention is: execution wire carries only what PM needs to place + dedup + expire the order.
Weather-AS lane (sibling, separate)
US (source=weather, this repo) and AS/Asian (source=weather_as, pmv2-weather-as-algo, a separate repo/session, ~$10–30 notional) are deliberately separate repos/lanes — NOT a shared engine. They share the contract (pmv2-contracts) and the executor (PM), nothing else.
Related
- weather-bet-live-status — ⭐ weather’s current state: the v2 producer adaptation status, the dry-test gate saga, repo move
- pmv2-reorg-and-weather-as-2026-06-22 — the AS-lane companion view of the same reorg (the
as_weather_algoPython producer + its validated-but-never-live edge) - weather-bet — project overview
- weather-strategy-validated-plan — the lock strategy that produces the signals (unchanged by the re-arch)
- wx-signal-nats-nkey-auth — the NATS publisher auth (NKey) the producer uses
- weather-bet-supabase-migration — the
weather_signalsSupabase row thatorigin_refpoints at