Full name-preserving port of src/rune/abstraction.py to src/go/abstraction (GO verdict, #2479 dichotomy tranche — 17/17 fns, dichotomy audit now FULL): - ExtractFacts: Hindsight what/when/where/who/why prompt byte-parity (incl. existing-facts context), Mem0 ADD/NOOP via sha256[:80-lower][:16] content-hash dedup, entity upsert/linking with fact_count, HippoRAG who+first-3-words triples, LLM-array extraction (first '[' to last ']', JSONDecodeError -> []), deterministic kernel fallback seam (cc_abstraction_extract_facts with sorted stopword table + min_len=10, blank items skipped, text/words/content_words -> experience fact with kernel:true), silent kernel-failure degrade, raw[:200] error row - SynthesizeObservations: >=3-fact gate, facts_text [type] lines, DESC created_at source_fact_ids, confidence default 0.7, observation_count update; ListBeliefs/Rules/Skills (confidence/success_count DESC + limit); RunLifecycle (30-day/access_count/confidence<0.5 gates + superseded observations, changes() accumulation); PipelineStats (7 tables, active<=state tables); HandleHook - CtxBudget over frozen DEFAULT_PROFILES snapshot; Now() Python isoformat semantics (microseconds only when nonzero); cc_loader_pin env pin + kernel probe seam; connect() schema 1:1 (WAL, 8 tables, 8 indexes) - LLM payload byte-parity (temperature 0.3, num_predict, num_ctx via CtxBudget); OLLAMA_URL seam; transport failure -> logged + "" - RunCLI: main() exact stdout incl. argparse usage/help/error text frozen from the oracle (COLUMNS=80), --json via Python json.dumps(indent=2) byte-parity encoder (pyjson.go, ensure_ascii=True, compact + indented) - truth.json: 77 frozen rows from the live oracle covering EVERY public function and every branch (LLM valid/prose-wrapped/unterminated/empty/ dedup/short/hash-80-truncation, kernel fallback/raise/none, prompt-bytes captures, payload captures, lifecycle gate matrix, CLI branches incl. help/bad-choice/usage) — replay 77/77 exact-equal, live-regenerated and re-verified (frozen == live); go test green; gofmt+vet clean - IO (sqlite via modernc.org/sqlite, LLM transport, clock, env, kernel) behind injectable seams; scripts/gen_abstraction_truth.py committed - CI: HARD abstraction FULL-port parity gate (replay + test, not swallowable) - abstraction.py untouched (Python oracle remains the parity authority); stale batch-28 extractor skeleton superseded by the full port Dichotomy audit: abstraction 17/17 → FULL (27%→28% function-level). Contract: BUILDER-2026-10-11T13:36:04Z-c0db01-factory-wave (phase A of factory protocol v2; improvement commit follows before closure).
661 lines
34 KiB
Python
661 lines
34 KiB
Python
#!/usr/bin/env python3
|
|
"""gen_abstraction_truth.py — freeze truth from the live Python oracle
|
|
(src/rune/abstraction.py) for the Go dichotomy port.
|
|
|
|
Exercises EVERY public function with real inputs across ALL branches:
|
|
ctx_budget, stopwords table, cc_loader_pin, kernel probe, _now format,
|
|
connect schema, llm_call transport+payload, extract_facts (LLM valid /
|
|
dedup / short / kernel fallback / no-kernel / empty-array),
|
|
synthesize_observations (missing / <3 / ok), list_beliefs/rules/skills
|
|
(ordering + limit), run_lifecycle (all archive gates), pipeline_stats,
|
|
handle_hook, and the full CLI surface (byte-exact stdout/stderr/exit).
|
|
"""
|
|
import contextlib
|
|
import io
|
|
import json
|
|
import os
|
|
import sqlite3
|
|
import sys
|
|
import tempfile
|
|
import types
|
|
import urllib.request
|
|
from datetime import datetime as _real_dt
|
|
|
|
os.environ["COLUMNS"] = "80" # argparse usage wrapping must be stable across re-runs
|
|
|
|
sys.path.insert(0, "/mnt/workdrive/rune/src")
|
|
|
|
FIXED_TS = "2026-10-01T00:00:00+00:00"
|
|
UNREACHABLE = "http://127.0.0.1:9"
|
|
|
|
import rune.abstraction as ab # noqa: E402
|
|
import rune.cc_loader as rcl # noqa: E402
|
|
|
|
# freeze the clock for every insert the oracle performs
|
|
_ORIG_NOW = ab._now
|
|
ab._now = lambda: FIXED_TS
|
|
os.environ["RUNE_COGNITION_CORE"] = "python" # pin kernels OFF by default
|
|
|
|
rows = []
|
|
|
|
|
|
def row(name, op, setup, want):
|
|
rows.append({"name": name, "op": op, "setup": setup, "want": want})
|
|
|
|
|
|
# ── ctx_budget ──────────────────────────────────────────────────────────────
|
|
for mid in ("qwen2.5:3b", "mimo-v2.5-pro", "unknown-model:7b", "deterministic", ""):
|
|
row(f"ctx_budget:{mid or 'default'}", "ctx_budget", {"model": mid}, ab._ctx_budget(mid))
|
|
|
|
# ── stopwords table ────────────────────────────────────────────────────────
|
|
row("stopwords", "stopwords", {}, ab._ABSTRACTION_STOPWORDS())
|
|
|
|
# ── cc_loader_pin ──────────────────────────────────────────────────────────
|
|
for envval in ("python", "auto", "", None):
|
|
key = envval if envval is not None else "unset"
|
|
if envval is None:
|
|
os.environ.pop("RUNE_COGNITION_CORE", None)
|
|
else:
|
|
os.environ["RUNE_COGNITION_CORE"] = envval
|
|
row(f"cc_loader_pin:{key}", "cc_loader_pin", {"env": {"RUNE_COGNITION_CORE": envval}}, ab.cc_loader_pin())
|
|
os.environ["RUNE_COGNITION_CORE"] = "python"
|
|
|
|
# ── _core / _cc_probe (kernel probe) ───────────────────────────────────────
|
|
def core_token():
|
|
c = ab._core()
|
|
return None if c is None else "kernel"
|
|
|
|
|
|
os.environ["RUNE_COGNITION_CORE"] = "python"
|
|
row("core:pinned", "core", {"pinned": True, "kernel": False}, core_token())
|
|
_old_probe = ab._cc_probe
|
|
os.environ.pop("RUNE_COGNITION_CORE", None)
|
|
ab._cc_probe = lambda: None # kernel absent
|
|
row("core:auto_no_kernel", "core", {"pinned": False, "kernel": False}, core_token())
|
|
ab._cc_probe = lambda: object() # kernel present
|
|
row("core:auto_with_kernel", "core", {"pinned": False, "kernel": True}, core_token())
|
|
ab._cc_probe = _old_probe
|
|
os.environ["RUNE_COGNITION_CORE"] = "python"
|
|
|
|
# ── _now format (isoformat, microseconds only when nonzero) ────────────────
|
|
class _FakeDT(_real_dt):
|
|
_fixed = None
|
|
|
|
@classmethod
|
|
def now(cls, tz=None):
|
|
return cls._fixed
|
|
|
|
|
|
_orig_dt = ab.datetime
|
|
for label, args in (("zero", (2026, 1, 1, 12, 30, 45, 0)), ("micros", (2026, 1, 1, 12, 30, 45, 123456))):
|
|
import datetime as _dtmod
|
|
_FakeDT._fixed = _real_dt(*args, tzinfo=_dtmod.timezone.utc)
|
|
ab.datetime = _FakeDT
|
|
ab._now = _ORIG_NOW # the real _now, against the frozen datetime
|
|
row(f"now:{label}", "now", {"usec": args[6]}, ab._now())
|
|
ab.datetime = _orig_dt
|
|
ab._now = lambda: FIXED_TS
|
|
|
|
# ── connect: schema on a fresh database ────────────────────────────────────
|
|
def fresh_db():
|
|
d = tempfile.mkdtemp(prefix="abstr-truth-")
|
|
path = os.path.join(d, "abstraction.db")
|
|
ab.DB_PATH = path
|
|
return path
|
|
|
|
|
|
def dump_db(path, tables=("facts", "entities", "fact_entities", "observations", "beliefs", "rules", "skills", "triples")):
|
|
out = {}
|
|
con = sqlite3.connect(path)
|
|
con.row_factory = sqlite3.Row
|
|
for t in tables:
|
|
if t == "fact_entities":
|
|
order = "fact_id, entity_id"
|
|
else:
|
|
order = "id"
|
|
out[t] = [dict(r) for r in con.execute(f"SELECT * FROM {t} ORDER BY {order}").fetchall()]
|
|
con.close()
|
|
return out
|
|
|
|
|
|
path = fresh_db()
|
|
db = ab.connect()
|
|
tables = sorted(r[0] for r in db.execute("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'"))
|
|
db.close()
|
|
row("connect:schema", "connect_schema", {}, tables)
|
|
|
|
# ── llm_call: transport + parse branches (patched urlopen) ─────────────────
|
|
_orig_urlopen = urllib.request.urlopen
|
|
|
|
|
|
class _FakeResp:
|
|
def __init__(self, body):
|
|
self._body = body
|
|
|
|
def read(self):
|
|
return self._body
|
|
|
|
def __enter__(self):
|
|
return self
|
|
|
|
def __exit__(self, *a):
|
|
return False
|
|
|
|
|
|
def with_urlopen(fake, fn):
|
|
urllib.request.urlopen = fake
|
|
try:
|
|
return fn()
|
|
finally:
|
|
urllib.request.urlopen = _orig_urlopen
|
|
|
|
|
|
captured = {}
|
|
|
|
|
|
def urlopen_ok(body, capture=False):
|
|
def fake(req, timeout=30):
|
|
if capture:
|
|
captured["url"] = req.full_url
|
|
captured["payload"] = req.data.decode()
|
|
return _FakeResp(body)
|
|
|
|
return fake
|
|
|
|
|
|
# payload capture: prompt/model/max_tokens → exact request body (byte parity)
|
|
ab.OLLAMA_URL = "http://127.0.0.1:11434"
|
|
got_payload = with_urlopen(
|
|
urlopen_ok(b'{"response": "x"}', capture=True),
|
|
lambda: ab.llm_call("Say hi", model=None, max_tokens=800),
|
|
)
|
|
row("llm_call:payload_default_model", "llm_payload", {"prompt": "Say hi", "model": "", "max_tokens": 800, "url": ab.OLLAMA_URL},
|
|
{"url": captured["url"], "payload": captured["payload"], "returned": got_payload})
|
|
got_payload = with_urlopen(
|
|
urlopen_ok(b'{"response": "x"}', capture=True),
|
|
lambda: ab.llm_call("Say hi", model="mimo-v2.5-pro", max_tokens=64),
|
|
)
|
|
row("llm_call:payload_cloud_model", "llm_payload", {"prompt": "Say hi", "model": "mimo-v2.5-pro", "max_tokens": 64, "url": ab.OLLAMA_URL},
|
|
{"url": captured["url"], "payload": captured["payload"], "returned": got_payload})
|
|
|
|
# response parsing branches
|
|
for label, body in (
|
|
("plain", b'{"response": " hello world "}'),
|
|
("missing_key", b'{"other": 1}'),
|
|
("whitespace_only", b'{"response": " \\n\\t "}'),
|
|
("non_dict", b'[1, 2]'),
|
|
("bad_json", b'not json'),
|
|
):
|
|
got = with_urlopen(urlopen_ok(body), lambda: ab.llm_call("p"))
|
|
row(f"llm_call:{label}", "llm_call", {"body": body.decode()}, got)
|
|
|
|
|
|
def urlopen_raise(req, timeout=30):
|
|
raise urllib.error.URLError("connection refused")
|
|
|
|
|
|
got = with_urlopen(urlopen_raise, lambda: ab.llm_call("p"))
|
|
row("llm_call:transport_error", "llm_call", {"raise": True}, got)
|
|
|
|
# ── extract_facts ──────────────────────────────────────────────────────────
|
|
def seed_db(path, seed):
|
|
ab.connect().close() # ensure the schema exists before seeding
|
|
if not seed:
|
|
return
|
|
con = sqlite3.connect(path)
|
|
for table, cols, rws in (
|
|
("facts", ["id", "episode_id", "what", "content_hash", "when_text", "where_text", "who_text", "why_text",
|
|
"fact_type", "entities_json", "temporal_start", "temporal_end", "confidence", "source", "state",
|
|
"access_count", "last_accessed", "created_at", "superseded_by"], seed.get("facts", [])),
|
|
("entities", ["id", "name", "entity_type", "summary", "fact_count", "observation_count", "created_at"],
|
|
seed.get("entities", [])),
|
|
("fact_entities", ["fact_id", "entity_id"], seed.get("fact_entities", [])),
|
|
("observations", ["id", "entity_id", "observation", "source_fact_ids", "confidence", "state", "created_at",
|
|
"superseded_by"], seed.get("observations", [])),
|
|
("beliefs", ["id", "claim", "confidence", "reasoning", "supporting_fact_ids", "counterevidence", "scope",
|
|
"state", "formed_at", "last_reinforced", "last_tested"], seed.get("beliefs", [])),
|
|
("rules", ["id", "pattern", "description", "confidence", "source_belief_ids", "trigger_count",
|
|
"success_count", "failure_count", "scope", "state", "created_at", "last_triggered"],
|
|
seed.get("rules", [])),
|
|
("skills", ["id", "name", "description", "procedure_json", "source_rule_ids", "success_count",
|
|
"failure_count", "state", "created_at", "last_used"], seed.get("skills", [])),
|
|
("triples", ["id", "subject", "predicate", "object", "source_fact_id", "confidence", "created_at"],
|
|
seed.get("triples", [])),
|
|
):
|
|
for r in rws:
|
|
vals = [r.get(c) for c in cols]
|
|
con.execute(
|
|
f"INSERT INTO {table} ({', '.join(cols)}) VALUES ({', '.join('?' * len(cols))})", vals)
|
|
con.commit()
|
|
con.close()
|
|
|
|
|
|
class FakeKernel:
|
|
"""Stands in for the Rust cc kernel: records args, scripted items."""
|
|
|
|
def __init__(self, items=None, raise_exc=False):
|
|
self.items = items or []
|
|
self.raise_exc = raise_exc
|
|
self.calls = []
|
|
|
|
def abstraction_extract_facts(self, text, stopwords, min_len=10):
|
|
self.calls.append({"text": text, "stopwords": list(stopwords), "min_len": min_len})
|
|
if self.raise_exc:
|
|
raise RuntimeError("kernel exploded")
|
|
return [dict(i) for i in self.items]
|
|
|
|
|
|
def run_steps(setup):
|
|
"""Execute the setup's steps against the live oracle; return results."""
|
|
results = []
|
|
path = fresh_db()
|
|
seed_db(path, setup.get("seed"))
|
|
llm_queue = list(setup.get("llm", []))
|
|
kernel = None
|
|
if "kernel" in setup:
|
|
kernel = FakeKernel(setup["kernel"], setup.get("kernel_raise", False))
|
|
old_llm = ab.llm_call
|
|
old_probe = ab._cc_probe
|
|
old_loader_fn = rcl.abstraction_extract_facts
|
|
old_core = ab._core
|
|
|
|
llm_captures = []
|
|
|
|
def scripted_llm(prompt, model=None, max_tokens=500):
|
|
llm_captures.append({"prompt": prompt, "model": model, "max_tokens": max_tokens})
|
|
return llm_queue.pop(0) if llm_queue else ""
|
|
|
|
ab.llm_call = scripted_llm
|
|
|
|
if kernel is not None:
|
|
# kernel path: env unpinned + probe returns non-None + loader faked
|
|
os.environ.pop("RUNE_COGNITION_CORE", None)
|
|
ab._cc_probe = lambda: object()
|
|
rcl.abstraction_extract_facts = kernel.abstraction_extract_facts
|
|
|
|
try:
|
|
for step in setup.get("steps", []):
|
|
fn = step["fn"]
|
|
if fn == "extract_facts":
|
|
results.append(ab.extract_facts(
|
|
step["text"], episode_id=step.get("episode_id"), source=step.get("source", "session")))
|
|
elif fn == "synthesize_observations":
|
|
results.append(ab.synthesize_observations(step["entity"]))
|
|
elif fn == "list_beliefs":
|
|
results.append(ab.list_beliefs(limit=step.get("limit", 20)))
|
|
elif fn == "list_rules":
|
|
results.append(ab.list_rules(limit=step.get("limit", 20)))
|
|
elif fn == "list_skills":
|
|
results.append(ab.list_skills(limit=step.get("limit", 20)))
|
|
elif fn == "run_lifecycle":
|
|
results.append(ab.run_lifecycle())
|
|
elif fn == "pipeline_stats":
|
|
results.append(ab.pipeline_stats())
|
|
elif fn == "handle_hook":
|
|
results.append(ab.handle_hook(step.get("event"), step.get("data")))
|
|
finally:
|
|
ab.llm_call = old_llm
|
|
ab._cc_probe = old_probe
|
|
rcl.abstraction_extract_facts = old_loader_fn
|
|
ab._core = old_core
|
|
os.environ["RUNE_COGNITION_CORE"] = "python"
|
|
|
|
want = {"results": results}
|
|
if setup.get("capture_llm"):
|
|
want["llm_calls"] = llm_captures
|
|
if setup.get("dump"):
|
|
want["db"] = dump_db(path, tuple(setup["dump"]))
|
|
if kernel is not None:
|
|
want["kernel_calls"] = kernel.calls
|
|
return want
|
|
|
|
|
|
FACT_A = ("The deployment pipeline failed because the staging database "
|
|
"was locked by a long running migration this morning")
|
|
FACT_A_DICT = {"what": FACT_A, "when": "this morning", "where": "staging",
|
|
"who": "deploy-bot", "why": "database lock", "type": "experience",
|
|
"entities": ["deploy-bot", "staging"]}
|
|
LLM_ARRAY = json.dumps([FACT_A_DICT])
|
|
|
|
# LLM valid JSON: entities linked, triple created (who + >3 words)
|
|
row("extract:llm_valid_entities_triple", "steps",
|
|
{"llm": [LLM_ARRAY], "dump": ["facts", "entities", "fact_entities", "triples"],
|
|
"steps": [{"fn": "extract_facts", "text": "deploy text", "episode_id": "ep-1", "source": "test"}]},
|
|
run_steps({"llm": [LLM_ARRAY], "dump": ["facts", "entities", "fact_entities", "triples"],
|
|
"steps": [{"fn": "extract_facts", "text": "deploy text", "episode_id": "ep-1", "source": "test"}]}))
|
|
|
|
# dedup: second identical fact is a NOOP; distinct facts both stored
|
|
row("extract:dedup_noop", "steps",
|
|
{"llm": [LLM_ARRAY, LLM_ARRAY, json.dumps([{"what": FACT_A + " and then recovered fully"}])],
|
|
"dump": ["facts"], "steps": [
|
|
{"fn": "extract_facts", "text": "t1", "episode_id": "e1"},
|
|
{"fn": "extract_facts", "text": "t2", "episode_id": "e2"},
|
|
{"fn": "extract_facts", "text": "t3", "episode_id": "e3"}]},
|
|
run_steps({"llm": [LLM_ARRAY, LLM_ARRAY, json.dumps([{"what": FACT_A + " and then recovered fully"}])],
|
|
"dump": ["facts"], "steps": [
|
|
{"fn": "extract_facts", "text": "t1", "episode_id": "e1"},
|
|
{"fn": "extract_facts", "text": "t2", "episode_id": "e2"},
|
|
{"fn": "extract_facts", "text": "t3", "episode_id": "e3"}]}))
|
|
|
|
# short / empty what skipped; missing type defaults to world; missing dims empty
|
|
SHORT_FACTS = json.dumps([
|
|
{"what": "tiny"}, # <10 chars → skip
|
|
{"what": ""}, # empty → skip
|
|
{"what": "A completely unpunctuated plain fact sentence"}, # defaults
|
|
])
|
|
row("extract:short_empty_defaults", "steps",
|
|
{"llm": [SHORT_FACTS], "dump": ["facts"], "steps": [{"fn": "extract_facts", "text": "t"}]},
|
|
run_steps({"llm": [SHORT_FACTS], "dump": ["facts"], "steps": [{"fn": "extract_facts", "text": "t"}]}))
|
|
|
|
# who + <=3 words → no triple; who empty → no triple; entity strip/empty handling
|
|
NO_TRIPLE = json.dumps([
|
|
{"what": "Deploy bot ran", "who": "deploy-bot", "entities": [" deploy-bot ", ""]},
|
|
{"what": "Nobody was around here at all", "who": "", "entities": []},
|
|
])
|
|
row("extract:no_triple_entity_strip", "steps",
|
|
{"llm": [NO_TRIPLE], "dump": ["facts", "entities", "fact_entities", "triples"],
|
|
"steps": [{"fn": "extract_facts", "text": "t"}]},
|
|
run_steps({"llm": [NO_TRIPLE], "dump": ["facts", "entities", "fact_entities", "triples"],
|
|
"steps": [{"fn": "extract_facts", "text": "t"}]}))
|
|
|
|
# hash truncation: two facts identical in first 80 chars dedup; different at 81+ don't
|
|
BASE80 = "x" * 79
|
|
DUP80 = json.dumps([{"what": BASE80 + "a"}, {"what": BASE80 + "b"}])
|
|
row("extract:hash_80_truncation", "steps",
|
|
{"llm": [DUP80], "dump": ["facts"], "steps": [{"fn": "extract_facts", "text": "t"}]},
|
|
run_steps({"llm": [DUP80], "dump": ["facts"], "steps": [{"fn": "extract_facts", "text": "t"}]}))
|
|
|
|
# prose-wrapped array extraction (first [ to last ])
|
|
WRAPPED = "Sure! Here are the facts:\n" + LLM_ARRAY + "\nHope that helps."
|
|
row("extract:prose_wrapped_array", "steps",
|
|
{"llm": [WRAPPED], "dump": ["facts"], "steps": [{"fn": "extract_facts", "text": "t"}]},
|
|
run_steps({"llm": [WRAPPED], "dump": ["facts"], "steps": [{"fn": "extract_facts", "text": "t"}]}))
|
|
|
|
# unparseable + kernel fallback (items mapped; blank item skipped; args recorded)
|
|
KERNEL_ITEMS = [{"text": " kernel derived fact sentence one ", "content_words": ["kernel", "derived"]},
|
|
{"text": " "},
|
|
{"text": "kernel derived fact sentence two", "content_words": None}]
|
|
row("extract:kernel_fallback", "steps",
|
|
{"llm": ["no json here"], "kernel": KERNEL_ITEMS, "dump": ["facts"],
|
|
"steps": [{"fn": "extract_facts", "text": "kernel episode text", "episode_id": "ek"}]},
|
|
run_steps({"llm": ["no json here"], "kernel": KERNEL_ITEMS, "dump": ["facts"],
|
|
"steps": [{"fn": "extract_facts", "text": "kernel episode text", "episode_id": "ek"}]}))
|
|
|
|
# kernel raises → silent degrade to the error row
|
|
row("extract:kernel_raises", "steps",
|
|
{"llm": [], "kernel": [], "kernel_raise": True, "steps": [{"fn": "extract_facts", "text": "t"}]},
|
|
run_steps({"llm": [], "kernel": [], "kernel_raise": True, "steps": [{"fn": "extract_facts", "text": "t"}]}))
|
|
|
|
# no LLM, no kernel → deterministic error row (raw "")
|
|
row("extract:no_llm_no_kernel", "steps",
|
|
{"llm": [], "steps": [{"fn": "extract_facts", "text": "t", "episode_id": None}]},
|
|
run_steps({"llm": [], "steps": [{"fn": "extract_facts", "text": "t", "episode_id": None}]}))
|
|
|
|
# LLM returns "[]" → falls through kernel (pinned off) → error with raw "[]"
|
|
row("extract:empty_array_no_kernel", "steps",
|
|
{"llm": ["[]"], "steps": [{"fn": "extract_facts", "text": "t"}]},
|
|
run_steps({"llm": ["[]"], "steps": [{"fn": "extract_facts", "text": "t"}]}))
|
|
|
|
# unterminated array (truncation) → JSONDecodeError → error path
|
|
row("extract:unterminated_array", "steps",
|
|
{"llm": ['[{"what": "truncated fact sentence here"}'], "steps": [{"fn": "extract_facts", "text": "t"}]},
|
|
run_steps({"llm": ['[{"what": "truncated fact sentence here"}'], "steps": [{"fn": "extract_facts", "text": "t"}]}))
|
|
|
|
# episode_id omitted → stored "" and returned null
|
|
row("extract:episode_id_null", "steps",
|
|
{"llm": [LLM_ARRAY], "dump": ["facts"], "steps": [{"fn": "extract_facts", "text": "t"}]},
|
|
run_steps({"llm": [LLM_ARRAY], "dump": ["facts"], "steps": [{"fn": "extract_facts", "text": "t"}]}))
|
|
|
|
# prompt bytes: the exact LLM prompt built for an episode (with existing-facts context)
|
|
row("extract:prompt_bytes", "steps",
|
|
{"llm": [LLM_ARRAY, LLM_ARRAY], "capture_llm": True, "steps": [
|
|
{"fn": "extract_facts", "text": "cold episode", "episode_id": "p1"},
|
|
{"fn": "extract_facts", "text": "warm episode", "episode_id": "p2"}]},
|
|
run_steps({"llm": [LLM_ARRAY, LLM_ARRAY], "capture_llm": True, "steps": [
|
|
{"fn": "extract_facts", "text": "cold episode", "episode_id": "p1"},
|
|
{"fn": "extract_facts", "text": "warm episode", "episode_id": "p2"}]}))
|
|
|
|
# ── synthesize_observations ────────────────────────────────────────────────
|
|
SEED_ENTITY = {"entities": [{"id": 1, "name": "deploy-bot", "entity_type": "concept", "summary": "",
|
|
"fact_count": 3, "observation_count": 0, "created_at": FIXED_TS}]}
|
|
SEED_3_FACTS = {"facts": [
|
|
{"id": i + 1, "episode_id": "e", "what": f"fact number {i} about the deploy bot system",
|
|
"content_hash": f"h{i}", "when_text": "", "where_text": "", "who_text": "", "why_text": "",
|
|
"fact_type": "world" if i % 2 else "experience", "entities_json": "[]", "temporal_start": "",
|
|
"temporal_end": "", "confidence": 0.7, "source": "s", "state": "active", "access_count": 0,
|
|
"last_accessed": None, "created_at": FIXED_TS, "superseded_by": None} for i in range(3)],
|
|
"fact_entities": [{"fact_id": i + 1, "entity_id": 1} for i in range(3)]}
|
|
|
|
row("observe:entity_missing", "steps",
|
|
{"steps": [{"fn": "synthesize_observations", "entity": "ghost"}]},
|
|
run_steps({"steps": [{"fn": "synthesize_observations", "entity": "ghost"}]}))
|
|
|
|
row("observe:too_few_facts", "steps",
|
|
{"seed": {"entities": [SEED_ENTITY["entities"][0]],
|
|
"facts": SEED_3_FACTS["facts"][:2], "fact_entities": [{"fact_id": 1, "entity_id": 1}]},
|
|
"steps": [{"fn": "synthesize_observations", "entity": "deploy-bot"}]},
|
|
run_steps({"seed": {"entities": [SEED_ENTITY["entities"][0]],
|
|
"facts": SEED_3_FACTS["facts"][:2], "fact_entities": [{"fact_id": 1, "entity_id": 1}]},
|
|
"steps": [{"fn": "synthesize_observations", "entity": "deploy-bot"}]}))
|
|
|
|
OBS_SCRIPT = json.dumps([
|
|
{"observation": "Deploy bot is unreliable under database contention", "confidence": 0.9},
|
|
{"observation": "short"}, # <10 → skipped
|
|
{"observation": "Deploy bot failures cluster around migrations"}, # default confidence
|
|
])
|
|
row("observe:ok", "steps",
|
|
{**SEED_ENTITY, **SEED_3_FACTS, "llm": [OBS_SCRIPT], "dump": ["observations", "entities"],
|
|
"steps": [{"fn": "synthesize_observations", "entity": "deploy-bot"}]},
|
|
run_steps({**SEED_ENTITY, **SEED_3_FACTS, "llm": [OBS_SCRIPT], "dump": ["observations", "entities"],
|
|
"steps": [{"fn": "synthesize_observations", "entity": "deploy-bot"}]}))
|
|
|
|
# observe prompt bytes
|
|
row("observe:prompt_bytes", "steps",
|
|
{**SEED_ENTITY, **SEED_3_FACTS, "fact_entities": SEED_3_FACTS["fact_entities"], "llm": [OBS_SCRIPT],
|
|
"capture_llm": True, "steps": [{"fn": "synthesize_observations", "entity": "deploy-bot"}]},
|
|
run_steps({**SEED_ENTITY, **SEED_3_FACTS, "fact_entities": SEED_3_FACTS["fact_entities"], "llm": [OBS_SCRIPT],
|
|
"capture_llm": True, "steps": [{"fn": "synthesize_observations", "entity": "deploy-bot"}]}))
|
|
|
|
# ordering: facts created_at DESC controls source_fact_ids
|
|
SEED_3_FACTS_DESC = {"facts": [
|
|
{"id": 1, "episode_id": "e", "what": "oldest fact sentence about the bot", "content_hash": "h1",
|
|
"when_text": "", "where_text": "", "who_text": "", "why_text": "", "fact_type": "world",
|
|
"entities_json": "[]", "temporal_start": "", "temporal_end": "", "confidence": 0.7, "source": "s",
|
|
"state": "active", "access_count": 0, "last_accessed": None, "created_at": "2026-01-01T00:00:00+00:00",
|
|
"superseded_by": None},
|
|
{"id": 2, "episode_id": "e", "what": "newest fact sentence about the bot", "content_hash": "h2",
|
|
"when_text": "", "where_text": "", "who_text": "", "why_text": "", "fact_type": "world",
|
|
"entities_json": "[]", "temporal_start": "", "temporal_end": "", "confidence": 0.7, "source": "s",
|
|
"state": "active", "access_count": 0, "last_accessed": None, "created_at": "2026-09-01T00:00:00+00:00",
|
|
"superseded_by": None},
|
|
{"id": 3, "episode_id": "e", "what": "middle fact sentence about the bot", "content_hash": "h3",
|
|
"when_text": "", "where_text": "", "who_text": "", "why_text": "", "fact_type": "world",
|
|
"entities_json": "[]", "temporal_start": "", "temporal_end": "", "confidence": 0.7, "source": "s",
|
|
"state": "active", "access_count": 0, "last_accessed": None, "created_at": "2026-05-01T00:00:00+00:00",
|
|
"superseded_by": None},
|
|
]}
|
|
row("observe:source_ids_order", "steps",
|
|
{**SEED_ENTITY, **SEED_3_FACTS_DESC, "llm": [json.dumps([{"observation": "Bot facts span the whole year range"}])],
|
|
"dump": ["observations"], "steps": [{"fn": "synthesize_observations", "entity": "deploy-bot"}]},
|
|
run_steps({**SEED_ENTITY, **SEED_3_FACTS_DESC,
|
|
"llm": [json.dumps([{"observation": "Bot facts span the whole year range"}])],
|
|
"dump": ["observations"], "steps": [{"fn": "synthesize_observations", "entity": "deploy-bot"}]}))
|
|
|
|
# ── list_beliefs / list_rules / list_skills (ordering + limit + state) ─────
|
|
SEED_LISTS = {
|
|
"beliefs": [
|
|
{"id": 1, "claim": "low confidence belief", "confidence": 0.3, "reasoning": "r",
|
|
"supporting_fact_ids": "[]", "counterevidence": "[]", "scope": "", "state": "active",
|
|
"formed_at": FIXED_TS, "last_reinforced": None, "last_tested": None},
|
|
{"id": 2, "claim": "high confidence belief", "confidence": 0.9, "reasoning": "r",
|
|
"supporting_fact_ids": "[]", "counterevidence": "[]", "scope": "", "state": "active",
|
|
"formed_at": FIXED_TS, "last_reinforced": None, "last_tested": None},
|
|
{"id": 3, "claim": "mid confidence belief", "confidence": 0.5, "reasoning": "r",
|
|
"supporting_fact_ids": "[]", "counterevidence": "[]", "scope": "", "state": "archived",
|
|
"formed_at": FIXED_TS, "last_reinforced": None, "last_tested": None},
|
|
],
|
|
"rules": [
|
|
{"id": 1, "pattern": "rule a", "description": "d", "confidence": 0.5, "source_belief_ids": "[]",
|
|
"trigger_count": 2, "success_count": 2, "failure_count": 0, "scope": "", "state": "active",
|
|
"created_at": FIXED_TS, "last_triggered": None},
|
|
{"id": 2, "pattern": "rule b", "description": "d", "confidence": 0.5, "source_belief_ids": "[]",
|
|
"trigger_count": 5, "success_count": 4, "failure_count": 1, "scope": "", "state": "active",
|
|
"created_at": FIXED_TS, "last_triggered": None},
|
|
],
|
|
"skills": [
|
|
{"id": 1, "name": "skill-low", "description": "d", "procedure_json": "[]", "source_rule_ids": "[]",
|
|
"success_count": 1, "failure_count": 0, "state": "active", "created_at": FIXED_TS, "last_used": None},
|
|
{"id": 2, "name": "skill-high", "description": "d", "procedure_json": "[]", "source_rule_ids": "[]",
|
|
"success_count": 9, "failure_count": 0, "state": "active", "created_at": FIXED_TS, "last_used": None},
|
|
],
|
|
}
|
|
row("beliefs:order_and_state", "steps",
|
|
{"seed": SEED_LISTS, "dump": [], "steps": [{"fn": "list_beliefs"}]},
|
|
run_steps({"seed": SEED_LISTS, "steps": [{"fn": "list_beliefs"}]}))
|
|
row("beliefs:limit", "steps",
|
|
{"seed": SEED_LISTS, "steps": [{"fn": "list_beliefs", "limit": 1}]},
|
|
run_steps({"seed": SEED_LISTS, "steps": [{"fn": "list_beliefs", "limit": 1}]}))
|
|
row("rules:order_tiebreak_and_state", "steps",
|
|
{"seed": SEED_LISTS, "steps": [{"fn": "list_rules"}]},
|
|
run_steps({"seed": SEED_LISTS, "steps": [{"fn": "list_rules"}]}))
|
|
row("skills:order_and_state", "steps",
|
|
{"seed": SEED_LISTS, "steps": [{"fn": "list_skills"}]},
|
|
run_steps({"seed": SEED_LISTS, "steps": [{"fn": "list_skills"}]}))
|
|
row("lists:empty_db", "steps",
|
|
{"steps": [{"fn": "list_beliefs"}, {"fn": "list_rules"}, {"fn": "list_skills"}]},
|
|
run_steps({"steps": [{"fn": "list_beliefs"}, {"fn": "list_rules"}, {"fn": "list_skills"}]}))
|
|
|
|
# ── run_lifecycle (all archive gates) ──────────────────────────────────────
|
|
LIFECYCLE_SEED = {"facts": [
|
|
{"id": 1, "episode_id": "", "what": "old low confidence untouched fact sentence",
|
|
"content_hash": "l1", "when_text": "", "where_text": "", "who_text": "", "why_text": "",
|
|
"fact_type": "world", "entities_json": "[]", "temporal_start": "", "temporal_end": "",
|
|
"confidence": 0.4, "source": "", "state": "active", "access_count": 0, "last_accessed": None,
|
|
"created_at": "2020-01-01T00:00:00+00:00", "superseded_by": None}, # → archived
|
|
{"id": 2, "episode_id": "", "what": "old but accessed fact sentence here",
|
|
"content_hash": "l2", "when_text": "", "where_text": "", "who_text": "", "why_text": "",
|
|
"fact_type": "world", "entities_json": "[]", "temporal_start": "", "temporal_end": "",
|
|
"confidence": 0.4, "source": "", "state": "active", "access_count": 1, "last_accessed": None,
|
|
"created_at": "2020-01-01T00:00:00+00:00", "superseded_by": None}, # stays
|
|
{"id": 3, "episode_id": "", "what": "old but confident fact sentence",
|
|
"content_hash": "l3", "when_text": "", "where_text": "", "who_text": "", "why_text": "",
|
|
"fact_type": "world", "entities_json": "[]", "temporal_start": "", "temporal_end": "",
|
|
"confidence": 0.8, "source": "", "state": "active", "access_count": 0, "last_accessed": None,
|
|
"created_at": "2020-01-01T00:00:00+00:00", "superseded_by": None}, # stays
|
|
{"id": 4, "episode_id": "", "what": "recent low confidence fact sentence",
|
|
"content_hash": "l4", "when_text": "", "where_text": "", "who_text": "", "why_text": "",
|
|
"fact_type": "world", "entities_json": "[]", "temporal_start": "", "temporal_end": "",
|
|
"confidence": 0.4, "source": "", "state": "active", "access_count": 0, "last_accessed": None,
|
|
"created_at": "2099-01-01T00:00:00+00:00", "superseded_by": None}, # stays
|
|
{"id": 5, "episode_id": "", "what": "already archived fact sentence",
|
|
"content_hash": "l5", "when_text": "", "where_text": "", "who_text": "", "why_text": "",
|
|
"fact_type": "world", "entities_json": "[]", "temporal_start": "", "temporal_end": "",
|
|
"confidence": 0.4, "source": "", "state": "archived", "access_count": 0, "last_accessed": None,
|
|
"created_at": "2020-01-01T00:00:00+00:00", "superseded_by": None}, # untouched
|
|
], "observations": [
|
|
{"id": 1, "entity_id": 1, "observation": "superseded observation sentence", "source_fact_ids": "[]",
|
|
"confidence": 0.7, "state": "active", "created_at": FIXED_TS, "superseded_by": 2}, # → archived
|
|
{"id": 2, "entity_id": 1, "observation": "current observation sentence", "source_fact_ids": "[]",
|
|
"confidence": 0.7, "state": "active", "created_at": FIXED_TS, "superseded_by": None}, # stays
|
|
{"id": 3, "entity_id": 1, "observation": "archived observation sentence", "source_fact_ids": "[]",
|
|
"confidence": 0.7, "state": "archived", "created_at": FIXED_TS, "superseded_by": 2}, # stays
|
|
]}
|
|
row("lifecycle:archive_gates", "steps",
|
|
{"seed": LIFECYCLE_SEED, "dump": ["facts", "observations"],
|
|
"steps": [{"fn": "run_lifecycle"}]},
|
|
run_steps({"seed": LIFECYCLE_SEED, "dump": ["facts", "observations"], "steps": [{"fn": "run_lifecycle"}]}))
|
|
|
|
# ── pipeline_stats ─────────────────────────────────────────────────────────
|
|
row("stats:mixed", "steps",
|
|
{"seed": {**LIFECYCLE_SEED, **SEED_LISTS, **SEED_ENTITY, **SEED_3_FACTS,
|
|
"fact_entities": SEED_3_FACTS["fact_entities"], "triples": [
|
|
{"id": 1, "subject": "s", "predicate": "p", "object": "o", "source_fact_id": 1,
|
|
"confidence": 0.6, "created_at": FIXED_TS}]},
|
|
"steps": [{"fn": "run_lifecycle"}, {"fn": "pipeline_stats"}]},
|
|
run_steps({"seed": {**LIFECYCLE_SEED, **SEED_LISTS, **SEED_ENTITY, **SEED_3_FACTS,
|
|
"fact_entities": SEED_3_FACTS["fact_entities"], "triples": [
|
|
{"id": 1, "subject": "s", "predicate": "p", "object": "o", "source_fact_id": 1,
|
|
"confidence": 0.6, "created_at": FIXED_TS}]},
|
|
"steps": [{"fn": "run_lifecycle"}, {"fn": "pipeline_stats"}]}))
|
|
|
|
row("stats:empty", "steps", {"steps": [{"fn": "pipeline_stats"}]},
|
|
run_steps({"steps": [{"fn": "pipeline_stats"}]}))
|
|
|
|
# ── handle_hook ────────────────────────────────────────────────────────────
|
|
row("hook:session_end_with_data", "steps",
|
|
{"steps": [{"fn": "handle_hook", "event": "on_session_end", "data": {"k": 1}}]},
|
|
run_steps({"steps": [{"fn": "handle_hook", "event": "on_session_end", "data": {"k": 1}}]}))
|
|
row("hook:other_event_no_data", "steps",
|
|
{"steps": [{"fn": "handle_hook", "event": "on_tick", "data": None}]},
|
|
run_steps({"steps": [{"fn": "handle_hook", "event": "on_tick", "data": None}]}))
|
|
|
|
# ── CLI (byte-exact stdout/stderr/exit) ────────────────────────────────────
|
|
def run_cli(setup):
|
|
import contextlib as _cl
|
|
path = fresh_db()
|
|
seed_db(path, setup.get("seed"))
|
|
llm_queue = list(setup.get("llm", []))
|
|
old_llm, old_url = ab.llm_call, ab.OLLAMA_URL
|
|
ab.llm_call = lambda prompt, model=None, max_tokens=500: llm_queue.pop(0) if llm_queue else ""
|
|
ab.OLLAMA_URL = UNREACHABLE
|
|
old_argv, old_out, old_err = sys.argv, sys.stdout, sys.stderr
|
|
out, err = io.StringIO(), io.StringIO()
|
|
code = 0
|
|
sys.argv = ["abstraction.py"] + setup["argv"]
|
|
sys.stdout, sys.stderr = out, err
|
|
try:
|
|
ab.main()
|
|
except SystemExit as e:
|
|
code = e.code if isinstance(e.code, int) else 1
|
|
finally:
|
|
sys.stdout, sys.stderr = old_out, old_err
|
|
sys.argv = old_argv
|
|
ab.llm_call, ab.OLLAMA_URL = old_llm, old_url
|
|
return {"stdout": out.getvalue(), "stderr": err.getvalue(), "code": code}
|
|
|
|
|
|
CLI_SETS = {
|
|
"extract_nojson": ({"llm": [LLM_ARRAY]}, ["extract", "deploy text", "--episode-id", "cx1"]),
|
|
"extract_json": ({"llm": [LLM_ARRAY]}, ["extract", "deploy text", "--json"]),
|
|
"extract_usage": ({}, ["extract"]),
|
|
"extract_fails": ({}, ["extract", "some episode text here"]),
|
|
"observe_ok": ({"seed": {**SEED_ENTITY, **SEED_3_FACTS, "fact_entities": SEED_3_FACTS["fact_entities"]},
|
|
"llm": [OBS_SCRIPT]}, ["observe", "deploy-bot", "--json"]),
|
|
"observe_missing": ({}, ["observe", "ghost"]),
|
|
"observe_usage": ({}, ["observe"]),
|
|
"beliefs_text": ({"seed": SEED_LISTS}, ["beliefs"]),
|
|
"beliefs_json": ({"seed": SEED_LISTS}, ["beliefs", "--json"]),
|
|
"beliefs_empty": ({}, ["beliefs"]),
|
|
"rules_text": ({"seed": SEED_LISTS}, ["rules"]),
|
|
"skills_text": ({"seed": SEED_LISTS}, ["skills"]),
|
|
"skills_json": ({"seed": SEED_LISTS}, ["skills", "--json"]),
|
|
"lifecycle_text": ({"seed": LIFECYCLE_SEED}, ["lifecycle"]),
|
|
"lifecycle_json": ({"seed": LIFECYCLE_SEED}, ["lifecycle", "--json"]),
|
|
"stats_text": ({"seed": SEED_LISTS}, ["stats"]),
|
|
"stats_json": ({"seed": SEED_LISTS}, ["stats", "--json"]),
|
|
"stats_empty_text": ({}, ["stats"]),
|
|
"hook": ({}, ["hook", "--event", "on_session_end", "--data", '{"session": "s1"}']),
|
|
"hook_no_event": ({}, ["hook"]),
|
|
"hook_data_null": ({}, ["hook", "--event", "on_tick"]),
|
|
"evolve_noop": ({}, ["evolve", "mem-123"]),
|
|
"bad_choice": ({}, ["nope"]),
|
|
"no_cmd": ({}, []),
|
|
"help": ({}, ["--help"]),
|
|
}
|
|
for label, (setup, argv) in CLI_SETS.items():
|
|
s = dict(setup)
|
|
s["argv"] = argv
|
|
row(f"cli:{label}", "cli", s, run_cli(s))
|
|
|
|
# ── freeze ─────────────────────────────────────────────────────────────────
|
|
doc = {
|
|
"frozen_ts": "2026-10-11T13:45:00Z",
|
|
"frozen_id": "abstraction-full-go-#2479",
|
|
"generator": "scripts/gen_abstraction_truth.py equivalent (session temp)",
|
|
"rows": rows,
|
|
}
|
|
out = sys.argv[1] if len(sys.argv) > 1 else "/mnt/workdrive/rune/src/go/abstraction/truth.json"
|
|
with open(out, "w") as f:
|
|
json.dump(doc, f, indent=1)
|
|
print(f"frozen {len(rows)} rows -> {out}")
|