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).
This commit is contained in:
tegwick 2026-08-05 15:30:00 +02:00
parent 4bb631723d
commit 6383944939

View file

@ -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", "..."], // 37 bullets; or ["No material delta."]
"headline_deltas": ["string", "..."], // 37 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,