2026-06-19 21:12:14 -05:00
|
|
|
#!/usr/bin/env python3
|
2026-07-21 13:35:51 -05:00
|
|
|
"""run_eval.py — current-version metrics for chorus-memory (v1.5+).
|
2026-07-03 13:30:01 -05:00
|
|
|
|
|
|
|
|
Replaces the historical 0.6-vs-0.7 A/B harness (see git history for that version).
|
|
|
|
|
Measures the four things the plugin now claims, against the deterministic mock
|
|
|
|
|
REST API (mock_olrapi.py) — no credentials, no live vault:
|
|
|
|
|
|
|
|
|
|
1. RETRIEVAL — hybrid recall (BM25 over entities + sessions/journal, fused with
|
|
|
|
|
freshness/status priors) vs a keyword-only baseline modeling the
|
|
|
|
|
pre-1.5 recall (per-word substring ranking over ENTITY notes only).
|
|
|
|
|
Metrics: precision@5, recall@5, MRR, session/journal queries
|
|
|
|
|
answered, freshness top-1 correctness.
|
|
|
|
|
2. DEDUP — duplicate notes created replaying a capture stream with the
|
2026-07-21 13:35:51 -05:00
|
|
|
pre-write gate ON (default) vs OFF (CHORUS_DUP_GATE=99, i.e. the
|
2026-07-03 13:30:01 -05:00
|
|
|
pre-1.5 warn-only behavior). Also counts legitimate captures
|
|
|
|
|
wrongly blocked (must be 0).
|
|
|
|
|
3. WRITE SAFETY— silent write failures (claimed success but ground truth wrong)
|
|
|
|
|
across fault injection: transient 503, phantom PUT, PATCH to a
|
|
|
|
|
missing heading, replayed append. Must be 0; failures must be LOUD.
|
|
|
|
|
4. DURABILITY — writes issued while the vault is unreachable must queue, replay
|
|
|
|
|
exactly once when it returns, and never duplicate on re-flush.
|
|
|
|
|
|
|
|
|
|
Ground truth is always read back independently via the mock's __debug__ endpoint.
|
|
|
|
|
Results print as a table and land in results/latest.json.
|
|
|
|
|
|
|
|
|
|
Usage: python3 run_eval.py [--port 8797]
|
2026-06-19 21:12:14 -05:00
|
|
|
"""
|
2026-07-03 13:30:01 -05:00
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
import argparse
|
|
|
|
|
import json
|
|
|
|
|
import os
|
|
|
|
|
import re
|
|
|
|
|
import subprocess
|
|
|
|
|
import sys
|
|
|
|
|
import tempfile
|
|
|
|
|
import time
|
|
|
|
|
import urllib.error
|
|
|
|
|
import urllib.request
|
2026-06-19 21:12:14 -05:00
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
HERE = Path(__file__).resolve().parent
|
2026-07-21 13:35:51 -05:00
|
|
|
SCRIPTS = HERE.parent / "chorus-memory.plugin.src" / "skills" / "chorus-memory" / "scripts"
|
|
|
|
|
CHORUS = SCRIPTS / "chorus.py"
|
2026-07-03 13:30:01 -05:00
|
|
|
SWEEP = SCRIPTS / "sweep.py"
|
|
|
|
|
KEY = "test-key-not-a-real-secret"
|
|
|
|
|
TODAY = "2026-07-03"
|
|
|
|
|
|
2026-06-19 21:12:14 -05:00
|
|
|
|
|
|
|
|
def http(method, url, body=None, headers=None):
|
|
|
|
|
data = body.encode() if isinstance(body, str) else body
|
|
|
|
|
req = urllib.request.Request(url, data=data, method=method,
|
|
|
|
|
headers={"Authorization": f"Bearer {KEY}", **(headers or {})})
|
|
|
|
|
try:
|
|
|
|
|
with urllib.request.urlopen(req, timeout=10) as r:
|
|
|
|
|
return r.status, r.read().decode("utf-8", "replace")
|
|
|
|
|
except urllib.error.HTTPError as e:
|
|
|
|
|
return e.code, e.read().decode("utf-8", "replace")
|
2026-07-03 13:30:01 -05:00
|
|
|
except Exception as e: # noqa: BLE001
|
2026-06-19 21:12:14 -05:00
|
|
|
return 0, str(e)
|
|
|
|
|
|
2026-07-03 13:30:01 -05:00
|
|
|
|
|
|
|
|
class Harness:
|
|
|
|
|
def __init__(self, base, state_dir):
|
|
|
|
|
self.base = base
|
|
|
|
|
self.state_dir = state_dir
|
|
|
|
|
|
|
|
|
|
def reset(self):
|
|
|
|
|
http("POST", f"{self.base}/__debug__reset")
|
|
|
|
|
|
|
|
|
|
def seed(self, path, content):
|
|
|
|
|
http("PUT", f"{self.base}/vault/{path}", content)
|
|
|
|
|
|
2026-06-19 21:12:14 -05:00
|
|
|
def ground(self, path):
|
2026-07-03 13:30:01 -05:00
|
|
|
_, body = http("GET", f"{self.base}/__debug__?path={path}")
|
2026-06-19 21:12:14 -05:00
|
|
|
return None if body == "<<MISSING>>" else body
|
|
|
|
|
|
2026-07-03 13:30:01 -05:00
|
|
|
def run(self, script, *args, env_extra=None, base=None):
|
2026-07-21 13:35:51 -05:00
|
|
|
env = dict(os.environ, CHORUS_BASE=base or self.base, CHORUS_KEY=KEY,
|
|
|
|
|
CHORUS_VERIFY="1", CHORUS_TODAY=TODAY, CHORUS_STATE_DIR=self.state_dir,
|
2026-07-03 13:30:01 -05:00
|
|
|
**(env_extra or {}))
|
|
|
|
|
return subprocess.run([sys.executable, str(script), *args],
|
|
|
|
|
capture_output=True, text=True, env=env)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def note(fm_type, title, body, status="active", created=TODAY, updated=TODAY, tags=None):
|
|
|
|
|
tags_s = "[" + ", ".join(tags or [fm_type]) + "]"
|
|
|
|
|
return (f"---\ntype: {fm_type}\nstatus: {status}\ncreated: {created}\n"
|
|
|
|
|
f"updated: {updated}\ntags: {tags_s}\nagent_written: true\nsource_notes: []\n---\n\n"
|
|
|
|
|
f"# {title}\n\n{body}\n\n## Related\n")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------- 1. RETRIEVAL
|
|
|
|
|
ENTITY_CORPUS = {
|
|
|
|
|
"resources/people/maria-lopez.md": note(
|
|
|
|
|
"person", "Maria Lopez", "Runs procurement at Vantage Systems. Prefers email over calls."),
|
|
|
|
|
"resources/companies/vantage-systems.md": note(
|
|
|
|
|
"company", "Vantage Systems", "Enterprise procurement platform vendor; the contract renews in Q3."),
|
|
|
|
|
"resources/concepts/bm25-ranking.md": note(
|
|
|
|
|
"concept", "BM25 Ranking", "Lexical retrieval scoring with term frequency saturation and document length normalization."),
|
|
|
|
|
"resources/concepts/graph-expansion.md": note(
|
|
|
|
|
"concept", "Graph Expansion", "Expanding a hit into its linked neighbourhood along Related links and source notes."),
|
|
|
|
|
"projects/active/website-redesign.md": note(
|
|
|
|
|
"project", "Website Redesign",
|
|
|
|
|
"Rebuild the marketing site. Navigation cleanup and new hero copy.",
|
|
|
|
|
status="active", created="2026-06-25", updated="2026-07-01"),
|
|
|
|
|
"projects/archived/old-website.md": note(
|
|
|
|
|
"project", "Old Website",
|
|
|
|
|
"Legacy marketing site. Navigation used nested dropdown menus. Navigation was the top complaint.",
|
|
|
|
|
status="archived", created="2024-03-01", updated="2024-08-10"),
|
|
|
|
|
"_agent/memory/semantic/tooling-preferences.md": note(
|
|
|
|
|
"semantic-memory", "Tooling Preferences",
|
|
|
|
|
"The operator prefers uv over pip for Python tooling, and ruff for linting."),
|
|
|
|
|
}
|
|
|
|
|
TIME_SERIES_CORPUS = {
|
|
|
|
|
"_agent/sessions/2026-06-20-1000-vantage-contract-review.md": note(
|
|
|
|
|
"session-log", "Vantage contract review",
|
|
|
|
|
"Reviewed the Vantage contract. Decided to renegotiate the SLA penalty clause before renewal.",
|
|
|
|
|
status="complete", created="2026-06-20", updated="2026-06-20", tags=["session-log"]),
|
|
|
|
|
"journal/daily/2026-06-20.md": (
|
|
|
|
|
"---\ntype: daily-note\ncreated: 2026-06-20\nupdated: 2026-06-20\ntags: [daily]\n---\n\n"
|
|
|
|
|
"# 2026-06-20\n\n## Agent Log\n- 2026-06-20: renegotiation kickoff email sent to Vantage\n"),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# (query, gold paths, only answerable from sessions/journal)
|
|
|
|
|
QUERIES = [
|
|
|
|
|
("uv or pip for python tooling", {"_agent/memory/semantic/tooling-preferences.md"}, False),
|
|
|
|
|
("procurement contact", {"resources/people/maria-lopez.md",
|
|
|
|
|
"resources/companies/vantage-systems.md"}, False),
|
|
|
|
|
("SLA penalty clause decision",
|
|
|
|
|
{"_agent/sessions/2026-06-20-1000-vantage-contract-review.md"}, True),
|
|
|
|
|
("marketing site navigation", {"projects/active/website-redesign.md",
|
|
|
|
|
"projects/archived/old-website.md"}, False),
|
|
|
|
|
("term frequency saturation", {"resources/concepts/bm25-ranking.md"}, False),
|
|
|
|
|
("contract renewal Q3", {"resources/companies/vantage-systems.md"}, False),
|
|
|
|
|
("renegotiation kickoff", {"journal/daily/2026-06-20.md"}, True),
|
|
|
|
|
("graph neighbourhood expansion", {"resources/concepts/graph-expansion.md"}, False),
|
|
|
|
|
]
|
|
|
|
|
FRESHNESS_QUERY = "marketing site navigation"
|
|
|
|
|
FRESHNESS_TOP1 = "projects/active/website-redesign.md"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def keyword_baseline(query, corpus, k=5):
|
|
|
|
|
"""Models pre-1.5 keyword recall: per-word substring counting over ENTITY note
|
|
|
|
|
bodies only (sessions/journal were not in the corpus before 1.5)."""
|
|
|
|
|
words = [w for w in re.findall(r"[a-z0-9]+", query.lower()) if len(w) > 2]
|
|
|
|
|
scored = []
|
|
|
|
|
for path, text in corpus.items():
|
|
|
|
|
body = text.lower()
|
|
|
|
|
s = sum(body.count(w) for w in words)
|
|
|
|
|
if s > 0:
|
|
|
|
|
scored.append((s, path))
|
|
|
|
|
scored.sort(key=lambda x: (-x[0], x[1]))
|
|
|
|
|
return [p for _, p in scored[:k]]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def metrics_for(ranked_by_query):
|
|
|
|
|
p_sum = r_sum = mrr_sum = 0.0
|
|
|
|
|
ts_answered = ts_total = 0
|
|
|
|
|
for (q, gold, ts_only), ranked in ranked_by_query:
|
|
|
|
|
top = ranked[:5]
|
|
|
|
|
hits = [p for p in top if p in gold]
|
|
|
|
|
p_sum += len(hits) / 5
|
|
|
|
|
r_sum += len(hits) / len(gold)
|
|
|
|
|
rr = 0.0
|
|
|
|
|
for i, p in enumerate(top):
|
|
|
|
|
if p in gold:
|
|
|
|
|
rr = 1.0 / (i + 1)
|
|
|
|
|
break
|
|
|
|
|
mrr_sum += rr
|
|
|
|
|
if ts_only:
|
|
|
|
|
ts_total += 1
|
|
|
|
|
ts_answered += int(bool(hits))
|
|
|
|
|
n = len(ranked_by_query)
|
|
|
|
|
return {"precision_at_5": round(p_sum / n, 3), "recall_at_5": round(r_sum / n, 3),
|
|
|
|
|
"mrr": round(mrr_sum / n, 3),
|
|
|
|
|
"session_journal_queries_answered": f"{ts_answered}/{ts_total}"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def eval_retrieval(h):
|
|
|
|
|
h.reset()
|
2026-07-21 13:35:51 -05:00
|
|
|
h.seed("_agent/chorus-vault.md", "---\nschema_version: 4\n---\n# marker\n")
|
2026-07-03 13:30:01 -05:00
|
|
|
for path, text in {**ENTITY_CORPUS, **TIME_SERIES_CORPUS}.items():
|
|
|
|
|
h.seed(path, text)
|
|
|
|
|
r = h.run(SWEEP, "--apply")
|
|
|
|
|
assert r.returncode == 0, f"sweep failed: {r.stdout}{r.stderr}"
|
|
|
|
|
|
|
|
|
|
current_ranked, baseline_ranked = [], []
|
|
|
|
|
fresh_top1_current = fresh_top1_baseline = None
|
|
|
|
|
for q, gold, ts_only in QUERIES:
|
2026-07-21 13:35:51 -05:00
|
|
|
r = h.run(CHORUS, "recall", q, "--json", "--limit", "5")
|
2026-07-03 13:30:01 -05:00
|
|
|
try:
|
|
|
|
|
primary = [hit["path"] for hit in json.loads(r.stdout).get("primary", [])]
|
|
|
|
|
except Exception: # noqa: BLE001
|
|
|
|
|
primary = []
|
|
|
|
|
current_ranked.append(((q, gold, ts_only), primary))
|
|
|
|
|
base = keyword_baseline(q, ENTITY_CORPUS)
|
|
|
|
|
baseline_ranked.append(((q, gold, ts_only), base))
|
|
|
|
|
if q == FRESHNESS_QUERY:
|
|
|
|
|
fresh_top1_current = primary[0] if primary else None
|
|
|
|
|
fresh_top1_baseline = base[0] if base else None
|
|
|
|
|
|
|
|
|
|
cur = metrics_for(current_ranked)
|
|
|
|
|
cur["freshness_top1_correct"] = fresh_top1_current == FRESHNESS_TOP1
|
|
|
|
|
bas = metrics_for(baseline_ranked)
|
|
|
|
|
bas["freshness_top1_correct"] = fresh_top1_baseline == FRESHNESS_TOP1
|
|
|
|
|
return {"current (hybrid+priors, full corpus)": cur,
|
|
|
|
|
"baseline (keyword, entities only)": bas}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ------------------------------------------------------------------- 2. DEDUP
|
|
|
|
|
# Attempts referring to an EXISTING entity under another name; a create == duplicate.
|
|
|
|
|
DUP_ATTEMPTS = [
|
|
|
|
|
("Robert Smith", "person", "resources/people/robert-smith.md"),
|
2026-07-21 13:35:51 -05:00
|
|
|
("Chorus Memory", "project", "projects/active/chorus-memory.md"),
|
2026-07-03 13:30:01 -05:00
|
|
|
("Acme Consulting", "company", "resources/companies/acme-consulting.md"),
|
|
|
|
|
]
|
|
|
|
|
# Legitimate NEW entities; a block == false positive (must create in both modes).
|
|
|
|
|
LEGIT_ATTEMPTS = [
|
2026-07-21 13:35:51 -05:00
|
|
|
("Chorus Review Meeting", "meeting",
|
|
|
|
|
f"resources/meetings/{TODAY}-chorus-review-meeting.md"), # cross-kind lookalike
|
2026-07-03 13:30:01 -05:00
|
|
|
("Zephyr", "concept", "resources/concepts/zephyr.md"), # unrelated
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _dedup_pass(h, gate_env):
|
|
|
|
|
h.reset()
|
2026-07-21 13:35:51 -05:00
|
|
|
h.seed("_agent/chorus-vault.md", "---\nschema_version: 4\n---\n# marker\n")
|
|
|
|
|
for title, kind in [("Bob Smith", "person"), ("Chorus", "project"), ("Acme", "company")]:
|
|
|
|
|
h.run(CHORUS, "capture", title, "--kind", kind, env_extra=gate_env)
|
2026-07-03 13:30:01 -05:00
|
|
|
dupes = blocked_legit = 0
|
|
|
|
|
for title, kind, would_be in DUP_ATTEMPTS:
|
2026-07-21 13:35:51 -05:00
|
|
|
h.run(CHORUS, "capture", title, "--kind", kind, env_extra=gate_env)
|
2026-07-03 13:30:01 -05:00
|
|
|
dupes += int(h.ground(would_be) is not None)
|
|
|
|
|
for title, kind, path in LEGIT_ATTEMPTS:
|
2026-07-21 13:35:51 -05:00
|
|
|
h.run(CHORUS, "capture", title, "--kind", kind, env_extra=gate_env)
|
2026-07-03 13:30:01 -05:00
|
|
|
blocked_legit += int(h.ground(path) is None)
|
|
|
|
|
return {"duplicate_notes_created": dupes, "legitimate_captures_blocked": blocked_legit,
|
|
|
|
|
"referring_attempts": len(DUP_ATTEMPTS), "legit_attempts": len(LEGIT_ATTEMPTS)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def eval_dedup(h):
|
|
|
|
|
return {"gate ON (v1.5.1 default)": _dedup_pass(h, {}),
|
2026-07-21 13:35:51 -05:00
|
|
|
"gate OFF (pre-1.5, warn-only)": _dedup_pass(h, {"CHORUS_DUP_GATE": "99"})}
|
2026-07-03 13:30:01 -05:00
|
|
|
|
|
|
|
|
|
|
|
|
|
# ------------------------------------------------------------- 3. WRITE SAFETY
|
|
|
|
|
def eval_write_safety(h):
|
|
|
|
|
h.reset()
|
2026-07-21 13:35:51 -05:00
|
|
|
h.seed("_agent/chorus-vault.md", "---\nschema_version: 4\n---\n# marker\n")
|
2026-07-03 13:30:01 -05:00
|
|
|
silent = loud = 0
|
|
|
|
|
ops = 0
|
|
|
|
|
|
|
|
|
|
def bodyfile(text):
|
|
|
|
|
f = tempfile.NamedTemporaryFile("w", suffix=".md", delete=False, encoding="utf-8")
|
|
|
|
|
f.write(text)
|
|
|
|
|
f.close()
|
|
|
|
|
return f.name
|
|
|
|
|
|
|
|
|
|
# transient 503 on first write ("flaky" marker) -> client must retry and land it
|
|
|
|
|
ops += 1
|
2026-07-21 13:35:51 -05:00
|
|
|
r = h.run(CHORUS, "put", "resources/concepts/flaky-note.md", bodyfile("# Flaky\nok\n"))
|
2026-07-03 13:30:01 -05:00
|
|
|
landed = h.ground("resources/concepts/flaky-note.md") is not None
|
|
|
|
|
if r.returncode == 0 and not landed:
|
|
|
|
|
silent += 1
|
|
|
|
|
if r.returncode != 0:
|
|
|
|
|
loud += 1
|
|
|
|
|
|
|
|
|
|
# phantom PUT ("phantom" marker: 200 but not persisted) -> verify must fail LOUD
|
|
|
|
|
ops += 1
|
2026-07-21 13:35:51 -05:00
|
|
|
r = h.run(CHORUS, "put", "resources/concepts/phantom-note.md", bodyfile("# Phantom\n"))
|
2026-07-03 13:30:01 -05:00
|
|
|
landed = h.ground("resources/concepts/phantom-note.md") is not None
|
|
|
|
|
if r.returncode == 0 and not landed:
|
|
|
|
|
silent += 1
|
|
|
|
|
if r.returncode != 0 and not landed:
|
|
|
|
|
loud += 1
|
|
|
|
|
|
|
|
|
|
# PATCH to a missing heading -> 400 must be surfaced, nothing written
|
|
|
|
|
ops += 1
|
|
|
|
|
h.seed("resources/concepts/target.md", "# Target\n\n## Real\nx\n")
|
2026-07-21 13:35:51 -05:00
|
|
|
r = h.run(CHORUS, "patch", "resources/concepts/target.md", "append", "heading",
|
2026-07-03 13:30:01 -05:00
|
|
|
"Target::Nope", bodyfile("lost?"))
|
|
|
|
|
if r.returncode == 0 and "lost?" not in (h.ground("resources/concepts/target.md") or ""):
|
|
|
|
|
silent += 1
|
|
|
|
|
if r.returncode != 0:
|
|
|
|
|
loud += 1
|
|
|
|
|
|
|
|
|
|
# replayed append -> whole-line idempotency, no duplicate
|
|
|
|
|
ops += 1
|
2026-07-21 13:35:51 -05:00
|
|
|
h.run(CHORUS, "append", "inbox/captures/inbox.md", f"- {TODAY}: replay me")
|
|
|
|
|
h.run(CHORUS, "append", "inbox/captures/inbox.md", f"- {TODAY}: replay me")
|
2026-07-03 13:30:01 -05:00
|
|
|
n = (h.ground("inbox/captures/inbox.md") or "").count("replay me")
|
|
|
|
|
if n != 1:
|
|
|
|
|
silent += 1
|
|
|
|
|
|
|
|
|
|
return {"fault_scenarios": ops, "silent_write_failures": silent,
|
|
|
|
|
"failures_surfaced_loud": loud}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# --------------------------------------------------------------- 4. DURABILITY
|
|
|
|
|
def eval_durability(h, dead_base):
|
|
|
|
|
h.reset()
|
2026-07-21 13:35:51 -05:00
|
|
|
h.seed("_agent/chorus-vault.md", "---\nschema_version: 4\n---\n# marker\n")
|
2026-07-03 13:30:01 -05:00
|
|
|
queued_ok = 0
|
|
|
|
|
for i in range(3):
|
2026-07-21 13:35:51 -05:00
|
|
|
r = h.run(CHORUS, "append", "inbox/captures/inbox.md",
|
2026-07-03 13:30:01 -05:00
|
|
|
f"- {TODAY}: offline capture {i}", base=dead_base)
|
|
|
|
|
queued_ok += int(r.returncode == 0 and "queued" in r.stdout)
|
|
|
|
|
# vault returns: flush replays; a second flush must not duplicate
|
2026-07-21 13:35:51 -05:00
|
|
|
h.run(CHORUS, "flush")
|
|
|
|
|
h.run(CHORUS, "flush")
|
2026-07-03 13:30:01 -05:00
|
|
|
inbox = h.ground("inbox/captures/inbox.md") or ""
|
|
|
|
|
landed = sum(1 for i in range(3) if f"offline capture {i}" in inbox)
|
|
|
|
|
dupes = sum(inbox.count(f"offline capture {i}") - 1
|
|
|
|
|
for i in range(3) if f"offline capture {i}" in inbox)
|
|
|
|
|
return {"writes_during_outage": 3, "reported_queued": queued_ok,
|
|
|
|
|
"landed_after_flush": landed, "lost": 3 - landed, "duplicated_on_reflush": dupes}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ------------------------------------------------------------------------ main
|
2026-06-19 21:12:14 -05:00
|
|
|
def main():
|
|
|
|
|
ap = argparse.ArgumentParser()
|
2026-07-03 13:30:01 -05:00
|
|
|
ap.add_argument("--port", type=int, default=8797)
|
2026-06-19 21:12:14 -05:00
|
|
|
a = ap.parse_args()
|
|
|
|
|
base = f"http://127.0.0.1:{a.port}"
|
|
|
|
|
srv = subprocess.Popen([sys.executable, str(HERE / "mock_olrapi.py"), "--port", str(a.port)],
|
|
|
|
|
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
|
2026-07-21 13:35:51 -05:00
|
|
|
state = tempfile.mkdtemp(prefix="chorus-eval-state-")
|
2026-06-19 21:12:14 -05:00
|
|
|
try:
|
2026-07-03 13:30:01 -05:00
|
|
|
for _ in range(50):
|
2026-06-19 21:12:14 -05:00
|
|
|
try:
|
2026-07-03 13:30:01 -05:00
|
|
|
urllib.request.urlopen(f"{base}/__debug__reset", data=b"", timeout=1)
|
|
|
|
|
break
|
|
|
|
|
except Exception: # noqa: BLE001
|
|
|
|
|
time.sleep(0.1)
|
|
|
|
|
h = Harness(base, state)
|
|
|
|
|
results = {
|
|
|
|
|
"version": "1.5.1", "date": TODAY,
|
|
|
|
|
"retrieval": eval_retrieval(h),
|
|
|
|
|
"dedup": eval_dedup(h),
|
|
|
|
|
"write_safety": eval_write_safety(h),
|
|
|
|
|
"durability": eval_durability(h, dead_base=f"http://127.0.0.1:{a.port + 1}"),
|
|
|
|
|
}
|
|
|
|
|
out = HERE / "results" / "latest.json"
|
|
|
|
|
out.parent.mkdir(exist_ok=True)
|
|
|
|
|
out.write_text(json.dumps(results, indent=2), encoding="utf-8")
|
|
|
|
|
|
2026-07-21 13:35:51 -05:00
|
|
|
print(f"\n== chorus-memory eval — v{results['version']} ({results['date']}) ==\n")
|
2026-07-03 13:30:01 -05:00
|
|
|
for section, data in results.items():
|
|
|
|
|
if section in ("version", "date"):
|
|
|
|
|
continue
|
|
|
|
|
print(f"## {section}")
|
|
|
|
|
for k, v in data.items():
|
|
|
|
|
if isinstance(v, dict):
|
|
|
|
|
print(f" {k}:")
|
|
|
|
|
for k2, v2 in v.items():
|
|
|
|
|
print(f" {k2:38} {v2}")
|
|
|
|
|
else:
|
|
|
|
|
print(f" {k:40} {v}")
|
|
|
|
|
print()
|
|
|
|
|
print(f"results -> {out}")
|
|
|
|
|
return 0
|
2026-06-19 21:12:14 -05:00
|
|
|
finally:
|
|
|
|
|
srv.terminate()
|
|
|
|
|
|
2026-07-03 13:30:01 -05:00
|
|
|
|
2026-06-19 21:12:14 -05:00
|
|
|
if __name__ == "__main__":
|
2026-07-03 13:30:01 -05:00
|
|
|
raise SystemExit(main())
|