Skip to main content

Semantic Kernel

Mekik.SemanticKernel integrates Semantic Kernel through the seam everything in SK funnels through — an IFunctionInvocationFilter. One registration covers direct kernel.InvokeAsync, auto function calling, ChatCompletionAgent and the other agent types, and planners. You don't convert plugins or hand the agent a different tool list.

kernel.UseMekik​

using Mekik.SemanticKernel;

.Node("agent", async (State state, IContext ctx) =>
{
using var _ = kernel.UseMekik(ctx, new()
{
["get_order"] = new ToolPolicy(), // shown
["refund_payment"] = new ToolPolicy { Approve = new ApproveSpec() }, // ask the human first
["internal_lookup"] = new ToolPolicy { Show = false }, // runs, not shown
["charge"] = new ToolPolicy { Redact = ["cardNumber"] }, // shown, masked
});

var settings = new PromptExecutionSettings { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() };
var reply = await kernel.InvokePromptAsync(state.Get<string>("input"), new KernelArguments(settings));
return Update.Of("reply", reply.ToString());
})

Why a filter, and what it covers​

An IFunctionInvocationFilter sits in front of every function invocation the kernel makes — direct calls, the auto function-calling loop, agents, planners. Registering one covers Semantic Kernel and anything built on top of it with a single line. That's the reason this integration is a filter and not a wrapper: SK has one choke point, so you use it.

The scope matters — dispose it​

UseMekik returns an IDisposable scope:

using var _ = kernel.UseMekik(ctx, policies);

Disposing removes the filter. This is important because a Kernel is usually long-lived while ctx belongs to a single graph run. Leaving the filter registered would leak a stale ctx into the next turn — the using scope ties the filter's lifetime to exactly the run it belongs to.

Policy lookup​

Policies are matched by plugin-qualified name (Plugin.Function) first, then the bare function name — specific where two plugins share a name, terse everywhere else:

new()
{
["Billing.Charge"] = new ToolPolicy { Approve = new ApproveSpec() }, // this plugin's Charge
["Charge"] = new ToolPolicy { Show = false }, // any other Charge
}

ToolPolicy and ApproveSpec are the same types Mekik.Agents uses, so you learn one policy shape for both .NET integrations.

Decline is a short-circuit​

When a human declines an approve function, the filter short-circuits: next is never called, so the function doesn't run, and the kernel receives DenyMessage as the result so the model can respond rather than see an exception. Same behaviour as the other integrations, expressed the SK way.

Notes​

  • Redact masks only what is surfaced; the function still receives real values.
  • Journaled results must survive a serializer round-trip, like any ilmek step.
  • Since KernelFunction derives from AIFunction, Mekik.Agents can also wrap SK functions directly — but wrapping changes the type, so the kernel's plugin collection would no longer accept them. Prefer this filter for Semantic Kernel.
  • Client tools work with SK too: ClientToolFunctions.Wrap(ctx) returns plain AIFunctions, and KernelFunctionFactory.CreateFromMethod / kernel.Plugins.AddFromFunctions can host them alongside your plugins — or a node can call one directly with Shuttle.CallClientToolAsync outside the kernel entirely. The filter does not need to see them: the wrapper already emits its own tool_call trace and journals through the interrupt machinery.

Where to go next​