Agent Autopsy

building
  • autopsies6
  • traces12
  • model calls0
  • cost to run$0

autopsies · 02 of 06 · ticket T-017

The cost blow-up.

Every step is different and reasonable. Every step re-sends everything, and the retrieval tool returns the whole knowledge base.

the ticket · T-017 · email · account ACC-1017 · synthetic

Invoice shows 12 seats but we have 10

INV-5017 charged us for 12 seats. We removed two people in August and have had 10 since. Please correct it.

01

The failure.

A seat-count dispute. The agent looks up the account, the invoice, and then searches the knowledge base six times with slightly different queries. search_kb has no top-k, so each search returns every article that matches at all, full text, and the loop appends all of it to the context. By the eleventh model call the request is 12,800 tokens; the ticket costs 22 times the median. No single step looks wrong, and the step limit never fires, because there are only twelve of them.

what to watch for The tokens-in column doubles, then doubles again. Watch the cost of each individual model call, not just the total.

before · the failure · T-017 · claude-haiku-4-5

Keys: arrows step, Space plays, Home and End jump, F finds the annotated step.

step
0 / 23
model calls
0
tokens in
0
tokens out
0
cost so far
$0.0000
latency
0 ms

Step 0 of 23, cost so far $0.0000.

cost, against the dearer of the two runs

tokens in, per model call

Input tokens per model call

    Press Play, or step with the arrow keys, to reveal the trace.

    02

    What it cost.

    The two runs side by side. Same ticket, same tools, same prices; one safeguard apart.

    cost, failure
    $0.0834
    cost, fix
    $0.0093
    tokens in
    80,857 → 7,782
    model calls
    12 → 6

    The failure cost 8.9 times the fixed run for the same ticket.

    03

    The fix: idempotency key, and a tool that returns what was asked.

    search_kb goes back to its two best articles. Every tool call is keyed on the tool name and its normalised arguments; a repeat is answered from the earlier result as a pointer, so the model can still see it, the context does not grow by it, and a flaky tool is never retried with a side effect. A per-task cost ceiling belongs behind this as the backstop: this run finished at $0.009, and a ceiling of $0.02 would have stopped the original at step 8.

    def cached(run):
        def call(args):
            key = canonical(name, args)
            if key in cache:
                return {"cached": True, "same_as_call": cache[key]}
            cache[key] = call_number
            return run(args)
        return call

    Before: search_kb with no top-k: every article that matches at all comes back with its full body, and the loop appends all of it to the context on every step; MAX_MODEL_CALLS at 20, so the step limit never fires. After: search_kb back to its two best articles, plus an idempotency key on every tool call: a repeat of the same call returns a pointer to the earlier result instead of the result again, so the context stops growing by it.

    04

    The same ticket, with the fix in.

    The identical scripted model, the identical tools; only the safeguard differs.

    after · the fix · T-017 · claude-haiku-4-5

    Keys: arrows step, Space plays, Home and End jump, F finds the annotated step.

    step
    0 / 11
    model calls
    0
    tokens in
    0
    tokens out
    0
    cost so far
    $0.0000
    latency
    0 ms

    Step 0 of 11, cost so far $0.0000.

    cost, against the dearer of the two runs

    tokens in, per model call

    Input tokens per model call

      Press Play, or step with the arrow keys, to reveal the trace.

      05

      How the eval would have caught this.

      The response and hallucination cases pass before and after: the reply was right both times, which is the point of this autopsy. The trajectory case fails both times because the model asked for search_kb more than once; after the fix that repeat cost nothing. The eval that catches a cost blow-up is the cost line in results/latest.json, compared against the per-task budget in the contract.

      casemetricbeforeafterwhy
      T-017-trajectorytool trajectoryfailfail

      before: called ['lookup_account', 'lookup_invoice', 'search_kb', 'search_kb', 'search_kb', 'lookup_invoice', 'search_kb', 'search_kb', 'lookup_account', 'search_kb', 'issue_refund'], expected ['lookup_account', 'lookup_invoice', 'search_kb', 'issue_refund']

      after: called ['lookup_account', 'lookup_invoice', 'search_kb', 'search_kb', 'issue_refund'], expected ['lookup_account', 'lookup_invoice', 'search_kb', 'issue_refund']

      T-017-responseresponse matchpasspasspasses both times
      T-017-hallucinationhallucinationpasspasspasses both times

      Gate: demo-bench-starter ci.yml, job 'api quality and evals': uv run python -m evals.run (exits 1 under any threshold)