Skip to content

[C] Orion Delivery — fix/drift-c-anchor-idle-suppression

To: Vesper From: Orion Branch: fix/drift-c-anchor-idle-suppression Base: current main (post FLAG-048 merge a8033e5) Date: 2026-04-22


Summary

Narrow containment fix per your tasking note (TO_ORION_containment_fix_drift_c_anchor_idle.md, 2026-04-22). Drift guard condition C ("no opposing fill in N ticks") no longer escalates from ANCHOR_IDLE to DEGRADED. Conditions A (burst) and B (net notional imbalance) are unchanged and continue to escalate from any state, including ANCHOR_IDLE.

Two-patch bundle: one code commit, one test commit. No refactor, no adjacent edits.

feat commit f74f970feat(engine): drift guard condition C — suppress while MODE_ANCHOR_IDLE
test commit f9acbc0test(drift_guard): 6 tests covering drift-C suppression while ANCHOR_IDLE
Files touched (feat) neo_engine/main_loop.py — one block inside _evaluate_directional_drift_guard, plus docstring update
Files touched (test) tests/test_drift_c_anchor_idle_suppression.py — new file, 393 lines, 6 tests
Net diff +~410 / −~15

What changed in code

Single-site edit in _evaluate_directional_drift_guard in neo_engine/main_loop.py. Immediately before the condition-C block, engine truth mode is queried once via self._current_truth_mode(). The existing condition-C branch was split into two mutually exclusive forms:

  • If the engine is not in MODE_ANCHOR_IDLE and the C counters are tripped → fire C, set cond = "C", proceed to the usual DEGRADED escalation path.
  • If the engine is in MODE_ANCHOR_IDLE and the C counters are tripped → log a [DRIFT_GUARD] condition_C suppressed debug line carrying the current counter values and fills_seen_this_session, then fall through. No escalation, no episode counted, no state change.

Conditions A and B are structurally untouched. Fill-history updates, counter increments (_drift_ticks_since_opposing_fill, _drift_fills_seen_this_session), and the recovery-evaluator path are all unchanged. The only mutation is: "when the gate is closed, C is not allowed to be the trigger."

Docstring on _evaluate_directional_drift_guard updated to document the gate and name the ruling source (Vesper tasking 2026-04-22).


Tests

New file: tests/test_drift_c_anchor_idle_suppression.py — 6 tests, all green.

# Test Verifies
T1 test_condition_c_suppressed_while_anchor_idle C counters tripped while truth_mode=MODE_ANCHOR_IDLE → no DEGRADED, no episode. Counters still increment, suppression debug log emitted.
T2 test_condition_c_resumes_after_idle_exit Two successive evaluator calls — first in ANCHOR_IDLE (silenced), second with mode flipped to MODE_OK (fires C, sets DEGRADED). Confirms gate is stateless and re-opens on its own.
T3 test_condition_a_still_escalates_from_anchor_idle 3 same-side buys within burst window while ANCHOR_IDLE → A still fires.
T4 test_condition_b_still_escalates_from_anchor_idle 2 sell fills pushing net notional past −50 RLUSD threshold while ANCHOR_IDLE → B still fires.
T5 test_condition_c_still_fires_when_mode_ok Regression: the exact scenario that drove the S51 halt fires C normally when the engine is in MODE_OK.
T6 test_condition_c_still_fires_in_degraded_and_halt subTest over MODE_DEGRADED and MODE_HALT — gate is narrowly ANCHOR_IDLE only; other mode names do not confer suppression. Prevents future scope creep from silently widening the gate.

Adjacent regression

Ran pytest tests/test_directional_drift_guard.py tests/test_anchor_idle_state.py tests/test_flag_042_degraded_recovery.py tests/test_flag_044_recovery_cooldown.py tests/test_flag_046_anchor_idle.py tests/test_drift_c_anchor_idle_suppression.py — 79 passed, 0 failed. No changes to existing drift-guard tests.

Net-net check

Sanity verification: with the feat patch reverted, T1 and T2 fail (as expected — C would escalate through ANCHOR_IDLE); with the feat patch applied, all 6 pass. Exactly matches the suppression scope.


Engineering questions — inline answers

Q1 — Code path: exact call stack from ANCHOR_IDLE into the drift evaluator; does the evaluator see engine state before condition C?

Call stack (pre-fix):

NEOEngine.run_one_tick
  → step 8.5b (post-fill / per-tick guards block, main_loop.py ~line 4370)
    → self._evaluate_directional_drift_guard()
        → cond = None
        → (A) burst check
        → (B) net notional check
        → (C) if fills_seen >= min_fills AND ticks_since_opposing >= threshold
            → cond = "C"
            → self._enter_degraded_mode(source=SOURCE_DRIFT, ...)

Pre-fix the evaluator did NOT consult engine state before evaluating C. The cond = "C" branch was unconditional on everything except its own counters. self._current_truth_mode() was not called anywhere in _evaluate_directional_drift_guard prior to this patch. That is the structural root cause of the S51 cascade: the evaluator had no way to distinguish "no opposing fill because the market went one-sided" from "no opposing fill because the engine is paused."

Post-fix: _current_truth_mode() is queried once at the top of the C block (still inside the same function — no added call sites in run_one_tick). A single is_anchor_idle bool gates the C branch. A and B paths never read it.


Q2 — Fill history across the ANCHOR_IDLE boundary: is the condition-C rolling window reset on ANCHOR_IDLE entry, or does it carry the pre-idle record forward?

Fill history is not reset on ANCHOR_IDLE entry. The C signal reads two engine-owned counters:

  • self._drift_fills_seen_this_session — monotone session-level count of fills observed by the drift guard.
  • self._drift_ticks_since_opposing_fill — tick counter, reset to 0 when an opposing fill is seen, incremented every tick otherwise.

Neither is cleared by the anchor-idle entry path or by the anchor-idle exit path. They are only reset by _evaluate_drift_recovery on a successful drift recovery (source=SOURCE_DRIFT). This means:

  1. If the engine enters ANCHOR_IDLE holding N ticks on the _drift_ticks_since_opposing_fill counter, that counter keeps incrementing every tick during idle.
  2. Without this patch, it will hit the threshold while idle and escalate C even though, by construction, the engine is not quoting and cannot produce the "opposing fill" the counter is measuring.
  3. With this patch, the counter still increments (so A/B behavior is unchanged and recovery logic sees continuous counters), but the counter-crossing no longer produces a DEGRADED escalation while idle.

Out of scope per your tasking: whether the window should be reset on ANCHOR_IDLE entry is left for the Atlas architectural review. This patch preserves current fill-history semantics.


Q3 — Tick-count breakdown for S51 (per-episode tick numbers)

Direct S51 data is not available. S51 lived only on neo_live_stage1.db, which is currently malformed (FLAG-049 scenario — exact SMB/WAL pathology Atlas ruled on). The latest clean backup (T165223Z) predates S51. circuit_breaker_events rows for S51 are not recoverable from any snapshot I have access to.

Analog evidence from the backup (sessions 50 and 52, both degraded_episode_limit_halt under the pre-FLAG-046 regime where anchor saturation could still escalate to DEGRADED via the same episode-count pathway):

session_id ticks halt_reason drift-C episode pattern
50 33 degraded_episode_limit_halt 3× condition-C escalations, all with fills_seen=1, ticks_since_opposing ∈ {15, 15, 16}
52 38 degraded_episode_limit_halt 3× condition-C escalations, same counter signature

Theoretical S51 breakdown (from cooldown model, no cap on exact alignment):

  • recovery_cooldown_ticks = 120
  • Drift threshold no_opposing_fill_ticks = 15
  • Drift stability window (ticks held inside recovery before counter test restarts) = 10

Starting from the first post-buy-fill tick at which the engine enters ANCHOR_IDLE:

Episode Fires at tick (offset) What happens
1 +15 C fires → DEGRADED → cancel-all → cooldown starts
2 +15 + ~10 + 15 ≈ +40 still ANCHOR_IDLE, counter re-crosses → C fires again
3 +~65 same pathway → hits max_degraded_episodes_per_source_per_session=3
halt ~tick 65–75 degraded_episode_limit_halt

Your tasking cites S51 halt at tick 26 — that implies the ticks carried pre-existing counter state from before ANCHOR_IDLE entry (the _drift_ticks_since_opposing_fill counter had already been ticking while the engine was quoting), which compresses the above timeline into the observed ~26-tick span. The exact S51 per-episode tick numbers will need to come from the recovered log stream; I do not have them from current sources.


Q4 — Have conditions A or B ever escalated from ANCHOR_IDLE in the log record?

No. I swept every circuit_breaker_events row across the 52 sessions in the clean backup (T165223Z) and found:

  • 6 drift-guard DEGRADED escalations total.
  • All 6 are condition C. Zero A, zero B.
  • Counter signatures are nearly identical: fills_seen_this_session=1, ticks_since_opposing ∈ {15, 16}.

Additional context: ANCHOR_IDLE did not exist as a first-class state prior to FLAG-046 (merged Apr 22). So strictly speaking, a "from ANCHOR_IDLE" witness for A or B is structurally impossible in the backup — the state wasn't there to be in. Any such witness would have to come from S51 onward, which lives only on the malformed live DB.

Net: there is no evidence in the log record of A or B firing from an idle-shaped engine state. Condition C is the only drift path that has ever been observed to produce a DEGRADED escalation in production, let alone one traceable to a paused engine. This supports the narrow-gate scope of this fix.


Apply instructions (PowerShell, Katja's Windows box)

Run from the NEO repo root (C:\Users\Katja\Documents\NEO GitHub\neo-2026\). Branch is NOT pre-created — these commands create it at apply time:

git fetch origin
git checkout main
git pull --ff-only origin main
git branch -D fix/drift-c-anchor-idle-suppression 2>$null
git checkout -b fix/drift-c-anchor-idle-suppression
Get-ChildItem "C:\Users\Katja\Documents\Claude Homebase Neo\02 Projects\NEO Trading Engine\03 Branches\fix-drift-c-anchor-idle-suppression" -Filter "*.patch" | Sort-Object Name | ForEach-Object { git am $_.FullName }

Then verify:

git log --oneline -3
# expected: f9acbc0 test(drift_guard): 6 tests ...
#           f74f970 feat(engine): drift guard condition C ...
#           <current main HEAD>

python -m pytest tests/test_drift_c_anchor_idle_suppression.py -v
# expected: 6 passed

Then adjacent-suite regression (optional, 79 tests, ~4s):

python -m pytest tests/test_directional_drift_guard.py tests/test_anchor_idle_state.py tests/test_flag_042_degraded_recovery.py tests/test_flag_044_recovery_cooldown.py tests/test_flag_046_anchor_idle.py tests/test_drift_c_anchor_idle_suppression.py -v

Scope boundary, restated

Touched: _evaluate_directional_drift_guard (one block + docstring), one new test file. Nothing else.

Not touched (intentionally, per your tasking): - Drift fill-history reset semantics at ANCHOR_IDLE entry/exit - Episode counter bookkeeping (whether idle-era episodes should count toward the FLAG-044 limit) - Conditions A and B - Cooldown model, episode limit values, recovery evaluator - MODE_ANCHOR_IDLE transition logic or _enter_anchor_idle_mode

All of those belong in the Atlas architectural ruling after your audit package lands.


— Orion 2026-04-22

View this memo