Skip to content

Interception Backends

Affiant has three interception backends — Semantic Kernel (SK), Microsoft Agent Framework (MAF), and Microsoft.Extensions.AI (M.E.AI) — sitting on one shared, backend-neutral pipeline in Affiant.Core. The provenance tagging, task inference, and review-gating behavior is defined once and is identical on all three; each backend package (Affiant.SemanticKernel, Affiant.AgentFramework, Affiant.Extensions.AI) is a thin bridge over that backend’s own tool-invocation seam and contains no tagging, inference, or review-gate logic of its own.

If your host… Use
Already runs on Semantic Kernel Affiant.SemanticKernel — the original, reference backend; every worked example in Quickstart uses it.
Is starting fresh, or wants Microsoft’s current-generation agent SDK Affiant.AgentFramework — MAF reached 1.0 GA on 2026-04-03 and is where Microsoft’s new feature investment goes.
Talks to IChatClient directly and doesn’t want an agent-framework dependency at all Affiant.Extensions.AI — the lower-level abstraction both SK and MAF sit on top of.

All three are fully supported; none is deprecated or experimental. SK remains a safe, first-class choice — see the FAQ for Microsoft’s own SK support timeline. If you’re not sure, the deciding question is simply which orchestration layer, if any, your host already has: an existing Kernel, an existing AIAgent, or neither.

One Affiant bridge per tool catalog. Never wire more than one of the three over the same tool catalog or chat-client pipeline — the neutral pipeline is not idempotent, so double-wrapping double-tags provenance, fires task inference twice, and files the same write proposal on the Docket twice.

Concern Semantic Kernel Microsoft Agent Framework Microsoft.Extensions.AI
Registration AddAffiantCore() + AddAffiantSemanticKernel() + AddAffiantInferenceOrchestration() AddAffiantCore() + AddAffiantAgentFramework() (one call covers both of SK’s) AddAffiantCore() + AddAffiantExtensionsAI() (one call covers both of SK’s)
Tool discovery [KernelFunction]-decorated methods, registered as SK plugins AffiantToolCatalog.FromType<T>() reflects every public instance method on a tool type AffiantToolCatalog.FromType<T>(), same shape as MAF
Attach point DI-registered filters on the Kernel agent.WithAffiant(services, catalog) — decorates, returns a new AIAgent chatOptions.WithAffiant(services, catalog) — decorates, returns a new ChatOptions
Provider abstraction IChatCompletionService + connector capabilities Microsoft.Extensions.AI.IChatClient Microsoft.Extensions.AI.IChatClient directly — no agent framework in between
Session/turn identity SK ChatHistory MAF AgentSession ChatOptions.ConversationId — see below, this one is easy to skip by accident

WithAffiant(...) is the only supported way to attach Affiant on MAF or M.E.AI. Both wrapping calls produce a new instance rather than mutating the one you pass in — a pre-wrap AIAgent or ChatOptions local that anything in your codebase still calls bypasses Affiant entirely, with no error. Discard the unwrapped local, or shadow it, so only the wrapped instance is reachable after the wiring line.

Set ConversationId on Microsoft.Extensions.AI — or inference silently degrades

Section titled “Set ConversationId on Microsoft.Extensions.AI — or inference silently degrades”

This is specific to the M.E.AI bridge and worth calling out on its own, because the failure is silent. Affiant runs task inference once per (conversation, tool, turn). When ChatOptions.ConversationId is left null, there’s no conversation to key on, so the idempotency key falls back to the identity of the ambient IContextFabric — and at this seam, that object is process-global (FunctionInvokingChatClient hands Affiant the provider the ChatClientBuilder was built from, your application root, not a per-conversation scope). Every conversation collapses onto the same key, and the second and every later conversation skips write-tool inference entirely — no exception, no warning, just Affidavits built from raw tool arguments with nothing inferred.

var chatOptions = new ChatOptions { Tools = [.. catalog.Functions] }
.WithAffiant(serviceProvider, catalog);
// Required, not optional — see above.
chatOptions.ConversationId = conversationId;
var response = await client.GetResponseAsync(messages, chatOptions);

Setting ConversationId per conversation costs one line and restores correct behavior. SK and MAF source their ambient provider the same underlying way, so this limitation is shared across all three bridges in principle — the framework-level fix (a per-turn scope) is tracked separately — but it’s the M.E.AI quickstart above where it’s easiest to forget, since nothing else in that wiring path requires you to touch ConversationId at all.

Every backend draws the identical line: Affiant sees only locally-invoked tool calls. A tool executed by the model provider’s own runtime — hosted MCP, code interpreter, web search, and similar provider-executed tools — never enters any backend’s client-side invocation pipeline, so no backend’s bridge can tag, gate, or swear to a write that happens inside one. This is architecturally true, not a missing feature — see The Honest Boundary for the full shape of it and what to do about the gap.

Each bridge makes the boundary structural rather than a silent gap, auditing the tool set at wire-up time and refusing by default if it finds an uncovered hosted tool, naming every one:

// MAF
builder.Services.AddAffiantAgentFramework(options =>
{
options.AcknowledgeUncoveredTools = ["code_interpreter"];
});
// M.E.AI
builder.Services.AddAffiantExtensionsAI(options =>
{
options.AcknowledgeUncoveredTools = ["code_interpreter"];
});

Each acknowledgment emits a telemetry span and a logged warning at wrap time, so an accepted gap is auditable, never silent.

Using Affiant with Microsoft Agent Framework covers the MAF bridge in full, including migrating an existing SK host. Packages has the package-level dependency picture for all three bridges. The Honest Boundary covers the hosted-tool limit these three backends share. Quickstart is the end-to-end worked example, built against Semantic Kernel; the same Affidavit → Docket → Evidence Card → IWriteExecutor flow applies unchanged on MAF and M.E.AI, because all three terminate in the same backend-neutral ReviewGate.