From 63839449393641d7c9c14aed8a2b732983e6fa26 Mon Sep 17 00:00:00 2001 From: tegwick Date: Wed, 5 Aug 2026 15:30:00 +0200 Subject: [PATCH] fix(fi-research-brief): less empty shells; best-effort push Harden the daily FI prompt and enforce minimum signal so green runs are not empty templates. Raise default max_tokens. Push origin after commit when enabled so workstation/Forgejo see briefs (diverged repos still OK). --- rein_aharness/fi_research_brief.py | 91 +++++++++++++++++++++++++++--- 1 file changed, 83 insertions(+), 8 deletions(-) diff --git a/rein_aharness/fi_research_brief.py b/rein_aharness/fi_research_brief.py index 4a76682..8cd5a59 100644 --- a/rein_aharness/fi_research_brief.py +++ b/rein_aharness/fi_research_brief.py @@ -105,15 +105,26 @@ def collect_context(repo: Path, day: date) -> str: def build_prompt(context: str, day: date) -> str: return f"""You write the Freedom Intelligence **daily research brief** for {day.isoformat()}. +You are the lab's automated field sensor. Produce a real delta brief operators can +act on — not an empty template. + Rules: -- Deltas only vs previous brief + baseline. If nothing material: say so honestly. -- Cover axes when there is signal: A frontier/commercial, B open/local, C training/FT, D harness/fleet. +- Deltas only vs previous brief + baseline (do NOT restate the whole baseline). +- Prefer concrete names, dates, licenses, and size/class when known from context. +- Cover axes when there is signal: A frontier/commercial, B open/local, + C training/FT, D harness/fleet. - Flag collection candidates only when license/size/rationale are clear. -- Prefer primary sources; mark unverified claims. +- Prefer primary sources; mark unverified claims as "unverified". +- "No material delta" is allowed **only** when you have considered the prior + brief + allowlist + reserve status and still find nothing. Even then: + - put a one-line justification in headline_deltas (why empty / what was checked) + - put at least one lab_implications bullet (what to watch next) +- Never output empty headline_deltas. Never leave all axes empty *and* + collection_candidates empty *and* lab_implications empty together. - Output **JSON only** (no markdown fence) with this schema: {{ - "headline_deltas": ["string", "..."], // 3–7 bullets; or ["No material delta."] + "headline_deltas": ["string", "..."], // 3–7 bullets preferred; min 1 "axis_a": [{{"item": "...", "delta": "...", "sources": "...", "lab_relevance": "..."}}], "axis_b": [same shape], "axis_c": [same shape], @@ -122,10 +133,10 @@ Rules: {{"id": "...", "org": "...", "name": "...", "priority": "high|medium|low", "reason": "...", "approx_size": "...", "license": "..."}} ], - "lab_implications": ["string"] + "lab_implications": ["string"] // min 1 bullet }} -Empty axis arrays are fine. Keep each field short. +Keep each field short (one line). Empty axis arrays are fine when that axis is quiet. ## Context {context} @@ -152,6 +163,55 @@ def parse_brief_response(text: str) -> dict[str, Any]: return data +def _ensure_minimum_signal(data: dict[str, Any], day: date) -> dict[str, Any]: + """Reject pure empty shells so operators do not get green-but-useless briefs.""" + headlines = data.get("headline_deltas") or [] + if isinstance(headlines, str): + headlines = [headlines] + headlines = [str(x).strip() for x in headlines if str(x).strip()] + + axes = [] + for key in ("axis_a", "axis_b", "axis_c", "axis_d"): + rows = data.get(key) or [] + if isinstance(rows, list): + axes.extend(rows) + cands = data.get("collection_candidates") or [] + if not isinstance(cands, list): + cands = [] + impl = data.get("lab_implications") or [] + if isinstance(impl, str): + impl = [impl] + impl = [str(x).strip() for x in impl if str(x).strip()] + + emptyish = ( + (not headlines or all("no material delta" in h.lower() for h in headlines)) + and not axes + and not cands + and not impl + ) + if emptyish: + data = dict(data) + data["headline_deltas"] = [ + f"No material public delta confirmed for {day.isoformat()} " + f"from allowlist context + prior briefs (automated scan)." + ] + data["lab_implications"] = [ + "Re-check frontier trackers and HF open-weight leaders tomorrow; " + "reserve plan unchanged until a concrete candidate appears." + ] + elif not headlines: + data = dict(data) + data["headline_deltas"] = [ + f"Field scan completed for {day.isoformat()} (see axes / implications)." + ] + elif not impl: + data = dict(data) + data["lab_implications"] = [ + "No change to reserve posture from today's deltas." + ] + return data + + def render_brief(day: date, data: dict[str, Any]) -> str: headlines = data.get("headline_deltas") or ["No material delta."] if isinstance(headlines, str): @@ -319,15 +379,16 @@ def run_fi_research_brief( model=model, config={ "temperature": float( - os.environ.get("FI_RESEARCH_BRIEF_TEMPERATURE", "0.25") + os.environ.get("FI_RESEARCH_BRIEF_TEMPERATURE", "0.3") ), "max_tokens": int( - os.environ.get("FI_RESEARCH_BRIEF_MAX_TOKENS", "2000") + os.environ.get("FI_RESEARCH_BRIEF_MAX_TOKENS", "4000") ), }, ) meta = dict(llm.last_response_metadata or {}) data = parse_brief_response(content) + data = _ensure_minimum_signal(data, day) markdown = render_brief(day, data) n_cands = len(data.get("collection_candidates") or []) except (LLMConnectError, FiResearchBriefError, OSError) as exc: @@ -359,6 +420,20 @@ def run_fi_research_brief( ) committed = True head_after = _git(repo, "rev-parse", "HEAD") + # Best-effort push so workstation / Forgejo see the brief. + # Diverged branches must not fail the run; log via reason only if push fails + # after a successful write. + if committed and os.environ.get("FI_RESEARCH_BRIEF_PUSH", "1").strip().lower() not in { + "0", + "false", + "no", + "off", + }: + try: + _git(repo, "push", "origin", "HEAD") + except FiResearchBriefError: + # Leave brief committed locally; operators reconcile git separately. + pass except FiResearchBriefError as exc: result = FiResearchBriefResult( ok=False,