Installation
Affiant ships as ten NuGet packages, all versioned together, targeting net10.0. You
install only the ones your project needs — the dependency graph is a strict DAG rooted at
Affiant.Abstractions, so referencing a higher package pulls in everything beneath it
automatically.
Prerequisites
Section titled “Prerequisites”- The .NET 10 SDK. All ten packages target
net10.0. - For a host application, one of Semantic Kernel, Microsoft Agent Framework, or
Microsoft.Extensions.AI. Affiant’s interception seam is its own: the
IToolInvocationFilterchain — the interface itself lives inAffiant.Abstractions— thatAffiant.Core‘sToolInvocationPipelineruns. Each adapter package bridges one of these three orchestrators’ tool-calling pipeline into it, so your project needs one of them already wired up (or one you’re about to wire up alongside Affiant — see Quickstart, which walks the Semantic Kernel path, and Interception Backends for how the other two differ). A contract-only project that implementsAffiant.Abstractionsinterfaces and nothing else needs no orchestrator at all — see the scenarios below. - If you plan to use
Affiant.EntityFrameworkfor durable storage, a database: SQLite for local development, PostgreSQL for production. Those are its two SQL backing stores; a third selection,ef.UseInMemory(), keeps chat sessions in memory and registers noIDocketStore.Affiant.Docketitself needs no database — it ships an in-memory store and the backend-neutral expiry sweep, and it is what you install for that sweep whichever persistence backend you choose; a host that schedules the sweep itself may omit it (see below).
Every install command below uses --prerelease. Affiant has not reached a stable 1.0.0
release yet — read the version note at the bottom of this page before you pin a version in a
production project.
The ten packages
Section titled “The ten packages”| Package | What it’s for | Depends on |
|---|---|---|
Affiant.Abstractions |
Every primitive type (Affidavit, ProvenanceTag, ToolEnvelope, DocketEntry, …) and every backend-neutral contract a host implements (IWriteExecutor, IFieldMapper<T>, IDocketStore, IApprovalPolicy, …). Three packages carry an adapter-local interface of their own besides: Affiant.SemanticKernel (IManualToolInvoker), Affiant.Extensions.AI (IAffiantWrappedFunction) and Affiant.Transport.SignalR (IAffiantHubClient). The abstract classes a host derives from live outside it too — StandingOrderBase, ReferralRuleBase and RiskScoreCalculatorBase in Affiant.Policies, ContextExtractor in Affiant.Core, and AffiantHub in Affiant.Transport.SignalR (what AddAffiantSignalR<THub> constrains THub to). Zero Affiant dependencies. |
(none) |
Affiant.Core |
Concrete services: ContextFabric, ReviewGate, task-inference merge, the deterministic short-circuit, DI wiring (AddAffiantCore, AddAffiantTool<T>). |
Abstractions |
Affiant.SemanticKernel |
The Semantic Kernel (SK) interception bridge — wires Affiant’s neutral pipeline into SK’s IFunctionInvocationFilter and IAutoFunctionInvocationFilter positions, per-provider connector capabilities, structured-output inference (AddAffiantSemanticKernel, AddAffiantInferenceOrchestration). |
Core |
Affiant.AgentFramework |
The Microsoft Agent Framework (MAF) interception bridge — reflects a tool type into an AffiantToolCatalog, attaches via AIAgent.WithAffiant(...), and runs the hosted-tool coverage audit (AddAffiantAgentFramework). |
Core |
Affiant.Extensions.AI |
The Microsoft.Extensions.AI (M.E.AI) interception bridge — wraps each AIFunction and attaches via ChatOptions.WithAffiant(...), for hosts that talk to IChatClient directly with no agent framework (AddAffiantExtensionsAI). |
Core |
Affiant.EntityFramework |
EF Core persistence for chat sessions and dockets — row-per-message schema, and the SQLite/PostgreSQL IChatSessionStore and IDocketStore implementations (AddAffiantEntityFramework). |
Abstractions, Core |
Affiant.Docket |
The review queue’s backend-neutral half — the in-memory IDocketStore plus the expiry sweep (AddAffiantDocket). Needed for that sweep even when the actual store comes from Affiant.EntityFramework; a host that runs the sweep on its own schedule may omit it. |
Abstractions, Core |
Affiant.Policies |
The fluent approval policy graph — Standing Orders (pre-authorized auto-approval), Referrals (escalation), and the risk-threshold hook (AddAffiantPolicies). Ships no scoring formula of its own: a Standing Order that declares a RiskThreshold needs a host RiskScoreCalculatorBase. |
Abstractions, Core |
Affiant.Transport.SignalR |
SignalR streaming transport and the Evidence Card round-trip hub (AddAffiantSignalR<THub>). |
Abstractions, Core |
Affiant.Testing.ComplianceHarness |
Test helper that proves every write strategy has a paired fixture asserting substantive provenance, not just shape, and — since 1.0.0-beta.3 — runs the protocol’s own conformance suite against the shipped packages, from a rulebook root you vendor and pass in (ConformanceSuite.Run(protocolRoot)); the fixtures and schemas are not inside the package. For your CI, not just ours. |
Core, Docket, Policies |
Pick one of Affiant.SemanticKernel, Affiant.AgentFramework, or Affiant.Extensions.AI —
never wire more than one over the same tool catalog. See
Interception Backends for how to choose.
Affiant.Docket and Affiant.EntityFramework are peers, not a chain: Affiant.Docket
supplies the in-memory store and the expiry sweep — the hosted DocketExpiryService, whose
tick runs three bounded phases: it commits the lapsed entries and, for a host that has
registered an IStreamingTransport, announces each entry it just expired (DocketExpired),
then warns about the entries approaching their deadline (DocketExpiring), then re-broadcasts
the still-pending entries’ Evidence Cards — and Affiant.EntityFramework goes in beside it,
not instead of it, whenever you want a durable, SQL-backed Docket. All three sends are guarded
on that transport: with none registered, a tick commits the lapsed entries and sends nothing.
That service owns a schedule, not the expiry rule: an entry past its ExpiresAt reads expired
whether or not the sweep has reached it, so a host with no long-lived process to run it — a
serverless deployment, a cron entry, a queue worker — can leave Affiant.Docket out and call
IDocketStore.ExpireDueAsync on its own cadence, as long as its IDocketStore comes from
somewhere. Installing Affiant.Docket alone does not pull Affiant.EntityFramework, EF Core,
SQLite, or Npgsql along with it: at every published 1.0.0 version Affiant.Docket depends on
Affiant.Abstractions and Affiant.Core and nothing else of Affiant’s, so a fully in-memory
host doesn’t carry a database driver it never uses.
What to install, by scenario
Section titled “What to install, by scenario”A host application (the common case)
Section titled “A host application (the common case)”An agent host that writes to a real database typically wants the full review path: context
extraction and the review gate, one interception bridge, a place to persist chat sessions and
docket entries, and a transport for streaming Evidence Cards to a reviewer. This example uses
the Semantic Kernel bridge; swap Affiant.SemanticKernel for Affiant.AgentFramework or
Affiant.Extensions.AI if your host runs on one of those instead.
dotnet add package Affiant.Core --prereleasedotnet add package Affiant.SemanticKernel --prereleasedotnet add package Affiant.EntityFramework --prereleasedotnet add package Affiant.Docket --prereleasedotnet add package Affiant.Transport.SignalR --prereleaseAdd Affiant.Policies if you want Standing Orders (auto-approval for low-risk writes) or
Referrals (escalation to a different reviewer), rather than routing every Affidavit to a reviewer
unconditionally:
dotnet add package Affiant.Policies --prereleaseThe package brings the policy graph, not a risk formula. AddAffiantPolicies registers a
placeholder RiskScoreCalculatorBase whose ComputeAsync throws an InvalidOperationException
naming the registration that is missing, so a Standing Order that declares a RiskThreshold
needs a scorer of your own, registered with SetRiskScoreCalculator<T>() inside
AddAffiantPolicies(...). Wire one and the order can auto-approve; leave it out and the startup
validator refuses the host, rather than letting an order that can never fire look wired.
A library or contract-only project
Section titled “A library or contract-only project”If you’re implementing a framework interface — a custom IDocketStore, an IFieldMapper<T>
for your domain model, an IWriteExecutor — without needing any of the concrete services,
Affiant.Abstractions alone is enough. It has zero Affiant dependencies.
dotnet add package Affiant.Abstractions --prereleaseA test project
Section titled “A test project”To verify that your write strategies produce substantive provenance — not just structurally-shaped Affidavits — add the compliance harness to your test project:
dotnet add package Affiant.Testing.ComplianceHarness --prereleaseVerifying the install
Section titled “Verifying the install”After adding packages, a restore should pull in your chosen interception bridge’s own
dependencies and, for Affiant.EntityFramework, the EF Core provider packages (SQLite and
PostgreSQL) transitively — you don’t need to add those yourself. At 1.0.0-beta.3 that SQLite
provider brings a transitive SQLitePCLRaw.lib.e_sqlite3 with a published high-severity
advisory, so the restore also emits NU1903; a build that treats NuGet audit warnings as errors
will stop there until you apply your usual audit policy to it.
Pin every Affiant package in the project to the same version, and check that pin whenever you
bump one. The packages declare their Affiant dependencies as minimum-version floors
(version="1.0.0-beta.3", not an exact [1.0.0-beta.3] range), so the two halves of a partial
upgrade fail in opposite ways. Bump an adapter and leave Affiant.Core behind and the restore
fails outright — error NU1605: Warning As Error: Detected package downgrade: Affiant.Core from 1.0.0-beta.3 to 1.0.0-beta.1, naming both packages and both versions, before anything compiles.
Bump Affiant.Core and leave the adapter behind and the restore and the build both succeed with
no diagnostic at all: that skew is silent, which is why the pin is worth keeping deliberate.
If your host boots without registering an IDocketStore or IStreamingTransport at all —
neither Affiant.EntityFramework nor Affiant.Docket’s in-memory store, or no transport
package — AddAffiantCore()’s startup validator throws an AffiantStartupException at boot,
naming the missing registration and which package supplies it, rather than failing silently at
the first write. A third contract joins that same list conditionally: register a tool whose
operation is update-shaped and leave IPreviousValueSource unregistered, and the exception names
it too — an update-shaped Affidavit swears to the value each field replaces, and only your own
system of record knows it — with AddPreviousValueSource<TSource>() named as the fix. Two things
change that. Setting AffiantCoreOptions.AcknowledgeMissingReviewWiring = true downgrades it to
one startup warning per missing contract — but only for a host with no unresolved gate wiring of
its own; when a write-capable tool is registered and IReviewContextProvider, ReviewGate or
IDecisionAuthorizationPolicy is missing, or a registered approval policy reports a
configuration fault of its own (the Standing Order with a RiskThreshold and no scorer above),
the acknowledgment does not apply and the boot still fails. And a DI container that does not
provide IServiceProviderIsService skips all of that — the missing-contract checks and the
policy-configuration check alike — logging one debug line instead: the validator cannot enumerate
registered contracts there without resolving them. The review-deadline check runs first in either
case, so a DefaultDocketTtl under a millisecond — or one large enough to overflow the deadline
stamp — fails the boot even in a container that skips the rest.
A note on versioning
Section titled “A note on versioning”All ten packages release under one shared version number — if you install
Affiant.Core at a given version, install every other Affiant package you reference at that
same version. The framework has not reached a stable 1.0.0 yet, and the public API surface —
type shapes, DI extension signatures, package boundaries — may still change before it does.
Pin an exact version in any project you deploy, and re-test after every bump. Once you’ve
installed what you need, continue to Quickstart to wire up a first
write tool end to end.