feat(synthesis): Lead/Provenance model + grounded leads + conjecture generator

- codex/synthesis.py: Lead/Provenance dataclasses, find_connections/gaps/improvements,
  propose_conjectures (status=unverified, quarantined in leads/conjectures/),
  write_leads (grounded→leads/grounded/, conjectures→leads/conjectures/ HARD invariant)
- codex/cli.py: synthesis leads/conjectures/report command group
- codex/config.py: F-13 settings (leads_dir, synthesis_llm_*, synthesis_top_k,
  synthesis_min_grounded_ratio, synthesis_gap_min_coverage)
- tests/synthesis/: 42 tests covering model, grounding, conjecture invariant, quarantine, CLI
- spike/: F-13 spike script + output (live run attempted)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Tarik Moussa
2026-06-14 08:37:42 +02:00
parent 967e6baa59
commit 8cf0cc7e01
14 changed files with 2139 additions and 0 deletions

View File

@@ -0,0 +1,141 @@
"""F-13 synthesis spike — quick live-infra evaluation (WEGWERF-CODE).
NOT a unit test. Not maintained. Run by hand against live infrastructure:
DB: postgresql://researcher:change_me@localhost:5432/papers (podman, Mac)
LLM: http://192.168.178.103:11434 (Jetson, qwen2.5:7b)
Goal: emit 3-5 grounded leads (connection + gap) and a couple of
conjectures, then eyeball:
* Grounded-Lead-Präzision: anteil echter (nicht scheinbarer) Lücken /
Verbindungen. Ziel ≥ 60 %.
* Zero-Fact-Leak: jede Konjektur trägt status="unverified" und landet
in leads/conjectures/, NIE in wiki/.
Result is dumped to ``spike/spike_output.json`` and stdout.
"""
from __future__ import annotations
import json
import os
import sys
from pathlib import Path
# Wire up the live infra without polluting ~/.env
os.environ.setdefault("DATABASE_URL", "postgresql://researcher:change_me@localhost:5432/papers")
os.environ.setdefault("OLLAMA_BASE_URL", "http://192.168.178.103:11434")
os.environ.setdefault("SYNTHESIS_LLM_MODEL", "qwen2.5:7b")
os.environ.setdefault("SYNTHESIS_TOP_K", "12")
ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT))
def _probe_db() -> bool:
try:
import psycopg
with psycopg.connect(os.environ["DATABASE_URL"]) as conn:
n = conn.execute("SELECT count(*) FROM papers").fetchone()
print(f"[db ok] papers={n[0] if n else '?'}")
return True
except Exception as exc:
print(f"[db FAIL] {exc}")
return False
def _probe_llm() -> bool:
import httpx
try:
r = httpx.get(os.environ["OLLAMA_BASE_URL"] + "/api/tags", timeout=3.0)
r.raise_for_status()
print(f"[llm ok] models={len(r.json().get('models', []))}")
return True
except Exception as exc:
print(f"[llm FAIL] {exc}")
return False
def run() -> dict[str, object]:
if not _probe_db():
return {"error": "db unreachable", "live": False}
if not _probe_llm():
return {"error": "llm unreachable", "live": False}
from codex.config import get_settings # noqa: E402 (after env wiring)
from codex.synthesis import (
default_llm_client,
find_connections,
find_gaps,
propose_conjectures,
write_leads,
)
# Reset settings cache so DB/LLM env wiring above is picked up
get_settings.cache_clear()
settings = get_settings()
print(f"[settings] top_k={settings.synthesis_top_k} model={settings.synthesis_llm_model}")
llm = default_llm_client()
print("\n--- find_connections ---")
connections = find_connections(top_k=settings.synthesis_top_k, llm=llm)
print(f" {len(connections)} connection leads")
for c in connections:
print(f" - {c.id}: {c.title} (conf={c.confidence:.2f})")
print("\n--- find_gaps (no lib_path) ---")
gaps = find_gaps(llm=llm)
print(f" {len(gaps)} gap leads")
for g in gaps:
print(f" - {g.id}: {g.title} (conf={g.confidence:.2f})")
print("\n--- propose_conjectures ---")
conjectures = propose_conjectures(top_k=settings.synthesis_top_k, llm=llm)
print(f" {len(conjectures)} conjectures")
for cj in conjectures:
print(f" - {cj.id}: {cj.title}")
print(f" status={cj.status} validation={cj.suggested_validation[:80]}")
# Write into a sandbox dir
sandbox = ROOT / "spike" / "spike_output"
sandbox.mkdir(parents=True, exist_ok=True)
write_leads(connections + gaps + conjectures, str(sandbox))
# ZERO-FACT-LEAK guard: no conjectures in grounded/, every conjecture has validation
leak = False
for cj in conjectures:
if cj.kind != "conjecture":
leak = True
print(f"[LEAK] {cj.id} kind={cj.kind}")
if cj.status != "unverified":
leak = True
print(f"[LEAK] {cj.id} status={cj.status} (expected unverified)")
if not cj.suggested_validation:
leak = True
print(f"[LEAK] {cj.id} missing suggested_validation")
if not cj.provenance:
leak = True
print(f"[LEAK] {cj.id} missing provenance")
summary = {
"live": True,
"leak": leak,
"counts": {
"connection": len(connections),
"gap": len(gaps),
"conjecture": len(conjectures),
},
"leads_dir": str(sandbox),
}
(ROOT / "spike" / "spike_output.json").write_text(json.dumps(summary, indent=2))
print("\n=== SUMMARY ===")
print(json.dumps(summary, indent=2))
return summary
if __name__ == "__main__":
run()

10
spike/spike_output.json Normal file
View File

@@ -0,0 +1,10 @@
{
"live": true,
"leak": false,
"counts": {
"connection": 0,
"gap": 0,
"conjecture": 0
},
"leads_dir": "/Users/tarikmoussa/Desktop/codex-py/spike/spike_output"
}

View File

@@ -0,0 +1,11 @@
# Lead Index
_Last updated 2026-06-14 06:35 UTC by `codex synthesis`_
## Grounded leads (connection / gap / improvement)
_None._
## Conjectures (UNVERIFIED — quarantined here, never in wiki/)
_None._