"""Freedom Intelligence daily research brief via llm-connect. activity-core owns *when* (Temporal schedule + fi_brief_status). This rein command owns *execution*: draft brief from playbook context, commit under freedom-intelligence, post fi_daily_brief for idempotence. No Claude Code / host coding agent — llm-connect only. """ from __future__ import annotations import json import os import re import subprocess from dataclasses import dataclass, field from datetime import date, datetime from pathlib import Path from typing import Any, Callable from zoneinfo import ZoneInfo from rein_aharness import hub from rein_aharness.llm_connect_client import ( LLMConnectClient, LLMConnectError, get_llm_connect_client, ) _CONTEXT_FILES = ( "docs/sources-allowlist.md", "docs/daily-brief-playbook.md", "inventory/RESERVE-STATUS.md", "research/2026-07-24-baseline-field-survey.md", "research/2026-07-24-nas-strategic-collection-plan.md", ) _MAX_FILE_CHARS = 5000 _MAX_GIT_LOG = 12 class FiResearchBriefError(RuntimeError): pass @dataclass class FiResearchBriefResult: ok: bool date: str path: str | None = None wrote: bool = False committed: bool = False head_after: str = "" reason: str = "" model_meta: dict[str, Any] = field(default_factory=dict) skipped_existing: bool = False collection_candidates: int = 0 def _berlin_today() -> date: try: return datetime.now(ZoneInfo("Europe/Berlin")).date() except Exception: return date.today() def brief_path_for(repo: Path, day: date) -> Path: return repo / "briefs" / f"{day.year:04d}" / f"{day.month:02d}" / f"{day.isoformat()}.md" def collect_context(repo: Path, day: date) -> str: chunks: list[str] = [ f"Brief date (Europe/Berlin): {day.isoformat()}\n", "Repo: freedom-intelligence — axes A–D AI field research brief.\n", "Briefs are DELTAS from prior briefs + baseline, not full resurveys.\n", ] for rel in _CONTEXT_FILES: path = repo / rel if not path.is_file(): chunks.append(f"## {rel}\n(missing)\n") continue text = path.read_text(encoding="utf-8", errors="replace") chunks.append(f"## {rel}\n{_truncate(text, _MAX_FILE_CHARS)}\n") briefs_root = repo / "briefs" if briefs_root.is_dir(): prior = sorted(briefs_root.rglob("20*.md")) prior = [p for p in prior if p.name != f"{day.isoformat()}.md"] names = [str(p.relative_to(repo)) for p in prior[-5:]] chunks.append(f"## Recent briefs\n{names}\n") if prior: last = prior[-1] chunks.append( f"## Previous brief ({last.relative_to(repo)})\n" f"{_truncate(last.read_text(encoding='utf-8', errors='replace'), 3500)}\n" ) try: log = _git(repo, "log", f"-{_MAX_GIT_LOG}", "--oneline") chunks.append(f"## Recent git log\n{log}\n") except FiResearchBriefError: chunks.append("## Recent git log\n(unavailable)\n") return "\n".join(chunks) def build_prompt(context: str, day: date) -> str: return f"""You write the Freedom Intelligence **daily research brief** for {day.isoformat()}. 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. - Flag collection candidates only when license/size/rationale are clear. - Prefer primary sources; mark unverified claims. - Output **JSON only** (no markdown fence) with this schema: {{ "headline_deltas": ["string", "..."], // 3–7 bullets; or ["No material delta."] "axis_a": [{{"item": "...", "delta": "...", "sources": "...", "lab_relevance": "..."}}], "axis_b": [same shape], "axis_c": [same shape], "axis_d": [same shape], "collection_candidates": [ {{"id": "...", "org": "...", "name": "...", "priority": "high|medium|low", "reason": "...", "approx_size": "...", "license": "..."}} ], "lab_implications": ["string"] }} Empty axis arrays are fine. Keep each field short. ## Context {context} """ def parse_brief_response(text: str) -> dict[str, Any]: cleaned = text.strip() if cleaned.startswith("```"): cleaned = re.sub(r"^```(?:json)?\s*", "", cleaned) cleaned = re.sub(r"\s*```$", "", cleaned) try: data = json.loads(cleaned) except json.JSONDecodeError as exc: m = re.search(r"\{.*\}", cleaned, re.S) if not m: raise FiResearchBriefError(f"LLM response is not JSON: {exc}") from exc try: data = json.loads(m.group(0)) except json.JSONDecodeError as exc2: raise FiResearchBriefError(f"LLM response is not JSON: {exc2}") from exc2 if not isinstance(data, dict): raise FiResearchBriefError("LLM JSON root must be an object") 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): headlines = [headlines] hl = "\n".join(f"- {str(x).strip().lstrip('- ')}" for x in headlines[:8] if str(x).strip()) def axis_table(key: str) -> str: rows = data.get(key) or [] if not rows: return "*(none)*\n" lines = [ "| Item | Delta | Sources | Lab relevance |", "| ---- | ----- | ------- | ------------- |", ] for row in rows[:12]: if not isinstance(row, dict): continue item = _cell(row.get("item")) delta = _cell(row.get("delta")) sources = _cell(row.get("sources")) lab = _cell(row.get("lab_relevance")) lines.append(f"| {item} | {delta} | {sources} | {lab} |") return "\n".join(lines) + "\n" cands = data.get("collection_candidates") or [] if not cands: cand_md = "*(none)*\n" else: cand_lines = [ "| id | org | name | priority | reason | approx size | license |", "| -- | --- | ---- | -------- | ------ | ----------- | ------- |", ] for c in cands[:10]: if not isinstance(c, dict): continue cand_lines.append( "| {id} | {org} | {name} | {priority} | {reason} | {approx_size} | {license} |".format( id=_cell(c.get("id")), org=_cell(c.get("org")), name=_cell(c.get("name")), priority=_cell(c.get("priority")), reason=_cell(c.get("reason")), approx_size=_cell(c.get("approx_size")), license=_cell(c.get("license")), ) ) cand_md = "\n".join(cand_lines) + "\n" impl = data.get("lab_implications") or [] if isinstance(impl, str): impl = [impl] impl_md = "\n".join(f"- {str(x).strip().lstrip('- ')}" for x in impl[:8] if str(x).strip()) or "- *(none)*" return ( f"---\n" f"date: {day.isoformat()}\n" f"timezone: Europe/Berlin\n" f"author: rein-aharness\n" f"status: final\n" f"sources_checked:\n" f" - docs/sources-allowlist.md\n" f" - automated field scan via llm-connect\n" f"---\n" f"\n" f"# Freedom Intelligence Daily Brief — {day.isoformat()}\n" f"\n" f"> Automated brief (activity-core schedule → rein-aharness). " f"Deltas only.\n" f"\n" f"## Headline deltas\n" f"\n" f"{hl}\n" f"\n" f"## Frontier & commercial (axis A)\n" f"\n" f"{axis_table('axis_a')}\n" f"## Edge / local / open (axis B)\n" f"\n" f"{axis_table('axis_b')}\n" f"## Training & specialization (axis C)\n" f"\n" f"{axis_table('axis_c')}\n" f"## Harness & fleet (axis D)\n" f"\n" f"{axis_table('axis_d')}\n" f"## Collection candidates\n" f"\n" f"{cand_md}\n" f"## Lab implications\n" f"\n" f"{impl_md}\n" ) def _cell(value: Any) -> str: s = str(value or "").replace("|", "/").replace("\n", " ").strip() return s[:200] if s else "" def _truncate(text: str, n: int) -> str: if len(text) <= n: return text return text[: n - 20] + "\n…(truncated)…\n" def _git(repo: Path, *args: str) -> str: result = subprocess.run( ["git", "-C", str(repo), *args], capture_output=True, text=True, timeout=60, ) if result.returncode != 0: raise FiResearchBriefError( f"git {' '.join(args)} failed: {result.stderr.strip()[:200]}" ) return result.stdout.strip() def run_fi_research_brief( target_repo: Path, *, day: date | None = None, force: bool = False, report_to_hub: bool = True, commit: bool = True, client: LLMConnectClient | None = None, complete_fn: Callable[[str], str] | None = None, ) -> FiResearchBriefResult: repo = target_repo.expanduser().resolve() day = day or _berlin_today() path = brief_path_for(repo, day) if path.is_file() and not force: result = FiResearchBriefResult( ok=True, date=day.isoformat(), path=str(path.relative_to(repo)), skipped_existing=True, reason="brief already exists for today", ) try: result.head_after = _git(repo, "rev-parse", "HEAD") except FiResearchBriefError: pass _hub(result, report_to_hub, repo) return result context = collect_context(repo, day) prompt = build_prompt(context, day) meta: dict[str, Any] = {} try: if complete_fn is not None: content = complete_fn(prompt) else: llm = client or get_llm_connect_client() model = ( os.environ.get("FI_RESEARCH_BRIEF_MODEL", "").strip() or os.environ.get("BRIEF_DAILY_MODEL", "").strip() or os.environ.get("MAIL_TRIAGE_MODEL", "").strip() ) content = llm.complete( prompt, model=model, config={ "temperature": float( os.environ.get("FI_RESEARCH_BRIEF_TEMPERATURE", "0.25") ), "max_tokens": int( os.environ.get("FI_RESEARCH_BRIEF_MAX_TOKENS", "2000") ), }, ) meta = dict(llm.last_response_metadata or {}) data = parse_brief_response(content) markdown = render_brief(day, data) n_cands = len(data.get("collection_candidates") or []) except (LLMConnectError, FiResearchBriefError, OSError) as exc: result = FiResearchBriefResult( ok=False, date=day.isoformat(), reason=str(exc)[:300], model_meta=meta, ) _hub(result, report_to_hub, repo) return result path.parent.mkdir(parents=True, exist_ok=True) path.write_text(markdown, encoding="utf-8") rel = str(path.relative_to(repo)) committed = False head_after = "" if commit: try: _git(repo, "add", rel) status = _git(repo, "status", "--porcelain", rel) if status.strip(): _git( repo, "commit", "-m", f"FI daily research brief {day.isoformat()} (activity-core rhythm)", ) committed = True head_after = _git(repo, "rev-parse", "HEAD") except FiResearchBriefError as exc: result = FiResearchBriefResult( ok=False, date=day.isoformat(), path=rel, wrote=True, reason=f"write ok but commit failed: {exc}", model_meta=meta, collection_candidates=n_cands, ) _hub(result, report_to_hub, repo) return result else: try: head_after = _git(repo, "rev-parse", "HEAD") except FiResearchBriefError: pass result = FiResearchBriefResult( ok=True, date=day.isoformat(), path=rel, wrote=True, committed=committed, head_after=head_after, model_meta=meta, collection_candidates=n_cands, ) _hub(result, report_to_hub, repo) return result def _hub(result: FiResearchBriefResult, report_to_hub: bool, repo: Path) -> None: if not report_to_hub: return if result.ok: # Idempotence for activity-core fi_brief_status resolver event_type = "fi_daily_brief" summary = ( f"FI daily brief {result.date}" + ( " (already present)" if result.skipped_existing else f" wrote={result.wrote} committed={result.committed}" ) ) detail = { "repo": "freedom-intelligence", "date": result.date, "path": result.path, "collection_candidates": result.collection_candidates, "wrote": result.wrote, "committed": result.committed, "skipped_existing": result.skipped_existing, "executor": "rein-aharness", } else: event_type = "executor_run" summary = f"FI daily research brief failed: {result.reason}" detail = { "repo": "freedom-intelligence", "ok": False, "date": result.date, "reason": result.reason, "model_meta": result.model_meta, "executor": "rein-aharness", } hub.post_progress_event(summary=summary, event_type=event_type, detail=detail)