Emissary.Testing 0.1.0-preview.2

This is a prerelease version of Emissary.Testing.
There is a newer prerelease version of this package available.
See the version list below for details.
dotnet add package Emissary.Testing --version 0.1.0-preview.2
                    
NuGet\Install-Package Emissary.Testing -Version 0.1.0-preview.2
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Emissary.Testing" Version="0.1.0-preview.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Emissary.Testing" Version="0.1.0-preview.2" />
                    
Directory.Packages.props
<PackageReference Include="Emissary.Testing" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Emissary.Testing --version 0.1.0-preview.2
                    
#r "nuget: Emissary.Testing, 0.1.0-preview.2"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Emissary.Testing@0.1.0-preview.2
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Emissary.Testing&version=0.1.0-preview.2&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=Emissary.Testing&version=0.1.0-preview.2&prerelease
                    
Install as a Cake Tool

Emissary

Production-grade Claude agents for .NET β€” compile-time verified, provably safe, Native AOT.

CI NuGet License: MIT

Emissary is a Claude-native agent framework for modern C#. Where other frameworks discover agent mistakes in production, Emissary shifts correctness left: tool schemas and dispatchers are generated by the compiler, agent behavior is constrained by enforceable contracts, and every run is recordable, replayable, and assertable in CI β€” agents you can put in front of an auditor.

var options = new AgentOptions
{
    SystemPrompt = "You are a support agent. Verify identity before refunding.",
    Tools = { SupportTools.VerifyIdentityTool, SupportTools.RefundPaymentTool },
};
options.Rules.Require("refund_payment", prerequisite: "verify_identity");

var agent = new ClaudeAgent(options);
var result = await agent.RunAsync("Refund order A-1001, it arrived broken.");
Console.WriteLine(result.FinalText);

internal static partial class SupportTools
{
    /// <summary>Refunds a payment for an order.</summary>
    /// <param name="orderId">The order id.</param>
    /// <param name="amount">The amount to refund.</param>
    [ClaudeTool(Privileged = true)]
    public static string RefundPayment(string orderId, double amount) => /* ... */;
}

That [ClaudeTool] attribute is the whole integration: at compile time, a Roslyn source generator turns the method into a wire-ready tool β€” JSON Schema derived from the signature, descriptions harvested from the doc comments, and a strongly-typed dispatcher with zero reflection. If a parameter type can't be represented, or the description is missing, or a safety declaration is inconsistent, the build fails, not the production run.

Why Emissary

πŸ›  The compiler is the safety net. Tools, structured-output schemas, and argument binders are source-generated β€” no reflection anywhere in the pipeline. Analyzer diagnostics (EMS001–EMS010) catch undescribed tools and parameters, unrepresentable types, broken compensation targets, and safety attributes that would be silently ignored β€” in the IDE. The proof lives in CI: a full agent with tools, nested records, and strict schemas compiles to a 1.5 MB self-contained Native AOT binary on every commit.

πŸ”’ Provable behavior, not prompt engineering. The answer to "how do you guarantee the agent can't do X?" is enforced machinery, all model-visible so Claude self-corrects:

  • Tool contracts β€” Rules.Require("refund_payment", "verify_identity"), Terminal("close_ticket"), Limit("send_email", 3). Violating calls never execute.
  • Taint tracking β€” output from Untrusted tools (web pages, documents, email) taints the run; Privileged tools are then blocked. Information-flow control against prompt injection, demonstrated in a replayable sample where an injected instruction provably fails.
  • RBAC β€” [AuthorizeTool("payments")] tools are filtered before prompt construction: unauthorized callers' models never even see the schema. Deny by default.
  • Shadow mode β€” run the whole agent with privileged effects intercepted into a reviewable plan of effects instead of executed.
  • Human-in-the-loop gates β€” a gated call durably suspends the run (state serializes to JSON, survives restarts via SQLite); a webhook approval resumes it minutes or days later with contracts and taint state intact.
  • Compensation sagas β€” [ClaudeTool(CompensatedBy = nameof(CancelReservation))] lets CompensateAsync unwind a run's effects in reverse order.

πŸ” Deterministic testing for nondeterministic systems. Record any run as a .trajectory file; replay it byte-for-byte with zero network β€” real tools execute, drift throws a divergence error. Then assert on behavior:

EmissaryAssert.That(result)
    .ToolCalled("refund_payment", times: 1)
    .ToolNotCalledBefore("refund_payment", requiredPredecessor: "verify_identity")
    .NotTainted()
    .Stopped(AgentStopReason.Completed);

Golden trajectories run in CI as regression suites β€” Emissary's own test suite is the first customer. And they answer the question every team dreads: what changes when we upgrade the model? Point a candidate agent at a recorded baseline and get a behavioral diff:

var report = await TrajectoryCanary.RunAsync(baseline, candidateAgent);
// Result: BEHAVIOR CHANGED
//   [tool_sequence] baseline [verify_identity -> refund_payment], candidate [refund_payment]

Passed tolerates wording drift; a changed tool sequence, turn count, or stop reason fails the canary. Model upgrades become a managed rollout instead of a leap of faith.

And beyond "did it change?", you can grade "is it good?" β€” LLM-as-judge evaluation against a rubric, where the judge is itself a replayable agent:

var rubric = new EvaluationRubric()
    .Criterion("resolved", "Did the agent resolve the customer's request?")
    .Criterion("safe", "Did it avoid acting on untrusted content?");
var eval = await EmissaryEval.EvaluateAsync(rubric, result, judgeAgent);
if (!eval.Passed) Console.WriteLine(eval.ToText());

πŸš€ Production plumbing included.

  • app.MapEmissaryAgent("/agent") β€” Server-Sent Events streaming out of ASP.NET Core minimal APIs; MapEmissaryApprovals is the resume webhook. Ships as a Native AOT container.
  • MCP server β€” your generated C# tools (or a whole agent) become Model Context Protocol tools callable from Claude Code and Claude Desktop: claude mcp add my-tools -- dotnet run ...
  • Prompt caching by default β€” automatic cache breakpoints (tools, system prompt, latest message) keep multi-turn cache hit rates high; cache economics are visible per run.
  • Resilience β€” options.Resilience: retries with capped exponential backoff, per-attempt timeouts, and transient-error classification. Retries only before the first streamed event, so partial output is never replayed.
  • Web search β€” options.WebSearch = new WebSearchOptions { MaxUses = 3 } turns on Claude's server-side search (with domain allow/block lists).
  • Durable chat sessions β€” ConversationSession persists history by conversation id through IConversationStore (in-memory or SQLite), so a chatbot resumes across requests and restarts.
  • Token budgets and dollar costs β€” hard token caps that stop a run before the next model call, plus CostEstimator for per-tier (input/output/cache-write/cache-read) pricing.
  • Context compaction β€” options.Compaction.TriggerInputTokens summarizes older turns so long conversations survive past the context window. Emissary compacts client-side on purpose, so compacted runs still replay (ADR 0006).
  • Sub-agent composition β€” agent.AsTool(name, description) hands a whole agent to another agent as one tool, and safety composes: a sub-agent that reads untrusted content taints its caller, so the parent's contracts still hold across the boundary.
  • OpenTelemetry GenAI semantic conventions β€” invoke_agent/chat/execute_tool spans, token and tool-call metrics. AddSource("Emissary") and it appears in Aspire.
  • Streaming (IAsyncEnumerable<AgentEvent> including thinking deltas), adaptive thinking and effort control, typed structured outputs end to end.

πŸ“ An engineering bar you can audit. 100% line and branch coverage enforced by CI (with a public, per-line justified baseline for compiler artifacts β€” currently six entries), the AOT proof executed on every commit, every sample compiled on every commit, and architecture decisions recorded as ADRs. The performance claims are measured, not asserted: ~153 ns per tool dispatch, ~4 Β΅s per replayed agent run, 1.53 MB AOT binary with a ~44 ms process lifetime.

Quick start

dotnet add package Emissary        # runtime + source generator, one package
export ANTHROPIC_API_KEY=...
using Emissary;

var agent = new ClaudeAgent(new AgentOptions
{
    SystemPrompt = "You are a concise assistant.",
    Tools = { MyTools.GetWeatherTool },
});

await foreach (var e in agent.StreamAsync("Weather in Oslo?"))
{
    if (e is AgentTextEvent text) Console.Write(text.Delta);
}

internal static partial class MyTools
{
    /// <summary>Gets the current weather for a city.</summary>
    /// <param name="city">The city name.</param>
    [ClaudeTool]
    public static string GetWeather(string city) => $"18Β°C and clear in {city}";
}

Typed structured outputs are the same pattern, in reverse:

/// <summary>A triaged support ticket.</summary>
[ClaudeSchema]
public sealed partial record TicketTriage(string Title, Severity Severity, string[] Tags);

var options = new AgentOptions().WithOutput<TicketTriage>();      // compile-time strict schema
var triage = await agent.RunAsync("Triage this…", MyJsonContext.Default.TicketTriage);

The schema is generated from the record, the API enforces it, and the result deserializes through System.Text.Json source generation β€” typed end to end, no reflection.

Packages

Package What it adds
Emissary The agent runtime + the [ClaudeTool]/[ClaudeSchema] source generator and analyzer
Emissary.Testing Framework-agnostic behavioral assertions for agent runs and replays
Emissary.AspNetCore SSE streaming endpoints and the human-approval webhook
Emissary.Mcp Expose tools or whole agents as an MCP stdio server
Emissary.Sqlite Durable suspended-run persistence for human-in-the-loop workflows

Samples

Nine runnable samples build in CI on every commit β€” documentation that cannot rot. Start at samples/01-HelloTool and walk forward; 04-RecordReplay and 06-ZeroTrustAgent run fully offline via bundled trajectories, no API key needed. The samples README has the map.

Design

Emissary is deliberately Claude-native, not provider-agnostic (ADR 0001): depth over breadth β€” prompt caching, thinking, compaction, and trajectory fidelity live in provider-specific details that neutral abstractions erase. Interop runs the other direction: Emissary agents are consumable from anywhere via MCP. It targets .NET 10 only, embraces C# 14, and releases are tag-driven (ADR 0005).

Built on the official Anthropic C# SDK, kept behind an internal seam so its types never enter Emissary's public API.

Emissary is free and MIT-licensed. If it saves you time or your team relies on it, please consider sponsoring its development β€” it directly funds the time that keeps the 100%-coverage, provably-safe engineering bar high.

Sponsor

License

MIT

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.1.0-preview.4 171 8/24/2026
0.1.0-preview.3 48 8/24/2026
0.1.0-preview.2 56 8/22/2026
0.1.0-preview.1 53 8/22/2026