diff --git a/dotnet/agent-framework-dotnet.slnx b/dotnet/agent-framework-dotnet.slnx
index 19fcb08fe7..5bfd03775e 100644
--- a/dotnet/agent-framework-dotnet.slnx
+++ b/dotnet/agent-framework-dotnet.slnx
@@ -171,6 +171,11 @@
+
+
+
+
+
diff --git a/dotnet/samples/SemanticKernelMigration/AgentOrchestrations/Step01_Concurrent/AgentOrchestrations_Step01_Concurrent.csproj b/dotnet/samples/SemanticKernelMigration/AgentOrchestrations/Step01_Concurrent/AgentOrchestrations_Step01_Concurrent.csproj
new file mode 100644
index 0000000000..a548ac03d0
--- /dev/null
+++ b/dotnet/samples/SemanticKernelMigration/AgentOrchestrations/Step01_Concurrent/AgentOrchestrations_Step01_Concurrent.csproj
@@ -0,0 +1,26 @@
+
+
+
+ Exe
+ net9.0
+ enable
+ enable
+ $(NoWarn);CA1812;RCS1102;CA1707;VSTHRD200
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dotnet/samples/SemanticKernelMigration/AgentOrchestrations/Step01_Concurrent/Program.cs b/dotnet/samples/SemanticKernelMigration/AgentOrchestrations/Step01_Concurrent/Program.cs
new file mode 100644
index 0000000000..ac6c276434
--- /dev/null
+++ b/dotnet/samples/SemanticKernelMigration/AgentOrchestrations/Step01_Concurrent/Program.cs
@@ -0,0 +1,109 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using Azure.AI.OpenAI;
+using Azure.Identity;
+using Microsoft.Agents.AI;
+using Microsoft.Agents.AI.Workflows;
+using Microsoft.Extensions.AI;
+using Microsoft.SemanticKernel;
+using Microsoft.SemanticKernel.Agents;
+using Microsoft.SemanticKernel.Agents.Orchestration;
+using Microsoft.SemanticKernel.Agents.Orchestration.Concurrent;
+using Microsoft.SemanticKernel.Agents.Runtime.InProcess;
+
+var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
+var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
+
+var agentInstructions = "You are a translation assistant who only responds in {0}. Respond to any input by outputting the name of the input language and then translating the input to {0}.";
+
+// This sample compares running concurrent orchestrations using
+// Semantic Kernel and the Agent Framework.
+Console.WriteLine("=== Semantic Kernel Concurrent Orchestration ===");
+await SKConcurrentOrchestration();
+
+Console.WriteLine("\n=== Agent Framework Concurrent Agent Workflow ===");
+await AFConcurrentAgentWorkflow();
+
+# region SKConcurrentOrchestration
+#pragma warning disable SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
+async Task SKConcurrentOrchestration()
+{
+ ConcurrentOrchestration orchestration = new([
+ GetSKTranslationAgent("French"),
+ GetSKTranslationAgent("Spanish")])
+ {
+ StreamingResponseCallback = StreamingResultCallback,
+ };
+
+ InProcessRuntime runtime = new();
+ await runtime.StartAsync();
+
+ // Run the orchestration
+ OrchestrationResult result = await orchestration.InvokeAsync("Hello, world!", runtime);
+ string[] texts = await result.GetValueAsync(TimeSpan.FromSeconds(20));
+
+ await runtime.RunUntilIdleAsync();
+}
+#pragma warning restore SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
+
+ChatCompletionAgent GetSKTranslationAgent(string targetLanguage)
+{
+ var kernel = Kernel.CreateBuilder().AddAzureOpenAIChatCompletion(deploymentName, endpoint, new AzureCliCredential()).Build();
+ return new ChatCompletionAgent()
+ {
+ Kernel = kernel,
+ Instructions = string.Format(agentInstructions, targetLanguage),
+ Description = $"Agent that translates texts to {targetLanguage}",
+ Name = $"SKTranslationAgent_{targetLanguage}"
+ };
+}
+
+ValueTask StreamingResultCallback(StreamingChatMessageContent streamedResponse, bool isFinal)
+{
+ Console.Write(streamedResponse.Content);
+
+ if (isFinal)
+ {
+ Console.WriteLine();
+ }
+
+ return ValueTask.CompletedTask;
+}
+# endregion
+
+# region AFConcurrentAgentWorkflow
+async Task AFConcurrentAgentWorkflow()
+{
+ var client = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential()).GetChatClient(deploymentName).AsIChatClient();
+ var frenchAgent = GetAFTranslationAgent("French", client);
+ var spanishAgent = GetAFTranslationAgent("Spanish", client);
+ var concurrentAgentWorkflow = AgentWorkflowBuilder.BuildConcurrent([frenchAgent, spanishAgent]);
+
+ await using StreamingRun run = await InProcessExecution.StreamAsync(concurrentAgentWorkflow, "Hello, world!");
+ await run.TrySendMessageAsync(new TurnToken(emitEvents: true));
+
+ string? lastExecutorId = null;
+ await foreach (WorkflowEvent evt in run.WatchStreamAsync().ConfigureAwait(false))
+ {
+ if (evt is AgentRunUpdateEvent e)
+ {
+ if (string.IsNullOrEmpty(e.Update.Text))
+ {
+ continue;
+ }
+
+ if (e.ExecutorId != lastExecutorId)
+ {
+ lastExecutorId = e.ExecutorId;
+ Console.WriteLine();
+ Console.Write($"{e.Update.AuthorName}: ");
+ }
+
+ Console.Write(e.Update.Text);
+ }
+ }
+}
+
+ChatClientAgent GetAFTranslationAgent(string targetLanguage, IChatClient chatClient) =>
+ new(chatClient, string.Format(agentInstructions, targetLanguage), name: $"AFTranslationAgent_{targetLanguage}");
+# endregion
diff --git a/dotnet/samples/SemanticKernelMigration/AgentOrchestrations/Step02_Sequential/AgentOrchestrations_Step02_Sequential.csproj b/dotnet/samples/SemanticKernelMigration/AgentOrchestrations/Step02_Sequential/AgentOrchestrations_Step02_Sequential.csproj
new file mode 100644
index 0000000000..a548ac03d0
--- /dev/null
+++ b/dotnet/samples/SemanticKernelMigration/AgentOrchestrations/Step02_Sequential/AgentOrchestrations_Step02_Sequential.csproj
@@ -0,0 +1,26 @@
+
+
+
+ Exe
+ net9.0
+ enable
+ enable
+ $(NoWarn);CA1812;RCS1102;CA1707;VSTHRD200
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dotnet/samples/SemanticKernelMigration/AgentOrchestrations/Step02_Sequential/Program.cs b/dotnet/samples/SemanticKernelMigration/AgentOrchestrations/Step02_Sequential/Program.cs
new file mode 100644
index 0000000000..e317ac0ff8
--- /dev/null
+++ b/dotnet/samples/SemanticKernelMigration/AgentOrchestrations/Step02_Sequential/Program.cs
@@ -0,0 +1,112 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using Azure.AI.OpenAI;
+using Azure.Identity;
+using Microsoft.Agents.AI;
+using Microsoft.Agents.AI.Workflows;
+using Microsoft.Extensions.AI;
+using Microsoft.SemanticKernel;
+using Microsoft.SemanticKernel.Agents;
+using Microsoft.SemanticKernel.Agents.Orchestration;
+using Microsoft.SemanticKernel.Agents.Orchestration.Sequential;
+using Microsoft.SemanticKernel.Agents.Runtime.InProcess;
+
+var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
+var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
+
+var agentInstructions = "You are a translation assistant who only responds in {0}. Respond to any input by outputting the name of the input language and then translating the input to {0}.";
+
+// This sample compares running sequential orchestrations using
+// Semantic Kernel and the Agent Framework.
+Console.WriteLine("=== Semantic Kernel Sequential Orchestration ===");
+await SKSequentialOrchestration();
+
+Console.WriteLine("\n=== Agent Framework Sequential Agent Workflow ===");
+await AFSequentialAgentWorkflow();
+
+# region SKSequentialOrchestration
+#pragma warning disable SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
+async Task SKSequentialOrchestration()
+{
+ SequentialOrchestration orchestration = new([
+ GetSKTranslationAgent("French"),
+ GetSKTranslationAgent("Spanish"),
+ GetSKTranslationAgent("English")])
+ {
+ StreamingResponseCallback = StreamingResultCallback,
+ };
+
+ InProcessRuntime runtime = new();
+ await runtime.StartAsync();
+
+ // Run the orchestration
+ OrchestrationResult result = await orchestration.InvokeAsync("Hello, world!", runtime);
+ string text = await result.GetValueAsync(TimeSpan.FromSeconds(20));
+
+ await runtime.RunUntilIdleAsync();
+}
+#pragma warning restore SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
+
+ChatCompletionAgent GetSKTranslationAgent(string targetLanguage)
+{
+ var kernel = Kernel.CreateBuilder().AddAzureOpenAIChatCompletion(deploymentName, endpoint, new AzureCliCredential()).Build();
+ return new ChatCompletionAgent()
+ {
+ Kernel = kernel,
+ Instructions = string.Format(agentInstructions, targetLanguage),
+ Description = $"Agent that translates texts to {targetLanguage}",
+ Name = $"SKTranslationAgent_{targetLanguage}"
+ };
+}
+
+ValueTask StreamingResultCallback(StreamingChatMessageContent streamedResponse, bool isFinal)
+{
+ Console.Write(streamedResponse.Content);
+
+ if (isFinal)
+ {
+ Console.WriteLine();
+ }
+
+ return ValueTask.CompletedTask;
+}
+# endregion
+
+# region AFSequentialAgentWorkflow
+async Task AFSequentialAgentWorkflow()
+{
+ var client = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential()).GetChatClient(deploymentName).AsIChatClient();
+ var frenchAgent = GetAFTranslationAgent("French", client);
+ var spanishAgent = GetAFTranslationAgent("Spanish", client);
+ var englishAgent = GetAFTranslationAgent("English", client);
+ var sequentialAgentWorkflow = AgentWorkflowBuilder.BuildSequential(
+ [frenchAgent, spanishAgent, englishAgent]);
+
+ await using StreamingRun run = await InProcessExecution.StreamAsync(sequentialAgentWorkflow, "Hello, world!");
+ await run.TrySendMessageAsync(new TurnToken(emitEvents: true));
+
+ string? lastExecutorId = null;
+ await foreach (WorkflowEvent evt in run.WatchStreamAsync().ConfigureAwait(false))
+ {
+ if (evt is AgentRunUpdateEvent e)
+ {
+ if (string.IsNullOrEmpty(e.Update.Text))
+ {
+ continue;
+ }
+
+ if (e.ExecutorId != lastExecutorId)
+ {
+ lastExecutorId = e.ExecutorId;
+ Console.WriteLine();
+ Console.Write($"{e.Update.AuthorName}: ");
+ }
+
+ Console.Write(e.Update.Text);
+ }
+ }
+}
+
+ChatClientAgent GetAFTranslationAgent(string targetLanguage, IChatClient chatClient) =>
+ new(chatClient, string.Format(agentInstructions, targetLanguage), name: $"AFTranslationAgent_{targetLanguage}");
+# endregion
diff --git a/dotnet/samples/SemanticKernelMigration/AgentOrchestrations/Step03_Handoff/AgentOrchestrations_Step03_Handoff.csproj b/dotnet/samples/SemanticKernelMigration/AgentOrchestrations/Step03_Handoff/AgentOrchestrations_Step03_Handoff.csproj
new file mode 100644
index 0000000000..a548ac03d0
--- /dev/null
+++ b/dotnet/samples/SemanticKernelMigration/AgentOrchestrations/Step03_Handoff/AgentOrchestrations_Step03_Handoff.csproj
@@ -0,0 +1,26 @@
+
+
+
+ Exe
+ net9.0
+ enable
+ enable
+ $(NoWarn);CA1812;RCS1102;CA1707;VSTHRD200
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dotnet/samples/SemanticKernelMigration/AgentOrchestrations/Step03_Handoff/Program.cs b/dotnet/samples/SemanticKernelMigration/AgentOrchestrations/Step03_Handoff/Program.cs
new file mode 100644
index 0000000000..f66fefe535
--- /dev/null
+++ b/dotnet/samples/SemanticKernelMigration/AgentOrchestrations/Step03_Handoff/Program.cs
@@ -0,0 +1,247 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.ComponentModel;
+using System.Text.Json;
+using Azure.AI.OpenAI;
+using Azure.Identity;
+using Microsoft.Agents.AI;
+using Microsoft.Agents.AI.Workflows;
+using Microsoft.Extensions.AI;
+using Microsoft.SemanticKernel;
+using Microsoft.SemanticKernel.Agents;
+using Microsoft.SemanticKernel.Agents.Orchestration;
+using Microsoft.SemanticKernel.Agents.Orchestration.Handoff;
+using Microsoft.SemanticKernel.Agents.Runtime.InProcess;
+using Microsoft.SemanticKernel.ChatCompletion;
+
+var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
+var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
+
+// Queries to simulate user input during the interactive orchestration
+List Queries = [
+ "I'd like to track the status of my first order 123.",
+ "I want to return another order of mine whose ID is 456 because it arrived damaged.",
+];
+
+// This sample compares running handoff orchestrations using
+// Semantic Kernel and the Agent Framework.
+Console.WriteLine("=== Semantic Kernel Handoff Orchestration ===");
+// State to help format the streaming output
+bool newAgentTurn = true;
+string previousFunctionCallId = string.Empty;
+await SKHandoffOrchestration();
+
+Console.WriteLine("\n=== Agent Framework Handoff Agent Workflow ===");
+await AFHandoffAgentWorkflow();
+
+# region SKHandoffOrchestration
+[KernelFunction]
+string SKCheckOrderStatus(string orderId) => $"Order {orderId} is shipped and will arrive in 2-3 days.";
+
+[KernelFunction]
+string SKProcessReturn(string orderId, string reason) => $"Return for order {orderId} has been processed successfully.";
+
+[KernelFunction]
+string SKProcessRefund(string orderId, string reason) => $"Refund for order {orderId} has been processed successfully.";
+
+#pragma warning disable SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
+async Task SKHandoffOrchestration()
+{
+ // Create agents
+ var triageAgent = GetSKAgent(
+ instructions: "You are a customer support agent that triages issues.",
+ name: "TriageAgent",
+ description: "Handle customer requests.");
+ var statusAgent = GetSKAgent(
+ instructions: "You are a customer support agent that checks order status.",
+ name: "OrderStatusAgent",
+ description: "Handle order status requests.");
+ statusAgent.Kernel.Plugins.AddFromFunctions("OrderStatusPlugin", [KernelFunctionFactory.CreateFromMethod(SKCheckOrderStatus)]);
+ var returnAgent = GetSKAgent(
+ instructions: "You are a customer support agent that handles order returns.",
+ name: "OrderReturnAgent",
+ description: "Handle order return requests.");
+ returnAgent.Kernel.Plugins.AddFromFunctions("OrderReturnPlugin", [KernelFunctionFactory.CreateFromMethod(SKProcessReturn)]);
+ var refundAgent = GetSKAgent(
+ instructions: "You are a customer support agent that handles order refunds.",
+ name: "OrderRefundAgent",
+ description: "Handle order refund requests.");
+ refundAgent.Kernel.Plugins.AddFromFunctions("OrderRefundPlugin", [KernelFunctionFactory.CreateFromMethod(SKProcessRefund)]);
+
+ Queue queries = new(Queries);
+
+ // Create orchestration with handoffs
+ HandoffOrchestration orchestration =
+ new(OrchestrationHandoffs
+ .StartWith(triageAgent)
+ .Add(triageAgent, statusAgent, returnAgent, refundAgent)
+ .Add(statusAgent, triageAgent, "Transfer to this agent if the issue is not status related")
+ .Add(returnAgent, triageAgent, "Transfer to this agent if the issue is not return related")
+ .Add(refundAgent, triageAgent, "Transfer to this agent if the issue is not refund related"),
+ triageAgent,
+ statusAgent,
+ returnAgent,
+ refundAgent)
+ {
+ InteractiveCallback = () =>
+ {
+ string input = queries.Count > 0 ? queries.Dequeue() : "exit";
+ Console.WriteLine($"\nUser: {input}");
+ return ValueTask.FromResult(new ChatMessageContent(AuthorRole.User, input));
+ },
+ StreamingResponseCallback = StreamingResultCallback,
+ };
+
+ InProcessRuntime runtime = new();
+ await runtime.StartAsync();
+
+ // Run the orchestration
+ OrchestrationResult result = await orchestration.InvokeAsync(
+ "I am a customer that needs help with my two orders",
+ runtime);
+ string text = await result.GetValueAsync();
+ Console.WriteLine($"\nFinal Result: {text}");
+
+ await runtime.RunUntilIdleAsync();
+}
+#pragma warning restore SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
+
+ChatCompletionAgent GetSKAgent(string instructions, string name, string description)
+{
+ var kernel = Kernel.CreateBuilder().AddAzureOpenAIChatCompletion(deploymentName, endpoint, new AzureCliCredential()).Build();
+ return new ChatCompletionAgent()
+ {
+ Kernel = kernel,
+ Instructions = instructions,
+ Description = description,
+ Name = name
+ };
+}
+
+ValueTask StreamingResultCallback(StreamingChatMessageContent streamedResponse, bool isFinal)
+{
+ if (newAgentTurn)
+ {
+ Console.Write($"\n{streamedResponse.AuthorName}: ");
+ newAgentTurn = false;
+ }
+ Console.Write(streamedResponse.Content);
+
+ if (streamedResponse.Items.OfType().FirstOrDefault()
+ is StreamingFunctionCallUpdateContent call)
+ {
+ if (call.CallId is not null && previousFunctionCallId != call.CallId)
+ {
+ Console.Write($"\nCalling function '{call.Name}' with arguments: ");
+ previousFunctionCallId = call.CallId;
+ }
+ if (!string.IsNullOrEmpty(call.Arguments))
+ {
+ Console.Write($"{call.Arguments}");
+ }
+ }
+
+ if (isFinal)
+ {
+ newAgentTurn = true;
+ previousFunctionCallId = string.Empty;
+ Console.WriteLine();
+ }
+
+ return ValueTask.CompletedTask;
+}
+# endregion
+
+# region AFHandoffAgentWorkflow
+[Description("Get the order status for a given order ID.")]
+static string AFCheckOrderStatus([Description("The order ID to check the status for.")] string orderId)
+ => $"Order {orderId} is shipped and will arrive in 2-3 days.";
+
+[Description("Process a return for a given order ID.")]
+static string AFProcessReturn(
+ [Description("The order ID to process the return for.")] string orderId,
+ [Description("The reason for the return.")] string reason)
+ => $"Return for order {orderId} has been processed successfully for the following reason: {reason}.";
+
+[Description("Process a refund for a given order ID.")]
+static string AFProcessRefund([Description("The order ID to process the refund for.")] string orderId)
+ => $"Refund for order {orderId} has been processed successfully.";
+
+async Task AFHandoffAgentWorkflow()
+{
+ // Create agents
+ var client = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential()).GetChatClient(deploymentName).AsIChatClient();
+
+ ChatClientAgent triageAgent = new(client,
+ instructions: "A customer support agent that triages issues.",
+ name: "TriageAgent",
+ description: "Handle customer requests.");
+ ChatClientAgent statusAgent = new(client,
+ name: "OrderStatusAgent",
+ instructions: "Handle order status requests.",
+ description: "A customer support agent that checks order status.",
+ tools: [AIFunctionFactory.Create(AFCheckOrderStatus)]);
+ ChatClientAgent returnAgent = new(client,
+ name: "OrderReturnAgent",
+ instructions: "Handle order return requests.",
+ description: "A customer support agent that handles order returns.",
+ tools: [AIFunctionFactory.Create(AFProcessReturn)]);
+ ChatClientAgent refundAgent = new(client,
+ name: "OrderRefundAgent",
+ instructions: "Handle order refund requests.",
+ description: "A customer support agent that handles order refund.",
+ tools: [AIFunctionFactory.Create(AFProcessRefund)]);
+
+ // Create workflow with handoffs
+ var handoffAgentWorkflow = AgentWorkflowBuilder.CreateHandoffBuilderWith(triageAgent)
+ .WithHandoffs(triageAgent, [statusAgent, returnAgent, refundAgent])
+ .WithHandoff(statusAgent, triageAgent, "Transfer to this agent if the issue is not status related")
+ .WithHandoff(returnAgent, triageAgent, "Transfer to this agent if the issue is not return related")
+ .WithHandoff(refundAgent, triageAgent, "Transfer to this agent if the issue is not refund related")
+ .Build();
+
+ // Run the workflow
+ List messages = [];
+ foreach (var query in Queries)
+ {
+ Console.WriteLine($"User: {query}");
+ messages.Add(new(ChatRole.User, query));
+
+ await using var run = await InProcessExecution.StreamAsync(handoffAgentWorkflow, messages);
+ await run.TrySendMessageAsync(new TurnToken(emitEvents: true));
+
+ string? lastExecutorId = null;
+ await foreach (WorkflowEvent evt in run.WatchStreamAsync().ConfigureAwait(false))
+ {
+ if (evt is AgentRunUpdateEvent e)
+ {
+ if (string.IsNullOrEmpty(e.Update.Text) && e.Update.Contents.Count == 0)
+ {
+ continue;
+ }
+
+ if (e.ExecutorId != lastExecutorId)
+ {
+ lastExecutorId = e.ExecutorId;
+ Console.WriteLine();
+ Console.Write($"{e.Update.AuthorName}: ");
+ }
+
+ Console.Write(e.Update.Text);
+
+ if (e.Update.Contents.OfType().FirstOrDefault()
+ is Microsoft.Extensions.AI.FunctionCallContent call)
+ {
+ Console.WriteLine();
+ Console.WriteLine($"Calling function '{call.Name}' with arguments: {JsonSerializer.Serialize(call.Arguments)}");
+ }
+ }
+ else if (evt is WorkflowOutputEvent output)
+ {
+ Console.WriteLine("\n");
+ messages.AddRange(output.As>()!);
+ }
+ }
+ }
+}
+# endregion