Skip to content

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.

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.

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 later
final snapshotId = task.snapshotId;

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.

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.

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.