diff --git a/dotnet/Directory.Packages.props b/dotnet/Directory.Packages.props
index 08532904c4..a643a6cc89 100644
--- a/dotnet/Directory.Packages.props
+++ b/dotnet/Directory.Packages.props
@@ -21,6 +21,7 @@
+
diff --git a/dotnet/agent-framework-dotnet.slnx b/dotnet/agent-framework-dotnet.slnx
index 95812435d8..de9ab13e9b 100644
--- a/dotnet/agent-framework-dotnet.slnx
+++ b/dotnet/agent-framework-dotnet.slnx
@@ -265,6 +265,7 @@
+
diff --git a/dotnet/samples/AgentWebChat/AgentWebChat.AgentHost/AgentWebChat.AgentHost.csproj b/dotnet/samples/AgentWebChat/AgentWebChat.AgentHost/AgentWebChat.AgentHost.csproj
index 802c864c1f..7347a642ae 100644
--- a/dotnet/samples/AgentWebChat/AgentWebChat.AgentHost/AgentWebChat.AgentHost.csproj
+++ b/dotnet/samples/AgentWebChat/AgentWebChat.AgentHost/AgentWebChat.AgentHost.csproj
@@ -8,6 +8,7 @@
+
diff --git a/dotnet/samples/AgentWebChat/AgentWebChat.AgentHost/Program.cs b/dotnet/samples/AgentWebChat/AgentWebChat.AgentHost/Program.cs
index 571b07b1d5..81430070eb 100644
--- a/dotnet/samples/AgentWebChat/AgentWebChat.AgentHost/Program.cs
+++ b/dotnet/samples/AgentWebChat/AgentWebChat.AgentHost/Program.cs
@@ -3,6 +3,7 @@
using A2A.AspNetCore;
using AgentWebChat.AgentHost;
using AgentWebChat.AgentHost.Utilities;
+using Azure.Storage.Blobs;
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.Hosting;
using Microsoft.Agents.AI.Workflows;
@@ -27,6 +28,13 @@ builder.AddAIAgent(
chatClientServiceKey: "chat-model")
.WithInMemoryThreadStore();
+builder.AddAIAgent(
+ "gambler",
+ instructions: "You are a gambler. Talk like a gambler.",
+ description: "An agent which gambles",
+ chatClientServiceKey: "chat-model")
+ .WithAzureBlobThreadStore(sp => new BlobContainerClient(connectionString: "UseDevelopmentStorage=true", "agent-threads"));
+
builder.AddAIAgent("knights-and-knaves", (sp, key) =>
{
var chatClient = sp.GetRequiredKeyedService("chat-model");
diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.AzureStorage/Blob/AzureBlobAgentThreadStore.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.AzureStorage/Blob/AzureBlobAgentThreadStore.cs
new file mode 100644
index 0000000000..2e14042dac
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Hosting.AzureStorage/Blob/AzureBlobAgentThreadStore.cs
@@ -0,0 +1,158 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.IO;
+using System.Text;
+using System.Text.Json;
+using System.Threading;
+using System.Threading.Tasks;
+using Azure;
+using Azure.Storage.Blobs;
+using Azure.Storage.Blobs.Models;
+using Microsoft.Shared.Diagnostics;
+
+namespace Microsoft.Agents.AI.Hosting.AzureStorage.Blob;
+
+internal sealed class AzureBlobAgentThreadStore : AgentThreadStore
+{
+ private static readonly BlobOpenWriteOptions s_uploadJsonOptions = new()
+ {
+ HttpHeaders = new()
+ {
+ ContentType = "application/json"
+ }
+ };
+
+ private readonly BlobContainerClient _containerClient;
+ private readonly AzureBlobAgentThreadStoreOptions _options;
+ private bool _containerInitialized;
+
+ ///
+ /// Initializes a new instance of the class using a .
+ ///
+ /// The blob container client to use for storage operations.
+ /// Optional configuration options. If , default options will be used.
+ /// is .
+ public AzureBlobAgentThreadStore(BlobContainerClient containerClient, AzureBlobAgentThreadStoreOptions? options = null)
+ {
+ this._containerClient = containerClient ?? throw new ArgumentNullException(nameof(containerClient));
+ this._options = options ?? new AzureBlobAgentThreadStoreOptions();
+ }
+
+ ///
+ public override async ValueTask SaveThreadAsync(
+ AIAgent agent,
+ string conversationId,
+ AgentThread thread,
+ CancellationToken cancellationToken = default)
+ {
+ Throw.IfNull(agent);
+ Throw.IfNull(conversationId);
+ Throw.IfNull(thread);
+
+ await this.EnsureContainerExistsAsync(cancellationToken).ConfigureAwait(false);
+
+ var blobName = this.GetBlobName(agent.Id, conversationId);
+ var blobClient = this._containerClient.GetBlobClient(blobName);
+
+ JsonElement serializedThread = thread.Serialize();
+#pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task
+ await using Stream stream = await blobClient.OpenWriteAsync(overwrite: true, s_uploadJsonOptions, cancellationToken).ConfigureAwait(false);
+ await using Utf8JsonWriter writer = new(stream);
+#pragma warning restore CA2007 // Consider calling ConfigureAwait on the awaited task
+
+ serializedThread.WriteTo(writer);
+ await writer.FlushAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ ///
+ public override async ValueTask GetThreadAsync(
+ AIAgent agent,
+ string conversationId,
+ CancellationToken cancellationToken = default)
+ {
+ Throw.IfNull(agent);
+ Throw.IfNull(conversationId);
+
+ await this.EnsureContainerExistsAsync(cancellationToken).ConfigureAwait(false);
+
+ var blobName = this.GetBlobName(agent.Id, conversationId);
+ var blobClient = this._containerClient.GetBlobClient(blobName);
+
+ try
+ {
+ var stream = await blobClient.OpenReadAsync(cancellationToken: cancellationToken).ConfigureAwait(false);
+ var jsonDoc = await JsonDocument.ParseAsync(stream, cancellationToken: cancellationToken).ConfigureAwait(false);
+ var serializedThread = jsonDoc.RootElement;
+
+ return agent.DeserializeThread(serializedThread);
+ }
+ catch (RequestFailedException ex) when (ex.Status == 404)
+ {
+ // Blob doesn't exist, return a new thread
+ return agent.GetNewThread();
+ }
+ }
+
+ ///
+ /// Ensures that the blob container exists, creating it if necessary.
+ ///
+ private async Task EnsureContainerExistsAsync(CancellationToken cancellationToken)
+ {
+ if (this._containerInitialized)
+ {
+ return;
+ }
+
+ if (!this._containerInitialized)
+ {
+ if (this._options.CreateContainerIfNotExists)
+ {
+ await this._containerClient.CreateIfNotExistsAsync(cancellationToken: cancellationToken).ConfigureAwait(false);
+ }
+
+ this._containerInitialized = true;
+ }
+ }
+
+ ///
+ /// Generates the blob name for a given agent and conversation.
+ ///
+ private string GetBlobName(string agentId, string conversationId)
+ {
+ string sanitizedAgentId = this.SanitizeBlobNameSegment(agentId);
+ string sanitizedConversationId = this.SanitizeBlobNameSegment(conversationId);
+ string baseName = $"{sanitizedAgentId}/{sanitizedConversationId}.json";
+
+ return string.IsNullOrEmpty(this._options.BlobNamePrefix)
+ ? baseName
+ : $"{this._options.BlobNamePrefix.TrimEnd('/')}/{baseName}";
+ }
+
+ ///
+ /// Sanitizes a string to be safe for use in blob names.
+ ///
+ private string SanitizeBlobNameSegment(string input)
+ {
+ if (string.IsNullOrWhiteSpace(input))
+ {
+ return "default";
+ }
+
+ // Replace invalid characters with underscore
+ StringBuilder builder = new(input.Length);
+ foreach (char c in input)
+ {
+ if (char.IsLetterOrDigit(c) || c == '-' || c == '_' || c == '.')
+ {
+ builder.Append(c);
+ }
+ else
+ {
+ builder.Append('_');
+ }
+ }
+
+ return builder.ToString();
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.AzureStorage/Blob/AzureBlobAgentThreadStoreOptions.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.AzureStorage/Blob/AzureBlobAgentThreadStoreOptions.cs
new file mode 100644
index 0000000000..d7b5cf8dd4
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Hosting.AzureStorage/Blob/AzureBlobAgentThreadStoreOptions.cs
@@ -0,0 +1,26 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+namespace Microsoft.Agents.AI.Hosting.AzureStorage.Blob;
+
+///
+/// Configuration options for .
+///
+public sealed class AzureBlobAgentThreadStoreOptions
+{
+ ///
+ /// Gets or sets a value indicating whether to automatically create the container if it doesn't exist.
+ ///
+ ///
+ /// Defaults to .
+ ///
+ public bool CreateContainerIfNotExists { get; set; } = true;
+
+ ///
+ /// Gets or sets the blob name prefix to use for organizing threads.
+ ///
+ ///
+ /// This can be used to namespace threads within a container.
+ /// For example, setting this to "prod/" will store all blobs under a "prod/" prefix.
+ ///
+ public string? BlobNamePrefix { get; set; }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.AzureStorage/HostedAgentBuilderExtensions.Blob.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.AzureStorage/HostedAgentBuilderExtensions.Blob.cs
new file mode 100644
index 0000000000..463bc47420
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Hosting.AzureStorage/HostedAgentBuilderExtensions.Blob.cs
@@ -0,0 +1,56 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using Azure.Storage.Blobs;
+using Microsoft.Agents.AI.Hosting.AzureStorage.Blob;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Microsoft.Agents.AI.Hosting;
+
+///
+/// Provides extension methods for configuring .
+///
+public static partial class HostedAgentBuilderExtensions
+{
+ ///
+ /// Configures the host agent builder to use an Azure Blob thread store with dependency injection.
+ /// Resolves from the service provider.
+ ///
+ /// The host agent builder to configure with the Azure blob thread store.
+ /// Optional configuration options for the blob thread store.
+ /// The same instance, configured to use Azure blob thread store.
+ ///
+ /// This overload requires a to be registered in the service collection.
+ /// Use Azure.Extensions.AspNetCore.Configuration.Secrets or similar to register the client.
+ ///
+ public static IHostedAgentBuilder WithAzureBlobThreadStore(this IHostedAgentBuilder builder, AzureBlobAgentThreadStoreOptions? options = null)
+ => WithAzureBlobThreadStore(builder, sp => sp.GetRequiredKeyedService(builder.Name), options);
+
+ ///
+ /// Configures the host agent builder to use an Azure Blob thread store for agent thread management.
+ ///
+ /// The host agent builder to configure with the Azure blob thread store.
+ /// The blob container client to use for storage operations.
+ /// Optional configuration options for the blob thread store.
+ /// The same instance, configured to use Azure blob thread store.
+ public static IHostedAgentBuilder WithAzureBlobThreadStore(this IHostedAgentBuilder builder, BlobContainerClient containerClient, AzureBlobAgentThreadStoreOptions? options = null)
+ => WithAzureBlobThreadStore(builder, sp => containerClient, options);
+
+ ///
+ /// Configures the agent builder to use Azure Blob Storage as the thread store for agent state persistence.
+ ///
+ /// The agent builder to configure with Azure Blob thread store support.
+ /// A factory function that provides a configured BlobContainerClient instance for accessing the Azure Blob
+ /// container used to store thread data.
+ /// Optional settings for customizing the Azure Blob thread store behavior. If null, default options are used.
+ /// The same agent builder instance, configured to use Azure Blob Storage for thread persistence.
+ public static IHostedAgentBuilder WithAzureBlobThreadStore(
+ this IHostedAgentBuilder builder,
+ Func createBlobContainer,
+ AzureBlobAgentThreadStoreOptions? options = null)
+ => builder.WithThreadStore((sp, key) =>
+ {
+ var blobContainer = createBlobContainer(sp);
+ return new AzureBlobAgentThreadStore(blobContainer, options);
+ });
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.AzureStorage/Microsoft.Agents.AI.Hosting.AzureStorage.csproj b/dotnet/src/Microsoft.Agents.AI.Hosting.AzureStorage/Microsoft.Agents.AI.Hosting.AzureStorage.csproj
new file mode 100644
index 0000000000..d6a1548212
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Hosting.AzureStorage/Microsoft.Agents.AI.Hosting.AzureStorage.csproj
@@ -0,0 +1,23 @@
+
+
+
+ $(ProjectsTargetFrameworks)
+ net9.0
+ preview
+ true
+
+
+ Microsoft Agent Framework AzureStorage
+ Provides AzureStorage integration with Microsoft Agent Framework.
+
+
+
+
+
+
+
+
+
+
+
+