Skip to content

Vesper Ruling — Directional Drift Guard Q1–Q5

To: Orion (he/him) From: Vesper (she/her) CC: Katja (Captain), Atlas (he/him) Date: 2026-04-19 Re: Pre-code rulings for feat/directional-drift-guard — green light given


Q1 — Fill event surface: ACCEPTED

The proposed get_fill_events_since(session_id, watermark_iso) helper is the correct design. Both live and paper fill paths write to the fills table — polling that table with a per-session watermark is a clean, uniform surface that requires no changes to the reconciler, execution engine, or paper fill sim. The join to orders.side is necessary and correct; no existing helper provides this shape. Approved as part of this branch.

The DB read per tick (one indexed range scan on idx_fills_session_id) is acceptable given the existing per-tick query load.


Q2 — Fill side convention: CONFIRMED

side='buy' = buy XRP, pay RLUSD (XRP up, RLUSD down). side='sell' = sell XRP, receive RLUSD. S41 on-chain evidence confirms this. Guard logic should use this convention throughout — a buy burst means XRP accumulation and RLUSD bleed.


Q3 — Insertion point (Step 8.5b): ACCEPTED

Immediately after the anchor saturation guard (Step 8.5), before the "no intents" log and Step 9 submit. Coexistence analysis is correct — both guards use _enter_degraded_mode (idempotent), independent one-shot flags, and independent circuit_breaker_events rows. No ordering conflict. One-tick lag for paper fills on Condition A/B is within the 30s/120s windows and acceptable.


Q3 clarification — live vs paper fill timing

For the record: in live mode, fills from this tick enter the DB in Step 5 (before Step 8.5b), so the guard observes them with ~0ms lag within the same tick. In paper mode, fills from this tick enter after Step 9, so they become visible on the next tick (~4s lag). This is acceptable per the window sizes and acceptable per Atlas's OR-condition design. No change needed.


Q4 — Time source: ACCEPTED

time.time() (wall-clock at observation) for Conditions A and B deques. Tick counter for Condition C. Consistent with the participation filter precedent at main_loop.py:2103. The 4s paper-mode lag is within acceptable margin for 30s/120s windows.

_drift_last_fill_watermark_iso uses fills.created_at (ISO string from DB) as the watermark value — this is correct and distinct from the deque timestamp. The watermark is for DB filtering; the deque timestamp is for window calculations. Both can coexist without confusion.


Q5 — record_circuit_breaker_event writer: CONFIRMED

Use the existing writer established in the anchor saturation guard branch. breaker="directional_drift_guard", manual_reset_required=True. Context payload per condition as designed. No new writer needed.


D1 — get_fill_events_since helper: APPROVED

Additive read helper, no schema change, uses existing index. Essential for this guard — approve as part of this branch. Add to state_manager.py following the existing helper pattern.


D2 — net_notional_threshold_rlusd = 50.0: APPROVED

50.0 RLUSD is a reasonable starting point for our current ~200 RLUSD portfolio. Wire from YAML, no hardcoding. Add a comment in config.example.yaml marking this as requiring calibration after the first live session with the guard active. Do not tune in this branch.


D3 — Wall-clock at observation: APPROVED

Acceptable. The 30s and 120s windows are large enough that the 4s paper-mode lag does not affect whether conditions fire. The approach is simpler and consistent with existing loop code. Approved.


One additional requirement

The design sketch shows _drift_guard_triggered_this_session set before side effects — confirmed this is required. The one-shot flag must be set as the first action inside the trigger block, before the WARNING log, before record_circuit_breaker_event, and before _enter_degraded_mode. This prevents any exception in the side-effect chain from leaving the flag unset and causing a double-trigger on the next tick.

_enter_degraded_mode reason string: "directional_drift_guard_A", "directional_drift_guard_B", "directional_drift_guard_C" — whichever condition fires first. Good — this gives operators immediate context at restart.


Green light

All five questions and three deviations resolved. No blockers. Build as planned.

Deliver in standard format. Vesper reviews before merge.

— Vesper