Skip to content

Vesper Tasking — Anchor Error Per-Tick Telemetry

To: Orion (he/him) From: Vesper (she/her) CC: Katja (Captain), Atlas (he/him) Date: 2026-04-21 Branch: feat/anchor-error-per-tick-telemetry Priority: HIGH — Phase 7.3 gate 6, required before live sessions can be evaluated


Context

The anchor saturation guard is live and evaluates last_anchor_divergence_bps on every tick using a rolling 25-tick window. The guard triggers DEGRADED when the bias and prevalence thresholds are exceeded. What we do not have: any persistent record of what the anchor error was doing tick-by-tick during a session.

After live sessions resume, we need to be able to answer:

  • Were the guard thresholds (6–8 bps mean, 40% prevalence) calibrated correctly?
  • Was the guard too sensitive or not sensitive enough?
  • What was the anchor error distribution over the full session?
  • At what tick did the error first exceed the bias threshold?

Without per-tick anchor error in the DB, post-session calibration is guesswork. This branch closes that gap.

What Already Exists

The anchor_error_bps reliability stat was added in fix/cleanup-and-anchor-instrumentation (Apr 18, commit 469e5bb):

  • _log_anchor_divergence_summary() in main_loop.py computes pct_error_above_5 at session summary time
  • engine_state key anchor.pct_error_above_5bps is written at session close
  • summarize_paper_run.py reads and displays it

That is a session-level aggregate. This branch adds the per-tick time series.

system_metrics is the existing per-tick telemetry table, written every tick, tagged with session_id. It is the correct home for per-tick anchor error. Adding a column there requires the _ensure_column additive migration pattern established on circuit_breaker_events (anchor saturation guard, D2.2).

Spec

One additive column on system_metrics:

anchor_error_bps REAL   -- last_anchor_divergence_bps at tick time; NULL when mid unavailable

Written on every tick when system_metrics is written. Value is self._strategy.last_anchor_divergence_bps — the same source the anchor saturation guard reads from. When that value is None (no market data this tick), write NULL (do not substitute 0.0).

No new table. system_metrics already has session_id and captures all per-tick state. One column is the right scope.

No guard logic. This branch is telemetry only — it does not change any guard behavior, thresholds, or DEGRADED transitions.

No strategy tuning. Config parameters are not touched.

Pre-Code Investigation Required

Before writing any code, investigate and report on the following:

Q1 — system_metrics write location. Where in main_loop.py is the system_metrics row written? What is the function or call site? At that point in the tick, is self._strategy.last_anchor_divergence_bps already computed and in scope? The anchor saturation guard reads this value at Step 8.5 (after Phase 3E anchor diagnostics). Confirm that the system_metrics write happens after Phase 3E — otherwise the value read will be from the previous tick.

Q2 — _ensure_column migration pattern. Confirm the additive column pattern: _ensure_column(conn, "system_metrics", "anchor_error_bps", "REAL") in initialize_database(). Is this the correct call site? Does state_manager.py already apply _ensure_column to system_metrics for any column, or is this the first use on that table? Check for any existing system_metrics schema setup that would need updating alongside the _ensure_column call.

Q3 — NULL vs 0.0 handling in system_metrics. Inspect how other nullable REAL columns in system_metrics are handled when their source value is None (e.g. distance_to_touch_bid_bps — added by Branch #6 and only populated in live mode). Confirm that passing None directly to the insert produces a SQL NULL (not a cast error or a 0.0 substitution). Identify the insert call so I know exactly where to add the new field.

Q4 — Existing anchor_error_bps references. Is there any existing column or field in system_metrics that already captures anchor error under a different name? Check state_manager.py, main_loop.py, and the schema migration history. We must not create a duplicate.

Report findings before writing any code.

Implementation Guidance

Migration: _ensure_column(conn, "system_metrics", "anchor_error_bps", "REAL") in initialize_database(). Existing rows will be NULL — correct, they predate this branch.

Write site: At the system_metrics insert, add anchor_error_bps = self._strategy.last_anchor_divergence_bps (or None if unavailable). Pass None directly — do not substitute 0.0.

No new infrastructure. No new writer function, no new config, no new guard state. One column, one value, one write per tick.

Test Requirements

Minimum 6 tests:

  1. Fresh DB — anchor_error_bps column present after initialize_database()
  2. Existing DB missing column — _ensure_column adds it without error (idempotency)
  3. Tick with valid last_anchor_divergence_bps — correct float written to column
  4. Tick with last_anchor_divergence_bps = None — NULL written (not 0.0)
  5. Multi-tick integration — two ticks written and read back, values match inputs, session_id populated on both
  6. Existing system_metrics columns unaffected — spot-check at least two other columns in a round-trip to confirm no regression

Windows teardown: if any integration test uses a real StateManager with a temp directory, apply the LIFO addCleanup pattern (sm.close() registered before tmpdir.cleanup()).

Commit Plan (suggested)

  1. feat(db): add anchor_error_bps column to system_metrics (per-tick anchor telemetry)
  2. feat(main_loop): write anchor_error_bps to system_metrics on each tick
  3. test(telemetry): anchor error per-tick telemetry — N tests

Regression Suite

After merge, the full regression suite will include this branch's new test file. Current count is 111 + new tests. Provide the updated pytest run command in your delivery.

Constraints

  • _ensure_column migration only — do not ALTER TABLE or DROP/RECREATE
  • No guard logic, no config changes, no strategy tuning
  • NULL for missing mid — do not substitute 0.0
  • No pre-creating the branch during investigation
  • PowerShell apply instructions must use Get-ChildItem form (not *.patch glob)
  • Always include defensive git branch -D before git checkout -b in apply instructions
  • Windows teardown fix required in any integration tests using real StateManager

Deliverable

Standard format: branch name, commit list with hashes and messages, test count and pass rate, pre-code investigation findings, any deviations flagged explicitly. Vesper reviews before merge.

— Vesper