scalable systems handle growth without breaking
← back · 2 min read ·

tool use error handling patterns in typescript

the agent doesn't care about exceptions. structure the result instead.

the agent calls your tool. your tool throws. now what?

the naive answer: the agent loop catches and continues. that’s what most harnesses do. but the agent doesn’t see the exception. it sees the tool result. if you let exceptions bubble, the harness has to convert them into a string, and now the model is reasoning about a stack trace instead of a structured error.

the result type

anthropic’s tool use docs describe the tool-result message as a free-form content field — they leave the shape to you. that’s the right call, but it puts the schema decision on you. the pattern i landed on after writing about ten of these — modeled on rust’s Result<T, E> — is:

type ToolResult<T> =
  | { ok: true; value: T }
  | { ok: false; error: { kind: ErrorKind; message: string; retriable: boolean } };

the agent gets the result back as json. it can read error.kind and decide whether to retry, switch strategies, or give up. it can read retriable and not loop forever on a 401. the model is good at this kind of reasoning when the data is structured. it’s bad at it when the data is a stack trace.

kind is your enum. mine usually has a small set: "not_found", "unauthorized", "rate_limited", "validation", "timeout", "unknown". the model behaves better with a small enum than with free text.

what i never do anymore

an extension layer is the right place for cross-cutting retry logic, so the tool handler stays focused on its one job. the tool returns a structured result; the harness decides whether to retry; the model never sees the difference.

structured errors aren’t about correctness. they’re about giving the model something it can reason about.

scalable labs·cvr 30091604·github·linkedin·hello@scalable.dk