microsoft / microsoft/semantic-kernel
New Feature: IGuardrailProvider interface for policy-based function invocation control
Nobody has claimed this yet.
- Dominant language
- C#
- Stars
- 28.6k
- Forks
- 4.8k
- Avg merge
- 14h 13m
- Merged PRs (30d)
- 18
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:
- #1409 -- "Guardrails for C#" (2023, closed with TypeChat redirect, no guardrail interface shipped)
- #5436 -- "Filters use cases to be supported before making feature non-experimental" (explicitly lists "Function Call Approval" as a core requirement)
- #10951 -- "Agent Invocation Filter" (2025, requesting filters that wrap agent invocations "allowing for scenarios like guardrails etc." for both .NET and Python)
- #13556 -- "Governance Policy Filter for Semantic Kernel" (2026, proposed a governance layer, closed and redirected to microsoft/agent-governance-toolkit)
- #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 -- #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 #5436
- Works for both .NET and Python with identical semantics
- Enables the agent-level guardrails requested in #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.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the existing IAutoFunctionInvocationFilter pipeline and its .NET and Python entry points, then compare how filters are registered and how invocation context and results are represented. Done means a provider contract and filter integration are defined consistently for both languages, including allow, deny, cancellation, and registration behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp, python
- Domain
- ai, authorization, security
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100