diff --git a/dotnet/agent-framework-dotnet.slnx b/dotnet/agent-framework-dotnet.slnx
index 47f1feaf1e..eafabd7c28 100644
--- a/dotnet/agent-framework-dotnet.slnx
+++ b/dotnet/agent-framework-dotnet.slnx
@@ -36,7 +36,7 @@
-
+
diff --git a/dotnet/samples/AzureFunctions/09_Workflow/OrderProcessingExecutors.cs b/dotnet/samples/AzureFunctions/09_Workflow/OrderProcessingExecutors.cs
new file mode 100644
index 0000000000..193735f192
--- /dev/null
+++ b/dotnet/samples/AzureFunctions/09_Workflow/OrderProcessingExecutors.cs
@@ -0,0 +1,59 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using Microsoft.Agents.AI.Workflows;
+
+namespace SingleAgent;
+
+///
+/// Parses an Order ID from a string input and returns an Order object populated.
+///
+internal sealed class OrderLookupExecutor() : Executor("OrderLookup")
+{
+ public override async ValueTask HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
+ {
+ // Populate Order information from OrderId.
+ return new Order(message, 100.0m);
+ }
+}
+
+///
+/// Enriches an Order object with additional information.
+///
+internal sealed class OrderEnricherExecutor() : Executor("EnrichOrder")
+{
+ public override async ValueTask HandleAsync(Order message, IWorkflowContext context, CancellationToken cancellationToken = default)
+ {
+ if (message.Customer is null)
+ {
+ // populate customer information for the order from database.
+ message.Customer = new Customer(1, "Jerry");
+ }
+
+ return message;
+ }
+}
+
+internal sealed class PaymentProcessorExecutor() : Executor("ProcessPayment")
+{
+ public override async ValueTask HandleAsync(Order message, IWorkflowContext context, CancellationToken cancellationToken = default)
+ {
+ message.PaymentReferenceNumber = Guid.NewGuid().ToString()[^4..];
+
+ return message;
+ }
+}
+
+internal sealed class Order
+{
+ public Order(string id, decimal amount)
+ {
+ this.Id = id;
+ this.Amount = amount;
+ }
+ public string Id { get; }
+ public decimal Amount { get; }
+ public Customer? Customer { get; set; }
+ public string? PaymentReferenceNumber { get; set; }
+}
+
+public sealed record Customer(int Id, string Name);
diff --git a/dotnet/samples/AzureFunctions/09_Workflow/Program.cs b/dotnet/samples/AzureFunctions/09_Workflow/Program.cs
index a4b8b4effb..db166d184d 100644
--- a/dotnet/samples/AzureFunctions/09_Workflow/Program.cs
+++ b/dotnet/samples/AzureFunctions/09_Workflow/Program.cs
@@ -1,87 +1,41 @@
// Copyright (c) Microsoft. All rights reserved.
-using Azure;
-using Azure.AI.OpenAI;
-using Azure.Identity;
-using Microsoft.Agents.AI;
using Microsoft.Agents.AI.Hosting.AzureFunctions;
using Microsoft.Agents.AI.Workflows;
using Microsoft.Azure.Functions.Worker.Builder;
using Microsoft.Extensions.Hosting;
-using OpenAI.Chat;
using SingleAgent;
-// Get the Azure OpenAI endpoint and deployment name from environment variables.
-string endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")
- ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
-string deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT")
- ?? throw new InvalidOperationException("AZURE_OPENAI_DEPLOYMENT is not set.");
-
-// Use Azure Key Credential if provided, otherwise use Azure CLI Credential.
-string? azureOpenAiKey = System.Environment.GetEnvironmentVariable("AZURE_OPENAI_KEY");
-AzureOpenAIClient client = !string.IsNullOrEmpty(azureOpenAiKey)
- ? new AzureOpenAIClient(new Uri(endpoint), new AzureKeyCredential(azureOpenAiKey))
- : new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential());
-
-// Set up an AI agent following the standard Microsoft Agent Framework pattern.
-const string JokerName = "Joker";
-const string JokerInstructions = "You are good at telling jokes.";
-const string FeedbackAnalysisInstructions = """
-You are a Customer Feedback Analyzer. Your task is to analyze customer survey responses and categorize them accurately.
-
-INPUT: You will receive customer feedback text that may include a rating and comments.
-
-OUTPUT: Return ONLY ONE category from this list:
-- Bug Report
-- General Feedback
-- Billing Question
-- Support Incident Status
-
-CATEGORIZATION RULES:
-- "Bug Report": Technical issues, errors, crashes, features not working as expected
-- "General Feedback": Suggestions, compliments, general comments about the product or service
-- "Billing Question": Payment issues, subscription inquiries, pricing questions, refund requests
-- "Support Incident Status": Follow-ups on existing tickets, status inquiries about previous issues
-
-RESPONSE FORMAT: Return only the category name exactly as shown above, with no additional text or explanation.
-""";
-
-AIAgent agent = client.GetChatClient(deploymentName).CreateAIAgent(JokerInstructions, JokerName);
-
-// 3 Executors usd by the workflow.
-// 1. Class based executor to parse survey response.
-SurveyResponseParserExecutor surveyResponseParserExecutor = new();
-
-// 2. AI Agent to analyze feedback and categorize it.
-AIAgent surveyFeedbackAgent = client.GetChatClient(deploymentName).CreateAIAgent(FeedbackAnalysisInstructions, "FeedbackAnalyzerAgent");
-
-// 3. Function delegate executor to route response based on category.
-Func responseHandlingExecutorFunc = input => input switch
+Func orderParserFunc = input =>
{
- var s when s.Contains("billing", StringComparison.OrdinalIgnoreCase)
- => "Will notify Billing Team",
-
- var s when s.Contains("Bug Report", StringComparison.OrdinalIgnoreCase)
- => "Will notify Technical Support Team",
-
- _ => "Will notify General Support Team"
-};
-var responseRouterExecutor = responseHandlingExecutorFunc.BindAsExecutor("ResponseRouterExecutor");
-
-WorkflowBuilder builder = new(surveyResponseParserExecutor);
-builder.AddEdge(surveyResponseParserExecutor, surveyFeedbackAgent);
-builder.AddEdge(surveyFeedbackAgent, responseRouterExecutor).WithOutputFrom(responseRouterExecutor);
-var workflow = builder.WithName("HandleSurveyResponse").Build();
-
-FunctionsApplication.CreateBuilder(args)
- .ConfigureFunctionsWebApplication()
- .ConfigureDurableAgents(options => options.AddAIAgent(agent))
- .ConfigureDurableOptions(options =>
+ // We accept both short ordereId(Ex:12345) and long order reference number(MSFT12345)
+ // OrderId is the last 5 digigs of order reference number.
+ const int OrderIdPartLength = 5;
+ if (input.Length > OrderIdPartLength)
{
- // Configure workflows
- options.Workflows.AddWorkflow(workflow);
+ return input[^OrderIdPartLength..];
+ }
- // Optional - Configure AI agents
- // options.Agents.AddAIAgent(agent);
- })
- .Build().Run();
+ return input;
+};
+var orderParserExecutor = orderParserFunc.BindAsExecutor("OrderParser");
+
+OrderLookupExecutor orderLookupExecutor = new();
+OrderEnricherExecutor orderEnricherExeecutor = new();
+PaymentProcessorExecutor paymentProcessorExecutor = new();
+
+Workflow workflow = new WorkflowBuilder(orderParserExecutor)
+ .WithName("FulfillOrder")
+ .WithDescription("Looks up an order by ID and run payment processing")
+ .AddEdge(orderParserExecutor, orderLookupExecutor)
+ .AddEdge(orderLookupExecutor, orderEnricherExeecutor)
+ .AddEdge(orderEnricherExeecutor, paymentProcessorExecutor)
+ .WithOutputFrom(paymentProcessorExecutor)
+ .Build();
+
+var host = FunctionsApplication.CreateBuilder(args)
+ .ConfigureFunctionsWebApplication()
+ .ConfigureDurableOptions(options => options.Workflows.AddWorkflow(workflow))
+ .Build();
+
+host.Run();
diff --git a/dotnet/samples/AzureFunctions/09_Workflow/SurveyResponseParserExecutor.cs b/dotnet/samples/AzureFunctions/09_Workflow/SurveyResponseParserExecutor.cs
deleted file mode 100644
index 0697dd4edf..0000000000
--- a/dotnet/samples/AzureFunctions/09_Workflow/SurveyResponseParserExecutor.cs
+++ /dev/null
@@ -1,82 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-
-using System.Text.Json;
-using System.Text.RegularExpressions;
-using Microsoft.Agents.AI.Workflows;
-
-namespace SingleAgent;
-
-///
-/// This executor parses survey responses and produces structured output.
-/// Example input: "Rating: 8. The app is good but checkout process is confusing."
-///
-[System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1812:Avoid uninstantiated internal classes", Justification = "Instantiated by workflow framework")]
-internal sealed partial class SurveyResponseParserExecutor() : Executor("SurveyResponseParserExecutor")
-{
- private static readonly JsonSerializerOptions s_jsonOptions = new()
- {
- WriteIndented = true
- };
-
- [GeneratedRegex(@"Rating:\s*(\d+)", RegexOptions.IgnoreCase)]
- private static partial Regex RatingRegex();
-
- public override ValueTask HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
- {
- SurveyResponse response = ParseSurveyResponse(message);
- string jsonResult = JsonSerializer.Serialize(response, s_jsonOptions);
- return ValueTask.FromResult(jsonResult);
- }
-
- private static SurveyResponse ParseSurveyResponse(string message)
- {
- // Parse the message to extract rating and comment
- int? rating = null;
- string comment = message;
-
- // Try to extract rating using pattern "Rating: {number}"
- Match ratingMatch = RatingRegex().Match(message);
- if (ratingMatch.Success && int.TryParse(ratingMatch.Groups[1].Value, out int parsedRating))
- {
- rating = parsedRating;
-
- // Remove the rating part from the message to get the comment
- // Find the position after the rating number
- int ratingEndIndex = ratingMatch.Index + ratingMatch.Length;
-
- // Skip any separators (period, comma, dash, etc.) and whitespace
- while (ratingEndIndex < message.Length &&
- (char.IsWhiteSpace(message[ratingEndIndex]) ||
- message[ratingEndIndex] == '.' ||
- message[ratingEndIndex] == ',' ||
- message[ratingEndIndex] == '-'))
- {
- ratingEndIndex++;
- }
-
- if (ratingEndIndex < message.Length)
- {
- comment = message[ratingEndIndex..].Trim();
- }
- else
- {
- comment = string.Empty;
- }
- }
-
- // Create and return the structured response
- return new SurveyResponse
- {
- Rating = rating,
- Comment = comment,
- OriginalMessage = message
- };
- }
-
- private sealed class SurveyResponse
- {
- public int? Rating { get; set; }
- public string Comment { get; set; } = string.Empty;
- public string OriginalMessage { get; set; } = string.Empty;
- }
-}
diff --git a/dotnet/samples/AzureFunctions/09_Workflow/demo.http b/dotnet/samples/AzureFunctions/09_Workflow/demo.http
index a3f7d87235..0c23d3c7ca 100644
--- a/dotnet/samples/AzureFunctions/09_Workflow/demo.http
+++ b/dotnet/samples/AzureFunctions/09_Workflow/demo.http
@@ -1,14 +1,14 @@
# Default endpoint address for local testing
@authority=http://localhost:7071
-### Start the workflow
-POST {{authority}}/api/workflows/HandleSurveyResponse/run
+### Look up a long order reference id
+POST {{authority}}/api/workflows/FulfillOrder/run
Content-Type: text/plain
-Rating: 10. Why was I charged $99 when my plan should be $49? I need a refund for the overcharge
+QWERTY80853
-### Start the workflow with another bug report input.
-POST {{authority}}/api/workflows/HandleSurveyResponse/run
+### Look up a short order id
+POST {{authority}}/api/workflows/FulfillOrder/run
Content-Type: text/plain
-Rating: 3. The app throws an InvalidOperationException when starting up.
\ No newline at end of file
+12345
diff --git a/dotnet/samples/AzureFunctions/12_WorkflowSharedState/12_WorkflowSharedState.csproj b/dotnet/samples/AzureFunctions/11_WorkflowSharedState/11_WorkflowSharedState.csproj
similarity index 100%
rename from dotnet/samples/AzureFunctions/12_WorkflowSharedState/12_WorkflowSharedState.csproj
rename to dotnet/samples/AzureFunctions/11_WorkflowSharedState/11_WorkflowSharedState.csproj
diff --git a/dotnet/samples/AzureFunctions/12_WorkflowSharedState/MyOrchFunction.cs b/dotnet/samples/AzureFunctions/11_WorkflowSharedState/MyOrchFunction.cs
similarity index 100%
rename from dotnet/samples/AzureFunctions/12_WorkflowSharedState/MyOrchFunction.cs
rename to dotnet/samples/AzureFunctions/11_WorkflowSharedState/MyOrchFunction.cs
diff --git a/dotnet/samples/AzureFunctions/12_WorkflowSharedState/OrderIdParserExecutor.cs b/dotnet/samples/AzureFunctions/11_WorkflowSharedState/OrderIdParserExecutor.cs
similarity index 100%
rename from dotnet/samples/AzureFunctions/12_WorkflowSharedState/OrderIdParserExecutor.cs
rename to dotnet/samples/AzureFunctions/11_WorkflowSharedState/OrderIdParserExecutor.cs
diff --git a/dotnet/samples/AzureFunctions/12_WorkflowSharedState/Program.cs b/dotnet/samples/AzureFunctions/11_WorkflowSharedState/Program.cs
similarity index 100%
rename from dotnet/samples/AzureFunctions/12_WorkflowSharedState/Program.cs
rename to dotnet/samples/AzureFunctions/11_WorkflowSharedState/Program.cs
diff --git a/dotnet/samples/AzureFunctions/11_WorkflowWithDifferentTypes/README.md b/dotnet/samples/AzureFunctions/11_WorkflowSharedState/README.md
similarity index 100%
rename from dotnet/samples/AzureFunctions/11_WorkflowWithDifferentTypes/README.md
rename to dotnet/samples/AzureFunctions/11_WorkflowSharedState/README.md
diff --git a/dotnet/samples/AzureFunctions/12_WorkflowSharedState/demo.http b/dotnet/samples/AzureFunctions/11_WorkflowSharedState/demo.http
similarity index 100%
rename from dotnet/samples/AzureFunctions/12_WorkflowSharedState/demo.http
rename to dotnet/samples/AzureFunctions/11_WorkflowSharedState/demo.http
diff --git a/dotnet/samples/AzureFunctions/11_WorkflowWithDifferentTypes/host.json b/dotnet/samples/AzureFunctions/11_WorkflowSharedState/host.json
similarity index 100%
rename from dotnet/samples/AzureFunctions/11_WorkflowWithDifferentTypes/host.json
rename to dotnet/samples/AzureFunctions/11_WorkflowSharedState/host.json
diff --git a/dotnet/samples/AzureFunctions/11_WorkflowWithDifferentTypes/11_WorkflowWithDifferentTypes.csproj b/dotnet/samples/AzureFunctions/11_WorkflowWithDifferentTypes/11_WorkflowWithDifferentTypes.csproj
deleted file mode 100644
index 4872f05930..0000000000
--- a/dotnet/samples/AzureFunctions/11_WorkflowWithDifferentTypes/11_WorkflowWithDifferentTypes.csproj
+++ /dev/null
@@ -1,48 +0,0 @@
-
-
- net10.0
- v4
- Exe
- enable
- enable
-
- SingleAgent
- SingleAgent
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/dotnet/samples/AzureFunctions/11_WorkflowWithDifferentTypes/OrderProcessingExecutors.cs b/dotnet/samples/AzureFunctions/11_WorkflowWithDifferentTypes/OrderProcessingExecutors.cs
deleted file mode 100644
index cc0dfef6b8..0000000000
--- a/dotnet/samples/AzureFunctions/11_WorkflowWithDifferentTypes/OrderProcessingExecutors.cs
+++ /dev/null
@@ -1,215 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-
-using Microsoft.Agents.AI.Workflows;
-
-namespace SingleAgent;
-
-///
-/// Represents the details of a customer order.
-///
-public sealed class OrderDetails
-{
- public int OrderId { get; set; }
-
- public string CustomerName { get; set; } = string.Empty;
-
- public string CustomerEmail { get; set; } = string.Empty;
-
- public List Items { get; set; } = [];
-
- public decimal TotalAmount { get; set; }
-
- public OrderStatus Status { get; set; }
-
- public DateTime OrderDate { get; set; }
-
- public DateTime? EstimatedDelivery { get; set; }
-}
-
-///
-/// Represents an item in an order.
-///
-public sealed class OrderItem
-{
- public string ProductName { get; set; } = string.Empty;
-
- public int Quantity { get; set; }
-
- public decimal UnitPrice { get; set; }
-
- public decimal Total => this.Quantity * this.UnitPrice;
-}
-
-///
-/// Represents the status of an order.
-///
-public enum OrderStatus
-{
- Pending,
- Processing,
- Shipped,
- Delivered,
- Cancelled
-}
-
-///
-/// Executor that looks up order details by order ID.
-/// Input: int (orderId)
-/// Output: OrderDetails
-///
-internal sealed class OrderLookupExecutor() : Executor("OrderLookupExecutor")
-{
- // Simulated order database
- private static readonly Dictionary s_orders = new()
- {
- [1001] = new OrderDetails
- {
- OrderId = 1001,
- CustomerName = "Alice Johnson",
- CustomerEmail = "alice@example.com",
- Items =
- [
- new OrderItem { ProductName = "Wireless Headphones", Quantity = 1, UnitPrice = 79.99m },
- new OrderItem { ProductName = "Phone Case", Quantity = 2, UnitPrice = 15.99m }
- ],
- TotalAmount = 111.97m,
- Status = OrderStatus.Shipped,
- OrderDate = DateTime.UtcNow.AddDays(-3),
- EstimatedDelivery = DateTime.UtcNow.AddDays(2)
- },
- [1002] = new OrderDetails
- {
- OrderId = 1002,
- CustomerName = "Bob Smith",
- CustomerEmail = "bob@example.com",
- Items =
- [
- new OrderItem { ProductName = "Laptop Stand", Quantity = 1, UnitPrice = 49.99m }
- ],
- TotalAmount = 49.99m,
- Status = OrderStatus.Processing,
- OrderDate = DateTime.UtcNow.AddDays(-1),
- EstimatedDelivery = DateTime.UtcNow.AddDays(5)
- },
- [1003] = new OrderDetails
- {
- OrderId = 1003,
- CustomerName = "Carol Davis",
- CustomerEmail = "carol@example.com",
- Items =
- [
- new OrderItem { ProductName = "USB-C Hub", Quantity = 1, UnitPrice = 35.00m },
- new OrderItem { ProductName = "HDMI Cable", Quantity = 3, UnitPrice = 12.99m },
- new OrderItem { ProductName = "Webcam", Quantity = 1, UnitPrice = 89.99m }
- ],
- TotalAmount = 163.96m,
- Status = OrderStatus.Delivered,
- OrderDate = DateTime.UtcNow.AddDays(-7),
- EstimatedDelivery = null
- }
- };
-
- public override ValueTask HandleAsync(int message, IWorkflowContext context, CancellationToken cancellationToken = default)
- {
- if (s_orders.TryGetValue(message, out OrderDetails? order))
- {
- return ValueTask.FromResult(order);
- }
-
- // Return a "not found" order
- return ValueTask.FromResult(new OrderDetails
- {
- OrderId = message,
- CustomerName = "Unknown",
- Status = OrderStatus.Cancelled,
- OrderDate = DateTime.UtcNow
- });
- }
-}
-
-///
-/// Executor that generates a human-readable summary from order details.
-/// Input: OrderDetails
-/// Output: string
-///
-internal sealed class OrderSummaryExecutor() : Executor("OrderSummaryExecutor")
-{
- public override ValueTask HandleAsync(OrderDetails message, IWorkflowContext context, CancellationToken cancellationToken = default)
- {
- if (message.CustomerName == "Unknown")
- {
- return ValueTask.FromResult($"❌ Order #{message.OrderId} was not found in our system.");
- }
-
- string statusEmoji = message.Status switch
- {
- OrderStatus.Pending => "⏳",
- OrderStatus.Processing => "🔄",
- OrderStatus.Shipped => "📦",
- OrderStatus.Delivered => "✅",
- OrderStatus.Cancelled => "❌",
- _ => "❓"
- };
-
- string itemsList = string.Join("\n", message.Items.Select(i =>
- $" • {i.ProductName} (x{i.Quantity}) - ${i.Total:F2}"));
-
- string deliveryInfo = message.Status == OrderStatus.Delivered
- ? "Delivered!"
- : message.EstimatedDelivery.HasValue
- ? $"Expected: {message.EstimatedDelivery.Value:MMM dd, yyyy}"
- : "Calculating...";
-
- string summary = $"""
- ═══════════════════════════════════════
- 📋 ORDER SUMMARY - #{message.OrderId}
- ═══════════════════════════════════════
-
- 👤 Customer: {message.CustomerName}
- 📧 Email: {message.CustomerEmail}
- 📅 Order Date: {message.OrderDate:MMM dd, yyyy}
-
- {statusEmoji} Status: {message.Status}
- 🚚 Delivery: {deliveryInfo}
-
- ─────────────────────────────────────────
- 📦 ITEMS:
- {itemsList}
- ─────────────────────────────────────────
-
- 💰 TOTAL: ${message.TotalAmount:F2}
- ═══════════════════════════════════════
- """;
-
- return ValueTask.FromResult(summary);
- }
-}
-
-///
-/// Executor that parses a string input to extract an order ID.
-/// Input: string (e.g., "Check order 1001" or just "1001")
-/// Output: int (orderId)
-///
-internal sealed class OrderIdParserExecutor() : Executor("OrderIdParserExecutor")
-{
- public override ValueTask HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
- {
- // Try to extract order ID from the message
- // Handles formats like: "1001", "order 1001", "Check order #1001", etc.
- string cleanedInput = message
- .Replace("order", "", StringComparison.OrdinalIgnoreCase)
- .Replace("#", "")
- .Trim();
-
- // Find the first number in the string
- string numberStr = new(cleanedInput.Where(char.IsDigit).ToArray());
-
- if (int.TryParse(numberStr, out int orderId))
- {
- return ValueTask.FromResult(orderId);
- }
-
- // Default to an invalid order ID if parsing fails
- return ValueTask.FromResult(-1);
- }
-}
diff --git a/dotnet/samples/AzureFunctions/11_WorkflowWithDifferentTypes/Program.cs b/dotnet/samples/AzureFunctions/11_WorkflowWithDifferentTypes/Program.cs
deleted file mode 100644
index a2a70a2a20..0000000000
--- a/dotnet/samples/AzureFunctions/11_WorkflowWithDifferentTypes/Program.cs
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-
-// This sample demonstrates a workflow with different input/output types:
-// - OrderIdParserExecutor: string → int
-// - OrderLookupExecutor: int → OrderDetails (custom POCO)
-// - OrderSummaryExecutor: OrderDetails → string
-//
-// Workflow: HTTP Request (string) → Parse Order ID → Lookup Order → Generate Summary
-
-using Microsoft.Agents.AI.Hosting.AzureFunctions;
-using Microsoft.Agents.AI.Workflows;
-using Microsoft.Azure.Functions.Worker.Builder;
-using Microsoft.Extensions.Hosting;
-using SingleAgent;
-
-// Create the executors with different input/output types
-OrderIdParserExecutor orderIdParser = new(); // string → int
-OrderLookupExecutor orderLookup = new(); // int → OrderDetails POCO
-OrderSummaryExecutor orderSummary = new(); // OrderDetails → string
-
-// Build the workflow: Parse → Lookup → Summarize
-Workflow workflow = new WorkflowBuilder(orderIdParser)
- .WithName("OrderLookupWorkflow")
- .WithDescription("Looks up an order by ID and returns a formatted summary")
- .AddEdge(orderIdParser, orderLookup) // string → int → OrderDetails
- .AddEdge(orderLookup, orderSummary) // OrderDetails → string
- .WithOutputFrom(orderSummary)
- .Build();
-
-// Configure the function app to host workflows.
-// This will automatically generate HTTP API endpoints for the workflow.
-FunctionsApplicationBuilder functionBuilder = FunctionsApplication.CreateBuilder(args);
-functionBuilder.ConfigureFunctionsWebApplication().ConfigureDurableOptions(options =>
-{
- // Register the workflow
- options.Workflows.AddWorkflow(workflow);
-});
-functionBuilder.Build().Run();
diff --git a/dotnet/samples/AzureFunctions/11_WorkflowWithDifferentTypes/ResponseRouterExecutor.cs b/dotnet/samples/AzureFunctions/11_WorkflowWithDifferentTypes/ResponseRouterExecutor.cs
deleted file mode 100644
index fdf1090ec2..0000000000
--- a/dotnet/samples/AzureFunctions/11_WorkflowWithDifferentTypes/ResponseRouterExecutor.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-
-using Microsoft.Agents.AI.Workflows;
-
-namespace SingleAgent;
-
-///
-/// Routes survey responses to appropriate teams based on rating and category.
-///
-public sealed class ResponseRouterExecutor() : Executor("ResponseRouterExecutor")
-{
- public override ValueTask HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
- {
- if (message.Contains("billing", StringComparison.OrdinalIgnoreCase))
- {
- return ValueTask.FromResult("Routed to Billing Team");
- }
- else if (message.Contains("technical", StringComparison.OrdinalIgnoreCase))
- {
- return ValueTask.FromResult("Routed to Technical Support Team");
- }
- else
- {
- return ValueTask.FromResult("Routed to General Support Team");
- }
- }
-}
diff --git a/dotnet/samples/AzureFunctions/11_WorkflowWithDifferentTypes/SurveyResponseParserExecutor.cs b/dotnet/samples/AzureFunctions/11_WorkflowWithDifferentTypes/SurveyResponseParserExecutor.cs
deleted file mode 100644
index f6a66c9038..0000000000
--- a/dotnet/samples/AzureFunctions/11_WorkflowWithDifferentTypes/SurveyResponseParserExecutor.cs
+++ /dev/null
@@ -1,82 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-
-using System.Text.Json;
-using System.Text.RegularExpressions;
-using Microsoft.Agents.AI.Workflows;
-
-namespace SingleAgent;
-
-///
-/// This executor parses survey responses and produces structured output.
-/// Example input: "Rating: 8. The app is good but checkout process is confusing."
-///
-[System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1812:Avoid uninstantiated internal classes", Justification = "Instantiated by workflow framework")]
-internal sealed partial class SurveyResponseParserExecutor() : Executor("SurveyResponseParserExecutor")
-{
- private static readonly JsonSerializerOptions s_jsonOptions = new()
- {
- WriteIndented = true
- };
-
- [GeneratedRegex(@"Rating:\s*(\d+)", RegexOptions.IgnoreCase)]
- private static partial Regex RatingRegex();
-
- public override ValueTask HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
- {
- SurveyResponse response = this.ParseSurveyResponse(message);
- string jsonResult = JsonSerializer.Serialize(response, s_jsonOptions);
- return ValueTask.FromResult(jsonResult);
- }
-
- private SurveyResponse ParseSurveyResponse(string message)
- {
- // Parse the message to extract rating and comment
- int? rating = null;
- string comment = message;
-
- // Try to extract rating using pattern "Rating: {number}"
- Match ratingMatch = RatingRegex().Match(message);
- if (ratingMatch.Success && int.TryParse(ratingMatch.Groups[1].Value, out int parsedRating))
- {
- rating = parsedRating;
-
- // Remove the rating part from the message to get the comment
- // Find the position after the rating number
- int ratingEndIndex = ratingMatch.Index + ratingMatch.Length;
-
- // Skip any separators (period, comma, dash, etc.) and whitespace
- while (ratingEndIndex < message.Length &&
- (char.IsWhiteSpace(message[ratingEndIndex]) ||
- message[ratingEndIndex] == '.' ||
- message[ratingEndIndex] == ',' ||
- message[ratingEndIndex] == '-'))
- {
- ratingEndIndex++;
- }
-
- if (ratingEndIndex < message.Length)
- {
- comment = message[ratingEndIndex..].Trim();
- }
- else
- {
- comment = string.Empty;
- }
- }
-
- // Create and return the structured response
- return new SurveyResponse
- {
- Rating = rating,
- Comment = comment,
- OriginalMessage = message
- };
- }
-
- private sealed class SurveyResponse
- {
- public int? Rating { get; set; }
- public string Comment { get; set; } = string.Empty;
- public string OriginalMessage { get; set; } = string.Empty;
- }
-}
diff --git a/dotnet/samples/AzureFunctions/11_WorkflowWithDifferentTypes/demo.http b/dotnet/samples/AzureFunctions/11_WorkflowWithDifferentTypes/demo.http
deleted file mode 100644
index e6275a79da..0000000000
--- a/dotnet/samples/AzureFunctions/11_WorkflowWithDifferentTypes/demo.http
+++ /dev/null
@@ -1,26 +0,0 @@
-# Default endpoint address for local testing
-@authority=http://localhost:7071
-
-### Look up Order #1001 (Shipped order with multiple items)
-POST {{authority}}/api/workflows/OrderLookupWorkflow/run
-Content-Type: text/plain
-
-Check order 1001
-
-### Look up Order #1002 (Processing order)
-POST {{authority}}/api/workflows/OrderLookupWorkflow/run
-Content-Type: text/plain
-
-order 1002
-
-### Look up Order #1003 (Delivered order)
-POST {{authority}}/api/workflows/OrderLookupWorkflow/run
-Content-Type: text/plain
-
-1003
-
-### Look up non-existent order
-POST {{authority}}/api/workflows/OrderLookupWorkflow/run
-Content-Type: text/plain
-
-order #9999
diff --git a/dotnet/samples/AzureFunctions/12_WorkflowSharedState/README.md b/dotnet/samples/AzureFunctions/12_WorkflowSharedState/README.md
deleted file mode 100644
index d4ac968978..0000000000
--- a/dotnet/samples/AzureFunctions/12_WorkflowSharedState/README.md
+++ /dev/null
@@ -1,89 +0,0 @@
-# Single Agent Sample
-
-This sample demonstrates how to use the Durable Agent Framework (DAFx) to create a simple Azure Functions app that hosts a single AI agent and provides direct HTTP API access for interactive conversations.
-
-## Key Concepts Demonstrated
-
-- Using the Microsoft Agent Framework to define a simple AI agent with a name and instructions.
-- Registering agents with the Function app and running them using HTTP.
-- Conversation management (via session IDs) for isolated interactions.
-
-## Environment Setup
-
-See the [README.md](../README.md) file in the parent directory for more information on how to configure the environment, including how to install and run common sample dependencies.
-
-## Running the Sample
-
-With the environment setup and function app running, you can test the sample by sending an HTTP request to the agent endpoint.
-
-You can use the `demo.http` file to send a message to the agent, or a command line tool like `curl` as shown below:
-
-Bash (Linux/macOS/WSL):
-
-```bash
-curl -X POST http://localhost:7071/api/agents/Joker/run \
- -H "Content-Type: text/plain" \
- -d "Tell me a joke about a pirate."
-```
-
-PowerShell:
-
-```powershell
-Invoke-RestMethod -Method Post `
- -Uri http://localhost:7071/api/agents/Joker/run `
- -ContentType text/plain `
- -Body "Tell me a joke about a pirate."
-```
-
-You can also send JSON requests:
-
-```bash
-curl -X POST http://localhost:7071/api/agents/Joker/run \
- -H "Content-Type: application/json" \
- -H "Accept: application/json" \
- -d '{"message": "Tell me a joke about a pirate."}'
-```
-
-To continue a conversation, include the `thread_id` in the query string or JSON body:
-
-```bash
-curl -X POST "http://localhost:7071/api/agents/Joker/run?thread_id=your-thread-id" \
- -H "Content-Type: application/json" \
- -H "Accept: application/json" \
- -d '{"message": "Tell me another one."}'
-```
-
-The response from the agent will be displayed in the terminal where you ran `func start`. The expected `text/plain` output will look something like:
-
-```text
-Why don't pirates ever learn the alphabet? Because they always get stuck at "C"!
-```
-
-The expected `application/json` output will look something like:
-
-```json
-{
- "status": 200,
- "thread_id": "ee6e47a0-f24b-40b1-ade8-16fcebb9eb40",
- "response": {
- "Messages": [
- {
- "AuthorName": "Joker",
- "CreatedAt": "2025-11-11T12:00:00.0000000Z",
- "Role": "assistant",
- "Contents": [
- {
- "Type": "text",
- "Text": "Why don't pirates ever learn the alphabet? Because they always get stuck at 'C'!"
- }
- ]
- }
- ],
- "Usage": {
- "InputTokenCount": 78,
- "OutputTokenCount": 36,
- "TotalTokenCount": 114
- }
- }
-}
-```
diff --git a/dotnet/samples/AzureFunctions/12_WorkflowSharedState/host.json b/dotnet/samples/AzureFunctions/12_WorkflowSharedState/host.json
deleted file mode 100644
index 9384a0a583..0000000000
--- a/dotnet/samples/AzureFunctions/12_WorkflowSharedState/host.json
+++ /dev/null
@@ -1,20 +0,0 @@
-{
- "version": "2.0",
- "logging": {
- "logLevel": {
- "Microsoft.Agents.AI.DurableTask": "Information",
- "Microsoft.Agents.AI.Hosting.AzureFunctions": "Information",
- "DurableTask": "Information",
- "Microsoft.DurableTask": "Information"
- }
- },
- "extensions": {
- "durableTask": {
- "hubName": "default",
- "storageProvider": {
- "type": "AzureManaged",
- "connectionStringName": "DURABLE_TASK_SCHEDULER_CONNECTION_STRING"
- }
- }
- }
-}
diff --git a/dotnet/src/Microsoft.Agents.AI.DurableTask/DurableWorkflowRunResult.cs b/dotnet/src/Microsoft.Agents.AI.DurableTask/DurableWorkflowRunResult.cs
new file mode 100644
index 0000000000..77d7bdcce9
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.DurableTask/DurableWorkflowRunResult.cs
@@ -0,0 +1,34 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Text.Json.Serialization;
+
+namespace Microsoft.Agents.AI.DurableTask;
+
+///
+/// Represents the result of a durable workflow orchestration execution.
+///
+public sealed class DurableWorkflowRunResult
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The name of the workflow that was executed.
+ /// The output from the workflow execution.
+ public DurableWorkflowRunResult(string workflowName, string output)
+ {
+ this.WorkflowName = workflowName;
+ this.Output = output;
+ }
+
+ ///
+ /// Gets the name of the workflow that was executed.
+ ///
+ [JsonPropertyName("workflowName")]
+ public string WorkflowName { get; }
+
+ ///
+ /// Gets the output from the workflow execution.
+ ///
+ [JsonPropertyName("output")]
+ public string Output { get; }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.DurableTask/DurableWorkflowRunner.cs b/dotnet/src/Microsoft.Agents.AI.DurableTask/DurableWorkflowRunner.cs
index becdf348fa..5a966b4688 100644
--- a/dotnet/src/Microsoft.Agents.AI.DurableTask/DurableWorkflowRunner.cs
+++ b/dotnet/src/Microsoft.Agents.AI.DurableTask/DurableWorkflowRunner.cs
@@ -43,29 +43,32 @@ public class DurableWorkflowRunner
/// Runs a workflow orchestration.
///
/// The task orchestration context.
- /// The workflow run request containing workflow name and input.
+ /// The workflow run input containing workflow name and input.
/// The replay-safe logger for orchestration logging.
- /// A list containing the workflow execution result.
- public async Task> RunWorkflowOrchestrationAsync(
+ /// The result of the workflow execution.
+ /// Thrown when the specified workflow is not found.
+ public async Task RunWorkflowOrchestrationAsync(
TaskOrchestrationContext context,
- DurableWorkflowRunRequest request,
+ string input,
ILogger logger)
{
ArgumentNullException.ThrowIfNull(context);
- ArgumentNullException.ThrowIfNull(request);
+ ArgumentNullException.ThrowIfNull(input);
- if (!this.Options.Workflows.TryGetValue(request.WorkflowName, out Workflow? workflow))
+ string orchestrationName = context.Name;
+ string workflowName = WorkflowNamingHelper.ToWorkflowName(orchestrationName);
+ if (!this.Options.Workflows.TryGetValue(workflowName, out Workflow? workflow))
{
- throw new InvalidOperationException($"Workflow '{request.WorkflowName}' not found.");
+ throw new InvalidOperationException($"Workflow '{workflowName}' not found.");
}
logger.LogRunningWorkflow(workflow.Name);
- string result = await this.ExecuteWorkflowLevelsAsync(context, workflow, request.Input, logger).ConfigureAwait(true);
+ string result = await this.ExecuteWorkflowLevelsAsync(context, workflow, input, logger).ConfigureAwait(true);
await CleanupWorkflowStateAsync(context).ConfigureAwait(true);
- return [result];
+ return result;
}
///
@@ -87,36 +90,23 @@ public class DurableWorkflowRunner
/// The extracted executor name.
protected static string ParseExecutorName(string activityFunctionName)
{
- const string Prefix = "dafx-";
-
- if (!activityFunctionName.StartsWith(Prefix, StringComparison.Ordinal))
+ if (!activityFunctionName.StartsWith(WorkflowNamingHelper.OrchestrationFunctionPrefix, StringComparison.Ordinal))
{
throw new InvalidOperationException(
- $"Activity function name '{activityFunctionName}' does not start with '{Prefix}' prefix.");
+ $"Activity function name '{activityFunctionName}' does not start with '{WorkflowNamingHelper.OrchestrationFunctionPrefix}' prefix.");
}
- string executorName = activityFunctionName[Prefix.Length..];
+ string executorName = activityFunctionName[WorkflowNamingHelper.OrchestrationFunctionPrefix.Length..];
if (string.IsNullOrEmpty(executorName))
{
throw new InvalidOperationException(
- $"Activity function name '{activityFunctionName}' is not in the expected format '{Prefix}{{executorName}}'.");
+ $"Activity function name '{activityFunctionName}' is not in the expected format '{WorkflowNamingHelper.OrchestrationFunctionPrefix}{{executorName}}'.");
}
return executorName;
}
- ///
- /// Gets the base name from an executor ID by removing any GUID suffix.
- ///
- /// The executor ID.
- /// The base name without the GUID suffix.
- protected static string GetBaseName(string executorId)
- {
- int underscoreIndex = executorId.IndexOf('_');
- return underscoreIndex > 0 ? executorId[..underscoreIndex] : executorId;
- }
-
///
/// Serializes a list of strings to JSON.
///
@@ -240,7 +230,8 @@ public class DurableWorkflowRunner
{
if (!executorInfo.IsAgenticExecutor)
{
- string triggerName = $"dafx-{GetBaseName(executorInfo.ExecutorId)}";
+ string executorName = WorkflowNamingHelper.GetExecutorName(executorInfo.ExecutorId);
+ string triggerName = WorkflowNamingHelper.ToOrchestrationFunctionName(executorName);
return await context.CallActivityAsync(triggerName, input).ConfigureAwait(true);
}
@@ -253,7 +244,7 @@ public class DurableWorkflowRunner
string input,
ILogger logger)
{
- string agentName = GetBaseName(executorInfo.ExecutorId);
+ string agentName = WorkflowNamingHelper.GetExecutorName(executorInfo.ExecutorId);
DurableAIAgent agent = context.GetAgent(agentName);
if (agent is null)
diff --git a/dotnet/src/Microsoft.Agents.AI.DurableTask/WorkflowNamingHelper.cs b/dotnet/src/Microsoft.Agents.AI.DurableTask/WorkflowNamingHelper.cs
new file mode 100644
index 0000000000..6c53fd5b36
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.DurableTask/WorkflowNamingHelper.cs
@@ -0,0 +1,127 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+namespace Microsoft.Agents.AI.DurableTask;
+
+///
+/// Provides helper methods for workflow naming conventions used in durable orchestrations.
+///
+public static class WorkflowNamingHelper
+{
+ ///
+ /// The prefix used for durable workflow orchestration function names.
+ ///
+ public const string OrchestrationFunctionPrefix = "dafx-";
+
+ ///
+ /// Converts a workflow name to its corresponding orchestration function name.
+ ///
+ /// The workflow name.
+ /// The orchestration function name.
+ /// Thrown when the workflow name is null or empty.
+ public static string ToOrchestrationFunctionName(string workflowName)
+ {
+ ArgumentException.ThrowIfNullOrEmpty(workflowName);
+ return $"{OrchestrationFunctionPrefix}{workflowName}";
+ }
+
+ ///
+ /// Converts an orchestration function name back to its workflow name.
+ ///
+ /// The orchestration function name.
+ /// The workflow name.
+ /// Thrown when the orchestration function name is null, empty, or doesn't have the expected prefix.
+ public static string ToWorkflowName(string orchestrationFunctionName)
+ {
+ ArgumentException.ThrowIfNullOrEmpty(orchestrationFunctionName);
+
+ if (!orchestrationFunctionName.StartsWith(OrchestrationFunctionPrefix, StringComparison.Ordinal))
+ {
+ throw new ArgumentException(
+ $"Orchestration function name '{orchestrationFunctionName}' does not start with the expected '{OrchestrationFunctionPrefix}' prefix.",
+ nameof(orchestrationFunctionName));
+ }
+
+ string workflowName = orchestrationFunctionName[OrchestrationFunctionPrefix.Length..];
+
+ if (string.IsNullOrEmpty(workflowName))
+ {
+ throw new ArgumentException(
+ $"Orchestration function name '{orchestrationFunctionName}' does not contain a workflow name after the prefix.",
+ nameof(orchestrationFunctionName));
+ }
+
+ return workflowName;
+ }
+
+ ///
+ /// Tries to convert an orchestration function name back to its workflow name.
+ ///
+ /// The orchestration function name.
+ /// When this method returns, contains the workflow name if the conversion succeeded, or null if it failed.
+ /// true if the conversion succeeded; otherwise, false.
+ public static bool TryGetWorkflowName(string? orchestrationFunctionName, out string? workflowName)
+ {
+ workflowName = null;
+
+ if (string.IsNullOrEmpty(orchestrationFunctionName))
+ {
+ return false;
+ }
+
+ if (!orchestrationFunctionName.StartsWith(OrchestrationFunctionPrefix, StringComparison.Ordinal))
+ {
+ return false;
+ }
+
+ workflowName = orchestrationFunctionName[OrchestrationFunctionPrefix.Length..];
+ return !string.IsNullOrEmpty(workflowName);
+ }
+
+ ///
+ /// The suffix separator used when the workflow builder appends a GUID to executor IDs.
+ ///
+ ///
+ /// For agentic executors, the workflow builder appends a GUID suffix to ensure uniqueness.
+ /// For example: "Physicist_8884e71021334ce49517fa2b17b1695b".
+ ///
+ private const char ExecutorIdSuffixSeparator = '_';
+
+ ///
+ /// Extracts the executor name from an executor ID.
+ ///
+ ///
+ ///
+ /// For non-agentic executors, the executor ID is the same as the executor name (e.g., "OrderParser").
+ ///
+ ///
+ /// For agentic executors, the workflow builder appends a GUID suffix separated by an underscore
+ /// (e.g., "Physicist_8884e71021334ce49517fa2b17b1695b"). This method extracts just the name portion.
+ ///
+ ///
+ /// The executor ID, which may contain a GUID suffix.
+ /// The executor name without any GUID suffix.
+ /// Thrown when the executor ID is null or empty.
+ public static string GetExecutorName(string executorId)
+ {
+ ArgumentException.ThrowIfNullOrEmpty(executorId);
+
+ int separatorIndex = executorId.IndexOf(ExecutorIdSuffixSeparator);
+ return separatorIndex > 0 ? executorId[..separatorIndex] : executorId;
+ }
+
+ ///
+ /// Determines whether the executor ID contains a GUID suffix.
+ ///
+ /// The executor ID to check.
+ /// true if the executor ID contains a suffix; otherwise, false.
+ public static bool HasExecutorIdSuffix(string? executorId)
+ {
+ if (string.IsNullOrEmpty(executorId))
+ {
+ return false;
+ }
+
+ int separatorIndex = executorId.IndexOf(ExecutorIdSuffixSeparator);
+ return separatorIndex > 0 && separatorIndex < executorId.Length - 1;
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/BuiltInFunctions.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/BuiltInFunctions.cs
index bb442471fe..21601406fa 100644
--- a/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/BuiltInFunctions.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/BuiltInFunctions.cs
@@ -75,10 +75,10 @@ internal static class BuiltInFunctions
[DurableClient] DurableTaskClient client,
FunctionContext context)
{
- var workflowName = context.FunctionDefinition.Name.Replace("http-", "");
- var orchestrationFunctionName = $"dafx-{workflowName}";
+ var workflowName = context.FunctionDefinition.Name.Replace(HttpPrefix, string.Empty);
+ var orchestrationFunctionName = WorkflowNamingHelper.ToOrchestrationFunctionName(workflowName);
var inputMessage = await req.ReadAsStringAsync();
- string instanceId = await client.ScheduleNewOrchestrationInstanceAsync(orchestrationFunctionName, new DurableWorkflowRunRequest { WorkflowName = workflowName, Input = inputMessage! }); //OrchFunction"); // dafx-MyTestWorkflow");
+ string instanceId = await client.ScheduleNewOrchestrationInstanceAsync(orchestrationFunctionName, inputMessage);
HttpResponseData response = req.CreateResponse(HttpStatusCode.Accepted);
await response.WriteStringAsync($"InvokeWorkflowOrechstrtationAsync is invoked for {workflowName}. Orchestration instanceId: {instanceId}");
@@ -221,7 +221,7 @@ internal static class BuiltInFunctions
}
#pragma warning disable DURTASK001 // Durable analyzer complained
- public static Task> WorkflowRunnerOrchestrationAsync(TaskOrchestrationContext context, DurableWorkflowRunRequest input)
+ public static Task WorkflowRunnerOrchestrationAsync(TaskOrchestrationContext context, DurableWorkflowRunRequest input)
{
ArgumentNullException.ThrowIfNull(context);
@@ -236,7 +236,7 @@ internal static class BuiltInFunctions
var workFlowName = input.WorkflowName;
- return Task.FromResult(new List() { workFlowName });
+ return Task.FromResult(new DurableWorkflowRunResult(workFlowName, workFlowName));
}
#pragma warning restore DURTASK001
diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/CoreAgentConfigurationExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/CoreAgentConfigurationExtensions.cs
index 16ae42662c..481d146f52 100644
--- a/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/CoreAgentConfigurationExtensions.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/CoreAgentConfigurationExtensions.cs
@@ -1,5 +1,6 @@
// Copyright (c) Microsoft. All rights reserved.
+using Microsoft.Agents.AI.DurableTask;
using Microsoft.Azure.Functions.Worker.Builder;
using Microsoft.Azure.Functions.Worker.Core.FunctionMetadata;
using Microsoft.Extensions.DependencyInjection;
@@ -39,7 +40,12 @@ internal static class CoreAgentConfigurationExtensions
/// The functions application builder for method chaining.
internal static FunctionsApplicationBuilder RegisterWorkflowServices(this FunctionsApplicationBuilder builder)
{
+ // Register FunctionsWorkflowRunner as a singleton
builder.Services.TryAddSingleton();
+
+ // Also register it as DurableWorkflowRunner so orchestrations can resolve it by base type
+ builder.Services.TryAddSingleton(sp => sp.GetRequiredService());
+
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton());
return builder;
diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/DurableOptionsExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/DurableOptionsExtensions.cs
index 6ec90142e6..30b7a4dc55 100644
--- a/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/DurableOptionsExtensions.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/DurableOptionsExtensions.cs
@@ -81,25 +81,24 @@ public static class DurableOptionsExtensions
private static void ConfigureWorkflowOrchestrations(FunctionsApplicationBuilder builder, DurableWorkflowOptions workflows)
{
- // Registering orchestration functions and the workflow state entity.
-
builder.ConfigureDurableWorker().AddTasks(tasks =>
{
- // Register the workflow state entity for durable state management
- // Each orchestration instance gets its own entity keyed by instance ID
+ // Register the workflow state entity for shared state management within workflows.
tasks.AddEntity(WorkflowSharedStateEntity.EntityName);
foreach (string workflowName in workflows.Workflows.Select(kp => kp.Key))
{
- tasks.AddOrchestratorFunc>(
- $"dafx-{workflowName}",
+ string orchestrationFunctionName = WorkflowNamingHelper.ToOrchestrationFunctionName(workflowName);
+
+ tasks.AddOrchestratorFunc(
+ orchestrationFunctionName,
async (orchestrationContext, request) =>
{
FunctionContext functionContext = orchestrationContext.GetFunctionContext()
?? throw new InvalidOperationException("FunctionContext is not available in the orchestration context.");
DurableWorkflowRunner runner = functionContext.InstanceServices.GetRequiredService();
- ILogger logger = orchestrationContext.CreateReplaySafeLogger($"dafx-orchestration-{workflowName}");
+ ILogger logger = orchestrationContext.CreateReplaySafeLogger(orchestrationFunctionName);
return await runner.RunWorkflowOrchestrationAsync(orchestrationContext, request, logger).ConfigureAwait(true);
});
diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/DurableWorkflowFunctionMetadataTransformer.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/DurableWorkflowFunctionMetadataTransformer.cs
index abdcd2b020..d231e2550c 100644
--- a/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/DurableWorkflowFunctionMetadataTransformer.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/DurableWorkflowFunctionMetadataTransformer.cs
@@ -61,7 +61,8 @@ internal sealed class DurableWorkflowFunctionMetadataTransformer : IFunctionMeta
{
if (executorInfos.TryGetValue(executorId, out ExecutorInfo? executorInfo))
{
- string functionName = $"dafx-{executorId.Split("_")[0]}";
+ string executorName = WorkflowNamingHelper.GetExecutorName(executorId);
+ string functionName = WorkflowNamingHelper.ToOrchestrationFunctionName(executorName);
// Check if the executor type is an agent-related type
if (WorkflowHelper.IsAgentExecutorType(executorInfo.ExecutorType))