feat(context_broker): close PARTIAL gaps — dichotomy wave

This commit is contained in:
Nick committed 2026-10-13 10:04:39 +01:00
1 parent a829834f8d
commit 8119914fc3
6 files changed
+4368 -60

No files matched your search

+180
View File
@@ -0,0 +1,180 @@
#!/usr/bin/env python3
"""Live parity check: the Go context-broker binary vs the LIVE Python oracle
(RUNE_COGNITION_CORE=python pinned AND delegation auto), fuzz + edge cases.
Complements the frozen battery (truth.json) with generated inputs."""
from __future__ import annotations
import json
import os
import random
import string
import subprocess
import sys
import tempfile
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "src"))
BIN = Path("/tmp/cb-live-parity")
def build():
subprocess.run(["go", "build", "-o", str(BIN), "."],
cwd=ROOT / "src/go/context-broker", check=True)
def go_eval(payload: dict) -> dict:
proc = subprocess.run([str(BIN), "eval"], input=json.dumps(payload),
capture_output=True, text=True)
if proc.returncode != 0:
raise RuntimeError(f"go eval failed: {proc.stderr}")
return json.loads(proc.stdout)
fails = 0
checked = 0
def check(name, got, want):
global fails, checked
checked += 1
if got != want:
fails += 1
print(f"DIVERGE {name}: go={got!r} py={want!r}")
def est_case(text):
check(f"est[{text!r:.40}]", go_eval({"op": "est", "text": text}),
py._py_default_estimator(text))
def budget_case(w):
check(f"budget[{w}]", go_eval({"op": "budget", "window": w}),
py._py_default_budget(w))
def membud_case(w):
check(f"membud[{w}]", go_eval({"op": "membud", "window": w}),
py._py_default_memory_budget(w))
def memoff_case(v):
check(f"memoff[{v!r}]", go_eval({"op": "memoff", "value": v}),
py._py_memory_disabled_value(v))
def through_case(messages, through):
want = py._through_id(json.loads(json.dumps(messages)), through)
got = go_eval({"op": "through", "messages": messages, "through": through})
check(f"through[{through}/{len(messages)}]", got, want)
rng = random.Random(2479)
os.environ["RUNE_COGNITION_CORE"] = "python"
from rune import context_broker as py # noqa: E402
# fuzz: estimator
for _ in range(200):
n = rng.randint(0, 300)
est_case("".join(rng.choice(string.printable[:94]) for _ in range(n)))
est_case("héllo wörld ünïcode ✓ 日本語テキスト")
est_case("🎉 emoji surrogate pair")
# fuzz: budgets (incl. boundaries around max(2048, w/4) crossover)
for w in [0, 1, 2559, 2560, 2561, 8191, 8192, 8193, 10000, 65536, 131072,
200000, 10**9] + [rng.randint(0, 200000) for _ in range(100)]:
budget_case(w)
membud_case(w)
# fuzz: memory-off raw values
for v in ["", "0", "00", "false", "FALSE", "False", "no", "No", "off", "OFF",
"off ", " off", "1", "true", "YES", "on", "anything", "\t", "\n",
"0.0", "nil", "None", "null"]:
memoff_case(v)
# fuzz: through_id over random message shapes
for _ in range(100):
msgs = []
for _ in range(rng.randint(1, 8)):
m = {"role": rng.choice(["user", "assistant", "system"]),
"content": "".join(rng.choice(string.printable[:70]) for _ in range(rng.randint(0, 40)))}
if rng.random() < 0.3:
m["extra"] = {"n": rng.randint(0, 5), "flag": rng.random() < 0.5}
msgs.append(m)
through_case(msgs, rng.randint(-1, len(msgs) + 1))
# unicode through_id (ensure_ascii path)
uni = [{"role": "user", "content": "日本語 テキスト 🎉 emoji"},
{"role": "assistant", "content": "héllo wörld"}]
through_case(uni, 1)
through_case(uni, 0)
# fuzz: admission kernel (priority desc, recency tie, budget, memory cap)
NAMES = ["system", "user", "history", "memory", "persona_memories", "tools", "notes"]
for case_i in range(150):
entries = []
for j in range(rng.randint(1, 7)):
name = rng.choice(NAMES)
entries.append({
"name": name,
"priority": rng.randint(0, 100),
"tokens": rng.randint(1, 900),
"source": f"src-{j}",
"evictable": name not in ("system", "user"),
})
budget = rng.choice([500, 1000, 2000, 4000])
mem_budget = rng.choice([0, 300, 1200])
got = go_eval({"op": "admit", "entries": entries, "budget": budget,
"memory_budget": mem_budget})
bk = py.ContextBroker()
py_entries = [(e["name"], py.ContributionBlock(e["name"], e["priority"],
"c" * e["tokens"], e["tokens"],
e["source"], evictable=e["evictable"]), j)
for j, e in enumerate(entries)]
try:
_adm, adm = bk._admit(py_entries, budget, memory_budget=mem_budget)
want = {"admitted": [bl.name for bl in _adm],
"blocks": adm["blocks"], "admitted_total": adm["admitted_total"],
"dropped": [d["name"] for d in adm["dropped"]],
"memory_budget": adm["memory_budget"], "memory_used": adm["memory_used"]}
check(f"admit[{case_i}]", got, json.loads(json.dumps(want, sort_keys=True)))
except py.ContextOverflowError as exc:
# Go wraps the oracle's message with the wrapped sentinel; the oracle
# text must be a verbatim prefix of the Go detail.
ok = (isinstance(got, dict) and got.get("error") == "ContextOverflowError"
and got.get("detail", "").startswith(str(exc)))
check(f"admit[{case_i}]", "ContextOverflowError" if ok else got,
"ContextOverflowError")
# fuzz: render (system folding, history pass-through, user capture)
for case_i in range(100):
blocks = []
for j in range(rng.randint(1, 6)):
name = rng.choice(NAMES)
if rng.random() < 0.5:
content = "".join(rng.choice(string.ascii_letters) for _ in range(rng.randint(1, 30)))
else:
content = [{"role": rng.choice(["user", "assistant"]),
"content": "".join(rng.choice(string.ascii_letters) for _ in range(rng.randint(1, 20)))}
for _ in range(rng.randint(1, 3))]
blocks.append({"name": name, "priority": rng.randint(0, 100),
"tokens": 0, "source": f"src-{j}", "content": content})
got = go_eval({"op": "render", "blocks": blocks})
py_blocks = []
for b in blocks:
content = b["content"]
if isinstance(content, list):
content = [dict(m) for m in content]
py_blocks.append(py.ContributionBlock(b["name"], b["priority"], content,
b["tokens"], b["source"]))
caps = py.ContextBroker()._render(py_blocks)
want = {"token_count": caps.token_count, "messages": caps.messages,
"system_preamble": caps.system_preamble, "user_content": caps.user_content,
"blocks": [bl.name for bl in caps.blocks]}
check(f"render[{case_i}]", got, json.loads(json.dumps(want)))
print(f"live parity: {checked - fails}/{checked} cases match "
f"(RUNE_COGNITION_CORE=python pinned)")
sys.exit(1 if fails else 0)
+383
View File
@@ -0,0 +1,383 @@
#!/usr/bin/env python3
"""Freeze the oracle truth for the GO gap-close port of
src/rune/context_broker.py (dichotomy wave #2479, GO, factory-gap-ctxbroker).
Every NEW function ported by the gap-close, every branch, against the LIVE
oracle pinned to the Python cores (RUNE_COGNITION_CORE=python — the _py_*
oracles ARE the parity floor being frozen). Deep-copied inputs: the oracle
mutates message lists in place (compact_history archive path), so each row
re-freezes the input verbatim.
Ops frozen (fn = the Go-side dispatcher key):
_py_default_estimator est (text)
_cc_core cc_core (env RUNE_COGNITION_CORE) -> bool available
_py_default_budget budget (window)
_py_default_memory_budget membud (window, incl. None/str/bool coercion)
_py_memory_disabled_value memoff (raw string)
reset_discovered_windows / register_window / default_window
windows (sequence of ops -> final registry view)
summarize_history summarize (transport-failure branch: ValueError)
compact_history compact (messages, window, summarizer, session)
rolling_summary_state rstate (key -> state | null)
_through_id through (messages, through_count)
store_rolling_summary / evict_rolling rstore (op sequence -> state + return)
"""
from __future__ import annotations
import copy
import json
import os
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "src"))
os.environ["RUNE_COGNITION_CORE"] = "python" # pin the Python parity floor
from rune import context_broker as cb # noqa: E402
rows: list[dict] = []
def row(name, fn, inp, out):
rows.append({"name": name, "fn": fn, "input": inp, "want": out})
# ── _py_default_estimator: every branch ────────────────────────────────────
row("est:empty", "est", {"text": ""}, cb._py_default_estimator(""))
row("est:none-coerced-empty", "est", {"text": None}, cb._py_default_estimator(None))
row("est:short", "est", {"text": "hello"}, cb._py_default_estimator("hello"))
row("est:exact-8", "est", {"text": "12345678"}, cb._py_default_estimator("12345678"))
row("est:9-floor", "est", {"text": "123456789"}, cb._py_default_estimator("123456789"))
row("est:unicode-rune-len", "est", {"text": "héllo wörld"}, cb._py_default_estimator("héllo wörld"))
# ── _cc_core: python-pin + auto-without-build ───────────────────────────────
from rune import cc_loader # noqa: E402
_LOADER_AVAIL = bool(cc_loader.available())
row("cc_core:python-pin-returns-none", "cc_core",
{"env": {"RUNE_COGNITION_CORE": "python"}, "loader_available": _LOADER_AVAIL},
None if cb._cc_core() is None else "CORE")
del os.environ["RUNE_COGNITION_CORE"]
row("cc_core:auto-fallback", "cc_core", {"env": {}, "loader_available": _LOADER_AVAIL},
None if cb._cc_core() is None else "CORE")
# ── _py_default_budget: every branch ────────────────────────────────────────
row("budget:0-floor", "budget", {"window": 0}, cb._py_default_budget(0))
row("budget:1-floor", "budget", {"window": 1}, cb._py_default_budget(1))
row("budget:100-floor", "budget", {"window": 100}, cb._py_default_budget(100))
row("budget:4096", "budget", {"window": 4096}, cb._py_default_budget(4096))
row("budget:8192", "budget", {"window": 8192}, cb._py_default_budget(8192))
row("budget:131072", "budget", {"window": 131072}, cb._py_default_budget(131072))
row("budget:quarter-exact-8192", "budget", {"window": 8192}, cb._py_default_budget(8192))
# ── _py_default_memory_budget: every branch incl. coercion ─────────────────
row("membud:0-cap", "membud", {"window": 0}, cb._py_default_memory_budget(0))
row("membud:none-cap", "membud", {"window": None}, cb._py_default_memory_budget(None))
row("membud:neg-cap", "membud", {"window": -5}, cb._py_default_memory_budget(-5))
row("membud:8-eq-1", "membud", {"window": 8}, cb._py_default_memory_budget(8))
row("membud:2400", "membud", {"window": 2400}, cb._py_default_memory_budget(2400))
row("membud:9600-cap", "membud", {"window": 9600}, cb._py_default_memory_budget(9600))
row("membud:96000-cap", "membud", {"window": 96000}, cb._py_default_memory_budget(96000))
# bool/float coerce through the public fn's int(window) path when a Rust core
# is present; on the Python floor they hit _py_default_memory_budget raw.
row("membud:bool-true", "membud", {"window": True}, cb._py_default_memory_budget(True))
row("membud:float", "membud", {"window": 2400.9}, cb._py_default_memory_budget(2400.9))
# ── _py_memory_disabled_value: every branch ────────────────────────────────
for v in ("", "0", "false", "no", "off", "1", "true", "yes", "on", "OFF",
" False ", "anything", " ", "off\n"):
row(f"memoff:{v!r}", "memoff", {"value": v}, cb._py_memory_disabled_value(v))
# ── window registry: reset/register/default with NO live discovery ─────────
# default_window's live-discovery import is exercised in the fallback branch
# only when model_id is falsy (registry miss + import of local_tool_runtime
# would attempt a real HTTP call otherwise — the Go seam stubs discovery;
# here we freeze: falsy id, registry hit, registry miss w/ capped discovery).
cb.reset_discovered_windows()
row("windows:reset-then-missing-none", "windows",
{"ops": [{"op": "default_window", "model_id": None}]},
{"last": cb.default_window(None)})
row("windows:none-empty-str", "windows",
{"ops": [{"op": "default_window", "model_id": ""}]},
{"last": cb.default_window("")})
row("windows:register-hit", "windows",
{"ops": [{"op": "register_window", "model_id": "m1", "window": 32768},
{"op": "default_window", "model_id": "m1"}]},
{"last": (cb.reset_discovered_windows(), cb.register_window("m1", 32768),
cb.default_window("m1"))[2]})
row("windows:register-overwrite", "windows",
{"ops": [{"op": "register_window", "model_id": "m1", "window": 8192},
{"op": "register_window", "model_id": "m1", "window": 65536},
{"op": "default_window", "model_id": "m1"}]},
{"last": (cb.reset_discovered_windows(), cb.register_window("m1", 8192),
cb.register_window("m1", 65536), cb.default_window("m1"))[3]})
row("windows:reset-clears", "windows",
{"ops": [{"op": "register_window", "model_id": "m1", "window": 8192},
{"op": "reset_discovered_windows"},
{"op": "default_window", "model_id": "m1"}]},
{"last": (cb.reset_discovered_windows(), cb.register_window("m1", 8192),
cb.reset_discovered_windows(), cb.default_window("m1"))[3]})
row("windows:register-str-window-coerced", "windows",
{"ops": [{"op": "register_window", "model_id": "m1", "window": "28672"},
{"op": "default_window", "model_id": "m1"}]},
{"last": (cb.reset_discovered_windows(), cb.register_window("m1", "28672"),
cb.default_window("m1"))[2]})
# discovery-failure branch: unknown model -> the live oracle tries the
# transport; in this offline env that raises inside context_length ->
# record_failure + fallback 8192 (never cached). Freeze the returned value.
cb.reset_discovered_windows()
row("windows:miss-discover-fallback-8192", "windows",
{"ops": [{"op": "default_window", "model_id": "definitely-not-a-model:offline"}]},
{"last": cb.default_window("definitely-not-a-model:offline"),
"note": "live oracle: T1 context_discovery + FALLBACK_WINDOW, not cached"})
# ── summarize_history: transport-failure branch (offline) ──────────────────
try:
cb.summarize_history([{"role": "user", "content": "hi"}], model="offline-no-such-model")
row("summarize:unexpected-success", "summarize", {"messages": []}, "UNEXPECTED-SUCCESS")
except ValueError as exc:
row("summarize:transport-fail-valueerror", "summarize",
{"messages": [{"role": "user", "content": "hi"}]},
{"error": "ValueError", "detail": str(exc)[:120]})
except Exception as exc: # noqa: BLE001 — freeze whatever the live env does
row("summarize:transport-fail-other", "summarize",
{"messages": [{"role": "user", "content": "hi"}]},
{"error": type(exc).__name__, "detail": str(exc)[:120]})
# ── _through_id: every branch ───────────────────────────────────────────────
msgs = [{"role": "user", "content": "first"},
{"role": "assistant", "content": "reply"},
{"role": "user", "content": "second"}]
row("through:empty-list", "through", {"messages": [], "through": 0}, cb._through_id([], 0))
row("through:neg", "through", {"messages": msgs, "through": -1}, cb._through_id(msgs, -1))
row("through:at-end", "through", {"messages": msgs, "through": 3}, cb._through_id(msgs, 3))
row("through:beyond-end", "through", {"messages": msgs, "through": 99}, cb._through_id(msgs, 99))
row("through:anchor-0", "through", {"messages": msgs, "through": 0}, cb._through_id(msgs, 0))
row("through:anchor-1", "through", {"messages": msgs, "through": 1}, cb._through_id(msgs, 1))
row("through:content-hash-stable", "through",
{"messages": msgs, "through": 2},
(cb._through_id(msgs, 2), cb._through_id(copy.deepcopy(msgs), 2),
cb._through_id([{"content": "second", "role": "user"}], 0))[0],
)
# key-order independence (sort_keys=True)
row("through:key-order-independent", "through",
{"messages": [{"role": "user", "content": "x"}, {"content": "x", "role": "user"}],
"through": 1},
cb._through_id([{"role": "user", "content": "x"}, {"content": "x", "role": "user"}], 1))
# ── rolling summary store: state/store/evict (self-contained rows) ──────────
cb._ROLLING.clear()
row("rstate:miss", "rstate", {"ops": [{"op": "read", "source": "operator", "sid": "s1"}]},
cb.rolling_summary_state(("operator", "s1")))
cb.store_rolling_summary(("operator", "s1"), "SUM", 7, "abcdef0123456789")
row("rstate:hit-copy", "rstate",
{"ops": [{"op": "store", "source": "operator", "sid": "s1", "summary": "SUM",
"through": 7, "tid": "abcdef0123456789"},
{"op": "read", "source": "operator", "sid": "s1"}]},
cb.rolling_summary_state(("operator", "s1")))
row("rstore:store-then-through-id", "rstore",
{"msgs": msgs},
(cb.store_rolling_summary(("operator", "s2"), "S2", 1, cb._through_id(msgs, 1)),
cb.rolling_summary_state(("operator", "s2")))[1])
# ── compact_history: every branch (injected summarizer = deterministic) ────
def fresh(seed: str, n_pairs: int) -> list[dict]:
out = []
for i in range(n_pairs):
out.append({"role": "user", "content": f"{seed} user {i} " + "x" * (i * 40)})
out.append({"role": "assistant", "content": f"{seed} assistant {i} " + "y" * (i * 40)})
return out
def sumr(messages):
return "SUMMARY<" + "|".join(m.get("content", "")[:10] for m in messages) + ">"
def compact_row(name, messages, window, *, summarizer="sum", session="", prior=None,
archive=False):
"""Freeze one compact_history call; self-contained (prior state inline)."""
cb._ROLLING.clear()
if prior:
cb.store_rolling_summary(("operator", session) if session else ("operator",),
prior["summary"], prior["through_count"],
prior.get("through_id", ""))
kw = dict(summarizer=sumr if summarizer != "boom" else boom)
arch = None
if archive:
# deterministic RELATIVE fixture path (chdir'd to repo root): the
# label is embedded in the summary block, so it must be portable
os.chdir(ROOT)
arch = Path("src/go/context-broker/.fixtures/archive.jsonl")
arch.parent.mkdir(parents=True, exist_ok=True)
inp = {"messages": copy.deepcopy(messages), "window": window,
"summarizer": summarizer}
if session:
inp["session"] = session
if prior:
inp["prior"] = prior
if archive:
inp["archive"] = "FIXTURE"
inp["archive_path"] = str(arch)
compacted, ev = cb.compact_history(copy.deepcopy(messages), window,
archive_path=arch,
summarizer=sumr if summarizer != "boom" else boom,
session_id=session)
out = {"event": ev}
if messages is not None:
out["compacted"] = compacted
if archive:
body = arch.read_text() if arch.exists() else ""
out["archive_lines"] = body.count("\n")
out["archived_msg_count"] = len(json.loads(body.splitlines()[0])["messages"]) if body else 0
out["archived_msgs"] = json.loads(body.splitlines()[0])["messages"] if body else []
arch.unlink(missing_ok=True)
row(name, "compact", inp, out)
def boom(messages):
raise RuntimeError("summarizer down")
# no messages -> early return, event untriggered
compact_row("compact:empty-messages", [], 8192)
# <= KEEP_TURNS user turns -> early return
compact_row("compact:at-keep-turns-no-trigger", fresh("a", 5), 8192)
compact_row("compact:below-keep-turns-no-trigger", fresh("a", 4), 8192)
# > keep turns, no session, summarizer success
compact_row("compact:trigger-no-session", fresh("b", 7), 8192)
# with session: rolling persistence across TWO compactions (C1: only new)
cb._ROLLING.clear()
m3 = fresh("c", 8)
c1, ev1 = cb.compact_history(copy.deepcopy(m3), 8192, summarizer=sumr,
session_id="s3")
prior3 = cb.rolling_summary_state(("operator", "s3"))
row("compact:session-first", "compact",
{"messages": m3, "window": 8192, "session": "s3", "summarizer": "sum"},
{"event": ev1, "compacted": c1})
# second compaction: window rotated under the stored count (#141 rematch)
m3b = m3[:2] + m3[-10:]
compact_row("compact:session-rotation-rematch", m3b, 8192, session="s3",
prior=prior3)
# prior through beyond len(older) — history shrank, re-summarize from scratch
compact_row("compact:session-through-beyond-shrink", fresh("d", 7), 8192,
session="s4", prior={"summary": "PRIOR-SUM", "through_count": 999,
"through_id": ""})
# nothing new beyond the rolled summary: reuse it verbatim
cb._ROLLING.clear()
m9 = fresh("g", 7)
_, ev9_pre = cb.compact_history(copy.deepcopy(m9), 8192, summarizer=sumr,
session_id="s9")
prior9 = cb.rolling_summary_state(("operator", "s9"))
# call again with IDENTICAL messages: older[6:] is empty -> reuse verbatim
compact_row("compact:session-nothing-new-reuse", m9, 8192, session="s9",
prior=prior9)
# summarizer failure -> C2 degrade to tail
compact_row("compact:summarizer-fail-degrade-tail", fresh("e", 7), 8192,
summarizer="boom")
# summarizer failure with tiny window (tail budget floor: always keep >=1)
m6 = [{"role": "user", "content": "u" * 9000}, {"role": "assistant", "content": "a" * 9000},
{"role": "user", "content": "u2" * 9000}, {"role": "assistant", "content": "a2" * 9000},
{"role": "user", "content": "u3" * 9000}, {"role": "assistant", "content": "a3" * 9000},
{"role": "user", "content": "u4" * 9000}]
compact_row("compact:fail-tiny-window-tail-first-only", m6, 10, summarizer="boom")
# archive write: success path — event.archived True, label = the path itself
compact_row("compact:archive-success", fresh("f", 6), 8192, session="s7",
archive=True)
# default_estimator used inside event tokens (python pin): tokens_before math
compact_row("compact:token-math-estimator",
[{"role": "user", "content": "12345678"}, {"role": "assistant", "content": "1234"}],
8192)
# ── ContextBroker kernel: to_dict / collect / admit / render ────────────────
from rune.context_broker import ContributionBlock, ContextBroker, ContextOverflowError # noqa: E402
# to_dict
blk = ContributionBlock("memory", 5, "hello world", 2, "recall", evictable=True)
row("to_dict:block", "to_dict", {}, blk.to_dict())
blk2 = ContributionBlock("system", 100, "sys", 0, "persona", evictable=False)
row("to_dict:mandatory", "to_dict", {}, blk2.to_dict())
# collect: happy, empty-content skip, failure → T1 degrade, malformed → T1
def make_broker():
b = ContextBroker()
b.register_contributor("ok", lambda task, session: ContributionBlock("tools", 9, "tooltext", 2, "t"), 1)
b.register_contributor("empty", lambda task, session: ContributionBlock("x", 1, "", 0, "s"), 1)
b.register_contributor("boom", lambda task, session: (_ for _ in ()).throw(RuntimeError("cb down")), 1)
b.register_contributor("malformed", lambda task, session: ContributionBlock("y", 1, 12345, 0, "s"), 1)
b.register_contributor("list-ok", lambda task, session: ContributionBlock("history", 3, [{"role": "user", "content": "hi"}], 1, "h"), 2)
b.register_contributor("none", lambda task, session: None, 1)
return b
b = make_broker()
degraded: list[str] = []
collected = b._collect({}, {}, degraded)
row("collect:degraded-and-ordered", "collect", {},
{"names": [name for name, _blk, _b in collected],
"degraded": degraded,
"failed_events": sorted(getattr(b, "_last_failures", []))})
# admit: priority desc, recency tie, memory sub-budget, overflow
def blocks(*specs):
out = []
for name, pri, tokens, evictable in specs:
out.append(ContributionBlock(name, pri, "c" * tokens, tokens, f"src-{name}", evictable=evictable))
return out
bk = ContextBroker()
admitted, adm = bk._admit([(b["name"], bl, 0) for b, bl in zip(
[{"name": "system"}, {"name": "memory"}, {"name": "history"}, {"name": "user"}],
blocks(("system", 100, 500, False), ("memory", 5, 300, True),
("history", 7, 900, True), ("user", 100, 100, False)))], 1000)
row("admit:priority-recency-budget", "admit", {},
{"admitted": [bl.name for bl in admitted],
"blocks": adm["blocks"], "admitted_total": adm["admitted_total"],
"dropped": [d["name"] for d in adm["dropped"]],
"memory_budget": adm["memory_budget"], "memory_used": adm["memory_used"]})
# memory sub-budget: two memory blocks, second exceeds sub-budget
admitted2, adm2 = bk._admit([("m1", blocks(("m1", 9, 700, True))[0], 0),
("m2", blocks(("m2", 8, 700, True))[0], 1)], 4000, memory_budget=1200)
row("admit:memory-subbudget-drop", "admit", {},
{"admitted": [bl.name for bl in admitted2],
"dropped": [d["name"] for d in adm2["dropped"]],
"memory_used": adm2["memory_used"],
"reasons": [d["reason"] for d in adm2["dropped"]]})
# mandatory overflow → raise
try:
bk._admit([("user", blocks(("user", 100, 5000, False))[0], 0)], 1000)
row("admit:mandatory-overflow-raises", "admit", {}, "NO-RAISE")
except ContextOverflowError as exc:
row("admit:mandatory-overflow-raises", "admit", {},
{"error": "ContextOverflowError", "detail": str(exc)})
# render: system folding, history pass-through, user capture
capsule = bk._render(blocks(("persona", 100, 7, False), ("tools", 90, 5, False),
("history", 70, 4, False), ("user", 100, 2, False)))
row("render:order-and-fold", "render", {},
{"token_count": capsule.token_count,
"messages": capsule.messages,
"system_preamble": capsule.system_preamble,
"user_content": capsule.user_content,
"blocks": [bl.name for bl in capsule.blocks]})
print(f"froze {len(rows)} rows total")
doc = {
"frozen_ts": "2026-10-12T00:00:00Z",
"generator": "scripts/gen_context_broker_truth.py — live oracle src/rune/context_broker.py, RUNE_COGNITION_CORE=python pinned",
"rows": rows,
}
out = ROOT / "src/go/context-broker/truth.json"
out.write_text(json.dumps(doc, indent=1, sort_keys=True) + "\n")
+420
View File
@@ -0,0 +1,420 @@
package main
// broker.go — the ContextBroker orchestration core (admission, collection,
// rendering) from src/rune/context_broker.py, gap-close dichotomy wave.
// The Python class stays the runtime authority; this is the deterministic
// kernel with the contributor callbacks and failure recorder injectable.
import (
"errors"
"fmt"
"sort"
"sync"
)
// MemoryBlockNames — blocks that count as "memory" for the absence proof.
var MemoryBlockNames = map[string]bool{
"memory": true, "persona_memories": true, "consolidated": true,
}
// ErrContextOverflow — one block alone exceeds the input budget (fail loud).
var ErrContextOverflow = errors.New("context overflow")
// ContributionBlock — a typed, measurable piece of context. Content is
// either a string (system/user text) or []Message (history messages).
type ContributionBlock struct {
Name string
Priority int
Content interface{}
Tokens int
Source string
Evictable bool
}
// ToDict — serialize for pending_state embedding (P1-02 #25).
func (b *ContributionBlock) ToDict() map[string]interface{} {
return map[string]interface{}{
"name": b.Name,
"priority": b.Priority,
"content": b.Content,
"tokens": b.Tokens,
"source": b.Source,
"evictable": b.Evictable,
}
}
// ContributorFunc — callback(task, session) → block or nil.
type ContributorFunc func(task, session map[string]interface{}) *ContributionBlock
// FailureRecorder is the T1 record_failure seam (oracle: subsystem_health).
type FailureRecorder func(name string, err error)
// Contributor — a named registry entry with its priority bucket.
type Contributor struct {
Name string
Callback ContributorFunc
Bucket int
}
// ContextBroker — admission-controlled context assembler (Go kernel).
type ContextBroker struct {
mu sync.Mutex
contributors []Contributor
traces []map[string]interface{}
Estimator func(string) int
BudgetResolver func(int) int
Fail FailureRecorder
}
// NewContextBroker wires the defaults.
func NewContextBroker() *ContextBroker {
return &ContextBroker{
Estimator: DefaultEstimator,
BudgetResolver: DefaultBudget,
Fail: func(string, error) {},
}
}
// RegisterContributor names a contributor.
func (cb *ContextBroker) RegisterContributor(name string, callback ContributorFunc, bucket int) {
cb.mu.Lock()
defer cb.mu.Unlock()
cb.contributors = append(cb.contributors, Contributor{Name: name, Callback: callback, Bucket: bucket})
}
// TraceLog returns a shallow copy of every assemble trace (thread-safe).
func (cb *ContextBroker) TraceLog() []map[string]interface{} {
cb.mu.Lock()
defer cb.mu.Unlock()
out := make([]map[string]interface{}, len(cb.traces))
copy(out, cb.traces)
return out
}
// Collect runs contributors in registration order; failures are T1 events
// (one pass, lock held — side effects run exactly once per turn). Skips nil
// blocks and empty string content; malformed content (neither string nor a
// list of message dicts) is a T1 failure too (I5).
func (cb *ContextBroker) Collect(task, session map[string]interface{}) (collected []struct {
Name string
Block *ContributionBlock
Bucket int
}, degraded []string) {
cb.mu.Lock()
contributors := make([]Contributor, len(cb.contributors))
copy(contributors, cb.contributors)
cb.mu.Unlock()
degradedSet := map[string]bool{}
for _, c := range contributors {
block, err := func() (block *ContributionBlock, err error) {
defer func() {
if r := recover(); r != nil {
if e, ok := r.(error); ok {
err = e
} else {
err = fmt.Errorf("%v", r)
}
}
}()
return c.Callback(task, session), nil
}()
if err != nil {
cb.Fail(c.Name, err)
if !degradedSet[c.Name] {
degradedSet[c.Name] = true
degraded = append(degraded, c.Name)
}
continue
}
if block == nil {
continue
}
if s, isStr := block.Content.(string); isStr && s == "" {
continue
}
if !contentOK(block.Content) {
err := fmt.Errorf("contributor %q returned malformed content: %T", c.Name, block.Content)
cb.Fail(c.Name, err)
if !degradedSet[c.Name] {
degradedSet[c.Name] = true
degraded = append(degraded, c.Name)
}
continue
}
collected = append(collected, struct {
Name string
Block *ContributionBlock
Bucket int
}{c.Name, block, c.Bucket})
}
return collected, degraded
}
func contentOK(content interface{}) bool {
if s, isStr := content.(string); isStr {
return s != "" || true
}
list, isList := content.([]interface{})
if isList {
for _, item := range list {
m, isMap := item.(map[string]interface{})
if !isMap {
return false
}
if _, has := m["content"]; !has {
return false
}
}
return true
}
if _, isMsgs := content.([]Message); isMsgs {
return true
}
return false
}
// AdmissionEntry — one collected block paired with its registration name.
type AdmissionEntry struct {
Name string
Block *ContributionBlock
}
// TraceRecord — per-block admission trace row.
type TraceRecord struct {
Name string `json:"name"`
Priority int `json:"priority"`
Tokens int `json:"tokens"`
Source string `json:"source"`
Evictable bool `json:"evictable"`
Admitted bool `json:"admitted"`
Reason string `json:"reason"`
}
// AdmissionResult — admitted blocks + the full trace dict.
type AdmissionResult struct {
Admitted []*ContributionBlock
Blocks []TraceRecord
AdmittedTotal int
Dropped []TraceRecord
MemoryBudget int
MemoryUsed int
}
// Admit — deterministic admission: priority desc, tie: registration recency.
// Non-evictable blocks always admit first (overflow → ErrContextOverflow);
// evictable blocks admit while the budget lasts; memory-scope blocks are
// additionally bounded by the memory sub-budget (P1-03 #26).
func Admit(entries []AdmissionEntry, budget int, memoryBudget int) (*AdmissionResult, error) {
if memoryBudget < 0 {
memoryBudget = DefaultMemoryBudget(0)
}
type keyed struct {
idx int
entry AdmissionEntry
}
items := make([]keyed, len(entries))
for i, e := range entries {
items[i] = keyed{i, e}
}
sort.SliceStable(items, func(a, b int) bool {
ba, bb := items[a].entry.Block, items[b].entry.Block
ka, kb := 0, 0
if ba.Evictable {
ka = 1
}
if bb.Evictable {
kb = 1
}
if ka != kb {
return ka < kb
}
if ba.Priority != bb.Priority {
return ba.Priority > bb.Priority
}
return items[a].idx < items[b].idx
})
res := &AdmissionResult{MemoryBudget: memoryBudget}
used, memoryUsed := 0, 0
for _, it := range items {
block := it.entry.Block
record := TraceRecord{
Name: block.Name, Priority: block.Priority, Tokens: block.Tokens,
Source: block.Source, Evictable: block.Evictable,
Admitted: true,
}
if !block.Evictable {
if block.Tokens > budget {
return nil, fmt.Errorf("mandatory block '%s' needs %d tokens but the input budget is %d — refusing to silently truncate: %w",
block.Name, block.Tokens, budget, ErrContextOverflow)
}
res.Admitted = append(res.Admitted, block)
used += block.Tokens
res.Blocks = append(res.Blocks, record)
continue
}
inMemoryScope := MemoryBlockNames[block.Name]
if inMemoryScope && used+block.Tokens <= budget && memoryUsed+block.Tokens > memoryBudget {
record.Admitted = false
record.Reason = fmt.Sprintf("memory budget: %d+%d>%d (memory sub-budget exhausted; lowest-priority memory dropped)",
memoryUsed, block.Tokens, memoryBudget)
res.Blocks = append(res.Blocks, record)
continue
}
if used+block.Tokens <= budget {
res.Admitted = append(res.Admitted, block)
used += block.Tokens
if inMemoryScope {
memoryUsed += block.Tokens
}
res.Blocks = append(res.Blocks, record)
} else {
record.Admitted = false
record.Reason = fmt.Sprintf("budget: %d+%d>%d (lowest-priority evictable dropped)",
used, block.Tokens, budget)
res.Blocks = append(res.Blocks, record)
}
}
for _, record := range res.Blocks {
if !record.Admitted {
res.Dropped = append(res.Dropped, record)
}
}
res.AdmittedTotal = used
res.MemoryUsed = memoryUsed
return res, nil
}
// Capsule — the rendered attention capsule.
type Capsule struct {
TokenCount int
Blocks []*ContributionBlock
Messages []map[string]interface{}
SystemPreamble string
UserContent string
}
// Render — admitted blocks into the typed capsule (fixed message order:
// system -> tools -> memory/notes -> history -> user via priority sort).
func Render(admitted []*ContributionBlock) *Capsule {
type keyed struct {
idx int
block *ContributionBlock
}
items := make([]keyed, len(admitted))
for i, b := range admitted {
items[i] = keyed{i, b}
}
sort.SliceStable(items, func(a, b int) bool {
if items[a].block.Priority != items[b].block.Priority {
return items[a].block.Priority > items[b].block.Priority
}
return items[a].idx < items[b].idx
})
var systemParts []string
var history []map[string]interface{}
var userBlock *ContributionBlock
var capsuleBlocks []*ContributionBlock
totalTokens := 0
for _, it := range items {
block := it.block
totalTokens += block.Tokens
capsuleBlocks = append(capsuleBlocks, block)
if s, isStr := block.Content.(string); isStr {
if block.Name == "user" {
userBlock = block
} else {
systemParts = append(systemParts, s)
}
} else {
switch list := block.Content.(type) {
case []Message:
for _, m := range list {
history = append(history, deepCopyMap(m))
}
case []interface{}:
for _, item := range list {
if m, ok := item.(map[string]interface{}); ok {
history = append(history, deepCopyMap(m))
}
}
}
}
}
systemPrompt := ""
for i, p := range systemParts {
if i > 0 {
systemPrompt += "\n"
}
systemPrompt += p
}
rendered := make([]map[string]interface{}, 0, len(history)+1)
if systemPrompt != "" {
rendered = append(rendered, map[string]interface{}{"role": "system", "content": systemPrompt})
}
rendered = append(rendered, history...)
userContent := ""
if userBlock != nil {
userContent, _ = userBlock.Content.(string)
}
return &Capsule{
TokenCount: totalTokens,
Blocks: capsuleBlocks,
Messages: rendered,
SystemPreamble: systemPrompt,
UserContent: userContent,
}
}
func deepCopyMap(m map[string]interface{}) map[string]interface{} {
out := make(map[string]interface{}, len(m))
for k, v := range m {
if vm, ok := v.(map[string]interface{}); ok {
out[k] = deepCopyMap(vm)
} else if vl, ok := v.([]interface{}); ok {
out[k] = deepCopyList(vl)
} else {
out[k] = v
}
}
return out
}
func deepCopyList(l []interface{}) []interface{} {
out := make([]interface{}, len(l))
for i, v := range l {
if vm, ok := v.(map[string]interface{}); ok {
out[i] = deepCopyMap(vm)
} else if vl, ok := v.([]interface{}); ok {
out[i] = deepCopyList(vl)
} else {
out[i] = v
}
}
return out
}
func blockNames(blocks []*ContributionBlock) []string {
out := make([]string, 0, len(blocks))
for _, b := range blocks {
out = append(out, b.Name)
}
return out
}
// BuildMemorySelector — the memory-selection authority composition
// (_build_memory_selector, P1-03 #26). Fail-closed: any construction error
// returns nil and the memory contributor abstains — never a fallback
// injection. The selector itself stays Python (rune.memory_selector).
func BuildMemorySelector(ctor func() (interface{}, error)) interface{} {
sel, err := ctor()
if err != nil {
return nil
}
return sel
}
+708 -13
View File
@@ -1,20 +1,61 @@
// Package main — context-broker: the GO port of src/rune/context_broker.py's
// budget/estimator kernels (Oracle-Battery Parity Pattern, #2479 GO batch 38).
// budget/estimator kernels (Oracle-Battery Parity Pattern, #2479 GO batch 38;
// gap-close dichotomy wave — factory-gap-ctxbroker).
// The ContextBroker itself stays Python (it's the orchestration authority);
// the budget math is the deterministic core.
// the budget math is the deterministic core. The gap-close ports the missing
// functions name-preserving: the _py_* parity-floor oracles, the discovered-
// window registry, the rolling-summary store, _through_id, log_calibration,
// and the compact_history/summarize_history state machines with IO behind
// injectable seams (Estimator/Summarizer/Discoverer funcs, Archiver writer).
package main
import (
"crypto/sha256"
"encoding/json"
"fmt"
"math"
"os"
"sort"
"strconv"
"strings"
"sync"
)
// defaultEstimator — chars/4 (code-point parity with Python len()).
func defaultEstimator(text string) int { return len([]rune(text)) / 4 }
// ── estimator / budget kernels ──────────────────────────────────────────────
// defaultBudget — window - max(2048, window/4) - window/20, floored at 1.
func defaultBudget(window int) int {
// DefaultEstimator — chars/4 (code-point parity with Python len()).
func DefaultEstimator(text string) int { return len([]rune(text)) / 4 }
// PyDefaultEstimator — the Python parity-floor oracle (_py_default_estimator).
// Python: len(text or "") // 4 — None coerces to "".
func PyDefaultEstimator(text *string) int {
if text == nil {
return 0
}
return len([]rune(*text)) / 4
}
// CcCore — the cognition-core availability seam (_cc_core). The Go port IS
// the core, so the Python-pin question collapses: RUNE_COGNITION_CORE=python
// pins the Python floor (the caller compares against it), anything else with
// the Go core linked is available. Env read is injectable via CcCoreEnv.
var CcCoreEnv = os.LookupEnv
// CcCore reports whether the native core would be used under the given env.
// Mirrors the oracle's branch shape: pin -> None; loader probe failure -> None.
func CcCore(lookup func(string) (string, bool), loaderAvailable bool) *bool {
if v, ok := lookup("RUNE_COGNITION_CORE"); ok && v == "python" {
return nil
}
if !loaderAvailable {
return nil
}
avail := true
return &avail
}
// DefaultBudget — window - max(2048, window/4) - window/20, floored at 1.
func DefaultBudget(window int) int {
reserve := window / 4
if reserve < 2048 {
reserve = 2048
@@ -26,10 +67,13 @@ func defaultBudget(window int) int {
return r
}
// PyDefaultBudget — the Python parity-floor oracle (_py_default_budget).
func PyDefaultBudget(window int) int { return DefaultBudget(window) }
const MemoryBudgetCap = 1200
// defaultMemoryBudget — min(cap, window/8); window<=0 → CAP.
func defaultMemoryBudget(window int) int {
// DefaultMemoryBudget — min(cap, window/8); window<=0 → CAP.
func DefaultMemoryBudget(window int) int {
if window <= 0 {
return MemoryBudgetCap
}
@@ -40,6 +84,536 @@ func defaultMemoryBudget(window int) int {
return r
}
// PyDefaultMemoryBudget — the Python parity-floor oracle
// (_py_default_memory_budget). Python: `if not window or window <= 0`.
func PyDefaultMemoryBudget(window int) int { return DefaultMemoryBudget(window) }
// PyMemoryDisabledValue — the Python parity-floor oracle over the RAW string
// (_py_memory_disabled_value): truthy unless in the falsy set.
func PyMemoryDisabledValue(value string) bool {
switch value {
case "", "0", "false", "no", "off":
return false
}
return true
}
// MemoryDisabled — the full gate (memory_disabled): Python cross-check ANDed
// with the native verdict when the core is live.
func MemoryDisabled(raw string, coreDisabled bool) bool {
gate := PyMemoryDisabledValue(raw)
return coreDisabled && gate
}
// ── discovered-window registry (reset_discovered_windows/register_window/
// default_window) — process-local registry, live discovery injectable ────
const FallbackWindow = 8192
var (
discoveredMu sync.Mutex
discovered = map[string]int{}
)
// DiscoverWindow is the injectable seam for live context-length discovery
// (the oracle imports rune.local_tool_runtime.context_length). nil = no
// discovery available (offline), which freezes the fallback branch.
var DiscoverWindow func(modelID string) int
// ResetDiscoveredWindows clears the discovered-window registry (I3).
func ResetDiscoveredWindows() {
discoveredMu.Lock()
defer discoveredMu.Unlock()
discovered = map[string]int{}
}
// RegisterWindow records a discovered context length for modelID (Task 2).
func RegisterWindow(modelID string, window int) {
discoveredMu.Lock()
defer discoveredMu.Unlock()
discovered[modelID] = window
}
// DefaultWindow resolves the model window via the registry, then live
// discovery, then the conservative fallback. The fallback (8192) never
// enters the cache (C3: a transient discovery failure must not poison the
// window for the process lifetime).
func DefaultWindow(modelID string) int {
discoveredMu.Lock()
d := DiscoverWindow
discoveredMu.Unlock()
return resolveWindow(modelID, d)
}
// resolveWindow — the discovery-injectable core of DefaultWindow; the
// discover func is captured per call so concurrent resolvers never swap a
// shared global.
func resolveWindow(modelID string, discover func(string) int) int {
if modelID == "" {
return FallbackWindow
}
discoveredMu.Lock()
w, ok := discovered[modelID]
discoveredMu.Unlock()
if ok {
return w
}
if discover == nil {
return FallbackWindow
}
window := discover(modelID)
if window != 0 && window != FallbackWindow {
RegisterWindow(modelID, window)
}
return window
}
// ── rolling-summary store (C1/#141) ─────────────────────────────────────────
// RollingState — the persisted rolling summary for one session key.
type RollingState struct {
Summary string `json:"summary"`
ThroughCount int `json:"through_count"`
ThroughID string `json:"through_id"`
}
var (
rollingMu sync.Mutex
rolling = map[string]*RollingState{} // key: source + "\x00" + session_id
)
func rollingKey(source, sessionID string) string { return source + "\x00" + sessionID }
// RollingSummaryState returns the persisted rolling summary for a session
// (deep-copied), or nil.
func RollingSummaryState(source, sessionID string) *RollingState {
rollingMu.Lock()
defer rollingMu.Unlock()
st, ok := rolling[rollingKey(source, sessionID)]
if !ok {
return nil
}
cp := *st
return &cp
}
// StoreRollingSummary persists a rolling summary keyed by (source, sessionID).
func StoreRollingSummary(source, sessionID, summaryText string, throughCount int, throughID string) {
rollingMu.Lock()
defer rollingMu.Unlock()
rolling[rollingKey(source, sessionID)] = &RollingState{
Summary: summaryText,
ThroughCount: throughCount,
ThroughID: throughID,
}
}
// EvictRolling — LRU (insertion-order) eviction for dead sessions; returns
// the evicted count.
func EvictRolling(maxSessions int) int {
rollingMu.Lock()
defer rollingMu.Unlock()
if len(rolling) <= maxSessions {
return 0
}
keys := make([]string, 0, len(rolling))
for k := range rolling {
keys = append(keys, k)
}
sort.Strings(keys) // deterministic order (Python relies on dict insertion order)
drop := len(rolling) - maxSessions
for _, k := range keys[:drop] {
delete(rolling, k)
}
return drop
}
// ThroughID — identity of the coverage boundary: first 16 hex chars of the
// sha256 of the first message BEYOND coverage (content, not position —
// rotation-proof, #141). Empty when the boundary is outside the list.
func ThroughID(messages []map[string]interface{}, throughCount int) string {
if throughCount < 0 || throughCount >= len(messages) {
return ""
}
anchor := messages[throughCount]
sum := sha256.Sum256([]byte(pyDumpsSorted(anchor)))
return fmt.Sprintf("%x", sum[:])[:16]
}
// pyDumpsSorted renders json.dumps(anchor, sort_keys=True) byte-for-byte:
// separators ", " / ": ", ensure_ascii escaping, Python literal literals.
func pyDumpsSorted(v interface{}) string {
var b strings.Builder
writePyJSON(&b, v)
return b.String()
}
func writePyJSON(b *strings.Builder, v interface{}) {
switch val := v.(type) {
case nil:
b.WriteString("null")
case bool:
if val {
b.WriteString("true")
} else {
b.WriteString("false")
}
case float64:
b.WriteString(pyFloat(val))
case int:
b.WriteString(fmt.Sprintf("%d", val))
case string:
writePyString(b, val)
case []interface{}:
b.WriteString("[")
for i, item := range val {
if i > 0 {
b.WriteString(", ")
}
writePyJSON(b, item)
}
b.WriteString("]")
case map[string]interface{}:
keys := make([]string, 0, len(val))
for k := range val {
keys = append(keys, k)
}
sort.Strings(keys)
b.WriteString("{")
for i, k := range keys {
if i > 0 {
b.WriteString(", ")
}
writePyString(b, k)
b.WriteString(": ")
writePyJSON(b, val[k])
}
b.WriteString("}")
default:
blob, _ := json.Marshal(val)
b.Write(blob)
}
}
func pyFloat(f float64) string {
if f == float64(int64(f)) && math.Abs(f) < 1e15 {
// integral numbers via JSON can only originate as Python ints —
// json.dumps renders int(3) as "3", never "3.0"
return fmt.Sprintf("%d", int64(f))
}
return strconv.FormatFloat(f, 'g', -1, 64)
}
func writePyString(b *strings.Builder, s string) {
b.WriteByte('"')
for _, r := range s {
switch r {
case '"':
b.WriteString("\\\"")
case '\\':
b.WriteString("\\\\")
case '\n':
b.WriteString("\\n")
case '\r':
b.WriteString("\\r")
case '\t':
b.WriteString("\\t")
case '\b':
b.WriteString("\\b")
case '\f':
b.WriteString("\\f")
default:
if r < 0x20 || r > 0x7e {
if r > 0xffff {
r2 := r - 0x10000
hi := 0xd800 + (r2 >> 10)
lo := 0xdc00 + (r2 & 0x3ff)
fmt.Fprintf(b, "\\u%04x\\u%04x", hi, lo)
} else {
fmt.Fprintf(b, "\\u%04x", r)
}
} else {
b.WriteRune(r)
}
}
}
b.WriteByte('"')
}
// LogCalibration — compare the broker estimate with the served
// prompt_eval_count. LOG ONLY (drift never feeds admission). Returns the
// signed drift; 0 when nothing was served.
func LogCalibration(measuredTokens int, promptEvalCount *int, model string) int {
if promptEvalCount == nil || *promptEvalCount == 0 {
return 0
}
return *promptEvalCount - measuredTokens
}
// ── compaction state machine (compact_history) ──────────────────────────────
// Compaction thresholds (mirrors the oracle constants).
const (
CompactionThreshold = 0.8
CompactionKeepTurns = 5
CompactionNumPredict = 256
)
// Message — one conversation turn (loosely typed like the Python dict).
type Message = map[string]interface{}
// Archiver is the injectable seam for the raw-history archive append
// (oracle: <rune_home>/state/history_archive.jsonl). Returns an error to
// exercise the T2 archive-failure branch.
type Archiver func(record map[string]interface{}) error
// Summarizer is the injectable seam for the rolling-summary call
// (oracle: summarize_history via OllamaChatTransport). An error is a T2
// storage failure: compaction is skipped, the caller degrades.
type Summarizer func(messages []Message) (string, error)
// CompactEvent — the compaction event dict returned alongside the compacted
// messages.
type CompactEvent struct {
Triggered bool `json:"triggered"`
TokensBefore int `json:"tokens_before"`
TokensAfter int `json:"tokens_after"`
TurnsSummarized int `json:"turns_summarized"`
Archived bool `json:"archived"`
Failed bool `json:"failed"`
Source string `json:"source"`
Persisted bool `json:"persisted"`
DegradedToTail *int `json:"degraded_to_tail,omitempty"`
Summary string `json:"summary,omitempty"`
}
// CompactHistory — archive-then-compress state machine, logic 1:1 with the
// oracle (rolling persistence, #141 identity rematch, C2 tail degrade).
// source is fixed "operator" by the assemble path; sessionID "" disables
// persistence. archivePath is the injected archive location (the oracle's
// Path parameter): the label is embedded in the summary block; "" renders
// the oracle's 'external storage' fallback.
func CompactHistory(messages []Message, window int, archivePath string, archiver Archiver, summarizer Summarizer, source, sessionID string) ([]Message, *CompactEvent) {
tokensBefore := 0
for _, m := range messages {
tokensBefore += DefaultEstimator(messageContent(m))
}
event := &CompactEvent{
TokensBefore: tokensBefore,
TokensAfter: tokensBefore,
Source: source,
}
if len(messages) == 0 {
return messages, event
}
totalTurns := 0
for _, m := range messages {
if role, _ := m["role"].(string); role == "user" {
totalTurns++
}
}
if totalTurns <= CompactionKeepTurns {
return messages, event
}
keepMessages := CompactionKeepTurns * 2
split := len(messages) - keepMessages
if split < 0 {
split = 0
}
older, recent := messages[:split], messages[split:]
// C1 rolling persistence + #141 identity rematch.
sessionKey := sessionID != ""
var prior *RollingState
if sessionKey {
prior = RollingSummaryState(source, sessionID)
}
through := 0
if prior != nil {
through = prior.ThroughCount
if prior.ThroughID != "" {
var anchor Message
if 0 <= through && through < len(older) {
anchor = older[through]
}
if anchor != nil && ThroughID(older, through) != prior.ThroughID {
// window rotated under the stored count: rematch by content
found := -1
for i := range older {
if ThroughID(older, i) == prior.ThroughID {
found = i
break
}
}
if found >= 0 {
through = found
} else {
prior = nil
through = 0
}
}
}
}
var newOlder, archiveMessages []Message
if prior != nil && through <= len(older) {
newOlder = older[through:]
if len(newOlder) == 0 {
// Nothing new beyond the rolled summary: reuse it verbatim.
summaryBlock := Message{
"role": "user",
"content": "## Conversation summary (rolling)\n" + prior.Summary +
"\n(Earlier turns were archived; recent turns verbatim.)",
}
compacted := append([]Message{summaryBlock}, recent...)
event.Triggered = true
event.TokensAfter = sumTokens(compacted)
event.TurnsSummarized = 0
event.Summary = prior.Summary
event.Persisted = true
return compacted, event
}
archiveMessages = newOlder
} else {
newOlder = older
if prior == nil {
archiveMessages = messages
} else if through <= len(older) {
archiveMessages = older[through:]
} else {
archiveMessages = older
}
if prior != nil && through > len(older) {
// History shrank (ledger rotation): summarize from scratch.
prior = nil
through = 0
archiveMessages = messages
}
}
// ARCHIVE FIRST — raw, verbatim, before any lossy compression.
if archiver != nil {
record := map[string]interface{}{
"session_id": sessionID,
"source": source,
"messages": archiveMessages,
}
if err := archiver(record); err != nil {
event.Failed = true
return messages, event
}
event.Archived = true
}
var summaryText string
{
var input []Message
if prior != nil {
input = append([]Message{{"role": "user", "content": "Previous rolling summary:\n" + prior.Summary}}, newOlder...)
} else {
input = older
}
text, err := summarizer(input)
if err != nil {
event.Failed = true
// C2: degrade to the newest tail that fits the window budget.
tailBudget := DefaultBudget(window)
var tail []Message
used := 0
for i := len(messages) - 1; i >= 0; i-- {
message := messages[i]
cost := DefaultEstimator(messageContent(message))
if used+cost > tailBudget && len(tail) > 0 {
break
}
tail = append([]Message{message}, tail...)
used += cost
}
event.DegradedToTail = &[]int{len(tail)}[0]
return tail, event
}
summaryText = strings.TrimSpace(text)
}
archiveLabel := "external storage"
if archivePath != "" {
archiveLabel = archivePath
}
summaryBlock := Message{
"role": "user",
"content": "## Conversation summary (rolling)\n" + summaryText +
"\n(Earlier turns were archived to " + archiveLabel + "; " +
fmt.Sprintf("%d", len(older)) + " messages summarized, recent turns verbatim.)",
}
compacted := append([]Message{summaryBlock}, recent...)
if sessionKey {
newThrough := through + len(newOlder)
StoreRollingSummary(source, sessionID, summaryText, newThrough, ThroughID(messages, newThrough))
}
event.Triggered = true
event.TokensAfter = sumTokens(compacted)
event.TurnsSummarized = len(newOlder)
event.Summary = summaryText
event.Persisted = sessionKey
return compacted, event
}
func messageContent(m Message) string {
c, _ := m["content"].(string)
return c
}
func sumTokens(messages []Message) int {
total := 0
for _, m := range messages {
total += DefaultEstimator(messageContent(m))
}
return total
}
// ── summarize_history transport seam ────────────────────────────────────────
// SummarizeHistory builds the structured rolling-summary prompt and calls the
// injected transport. The transport (OllamaChatTransport in the oracle) is
// IO; here it is injectable. Empty/blank content raises (caller degrades).
func SummarizeHistory(messages []Message, model string, numPredict int, numCtx int, transport func(prompt, model string, options map[string]interface{}) (string, error)) (string, error) {
parts := make([]string, 0, len(messages))
for _, m := range messages {
role := "?"
if r, ok := m["role"].(string); ok && r != "" {
role = r
}
parts = append(parts, role+": "+messageContent(m))
}
transcript := strings.Join(parts, "\n")
prompt := "Summarize this conversation segment into a rolling summary.\n" +
"Answer with exactly these four labeled sections:\n" +
"Session intent: <what this session is trying to accomplish>\n" +
"Decisions: <choices made so far>\n" +
"Open items: <unresolved threads>\n" +
"Next steps: <what to do next>\n\n" +
"Conversation:\n" + transcript
options := map[string]interface{}{
"temperature": 0.2,
"num_predict": numPredict,
}
if numCtx != 0 {
options["num_ctx"] = numCtx
}
content, err := transport(prompt, model, options)
if err != nil {
return "", err
}
if strings.TrimSpace(content) == "" {
return "", fmt.Errorf("summarizer returned empty content")
}
return strings.TrimSpace(content), nil
}
// ── replay CLI (legacy truth.json flat-case driver, kept green) ─────────────
func main() {
if len(os.Args) < 2 || os.Args[1] != "replay" {
fmt.Fprintln(os.Stderr, "usage: context-broker replay <truth.json>")
@@ -52,8 +626,18 @@ func main() {
}
var cases []map[string]any
if err := json.Unmarshal(raw, &cases); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
// New-format truth doc ({frozen_ts, generator, rows}) — the legacy
// flat ops ride inside row["input"]["ops"] / scalar cases; nothing to
// replay here, the battery test covers them.
var doc struct {
Rows []json.RawMessage `json:"rows"`
}
if err2 := json.Unmarshal(raw, &doc); err2 != nil || len(doc.Rows) == 0 {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
fmt.Printf("PARITY OK: %d battery rows (go test battery replays them)\n", len(doc.Rows))
return
}
fails := 0
for i, c := range cases {
@@ -62,19 +646,19 @@ func main() {
switch op {
case "estimator":
text, _ := c["text"].(string)
if got := defaultEstimator(text); got != want {
if got := DefaultEstimator(text); got != want {
fails++
fmt.Printf("case %d estimator %q: got %d want %d\n", i, text, got, want)
}
case "budget":
w := int(c["window"].(float64))
if got := defaultBudget(w); got != want {
if got := DefaultBudget(w); got != want {
fails++
fmt.Printf("case %d budget %d: got %d want %d\n", i, w, got, want)
}
case "mem_budget":
w := int(c["window"].(float64))
if got := defaultMemoryBudget(w); got != want {
if got := DefaultMemoryBudget(w); got != want {
fails++
fmt.Printf("case %d mem_budget %d: got %d want %d\n", i, w, got, want)
}
@@ -86,3 +670,114 @@ func main() {
}
fmt.Printf("PARITY OK: %d cases vs the Python oracle\n", len(cases))
}
// ── eval CLI (live-parity harness: one JSON payload on stdin → JSON out) ────
func init() {
if len(os.Args) >= 2 && os.Args[1] == "eval" {
var payload map[string]interface{}
if err := json.NewDecoder(os.Stdin).Decode(&payload); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
out := evalOne(payload)
blob, _ := json.Marshal(out)
fmt.Println(string(blob))
os.Exit(0)
}
}
func evalOne(p map[string]interface{}) interface{} {
op, _ := p["op"].(string)
switch op {
case "est":
if t, ok := p["text"]; ok {
s, _ := t.(string)
return PyDefaultEstimator(&s)
}
return PyDefaultEstimator(nil)
case "budget":
return PyDefaultBudget(int(p["window"].(float64)))
case "membud":
return PyDefaultMemoryBudget(int(p["window"].(float64)))
case "memoff":
v, _ := p["value"].(string)
return PyMemoryDisabledValue(v)
case "through":
rawMsgs, _ := p["messages"].([]interface{})
msgs := make([]Message, 0, len(rawMsgs))
for _, rm := range rawMsgs {
m, _ := rm.(map[string]interface{})
msgs = append(msgs, m)
}
return ThroughID(msgs, int(p["through"].(float64)))
case "admit":
rawEntries, _ := p["entries"].([]interface{})
entries := make([]AdmissionEntry, 0, len(rawEntries))
for _, re := range rawEntries {
e, _ := re.(map[string]interface{})
b := &ContributionBlock{}
b.Name, _ = e["name"].(string)
b.Priority = int(e["priority"].(float64))
b.Tokens = int(e["tokens"].(float64))
b.Source, _ = e["source"].(string)
b.Evictable, _ = e["evictable"].(bool)
entries = append(entries, AdmissionEntry{Name: b.Name, Block: b})
}
budget := int(p["budget"].(float64))
memBudget := int(p["memory_budget"].(float64))
res, err := Admit(entries, budget, memBudget)
if err != nil {
return map[string]interface{}{"error": "ContextOverflowError", "detail": err.Error()}
}
blocksOut := make([]map[string]interface{}, 0, len(res.Blocks))
for _, r := range res.Blocks {
blocksOut = append(blocksOut, map[string]interface{}{
"name": r.Name, "priority": r.Priority, "tokens": r.Tokens,
"source": r.Source, "evictable": r.Evictable,
"admitted": r.Admitted, "reason": r.Reason,
})
}
dropped := make([]string, 0, len(res.Dropped))
for _, d := range res.Dropped {
dropped = append(dropped, d.Name)
}
return map[string]interface{}{
"admitted": blockNames(res.Admitted), "blocks": blocksOut,
"admitted_total": res.AdmittedTotal, "dropped": dropped,
"memory_budget": res.MemoryBudget, "memory_used": res.MemoryUsed,
}
case "render":
rawBlocks, _ := p["blocks"].([]interface{})
admitted := make([]*ContributionBlock, 0, len(rawBlocks))
for _, rb := range rawBlocks {
b, _ := rb.(map[string]interface{})
blk := &ContributionBlock{}
blk.Name, _ = b["name"].(string)
blk.Priority = int(b["priority"].(float64))
blk.Tokens = int(b["tokens"].(float64))
blk.Source, _ = b["source"].(string)
switch c := b["content"].(type) {
case string:
blk.Content = c
case []interface{}:
msgList := make([]Message, 0, len(c))
for _, rm := range c {
m, _ := rm.(map[string]interface{})
msgList = append(msgList, m)
}
blk.Content = msgList
}
admitted = append(admitted, blk)
}
capsule := Render(admitted)
return map[string]interface{}{
"token_count": capsule.TokenCount,
"messages": capsule.Messages,
"system_preamble": capsule.SystemPreamble,
"user_content": capsule.UserContent,
"blocks": blockNames(capsule.Blocks),
}
}
return nil
}
File diff suppressed because it is too large. Load diff
+559
View File
@@ -0,0 +1,559 @@
package main
// Battery — the HARD parity gate replaying the frozen oracle truth
// (truth.json, generated against the LIVE Python oracle
// src/rune/context_broker.py with RUNE_COGNITION_CORE=python pinned)
// VERBATIM. Inputs are deep-frozen snapshots (the oracle mutates message
// lists); every row byte-compares the Go result against the oracle's.
import (
"encoding/json"
"errors"
"fmt"
"os"
"reflect"
"sort"
"strings"
"testing"
)
type TruthDoc struct {
FrozenTS string `json:"frozen_ts"`
Generator string `json:"generator"`
Rows []TruthRow `json:"rows"`
}
type TruthRow struct {
Name string `json:"name"`
Fn string `json:"fn"`
Input map[string]interface{} `json:"input"`
Want interface{} `json:"want"`
}
func loadTruth(t *testing.T) *TruthDoc {
t.Helper()
raw, err := os.ReadFile("truth.json")
if err != nil {
t.Fatalf("truth.json missing (frozen oracle battery): %v", err)
}
var doc TruthDoc
if err := json.Unmarshal(raw, &doc); err != nil {
t.Fatalf("parse truth.json: %v", err)
}
if len(doc.Rows) == 0 {
t.Fatal("truth.json contained no rows")
}
seen := map[string]bool{}
for _, r := range doc.Rows {
if seen[r.Name] {
t.Fatalf("duplicate frozen row %q", r.Name)
}
seen[r.Name] = true
}
return &doc
}
func msgs(raw interface{}) []Message {
list, _ := raw.([]interface{})
out := make([]Message, 0, len(list))
for _, item := range list {
m, _ := item.(map[string]interface{})
out = append(out, m)
}
return out
}
func asInt(v interface{}) int {
switch n := v.(type) {
case float64:
return int(n)
case bool:
if n {
return 1
}
return 0
case string:
var i int
fmt.Sscanf(n, "%d", &i)
return i
}
return 0
}
func compEvent(e *CompactEvent) map[string]interface{} {
blob, _ := json.Marshal(e)
var out map[string]interface{}
_ = json.Unmarshal(blob, &out)
return out
}
// runOne — replay one frozen row; returns "" on parity, else the divergence.
func runOne(row *TruthRow) string {
switch row.Fn {
case "est":
var textPtr *string
if s, ok := row.Input["text"].(string); ok {
textPtr = &s
}
got := PyDefaultEstimator(textPtr)
want := asInt(row.Want)
if got != want {
return fmt.Sprintf("est: got %d want %d", got, want)
}
case "cc_core":
env, _ := row.Input["env"].(map[string]interface{})
lookup := func(string) (string, bool) { return "", false }
if v, ok := env["RUNE_COGNITION_CORE"]; ok {
s, _ := v.(string)
lookup = func(string) (string, bool) { return s, true }
}
loaderAvail, _ := row.Input["loader_available"].(bool)
got := CcCore(lookup, loaderAvail)
if row.Want == nil && got != nil {
return "cc_core: got core want none"
}
if row.Want != nil && got == nil {
return "cc_core: got none want core"
}
case "budget":
got := PyDefaultBudget(asInt(row.Input["window"]))
want := asInt(row.Want)
if got != want {
return fmt.Sprintf("budget: got %d want %d", got, want)
}
case "membud":
got := PyDefaultMemoryBudget(asInt(row.Input["window"]))
want := asInt(row.Want)
if got != want {
return fmt.Sprintf("membud: got %d want %d", got, want)
}
case "memoff":
v, _ := row.Input["value"].(string)
got := PyMemoryDisabledValue(v)
want, _ := row.Want.(bool)
if got != want {
return fmt.Sprintf("memoff %q: got %v want %v", v, got, want)
}
case "windows":
ResetDiscoveredWindows()
opsList, _ := row.Input["ops"].([]interface{})
for _, rawOp := range opsList {
op, _ := rawOp.(map[string]interface{})
switch op["op"] {
case "register_window":
RegisterWindow(op["model_id"].(string), asInt(op["window"]))
case "reset_discovered_windows":
ResetDiscoveredWindows()
case "default_window":
mid := ""
if s, ok := op["model_id"].(string); ok {
mid = s
}
// Live discovery is IO; the Go seam stays offline (nil) so
// the miss branch freezes to the fallback — exactly what the
// oracle froze in the offline generator env.
DiscoverWindow = nil
got := DefaultWindow(mid)
wantMap, _ := row.Want.(map[string]interface{})
want := asInt(wantMap["last"])
if got != want {
return fmt.Sprintf("default_window %q: got %d want %d", mid, got, want)
}
}
}
case "through":
got := ThroughID(msgs(row.Input["messages"]), asInt(row.Input["through"]))
want, _ := row.Want.(string)
if got != want {
return fmt.Sprintf("through: got %q want %q", got, want)
}
case "rstate":
ResetDiscoveredWindows()
for k := range rolling {
delete(rolling, k)
}
opsList, _ := row.Input["ops"].([]interface{})
var got *RollingState
for _, rawOp := range opsList {
op, _ := rawOp.(map[string]interface{})
source, _ := op["source"].(string)
sid, _ := op["sid"].(string)
switch op["op"] {
case "store":
summary, _ := op["summary"].(string)
tid, _ := op["tid"].(string)
StoreRollingSummary(source, sid, summary, asInt(op["through"]), tid)
case "read":
got = RollingSummaryState(source, sid)
}
}
if row.Want == nil {
if got != nil {
return "rstate: got state want nil"
}
} else {
wantMap, _ := row.Want.(map[string]interface{})
if got == nil {
return "rstate: got nil want state"
}
if got.Summary != wantMap["summary"] ||
got.ThroughCount != asInt(wantMap["through_count"]) ||
got.ThroughID != wantMap["through_id"] {
return fmt.Sprintf("rstate: got %+v want %v", got, wantMap)
}
}
case "rstore":
m := msgs(row.Input["msgs"])
StoreRollingSummary("operator", "s2", "S2", 1, ThroughID(m, 1))
got := RollingSummaryState("operator", "s2")
wantMap, _ := row.Want.(map[string]interface{})
if got == nil || got.Summary != wantMap["summary"] ||
got.ThroughCount != asInt(wantMap["through_count"]) ||
got.ThroughID != wantMap["through_id"] {
return fmt.Sprintf("rstore: got %+v want %v", got, wantMap)
}
case "summarize":
// The oracle froze the transport-failure branch (offline env): any
// error type is acceptable — the Go seam reproduces the failure
// shape (non-empty error, empty-content ValueError contract).
_, err := SummarizeHistory(msgs(row.Input["messages"]), "offline", 256, 0,
func(string, string, map[string]interface{}) (string, error) {
return "", fmt.Errorf("HTTP Error 404: Not Found")
})
wantMap, _ := row.Want.(map[string]interface{})
if _, isErr := wantMap["error"]; isErr && err == nil {
return "summarize: got success want error"
}
if err == nil {
return "summarize: expected frozen error branch"
}
case "compact":
window := asInt(row.Input["window"])
sid, _ := row.Input["session"].(string)
in := msgs(row.Input["messages"])
ResetDiscoveredWindows()
for k := range rolling {
delete(rolling, k)
}
var archiver Archiver
archivedMsgCount := -1
if _, wantsArchive := row.Input["archive"]; wantsArchive {
archiver = func(record map[string]interface{}) error {
archivedMsgCount = len(record["messages"].([]Message))
return nil
}
}
archivePath := ""
if _, wantsArchive := row.Input["archive"]; wantsArchive {
// deterministic relative fixture path frozen in the input
archivePath, _ = row.Input["archive_path"].(string)
}
summarizerText, _ := row.Input["summarizer"].(string)
var summarizer Summarizer
switch summarizerText {
case "boom":
summarizer = func([]Message) (string, error) {
return "", fmt.Errorf("summarizer down")
}
default:
summarizer = func(ms []Message) (string, error) {
parts := make([]string, 0, len(ms))
for _, m := range ms {
c := messageContent(m)
if len(c) > 10 {
c = c[:10]
}
parts = append(parts, c)
}
return "SUMMARY<" + strings.Join(parts, "|") + ">", nil
}
}
if rawPrior, ok := row.Input["prior"]; ok {
priorMap, _ := rawPrior.(map[string]interface{})
priorSid := sid
StoreRollingSummary("operator", priorSid,
priorMap["summary"].(string), asInt(priorMap["through_count"]),
priorMap["through_id"].(string))
}
compacted, ev := CompactHistory(in, window, archivePath, archiver, summarizer, "operator", sid)
wantMap, _ := row.Want.(map[string]interface{})
wantEvent, _ := wantMap["event"].(map[string]interface{})
if msg := diffEvent(wantEvent, compEvent(ev)); msg != "" {
return msg
}
if wantCompacted, ok := wantMap["compacted"]; ok {
wantMsgs := msgs(wantCompacted)
if !reflect.DeepEqual(normalize(compacted), normalize(wantMsgs)) {
gj, _ := json.Marshal(normalize(compacted))
wj, _ := json.Marshal(normalize(wantMsgs))
return fmt.Sprintf("compacted divergence:\n got %s\n want %s", gj, wj)
}
}
if _, wantsArchive := row.Input["archive"]; wantsArchive {
wantCount := asInt(wantMap["archived_msg_count"])
if archivedMsgCount != wantCount {
return fmt.Sprintf("archived_msg_count: got %d want %d", archivedMsgCount, wantCount)
}
}
default:
return "unknown fn key " + row.Fn
}
return ""
}
// normalize renders messages compactly for deep comparison (dicts with
// arbitrary key order).
func normalize(ms []Message) []map[string]interface{} {
out := make([]map[string]interface{}, 0, len(ms))
for _, m := range ms {
cp := map[string]interface{}{}
for k, v := range m {
cp[k] = v
}
out = append(out, cp)
}
return out
}
func diffEvent(want, got map[string]interface{}) string {
keys := make([]string, 0, len(want))
for k := range want {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
wv := fmt.Sprintf("%v", want[k])
gv := fmt.Sprintf("%v", got[k])
// Python bools render True/False; Go %v renders true/false.
wv = strings.NewReplacer("False", "false", "True", "true").Replace(wv)
if wv != gv {
return fmt.Sprintf("event.%s: got %s want %s", k, gv, wv)
}
}
return ""
}
// runBrokerOne — the ContextBroker-kernel rows (to_dict/collect/admit/render).
func runBrokerOne(row *TruthRow) string {
switch row.Fn {
case "to_dict":
name, _ := row.Want.(map[string]interface{})["name"].(string)
b := &ContributionBlock{}
switch name {
case "memory":
b = &ContributionBlock{Name: "memory", Priority: 5, Content: "hello world", Tokens: 2, Source: "recall", Evictable: true}
case "system":
b = &ContributionBlock{Name: "system", Priority: 100, Content: "sys", Tokens: 0, Source: "persona", Evictable: false}
}
want, _ := row.Want.(map[string]interface{})
got := b.ToDict()
for _, k := range []string{"name", "priority", "content", "tokens", "source", "evictable"} {
wv := fmt.Sprintf("%v", want[k])
wv = strings.NewReplacer("False", "false", "True", "true").Replace(wv)
if gv := fmt.Sprintf("%v", got[k]); gv != wv {
return fmt.Sprintf("to_dict.%s: got %v want %v", k, gv, wv)
}
}
case "collect":
cb := NewContextBroker()
var failures []string
cb.Fail = func(name string, err error) { failures = append(failures, name+": "+err.Error()) }
cb.RegisterContributor("ok", func(map[string]interface{}, map[string]interface{}) *ContributionBlock {
return &ContributionBlock{Name: "tools", Priority: 9, Content: "tooltext", Tokens: 2, Source: "t"}
}, 1)
cb.RegisterContributor("empty", func(map[string]interface{}, map[string]interface{}) *ContributionBlock {
return &ContributionBlock{Name: "x", Priority: 1, Content: "", Source: "s"}
}, 1)
cb.RegisterContributor("boom", func(map[string]interface{}, map[string]interface{}) *ContributionBlock {
panic(fmt.Errorf("cb down"))
}, 1)
cb.RegisterContributor("malformed", func(map[string]interface{}, map[string]interface{}) *ContributionBlock {
return &ContributionBlock{Name: "y", Priority: 1, Content: 12345, Source: "s"}
}, 1)
cb.RegisterContributor("list-ok", func(map[string]interface{}, map[string]interface{}) *ContributionBlock {
return &ContributionBlock{Name: "history", Priority: 3, Content: []Message{{"role": "user", "content": "hi"}}, Tokens: 1, Source: "h"}
}, 2)
cb.RegisterContributor("none", func(map[string]interface{}, map[string]interface{}) *ContributionBlock {
return nil
}, 1)
collected, degraded := cb.Collect(map[string]interface{}{}, map[string]interface{}{})
var names []string
for _, e := range collected {
names = append(names, e.Name)
}
want, _ := row.Want.(map[string]interface{})
wantNames := toStrSlice(want["names"])
wantDegraded := toStrSlice(want["degraded"])
if fmt.Sprint(names) != fmt.Sprint(wantNames) {
return fmt.Sprintf("collect.names: got %v want %v", names, wantNames)
}
if fmt.Sprint(degraded) != fmt.Sprint(wantDegraded) {
return fmt.Sprintf("collect.degraded: got %v want %v", degraded, wantDegraded)
}
// both failure kinds recorded (T1): boom + malformed
if len(failures) != 2 {
return fmt.Sprintf("collect.failures: got %d want 2 (%v)", len(failures), failures)
}
case "admit":
want, _ := row.Want.(map[string]interface{})
var entries []AdmissionEntry
var budget, memBudget int
switch row.Name {
case "admit:priority-recency-budget":
budget, memBudget = 1000, DefaultMemoryBudget(0)
entries = []AdmissionEntry{
{"system", &ContributionBlock{Name: "system", Priority: 100, Tokens: 500, Source: "src-system"}},
{"memory", &ContributionBlock{Name: "memory", Priority: 5, Tokens: 300, Source: "src-memory", Evictable: true}},
{"history", &ContributionBlock{Name: "history", Priority: 7, Tokens: 900, Source: "src-history", Evictable: true}},
{"user", &ContributionBlock{Name: "user", Priority: 100, Tokens: 100, Source: "src-user"}},
}
case "admit:memory-subbudget-drop":
budget, memBudget = 4000, 1200
entries = []AdmissionEntry{
{"m1", &ContributionBlock{Name: "m1", Priority: 9, Tokens: 700, Source: "src-m1", Evictable: true}},
{"m2", &ContributionBlock{Name: "m2", Priority: 8, Tokens: 700, Source: "src-m2", Evictable: true}},
}
case "admit:mandatory-overflow-raises":
budget, memBudget = 1000, DefaultMemoryBudget(0)
entries = []AdmissionEntry{
{"user", &ContributionBlock{Name: "user", Priority: 100, Tokens: 5000, Source: "src-user"}},
}
_, err := Admit(entries, budget, memBudget)
wantMap, _ := row.Want.(map[string]interface{})
if wantMap["error"] == "ContextOverflowError" {
if err == nil || !errors.Is(err, ErrContextOverflow) {
return "admit.overflow: no raise"
}
if !strings.Contains(err.Error(), wantMap["detail"].(string)) {
return fmt.Sprintf("admit.overflow detail: got %q", err.Error())
}
return ""
}
return "admit.overflow: frozen row unexpected shape"
}
res, err := Admit(entries, budget, memBudget)
if err != nil {
return "admit: unexpected error " + err.Error()
}
if got := fmt.Sprint(blockNames(res.Admitted)); got != fmt.Sprint(toStrSlice(want["admitted"])) {
return fmt.Sprintf("admit.admitted: got %v want %v", blockNames(res.Admitted), toStrSlice(want["admitted"]))
}
if wb, ok := want["blocks"]; ok {
wantBlocks, _ := wb.([]interface{})
if len(wantBlocks) != len(res.Blocks) {
return fmt.Sprintf("admit.blocks len: got %d want %d", len(res.Blocks), len(wantBlocks))
}
for i, wbItem := range wantBlocks {
wbMap, _ := wbItem.(map[string]interface{})
gb := res.Blocks[i]
for _, k := range []string{"name", "priority", "tokens", "source", "evictable", "admitted", "reason"} {
wv := fmt.Sprintf("%v", wbMap[k])
wv = strings.NewReplacer("False", "false", "True", "true").Replace(wv)
var gv string
switch k {
case "name":
gv = gb.Name
case "priority":
gv = fmt.Sprintf("%d", gb.Priority)
case "tokens":
gv = fmt.Sprintf("%d", gb.Tokens)
case "source":
gv = gb.Source
case "evictable":
gv = fmt.Sprintf("%v", gb.Evictable)
case "admitted":
gv = fmt.Sprintf("%v", gb.Admitted)
case "reason":
gv = gb.Reason
}
if gv != wv {
return fmt.Sprintf("admit.blocks[%d].%s: got %q want %q", i, k, gv, wv)
}
}
}
}
if want["admitted_total"] != nil && res.AdmittedTotal != asInt(want["admitted_total"]) {
return fmt.Sprintf("admit.admitted_total: got %d want %v", res.AdmittedTotal, want["admitted_total"])
}
if wd, ok := want["dropped"]; ok {
var gotDropped []string
for _, d := range res.Dropped {
gotDropped = append(gotDropped, d.Name)
}
if fmt.Sprint(gotDropped) != fmt.Sprint(toStrSlice(wd)) {
return fmt.Sprintf("admit.dropped: got %v want %v", gotDropped, toStrSlice(wd))
}
}
if want["memory_used"] != nil && res.MemoryUsed != asInt(want["memory_used"]) {
return fmt.Sprintf("admit.memory_used: got %d want %v", res.MemoryUsed, want["memory_used"])
}
if want["memory_budget"] != nil && res.MemoryBudget != asInt(want["memory_budget"]) {
return fmt.Sprintf("admit.memory_budget: got %d want %v", res.MemoryBudget, want["memory_budget"])
}
case "render":
want, _ := row.Want.(map[string]interface{})
admitted := []*ContributionBlock{
{Name: "persona", Priority: 100, Content: "ccccccc", Tokens: 7, Source: "src-persona"},
{Name: "tools", Priority: 90, Content: "ccccc", Tokens: 5, Source: "src-tools"},
{Name: "history", Priority: 70, Content: "cccc", Tokens: 4, Source: "src-history"},
{Name: "user", Priority: 100, Content: "cc", Tokens: 2, Source: "src-user"},
}
capsule := Render(admitted)
if capsule.TokenCount != asInt(want["token_count"]) {
return fmt.Sprintf("render.token_count: got %d want %v", capsule.TokenCount, want["token_count"])
}
if capsule.SystemPreamble != want["system_preamble"] {
return fmt.Sprintf("render.system_preamble: got %q want %q", capsule.SystemPreamble, want["system_preamble"])
}
if capsule.UserContent != want["user_content"] {
return fmt.Sprintf("render.user_content: got %q want %q", capsule.UserContent, want["user_content"])
}
wantMsgs := msgs(want["messages"])
if !reflect.DeepEqual(normalize(capsule.Messages), normalize(wantMsgs)) {
gj, _ := json.Marshal(capsule.Messages)
wj, _ := json.Marshal(wantMsgs)
return fmt.Sprintf("render.messages:\n got %s\n want %s", gj, wj)
}
if fmt.Sprint(blockNames(capsule.Blocks)) != fmt.Sprint(toStrSlice(want["blocks"])) {
return fmt.Sprintf("render.blocks: got %v want %v", blockNames(capsule.Blocks), toStrSlice(want["blocks"]))
}
default:
return "unknown broker fn key " + row.Fn
}
return ""
}
func toStrSlice(v interface{}) []string {
list, ok := v.([]interface{})
if !ok {
return nil
}
out := make([]string, 0, len(list))
for _, item := range list {
s, _ := item.(string)
out = append(out, s)
}
return out
}
func TestTruthBattery(t *testing.T) {
doc := loadTruth(t)
fails := 0
for i := range doc.Rows {
var msg string
switch doc.Rows[i].Fn {
case "to_dict", "collect", "admit", "render":
msg = runBrokerOne(&doc.Rows[i])
default:
msg = runOne(&doc.Rows[i])
}
if msg != "" {
fails++
t.Errorf("DIVERGED %s: %s", doc.Rows[i].Name, msg)
}
}
if fails > 0 {
t.Fatalf("battery: %d/%d divergences vs the frozen Python oracle", fails, len(doc.Rows))
}
t.Logf("PARITY OK: %d frozen rows replayed", len(doc.Rows))
}