agent_panel: alerts use the macro persona's verdict; all 4 takes still logged
agent_panel.verdict_source defaults to "macro": the macro take's stance is the verdict, the synthesizer call is skipped (4 LLM calls per panel instead of 5); "chair" restores the four-voice consensus + OPPOSE guard. Verdict source is cached per mode and logged (panel_verdict_source). 406 tests. Claude-Session: https://claude.ai/code/session_01FC4s6VCURFte4dub4xKxYL
This commit is contained in:
+105
-6
@@ -6319,6 +6319,22 @@ def _():
|
||||
pass
|
||||
|
||||
|
||||
@test("app_config: agent_panel.verdict_source validated")
|
||||
def _():
|
||||
assert load_app_config(_write_json(
|
||||
{"agent_panel": {"verdict_source": "macro"}}
|
||||
)).agent_panel_settings["verdict_source"] == "macro"
|
||||
assert load_app_config(_write_json(
|
||||
{"agent_panel": {"verdict_source": "chair"}}
|
||||
)).agent_panel_settings["verdict_source"] == "chair"
|
||||
try:
|
||||
load_app_config(_write_json(
|
||||
{"agent_panel": {"verdict_source": "technical"}}))
|
||||
assert False, "should have raised"
|
||||
except AppConfigError:
|
||||
pass
|
||||
|
||||
|
||||
import agent_panel as ap
|
||||
from signal_aggregator import SignalReport, TradeSetup
|
||||
|
||||
@@ -6361,7 +6377,7 @@ _ALL_OK = {ap._MACRO_SYSTEM: _TAKE_OK, ap._TECH_SYSTEM: _TAKE_OK,
|
||||
ap._SYNTH_SYSTEM: _SYNTH_OK}
|
||||
_PANEL_ON = {"enabled": True, "telegram": True, "dashboard": False,
|
||||
"min_quality": "B", "max_panels_per_day": 20,
|
||||
"reuse_ttl_seconds": 3600}
|
||||
"reuse_ttl_seconds": 3600, "verdict_source": "chair"}
|
||||
|
||||
|
||||
def _panel_report(quality="A", signal=SignalStrength.BUY):
|
||||
@@ -6490,7 +6506,8 @@ def _():
|
||||
for synth in (None, {"verdict": "MAYBE", "confidence": 50},
|
||||
{"verdict": "AGREE", "confidence": "hot"}):
|
||||
lc._client = _PanelStub({**_ALL_OK, ap._SYNTH_SYSTEM: synth})
|
||||
assert ap.run_panel(_panel_report()) is None, f"synth={synth}"
|
||||
assert ap.run_panel(_panel_report(), verdict_source="chair") is None, \
|
||||
f"synth={synth}"
|
||||
finally:
|
||||
lc._client = None
|
||||
ap.reset_panel_state()
|
||||
@@ -6508,8 +6525,9 @@ def _():
|
||||
stub.replies[ap._MACRO_SYSTEM] = {
|
||||
**stub.replies[ap._MACRO_SYSTEM], "stance": "oppose"}
|
||||
lc._client = stub
|
||||
v = ap.run_panel(_panel_report())
|
||||
v = ap.run_panel(_panel_report(), verdict_source="chair")
|
||||
assert v.verdict == "OPPOSE" and v.confidence == 72.4
|
||||
assert v.source == "chair"
|
||||
assert v.payload_hash.startswith("sha256:")
|
||||
assert len(v.rationale) == 240, "rationale truncated"
|
||||
assert [t.persona for t in v.takes] == \
|
||||
@@ -6522,6 +6540,57 @@ def _():
|
||||
ap.reset_panel_state()
|
||||
|
||||
|
||||
@test("agent panel: macro-only mode uses the macro take as the verdict and skips the chair")
|
||||
def _():
|
||||
try:
|
||||
stub = _PanelStub(dict(_ALL_OK))
|
||||
lc._client = stub
|
||||
v = ap.run_panel(_panel_report())
|
||||
assert v is not None
|
||||
assert ap._SYNTH_SYSTEM not in [s for s, _ in stub.calls], \
|
||||
"macro-only mode must not spend the synthesizer"
|
||||
assert len(stub.calls) == 4
|
||||
assert v.verdict == "AGREE", "macro take's stance is support"
|
||||
assert v.confidence == _TAKE_OK["confidence"]
|
||||
assert v.rationale == _TAKE_OK["note"]
|
||||
assert v.source == "macro"
|
||||
assert [t.persona for t in v.takes] == \
|
||||
["macro", "technical", "risk", "contrarian"]
|
||||
finally:
|
||||
lc._client = None
|
||||
ap.reset_panel_state()
|
||||
|
||||
try:
|
||||
stub2 = _PanelStub(dict(_ALL_OK))
|
||||
stub2.replies[ap._MACRO_SYSTEM] = {**_TAKE_OK, "stance": "oppose"}
|
||||
lc._client = stub2
|
||||
v2 = ap.run_panel(_panel_report())
|
||||
assert v2.verdict == "OPPOSE", \
|
||||
"macro-only verdict follows macro even when others support"
|
||||
assert ap._SYNTH_SYSTEM not in [s for s, _ in stub2.calls]
|
||||
finally:
|
||||
lc._client = None
|
||||
ap.reset_panel_state()
|
||||
|
||||
|
||||
@test("agent panel: macro take missing falls back to the chair")
|
||||
def _():
|
||||
try:
|
||||
stub = _PanelStub(dict(_ALL_OK))
|
||||
stub.replies[ap._MACRO_SYSTEM] = None # _validate_take rejects it
|
||||
lc._client = stub
|
||||
v = ap.run_panel(_panel_report())
|
||||
assert v is not None
|
||||
assert ap._SYNTH_SYSTEM in [s for s, _ in stub.calls], \
|
||||
"a missing macro take must fall back to the synthesizer"
|
||||
assert v.source == "chair"
|
||||
assert v.verdict == _SYNTH_OK["verdict"]
|
||||
assert [t.persona for t in v.takes] == ["technical", "risk", "contrarian"]
|
||||
finally:
|
||||
lc._client = None
|
||||
ap.reset_panel_state()
|
||||
|
||||
|
||||
@test("agent_panel: NEUTRAL signal → no panel even forced")
|
||||
def _():
|
||||
try:
|
||||
@@ -6590,6 +6659,32 @@ def _():
|
||||
ap.reset_panel_state()
|
||||
|
||||
|
||||
@test("agent panel: attach_panel honours verdict_source and caches per source")
|
||||
def _():
|
||||
try:
|
||||
stub = _PanelStub(dict(_ALL_OK))
|
||||
lc._client = stub
|
||||
ap._dp = _NullDP()
|
||||
report = _panel_report()
|
||||
assert ap.attach_panel(report, {**_PANEL_ON, "verdict_source": "macro"},
|
||||
context=FundamentalContext()) is True
|
||||
assert report.panel_verdict_source == "macro"
|
||||
assert report.panel_cache_status == "MISS"
|
||||
assert len(stub.calls) == 4
|
||||
|
||||
report2 = _panel_report()
|
||||
assert ap.attach_panel(report2, {**_PANEL_ON, "verdict_source": "chair"},
|
||||
context=FundamentalContext()) is True
|
||||
assert report2.panel_verdict_source == "chair"
|
||||
assert report2.panel_cache_status == "MISS", \
|
||||
"a chair-mode attach must not reuse a macro-mode cached verdict"
|
||||
assert len(stub.calls) == 9
|
||||
finally:
|
||||
lc._client = None
|
||||
ap._dp = None
|
||||
ap.reset_panel_state()
|
||||
|
||||
|
||||
@test("agent_panel: daily budget exhausts and re-arms on UTC roll")
|
||||
def _():
|
||||
try:
|
||||
@@ -6972,7 +7067,8 @@ def _():
|
||||
lc._client = stub
|
||||
try:
|
||||
ap.reset_panel_state_for_tests() if hasattr(ap, "reset_panel_state_for_tests") else None
|
||||
verdict = ap.run_panel(_panel_report(), FundamentalContext())
|
||||
verdict = ap.run_panel(_panel_report(), FundamentalContext(),
|
||||
verdict_source="chair")
|
||||
assert verdict is not None
|
||||
assert verdict.verdict == "NEUTRAL", verdict
|
||||
assert verdict.confidence <= 49.0
|
||||
@@ -7190,13 +7286,15 @@ def _():
|
||||
slog._dumps(report.panel_takes),
|
||||
created_at=report.panel_created_at,
|
||||
cache_status=report.panel_cache_status,
|
||||
payload_hash=report.panel_payload_hash)
|
||||
payload_hash=report.panel_payload_hash,
|
||||
verdict_source="macro")
|
||||
lg.close()
|
||||
conn = sqlite3.connect(path)
|
||||
row = conn.execute(
|
||||
"SELECT panel_verdict, panel_confidence, panel_rationale,"
|
||||
" panel_takes_json, panel_created_at, panel_cache_status,"
|
||||
" panel_payload_hash, advisory_outcome_status"
|
||||
" panel_payload_hash, advisory_outcome_status,"
|
||||
" panel_verdict_source"
|
||||
" FROM signals WHERE id=?",
|
||||
(rowid,)).fetchone()
|
||||
conn.close()
|
||||
@@ -7206,6 +7304,7 @@ def _():
|
||||
assert row[4] == report.panel_created_at and row[5] == "MISS"
|
||||
assert row[6] == report.panel_payload_hash
|
||||
assert row[7] == "PENDING"
|
||||
assert row[8] == "macro"
|
||||
finally:
|
||||
slog.reset_loggers_for_tests()
|
||||
os.remove(path)
|
||||
|
||||
Reference in New Issue
Block a user