Reproducing the Benchmark
July 3, 2026 · View on GitHub
These commands reproduce the bench-fixture CI lane on a fresh clone.
A CI workflow (repro-docs.yml.staged) executes exactly these fenced blocks;
if a command here does not work, the staged workflow fails too.
Commands that require run-bench.mjs are marked # requires run-bench (Worker A).
All commands below run from the repo root (agentrecall/ after cloning).
Prerequisites
- Node.js 20 or later (
.nvmrcpins20; runnvm useif you use nvm) - npm 10 or later (ships with Node 20)
- A POSIX shell (bash or sh)
- No global binaries required — everything is installed via
npm ci
Step 1 — Clone and install
git clone https://github.com/Goldentrii/AgentRecall-MCP.git agentrecall
cd agentrecall
npm ci
npm ci installs from the lockfile and builds all workspace packages. No network
access is needed after this step for fixture runs. Expect deprecation warnings and
a vulnerabilities summary in the output; exit code 0 is the success signal.
Step 2 — Build packages
npm run build
Expected: exits 0. TypeScript compilation for all packages in packages/.
Step 3 — Validate fixture corpus
This step does not require run-bench.mjs. It is a standalone shape check.
node scripts/eval/fixtures/validate-fixture.mjs
Expected output:
=== validate-fixture.mjs — corpus-v1 ===
corpus root : <repo>/scripts/eval/fixtures/corpus-v1
projects : alpha-platform, beta-api, delta-infra, gamma-cli
n_on_disk : 26
n_counted : 23
n_excluded : 3
excluded[]
missing-both-excluded-case (project: delta-infra, reason: missing_rule)
missing-rule-excluded-case (project: gamma-cli, reason: missing_rule)
missing-date-excluded-case (project: gamma-cli, reason: missing_date)
...
ALL ASSERTIONS PASSED — fixture corpus-v1 is valid.
Exit code must be 0.
Step 4 — Run the fixture benchmark
requires run-bench (Worker A)
TZ=UTC node scripts/eval/run-bench.mjs --corpus fixture
Expected: exits 0. The artifact is written to
scripts/eval/baselines/correction-transfer-fixture-baseline.json (envelope
schema_version: "bench-result/v1"). The accounting chain in the printed summary
reads (verbatim excerpt):
n_on_disk 26 (export records; raw files=26)
n_counted 23 (= 26 − 3 dropped_from_corpus: missing rule/date)
Step 5 — Check determinism (double-run byte-diff)
requires run-bench (Worker A)
TZ=UTC node scripts/eval/run-bench.mjs --corpus fixture --check-determinism
This runs the scorer twice and byte-diffs the output after stripping
generated_utc and environment. Any difference exits non-zero.
Expected: exits 0 with message:
PASS: byte-identical after stripping generated_utc/environment
Step 6 — Verify baselines
--verify-baselines is a standalone verify-only pass — no benchmark run.
It ignores --corpus, prints run-bench: --verify-baselines (no benchmark run),
and exits without scoring anything.
TZ=UTC node scripts/eval/run-bench.mjs --verify-baselines
For each file under scripts/eval/baselines/ it recomputes every headline metric
from per_item and asserts equality with the stored metrics, and recomputes
corpus_hash from the embedded manifest (generated_utc and environment are
excluded from comparison).
Expected: exits 0 with all baselines verified.
Step 7 — Math.random gate
grep -rnE 'Math\.random\s*\(\s*\)' scripts/eval && exit 1 || echo "no Math.random invocation — pass"
Expected output: no Math.random invocation — pass
Matches invocation syntax only (Math.random(), including the spaced form
Math.random ()). Doc-comment mentions of the name and the deliberate runtime
guard in run-bench.mjs (Math.random = () => { throw ... }) are allowed and do
not trigger. There is deliberately no exclusion pipe — a comment on a violating
line cannot evade the gate. Aliased references (e.g. arr.sort(Math.random)) are
caught at runtime by the guard, not by this static check.
Any Math.random() invocation in scripts/eval/** causes this to exit non-zero
and fail the CI lane.
Notes on reproducibility
- TZ=UTC must be set for fixture runs. Day bucketing uses
toLocaleDateString("sv")which is TZ-sensitive. Fixture events are authored at12:00Zso the result is unambiguous, but the TZ pin is still required for byte-identical output. - Corpus hash is computed from the sorted record hashes (canonical JSON, sorted
keys, UTF-8 NFC, LF). Any modification to a fixture record changes the hash and
causes
--verify-baselinesto fail — that is the intended behavior. - Per-item sort in baselines is
(project, id)so ordering is out of the byte-diff contract. generated_utcandenvironmentare stripped before byte-diff; those fields legitimately differ across machines and times.