Skip to content

New Feature: Agent Invocation Filter #10951

Closed
Closed

Description


name: Feature request
about: Suggest an idea for this project


Similar to the existing Function Invocation Filters, a filter that wraps the Agent invocation, allowing for scenario's like guardrails etc. This is for both Python and .Net

Activity

  1. changed the title [-]New Feature: Agent Invocation Filter[/-] [+]Python: New Feature: Agent Invocation Filter[/+] on Mar 13, 2025
  2. changed the title [-]New Feature: Agent Invocation Filter[/-] [+].Net: New Feature: Agent Invocation Filter[/+] on Mar 13, 2025
  3. unsafecode commented on Mar 13, 2025

    @unsafecode
    Contributor

    We have many asks about "agentic guardrails" to provide some business guidelines or "fencing" around agent execution:

    • Check if an agent instructions comply with high-level policies, regardless of its current config
    • Check if an input provided to an agent is "safe", for instance by calling Azure Content Safety
    • Check if an agent output complies with some corporate guidance, and possibly either review it or provide feedback to the agent to fix it
  4. changed the title [-].Net: New Feature: Agent Invocation Filter[/-] [+]New Feature: Agent Invocation Filter[/+] on Mar 13, 2025
  5. stevenelliottjr commented on Feb 8, 2026

    @stevenelliottjr

    Design Proposal: IAgentInvocationFilter

    Eduard van Valkenburg (@eavanvalkenburg) unsafecode — I'd like to propose a design for this feature and would appreciate your feedback before opening a PR.

    Interface

    Following the existing filter pattern (ADR 0043, Option 4) with the next delegate middleware:

    namespace Microsoft.SemanticKernel.Agents;
    
    public interface IAgentInvocationFilter
    {
        Task OnAgentInvocationAsync(
            AgentInvocationContext context,
            Func<AgentInvocationContext, Task> next);
    }

    Context Class

    public class AgentInvocationContext
    {
        public Agent Agent { get; }
        public Kernel Kernel { get; }
        public string? Instructions => Agent.Instructions;
        public ICollection<ChatMessageContent> Messages { get; set; }  // mutable for pre-invocation modification
        public AgentThread? Thread { get; set; }
        public AgentInvokeOptions? Options { get; set; }
        public CancellationToken CancellationToken { get; init; }
        public bool IsStreaming { get; init; }
    
        // Non-streaming: populated after next(). Filter can inspect/modify.
        public IList<AgentResponseItem<ChatMessageContent>>? Result { get; set; }
    
        // Streaming: deferred IAsyncEnumerable set after next(). Filter can wrap/replace. No buffering.
        public IAsyncEnumerable<AgentResponseItem<StreamingChatMessageContent>>? StreamingResult { get; set; }
    }

    Where filters live

    SemanticKernel.Abstractions (where Kernel lives) cannot reference Agents.Abstractions. So the interface, context, and pipeline all live in Agents.Abstractions. DI-registered filters are still resolved from Kernel.Services.GetServices<IAgentInvocationFilter>().

    Registration paths:

    1. DI: services.AddSingleton<IAgentInvocationFilter, MyFilter>() → resolved from Kernel.Services
    2. Direct: agent.AgentInvocationFilters.Add(new MyFilter()) → NonNullCollection<IAgentInvocationFilter> on Agent
    3. Both are merged at invocation time (DI first, then direct).

    Streaming strategy

    • Non-streaming: next materializes all results into context.Result. Filter can inspect/modify after next.
    • Streaming: next assigns the deferred IAsyncEnumerable to context.StreamingResult without enumerating. Filter can wrap/replace it. No mandatory buffering — tokens flow through as they arrive.

    Breaking change (abstract rename)

    The current public abstract InvokeAsync(ICollection<ChatMessageContent>, ...) becomes protected abstract InvokeCoreAsync(...), and the new public InvokeAsync(...) runs the filter pipeline before delegating to InvokeCoreAsync. This is a breaking change for anyone subclassing Agent, but acceptable under SKEXP0110 (experimental).

    Use cases addressed

    All three use cases from the issue:

    1. ✅ Check agent instructions comply with policies → inspect context.Instructions before next
    2. ✅ Check input safety → inspect/modify context.Messages before next
    3. ✅ Check output compliance → inspect/modify context.Result or wrap context.StreamingResult after next

    Known limitations

    • Legacy AgentChat/AgentChannel path: The ChatHistoryAgent.InvokeAsync(ChatHistory, ...) method used by ChatHistoryChannel is not wrapped by this filter. Could be a follow-up.
    • Per-token streaming interception: Filters can wrap the IAsyncEnumerable but don't get called per-token without custom wrapper logic. By design to avoid mandatory buffering.

    Happy to open a PR with the full implementation once we have alignment on this design. 🙏

  6. AUTHENSOR commented on Feb 13, 2026

    @AUTHENSOR

    This would be a valuable addition. The existing Function Invocation Filters cover individual function calls, but agent invocations involve a higher-level decision loop (which tools to call, in what order, with what arguments) that needs its own interception point.

    One pattern that works well for agent invocation filtering is policy-based action gating. Before each tool call in the agent loop executes, a policy engine evaluates the request against a set of allow/deny/require-approval rules. The rules match on action type (file_read, shell_exec, http_request) and argument patterns (regex on file paths, URLs, command strings). This gives developers fine-grained control without modifying individual tool implementations.

    Key design considerations:

    • Tri-state decisions. Allow, deny, and require-approval covers most real-world needs. Auto-approve safe operations, hard-block dangerous ones, pause for human confirmation on everything else.
    • Fail closed. If the filter cannot evaluate a request (error, timeout, misconfiguration), the action should be denied. Fail-open in a security filter defeats the purpose.
    • Per-action audit logging. Every decision (including allows) should be recorded with the action, the matching rule, and a timestamp. This is critical for debugging and compliance.

    For reference, SafeClaw implements this pattern as a standalone policy engine for AI agents. It evaluates each action against YAML-defined policies with sub-millisecond overhead and logs decisions to a tamper-proof hash chain. The engine is framework-agnostic, so the same approach could be adapted for Semantic Kernel's filter pipeline.

    Having this at the agent invocation level (not just the function level) is the right call because it lets you enforce policies on the agent's overall behavior, not just individual function calls. For example, you might want a rule like "this agent can call at most 3 external APIs per turn" or "block any sequence where a file_read is followed by an http_request" (a common data exfiltration pattern).

  7. eavanvalkenburg commented on Feb 16, 2026

    @eavanvalkenburg
    MemberAuthor

    I'm closing this, because this is what AgentMiddleware does for you, see the docs here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    .NETIssue or Pull requests regarding .NET codepythonPull requests for the Python Semantic Kernel

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions