Add fi-research-brief executor for Freedom Intelligence daily rhythm.

activity-core owns schedule and spawn; rein-aharness executes the research
brief via llm-connect, commits the artifact, and posts fi_daily_brief for
fi_brief_status idempotence — same layering as brief-daily for Binky.
This commit is contained in:
tegwick 2026-08-03 17:53:06 +02:00
parent 2ab33869f8
commit 877688b2e3
2 changed files with 492 additions and 0 deletions

View file

@ -260,6 +260,35 @@ def main(argv: list[str] | None = None) -> int:
help="Write brief file but do not git commit",
)
fi_brief = sub.add_parser(
"fi-research-brief",
help=(
"Freedom Intelligence research brief via llm-connect "
"(activity-core consumer; posts fi_daily_brief)"
),
)
fi_brief.add_argument(
"--target-repo",
required=True,
help="freedom-intelligence checkout path",
)
fi_brief.add_argument(
"--date",
default=None,
help="Brief date YYYY-MM-DD (default: today Europe/Berlin)",
)
fi_brief.add_argument(
"--force",
action="store_true",
help="Overwrite if today's brief already exists",
)
fi_brief.add_argument("--no-hub", action="store_true", help="Skip hub reporting")
fi_brief.add_argument(
"--no-commit",
action="store_true",
help="Write brief file but do not git commit",
)
validate = sub.add_parser(
"validate",
help="Validate instance manifest (.kaizen/schedule.yml + harness fields)",
@ -425,6 +454,40 @@ def main(argv: list[str] | None = None) -> int:
)
return 0 if brief_result.ok else 1
if args.command == "fi-research-brief":
from datetime import date as date_cls
from rein_aharness.fi_research_brief import run_fi_research_brief
day = None
if args.date:
day = date_cls.fromisoformat(args.date)
fi_result = run_fi_research_brief(
target_repo=Path(args.target_repo).expanduser(),
day=day,
force=bool(args.force),
report_to_hub=not args.no_hub,
commit=not args.no_commit,
)
print(
json.dumps(
{
"ok": fi_result.ok,
"date": fi_result.date,
"path": fi_result.path,
"wrote": fi_result.wrote,
"committed": fi_result.committed,
"skipped_existing": fi_result.skipped_existing,
"collection_candidates": fi_result.collection_candidates,
"head_after": fi_result.head_after,
"reason": fi_result.reason,
"model_meta": fi_result.model_meta,
},
indent=2,
)
)
return 0 if fi_result.ok else 1
if args.command == "mail-scan":
from rein_aharness.mailscan import run_mail_scan

View file

@ -0,0 +1,429 @@
"""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 AD 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", "..."], // 37 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)