evals(compaction): document the Jev repeated-compaction recipe and add the table renderer

jev_cycles_report.py renders the cycles/freed/floor/end-state table from
jev_cycles.py outputs; README carries the exact commands, thresholds and
cost so the diminishing-returns and stuck/fallback findings can be
reproduced. Fallback records now count the real tool calls.
This commit is contained in:
teknium1
2026-09-19 11:49:42 -07:00
committed by Teknium
parent 085ed46b63
commit 00919d1e8a
4 changed files with 77 additions and 3 deletions

View File

@@ -88,6 +88,32 @@ priced at the OpenRouter list price of the model that answered). The run
also writes `eval_usage.json` — the harness's own question/answer/judge
token bill.
## Repeated-compaction simulation (`scripts/jev_cycles.py`)
A one-shot recall score misses the failure mode of "decide, don't summarise"
compaction: it never removes user/assistant text, so each cycle frees only
`threshold − text_floor` and the floor grows monotonically. `jev_cycles.py`
feeds a lineage chronologically and compacts with the Jev arm every time the
estimate crosses the threshold, recording per cycle: tokens before/after,
percent freed, text floor, candidate/dropped calls, fitting stage, state
tokens, requests and Jev cost. It stops at end of transcript, when a cycle
frees nothing (`stuck`), or when the state cannot fit Jev's 25K ceiling
(`fallback` — the plugin throws there).
```bash
# lineage from a state.db COPY (see above), then, with OPENROUTER_API_KEY set:
python evals/compaction/scripts/jev_cycles.py /path/lineage.json 500000 40 > cycles-500k.json
python evals/compaction/scripts/jev_cycles.py /path/lineage.json 160000 60 > cycles-160k.json
python evals/compaction/scripts/jev_cycles_report.py cycles-*.json # markdown table
```
Threshold 500000 ≈ Hermes' 1M-window posture; 160000 ≈ a 200K-window host.
Each cycle costs 1–8 Jev requests (< 1¢); a 40-cycle run is ~$0.20. The
2026-09-19 runs are committed under `results/jev-cycles-2026-09-19/` (counts
only, no transcript content) and summarised in `SCORECARD-2026-09-19-jev.md`:
freed-per-cycle decayed 63% → 8% / 76% → 20% / 89% → 55% over 32–40 cycles,
one 200K run was stuck after 0.42M tokens of work, one transcript never fit.
## Notes
- Question generation and judging use `agent.auxiliary_client.call_llm`

View File

@@ -11,7 +11,7 @@
"before": 500301,
"fallback": "history too large for Jev (~25972 tokens after truncation, limit 25000)",
"floor": 22650,
"calls": 0
"calls": 542
}
],
"jev_usd_total": 0.0

View File

@@ -14,7 +14,7 @@ from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[3]))
from evals.compaction.fixtures import estimate_tokens, load_transcript # noqa: E402
from evals.compaction.jev_arm import JevCompactor, JevOptions # noqa: E402
from evals.compaction.jev_arm import JevCompactor, JevOptions, collect_tool_calls # noqa: E402
path, threshold, max_cycles = sys.argv[1], int(sys.argv[2]), int(sys.argv[3])
name = Path(path).stem
@@ -40,7 +40,8 @@ while i < len(msgs) and len(cycles) < max_cycles:
out = jc.compress(ctx)
except ValueError as e:
cycles.append({"cycle": len(cycles) + 1, "at_msg": i, "before": before, "fallback": str(e)[:90],
"floor": text_floor(ctx), "calls": len(jc.decisions)})
"floor": text_floor(ctx),
"calls": len(collect_tool_calls(ctx, jc.opt.preserve_recent_messages))})
break
total_jev_usd += jc.usage.cost_usd
after = tokens(out)

View File

@@ -0,0 +1,47 @@
#!/usr/bin/env python3
"""Render the repeated-compaction table from jev_cycles.py outputs.
Usage: jev_cycles_report.py <cycles_json> [<cycles_json> ...]
One row per run: cycles reached, raw session consumed, freed-per-cycle at the
first and last cycle, text floor at the first and last cycle, and how the run
ended (still working / stuck / plugin fallback). Prints a markdown table.
"""
import json
import sys
def describe(d: dict) -> dict:
cycles = d["cycles"]
scored = [c for c in cycles if "freed_pct" in c]
row = {
"run": f"{d['transcript']} @{d['threshold'] // 1000}K",
"cycles": len(scored),
"raw": f"{d['raw_tokens_consumed'] / 1e6:.2f}M of {d['raw_total'] / 1e6:.1f}M",
"freed": "—",
"floor": "—",
"end": "still working",
}
if scored:
row["freed"] = f"{scored[0]['freed_pct']:.0f}% → {scored[-1]['freed_pct']:.0f}%"
row["floor"] = f"{scored[0]['floor'] / 1000:.0f}K → {scored[-1]['floor'] / 1000:.0f}K"
if scored[-1].get("stuck"):
row["end"] = "STUCK (floor ≥ threshold, 0% freed)"
elif scored[-1]["freed_pct"] < 10:
row["end"] = f"degraded: {scored[-1]['before'] - scored[-1]['after']:,} tokens freed/cycle"
if cycles and "fallback" in cycles[-1]:
c = cycles[-1]
row["end"] = f"fallback on cycle {c['cycle']} ({c['calls']} calls, state does not fit)"
return row
def main() -> None:
rows = [describe(json.load(open(p, encoding="utf-8"))) for p in sys.argv[1:]]
print("| run | cycles | raw session consumed | freed per cycle | text floor | end state |")
print("|---|---|---|---|---|---|")
for r in rows:
print(f"| {r['run']} | {r['cycles']} | {r['raw']} | {r['freed']} | {r['floor']} | {r['end']} |")
if __name__ == "__main__":
main()