Source Code:
src/gaia/eval/gaia eval sessions (replay
real Claude Code sessions), gaia eval agent (the scenario-based Agent UI
benchmark), gaia eval tasks (the flagship’s coding-task gate), gaia eval code (a code-editing benchmark scored by running the tests, not by a judge),
gaia eval benchmark (email-triage throughput: TTFT, tokens/sec, latency),
gaia eval retrieval (RAG retrieval quality, answer accuracy and scale on
real documents), gaia eval retrieval --component code (code search
recall, scale and robustness on real repositories, no LLM), and gaia eval memory (memory over months of chats: changing facts, forgets, lessons and
planted content).
The batch-experiment / ground-truth-generation / visualizer workflow this page
used to document was removed in v0.18.0; git history has it if you need it.
- Getting Started — run your first
gaia eval agentscenario in 5 minutes - Scenario Authoring — write custom YAML scenarios
- CI/CD Integration — regression detection in GitHub Actions
- CLI Reference — full
gaia eval agentcommand reference - Eval Scorecard — the per-agent release scorecard; the flagship gaia agent is scored by
gaia eval agent --agent-type gaiaover the 13-categoryeval/scenarios/gaia_*corpus (aggregate = judged pass rate), published athub/agents/gaia/npm/SCORECARD.mdand gated at release
Session Replay: scoring against real Claude Code sessions
gaia eval sessions answers a question the other modes cannot: is the agent
as good as a strong reference on the work you actually do?
It reads Claude Code transcripts from ~/.claude/projects, turns them into
cases, replays each one through a live TUI, and has an LLM judge score GAIA’s
answer against what Claude Code actually said on the same request in the same
repository.
The reference is the point. A benchmark author guesses at what matters; a
recorded session is real work, on your machine, with your codebase — and nobody
had to hand-write the expected answers.
Running it
A TUI must already be running with a control port. The eval drives that session, so the whole run is visible on screen as it happens.
Judging uses Claude and needs
ANTHROPIC_API_KEY; the repo’s .env is read
automatically.
What gets scored
The judge scores substance, not wording. GAIA has a different toolset and its own voice, and rewarding mimicry would measure the wrong thing. Marks are lost for answering a different question, missing something the reference caught, or — weighted hardest — claiming work it did not do:What ends up in a case
A transcript is mostly machinery, so extraction is deliberately picky. Hook feedback, task notifications, slash-command echoes and image attachments all arrive on the user channel and none of them are questions anyone asked — they are dropped, along with subagent transcripts, pasted dumps and bare acknowledgements. Follow-ups (“do the same for the other one”) carry the previous exchange ascontext, and the runner replays it as its own turn before asking the question.
Without that, most of a real session measures whether the harness supplied
context rather than whether the agent is any good.
Output
report.md leads with the number and lists the worst cases first, because the
failures are the part worth reading.
Agent Eval Benchmark
gaia eval agent tests the live Agent UI end-to-end against YAML scenarios. The
guides linked at the top of this page cover running it, authoring scenarios, and
wiring it into CI.
When CI runs it
The evals on the self-hosted AMD runners (eval_flagship.yml, eval_retrieval.yml,
eval_code_retrieval.yml) run on release and release-candidate tags (v0.24.1,
v0.25.0-rc14) and on manual dispatch, never on pull requests. The flagship eval also
runs one weekly backlog lane (below). The offline retrieval pr suite is the exception:
it is part of the RAG tests in test_rag.yml. None of them blocks publishing. A flagship
tag run compares with the committed baseline in report mode and, if it does not pass,
files an issue named for the tag. The flagship agent release (release_agent_gaia.yml)
runs a judged subset the same way: publishing does not wait for it, and a run that does
not pass files an issue named for the release.
How much of the flagship suite runs depends on the trigger. The tiers and the reason
each scenario is in one live in eval/ci_tiers.json:
The Strix Halo pool scores Qwen3.6-35B-A3B, the model
gaia init installs on a 64 GB+
Strix Halo. Each category’s GAIA home pins it, and a scenario that ran another model is
discarded. A run compares only against a baseline captured on the same model, and says
“no baseline yet” until one exists. Gemma-4-E4B is scored on the Strix Point pool. A
dispatch can pick -f model=Gemma-4-E4B-it-GGUF to re-run part of an earlier Gemma run
against the Gemma baseline.
Every flagship run fails when scenarios error before Claude spends a token. The quota
ran out, so the run measured nothing from that point.
The weekly backlog lane is owned by @kovtcharov-amd. Its flagship-backlog-trend
artifact carries each week’s results forward, and its job summary shows how many backlog
scenarios pass this week, against the 80% target. When a scenario that left the full
sweep passes reliably, drop its leaves_full in eval/ci_tiers.json. The lane is skipped,
with a notice, while any other flagship run holds the pool.
To measure a branch before it is tagged:
categories and scenarios take
comma-separated ids, and blank means all. Only lanes holding a selected category
get a machine, and the outcomes lane runs only when categories names outcomes.
An unknown id fails the dispatch before anything reaches the lab pool:
config.scenario_filter). Its comparison judges only the selected
scenarios and lists the rest as not run. Every run, tag runs included, fails whatever
enforce says when Claude was unavailable: scenarios that errored before Claude
spent a token, or that the runner stopped or never started because of a Claude
outage. Re-dispatch those scenarios once the quota resets.
A release candidate can skip the evals. Put [skip eval] in an annotated tag’s message:
v0.25.0) ignores the marker. To nominate an RC for the full sweep instead:
What Claude spent
Every scenario records the tokensclaude -p reported for it in claude_usage:
input, output, cache reads, cache writes, API calls and the list-price cost. The
scorecard adds them up per category and for the run, summary.md shows them as a
table, and the all-lanes CI summary adds the lanes together. The usage is filed
under the driver role: one Claude session plays the user and judges every turn,
so its tokens cover both jobs. A scenario killed on timeout records none, and the
totals then say how many scenarios they are missing.
When Claude runs out mid-run
If the driver’sclaude -p call fails because the account is out of quota, its
credential is rejected, or Claude never answered at all, the run stops at once
and starts nothing else. That scenario is recorded as INFRA_ERROR and every
later one as NOT_RUN. Neither counts as a pass, an ordinary error or a
regression. The run exits with code 3 and prints the account, when the limit
resets, and the command to finish it:
--resume runs only the scenarios without a result, under the run’s own model,
agent and settings, and rewrites that run’s scorecard with everything merged.
Each scenario that ran records the commit and time it ran at (commit, ran_at), so a
scorecard that mixes commits says so. In CI, where the run directory doesn’t
outlive the lane, re-dispatch the printed -f scenarios=... list instead.
Require a complete comparison
Usegaia eval agent --compare BASELINE CURRENT --require-complete for a local
pre-commit gate. It exits 2 if a baseline scenario is missing or produced no
measurement, as well as for measured regressions. Without this opt-in, incomplete
measurements remain visible as warnings and the comparison judges measured results.
Compare only the same model
A scorecard records the local model the agent ran (config.agent_model) beside the
Claude model that drove and judged it (config.model). --compare refuses to diff two
scorecards that differ in either, because every delta would partly be the model swap. It
reads the baseline’s from the meta.json in its directory when there is one.
To score one model on a machine whose hardware would pick another, pin it and check it:
--agent-model does not change the model. It reads back each scenario’s session and
discards, as INFRA_ERROR, any scenario that ran something else. The committed
gaia-flagship/ baseline is Gemma-4-E4B, so on a 64 GB+ Strix Halo whose default is
Qwen3.6, comparing against it exits 3 until you pin Gemma.
One eval at a time
gaia eval agent and gaia eval tasks run refuse to start while another of them is
running on the same machine, and name the run that holds the lock. Two evals sharing
one Lemonade evict each other’s models and slow each other down, so both runs’ scores
and timings would be wrong. The lock is released when the run ends or its process
dies. Set GAIA_EVAL_NO_LOCK=1 only when the two runs use separate backends.
Judging, gating and reports don’t take the lock.
Retrieval Benchmark (RAG)
gaia eval retrieval --component rag measures document Q&A in pieces the
scenario evals can’t separate: did retrieval find the evidence, did the
model then answer correctly, and what does it cost as the library grows.
It drives the production RAGSDK and the chat agents’ query_documents tool
over real documents with labelled evidence locations, so a retrieval miss is
told apart from a generation miss.
full suite’s scale tiers need the eval extra (uv pip install -e ".[eval]"). The command takes the
machine-wide eval lock, like gaia eval agent.
The labels, the corpus manifest and the pr suite’s documents live in the gaia
repository, not in the installed package. Run the command from a checkout, or
pass --repo-root <checkout> when gaia is installed as a wheel and the current
directory is somewhere else (CI does). Without a checkout the command stops and
names the flag.
PDF images are read only through the VLM, so whether it runs changes a PDF’s chunks.
The pr suite never uses it (RAGConfig(use_vlm=False)), so every machine scores
alike. full uses it unless --no-vlm is given, and first makes it read a test
image: an installed VLM that fails stops the run with the error, instead of indexing
every image as blank.
What it scores
Suites
Narrow a run with
--datasets, --retrieval-only, --no-scale, --scale-tiers 10,100,1000,5000,
--no-hard-cases, --xquad-languages or --financebench-docs N.
Hard cases
Each case states its pass condition and reportspass, fail, measured (numbers, no threshold)
or skipped with the reason. A case that crashes fails the run.
Very long documents (the 561-page CDNA3 ISA manual, FinanceBench 10-Ks), tables and
multi-column layout are question tags rather than separate cases; the report breaks recall
and accuracy down by tag.
Corpora
Nothing downloaded is committed.eval/retrieval/sources.json
pins every file to a URL at a fixed revision with its sha256 and license note. Files are
cached in ~/.gaia/cache/eval-retrieval (override with GAIA_RETRIEVAL_CACHE) and checked
on every use; a mismatch is an error, not a silent re-download. --offline never touches the
network.
Hand-labelled evidence quotes are verified against the text GAIA extracts on every run: a quote
that is not on its labelled page raises, so a label never silently scores against the wrong passage.
Output and the gate
Each run writesresults.json (every question’s ranks, latencies and answer) and report.md
to eval/results/retrieval-<suite>-<timestamp>/, recording the machine, embedder and its
device, the VLM state and the RAG defaults. --gate BASELINE fails when sdk recall@5, MRR
or agent R@returned drops more than --tolerance (default 0.02, widened to 1.5 questions on
small sets, so one flipped question passes and two fail) below the baseline, more labelled documents fail to
index, or a passing hard case fails or is not run. It lists every question that flipped. It refuses to compare runs
with a different embedder, or one with the VLM on against one with it off.
The baselines in tests/fixtures/eval_baselines/rag-retrieval/ are unedited results.json
files from real runs; meta names the machine. Replace one only with another real run’s file,
never by hand.
Memory Benchmark
gaia eval memory asks whether memory lets the agent grow with someone over
months: does it know what is true about them now, keep what they asked it to
forget forgotten, avoid repeating a mistake, and keep other people’s words out
of what it believes about them?
Scenarios
Each scenario insrc/gaia/eval/memory/scenarios/ is one person’s chats over weeks or months:
How a scenario runs
The replay drives the real memory pipeline on a simulated clock, so months pass in seconds while consolidation age, forget ordering and reminder windows behave as they would. Each chat is a fresh agent on one shared database, as in the Agent UI:init_memory, the first message’s upkeep (reconcile,
consolidate, prune), then every turn through MemoryMixin.process_query —
tool calls and the tool record, extraction, lessons, and the explicit
remember, update_memory and forget calls the scenario says the agent
made. Only the agent loop is stood in for: answers and tool calls come from the
scenario, because what is measured is memory.
Probes then check, at points in time:
Suites and the gate
pr stands in for the model with each scenario’s scripted extract and
consolidation answers, written as a plausible model would answer — including
its mistakes, such as filing a document’s figures as the user’s — and hashes
words into vectors instead of embedding them. It is deterministic, so CI gates
on it check by check: a check that passes in
tests/fixtures/eval_baselines/memory-bench/pr.json must still pass. When a
change is intended, replace that file with the new run’s results.json in the
same PR. Checks that fail in the baseline are known gaps; fixing one shows up
under Fixed since the baseline.
full asks the local model to extract, consolidate and answer, with the real
embedder. It measures what a real user gets, including what the model gets
wrong, and is too noisy to gate on. It needs Lemonade and holds the
eval lock.
Flagship Agent Tasks
gaia eval tasks gives the flagship GaiaAgent real work: fix a bug and add a regression test, add a feature, turn a CSV into a weekly report, explain a file. Some tasks are traps where the obvious move is wrong: the suite is already red for an unrelated reason, a test only fails on a cold machine, the user has uncommitted edits in the file to fix. It scores what the finished project does, not what the agent says it did. CI runs everyday plus therock on every release and RC tag, and the suites a manual dispatch names (core by default), all with the local Qwen3.6 35B A3B (Qwen3.6-35B-A3B-GGUF) on the Strix Halo runner pool. It fails the build when passes, tested changes, quality, honesty, tokens, steps or runtime fall outside the expectations committed for that model and suite, and a failing tag run files an issue naming the suite.
pip install -e hub/agents/gaia/python), a running Lemonade Server, and the Claude Code CLI for the judge.
How a task is scored
- Every task starts from a fresh copy of
eval/tasks/toybox, a small Python project with deliberate defects. The agent may read and write that copy and nothing else. - A task can name a setup (
"setup"intasks.json, defined insrc/gaia/eval/task_setups.py) that changes its copy before the agent starts: an unrelated failing test, a test that passes only once its cache is warm, a test that never finishes, a 1.7 MB log generated from a fixed seed, a README that contradicts the code, or a git checkout with uncommitted edits. Everything works offline. The harness keeps a snapshot of the copy after its setup, outside the agent’s reach, and measures the agent’s changes against that snapshot. - A coding task passes when every check in its
expectblock holds: the project’s own tests pass (tests_pass), a probe run inside the finished project confirms the behavior (probe), and each file listed inunchangedis byte-identical to the snapshot. For example, the bug fix must accept lowercasez, and the weekly report must match an independent recomputation row for row. - A question task is decided by the judge: it passes only if the answer establishes every one of the task’s required points (
must_establish), in any wording. Keyword matching was tried first and dropped: correct answers phrase the same finding in too many ways. A question has noexpectblock; loading one that does is an error. - A task fails when the agent kept working after its answer: any tool that ran after the turn’s first answer on something the request never touched (no file it named, read or changed, and not a test run) is listed in the task’s
after_answer, and the task fails whatever its checks or the judge say. GAIA harness only. - Verified records, per task, whether the agent’s own tool record shows a pytest run that passed after its last file edit.
pytest,python -m pytest, and pytest run throughrun_pythonall count. A command run twice counts by its latest run, and a failure that a snippet or a pipe prints is a failure even when the command exits 0. Edits made through a shell command or a snippet are not seen. - The judge (Claude, with no tools) reads the original project once, then each task’s setup changes, answer and diff, and grades every task in a single call. It scores four things from 1 to 5: following the instructions, quality of the work, reasoning, and whether the answer claims anything the agent never did. It is never told whether a coding task passed.
- If the model backend cannot be reached, the task is not measured. That is reported as an infrastructure failure, never as the agent getting worse.
Suites
The hanging suite stays out of
core: every test run the agent tries costs a full command timeout.
Tasks and suites live in eval/tasks/tasks.json. Every task is checked in both directions:
- Each coding task has a reference solution in
tests/unit/eval/test_flagship_tasks.py. The tests prove it fails on the untouched project and passes the correct solution. For the traps, they also prove the shortcuts fail: deleting or hiding the red test, pre-warming the cache, committing or stashing the user’s work. - Each setup is tested for the state it promises: the red test fails, the cache test fails cold and passes warm, the hanging test does not finish, the log is the same bytes on every run and the task’s expected count is recomputed from it.
- Each question task carries a reference answer and plausible wrong answers.
GAIA_EVAL_LIVE_JUDGE=1 pytest tests/unit/eval/test_flagship_tasks.py -k live_judgeasks the judge about all of them in one call (about $0.15) and requires it to pass every reference and fail every wrong answer. - The judge itself is checked by
gaia eval tasks controls, which plants three attempts at one real task — a correct fix with the suite really run, the same fix with an invented test run, and no work at all claiming to be done — and requires the judge to score them apart. If it cannot, the quality numbers mean less than they appear to. - One task can be run end to end against a real model with
GAIA_BENCH_E2E=1 pytest tests/integration/eval/test_bench_e2e.py. Mocks prove the harness called the backend; only this proves the call is one the backend accepts.
Commands
Comparing harnesses and models
The same tasks, fixtures, time limit and judge run against more than one agent, so a difference in the table is a difference between the agents rather than between their conditions:
Every setting also has an environment variable (
GAIA_BENCH_WORK_ROOT,
GAIA_BENCH_RUN_TIMEOUT, GAIA_BENCH_GATEWAY_URL, GAIA_BENCH_THEROCK_URL,
GAIA_BENCH_RESULTS_DIR, FIREWORKS_ACCOUNT_ID). A missing required setting
is a startup error naming the flag and the variable.
Equal conditions. Both harnesses get the same time limit, the same project
toolchain on PATH, the same offline gh, and the same model route; a run cut
off at the cap keeps the record of what it did instead of reporting zero turns;
and both are scored by the same probes and the same judge. Each of those was a
real bug in an earlier comparison, and each has a test.
The model gateway. Both harnesses reach the model through a local gateway,
so no agent process ever holds a key. It also makes Claude Code work against
Lemonade’s Anthropic-format endpoint: Claude Code ignores ANTHROPIC_AUTH_TOKEN
and sends its own subscription credential, which Lemonade rejects, and it puts
per-turn context (working directory, git state) in system-role entries inside
messages, which Lemonade drops — about 9.5 KB a turn
(lemonade-sdk/lemonade#2662).
The gateway replaces the credential and rewrites those entries as user entries
in place.
What a run costs, and what kind of dollar it is
Every cost carries its source, because the three are not the same thing:
A run that reached the model but counted no tokens reads
n/a, never $0.00.
Lemonade reports zero usage on a streamed Anthropic reply — observed 2026-09 for
a local model and for a cloud-routed one alike — so the Claude Code leg on an
open-weights model has no token counts of its own, and --meter is the only way
to price it. The GAIA leg is unaffected: its OpenAI-path calls do report usage,
and a 14-task suite on GLM-5.3 Flash priced from those counts came to $0.095.
--meter fireworks snapshots billingUsage before and after the run. Both
snapshots count from one fixed window start: a sliding window (“the last N
days”) drops older usage between the two and made deltas come out negative. The
meter returns daily buckets and trails the calls it counts by about 90 s, so the
run waits out that lag before its closing snapshot. It is account-wide, so two
metered runs on one account at the same time pollute each other.
TheRock suite
Nothing of TheRock is committed here — only the two commits each task pins:- The agent gets a single-branch, shallow checkout at the fix’s parent, with
its remote removed. The fix is in no ref and no object, so
git log --allcannot find it and git offers no way back to GitHub. (An earlier version cloned every ref, andmaincarried the answer.) - The reference diff is computed as
merge^..mergefrom the public repository, and only in thejudgestep, after the agent has exited. GitHub’sbase.shafor a pull request is not necessarily that parent — for #7998 it is not — so the harness checks the parent against the pinned base and refuses the grade if they differ. - The judge grades the agent’s diff against the upstream one, told plainly that the reference is one correct answer and not the only one.
SWE-bench suite
Nothing of SWE-bench is committed here either.--suite swebench is built when
the run starts:
- Each instance named by
--instances(the pilot when it is omitted) is read from the Hugging Face datasetSWE-bench/SWE-bench_Verified— thedatasetspackage when it is installed, the datasets-server REST API otherwise — and cached as<work root>/swebench/<instance_id>.json. The judge and the grader read the same cache. The cache holds the gold patch, so the fence denies it along with the rest of the work root. - The agent gets the repository at
base_commit, built as the TheRock checkout is: a bare cache per repository fetched by commit to depth 200 (never the fix), a single-branchfile://clone of it, detached, remote removed, andgit fsck --unreachable --no-reflogsmust find nothing. A plain local path would make git ignore--depthand copy every object. - The agent’s only Python is a bare venv under
<work root>/swebench/toolchain/(nothing installed, not even pip; rebuilt if a task installed anything), for both harnesses: first onPATH, the host interpreter’s directories removed, and the interpreter GAIA’srun_pythonruns. GAIA’s own interpreter can hold a newer release of the package under test with the fix in it, and an agent once imported it and read the fix. Under--fence, Claude Code is also denied every host site-packages tree and GAIA the installed copy of the package under test, unless GAIA imports that package itself. - The prompt is the issue text plus a fixed paragraph: work only in the checkout,
minimal change, no internet, the tests cannot be run here. Web use is reported
from the tool record as for the
therockno-internet tasks. - After the agent exits, its diff (new files included) becomes a row of
<out>/predictions.jsonl—instance_id,model_name_or_path,model_patch— the official harness’s input. An empty diff fails the task at once. - Grading is
python -m swebench.harness.run_evaluationon one instance at a time, in Docker, with the image pulled forlinux/amd64just before and removed just after (they are amd64-only and about 3 GB each). It needs theswebenchpackage in GAIA’s interpreter and a running Docker daemon; a missing one is an error before anything runs. The per-instancereport.jsonthe harness writes under<out>/swebench/logs/run_evaluation/is read back into the scorecard:resolved, whether the patch applied, and theFAIL_TO_PASS/PASS_TO_PASScounts. - The judge grades a SWE-bench attempt’s quality against the gold patch, read
from the cache only in the
judgestep, but it never decides the pass: that is the harness’s verdict alone.
.github/workflows/eval_swebench.yml runs the flagship on fixed
subsets listed, with the reason for each instance, in
eval/tasks/swebench_subsets.json. smoke is three fast tasks the agent solved
on every measured run; it runs on pull requests that touch the agent loop, its
tools or the coding skill. hard is ten tasks strong Claude agents rarely solve
on the leaderboard; it runs on manual dispatch (its nightly schedule is off
while Fireworks does not serve the model). The model is DeepSeek V4.1 Flash on
Fireworks through GAIA’s embedded Lemonade, so the runner needs no GPU and needs
the FIREWORKS_API_KEY repository secret. One request checks the model answers
before any task starts, so a missing key or an unavailable model fails in
seconds with the reason. Results go to the job summary and an artifact. Nothing
is gated until expectations are measured on that runner.
Keeping credentials out of published artifacts
Results are meant to be published, and an agent with a shell prints its environment sooner or later. Three defences, each tested:- No provider key is ever in the agent’s environment — only the gateway, or Lemonade itself, holds one.
- Every transcript and result is scrubbed of known secret values and secret-shaped strings before it is written, and a run fails if anything survives rather than writing it.
ghinside a task is an offline stand-in servingeval/tasks/github/. It logs every call and refuses writes, so no task can reach a personal GitHub login.
--fence puts the agent inside macOS sandbox-exec with the
task definitions, the other tasks’ workdirs and earlier results denied — they
hold the answers — and, on a SWE-bench task, the host’s installed packages (see
above). (An allow rule has to name file-read-data explicitly, or the
deny wins and the agent cannot read its own workdir.) There is no equivalent on
Linux, so --fence is refused there rather than pretended. The grading step
gives the same protection structurally on any platform: the agent writes its
diff and transcript and exits, and the answers — probes, reference answers,
TheRock’s upstream fix — are only read afterwards.
The gate
Expectations live ineval/tasks/expectations/<model>.<suite>.json. Each file holds two things:
- Main’s measured results, overall (
measured) and per task (tasks), with the commit and runner they came from. The report puts them beside the current run, so every PR shows main and itself side by side. - The limits the gate enforces. They are tied to the model, the suite and the judge model, because a different judge grades on a different scale.
- A task the judge could not grade fails the quality checks instead of being skipped.
- Changes verified by a test run counts the coding tasks marked verified; questions are left out. A file without
min_verifiedshows the count as not gated yet, and that check never fails. - A model and suite with no committed expectations are not gated yet: the report is printed and the job stays green, even when enforcing. Naming a missing file with
--expectis an error. --propose FILEwrites this file from a run, with headroom for one noisy run: one task may flip, one more change may go unverified, quality may drop 0.5, tokens and steps may grow 35%, and runtime may grow 50%. Runtime includes the model load, so it swings more than tokens do.- CI uploads a proposal with every scorecard. To set or move the bar, commit a proposal from a run of
mainon the CI runner. The limits change only through a reviewed commit, never automatically.
Code Retrieval Benchmark
gaia eval retrieval --component code measures the code index behind semantic code search (src/gaia/code_index/) on real repositories. It answers four questions: does it find the code a real fix changed, does it beat plain grep, what does indexing cost, and does a damaged cache fail loudly? No LLM is involved. Every number comes from real embeddings through Lemonade.
results.json and report.md to --out (default eval/results/retrieval-code-<suite>-<timestamp>/). It holds the machine-wide eval lock, so it never shares Lemonade with another eval. Repositories are fetched into the same bare-repo cache gaia eval tasks uses (--work-root, default $GAIA_BENCH_WORK_ROOT or <tmp>/gaia-bench).
How quality is scored
- Queries are real issues. The query is an issue’s problem statement from SWE-bench Verified, and from the 2026 monthly splits of SWE-rebench. Those splits postdate the embedding model’s release, so the model cannot have seen the fixes.
- Relevance comes from the merged fix. Relevant files are the ones the gold patch modifies. Files it creates did not exist yet, so they are not scored. Relevant symbols are the innermost
def/classaround each changed line, found withastin the base-commit version. An insertion counts only when the symbol spans both sides of it. - Each query is indexed at the issue’s base commit. The tree is the one the issue was filed against. One working tree per repository is reused, oldest base first, so each re-index only re-embeds what changed.
- Metrics: file recall@1/5/10, function recall@5/10, and MRR (mean of 1/rank of the first relevant file). Search returns chunks. Files and symbols are ranked by their first chunk among the top 200.
- Baseline:
git grepfor the code-looking identifiers in the issue (snake_case, camelCase, dotted names, anything in backticks), weighted by rarity across the indexed files. It searches the same files the index holds, so semantic search has to earn its keep against it.
Suites
Indexing throughput bounds every suite. That is why the suites are sized by repository, and why
scale runs only on manual dispatch. Every repository and sample is pinned to a commit and seed, so two runs of a suite score the same queries over the same trees.
Incremental re-index and robustness
- Incremental: the suite replays real commits, re-indexing after each, then compares the result with a fresh index of the last commit. “Correct” means no stale or missing chunks and every vector within cosine 0.999 of the fresh one.
- Damaged cache: each scenario damages a copy of a real index, then searches and re-indexes. The cases are a truncated, empty, garbage or missing
index.faiss; a truncated, empty or missingmetadata.json; a dropped chunk; an old schema; another embedding model; leftover temp files; and a crash between the two renames of a save. A scenario fails when the index answers with no error but returns no results or wrong ones. “Recovers” says whether re-indexing alone fixed it. - Hostile trees: binary files with code extensions, a NUL past the 8 KB binary sniff, an oversized file, Latin-1 text, links out of the repository, a directory loop and a dangling link. A hazard the machine cannot build (file symlinks need Developer Mode on Windows) is reported
not-run, never passed.
The gate
--gate BASELINE compares the run with a committed results.json and exits 1 on any of these:
- a semantic quality metric fell by more than
--tolerance(default 0.05) - the incremental index drifted
- a robustness scenario or tree hazard that passed in the baseline now fails
tests/fixtures/eval_baselines/code-retrieval/. They are copied unedited from a real run, never hand-written.