Background execution
Background execution lets a client submit work, disconnect, and return later. It requires server-managed state because the server needs a snapshot to track progress, liveness, completion, failure, and cancellation.
Use background execution for work that may outlive the request or the browser tab, such as report generation, long research tasks, multi-step planning, or tool-heavy workflows. Keep normal foreground streaming for short turns where the user is actively waiting and foreground cancellation is enough.
Store support matters for background work. See Session stores for which stores support snapshot status changes and aborting detached work.
Server requirements
Section titled “Server requirements”Configure a store and expose companion endpoints when serving a remote agent:
import 'package:genkit/genkit.dart';import 'package:genkit/experimental.dart';import 'package:genkit/experimental_io.dart';import 'package:genkit/io.dart';
final reportAgent = ai.defineAgent( name: 'reportAgent', system: 'Create detailed research reports.', store: FileSessionStore('.sessions'),);
void main() async { // Mounts /api/reportAgent plus its /getSnapshot and /abort companions. final genkit = GenkitRouter() ..addAgent(reportAgent, path: '/api/reportAgent'); await genkit.serve();}The runtime writes a pending snapshot and refreshes its heartbeat while the background thread processes the turn.
Detach from a turn
Section titled “Detach from a turn”Submit a background task from the client using detach() on the AgentChat:
final chat = reportAgent.chat(sessionId: 'report-123');final task = await chat.detach(text: 'Write the quarterly market report.');
// Save snapshotId so you can poll or abort it laterfinal snapshotId = task.snapshotId;Poll or wait
Section titled “Poll or wait”Use poll() to yield status snapshots over a Stream until the task reaches a terminal status:
await for (final snapshot in task.poll(interval: Duration(milliseconds: 1500))) { print('Current Status: ${snapshot.status?.value}');
if (snapshot.status?.value == 'completed') { final report = snapshot.messages.last.content.first.text; print(report); }}Pass metadataOnly: true to task.poll(...) or reportAgent.getSnapshot(...) when you only need status, timestamps, or error info without transferring the full conversation state:
final statusSnap = await reportAgent.getSnapshot( snapshotId: snapshotId, metadataOnly: true,);Use wait() to block execution as a Future until completion:
final finalSnapshot = await task.wait(interval: Duration(milliseconds: 1500));
if (finalSnapshot.status?.value == 'failed') { print('Task failed: ${finalSnapshot.error?.message}');}Non-terminal statuses are pending and aborting. Terminal statuses are completed, failed, aborted, and expired.
Reconnect by snapshot ID
Section titled “Reconnect by snapshot ID”To reconnect and inspect or resume a detached task from a different client process, read the stored snapshot ID and load it:
final snapshot = await reportAgent.getSnapshot(snapshotId: snapshotId);
if (snapshot?.status?.value == 'completed') { final chat = await reportAgent.loadChat(snapshotId: snapshotId); final res = await chat.send(text: 'Summarize this report.'); print(res.text);} else if (snapshot?.status?.value == 'failed' || snapshot?.status?.value == 'aborted') { // Failed and aborted snapshots preserve last-good progress and can be rerun: final rerun = reportAgent.chat(snapshotId: snapshotId); await rerun.send();}completed, failed, and aborted snapshots are valid resume points (expired is terminal and not resumable; resume from its parentId instead).
Abort work
Section titled “Abort work”Cancel a pending task from the client using task.abort():
await task.abort();Or abort directly by snapshot ID from the AgentApi handle:
await reportAgent.abort(snapshotId);Aborting transitions a running pending snapshot to aborting while the background worker unwinds and saves its completed turns, then commits an aborted snapshot holding the last-good state.