Skip to main content
Source Code: src/gaia/eval/
GAIA’s live evaluation surface is these commands: 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 agent scenario in 5 minutes
  • Scenario Authoring — write custom YAML scenarios
  • CI/CD Integration — regression detection in GitHub Actions
  • CLI Reference — full gaia eval agent command reference
  • Eval Scorecard — the per-agent release scorecard; the flagship gaia agent is scored by gaia eval agent --agent-type gaia over the 13-category eval/scenarios/gaia_* corpus (aggregate = judged pass rate), published at hub/agents/gaia/npm/SCORECARD.md and 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.
Build the dataset without running anything:
The dataset and results contain your own prompts and the answers Claude Code gave you. eval/results/sessions*/ is gitignored for that reason; don’t commit it or upload it as a CI artifact.
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 as context, 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:
A dispatch can run part of the suite. 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:
A targeted run is labelled as one in its summary, its lane artifacts and every scorecard (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:
The run then skips its lanes and says so in a notice and in the job summary. A lightweight tag, or one without the marker, runs the evals. A final release (v0.25.0) ignores the marker. To nominate an RC for the full sweep instead:

What Claude spent

Every scenario records the tokens claude -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’s claude -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

Use gaia 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.
A running Lemonade with the RAG embedder is required, and the 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 reports pass, 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 writes results.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 in src/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.
It needs the flagship installed (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" in tasks.json, defined in src/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 expect block holds: the project’s own tests pass (tests_pass), a probe run inside the finished project confirms the behavior (probe), and each file listed in unchanged is byte-identical to the snapshot. For example, the bug fix must accept lowercase z, 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 no expect block; 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 through run_python all 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_judge asks 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 --all cannot find it and git offers no way back to GitHub. (An earlier version cloned every ref, and main carried the answer.)
  • The reference diff is computed as merge^..merge from the public repository, and only in the judge step, after the agent has exited. GitHub’s base.sha for 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.
Two of the four tasks say in the prompt not to use the internet. The network is left open: the detector reads any web use out of the tool record, the same way for both harnesses, and reports it as a compliance finding.

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 dataset SWE-bench/SWE-bench_Verified — the datasets package 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-branch file:// clone of it, detached, remote removed, and git fsck --unreachable --no-reflogs must find nothing. A plain local path would make git ignore --depth and 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 on PATH, the host interpreter’s directories removed, and the interpreter GAIA’s run_python runs. 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 therock no-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_evaluation on one instance at a time, in Docker, with the image pulled for linux/amd64 just before and removed just after (they are amd64-only and about 3 GB each). It needs the swebench package in GAIA’s interpreter and a running Docker daemon; a missing one is an error before anything runs. The per-instance report.json the harness writes under <out>/swebench/logs/run_evaluation/ is read back into the scorecard: resolved, whether the patch applied, and the FAIL_TO_PASS / PASS_TO_PASS counts.
  • The judge grades a SWE-bench attempt’s quality against the gold patch, read from the cache only in the judge step, but it never decides the pass: that is the harness’s verdict alone.
In CI. .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.
  • gh inside a task is an offline stand-in serving eval/tasks/github/. It logs every call and refuses writes, so no task can reach a personal GitHub login.
The fence. --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 in eval/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.
An abridged, illustrative file:
  • 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_verified shows 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 --expect is an error.
  • --propose FILE writes 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 main on the CI runner. The limits change only through a reviewed commit, never automatically.
The run step never holds the judge’s credentials: run removes ANTHROPIC_API_KEY and CLAUDE_CODE_OAUTH_TOKEN from its own environment before the agent starts, because the agent runs shell commands. In CI they exist only in the judge step. The CI artifact is public and holds each task’s transcript and diffs as well as the scores; every file is scanned for the CI credentials and redacted before upload, but that catches credentials only.

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.
Each run writes 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/class around each changed line, found with ast in 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 grep for 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 missing metadata.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
Scale numbers are shown against the baseline but never gated. A baseline that scored different queries is refused, not compared. Baselines live in tests/fixtures/eval_baselines/code-retrieval/. They are copied unedited from a real run, never hand-written.