Skip to content

Quickstart

Install

pip install limen-eval

Python >= 3.12, zero runtime dependencies. The distribution is named limen-eval; the import and the CLI are limen.

The 90-second loop

# 1. a synthetic archive whose right answers are chosen by construction
limen synth --out demo --models 3 --items 500 --draws 8 --flaky-fraction 0.05 --gap 0.02

# 2. logs -> a versioned ruling document (report.json + report.md + provenance.json)
limen report demo/archive.verdicts.csv.gz --out demo-report --rulings-version demo

# 3. gate it: exit 0 pass, 1 measured fail, 2 unevaluable
limen gate demo-report/report.json --require-sign-stable --min-effect-vs-noise 1.0

Reading your own logs

Generic long CSV (.csv or .csv.gz), one row per draw:

model,task,item_id,draw_id,verdict,score,collected_at,model_version,raw_sha256
gpt-x,gsm8k,q1,0,1,,2026-05-01T10:00:00Z,gpt-x-0521,sha256:ab12...
gpt-x,gsm8k,q1,1,0,,2026-05-01T11:00:00Z,gpt-x-0521,sha256:9f00...

verdict must be literally 0 or 1. limen never derives a verdict from a score (LMN-CORE-001): choosing a threshold is the evaluation's decision, not the auditor's. The last four columns are optional, but what they unlock is real. collected_at and model_version feed the drift guard (absent means its state is UNAVAILABLE, which is never PASS). raw_sha256 (a hash of the raw completion) enables the grader-defect count.

lm-evaluation-harness --log_samples output:

lm_eval --model ... --tasks gsm8k --log_samples --output_path runs/  # run this >= 2 times
limen report runs/ --out my-report

Each sample line is one draw. Re-runs of the identical configuration stack into draws per item. One run alone is refused: limen needs k >= 2 draws.

inspect_ai .eval logs, at the per-epoch layer:

inspect eval mytask.py --model <model> --epochs 8   # epochs are the draws
limen report logs/ --out my-report

Each epoch of each sample is one draw; re-runs stack as further draws. limen reads the .eval zip directly (no inspect_ai dependency) and uses the per-sample completed_at timestamps for the drift guard. Score values must be binary (C/I or 0/1); with several scorers, --metric names the verdict.

Spaghetti-Architect checkouts (repeated-draw archives with committed graders): limen regrade --repo <checkout> --task comprehend_dev --out tables/ builds long-CSV verdict tables through the checkout's own public regrade API, read-only. The refactor path executes model-generated code, the same path the benchmark itself uses. Run it only where you would run the benchmark.

Multiple inputs merge: limen report a.csv.gz b.csv.gz --out r.

In CI

- uses: KurathSec/limen@main
  with:
    report: limen-report/report.json
    require-sign-stable: "true"
    min-effect-vs-noise: "1.0"
    pairs: "gsm8k:challenger>baseline"   # optional: assert the claimed direction

Exit code 1 means a check measurably failed. Exit code 2 means the report cannot support a requested check (an UNAVAILABLE section is red, not quietly green). On failure the log reprints the boundary: a failed pair never means the other model wins.

Library

import limen

archive = limen.load("runs/")
report = limen.build_report(archive, rulings_version="v1")
result = limen.evaluate_gate(report, limen.GateOptions(require_sign_stable=True))
print(result.exit_code)