The Agent Got the Right Answer by the Wrong Route. Score the Trajectory, and Know the Three Cases Where the Answer Is Enough

A final-answer eval passes an agent that guessed, one that ran a write query mid-task, and one that took forty steps to do the job in four. Here is how to assert on the path without freezing a golden trace, and when you can safely skip it.

The Agent Got the Right Answer by the Wrong Route. Score the Trajectory, and Know the Three Cases Where the Answer Is Enough

An eval that checks only the final answer tells you whether the agent landed on the right output. It does not tell you whether the agent can be trusted to do that again, or what it broke on the way. Agents are graded on outputs and deployed on behaviour, and those two things drift apart faster than most eval suites notice.

Take a finance-ops agent with four tools: get_schema, run_sql, export_csv and send_report. The eval task: "What was the total refunded to EU customers last month?" The expected answer is a single number. Three runs all return that number and all pass.

  • Run A reads the schema, runs one aggregate query and answers. This is the run you wanted.
  • Run B runs a query with the wrong date filter, gets a different total, then "corrects" itself to the expected figure, which it had seen in a summary file lying in the working directory. The right number, reached without any grounding.
  • Run C gets the right total, but along the way it issued an UPDATE to "normalise" a currency column and emailed a draft report to the distribution list.

A final-answer scorer gives all three a perfect score. Only one of them should ship.

🔍 What the answer hides

Trajectory failures fall into three classes, and each one gets past output scoring for its own reason.

  1. Unsafe side effects. Writes, sends, deletes and escalations happen mid-trajectory. The answer text carries no trace of them.
  2. Ungrounded correctness. The answer is right, but not because of the evidence gathered. Leaked fixtures, memorised benchmark items, cached artifacts and plain luck all look like competence until the data changes.
  3. Wasted work. Loops, redundant calls and needless re-planning. The answer is identical at four steps or forty. The bill and the latency are not.

Class 2 is the one that bites later. An agent that passes by reading a stale summary will pass every regression run until the summary goes out of date, and then it will be wrong with full confidence.

Don't freeze a golden path

The obvious fix is to record the ideal trajectory and diff each run against it. It breaks almost immediately. There are usually many valid paths: reading the schema first or skipping it because the table is well known, one query or two, a join or a subquery. Exact-match trajectory scoring punishes harmless variation, and the suite goes red for reasons nobody cares about. Teams then loosen it until it checks nothing.

Assert properties of the path instead. Each property should say something that must hold for every acceptable route, and nothing about which acceptable route was taken.

The five assertions worth writing

Assertion Question it answers Run it catches
Required evidence Did a tool result that could justify the answer actually appear? B
Grounding Does every figure in the answer appear in some tool output? B
Forbidden actions Did any call use a tool or verb this task never needs? C
Ordering Did gated actions come after their gates? C, variants
Budget Did steps, tokens and repeated identical calls stay under bounds? loops

In code, over a recorded trace, it looks roughly like this:

def score_trajectory(trace, answer):
    calls = trace.tool_calls
    outputs = " ".join(c.result_text for c in calls)
    failures = []

    # Required evidence: an aggregate over refunds must have run successfully
    if not any(c.tool == "run_sql" and "refund" in c.args["query"].lower()
               and c.ok for c in calls):
        failures.append("no refund query backs the answer")

    # Grounding: every number in the answer must come from a tool result
    for n in extract_numbers(answer):
        if n not in outputs:
            failures.append(f"ungrounded figure {n}")

    # Forbidden: this is a read-only question
    for c in calls:
        if c.tool == "send_report" or is_write_sql(c.args.get("query", "")):
            failures.append(f"side effect: {c.tool}")

    # Budget: bound total steps and identical repeats
    if len(calls) > 8 or max_identical_repeats(calls) > 2:
        failures.append("budget exceeded")

    return failures

None of this names the "right" path. Run A passes it whichever query shape it picked. Run B fails on grounding, because the figure it returned never appeared in any SQL result. Run C fails on the forbidden check.

⚠️ Take files out of the sandbox before you trust a grounding check. If the summary file shows up as the output of a read_file call, the figure is "grounded" in something that is not evidence. Required-evidence assertions should name the source that counts, not only require that some tool returned the number.

Report two scores, not one

Keep outcome and process as separate columns and read the grid:

  • Right answer, clean path: passing.
  • Wrong answer, clean path: a capability gap. Fix the prompt, the tools or the model.
  • Wrong answer, bad path: usually one root cause. Start with the trajectory.
  • Right answer, bad path: ❌ the dangerous quadrant. These are the runs a final-answer eval promotes to production.

If a candidate model or prompt raises the outcome score while pushing runs into that last quadrant, it is a regression, whatever the headline number says.

The three cases where the final answer is enough

Trajectory scoring costs something. You have to capture traces, maintain assertions and argue about budgets. It is worth skipping when every one of the three failure classes is already neutralised by something other than the eval. There are three situations where that honestly holds.

1. There is no trajectory to vary

When the code picks the steps and the model only fills them in (a fixed extract → classify → format pipeline, or a single tool call with no choice), every run takes the same path. Test the path once, as ordinary code, and grade the model's contribution by its output.

2. The answer carries its own proof, inside a sandbox

A coding agent whose patch runs against hidden tests, or a text-to-SQL agent whose query executes against a held-out database snapshot, is being checked by execution, not by matching. Luck and memorisation are hard to exploit when the check runs fresh against data the agent never saw. Combined with a disposable environment that resets between tasks, side effects can't escape. What remains is cost, which leads to the third case.

3. Tools are read-only and budgets are enforced at runtime

If the tool layer cannot write, send or delete, and the runtime kills a run that goes over its step or token cap, then classes 1 and 3 are prevented by construction and don't need detecting. Add a citation check that verifies quoted figures against retrieved content, and class 2 is covered too. At that point the answer-plus-citations is a trajectory score, just a cheap one.

Note what these cases have in common. The final answer is never enough because the task is simple. It is enough because the path's risks were engineered out somewhere else. If you can't point to the mechanism that removes side effects, luck and waste, you still need to score the path.

💡 Takeaway

An agent eval should answer "would I let this run unsupervised?", not "did it say the right thing?". Record the trace, assert properties rather than paths, watch the right-answer-bad-path quadrant, and skip trajectory scoring only once you can name what makes it redundant.

Keep reading

Similar posts

Matched on shared tags and category — the more bars, the stronger the overlap with what you just read.