Microsoft Agent Framework Agents Created: 18 Feb 2026 Updated: 18 Feb 2026

Background Responses in the Microsoft Agent Framework

Background responses are a feature of the Microsoft Agent Framework that allows long-running agent operations to be handled without blocking the caller. Instead of waiting for a response that may take a significant amount of time, the framework returns a continuation token that the caller can use to poll for completion or to resume an interrupted stream.

What Are Background Responses?

When an agent is given a complex task, the underlying AI model may take a long time to generate a complete response. Under normal operation the call blocks until the response is fully received. Background responses change this behaviour in two important ways:

  1. Polling (non-streaming): The first call may return immediately with a continuation token rather than a final answer. The caller then polls by making repeated calls, passing the previous token back each time, until the token becomes null which signals completion.
  2. Stream resumption (streaming): Every streaming update carries a continuation token. If the network connection is dropped or the process restarts, the stream can be resumed from exactly the last received update by passing that update's token to the next streaming call.

Project Setup

All three demos share a common agent factory defined in BackgroundResponsesSetup.cs. This class reads the OpenAI API key from the OPEN_AI_KEY environment variable and creates a ChatClientAgent that supports background responses via ChatClientAgentContinuationToken.

Required NuGet packages

<PackageReference Include="Microsoft.Agents.AI" Version="1.0.0-preview.*" />
<PackageReference Include="OpenAI" Version="2.*" />

BackgroundResponsesSetup.cs — shared agent factory

using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using OpenAI;

namespace MicrosoftAgentFrameworkLesson.ConsoleApp.Lesson6;

/// <summary>
/// Helper class for creating agents that support background responses.
///
/// Background responses are supported through AgentRunOptions.AllowBackgroundResponses.
/// The ChatClientAgent (created via AsAIAgent()) implements continuation tokens using
/// ChatClientAgentContinuationToken, which serializes conversation state for polling
/// and stream resumption scenarios.
/// </summary>
public static class BackgroundResponsesSetup
{
public static AIAgent CreateAgent(string modelName = "gpt-4o")
{
var apiKey = Environment.GetEnvironmentVariable("OPEN_AI_KEY");
if (string.IsNullOrWhiteSpace(apiKey))
throw new InvalidOperationException("Please set the OPEN_AI_KEY environment variable.");

return new OpenAIClient(apiKey)
.GetChatClient(modelName)
.AsIChatClient()
.AsAIAgent(new ChatClientAgentOptions
{
Name = "BackgroundResponseAgent",
Description = "An agent that demonstrates background responses and continuation tokens."
});
}
}

The .AsIChatClient() adapter converts the OpenAI ChatClient into the IChatClient interface required by .AsAIAgent(). This is the correct construction chain for ChatClientAgent.

Demo 1: Non-Streaming Background Response with Polling

This is the simplest pattern. A single prompt is sent with AllowBackgroundResponses = true. The initial call may return immediately (token is null) or may return a continuation token indicating the operation is still in progress. If a token is returned, the caller waits 2 seconds and polls until the token becomes null.

Demo1_NonStreamingPolling.cs

using Microsoft.Agents.AI;

namespace MicrosoftAgentFrameworkLesson.ConsoleApp.Lesson6;

/// <summary>
/// DEMO 1 — Non-Streaming Background Response with Polling
///
/// Demonstrates how to start a potentially long-running agent task
/// and poll for completion using a continuation token.
///
/// Key points:
/// • Set AgentRunOptions.AllowBackgroundResponses = true
/// • The initial RunAsync may return immediately (ContinuationToken == null)
/// or may return a token indicating the operation is still in progress.
/// • Poll by setting options.ContinuationToken and calling RunAsync again.
/// • When ContinuationToken is null the operation is complete.
/// </summary>
public static class Demo1_NonStreamingPolling
{
public static async Task RunAsync(AIAgent agent)
{
Console.WriteLine("╔═══════════════════════════════════════════════╗");
Console.WriteLine("║ DEMO 1 — Non-Streaming Background Response ║");
Console.WriteLine("╚═══════════════════════════════════════════════╝\n");

// Enable background responses in the run options
AgentRunOptions options = new()
{
AllowBackgroundResponses = true
};

// A session maintains conversation context across polling calls
AgentSession session = await agent.CreateSessionAsync();

const string prompt =
"Compare object-oriented, functional, and procedural programming paradigms. " +
"Include pros, cons, and real-world use cases for each.";

Console.WriteLine($"Prompt: {prompt}\n");
Console.WriteLine("Sending request (background responses enabled)...\n");

// Initial call — agent may complete immediately or start a background operation
AgentResponse response = await agent.RunAsync(prompt, session, options);

if (response.ContinuationToken is null)
{
// Agent completed without needing a background operation
Console.WriteLine("[Completed immediately — no polling needed]\n");
}
else
{
// Agent started a background operation; poll until complete
Console.WriteLine("[Background operation started — polling for completion]\n");
int pollCount = 0;

while (response.ContinuationToken is not null)
{
pollCount++;
Console.WriteLine($" Poll #{pollCount} — waiting 2 seconds before next check...");
await Task.Delay(TimeSpan.FromSeconds(2));

// Pass the token from the previous response to continue
options.ContinuationToken = response.ContinuationToken;
response = await agent.RunAsync(session, options);
}

Console.WriteLine($"\n[Completed after {pollCount} poll(s)]\n");
}

Console.WriteLine("Response:\n");
Console.WriteLine(response.Text);
Console.WriteLine("\n\n✓ Demo 1 completed successfully!");
}
}

How to run Demo 1

AIAgent agent = BackgroundResponsesSetup.CreateAgent();
await Demo1_NonStreamingPolling.RunAsync(agent);

Key points

  1. The initial RunAsync call passes the user message along with the session and options.
  2. Subsequent polling calls pass only the session and options — no new user message is needed.
  3. If ContinuationToken is null after the first call, the operation completed immediately and no polling loop runs.
  4. A fixed 2-second delay is used between polls. See Demo 3 for the production-ready exponential backoff variant.

Demo 2: Streaming with Interruption and Resumption

In streaming mode every AgentResponseUpdate carries a ContinuationToken that captures the exact position in the stream. Storing the last received token before a break allows you to resume the stream from exactly that position rather than re-running the full prompt from scratch.

Demo2_StreamingResumption.cs

using Microsoft.Agents.AI;

namespace MicrosoftAgentFrameworkLesson.ConsoleApp.Lesson6;

/// <summary>
/// DEMO 2 — Streaming with Interruption and Resumption
///
/// Demonstrates how background responses work in streaming mode.
/// Every AgentResponseUpdate carries a ContinuationToken that captures
/// the exact position in the stream. If the connection is interrupted,
/// the stream can be resumed from the last received token.
///
/// Key points:
/// • RunStreamingAsync yields AgentResponseUpdate items, each with a token.
/// • Store the last received update's ContinuationToken before interruption.
/// • Resume by setting options.ContinuationToken and calling RunStreamingAsync again.
/// • When an update's ContinuationToken is null the stream is complete.
/// </summary>
public static class Demo2_StreamingResumption
{
public static async Task RunAsync(AIAgent agent)
{
Console.WriteLine("╔═══════════════════════════════════════════════╗");
Console.WriteLine("║ DEMO 2 — Streaming with Resumption ║");
Console.WriteLine("╚═══════════════════════════════════════════════╝\n");

AgentRunOptions options = new()
{
AllowBackgroundResponses = true
};

AgentSession session = await agent.CreateSessionAsync();

const string prompt = "Write a short poem about the ocean and the stars.";
Console.WriteLine($"Prompt: {prompt}\n");
Console.WriteLine("Starting stream — will simulate an interruption after a few updates...\n");
Console.WriteLine("--- Stream output (Phase 1) ---");

AgentResponseUpdate? lastUpdate = null;
int updateCount = 0;
const int interruptAfter = 3; // simulate interruption after receiving 3 updates

// Phase 1: stream until simulated interruption
await foreach (var update in agent.RunStreamingAsync(prompt, session, options))
{
Console.Write(update.Text);
lastUpdate = update;
updateCount++;

if (updateCount >= interruptAfter)
{
Console.WriteLine($"\n\n[Simulated network interruption after {updateCount} update(s)]\n");
break;
}
}

if (lastUpdate?.ContinuationToken is null)
{
// Stream finished before the simulated interruption was triggered
Console.WriteLine("[Stream already complete — no resumption required]\n");
}
else
{
// Phase 2: resume from the point of interruption
Console.WriteLine("[Resuming stream using continuation token...]\n");
Console.WriteLine("--- Stream output (Phase 2 — resumed) ---");

options.ContinuationToken = lastUpdate.ContinuationToken;

await foreach (var update in agent.RunStreamingAsync(session, options))
{
Console.Write(update.Text);
}

Console.WriteLine("\n\n[Resumed stream completed successfully]");
}

Console.WriteLine("\n\n✓ Demo 2 completed successfully!");
}
}

How to run Demo 2

AIAgent agent = BackgroundResponsesSetup.CreateAgent();
await Demo2_StreamingResumption.RunAsync(agent);

Key points

  1. Always store the latest received AgentResponseUpdate before breaking out of the await foreach loop.
  2. The resumed stream continues from exactly the point where the interruption occurred — no content is repeated or skipped.
  3. The resumed call uses RunStreamingAsync(session, options) — no new user message is passed, only the session and options with the continuation token.
  4. If the stream finishes in fewer than interruptAfter updates, ContinuationToken is null and no resumption is needed.

Demo 3: Polling with Exponential Backoff

In a production application it is important not to hammer the service with polling requests. This demo doubles the wait interval on each unsuccessful poll up to a configurable maximum of 30 seconds. The formula is:

newDelay = Min(currentDelay * 2, maxDelaySeconds)

This means the sequence of delays is: 1s, 2s, 4s, 8s, 16s, 30s, 30s, 30s, …

Demo3_ExponentialBackoffPolling.cs

using Microsoft.Agents.AI;

namespace MicrosoftAgentFrameworkLesson.ConsoleApp.Lesson6;

/// <summary>
/// DEMO 3 — Polling with Exponential Backoff
///
/// Demonstrates a production-ready polling strategy that doubles the wait
/// interval on each poll attempt (up to a configurable maximum). This avoids
/// hammering the service while still detecting completion reasonably quickly.
///
/// Key points:
/// • Start with a short poll interval (e.g. 1 second).
/// • Double the delay on each attempt: 1s → 2s → 4s → 8s → 16s → 30s (cap).
/// • Stop polling when ContinuationToken becomes null.
/// • Store continuation tokens persistently in real apps to survive restarts.
/// </summary>
public static class Demo3_ExponentialBackoffPolling
{
public static async Task RunAsync(AIAgent agent)
{
Console.WriteLine("╔═══════════════════════════════════════════════╗");
Console.WriteLine("║ DEMO 3 — Polling with Exponential Backoff ║");
Console.WriteLine("╚═══════════════════════════════════════════════╝\n");

AgentRunOptions options = new()
{
AllowBackgroundResponses = true
};

AgentSession session = await agent.CreateSessionAsync();

const string prompt =
"Summarize the key milestones in the history of computing from 1940 to the present day.";

Console.WriteLine($"Prompt: {prompt}\n");
Console.WriteLine("Sending request with exponential backoff polling...\n");

AgentResponse response = await agent.RunAsync(prompt, session, options);

if (response.ContinuationToken is null)
{
Console.WriteLine("[Completed immediately — no polling needed]\n");
}
else
{
TimeSpan delay = TimeSpan.FromSeconds(1);
const double maxDelaySeconds = 30.0;
int pollCount = 0;

Console.WriteLine("[Background operation started]\n");

while (response.ContinuationToken is not null)
{
pollCount++;
Console.WriteLine($" Poll #{pollCount}: waiting {delay.TotalSeconds:F1}s before next check...");
await Task.Delay(delay);

options.ContinuationToken = response.ContinuationToken;
response = await agent.RunAsync(session, options);

// Double the delay, capped at maxDelaySeconds
delay = TimeSpan.FromSeconds(Math.Min(delay.TotalSeconds * 2, maxDelaySeconds));
}

Console.WriteLine($"\n[Completed after {pollCount} poll(s)]\n");
}

Console.WriteLine("Response:\n");
Console.WriteLine(response.Text);
Console.WriteLine("\n\n✓ Demo 3 completed successfully!");
}
}

How to run Demo 3

AIAgent agent = BackgroundResponsesSetup.CreateAgent();
await Demo3_ExponentialBackoffPolling.RunAsync(agent);

Why exponential backoff?

  1. Short tasks complete quickly so the first poll interval is small (1 second).
  2. Long tasks do not need frequent polling — backing off reduces unnecessary API calls.
  3. Capping the delay at 30 seconds prevents indefinitely long waits between polls.

Key API Types

Type / PropertyPurpose
AgentRunOptions.AllowBackgroundResponsesSet to true to allow the agent to initiate a background operation.
AgentRunOptions.ContinuationTokenPass the token from the previous response to poll or resume a stream.
AgentResponse.ContinuationTokenToken returned by RunAsync. null means the operation is complete.
AgentResponseUpdate.ContinuationTokenToken on each streaming update. Store the last one before interruption to enable resumption.
AIAgent.RunAsync(prompt, session, options)Initial call — sends the user message and starts the operation.
AIAgent.RunAsync(session, options)Polling call — does not require a new user message, just the session and continuation token.
AIAgent.RunStreamingAsync(prompt, session, options)Initial streaming call — sends the user message and begins streaming updates.
AIAgent.RunStreamingAsync(session, options)Resume streaming from the continuation token — does not require a new user message.
AIAgent.CreateSessionAsync()Creates an AgentSession that maintains conversation context across polling calls.
ChatClientAgentOptionsConfiguration for the agent name and description passed to AsAIAgent().

Best Practices

  1. Always check for null continuation tokens. Many tasks complete immediately even when background responses are enabled. Your code must handle both the immediate and deferred cases, as shown in all three demos.
  2. Use exponential backoff in production. Start with a 1-second poll interval and double it on each unsuccessful poll up to a maximum of 30 seconds (Demo 3). Use the fixed 2-second interval (Demo 1) only for simple demos.
  3. Persist continuation tokens. For operations that may span application restarts or separate HTTP requests, store the continuation token in durable storage (a database or cache) so you can resume after a crash.
  4. Set a timeout. Always define a maximum number of polls or a wall-clock timeout to prevent infinite waiting. The demos do not include a timeout to keep the code focused, but production code must have one.
  5. Use specific credentials in production. Prefer ManagedIdentityCredential over DefaultAzureCredential for Azure agents to avoid credential probing latency.

When to Use Background Responses

  1. Complex reasoning tasks that require significant processing time.
  2. Scenarios where client timeouts are shorter than the expected response time.
  3. Mobile or browser clients that may lose connectivity during a long operation.
  4. Tasks that should survive application restarts (e.g. report generation, batch summarization).

Limitations

  1. Full server-side background processing is only supported by OpenAI Responses API agents. The ChatClientAgent (used in these demos) simulates the same API surface client-side via ChatClientAgentContinuationToken.
  2. Continuation tokens encode conversation state and can be large — plan storage size accordingly.
  3. Tokens are agent-specific and cannot be transferred between different agent instances or types.

Reference

  1. Official documentation: Microsoft Learn - Background Responses
  2. Source code for this lesson: MicrosoftAgentFrameworkLesson.ConsoleApp/Lesson6/


Share this lesson: