diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/DefaultFunctionsAgentOptionsProvider.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/DefaultFunctionsAgentOptionsProvider.cs
index 1039fb5aec..4debb5facf 100644
--- a/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/DefaultFunctionsAgentOptionsProvider.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/DefaultFunctionsAgentOptionsProvider.cs
@@ -6,7 +6,8 @@ namespace Microsoft.Agents.AI.Hosting.AzureFunctions;
///
/// Provides access to agent-specific options for functions agents by name.
-/// Returns default options (HTTP trigger enabled, MCP tool disabled) when no explicit options were configured.
+/// Returns when no explicit options have been configured for an agent,
+/// which distinguishes standalone agents from those auto-registered by workflows.
///
internal sealed class DefaultFunctionsAgentOptionsProvider(IReadOnlyDictionary functionsAgentOptions)
: IFunctionsAgentOptionsProvider
@@ -14,32 +15,19 @@ internal sealed class DefaultFunctionsAgentOptionsProvider(IReadOnlyDictionary _functionsAgentOptions =
functionsAgentOptions ?? throw new ArgumentNullException(nameof(functionsAgentOptions));
- // Default options. HTTP trigger enabled, MCP tool disabled.
- private static readonly FunctionsAgentOptions s_defaultOptions = new()
- {
- HttpTrigger = { IsEnabled = true },
- McpToolTrigger = { IsEnabled = false }
- };
-
///
/// Attempts to retrieve the options associated with the specified agent name.
- /// If not found, a default options instance (with HTTP trigger enabled) is returned.
+ /// Returns when no options have been explicitly configured for the agent.
///
/// The name of the agent whose options are to be retrieved. Cannot be null or empty.
- /// The options for the specified agent. Will never be null.
- /// Always true. Returns configured options if present; otherwise default fallback options.
+ ///
+ /// When this method returns , contains the options for the specified agent;
+ /// otherwise, .
+ ///
+ /// if options were found for the agent; otherwise, .
public bool TryGet(string agentName, [NotNullWhen(true)] out FunctionsAgentOptions? options)
{
ArgumentException.ThrowIfNullOrEmpty(agentName);
-
- if (this._functionsAgentOptions.TryGetValue(agentName, out FunctionsAgentOptions? existing))
- {
- options = existing;
- return true;
- }
-
- // If not defined, return default options.
- options = s_defaultOptions;
- return true;
+ return this._functionsAgentOptions.TryGetValue(agentName, out options);
}
}
diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/DurableAgentFunctionMetadataTransformer.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/DurableAgentFunctionMetadataTransformer.cs
index 65578a7383..fe20eeb6f9 100644
--- a/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/DurableAgentFunctionMetadataTransformer.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/DurableAgentFunctionMetadataTransformer.cs
@@ -6,9 +6,13 @@ using Microsoft.Extensions.Logging;
namespace Microsoft.Agents.AI.Hosting.AzureFunctions;
///
-/// Transforms function metadata by registering durable agent functions for each configured agent.
+/// Transforms function metadata by registering durable agent functions for each explicitly configured agent.
///
-/// This transformer adds both entity trigger and HTTP trigger functions for every agent registered in the application.
+///
+/// This transformer adds entity, HTTP, and MCP tool trigger functions for agents that have
+/// explicit . Agents auto-registered by workflows
+/// (which lack explicit options) are handled by .
+///
internal sealed class DurableAgentFunctionMetadataTransformer : IFunctionMetadataTransformer
{
private readonly ILogger _logger;
@@ -38,24 +42,27 @@ internal sealed class DurableAgentFunctionMetadataTransformer : IFunctionMetadat
{
string agentName = kvp.Key;
- this._logger.LogRegisteringTriggerForAgent(agentName, "entity");
+ // Only generate triggers for agents with explicit Functions agent options.
+ // Agents auto-registered by workflows are handled by DurableWorkflowsFunctionMetadataTransformer.
+ if (!this._functionsAgentOptionsProvider.TryGet(agentName, out FunctionsAgentOptions? agentTriggerOptions))
+ {
+ continue;
+ }
+ this._logger.LogRegisteringTriggerForAgent(agentName, "entity");
original.Add(FunctionMetadataFactory.CreateEntityTrigger(agentName));
- if (this._functionsAgentOptionsProvider.TryGet(agentName, out FunctionsAgentOptions? agentTriggerOptions))
+ if (agentTriggerOptions.HttpTrigger.IsEnabled)
{
- if (agentTriggerOptions.HttpTrigger.IsEnabled)
- {
- this._logger.LogRegisteringTriggerForAgent(agentName, "http");
- original.Add(FunctionMetadataFactory.CreateHttpTrigger(agentName, $"agents/{agentName}/run", BuiltInFunctions.RunAgentHttpFunctionEntryPoint));
- }
+ this._logger.LogRegisteringTriggerForAgent(agentName, "http");
+ original.Add(FunctionMetadataFactory.CreateHttpTrigger(agentName, $"agents/{agentName}/run", BuiltInFunctions.RunAgentHttpFunctionEntryPoint));
+ }
- if (agentTriggerOptions.McpToolTrigger.IsEnabled)
- {
- AIAgent agent = kvp.Value(this._serviceProvider);
- this._logger.LogRegisteringTriggerForAgent(agentName, "mcpTool");
- original.Add(CreateMcpToolTrigger(agentName, agent.Description));
- }
+ if (agentTriggerOptions.McpToolTrigger.IsEnabled)
+ {
+ AIAgent agent = kvp.Value(this._serviceProvider);
+ this._logger.LogRegisteringTriggerForAgent(agentName, "mcpTool");
+ original.Add(CreateMcpToolTrigger(agentName, agent.Description));
}
}
}
diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/DurableAgentsOptionsExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/DurableAgentsOptionsExtensions.cs
index ad21d8f4e1..8d161710ae 100644
--- a/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/DurableAgentsOptionsExtensions.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/DurableAgentsOptionsExtensions.cs
@@ -134,4 +134,17 @@ public static class DurableAgentsOptionsExtensions
{
return new Dictionary(s_agentOptions, StringComparer.OrdinalIgnoreCase);
}
+
+ ///
+ /// Ensures every agent in has an entry in the
+ /// options registry. Agents that already have explicit options are left untouched.
+ /// New entries receive the default configuration (HTTP trigger enabled, MCP tool disabled).
+ ///
+ internal static void EnsureDefaultOptionsForAll(IEnumerable agentNames)
+ {
+ foreach (string name in agentNames)
+ {
+ s_agentOptions.TryAdd(name, new FunctionsAgentOptions { HttpTrigger = { IsEnabled = true } });
+ }
+ }
}
diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/FunctionsApplicationBuilderExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/FunctionsApplicationBuilderExtensions.cs
index 959ffab2f6..3c5e7936da 100644
--- a/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/FunctionsApplicationBuilderExtensions.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/FunctionsApplicationBuilderExtensions.cs
@@ -27,9 +27,16 @@ public static class FunctionsApplicationBuilderExtensions
{
ArgumentNullException.ThrowIfNull(configure);
+ // Create/get shared options BEFORE the DurableTask library call so it can find them.
+ FunctionsDurableOptions sharedOptions = GetOrCreateSharedOptions(builder.Services);
+
// The main agent services registration is done in Microsoft.DurableTask.Agents.
builder.Services.ConfigureDurableAgents(configure);
+ // Ensure all agents registered through this path have default FunctionsAgentOptions.
+ // This distinguishes them from agents auto-registered by workflows.
+ DurableAgentsOptionsExtensions.EnsureDefaultOptionsForAll(sharedOptions.Agents.GetAgentFactories().Keys);
+
builder.Services.TryAddSingleton(_ =>
new DefaultFunctionsAgentOptionsProvider(DurableAgentsOptionsExtensions.GetAgentOptionsSnapshot()));
@@ -67,7 +74,7 @@ public static class FunctionsApplicationBuilderExtensions
builder.Services.ConfigureDurableOptions(configure);
- if (sharedOptions.Agents.GetAgentFactories().Count > 0)
+ if (DurableAgentsOptionsExtensions.GetAgentOptionsSnapshot().Count > 0)
{
builder.Services.TryAddSingleton(_ =>
new DefaultFunctionsAgentOptionsProvider(DurableAgentsOptionsExtensions.GetAgentOptionsSnapshot()));
diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/Workflows/DurableWorkflowsFunctionMetadataTransformer.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/Workflows/DurableWorkflowsFunctionMetadataTransformer.cs
index 8066eefccc..dc7b799b00 100644
--- a/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/Workflows/DurableWorkflowsFunctionMetadataTransformer.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/Workflows/DurableWorkflowsFunctionMetadataTransformer.cs
@@ -50,8 +50,11 @@ internal sealed class DurableWorkflowsFunctionMetadataTransformer : IFunctionMet
int initialCount = original.Count;
this._logger.LogTransformingFunctionMetadata(initialCount);
- // Track registered function names to avoid duplicates when workflows share executors.
- HashSet registeredFunctions = [];
+ // Seed with existing function names to avoid duplicates across transformers
+ // (e.g., when DurableAgentFunctionMetadataTransformer already registered entity triggers).
+ HashSet registeredFunctions = new(
+ original.Select(f => f.Name!),
+ StringComparer.OrdinalIgnoreCase);
DurableWorkflowOptions workflowOptions = this._options.Workflows;
foreach (var workflow in workflowOptions.Workflows)
diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.AzureFunctions.UnitTests/DurableAgentFunctionMetadataTransformerTests.cs b/dotnet/tests/Microsoft.Agents.AI.Hosting.AzureFunctions.UnitTests/DurableAgentFunctionMetadataTransformerTests.cs
index 7d3a2ec13e..82824e0d8c 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Hosting.AzureFunctions.UnitTests/DurableAgentFunctionMetadataTransformerTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.AzureFunctions.UnitTests/DurableAgentFunctionMetadataTransformerTests.cs
@@ -148,6 +148,45 @@ public sealed class DurableAgentFunctionMetadataTransformerTests
}
}
+ [Fact]
+ public void Transform_SkipsAgents_WithoutExplicitOptions()
+ {
+ // Arrange: two agents in the dictionary, but only one has explicit FunctionsAgentOptions.
+ // This simulates a workflow-auto-registered agent (workflowAgent) alongside a standalone agent.
+ Dictionary> agents = new()
+ {
+ { "standaloneAgent", _ => new TestAgent("standaloneAgent", "Standalone agent") },
+ { "workflowAgent", _ => new TestAgent("workflowAgent", "Auto-registered by workflow") }
+ };
+
+ FunctionsAgentOptions standaloneOptions = new();
+ standaloneOptions.HttpTrigger.IsEnabled = true;
+
+ // Only standaloneAgent has explicit options; workflowAgent does not.
+ IFunctionsAgentOptionsProvider agentOptionsProvider = new FakeOptionsProvider(new Dictionary
+ {
+ { "standaloneAgent", standaloneOptions }
+ });
+
+ List metadataList = [];
+
+ DurableAgentFunctionMetadataTransformer transformer = new(
+ agents,
+ NullLogger.Instance,
+ new FakeServiceProvider(),
+ agentOptionsProvider);
+
+ // Act
+ transformer.Transform(metadataList);
+
+ // Assert: only standaloneAgent should have triggers (entity + http = 2).
+ // workflowAgent should be skipped entirely.
+ Assert.Equal(2, metadataList.Count);
+ Assert.Contains(metadataList, m => m.Name == "dafx-standaloneAgent");
+ Assert.Contains(metadataList, m => m.Name == "http-standaloneAgent");
+ Assert.DoesNotContain(metadataList, m => m.Name!.Contains("workflowAgent"));
+ }
+
private static List BuildFunctionMetadataList(int numberOfFunctions)
{
List list = [];