Emissary.AspNetCore
0.1.0-preview.1
See the version list below for details.
dotnet add package Emissary.AspNetCore --version 0.1.0-preview.1
NuGet\Install-Package Emissary.AspNetCore -Version 0.1.0-preview.1
<PackageReference Include="Emissary.AspNetCore" Version="0.1.0-preview.1" />
<PackageVersion Include="Emissary.AspNetCore" Version="0.1.0-preview.1" />
<PackageReference Include="Emissary.AspNetCore" />
paket add Emissary.AspNetCore --version 0.1.0-preview.1
#r "nuget: Emissary.AspNetCore, 0.1.0-preview.1"
#:package Emissary.AspNetCore@0.1.0-preview.1
#addin nuget:?package=Emissary.AspNetCore&version=0.1.0-preview.1&prerelease
#tool nuget:?package=Emissary.AspNetCore&version=0.1.0-preview.1&prerelease
Emissary
Production-grade Claude agents for .NET — compile-time verified, provably safe, Native AOT.
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–EMS009) catch undescribed tools, unrepresentable types, and broken contracts 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
Untrustedtools (web pages, documents, email) taints the run;Privilegedtools 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))]letsCompensateAsyncunwind 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.
🚀 Production plumbing included.
app.MapEmissaryAgent("/agent")— Server-Sent Events streaming out of ASP.NET Core minimal APIs;MapEmissaryApprovalsis 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.
- Token budgets — hard caps that stop a run before the next model call.
- OpenTelemetry GenAI semantic conventions —
invoke_agent/chat/execute_toolspans, 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.
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 { OutputSchemaJson = TicketTriage.JsonSchema };
var triage = result.FinalAs(MyJsonContext.Default.TicketTriage); // guaranteed to conform
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.
License
| Product | Versions 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. |
-
net10.0
- Emissary (>= 0.1.0-preview.1)
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 | 59 | 8/24/2026 |
| 0.1.0-preview.3 | 63 | 8/24/2026 |
| 0.1.0-preview.2 | 71 | 8/22/2026 |
| 0.1.0-preview.1 | 63 | 8/22/2026 |