Final-answer accuracy hides the failures that hurt in production: unnecessary tool calls, loops, skipped verification. Evaluate the trajectory.
Metrics worth tracking per test case:
- Task success — programmatic assertion on the end state, not a vibe check. - Tool precision/recall — were the required tools called, and only those? - Steps and tokens to success (cost proxies). - Recovery rate — success after an injected tool failure.
ts
const cases = [{
goal: "Refund order 8123 and email the customer",
mustCall: ["get_order", "create_refund", "send_email"],
mustNotCall: ["delete_order"],
assert: async () => (await db.refunds.findByOrder(8123))?.status === "issued",
}];
for (const c of cases) {
const trace = await runAgentTraced(c.goal);
expect(c.mustCall.every((t) => trace.calls.includes(t))).toBe(true);
expect(c.mustNotCall.some((t) => trace.calls.includes(t))).toBe(false);
expect(await c.assert()).toBe(true);
}Run this suite in CI against mocked tools so it is deterministic, then a smaller suite nightly against staging services.
Exercise: add a case where `create_refund` fails once with a 500 and assert the agent retries and still succeeds.