"""The two anti-gaming scripted agents (spec: build day one, wired forever). flag_everything: must score clearly negative on every task, or the FP penalty is too weak. no_evidence: must approach zero on L2+, or a defect is leaking into the ledger row. Both paginate query_ledger past the 200-row cap so real L1 populations (400+) are fully covered.""" from __future__ import annotations from collections.abc import Sequence from je_validation.envir.agent import ToolCall ROW_CAP = 200 def _query_pages(history): return [(c, o) for c, o in history if c.tool == "query_ledger"] def _collect_ids(history) -> list[str]: ids: list[str] = [] for _, obs in _query_pages(history): if isinstance(obs, list): ids += [row["entry_id"] for row in obs] return ids def _next_query(history) -> ToolCall | None: pages = _query_pages(history) if not pages: return ToolCall("query_ledger", {"order_by": "entry_id"}) last_obs = pages[-1][1] if isinstance(last_obs, list) and len(last_obs) == ROW_CAP: return ToolCall("query_ledger", {"order_by": "entry_id", "offset": len(_collect_ids(history))}) return None def _foot(lines) -> tuple: dr = cr = 0 for ln in lines: if "side" in ln: # postingised: integer cents if ln["side"] == "D": dr += ln.get("amount_cents", 0) else: cr += ln.get("amount_cents", 0) else: # toy: float dr/cr columns dr = round(dr + ln.get("dr", 0), 2) cr = round(cr + ln.get("cr", 0), 2) return dr, cr class FlagEverythingAgent: def __init__(self, issue_types: Sequence[str] | None = None): # a pinned vocabulary (tools.py enforces it at disposition time) means # the baseline must flag with a member label; "defect" survives only # for unpinned tasks (JE-TOY) self._issue_type = (issue_types or ("defect",))[0] def decide(self, brief: str, history) -> ToolCall: nxt = _next_query(history) if nxt is not None: return nxt ids = _collect_ids(history) n_disp = sum(1 for c, _ in history if c.tool == "disposition") if n_disp < len(ids): return ToolCall("disposition", { "entry_id": ids[n_disp], "verdict": "flag", "issue_type": self._issue_type, "rationale": "flagged by baseline", "evidence_ids": []}) return ToolCall("submit", {}) class NoEvidenceAgent: def decide(self, brief: str, history) -> ToolCall: nxt = _next_query(history) if nxt is not None: return nxt ids = _collect_ids(history) gets = [(c, o) for c, o in history if c.tool == "get_entry"] if len(gets) < len(ids): # inspect each ledger row return ToolCall("get_entry", {"entry_id": ids[len(gets)]}) n_disp = sum(1 for c, _ in history if c.tool == "disposition") if n_disp < len(ids): # then disposition each eid = ids[n_disp] obs = next(o for c, o in gets if c.args.get("entry_id") == eid) lines = obs.get("lines", []) if isinstance(obs, dict) else [] dr, cr = _foot(lines) if dr != cr: return ToolCall("disposition", { "entry_id": eid, "verdict": "flag", "issue_type": "unbalanced", "rationale": f"dr {dr} != cr {cr}", "evidence_ids": []}) return ToolCall("disposition", { "entry_id": eid, "verdict": "approve", "issue_type": "", "rationale": "no issue visible in ledger", "evidence_ids": []}) return ToolCall("submit", {})