You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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
publicclassAgentInvocationContext{publicAgentAgent{get;}publicKernelKernel{get;}publicstring?Instructions=>Agent.Instructions;publicICollection<ChatMessageContent>Messages{get;set;}// mutable for pre-invocation modificationpublicAgentThread?Thread{get;set;}publicAgentInvokeOptions?Options{get;set;}publicCancellationTokenCancellationToken{get;init;}publicboolIsStreaming{get;init;}// Non-streaming: populated after next(). Filter can inspect/modify.publicIList<AgentResponseItem<ChatMessageContent>>?Result{get;set;}// Streaming: deferred IAsyncEnumerable set after next(). Filter can wrap/replace. No buffering.publicIAsyncEnumerable<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:
DI: services.AddSingleton<IAgentInvocationFilter, MyFilter>() → resolved from Kernel.Services
Direct: agent.AgentInvocationFilters.Add(new MyFilter()) → NonNullCollection<IAgentInvocationFilter> on Agent
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 deferredIAsyncEnumerable 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:
✅ Check agent instructions comply with policies → inspect context.Instructions before next
✅ Check input safety → inspect/modify context.Messages before next
✅ 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. 🙏
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).
Activity
[-]New Feature: Agent Invocation Filter[/-][+]Python: New Feature: Agent Invocation Filter[/+][-]New Feature: Agent Invocation Filter[/-][+].Net: New Feature: Agent Invocation Filter[/+]unsafecode commentedon Mar 13, 2025
We have many asks about "agentic guardrails" to provide some business guidelines or "fencing" around agent execution:
instructionscomply with high-level policies, regardless of its current config[-].Net: New Feature: Agent Invocation Filter[/-][+]New Feature: Agent Invocation Filter[/+]stevenelliottjr commentedon Feb 8, 2026
Design Proposal:
IAgentInvocationFilterEduard 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
nextdelegate middleware:Context Class
Where filters live
SemanticKernel.Abstractions(whereKernellives) cannot referenceAgents.Abstractions. So the interface, context, and pipeline all live inAgents.Abstractions. DI-registered filters are still resolved fromKernel.Services.GetServices<IAgentInvocationFilter>().Registration paths:
services.AddSingleton<IAgentInvocationFilter, MyFilter>()→ resolved fromKernel.Servicesagent.AgentInvocationFilters.Add(new MyFilter())→NonNullCollection<IAgentInvocationFilter>onAgentStreaming strategy
nextmaterializes all results intocontext.Result. Filter can inspect/modify afternext.nextassigns the deferredIAsyncEnumerabletocontext.StreamingResultwithout 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>, ...)becomesprotected abstract InvokeCoreAsync(...), and the newpublic InvokeAsync(...)runs the filter pipeline before delegating toInvokeCoreAsync. This is a breaking change for anyone subclassingAgent, but acceptable under SKEXP0110 (experimental).Use cases addressed
All three use cases from the issue:
context.Instructionsbeforenextcontext.Messagesbeforenextcontext.Resultor wrapcontext.StreamingResultafternextKnown limitations
AgentChat/AgentChannelpath: TheChatHistoryAgent.InvokeAsync(ChatHistory, ...)method used byChatHistoryChannelis not wrapped by this filter. Could be a follow-up.IAsyncEnumerablebut 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. 🙏
AUTHENSOR commentedon Feb 13, 2026
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:
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).
eavanvalkenburg commentedon Feb 16, 2026
I'm closing this, because this is what AgentMiddleware does for you, see the docs here