mirror of
https://github.com/samvallad33/vestige.git
synced 2026-07-26 23:51:02 +02:00
Three agents fix one failing test concurrently. The fix needs a signing key id that is randomized per trial from a 50-key keyring and appears in no file the agents can read. It exists only in the memory layer. Seven arms: a no-memory control, dense cosine RAG, Vestige, mem0, supermemory, hindsight, Zep/Graphiti. The measured result is about the first retrieval, not the final score. Across 5 models and 22 trials, a query-based arm's FIRST memory call surfaced the correct key 6 times out of 87. Vestige, which takes no query argument, did it 56 out of 56. Every downstream number follows from that. Ships the raw evidence rather than a summary: all agent transcripts, the harness, and tests/bm25_baseline.py, which reproduces the ranking table offline with no API key. Two arms were contaminated by a flush bug in this harness that served them a previous trial's facts; both were fixed and re-run, and the contaminated cells are withheld rather than published. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
202 lines
10 KiB
Python
Executable file
202 lines
10 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
"""Aggregate an N-trial Silent Rotation experiment into a statistical result.
|
|
|
|
Reads experiment-<seed>-<N>/trial-*/{anarchy,sync}.json and prints:
|
|
- a per-trial table (correct key, anarchy verdict, sync verdict, backfill use)
|
|
- aggregate: anarchy success X/N, sync success Y/N
|
|
- a one-sided p-value for "sync succeeds more than anarchy" (exact, stdlib only)
|
|
- the honest headline a senior engineer reads
|
|
|
|
"Success" = fleet_verdict == 'fixed_correctly' (green AND production-safe AND
|
|
right key). Anything else (merge conflict, still-red, green_but_voids_prod) is a
|
|
failure. This is the outcome that matters: shipped a correct, production-safe fix.
|
|
"""
|
|
import json
|
|
import math
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
|
|
def is_success(v: dict) -> bool:
|
|
return v.get("fleet_verdict") == "fixed_correctly"
|
|
|
|
|
|
def binom_p_at_least(k: int, n: int, p: float) -> float:
|
|
"""P(X >= k) for X ~ Binomial(n, p). Exact via stdlib."""
|
|
if k <= 0:
|
|
return 1.0
|
|
total = 0.0
|
|
for i in range(k, n + 1):
|
|
total += math.comb(n, i) * (p ** i) * ((1 - p) ** (n - i))
|
|
return total
|
|
|
|
|
|
def main():
|
|
expdir = Path(sys.argv[1])
|
|
n = int(sys.argv[2])
|
|
# Which arms to aggregate. Defaults to the 3-arm set; a 2-arm run (anarchy
|
|
# sync) still works because a missing rag.json is tolerated per trial.
|
|
import os as _os
|
|
arms = _os.environ.get("ARMS", "anarchy rag sync").split()
|
|
rows = []
|
|
succ = {arm: 0 for arm in arms}
|
|
tot_tok = {arm: 0 for arm in arms}
|
|
tot_cost = {arm: 0.0 for arm in arms}
|
|
# Per-arm MEMORY-LAYER HEALTH. Without this, an arm whose backend never
|
|
# answered renders as a plain "0/5" -- visually identical to a real
|
|
# retrieval loss. That is exactly how three dead competitor arms
|
|
# (hindsight 19/19 errors, zep 22/22, mem0 14/15) were published as
|
|
# substantive defeats in earlier runs. A trial only counts as a real
|
|
# measurement if that arm's memory layer actually answered.
|
|
dead_trials = {arm: 0 for arm in arms} # memory layer never answered
|
|
live_trials = {arm: 0 for arm in arms} # memory layer answered at least once
|
|
no_data_trials = {arm: 0 for arm in arms} # arm produced no result JSON at all
|
|
for t in range(1, n + 1):
|
|
td = expdir / f"trial-{t}"
|
|
try:
|
|
man = json.load(open(td / "manifest.json"))
|
|
except (FileNotFoundError, json.JSONDecodeError):
|
|
rows.append((t, "?", {arm: "MISSING" for arm in arms}, "?"))
|
|
continue
|
|
verdicts = {}
|
|
for arm in arms:
|
|
try:
|
|
d = json.load(open(td / f"{arm}.json"))
|
|
except (FileNotFoundError, json.JSONDecodeError):
|
|
# No JSON at all: the arm never produced a result (crashed before
|
|
# writing, or the run was stopped before reaching it). Either way
|
|
# this trial is NOT a measurement of that product.
|
|
verdicts[arm] = "MISSING"
|
|
if arm != "anarchy":
|
|
no_data_trials[arm] += 1
|
|
continue
|
|
verdicts[arm] = d.get("fleet_verdict", "?")
|
|
succ[arm] += int(is_success(d))
|
|
# anarchy has NO memory tool by design, so it is never "dead".
|
|
if arm != "anarchy":
|
|
if (d.get("memory_unavailable") is True
|
|
or d.get("memory_layer_alive") is False
|
|
or d.get("fleet_verdict") == "errored"):
|
|
dead_trials[arm] += 1
|
|
verdicts[arm] = "MEMORY_UNAVAILABLE"
|
|
elif d.get("memory_layer_alive") is True:
|
|
live_trials[arm] += 1
|
|
tot_tok[arm] += d.get("fleet_tokens", {}).get("total", 0) or 0
|
|
tot_cost[arm] += d.get("fleet_cost_usd", 0) or 0
|
|
# backfill count comes from the sync arm (the memory arm)
|
|
bf = "?"
|
|
try:
|
|
s = json.load(open(td / "sync.json"))
|
|
bf = f"{s.get('agents_used_vestige_backfill', '?')}"
|
|
except (FileNotFoundError, json.JSONDecodeError):
|
|
pass
|
|
rows.append((t, man.get("correct_kid", "?"), verdicts, bf))
|
|
|
|
# Display labels per arm. Any arm not listed falls back to its uppercased
|
|
# name (render is robust to new arms).
|
|
LABEL = {"anarchy": "ANARCHY (no mem)", "rag": "RAG (similarity)",
|
|
"sync": "SYNC (Vestige)", "mem0": "MEM0 (mem0ai)",
|
|
"supermemory": "SUPERMEMORY", "hindsight": "HINDSIGHT",
|
|
"zep": "ZEP (Graphiti KG)"}
|
|
line = "=" * (24 + 26 * len(arms) + 10)
|
|
print(line)
|
|
print(" SILENT ROTATION — N-TRIAL RANDOMIZED BENCHMARK (per-trial)")
|
|
print(line)
|
|
hdr = f" {'trial':<6} {'correct key':<14} "
|
|
hdr += "".join(f"{LABEL.get(a, a.upper()):<26}" for a in arms)
|
|
hdr += "backfill"
|
|
print(hdr)
|
|
print(" " + "-" * (len(hdr) - 2))
|
|
for (t, kid, verdicts, bf) in rows:
|
|
rowstr = f" {t:<6} {kid:<14} "
|
|
rowstr += "".join(f"{verdicts.get(a, '?'):<26}" for a in arms)
|
|
rowstr += bf
|
|
print(rowstr)
|
|
print(line)
|
|
|
|
# Cost + tokens per arm (measured, from each trial's fleet_cost_usd / fleet_tokens).
|
|
print(f" {'':<20}" + "".join(f"{LABEL.get(a, a.upper()):>26}" for a in arms))
|
|
print(f" {'Total tokens':<20}" + "".join(f"{tot_tok[a]:>26,}" for a in arms))
|
|
print(f" {'Total cost (USD)':<20}" + "".join(f"{('$%.4f' % tot_cost[a]):>26}" for a in arms))
|
|
grand_cost = sum(tot_cost.values())
|
|
grand_tok = sum(tot_tok.values())
|
|
print(f" {'Experiment total':<20} {('$%.4f' % grand_cost)} ({grand_tok:,} tokens across {len(arms)} arms)")
|
|
print(line)
|
|
|
|
for arm in arms:
|
|
# A score is only honest if that arm's memory layer actually answered.
|
|
# Flag any arm with dead trials LOUDLY and inline -- never let a crash
|
|
# masquerade as a retrieval loss in the published table.
|
|
if arm != "anarchy" and (dead_trials[arm] or no_data_trials[arm]):
|
|
bad = dead_trials[arm] + no_data_trials[arm]
|
|
flag = (f" <-- !! {bad}/{n} trials NOT A MEASUREMENT "
|
|
f"(dead={dead_trials[arm]} no-data={no_data_trials[arm]}; "
|
|
f"backend never answered -- do NOT publish this score)")
|
|
else:
|
|
flag = ""
|
|
print(f" {LABEL.get(arm, arm.upper()):<28} shipped correct+prod-safe: {succ[arm]}/{n}{flag}")
|
|
# Explicit per-arm health line so a reader never has to open the raw JSON.
|
|
print()
|
|
print(" MEMORY-LAYER HEALTH (trials where the arm's backend actually answered):")
|
|
for arm in arms:
|
|
if arm == "anarchy":
|
|
print(f" {LABEL.get(arm, arm.upper()):<28} n/a (no memory tool by design)")
|
|
else:
|
|
bad = dead_trials[arm] + no_data_trials[arm]
|
|
state = "OK" if bad == 0 and live_trials[arm] == n else (
|
|
"UNUSABLE" if live_trials[arm] == 0 else "DEGRADED")
|
|
print(f" {LABEL.get(arm, arm.upper()):<28} live {live_trials[arm]}/{n}"
|
|
f" dead {dead_trials[arm]}/{n} no-data {no_data_trials[arm]}/{n} [{state}]")
|
|
anarchy_succ = succ.get("anarchy", 0)
|
|
sync_succ = succ.get("sync", 0)
|
|
|
|
# One-sided significance under the NULL that memory does nothing: a memoryless
|
|
# agent's chance of blindly picking the correct key is 1/K where K = keyring
|
|
# size (recovered from a trial manifest). With a large keyring this null is
|
|
# tiny, so even one clean sync trial is significant, and a full sweep is
|
|
# crushing. p0 = 1/K is a CONSERVATIVE bound (the fleet must also AGREE).
|
|
keyring_size = 3
|
|
try:
|
|
m1 = json.load(open(expdir / "trial-1" / "manifest.json"))
|
|
keyring_size = max(2, len(m1.get("key_names", [])) or 3)
|
|
except (FileNotFoundError, json.JSONDecodeError):
|
|
pass
|
|
p0 = 1.0 / keyring_size
|
|
pval = binom_p_at_least(sync_succ, n, p0)
|
|
print(f" keyring size (K): {keyring_size} -> blind-guess success = 1/{keyring_size} = {100.0/keyring_size:.1f}%")
|
|
print(f" p-value (sync successes vs blind-guess null p0=1/{keyring_size}): {pval:.4g}")
|
|
if anarchy_succ == 0 and sync_succ == n and n >= 3:
|
|
print(f" => Clean separation: anarchy 0/{n}, sync {n}/{n}. "
|
|
f"Under the null, that is p = (1/{keyring_size})^{n} = {p0**n:.2e}.")
|
|
print(line)
|
|
# The headline is now DERIVED, never asserted. It used to be four
|
|
# unconditional print() calls with no reference to the data, so it fired
|
|
# even when the numbers contradicted it -- it already sat directly under
|
|
# "RAG 2/5" in the GLM summary, and it would have printed unchanged had
|
|
# Vestige scored 0/5. A constant is not a finding.
|
|
if anarchy_succ == 0 and sync_succ == n and n >= 3:
|
|
print(" HEADLINE: With the correct key RANDOMIZED and absent from every file")
|
|
print(" the agent can read, the memoryless fleet did not ship a")
|
|
print(" production-safe fix in any trial; the Vestige-coordinated fleet did")
|
|
print(f" in all {n}. Same model, same corpus, same turn budget.")
|
|
else:
|
|
print(f" RESULT: anarchy {anarchy_succ}/{n}, sync (Vestige) {sync_succ}/{n}.")
|
|
print(" This run did NOT produce clean separation -- report the numbers as")
|
|
print(" measured. Do not reuse the clean-separation headline.")
|
|
# Fairness qualifiers that must travel WITH the numbers, always.
|
|
print(line)
|
|
print(" METHOD NOTES (must be published alongside any number above):")
|
|
print(" - The Vestige arm additionally has a vestige_log WRITE tool for fleet")
|
|
print(" coordination that no competitor arm is given. Transcripts show the")
|
|
print(" key was recovered by the read path (backfill) before any log write.")
|
|
print(" - Vestige's causal join follows a shared 'active_key' tag that the")
|
|
print(" seed places on the cause and the failure. Competitor arms receive")
|
|
print(" the corpus with tag metadata stripped.")
|
|
print(" - Any arm whose memory layer returned zero successful retrievals is")
|
|
print(" memory_unavailable, NOT a retrieval loss. Check memory_layer_alive")
|
|
print(" in each arm's JSON before quoting its score.")
|
|
print(line)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|