Agent error handling
Genkit agent failures have different recovery paths depending on when they happen. A request can be rejected before an invocation starts, a turn can fail after state has been loaded, or a tool can return a domain-level result that the model can handle.
Failure categories
Section titled “Failure categories”- Init misuse throws before a turn starts. Fix the caller, such as by not sending
stateto a store-backed agent. - Failed turns throw
AgentErrorwithresponse,status,details,state, andsnapshotId. Resume from the last-good state or snapshot. - Foreground aborts resolve with
finishReason: 'aborted'. Let the user retry or revise the request. - Background failures appear as snapshot status
failed,aborted, orexpired. Show the status, inspect snapshot error details, and retry from a completed snapshot when possible. - Domain-level tool problems should be structured tool output when the model can recover. Let the model explain the issue or ask the user for corrected input.
Handle failed turns
Section titled “Handle failed turns”import { AgentError } from 'genkit/beta/client';
try { const res = await chat.send('Look up order 123.'); console.log(res.text);} catch (err) { if (err instanceof AgentError) { console.error(err.status); console.error(err.details); console.error(err.snapshotId); console.error(err.state);
const recoveryChat = err.snapshotId ? await agent.loadChat({ snapshotId: err.snapshotId }) : agent.chat({ state: err.response.raw.state });
await recoveryChat.send('Try again with order 456.'); } else { throw err; }}For streaming turns, catch errors around stream consumption and the final response. The stream rethrows failed-turn errors after yielding any available chunks.
const turn = chat.sendStream('Write a report.');
try { for await (const chunk of turn.stream) { render(chunk); }
await turn.response;} catch (err) { showFailure(err);}Tool exceptions
Section titled “Tool exceptions”Throw from a tool when the system cannot safely continue, such as a database outage, auth failure, or invariant violation.
const lookupOrder = ai.defineTool( { name: 'lookupOrder', description: 'Looks up an order by ID.', inputSchema: z.object({ orderId: z.string() }), }, async ({ orderId }) => { const order = await db.orders.find(orderId); if (!order) { throw new Error(`Order ${orderId} was not found.`); } return order; },);Return structured data when the model can recover:
return { ok: false, reason: 'ORDER_NOT_FOUND', message: 'Ask the user to check the order ID.',};Response validation
Section titled “Response validation”Call res.assertValid() when a caller requires a model message and wants blocked responses to throw.
const res = await chat.send('Write the summary.');res.assertValid();Failure categories
Section titled “Failure categories”- Rejected init returns a non-nil Go error from
Connect,Run, orRunText. Fix the caller or selected resume source. - Failed turns return
AgentOutputwithFinishReasonFailedandError. Resume fromStatefor client-managed agents orSnapshotIDfor server-managed agents. - Tool domain problems should return structured tool output when the coordinator or model can recover.
- Background failures appear as snapshot status
failed,aborted, orexpired. Inspect snapshot error details or retry from a completed snapshot.
Check both error channels
Section titled “Check both error channels”import aix "github.com/firebase/genkit/go/ai/exp"out, err := agent.RunText(ctx, "Look up order 123.")if err != nil { return fmt.Errorf("agent invocation did not start: %w", err)}
if out.FinishReason == aix.AgentFinishReasonFailed { if out.Error != nil { return fmt.Errorf("agent turn failed: %s: %s", out.Error.Status, out.Error.Message) } return fmt.Errorf("agent turn failed")}
fmt.Println(out.Message.Text())A non-nil Go error means the invocation was rejected or could not produce an output. A failed turn is in-band so the output can include recovery state or a recovery snapshot ID.
Resume after failure
Section titled “Resume after failure”For client-managed agents, pass the last-good state from the failed output:
retry, err := agent.RunText(ctx, "Try order 456.", aix.WithState(out.State),)For server-managed agents, resume from the recovery snapshot:
retry, err := agent.RunText(ctx, "Try order 456.", aix.WithSnapshotID[OrderState](out.SnapshotID),)Only completed snapshots are resumable. If the latest session snapshot is failed, aborted, or pending, use a known completed snapshot ID or wait for the pending work to finish.
Tool errors
Section titled “Tool errors”Return a Go error when the tool cannot safely produce a meaningful result:
func lookupOrder(ctx *ai.ToolContext, input LookupOrderInput) (LookupOrderOutput, error) { order, err := db.LookupOrder(ctx, input.OrderID) if err != nil { // The lookup itself failed (e.g. the database is unreachable); this is // a tool failure, distinct from a found-but-empty result below. return LookupOrderOutput{}, fmt.Errorf("look up order %q: %w", input.OrderID, err) } if order == nil { return LookupOrderOutput{}, core.NewError(core.NOT_FOUND, "order %q not found", input.OrderID) } return LookupOrderOutput{OK: true, Order: order}, nil}Return structured output when the model should recover:
if order == nil { return LookupOrderOutput{ OK: false, Reason: "ORDER_NOT_FOUND", Message: "Ask the user to check the order ID.", }, nil}Transform failures
Section titled “Transform failures”State and stream transforms fail closed. If a transform returns an error, the read or invocation fails instead of exposing unredacted data. Use this behavior for authorization-dependent redaction where returning raw state would leak sensitive information.