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 an immediate exception (e.g.,
AgentInitErrororGenkitException) before a turn starts. Fix the parameters, such as by not sendingstateto a store-backed agent. - Failed turns throw
AgentErrorcontaining status, details, the latest snapshot ID (err.snapshotId), and the recoverable last-good state (err.response.raw.stateholds the fullSessionState;err.stateholds the typed custom state). - Aborted turns (a cancelled request or hitting
maxTurns) resolve to a response withfinishReason: AgentFinishReason.abortedrather than throwingAgentError, and also commit a rerunnable snapshot (res.snapshotId) with the last-good state. - Background/detached failures appear as snapshot status
failed,aborted, orexpired. Inspect the snapshot’s error property and rerun from it. - Tool domain problems should return structured tool outputs when the model can recover. Let the model explain the issue or ask the user for corrected input.
Handle failed and aborted turns
Section titled “Handle failed and aborted turns”A failed or aborted turn commits a rerunnable snapshot on server-managed agents: it carries the structured error (on failure) plus the last-good history, ending before the turn that failed or aborted. Resume that snapshot with chat(snapshotId: ...), then send an empty message to re-drive the same turn, or send new text to steer the retry.
try { final res = await chat.send(text: 'Look up order 123.'); if (res.finishReason == AgentFinishReason.aborted && res.snapshotId != null) { // Aborted turn (cancelled or hit maxTurns): rerun from the committed snapshot. final rerun = agent.chat(snapshotId: res.snapshotId); await rerun.send(); // or: rerun.send(text: 'Try a shorter summary.') } else { print(res.text); }} on AgentError catch (err) { print('Turn failed: ${err.status}'); print('Error details: ${err.message}');
if (err.snapshotId != null) { // Server-managed agent: rerun the failed snapshot. send() with no message // re-drives the same turn; pass text to steer the retry. final rerun = agent.chat(snapshotId: err.snapshotId); await rerun.send(); // or: rerun.send(text: 'Try again with order 456.') } else { // Client-managed agent: the existing `chat` instance automatically rolls // back the failed turn's user message, or you can construct a fresh chat // from the complete SessionState (`err.response.raw.state`). final recoveryChat = agent.chat(state: err.response.raw.state); await recoveryChat.send(text: 'Try again with order 456.'); }}For streaming turns, catch errors around the chunk stream consumption and the final response Future. The stream rethrows failed-turn errors after yielding any chunks that arrived before the failure occurred.
final turn = chat.sendStream(text: 'Write a long report.');
try { await for (final chunk in turn.stream) { render(chunk); } await turn.response;} on AgentError catch (err) { showFailure(err);}Tool exceptions
Section titled “Tool exceptions”Throw an exception from a tool when the system cannot safely proceed, such as a database outage or auth failure:
final lookupOrder = ai.defineTool( name: 'lookupOrder', description: 'Looks up an order by ID.', inputSchema: LookupOrderInput.$schema, outputSchema: Order.$schema, fn: (input, _) async { final order = await db.orders.find(input.orderId); if (order == null) { throw Exception('Order ${input.orderId} was not found.'); } return order; },);When the error is soft and the model has a chance to recover (e.g. invalid user input), return structured output:
if (order == null) { return { 'ok': false, 'reason': 'ORDER_NOT_FOUND', 'message': 'Ask the user to check the order ID.', };}