Skip to content

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.

  • Rejected init returns a non-nil Go error from Connect, Run, or RunText. Fix the caller or selected resume source.
  • Failed turns return AgentOutput with FinishReasonFailed and Error. Resume from State for client-managed agents or SnapshotID for 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, or expired. Inspect snapshot error details or retry from a completed snapshot.
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.

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.

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
}

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.