Searching a billion tokens of agent data
How our data agent uses evals-as-indexes for better, faster, cheaper search.
Drag the slider to see how evals turn raw traces into searchable indexes.
Searching through agent traces is a new and hard problem
Most agent failures don’t come with a big flashing error. You usually find them by accident, while digging into how users interact with your agent, when you notice it did something unexpected. Then comes the harder question: how often has this happened? Answering it means searching through billions of tokens of production data.
Large-scale data systems have long turned vast amounts of raw data into useful signals. Meta’s ads platform, for example, used tens of thousands of engineered features (e.g. recently clicked ads and page-visit counts) to personalize recommendations. At its core, the recommendation system turns vast amounts of user activity into better insights about people's preferences.
Long-horizon agents now produce data at a much larger scale. Their traces do contain structured events, such as searches, page visits and tool calls, but much of what we care about lives in free-form text across those events. A single run can produce thousands of model calls, tool invocations and intermediate decisions, most of them unstructured text. Whether a run actually succeeded often depends on context spread across many spans: a request the agent quietly dropped, a correction from the user, or a tool call that “succeeded” but did the wrong thing. However, the infrastructure and best practices for making sense of this data are still developing. Reading traces by hand helps you understand your agent’s behavior and discover what to look for. But finding every instance of that behavior across billions of tokens requires a more scalable approach.
The natural next step is a data agent: an agent that answers questions about your production traffic by writing SQL over your traces, reading the relevant spans, and citing the evidence it finds. But pointed at raw traces alone, it hits the same wall we do. The behaviors we care about are often latent, so keyword search can’t reliably find them, and the data agent has to find and evaluate them from scratch on every question.
Luckily, LLMs have made it much easier to semantically parse the traces we’re storing. Compared to earlier BERT-style models, LLMs are more capable of handling arbitrary, complex rubrics without fine-tuning. That lets us break each run down along multiple semantic dimensions:
- Was the user frustrated?
- Did the agent follow the specified instructions?
- What was the action’s outcome?
Each of these is an eval, and many teams already run evals like these as online monitoring, to track quality and catch regressions in production. But the same results can also serve as an index, a column you can filter and search on. That makes your data much easier to understand, and makes it far more likely you’ll catch what’s going wrong.
Why search needs these evals
Without evals, a data agent typically falls back on one of two approaches:
- Read traces one by one. With no index to narrow the search, it has to inspect trace after trace. It’s slow, and for a data agent it fills the context window with irrelevant results. As the context grows, the agent suffers from context rot, and the evidence it found earlier gets truncated or compacted away, so the search gets worse the longer it runs.
- Search by keyword. SQL
LIKEor grep only finds the words it thinks to try, and some questions have no words to search for. Take “did the agent follow the policy it was given?” It may not know what each agent’s policy says, and breaking it doesn’t leave any particular word behind. To answer, it have to read the policy, read what the agent did, and compare the two.- What about vector search? Vector search can find relevant passages even when their wording differs from the query, and a reranker can help prioritize the results. But answering questions like “did the agent repeat a mistake after being corrected?” still requires evaluating the evidence, sometimes across multiple spans. Evals-as-indexes do that work ahead of time for behaviors you already know you want to track. Subsequent searches can filter the stored results and inspect the supporting evidence, rather than evaluate those behaviors from scratch.
Say you run a sales agent and want to find requests it did not complete. Consider the query: “Find email requests that were not completed.”
For this example, we use three evals, some run as standard LLM-as-a-judge and some with Jev:
requested_action: what did the user ask the agent to do? For a sales agent:send_email,update_pipeline,research_account,schedule_meeting.action_outcome: what actually happened to it?completed,partially_completed,pending_approval,not_doneorfailed.failure_recovery: if something failed, did a later attempt fix it?recovered,partially_recoveredorunresolved.
Evals can see an entire trace or an individual span. Their results are stored in telemetry.scores, alongside the traces and spans they describe, with the eval name (judge), its result (label) and an explanation (reason). Each result links to the relevant trace and supporting span. In this example, the evals assess whether the email request was completed at the trace level and cite supporting spans within that trace. We assume at most one result per eval per trace.
Hover, focus, or tap a SQL block to explore it. Related blocks highlight in both queries.
Without evals-as-indexes
-- Find evidence via grep-style text matching SELECT trace_id, span_id, span_name, substringUTF8(input, 1, 500) AS input_preview, substringUTF8(output, 1, 700) AS output_previewFROM telemetry.spansWHERE span_name IN ('send_email', 'save_email_draft')OR ( span_name = 'chat_completions' AND ( output ILIKE '%not been sent%' OR output ILIKE '%haven''t sent%' OR output ILIKE '%did not send%' OR output ILIKE '%nothing was sent%' OR output ILIKE '%saved a draft%' OR output ILIKE '%not yet%' ) )ORDER BY trace_id, started_at;
Read the evidence
Return trace and span IDs, tool names, and shortened input and output previews. The agent still needs to inspect these candidates.
With evals-as-indexes
-- Find evidence by filtering across three evals SELECT trace_id, groupUniqArray(span_id) AS evidence_span_ids,anyIf(label, judge = 'requested_action') AS action, anyIf(label, judge = 'action_outcome') AS outcome, anyIf(reason, judge = 'action_outcome') AS reason, anyIf(label, judge = 'failure_recovery') AS recoveryFROM telemetry.scores WHERE judge IN ( 'requested_action', 'action_outcome', 'failure_recovery' )GROUP BY trace_idHAVING action = 'send_email'AND outcome IN ( 'not_done', 'failed', 'pending_approval', 'partially_completed' )AND coalesce(recovery, '') != 'recovered';
Read the evaluated evidence
Return each trace ID and collect its supporting span IDs so the agent can inspect the original evidence.
Swipe sideways to compare both queries.
The raw query can find the right trace even when the sales agent tells the user “Done, the follow-up is ready.” A save_email_draft receipt with sent_at: null tells us the message was still a draft at that point. The search can miss the evidence that tells us whether the email was ever sent. A later delivery confirmation, for example, might appear in a span that matches neither the tool names nor the phrases in the query. With evals-as-indexes, it can filter for requests labeled not_done, exclude those labeled recovered, and inspect the supporting spans.
Using evals-as-indexes helps the data agent find relevant evidence more accurately, finish faster, and consume fewer input tokens. The search costs below exclude the upfront cost of generating the evals, which can be reused across subsequent searches.
The walkthrough below uses a simplified SQL schema to make the Sonnet 5.5 run easier to follow.
Measured results (Sonnet 5.5)
| Metric | Without indexes | With indexes |
|---|---|---|
| Search calls | 50 | 24 |
| Reference traces found | 2 of 9 (22%) | 7 of 9 (78%) |
| Search time | 8.1 min | 2.3 min |
| Input tokens | 4.3M | 0.6M |
| Answer qualityWeighted rubric | 71.7% | 80.0% |
Benchmarking
To measure whether evals-as-indexes actually help, we built a search benchmark from perturbed internal traffic: 4K+ long-horizon agent traces (1B+ tokens), with 50+ search questions. Each question includes a reference answer, human-reviewed evidence trace IDs, and a rubric that checks whether the agent correctly analyzes the evidence.
We built it with an AutoBenchmark-style loop:
- Generate questions. LLMs role-played people who use our platform (PMs, agent engineers, CTOs) to write realistic sets of questions grounded in the agent trace structure.
- Collect evidence. A research agent searched the traces to assemble each question’s answer, evidence IDs and rubric, which humans then reviewed.
- Test with solver agents. We ran agents of varying strength on every question and rewrote or dropped any that were too vague or had valid alternate answers.
- Add adversarial distractors. We inserted near-miss traces (for example, a failure the agent later recovered from) and follow-up turns designed to mislead a search agent that doesn’t verify its evidence.
We measure evidence precision and recall against the reference IDs and grade answer quality with the rubric. We ran every model with and without evals-as-indexes.
Results
Our index reuses 18 online monitoring evals, including requested_action, action_outcome and failure_recovery, for a total of 40,482 evals across the dataset. Each trace is first parsed programmatically to extract the evidence each eval needs, then scored with MiniMax M3.1 Flash and Jev, for a total cost of about $60.
Averaging each of the seven models’ relative changes, indexes reduced input tokens by 23.9%, search time by 21.4%, and estimated search costs by 20.8%. Answer quality improved by 22.7%.
In addition to these results, several open-source models saw substantial gains in recall and precision, most of the time because the evals had already surfaced cases the models were less likely to find by searching the raw text. However, stronger retrieval did not always translate into stronger analysis. Some open-source models appeared less likely to challenge the eval results, potentially making them more susceptible to incorrect or incomplete indexes. Across all seven models, recall increased by 54.8%, precision by 43.0%, and SQL calls decreased by 11.8%, averaging each model’s relative change.
| Model | Recall | Precision | F1 | Cost per search | SQL calls per search | Time per search | Input tokens per search |
|---|---|---|---|---|---|---|---|
| GPT-6 Astra | 28.2%46.9% | 28.9%39.8% | 28.5%43.1% | $7.696$5.541 | 59.449.3 | 11.2 min9.8 min | 4.33M2.73M |
| GPT-6 Sol | 19.0%32.5% | 21.5%36.5% | 20.2%34.4% | $1.665$1.264 | 62.252.5 | 10.6 min4.8 min | 5.21M3.62M |
| Opus 5.5 | 39.0%38.7% | 26.3%28.2% | 31.4%32.6% | $2.552$1.834 | 47.136.2 | 11.8 min7.1 min | 4.95M3.13M |
| Sonnet 5.5 | 24.6%36.1% | 23.1%35.3% | 23.8%35.7% | $0.719$0.438 | 40.126.5 | 4.9 min3.3 min | 2.04M1.00M |
| Kimi K3 | 48.2%85.6% | 36.5%52.0% | 41.5%64.7% | $0.842$1.017 | 31.430.2 | 8.1 min7.9 min | 1.12M1.74M |
| GLM 5.3 | 33.1%47.2% | 28.0%43.2% | 30.3%45.1% | $0.797$0.454 | 36.426.7 | 6.0 min4.2 min | 2.07M1.03M |
| DeepSeek Flash | 23.9%43.3% | 22.3%30.3% | 23.1%35.7% | $0.186$0.179 | 45.662.7 | 4.4 min5.4 min | 13.96M11.54M |
- Recall
- 28.2% → 46.9%
- Precision
- 28.9% → 39.8%
- F1
- 28.5% → 43.1%
- Cost per search
- $7.696 → $5.541
- SQL calls per search
- 59.4 → 49.3
- Time per search
- 11.2 min → 9.8 min
- Input tokens per search
- 4.33M → 2.73M
- Recall
- 19.0% → 32.5%
- Precision
- 21.5% → 36.5%
- F1
- 20.2% → 34.4%
- Cost per search
- $1.665 → $1.264
- SQL calls per search
- 62.2 → 52.5
- Time per search
- 10.6 min → 4.8 min
- Input tokens per search
- 5.21M → 3.62M
- Recall
- 39.0% → 38.7%
- Precision
- 26.3% → 28.2%
- F1
- 31.4% → 32.6%
- Cost per search
- $2.552 → $1.834
- SQL calls per search
- 47.1 → 36.2
- Time per search
- 11.8 min → 7.1 min
- Input tokens per search
- 4.95M → 3.13M
- Recall
- 24.6% → 36.1%
- Precision
- 23.1% → 35.3%
- F1
- 23.8% → 35.7%
- Cost per search
- $0.719 → $0.438
- SQL calls per search
- 40.1 → 26.5
- Time per search
- 4.9 min → 3.3 min
- Input tokens per search
- 2.04M → 1.00M
- Recall
- 48.2% → 85.6%
- Precision
- 36.5% → 52.0%
- F1
- 41.5% → 64.7%
- Cost per search
- $0.842 → $1.017
- SQL calls per search
- 31.4 → 30.2
- Time per search
- 8.1 min → 7.9 min
- Input tokens per search
- 1.12M → 1.74M
- Recall
- 33.1% → 47.2%
- Precision
- 28.0% → 43.2%
- F1
- 30.3% → 45.1%
- Cost per search
- $0.797 → $0.454
- SQL calls per search
- 36.4 → 26.7
- Time per search
- 6.0 min → 4.2 min
- Input tokens per search
- 2.07M → 1.03M
- Recall
- 23.9% → 43.3%
- Precision
- 22.3% → 30.3%
- F1
- 23.1% → 35.7%
- Cost per search
- $0.186 → $0.179
- SQL calls per search
- 45.6 → 62.7
- Time per search
- 4.4 min → 5.4 min
- Input tokens per search
- 13.96M → 11.54M
When the index isn’t worth it
A data agent can always query raw traces with SQL, and evals-as-indexes are an addition on top of that. In two cases, plain SQL are probably all it needs:
- Your data is small. At low scale, the data agent finds shortcuts. If a preview of the whole trace corpus fits in a single model call, it can skim everything, spot the right keywords and search for them directly. For a small dataset, it can start by reading the 100 most recent spans and follow the relevant trace IDs for more context.
SELECT trace_id, span_id, span_name, input, output
FROM telemetry.spans
ORDER BY started_at DESC
LIMIT 100;
- Your evals aren’t reliable. Eval results only point the search somewhere, so their quality matters: an eval that over-fires buries real cases in noise, and one that under-fires hides them. Some open-source models take eval results at face value, which likely explains why their recall rose more than their answer quality. Check your evals against hand-labeled examples before relying on them for search.
Conclusion
As agents take on longer and more consequential work, they produce more data than anyone can read manually. Failures like a missed instruction, or a mistake repeated after the user corrected it, span many steps and rarely leave a keyword behind, which makes them hard to find reliably with keyword search at scale.
But most teams already run online monitoring evals to track agent quality, and those results can be reused as indexes. Every search then starts from behavior that’s already been evaluated, and the data agent spends its effort verifying evidence instead of hunting for it.
Our goal is to make agent data as easy to search as any other data: ask anything about what your agents did in production, and get an answer backed by evidence you can check.