Ralph Loop 2026-03-16: Candidate Review Gate Fix
Change
Tightened PRD 003 candidate-review classification so research-sleeve actions are no longer labeled deployable_trade unless both of these are true:
- the row itself clears the hard edge gate (
live_edge_bps >= minimum_edge_bps) - the current loop-level deployment gate is ready (
confidence.confident == true)
When either gate fails, the row now stays in review_candidate and records explicit deployment blockers such as live_edge_bps<100.0 and global:score<5.0.
Files:
- research/candidate_review.py
- research/continuous_loop.py
- research/status.py
- research/discord_status.py
- research/README.md
- tests/test_candidate_review.py
Why
The previous packet was overstating live readiness. Politics midprice rows with only 25 bps of modeled edge were being emitted as deployable_trade purely because the research sleeve returned a non-watch action.
That violated the hard deployment gate in PRD 003 and made the status output look more live-ready than the replay confidence justified.
Result
Focused smoke on current data now shows:
deployable=0review=9research=27- top review candidate
KXTAIWANLVL4-26JUL01 - top review blockers include
live_edge_bps<100.0plus global confidence blockers
This keeps current live rows visible for review without blurring them into tradable output.
Validation
Commands run:
pytest -q tests/test_candidate_review.py tests/test_research_status.py
python3 -m py_compile research/candidate_review.py research/continuous_loop.py research/status.py research/discord_status.py
python3 research/candidate_review.py --db data/unicorn.db --recent-runs 6 --max-per-state 3
python3 research/status.py
python3 research/discord_status.py
Observed behavior:
5 passedon the focused research status and candidate-review tests.- Candidate-review smoke no longer emits any
deployable_tradeswhile the loop confidence gate is false. - Human and Discord summaries now report
gate_ready=Falseand show the review candidate’s deployment blockers.
Next bottleneck
The status output still reflects a stale loop snapshot next to a fresher current replay check. The next bounded change should tighten freshness semantics so latest_state.json carries an explicit age or freshness warning for candidate-review and replay handoff consumers.