"""Approach registry: match ops_run → cheapest correct executor (REIN-A-0002-T02). Prefer deterministic / structured llm-connect adapters before open-ended agent sessions. Add rows in APPROACH_RULES (order = priority). How to add a row: 1. Implement a ``run_*`` function or CLI command. 2. Append an ApproachRule with match predicates (labels / blob substrings). 3. Wire execution in ``execute_approach``. 4. Add pure match tests in ``tests/test_approaches.py``. """ from __future__ import annotations from dataclasses import dataclass, field from pathlib import Path from typing import Any from rein_aharness.ops_run_client import OpsRun, OpsRunConfig, resolve_ops_target from rein_aharness.taskspec import TaskSpecError # Approach command names (stable; used in metrics + ops_run.result) APPROACH_FI_RESEARCH_BRIEF = "fi-research-brief" APPROACH_BRIEF_DAILY = "brief-daily" APPROACH_BRIEF_WEEKLY = "brief-weekly" APPROACH_MAIL_SCAN = "mail-scan" APPROACH_MAIL_TRIAGE = "mail-triage" APPROACH_MAIL_PIPELINE = "mail-scan+triage" APPROACH_AGENT_SESSION = "agent-session" APPROACH_UNMATCHED = "unmatched" @dataclass(frozen=True) class ApproachRule: name: str """Match if any of these labels appear (case-insensitive).""" labels_any: frozenset[str] = frozenset() """Match if all of these labels appear.""" labels_all: frozenset[str] = frozenset() """Match if any substring appears in definition id + labels + title + hint.""" blob_contains: frozenset[str] = frozenset() """If approach_hint equals this command name, select immediately.""" hint_name: str | None = None # Ordered: first match wins. Cheapest / most specific first. APPROACH_RULES: tuple[ApproachRule, ...] = ( ApproachRule( name=APPROACH_FI_RESEARCH_BRIEF, labels_any=frozenset({"research-brief", "freedom-intelligence"}), blob_contains=frozenset( {"fi-daily", "fi_daily", "fi-research", "freedom intelligence"} ), hint_name=APPROACH_FI_RESEARCH_BRIEF, ), ApproachRule( name=APPROACH_BRIEF_WEEKLY, labels_all=frozenset({"binky", "weekly-review"}), blob_contains=frozenset( {"binky-weekly-review", "binky_weekly_review", "weekly founder review"} ), hint_name=APPROACH_BRIEF_WEEKLY, ), ApproachRule( name=APPROACH_BRIEF_DAILY, labels_any=frozenset({"rhythm", "binky-daily", "daily-brief"}), labels_all=frozenset(), # rhythm alone is enough for Binky daily blob_contains=frozenset( {"binky-daily", "binky_daily", "daily-rhythm", "daily_brief"} ), hint_name=APPROACH_BRIEF_DAILY, ), ApproachRule( name=APPROACH_MAIL_PIPELINE, labels_any=frozenset({"mail-intake", "mail_intake"}), blob_contains=frozenset({"mail-intake", "mail_intake", "weekly-mail"}), hint_name=APPROACH_MAIL_PIPELINE, ), ApproachRule( name=APPROACH_MAIL_SCAN, labels_any=frozenset({"mail-scan"}), hint_name=APPROACH_MAIL_SCAN, ), ApproachRule( name=APPROACH_MAIL_TRIAGE, labels_any=frozenset({"mail-triage"}), hint_name=APPROACH_MAIL_TRIAGE, ), ApproachRule( name=APPROACH_AGENT_SESSION, labels_any=frozenset({"agent-session", "agent_session"}), blob_contains=frozenset({"agent-session"}), hint_name=APPROACH_AGENT_SESSION, ), ) def _match_blob(run: OpsRun) -> str: parts = [ run.activity_definition_id or "", run.title or "", run.approach_hint or "", " ".join(run.labels or []), run.source_id or "", run.target_repo or "", ] return " ".join(parts).lower() def select_approach(run: OpsRun) -> str: """Return approach command name for this ops_run (pure).""" hint = (run.approach_hint or "").strip().lower() if hint: for rule in APPROACH_RULES: if rule.hint_name and hint in { rule.hint_name, rule.name, rule.hint_name.replace("-", "_"), }: return rule.name # Explicit known command as hint known = { APPROACH_FI_RESEARCH_BRIEF, APPROACH_BRIEF_DAILY, APPROACH_BRIEF_WEEKLY, APPROACH_MAIL_SCAN, APPROACH_MAIL_TRIAGE, APPROACH_MAIL_PIPELINE, APPROACH_AGENT_SESSION, } if hint in known or hint.replace("_", "-") in known: return hint.replace("_", "-") labels = {str(x).lower() for x in (run.labels or [])} blob = _match_blob(run) for rule in APPROACH_RULES: matches_all = bool(rule.labels_all) and rule.labels_all.issubset(labels) matches_any = bool(rule.labels_any) and bool(rule.labels_any & labels) matches_blob = bool(rule.blob_contains) and any( substring in blob for substring in rule.blob_contains ) if matches_all or matches_any or matches_blob: return rule.name return APPROACH_UNMATCHED @dataclass class ApproachResult: ok: bool approach: str result: dict[str, Any] = field(default_factory=dict) reason: str = "" reopen: bool = False def execute_approach( run: OpsRun, *, approach: str | None = None, config: OpsRunConfig | None = None, report_to_hub: bool = True, commit: bool = True, ) -> ApproachResult: """Dispatch to the selected executor. Never raises for business failure.""" cfg = config or OpsRunConfig.from_env() name = approach or select_approach(run) if name == APPROACH_UNMATCHED: return ApproachResult( ok=False, approach=name, reason=( "no approach matched labels/definition; " f"labels={run.labels!r} def={run.activity_definition_id!r}" ), reopen=False, ) try: target = resolve_ops_target(run, cfg) except TaskSpecError as exc: return ApproachResult( ok=False, approach=name, reason=str(exc), reopen=False, ) try: if name == APPROACH_FI_RESEARCH_BRIEF: return _run_fi(target, report_to_hub=report_to_hub, commit=commit) if name == APPROACH_BRIEF_DAILY: return _run_brief_daily(target, report_to_hub=report_to_hub, commit=commit) if name == APPROACH_BRIEF_WEEKLY: return _run_brief_weekly(target, report_to_hub=report_to_hub, commit=commit) if name == APPROACH_MAIL_SCAN: return _run_mail_scan(target, report_to_hub=report_to_hub) if name == APPROACH_MAIL_TRIAGE: return _run_mail_triage(target, report_to_hub=report_to_hub, commit=commit) if name == APPROACH_MAIL_PIPELINE: return _run_mail_pipeline( target, report_to_hub=report_to_hub, commit=commit ) if name == APPROACH_AGENT_SESSION: return _run_agent_session(run, target, report_to_hub=report_to_hub) except Exception as exc: # noqa: BLE001 — surface as fail ops_run return ApproachResult( ok=False, approach=name, reason=f"{type(exc).__name__}: {exc}", reopen=True, ) return ApproachResult( ok=False, approach=name, reason=f"approach not implemented: {name}", reopen=False, ) def _run_fi(target: Path, *, report_to_hub: bool, commit: bool) -> ApproachResult: from rein_aharness.fi_research_brief import run_fi_research_brief r = run_fi_research_brief( target_repo=target, report_to_hub=report_to_hub, commit=commit, ) return ApproachResult( ok=r.ok, approach=APPROACH_FI_RESEARCH_BRIEF, result={ "date": r.date, "path": r.path, "wrote": r.wrote, "committed": r.committed, "skipped_existing": r.skipped_existing, "collection_candidates": r.collection_candidates, "head_after": r.head_after, "target_repo": "freedom-intelligence", }, reason=r.reason, reopen=not r.ok and not r.skipped_existing, ) def _run_brief_daily( target: Path, *, report_to_hub: bool, commit: bool ) -> ApproachResult: from rein_aharness.brief_daily import run_brief_daily r = run_brief_daily( target_repo=target, report_to_hub=report_to_hub, commit=commit, ) return ApproachResult( ok=r.ok, approach=APPROACH_BRIEF_DAILY, result={ "date": r.date, "path": r.path, "wrote": r.wrote, "committed": r.committed, "skipped_existing": r.skipped_existing, "head_after": r.head_after, "target_repo": "binky-control", }, reason=r.reason, reopen=not r.ok and not r.skipped_existing, ) def _run_brief_weekly( target: Path, *, report_to_hub: bool, commit: bool ) -> ApproachResult: from rein_aharness.brief_weekly import run_brief_weekly r = run_brief_weekly( target_repo=target, report_to_hub=report_to_hub, commit=commit, ) return ApproachResult( ok=r.ok, approach=APPROACH_BRIEF_WEEKLY, result={ "date": r.date, "path": r.path, "wrote": r.wrote, "committed": r.committed, "skipped_existing": r.skipped_existing, "milestone_moved": r.milestone_moved, "risk005_state": r.risk005_state, "head_after": r.head_after, "target_repo": "binky-control", }, reason=r.reason, reopen=not r.ok and not r.skipped_existing, ) def _run_mail_scan(target: Path, *, report_to_hub: bool) -> ApproachResult: from rein_aharness.mailscan import run_mail_scan r = run_mail_scan(target_repo=target, report_to_hub=report_to_hub) return ApproachResult( ok=r.ok, approach=APPROACH_MAIL_SCAN, result={ "report": r.report_path, "new_messages": r.new_messages, "auth_lane": r.auth_lane, }, reason=r.reason, reopen=not r.ok, ) def _run_mail_triage( target: Path, *, report_to_hub: bool, commit: bool ) -> ApproachResult: from rein_aharness.mail_triage import run_mail_triage r = run_mail_triage( target_repo=target, report_to_hub=report_to_hub, commit=commit, ) return ApproachResult( ok=r.ok, approach=APPROACH_MAIL_TRIAGE, result={ "report": r.report, "entries_applied": r.entries_applied, "committed": r.committed, "head_after": r.head_after, }, reason=r.reason, reopen=not r.ok, ) def _run_mail_pipeline( target: Path, *, report_to_hub: bool, commit: bool ) -> ApproachResult: scan = _run_mail_scan(target, report_to_hub=report_to_hub) if not scan.ok: scan.approach = APPROACH_MAIL_PIPELINE return scan triage = _run_mail_triage(target, report_to_hub=report_to_hub, commit=commit) return ApproachResult( ok=triage.ok, approach=APPROACH_MAIL_PIPELINE, result={"scan": scan.result, "triage": triage.result}, reason=triage.reason or scan.reason, reopen=not triage.ok, ) def _run_agent_session( run: OpsRun, target: Path, *, report_to_hub: bool ) -> ApproachResult: from rein_aharness.ops_run_client import ops_run_to_taskspec from rein_aharness.runner import run_task # Infer agent/event from labels (reuse intake hints) from rein_aharness.intake import EmittedIssue, infer_agent_and_event pseudo = EmittedIssue( issue_id=run.id, title=run.title, description=run.description, labels=list(run.labels), target_repo=run.target_repo, activity_definition_id=run.activity_definition_id, ) agent, event = infer_agent_and_event(pseudo) spec = ops_run_to_taskspec( run, agent=agent, completion_event_type=event, ) # target already resolved into TaskSpec assert spec.target_repo == target or True r = run_task(spec, report_to_hub=report_to_hub) return ApproachResult( ok=r.ok, approach=APPROACH_AGENT_SESSION, result={ "committed": r.committed, "head_after": r.head_after, "tool_profile": r.tool_profile, "tokens_spent": r.tokens_spent, }, reason=r.reason, reopen=not r.ok, )