-
Notifications
You must be signed in to change notification settings - Fork 4.5k
Description
Feature Request
Title: New Feature: IGuardrailProvider interface for policy-based function invocation control
Is your feature request related to a problem?
Enterprise deployments of Semantic Kernel agents need a standardized way to enforce authorization policies on function invocations -- deciding whether a tool call should proceed based on caller identity, resource scope, risk level, or organizational policy. Today this is achievable through IAutoFunctionInvocationFilter, but each team builds ad-hoc policy logic inside their filter implementations with no shared contract for pluggable policy providers.
This gap has been raised repeatedly:
- Guardrails for C# https://github.com/ShreyaR/guardrails #1409 -- "Guardrails for C#" (2023, closed with TypeChat redirect, no guardrail interface shipped)
- .Net: Filters use cases to be supported before making the feature non-experimental #5436 -- "Filters use cases to be supported before making feature non-experimental" (explicitly lists "Function Call Approval" as a core requirement)
- New Feature: Agent Invocation Filter #10951 -- "Agent Invocation Filter" (2025, requesting filters that wrap agent invocations "allowing for scenarios like guardrails etc." for both .NET and Python)
- Python: Proposal: Governance Policy Filter for Semantic Kernel #13556 -- "Governance Policy Filter for Semantic Kernel" (2026, proposed a governance layer, closed and redirected to microsoft/agent-governance-toolkit)
- Python: Bug: In Handoff orchestration - when handling a request message from an agent in the handoff group, the persona adoption message is sent by the User which triggers openAI jailbreak guardrails #12294 -- Handoff orchestration triggering OpenAI jailbreak guardrails (demonstrates the practical need for pre-invocation policy checks)
Describe the solution you'd like
A thin IGuardrailProvider interface that plugs into the existing filter pipeline. It separates the policy decision (should this call proceed?) from the filter mechanics (intercepting the pipeline).
C# (.NET)
namespace Microsoft.SemanticKernel;
/// <summary>
/// Provides policy-based authorization decisions for function invocations.
/// Implementations are registered with the Kernel and consulted by a
/// built-in AutoFunctionInvocationFilter before each tool call.
/// </summary>
public interface IGuardrailProvider
{
/// <summary>
/// Evaluates whether a function invocation should proceed.
/// </summary>
Task<GuardrailDecision> EvaluateAsync(
GuardrailContext context,
CancellationToken cancellationToken = default);
}
public sealed class GuardrailContext
{
public KernelFunction Function { get; init; }
public KernelArguments Arguments { get; init; }
public AutoFunctionInvocationContext InvocationContext { get; init; }
/// <summary>Optional caller identity for multi-tenant scenarios.</summary>
public ClaimsPrincipal? Principal { get; init; }
}
public sealed class GuardrailDecision
{
public bool IsAllowed { get; init; }
public string? Reason { get; init; }
public static GuardrailDecision Allow() => new() { IsAllowed = true };
public static GuardrailDecision Deny(string reason) =>
new() { IsAllowed = false, Reason = reason };
}Python
from abc import ABC, abstractmethod
from dataclasses import dataclass
from semantic_kernel.filters.auto_function_invocation.auto_function_invocation_context import (
AutoFunctionInvocationContext,
)
@dataclass
class GuardrailContext:
function_name: str
plugin_name: str
arguments: dict
invocation_context: AutoFunctionInvocationContext
principal: dict | None = None # Caller identity claims
@dataclass
class GuardrailDecision:
is_allowed: bool
reason: str | None = None
@staticmethod
def allow() -> "GuardrailDecision":
return GuardrailDecision(is_allowed=True)
@staticmethod
def deny(reason: str) -> "GuardrailDecision":
return GuardrailDecision(is_allowed=False, reason=reason)
class GuardrailProvider(ABC):
@abstractmethod
async def evaluate(self, context: GuardrailContext) -> GuardrailDecision:
"""Return whether this function invocation should proceed."""
...Integration with existing filters
The provider does not replace filters -- it plugs into them. A built-in GuardrailAutoFunctionInvocationFilter consults registered providers:
public class GuardrailAutoFunctionInvocationFilter : IAutoFunctionInvocationFilter
{
private readonly IEnumerable<IGuardrailProvider> _providers;
public async Task OnAutoFunctionInvocationAsync(
AutoFunctionInvocationContext context,
Func<AutoFunctionInvocationContext, Task> next)
{
var guardrailContext = new GuardrailContext
{
Function = context.Function,
Arguments = context.Arguments,
InvocationContext = context
};
foreach (var provider in _providers)
{
var decision = await provider.EvaluateAsync(guardrailContext);
if (!decision.IsAllowed)
{
context.Result = new FunctionResult(context.Function,
$"Blocked by guardrail: {decision.Reason}");
return; // skip next -- do not invoke function
}
}
await next(context);
}
}Registration
var kernel = Kernel.CreateBuilder()
.AddAzureOpenAIChatCompletion(...)
.Build();
kernel.AutoFunctionInvocationFilters.Add(
new GuardrailAutoFunctionInvocationFilter(
new MyOrgPolicyProvider(),
new AzureContentSafetyProvider()
));Describe alternatives you've considered
- Raw
IAutoFunctionInvocationFilteronly -- Works today, but every team re-invents the allow/deny pattern. No shared contract means no ecosystem of pluggable providers. - External governance toolkit -- Python: Proposal: Governance Policy Filter for Semantic Kernel #13556 was redirected to microsoft/agent-governance-toolkit, but a lightweight in-kernel interface would enable third-party providers without requiring a separate toolkit dependency.
Why this fits Semantic Kernel's architecture
- Follows the existing filter pipeline pattern (
IAutoFunctionInvocationFilter,IFunctionInvocationFilter,IPromptRenderFilter) - Does not modify the filter contract -- composes on top of it
- Supports the "Function Call Approval" scenario explicitly called out in .Net: Filters use cases to be supported before making the feature non-experimental #5436
- Works for both .NET and Python with identical semantics
- Enables the agent-level guardrails requested in New Feature: Agent Invocation Filter #10951
Additional context
A reference implementation of this provider pattern exists in APort Agent Guardrails, which enforces passport-based tool authorization policies for AI agents. The interface proposed here is deliberately provider-agnostic so that any policy backend (Azure Content Safety, OPA, custom rules, etc.) can plug in.