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.
Picking one
Section titled “Picking one”| 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.
How each backend attaches
Section titled “How each backend attaches”| 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.
The hosted-tool boundary, on all three
Section titled “The hosted-tool boundary, on all three”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:
// MAFbuilder.Services.AddAffiantAgentFramework(options =>{ options.AcknowledgeUncoveredTools = ["code_interpreter"];});
// M.E.AIbuilder.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.
Where this fits
Section titled “Where this fits”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.