Skip to content

The Honest Boundary

Affiant’s whole premise is that every AI-proposed write is sworn: a durable Affidavit with field-level provenance, held on the Docket until a human approves it. That promise only holds for writes Affiant can actually see. This page states the boundary of what Affiant sees precisely, explains why the boundary is where it is, and tells you what to do about the part it can’t reach.

Affiant intercepts locally-invoked tool calls — function calls that execute as code running inside your own process, addressed and invoked by whichever backend object your host application constructed: a Semantic Kernel Kernel, a Microsoft Agent Framework AIAgent, or a Microsoft.Extensions.AI ChatOptions/IChatClient pairing. See Interception Backends for how the three attach; this page’s prose mostly speaks in Semantic Kernel’s vocabulary (Kernel, KernelFunction) since it’s the framework’s original and still-reference backend, but the boundary described here is identical on all three — each backend’s own hosted-tool audit enforces it structurally at wire-up.

Concretely, that means:

  • Any [KernelFunction]-decorated method in a plugin registered on your Kernel (or, on the other two backends, any method reflected into an AffiantToolCatalog), whether marked with [AffiantWriteTool] or left as a plain read tool.
  • Locally-invoked MCP tools — tools from an MCP (Model Context Protocol) server that you’ve imported into your Kernel as plugins, the standard Semantic Kernel pattern for consuming MCP tools. Once imported, an MCP-backed tool is an ordinary KernelFunction as far as the Kernel is concerned. Nothing in Affiant’s interception layer needs to know, or care, that a function’s implementation happens to proxy to an MCP server running on your machine or your network — only that your process’s Kernel is the one invoking it. That symmetry is why locally-invoked MCP tool writes get sworn Affidavits for free, with no MCP-specific code anywhere in the framework. The same holds for a locally-invoked MCP tool on the other two backends.

Affiant cannot see hosted / server-side tool execution — tool calls that a model provider’s own runtime executes remotely, without ever handing control back to your process to make the call.

This covers hosted MCP tools (an MCP server the provider runs and invokes on your behalf, as distinct from one you run and invoke yourself), code interpreter, web search, and other provider-hosted toolboxes exposed by a given model API. If a write happens inside one of those, Affiant never sees the function name, the arguments, or the result — the call and its execution live entirely inside infrastructure Affiant’s host process never touches.

A concrete example: suppose your agent has access to a provider-hosted code-interpreter tool that can, in principle, call out to an API and persist a result somewhere. If a user’s request causes the model to route a mutation through that hosted tool rather than through one of your own [KernelFunction] plugins, nothing about that mutation ever reaches your Kernel — no IFunctionInvocationFilter fires, no Affidavit is produced, no Evidence Card is shown to a reviewer. The write either already happened, or it didn’t; Affiant has no visibility into which, and no opportunity to gate it either way.

Why this is architecturally true, not a missing feature

Section titled “Why this is architecturally true, not a missing feature”

Affiant’s interception seam is Semantic Kernel’s function-invocation filter pipeline: IFunctionInvocationFilter (wraps every function invocation) and IAutoFunctionInvocationFilter (wraps each function invoked by SK’s automatic tool-calling loop). Both filter types attach to your Kernel instance and fire only when that Kernel is the thing invoking the function — whether through SK’s normal auto-invocation loop or through the manual-invocation fallback path SK provides for connectors that don’t support the auto filter.

A hosted tool never enters that pipeline, because it was never invoked through your Kernel at all. The model provider’s server decided to call the tool, executed it in its own runtime, and returned only the final result to your process — often folded into the same response that would otherwise contain a function-call request. There is no hook in Semantic Kernel’s filter pipeline for a call that never passed through your Kernel, because the entire pipeline is defined as a set of filters on that object. Affiant cannot swear to a write it structurally never observes.

This is not particular to Semantic Kernel, either. Microsoft’s own successor framework, Microsoft Agent Framework (MAF), draws the identical line at the identical seam — its function-invocation middleware (FunctionInvocationContext) is documented as working only with tools “the client invokes locally,” while hosted tools are executed by the provider and carry a separate, server-side approval mechanism instead. Microsoft.Extensions.AI (M.E.AI) — the lower-level abstraction both SK and MAF sit on top of — draws it a third time at its own FunctionInvokingChatClient seam: only client-invoked AIFunctions pass through it, and its own hosted-tool markers (HostedWebSearchTool, HostedCodeInterpreterTool, HostedMcpServerTool, and similar) are provider-executed with no client-side invocation to wrap. See the FAQ for the sourcing on the MAF boundary, and Interception Backends for the M.E.AI one. The boundary is a property of how hosted-tool execution is architected industry-wide, not a gap specific to Affiant’s current adapter — all three of Affiant’s bridges enforce it the same way, refusing at wire-up time by default rather than staying silent about an uncovered hosted tool.

If a value must be sworn — if you need to know, and prove, exactly where a written field came from before it reaches your database — route that write through a locally-invoked tool. That’s the only path Affiant’s filters can see, and it’s the path Rule 3 (write tools never write directly; they produce a WriteProposal for review) and Rule 7 (every Affidavit field carries provenance, no exceptions) are built to govern.

Concretely, that leaves you with three honest options when a capability you’d otherwise want is only available as a hosted tool:

  1. Keep the hosted-tool path read-only. Let the provider’s hosted web search or code interpreter inform the model’s reasoning, but never let it be the thing that writes to your database. Route the actual mutation through a locally-invoked write tool that captures the fields with provenance, even if the values were suggested by something the hosted tool surfaced.
  2. Wrap the capability behind a local proxy. If you need code-interpreter-like execution or a specific MCP server’s tools, run the equivalent locally and expose it to your Kernel as an ordinary plugin instead of consuming the provider’s hosted version of it. The moment it’s invoked by your Kernel, it’s back inside Affiant’s interception surface.
  3. If a hosted write path is unavoidable, treat it as explicitly unsworn. Don’t let a hosted-tool write reach the same downstream systems as reviewed writes without a separate, clearly-labeled reviewed path — a hosted write and a sworn write should never look the same to whoever reads the audit trail afterward.

The point of naming this boundary plainly is that a framework which claims coverage it doesn’t have is worse than no framework at all — an Evidence Card that looks complete but was silently bypassed by a hosted tool is exactly the kind of hollow assurance Affiant exists to prevent.