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:
final reportAgent = ai.defineAgent( name: 'reportAgent', system: 'Create detailed research reports.', store: FileSessionStore('.sessions'),);
void main() { final router = Router(); router.post('/api/reportAgent', shelfHandler(reportAgent.action)); router.post('/api/reportAgent/getSnapshot', shelfHandler(reportAgent.getSnapshotDataAction)); router.post('/api/reportAgent/abort', shelfHandler(reportAgent.abortAgentAction));}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); }}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}');}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);}Only completed snapshots can be resumed.
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 shifts the pending snapshot status to aborted. The background worker observes this change and safely terminates the turn loop.