Json.Ai.AgentMesh 0.3.0

dotnet add package Json.Ai.AgentMesh --version 0.3.0
                    
NuGet\Install-Package Json.Ai.AgentMesh -Version 0.3.0
                    
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="Json.Ai.AgentMesh" Version="0.3.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Json.Ai.AgentMesh" Version="0.3.0" />
                    
Directory.Packages.props
<PackageReference Include="Json.Ai.AgentMesh" />
                    
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 Json.Ai.AgentMesh --version 0.3.0
                    
#r "nuget: Json.Ai.AgentMesh, 0.3.0"
                    
#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 Json.Ai.AgentMesh@0.3.0
                    
#: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=Json.Ai.AgentMesh&version=0.3.0
                    
Install as a Cake Addin
#tool nuget:?package=Json.Ai.AgentMesh&version=0.3.0
                    
Install as a Cake Tool

AgentMesh

.NET library for orchestrating AI agents as a graph of nodes and edges, with typed state flowing between nodes and conditional edges deciding the path at runtime. Model access goes through OpenRouter.

Architecture decisions: docs/architecture.md

Installation

dotnet add package Json.Ai.AgentMesh

Single package — abstractions, execution, and the OpenRouter adapter ship together. Target: net10.0.

Configuration

Everything is registered from AddAgentMesh, at application bootstrap:

services.AddAgentMesh(agentMesh =>
{
    agentMesh.UseOpenRouter(options =>
    {
        options.ApiKey = Environment.GetEnvironmentVariable("OPENROUTER_API_KEY")!;
        options.DefaultModelId = "openai/gpt-5-mini";
        options.SiteTitle = "My App";
    });

    agentMesh.AddPipeline<MyState>("my-pipeline", graph => { /* ... */ });
});

OpenRouterOptions

Property Default What it's for
ApiKey — Required. Sent as Authorization: Bearer.
DefaultModelId openai/gpt-5-mini Model used by agents that don't call UseModel.
SiteUrl / SiteTitle null Sent in HTTP-Referer / X-Title; identify the application in OpenRouter's rankings.
Timeout 2 min Maximum time per call.
CatalogCacheDuration 1 h Cache duration for IModelCatalog.
BaseAddress https://openrouter.ai/api/v1/ API endpoint.

Usage

The full path is tool → agent → node → pipeline.

1. Tool

What the model can invoke. ExecuteAsync receives the raw arguments generated by the model — it's up to the tool to deserialize and validate them.

public interface IWebSearchTool : ITool;

public sealed class WebSearchTool : IWebSearchTool
{
    public string Name => "web_search";
    public string Description => "Searches the web for a search term.";

    public object ParametersSchema => new
    {
        type = "object",
        properties = new { query = new { type = "string", description = "Search term" } },
        required = new[] { "query" },
    };

    public async Task<ToolOutput> ExecuteAsync(string argumentsJson, CancellationToken ct = default)
    {
        var query = JsonDocument.Parse(argumentsJson).RootElement.GetProperty("query").GetString();
        return await SearchAsync(query, ct); // string implicitly converts to ToolOutput
    }
}

2. Agent

The LLM persona, configured through fluent calls in its own constructor. An agent knows nothing about state or graph — it only knows how to run an AgentTask.

public sealed class ResearcherAgent : AgentBase
{
    public ResearcherAgent(IModelAdapter model, IWebSearchTool webSearch) : base(model)
    {
        WithRole("Researcher");
        WithGoal("Find and summarize reliable information about any topic.");
        WithBackstory("You always verify sources before answering.");

        UseModel("anthropic/claude-sonnet-5");
        UseTool(webSearch);
        WithRouting(ProviderRouting.Create().PreferLowLatency().AllowFallbacks());
    }
}
Method Effect
WithRole role — who the agent is.
WithGoal goal — what it's after.
WithBackstory backstory — context and way of working.
UseTool Declares that the agent has the tool.
UseModel Pins the model; without it uses DefaultModelId.
WithRouting Provider preferences.
AddSubagent Exposes another agent as a tool, enabling delegation.

The first three make up the system prompt. The agent runs in a loop: calls the model, executes whatever tools it asks for, returns the results, and repeats until a final answer comes back.

3. Node

The graph's processing unit. The node is what knows the state: it builds the AgentTask from it and applies the result back.

public sealed class ResearchNode : INode<MyState>
{
    private readonly ResearcherAgent _agent;

    public ResearchNode(ResearcherAgent agent) => _agent = agent;

    public async Task<MyState> RunAsync(MyState state, CancellationToken ct = default)
    {
        var task = new AgentTask(
            description: $"Research: {state.Topic}",
            expectedOutput: "A structured summary, with the sources used.");

        var result = await _agent.RunAsync(task, ct);

        state.Research = result.Output;
        return state;
    }
}

The dependency is one-directional: a node may receive an agent; an agent never receives a node.

For the trivial case — text in, text out, with no rule of its own — there's AgentNode<TAgent>, which uses AgentContext as state and doesn't need to be written by hand.

4. Pipeline

The graph, declared at bootstrap. Nodes and edges, with GraphNode.Start and GraphNode.End as terminals.

agentMesh.AddPipeline<MyState>("research", graph =>
{
    graph.AddTool<IWebSearchTool, WebSearchTool>();
    graph.AddAgent<ResearcherAgent>();

    graph.AddNode<ResearchNode>();
    graph.AddNode<WriteNode>();
    graph.AddNode<ReviewNode>();

    graph.AddEdge(GraphNode.Start, GraphNode.Of<ResearchNode>());
    graph.AddEdge(GraphNode.Of<ResearchNode>(), GraphNode.Of<WriteNode>());

    // The path can be decided at runtime, based on the state.
    graph.AddConditionalEdge(
        GraphNode.Of<WriteNode>(),
        state => state.NeedsReview
            ? GraphNode.Of<ReviewNode>()
            : GraphNode.End);

    graph.AddEdge(GraphNode.Of<ReviewNode>(), GraphNode.End);
});

The graph is compiled at registration: an invalid topology — a node with no outgoing edge, an edge to an undeclared node, two initial nodes — fails at bootstrap, not in production.

5. Execution

The pipeline is resolved by name (keyed service). The state is an invocation argument, not a container service:

var pipeline = provider.GetRequiredKeyedService<IPipeline<MyState>>("research");

var result = await pipeline.RunAsync(new MyState { Topic = "hexagonal architecture" });

Or injected wherever it's used:

public sealed class ResearchEndpoint(
    [FromKeyedServices("research")] IPipeline<MyState> pipeline)
{
    public Task<MyState> HandleAsync(string topic, CancellationToken ct) =>
        pipeline.RunAsync(new MyState { Topic = topic }, ct);
}

Guardrails

Validate the output after the model has responded. They belong to the task, not the agent — the same agent can run tasks with different validations.

public sealed class NotEmptyGuardrail : IGuardrail
{
    public string Name => "not_empty";

    public GuardrailResult Verify(string output) =>
        string.IsNullOrWhiteSpace(output)
            ? GuardrailResult.Rejected("the response cannot be empty")
            : GuardrailResult.Ok();
}
var task = new AgentTask("Summarize the text.", "One paragraph.")
    .AddGuardrails(new NotEmptyGuardrail())
    .AddTools(extraTool)   // tools valid only for this execution
    .WithMaxRetries(2);    // retries if a guardrail rejects the output

If rejected, the agent tries again, reporting the reason to the model. Once retries are exhausted, it throws GuardrailViolationException.

Provider routing

ProviderRouting maps to OpenRouter's routing options:

WithRouting(ProviderRouting.Create()
    .PreferLowCost()                       // or PreferLowLatency() / PreferHighThroughput()
    .WithOrder("anthropic", "openai")      // try order
    .AllowFallbacks(false)                 // require the specified provider
    .Ignore("deepinfra")
    .DenyDataCollection()                  // avoid providers that train on the data
    .WithQuantizations("fp16", "bf16"));

For simple cases, the shortcuts on the model name itself work without ProviderRouting: "openai/gpt-5-mini:floor" (lowest price) and ":nitro" (highest throughput).

Cost and consumption

AgentResult.Usage accumulates tokens and cost across every model call of an execution — the turns of the tool-calling loop, guardrail retries, and the consumption of delegated subagents.

var result = await agent.RunAsync(task);

Console.WriteLine($"US$ {result.Usage.CostUsd:F4}");
Console.WriteLine($"{result.Usage.TotalTokens} tokens across {result.Usage.ModelCalls} calls");

Using AgentContext as state, the whole pipeline's accumulated cost comes out in result.TotalCostUsd.

Model catalog

IModelCatalog looks up the price, context, and capabilities of the available models — useful for picking a model dynamically:

public sealed class PickModelNode(IModelCatalog catalog) : INode<MyState>
{
    public async Task<MyState> RunAsync(MyState state, CancellationToken ct = default)
    {
        var models = await catalog.ListAsync(ct);

        state.Model = models
            .Where(m => m.SupportsTools && m.ContextLength >= 128_000)
            .OrderBy(m => m.Pricing.PromptPricePerToken)
            .First().Id;

        return state;
    }
}

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 (1)

Showing the top 1 NuGet packages that depend on Json.Ai.AgentMesh:

Package Downloads
Json.Ai.AgentMesh.OpenRouter

Adapter OpenRouter para o AgentMesh: chat completions, tool calling, roteamento de provedor e catálogo de modelos.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.3.0 126 8/24/2026
0.2.1 114 8/22/2026
0.2.0 104 8/22/2026
0.1.0 118 8/22/2026