Agent interrupts
Interrupts let a tool pause execution and return a tool request to the client. The client can approve, reject, provide missing data, refresh credentials, or ask the user a question, then resume the turn.
Use interrupts when the model can decide that outside input is needed but the tool should not proceed automatically. Common cases include human approval, missing user choices, risky operations, payments, external auth, and actions that need a fresh environment check.
Define an interrupt
Section titled “Define an interrupt”In Genkit Dart, interrupts are modeled as tools that invoke ctx.interrupt() to pause execution and prompt the client for external validation or missing inputs.
import 'package:genkit/genkit.dart';import 'package:schemantic/schemantic.dart';
part 'banking_agent.g.dart';
@Schema()abstract class $UserApprovalInput { @Field(description: 'The action to be approved') String get action; @Field(description: 'Details about the action') String get details;}
final userApproval = ai.defineTool( name: 'userApproval', description: 'Ask the user for approval before proceeding with a sensitive action.', inputSchema: UserApprovalInput.$schema, // No outputSchema needed: the output is provided by the client on resume fn: (input, ctx) async => ctx.interrupt(),);
final bankingAgent = ai.defineAgent( name: 'bankingAgent', system: 'If the user wants to transfer money, ALWAYS use userApproval.', tools: [userApproval, transferMoney],);Receive interrupts
Section titled “Receive interrupts”Interrupted tool requests surface on res.interrupts at turn end, or can be checked as tool requests inside stream chunks.
final res = await chat.send(text: 'Transfer $500 to savings.');
for (final interrupt in res.interrupts) { print(interrupt.name); print(interrupt.input);}When an interrupt occurs, the agent’s final turn finishReason resolves as interrupted.
Respond pattern
Section titled “Respond pattern”Use respond() when the client has final tool outputs ready. This builder returns a ToolResponsePart that you pass to chat.resume() via its respond parameter. This resolves the tool call directly without re-executing the tool function on the server.
final res = await chat.send(text: 'Transfer $500 to savings.');final approvals = res.interrupts.where((i) => i.name == 'userApproval').toList();
if (approvals.isNotEmpty) { final continued = await chat.resume( respond: [ approvals.first.respond({'approved': true, 'feedback': 'Looks good!'}), ], ); print(continued.text);}Restart pattern
Section titled “Restart pattern”Use restart() when the tool itself should execute again on the server after state, parameters, or external configurations are corrected by the user.
final res = await chat.send(text: 'Deploy code to production.');final deployInterrupts = res.interrupts.where((i) => i.name == 'deployApproval').toList();
if (deployInterrupts.isNotEmpty) { final continued = await chat.resume( restart: [ deployInterrupts.first.restart(), ], ); print(continued.text);}The restart builder preserves the original tool input and forces the server-side tool function to execute again with those parameters.
Resume validation
Section titled “Resume validation”To prevent client-side spoofing, the runtime validates that every respond and restart entry matches an active paused tool call by name and unique reference ID.