Ralph Loop 2026-03-16: Candidate Review Blocker Split
Change
Added explicit local-vs-global blocker attribution to the PRD 003 candidate-review packet.
Files:
- research/candidate_review.py
- research/continuous_loop.py
- research/status.py
- research/discord_status.py
- research/README.md
- tests/test_candidate_review.py
- tests/test_continuous_loop.py
- tests/test_research_status.py
Why
The packet already exposed top_invalidation_reasons, but that list can mix:
- row-level sleeve or market blockers such as
live_edge_bps<100.0 - loop-level deployment blockers such as
global:score<5.0
When the loop is globally blocked, Ralph needs to know whether candidate quality is still improving underneath that gate. A single mixed blocker list makes that hard to see.
Result
Candidate review now carries:
top_invalidation_reasonstop_local_invalidation_reasonstop_global_invalidation_reasons
The continuous-loop event payload now also emits the top local/global blocker, and the human + Discord status summaries print those splits directly.
Validation
pytest -q tests/test_candidate_review.py tests/test_continuous_loop.py tests/test_research_status.py- Result:
15 passed python3 -m py_compile research/candidate_review.py research/continuous_loop.py research/status.py research/discord_status.pypython3 research/candidate_review.py --db data/unicorn.db --recent-runs 6 --max-per-state 2- Observed:
top_local_invalidation_reasonsis dominated byheuristic_direction=neutral,live_price<0.7, andcategory!=Economics, whiletop_global_invalidation_reasonsisolates the loop gate asglobal:deployment_gate_not_met python3 research/status.py- Observed: status now prints both
local_blockers=...andglobal_blockers=...
Outcome
This is a bounded PRD 003/operator-quality improvement. It does not create trades or loosen the deployment gate; it makes candidate-review quality legible when the loop remains globally blocked.