![]() |
VOOZH | about |
dotnet add package AgentCircuits.Core --version 0.7.134
NuGet\Install-Package AgentCircuits.Core -Version 0.7.134
<PackageReference Include="AgentCircuits.Core" Version="0.7.134" />
<PackageVersion Include="AgentCircuits.Core" Version="0.7.134" />Directory.Packages.props
<PackageReference Include="AgentCircuits.Core" />Project file
paket add AgentCircuits.Core --version 0.7.134
#r "nuget: AgentCircuits.Core, 0.7.134"
#:package AgentCircuits.Core@0.7.134
#addin nuget:?package=AgentCircuits.Core&version=0.7.134Install as a Cake Addin
#tool nuget:?package=AgentCircuits.Core&version=0.7.134Install as a Cake Tool
Simple by Default. Powerful When Needed.
Build everything from one-shot AI queries to complex multi-agent orchestrationsโall in idiomatic C# with minimal overhead.
// Get started in one line
var result = await Agent.Query("Create a C# function that validates email addresses");
| Feature | AgentCircuits | Others |
|---|---|---|
| In-Process Performance | Run 100+ agents in a single process | Most require separate processes/containers |
| Progressive API | Start with 1 line, scale to advanced | Often all-or-nothing complexity |
| Extended Thinking | Native ThinkingConfig with streaming events and multi-turn continuity (Anthropic, OpenAI, Gemini, Ollama, Bedrock) |
Often no thinking/reasoning mode support |
| Image Generation | Native support for Gemini image generation with ImageEvent streaming |
Usually requires separate APIs |
| Background Tasks | TaskStop, TaskOutput, JSONL logs, progress metrics, TTL-based cleanup | Often no background task management |
| Turn-by-Turn Control | StepAsync() for approvals, debugging, multi-day workflows |
Black-box loops with no control between turns |
| Context Builders | Custom formats (XML, Compact) with 30-50% token savings | Fixed message format, no optimisation |
| Real-Time Streaming | Built-in streaming with 83% faster perceived latency | Often requires custom SSE handling |
| Multimodal Input | Native image support with all vision models | Often limited or provider-specific |
| Native Multi-Agent | Built-in sub-agents and orchestration | Usually requires custom coordination |
| Web Management Portal | Built-in dashboard, session viewer, playground UI | Usually requires custom development |
| Chat Interface | Modern SvelteKit UI with streaming, tool visualisation, themes | Usually requires custom development |
| A2A Protocol | Standard agent-to-agent communication (Python, Java, JS interop) | Often no cross-framework integration |
| C# Idiomatic | Feels like native .NET with attributes, async/await, records | Python-first with C# as afterthought |
| MCP Integration | First-class Model Context Protocol support | Limited or no MCP support |
| Agent Hooks | Cross-cutting concerns (auth, logging, cost tracking) | Requires custom middleware |
| Native PDF Support | Upload PDFs directly to Claude and Gemini models | Usually requires text extraction |
| Session Names | LLM-generated session summaries for easy identification | Manual naming or no naming |
| Task Management | Built-in task CRUD (TaskCreate/Update/List) with shared task lists across sessions | Requires custom implementation |
| Session Cancellation | Cancel running executions with execution tracking | Often no cancellation support |
| User Management | Built-in user entities, authentication, and access control | Requires custom implementation |
| SQL Storage | Production-ready PostgreSQL storage for sessions | Often requires custom implementation |
# Core framework (required)
dotnet add package AgentCircuits.Core
# LLM Providers (choose one or more)
dotnet add package AgentCircuits.Providers.Anthropic # For Claude models
dotnet add package AgentCircuits.Providers.OpenAI # For GPT models
dotnet add package AgentCircuits.Providers.Gemini # For Google Gemini models
dotnet add package AgentCircuits.Providers.Grok # For xAI Grok models
dotnet add package AgentCircuits.Providers.Ollama # For local models (Llama, Mistral, etc.)
dotnet add package AgentCircuits.Providers.Bedrock # For AWS Bedrock (Claude, Titan, etc.)
# Optional Features
dotnet add package AgentCircuits.A2A # A2A protocol support
dotnet add package AgentCircuits.Channels # Multi-channel routing (WhatsApp, Slack, Teams)
dotnet add package AgentCircuits.Portal # Web management portal
dotnet add package AgentCircuits.Server # Turnkey host (Portal + UI + hubs)
# Storage Providers (for production deployments)
dotnet add package AgentCircuits.Storage.Sql # PostgreSQL storage
# Chat UI (npm package, requires Portal backend)
# See: agentcircuits.ui/ for the SvelteKit chat interface
Core Package Features:
The simplest way to use AgentCircuitsโget an answer in one line:
using AgentCircuits;
using AgentCircuits.Providers.Anthropic;
var result = await Agent.Query(
"Analyze this code for bugs",
model: Anthropic.LanguageModel("claude-sonnet-4-5"),
tools: [BuiltInTools.Read, BuiltInTools.Grep]
);
Console.WriteLine(result);
For multi-turn interactions with context retention:
await using var agent = new Agent
{
SystemPrompt = "You are a senior software architect",
Tools = BuiltInTools.Safe, // Read, Write, Edit, Grep, etc.
Model = "claude-sonnet-4-5",
WorkingDirectory = "./my-project"
};
// First turn
await agent.SendAsync("Review the authentication module");
await foreach (var evt in agent.ReceiveAsync())
{
if (evt is TextEvent text)
Console.Write(text.Content);
if (evt is ToolUseEvent tool)
Console.WriteLine($"\n[Using: {tool.ToolName}]");
}
// Follow-up turn - agent remembers context
await agent.SendAsync("Now fix the security issues you found");
await foreach (var evt in agent.ReceiveAsync())
{
if (evt is TextEvent text)
Console.Write(text.Content);
}
Key Features:
For advanced scenarios requiring control between turnsโhuman approvals, multi-day workflows, or custom retry logicโuse StepAsync() for step-by-step execution:
Use ReceiveAsync() for:
Use StepAsync() for:
Stop before dangerous operations and require human confirmation:
using AgentCircuits.Events;
var agent = new Agent
{
SystemPrompt = "You are a deployment assistant",
Model = "claude-sonnet-4-5",
Tools = [BuiltInTools.Bash, deployProdTool]
};
await agent.SendAsync("Deploy version 2.1.0 to production");
while (true)
{
var turn = await agent.StepAsync();
// Check for dangerous operations BEFORE they execute
if (turn.ToolCalls.Any(t => t.ToolName == "deploy_prod"))
{
Console.Write("Approve deployment? (y/n): ");
if (Console.ReadLine() != "y")
{
await agent.SendAsync("Deployment denied by operator");
continue;
}
}
foreach (var evt in turn.Events)
if (evt is TextEvent text)
Console.WriteLine(text.Content);
if (turn.FinishReason == FinishReason.Stop || !turn.RequiresNextTurn)
break;
}
Exit and resume when waiting for external approvals:
// Tool returns operation ID immediately with Interrupt flag
[Tool("request_approval")]
public async Task<ToolResult> RequestApproval(string approver, string question)
{
var operationId = $"req_{Guid.NewGuid():N}";
// Store in database, send notification
await _db.SavePendingOperationAsync(operationId, approver, question);
await _notificationService.NotifyAsync(approver, question);
// Return with Interrupt flag - signals agent to exit
return new ToolResult
{
IsSuccess = true,
Interrupt = true, // Exit agent loop
Content = operationId,
Metadata = new() { ["operation_id"] = operationId, ["status"] = "pending" }
};
}
// Orchestration with StepAsync
await agent.SendAsync("I need database access");
while (true)
{
var turn = await agent.StepAsync();
// Check for operations that need external approval
var interruptedOps = turn.Events
.OfType<ToolResultEvent>()
.Where(e => e.Metadata?.ContainsKey("operation_id") == true);
if (interruptedOps.Any())
{
// Save session and exit - don't block for hours/days
await _sessionService.SaveAsync(agent.Session);
Console.WriteLine("Approval requested. Exiting...");
break;
}
if (turn.FinishReason == FinishReason.Stop || !turn.RequiresNextTurn)
break;
}
// Hours or days later: Resume when approval arrives
var session = await _sessionService.GetSessionAsync(sessionId);
var resumedAgent = new Agent { Session = session, Model = "claude-sonnet-4-5" };
await resumedAgent.SendAsync($"Approval granted for operation {operationId}");
await foreach (var evt in resumedAgent.ReceiveAsync())
{
// Agent continues from where it left off
}
Key Benefits:
Implementation Notes:
ReceiveAsync() internally calls StepAsync() in a loop (single execution path)Enable streaming for character-by-character responsesโ83% faster perceived latency (0.5s vs 3-5s to first content):
await using var agent = new Agent
{
Model = Anthropic.LanguageModel("claude-sonnet-4-5"),
UseStreaming = true, // Enable streaming
SystemPrompt = "You are a helpful coding assistant"
};
await agent.SendAsync("Explain async/await in C#");
await foreach (var evt in agent.ReceiveAsync())
{
switch (evt)
{
case TextEvent { Partial: true } text:
// Real-time partial content - display immediately
Console.Write(text.Content);
break;
case TextEvent { Partial: false }:
// Final complete text - skip (already displayed)
Console.WriteLine();
break;
case ResultEvent result:
Console.WriteLine($"\nCost: ${result.Usage?.TotalCostUsd:F4}");
break;
}
}
Streaming Benefits:
Enable models to "think out loud" before responding, improving reasoning quality for complex tasks:
using AgentCircuits;
using AgentCircuits.Events;
using AgentCircuits.Providers;
using AgentCircuits.Sessions;
var agent = new Agent(new InMemorySessionService())
{
Name = "thinking_agent",
SystemPrompt = "You are a helpful assistant. Think through problems carefully.",
Model = "claude-sonnet-4-5",
MaxTokens = 16000,
Thinking = new ThinkingConfig { Enabled = true, Effort = ThinkingEffort.High },
UseStreaming = true
};
await agent.SendAsync("What is the sum of the first 10 prime numbers?");
await foreach (var evt in agent.ReceiveAsync())
{
switch (evt)
{
case ThinkingEvent thinking when thinking.Partial:
// Stream thinking content in real-time
Console.ForegroundColor = ConsoleColor.DarkGray;
Console.Write(thinking.Thinking);
Console.ResetColor();
break;
case ThinkingEvent thinking when !thinking.Partial:
// Complete thinking block - signature preserved for multi-turn
Console.WriteLine("\n");
break;
case TextEvent text when text.Partial:
// Stream response content
Console.Write(text.Content);
break;
case ResultEvent result:
Console.WriteLine($"\n[{result.DurationMs}ms, {result.Usage?.TotalCostUsd:F4}]");
break;
}
}
var agent = new Agent
{
Thinking = new ThinkingConfig
{
Enabled = true,
Effort = ThinkingEffort.High // Low, Medium, High
}
};
Thinking signatures are automatically preserved across turns for continuity:
var agent = new Agent(new InMemorySessionService())
{
Model = "claude-sonnet-4-5",
MaxTokens = 8192,
Thinking = new ThinkingConfig { Enabled = true, Effort = ThinkingEffort.Medium },
UseStreaming = true
};
// First turn
await agent.SendAsync("What is a factorial?");
await foreach (var evt in agent.ReceiveAsync())
{
// ThinkingEvent emitted with signature
}
// Second turn - model has context from previous thinking
await agent.SendAsync("Calculate 5! step by step");
await foreach (var evt in agent.ReceiveAsync())
{
// Thinking continues with preserved context
}
Key Features:
TokenDistribution.ThinkingTokens tracks thinking token usageThinkingEvent.Partial for real-time thinking deltasEffort (Low/Medium/High)Provider Support:
See for complete examples.
Send images to vision-capable models (Claude, GPT-4o, Gemini) for analysis, description, or comparison:
using AgentCircuits.Providers;
// Load an image
var image = await ImageHelper.FromFileAsync("screenshot.png");
// Send with a prompt
await agent.SendAsync(Message.UserImage(
imageData: image.Data,
mediaType: image.MediaType,
caption: "What UI issues do you see in this screenshot?"
));
await foreach (var evt in agent.ReceiveAsync())
{
if (evt is TextEvent text)
Console.WriteLine(text.Content);
}
Multi-Image Messages:
// Compare before/after screenshots
var before = await ImageHelper.FromFileAsync("before.png");
var after = await ImageHelper.FromFileAsync("after.png");
await agent.SendAsync(Message.UserBlocks(
new TextContent { Text = "Compare these screenshots:" },
new ImageContent { Data = before.Data, MediaType = before.MediaType },
new TextContent { Text = "\nAfter:" },
new ImageContent { Data = after.Data, MediaType = after.MediaType }
));
Supported Formats:
Upload PDF documents directly to compatible models for analysis:
using AgentCircuits.Providers;
// Load a PDF document
var pdfContent = new DocumentContent
{
Data = Convert.ToBase64String(await File.ReadAllBytesAsync("report.pdf")),
MediaType = "application/pdf"
};
// Send to Claude or Gemini
await agent.SendAsync(Message.UserBlocks(
new TextContent { Text = "Summarise the key findings in this report:" },
pdfContent
));
await foreach (var evt in agent.ReceiveAsync())
{
if (evt is TextEvent text)
Console.WriteLine(text.Content);
}
Supported Providers:
See for complete examples including UI/UX analysis and visual diff tools.
Generate images using AI models that support image generation (e.g., Gemini 2.0 Flash with image generation):
using AgentCircuits;
using AgentCircuits.Events;
using AgentCircuits.Providers.Gemini;
var agent = new Agent
{
Model = Gemini.LanguageModel("gemini-2.0-flash-exp-image-generation"),
SystemPrompt = "You are a creative assistant that generates images."
};
await agent.SendAsync("Generate an image of a sunset over mountains");
await foreach (var evt in agent.ReceiveAsync())
{
switch (evt)
{
case ImageEvent image:
// Save generated image
var imageBytes = Convert.FromBase64String(image.Data);
await File.WriteAllBytesAsync($"generated_{Guid.NewGuid()}.png", imageBytes);
Console.WriteLine($"Image generated: {image.MediaType}");
break;
case TextEvent text:
Console.Write(text.Content);
break;
}
}
Key Features:
ImageEvent emitted for generated images during streamingThoughtSignature for reasoning transparencySupported Models:
gemini-2.0-flash-exp-image-generationgemini-2.5-flash-image (Nano Banana)gemini-3-pro-image-previewAgentCircuits automatically manages context limits to prevent overflowโenabled by default with zero configuration:
await using var agent = new Agent
{
Model = "claude-sonnet-4-5",
AutoCompaction = true, // Default: true (on by default)
SystemPrompt = "You are a coding assistant"
};
// Agent automatically compacts context at 90% of limit
// No manual intervention needed - continues seamlessly
await agent.SendAsync("Read all files in this large project and analyze them");
How It Works:
Key Features:
Configure Compaction Behavior:
var agent = new Agent
{
AutoCompaction = false, // Turn off for full history retention
// Or customize thresholds
ContextWindow = new ContextWindowConfig
{
CompactionThreshold = 0.85, // Trigger at 85% instead of 90%
OutputBufferTokens = 3000, // Reserve more space for output
TokensPerToolDefinition = 200 // Adjust tool definition estimates
}
};
System Events:
await foreach (var evt in agent.ReceiveAsync())
{
if (evt is SystemEvent sys && sys.Subtype == "compaction")
{
Console.WriteLine($"[SYSTEM] {sys.Data["message"]}");
// Output: "Context usage approaching limit (90%). Compacting conversation history..."
// Output: "Compaction complete. Continuing..."
}
}
Customize how session events are converted to LLM messages for 30-50% token savings, model-specific optimization, or domain-specific formats:
await using var agent = new Agent
{
Model = "claude-sonnet-4-5",
ContextBuilder = ContextBuilders.Compact(), // 30-50% token reduction
SystemPrompt = "You are a helpful assistant"
};
await agent.SendAsync("Analyze this project");
// Events converted to compact format: "U: message" instead of full Message objects
1. Compact (Token Optimization)
var agent = new Agent
{
ContextBuilder = ContextBuilders.Compact(maxEventLength: 150)
};
// Output format (30-50% savings):
// U: user message
// A: assistant response
// T: tool_name(arg1=val1)
// Rโ: tool result
2. XML (Claude-Optimized)
var agent = new Agent
{
ContextBuilder = ContextBuilders.Xml(includeTimestamps: true)
};
// Output format (optimized for Claude):
// <conversation>
//
// <user>message</user>
// <tool_use name="read">...</tool_use>
// <tool_result success="true">result</tool_result>
// <assistant>response</assistant>
// </conversation>
3. Custom Implementation
public class MedicalContextBuilder : IContextBuilder
{
public List<Message> BuildMessages(
IReadOnlyList<Event> events,
string? systemPrompt,
IEnumerable<ITool> tools,
Message? firstTurnOverride = null)
{
// Custom formatting logic for medical records...
return formattedMessages;
}
}
var agent = new Agent { ContextBuilder = new MedicalContextBuilder() };
4. Inline Functions
var agent = new Agent
{
ContextBuilder = ContextBuilders.FromFunc(events =>
{
var summary = string.Join("\n", events
.OfType<TextEvent>()
.Select(e => $"{e.Author}: {e.Content}"));
return new List<Message>
{
Message.User(summary)
};
})
};
Key Features:
IContextBuilder for custom formatsSee for examples.
AgentCircuits provides real-time visibility into context window usage and performance metrics:
Track token usage and get proactive warnings before hitting context limits:
using AgentCircuits.Sessions;
var agent = new Agent
{
Model = "claude-sonnet-4-5",
SystemPrompt = "You are a helpful assistant",
Tools = BuiltInTools.Safe
};
await agent.SendAsync("Analyze this project");
// Monitor context usage during execution
var stats = await agent.GetContextStatsAsync();
Console.WriteLine($"Context Usage: {stats.UsagePercentage:F1}%");
Console.WriteLine($"Tokens: {stats.TotalTokens:N0} / {stats.MaxContextTokens:N0}");
Console.WriteLine($"Remaining: {stats.RemainingTokens:N0}");
Console.WriteLine($"Action: {stats.RecommendedAction}");
// Token distribution breakdown
var dist = stats.Distribution;
Console.WriteLine($"System Prompt: {dist.SystemPromptTokens:N0}");
Console.WriteLine($"Tool Definitions: {dist.ToolDefinitionTokens:N0}");
Console.WriteLine($"User Messages: {dist.UserMessageTokens:N0}");
Console.WriteLine($"Assistant: {dist.AssistantTokens:N0}");
Console.WriteLine($"Tool Results: {dist.ToolResultTokens:N0} (typically 50-80%)");
Recommended Actions:
None (<60%): All goodMonitor (60-80%): Watch usageCompact (80-95%): Consider compactionCritical (>95%): Near limitGet detailed performance data for every LLM call:
await agent.SendAsync("Review the authentication code");
await foreach (var evt in agent.ReceiveAsync())
{
if (evt is TurnMetricsEvent metrics)
{
Console.WriteLine($"\n=== Turn {metrics.TurnNumber} Metrics ===");
Console.WriteLine($"Duration: {metrics.DurationMs}ms");
Console.WriteLine($"Tokens: {metrics.Usage.InputTokens} in, {metrics.Usage.OutputTokens} out");
Console.WriteLine($"Cost: ${metrics.Usage.TotalCostUsd:F4}");
var perf = metrics.Performance;
Console.WriteLine($"Throughput: {perf.TokensPerSecond:F1} tokens/sec");
Console.WriteLine($"TTFT: {perf.TimeToFirstTokenMs}ms");
Console.WriteLine($"Tools: {perf.ToolCallsSuccessful}/{perf.ToolCallsAttempted}");
// Per-tool statistics
if (perf.ToolCallsByName != null)
{
foreach (var (tool, stats) in perf.ToolCallsByName)
{
Console.WriteLine($" {tool}: {stats.SuccessCount}/{stats.CallCount} " +
$"({stats.AverageExecutionMs:F0}ms avg)");
}
}
}
if (evt is ResultEvent result && result.Performance != null)
{
// Aggregate metrics across all turns
var aggPerf = result.Performance;
Console.WriteLine($"\n=== Session Summary ===");
Console.WriteLine($"Total Tokens/sec: {aggPerf.TokensPerSecond:F1}");
Console.WriteLine($"Total Tools: {aggPerf.ToolCallsSuccessful}/{aggPerf.ToolCallsAttempted}");
Console.WriteLine($"Avg Tool Time: {aggPerf.AverageToolExecutionMs:F0}ms");
}
}
Metrics Captured:
Use Cases:
Create domain-specific tools with simple attributes:
public class DatabaseTools
{
private readonly IDbConnection _db;
public DatabaseTools(IDbConnection db) => _db = db;
[Tool("query_users", "Query the user database")]
public async Task<ToolResult> QueryUsers(
[ToolParam("SQL WHERE clause")] string where,
[ToolParam("Maximum rows")] int limit,
IToolContext context)
{
if (limit > 1000)
return ToolResult.Error("Limit cannot exceed 1000");
var sql = $"SELECT * FROM users WHERE {where} LIMIT {limit}";
var users = await _db.QueryAsync(sql);
return ToolResult.Success(JsonSerializer.Serialize(users));
}
}
// Use with agent
var dbTools = new DatabaseTools(dbConnection);
var agent = new Agent
{
SystemPrompt = "You are a data analyst assistant",
Tools = Tool.FromInstance(dbTools), // Auto-discovers [Tool] methods
Model = "claude-sonnet-4-5"
};
Features:
Orchestrate specialized sub-agents for complex workflows:
// Define specialized agents
var securityAgent = new Agent
{
Name = "security_scanner",
Description = "Scans code for security vulnerabilities",
SystemPrompt = "You are a security expert. Look for SQL injection, XSS, auth issues...",
Tools = [BuiltInTools.Read, BuiltInTools.Grep],
Model = "claude-sonnet-4-5"
};
var performanceAgent = new Agent
{
Name = "performance_analyzer",
Description = "Analyzes performance bottlenecks",
SystemPrompt = "You are a performance expert. Find N+1 queries, memory leaks...",
Tools = [BuiltInTools.Read, BuiltInTools.Bash],
Model = "claude-sonnet-4-5"
};
// Orchestrator delegates to specialists
var orchestrator = new Agent
{
SystemPrompt = """
Coordinate code analysis by delegating to specialists:
1. Use security_scanner for security review
2. Use performance_analyzer for performance analysis
3. Synthesize findings into actionable report
""",
SubAgents = [securityAgent, performanceAgent],
Tools = [BuiltInTools.Task, BuiltInTools.Read], // Task tool enables delegation
Model = "claude-sonnet-4-5"
};
await orchestrator.SendAsync("Analyze the API endpoints");
await foreach (var evt in orchestrator.ReceiveAsync())
{
if (evt is ToolUseEvent { ToolName: "Task" } task)
{
var subAgent = task.Arguments["subagent_type"];
Console.WriteLine($"โ Delegating to: {subAgent}");
}
if (evt is TextEvent text)
Console.WriteLine(text.Content);
}
Patterns Supported:
AgentHost provides a minimal API for running configured agents against sessions:
using AgentCircuits.Host;
using AgentCircuits.Internal;
using AgentCircuits.Providers;
using AgentCircuits.Sessions;
// Setup AgentHost
var sessionService = new InMemorySessionService();
var agentRepo = new FileBasedAgentConfigRepository("./agents");
var host = new AgentHost(sessionService, agentRepo);
var result = await host.RunAsync(
agentId: "support-bot",
message: Message.User("Help me reset my password"));
Console.WriteLine($"Session: {result.SessionId}");
var responseText = MessageConverter.ExtractText(result.Response.Events, excludePartial: true);
Console.WriteLine($"Response: {responseText}");
1. Continue conversation (existing session):
var followUp = await host.RunAsync(
agentId: "support-bot",
message: Message.User("What if I forgot my username too?"),
sessionId: result.SessionId,
resolution: SessionIdResolution.RequireExisting
);
2. Force a new session:
var freshStart = await host.RunAsync(
agentId: "support-bot",
message: Message.User("Start over"),
sessionId: result.SessionId,
resolution: SessionIdResolution.CreateNew
);
3. Retrieve session history:
var session = await sessionService.GetSessionAsync(result.SessionId);
if (session != null)
{
foreach (var evt in session.Events)
{
Console.WriteLine($"{evt.Author}: {evt.GetType().Name}");
}
}
Define agents in JSON files for version control and easy deployment:
agents/support-bot.json:
{
"id": "support-bot",
"name": "Support Assistant",
"modelId": "claude-sonnet-4-5",
"toolNames": ["read", "write"],
"systemPrompt": "You are a helpful IT support assistant",
"maxIterations": 50
}
// Agents auto-loaded from *.json files
var repo = new FileBasedAgentConfigRepository("./agents");
var host = new AgentHost(sessionService, repo);
// Use any agent
await host.RunAsync("support-bot", Message.User("Help me reset my password"));
Handle long-running operations with external completion:
var asyncOps = new AsyncOperationService(asyncOpRepo, sessionService, logger);
// Create operation
var op = await asyncOps.CreateAsync(
sessionId: sessionId,
agentId: "approval-agent",
type: "approval",
metadata: JsonSerializer.SerializeToElement(new { question = "Deploy to production?" }),
timeout: TimeSpan.FromMinutes(5)
);
// External system completes (e.g., webhook)
await asyncOps.CompleteAsync(op.Id, JsonSerializer.SerializeToElement(new { approved = true }));
// No automatic agent wake-up; trigger follow-up explicitly if needed
AgentHost builds on Core primitives to provide a minimal execution entry point:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ YOUR APPLICATION โ
โ host.RunAsync(...) โ
โโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโ
โ โ
โ AgentHost (Execution Layer) โ
โ Session resolution + agent execution โ
โ โ
โโโโฆโโโโโโโโโโโโโโโโฆโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฆโ
โ โ โ
โผ โผ โผ
โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโ
โIAgentConfig โ โISession โ โAgentCircuits.Core โ
โRepository โ โService โ โ(Agent, Tools, etc.)โ
โโโโโโโโโโโโโโโโโโโค โโโโโโโโโโโโโโโโค โโโโโโโโโโโโโโโโโโโโโโ
โโข In-Memory โ โโข In-Memory โ
โโข File (JSON) โ โโข JSON File โ
โโข Custom โ โโข Custom โ
โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ
Key Design Principles:
See Also:
AgentCircuits includes a complete web-based management portal for monitoring, configuring, and interacting with agents:
Dashboard:
Agent Management:
Session Viewer:
Interactive Playground:
User Management:
Provider Management:
MCP Server Management:
Session Input Routing:
Async Operations Monitoring:
Option 1: Docker (Recommended for Production)
docker run -d \
-p 8080:8080 \
-v ./data:/data \
-e AGENTCIRCUITS__PROVIDERS__ANTHROPIC__API_KEY=sk-ant-... \
-e AGENTCIRCUITS__PROVIDERS__BEDROCK__ENABLED=true \
ghcr.io/agent-circuits/agentcircuits:latest
Or with docker-compose:
services:
agentcircuits:
image: ghcr.io/agent-circuits/agentcircuits:latest
ports:
- "8080:8080"
environment:
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
- AGENTCIRCUITS__STORAGE__PATH=/data
- AGENTCIRCUITS__PROVIDERS__ANTHROPIC__ENABLED=true
volumes:
- ./data:/data
Option 2: AgentCircuits.Server (.NET)
Run Portal + Chat UI + SignalR hubs together:
dotnet run --project agentcircuits.server/src/AgentCircuits.Server.csproj
# Navigate to http://localhost:8080
Option 3: Portal Only
cd src/AgentCircuits.Portal
dotnet run
# Navigate to http://localhost:8080
The portal uses file-based storage by default:
data/agents/*.jsondata/sessions/*.jsondata/providers/*.jsondata/mcp-servers/*.jsondata/async-operations/*.json// Portal uses Runtime repositories for data persistence
services.AddSingleton<IAgentConfigRepository, FileBasedAgentConfigRepository>();
services.AddSingleton<ISessionService, JsonFileSessionService>();
services.AddSingleton<IProviderConfigRepository, FileBasedProviderConfigRepository>();
services.AddSingleton<IMcpServerRepository, FileBasedMcpServerRepository>();
services.AddSingleton<IAsyncOperationRepository, JsonAsyncOperationRepository>();
// AgentHost for agent execution
services.AddSingleton<IAgentHost, AgentHost>();
Technology Stack:
Key Features:
A modern end-user chat interface for interacting with AgentCircuits agents, built with SvelteKit. While the Portal provides admin/management capabilities, the UI provides a polished chat experience for end users.
NuGet Package (Recommended):
dotnet add package AgentCircuits.UI
Usage:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
// Mount at root "/"
app.UseAgentCircuitsUI();
// Or mount at a specific path (e.g., behind reverse proxy at /myapp/chat)
app.UseAgentCircuitsUI("/chat");
// With custom options
app.UseAgentCircuitsUI("/chat", options =>
{
options.EnableCacheControlHeaders = false;
options.EnableSpaFallback = true;
});
app.Run();
The NuGet package includes pre-built SvelteKit assets as embedded resourcesโno Node.js required at runtime. When mounted at a sub-path, the UI automatically configures API calls to use the correct base path.
Chat Interface:
Tool Call Visualisation:
Task Management Panel:
Session Management:
Agent Details:
Operation Notifications:
Metrics Display:
Keyboard Shortcuts:
| Shortcut | Action |
|---|---|
Enter |
Send message |
Shift+Ctrl/Cmd+O |
New chat |
Shift+Ctrl/Cmd+D |
Delete conversation |
Ctrl/Cmd+K |
Search conversations |
Option 1: Docker (Recommended for Production)
docker run -d -p 8080:8080 \
-e AGENTCIRCUITS__PROVIDERS__ANTHROPIC__API_KEY=sk-ant-... \
ghcr.io/agent-circuits/agentcircuits:latest
# Chat UI: http://localhost:8080
# Portal: http://localhost:8080/portal
Option 2: AgentCircuits.Server (.NET)
dotnet run --project agentcircuits.server/src/AgentCircuits.Server.csproj
# Chat UI: http://localhost:8080
# Portal: http://localhost:8080/portal
Option 3: Development Setup (for UI modifications)
For UI development (modifying the SvelteKit source):
# 1. Start the backend server
dotnet run --project agentcircuits.server/src/AgentCircuits.Server.csproj
# 2. Install UI dependencies
cd agentcircuits.ui
npm install
# 3. Start the UI development server (proxies to backend on port 8080)
npx vite
# 4. Open http://localhost:5173
AgentCircuits.UI (SvelteKit)
โ
โ REST API + SignalR WebSocket
โผ
AgentCircuits.Server / Portal (ASP.NET Core)
โ
โ Agent Execution
โผ
AgentCircuits SDK (Agent, Tools, LLM Providers)
Technology Stack:
AgentCircuits implements the Agent2Agent (A2A) Protocol, an open standard for cross-platform agent communication:
A2A enables agents from different frameworks and languages to collaborate:
Key Difference:
Both protocols are complementary: A2A agents use MCP to access their tools.
using AgentCircuits.A2A;
using AgentCircuits.A2A.Tools;
// Register remote agent in Portal or via repository
var remoteAgentConfig = new RemoteAgentConfig
{
Id = "python-researcher",
Name = "Python Research Agent",
BaseUrl = "https://research.company.com",
Auth = new AuthenticationConfig
{
Type = "Bearer",
Token = "your-api-key"
}
};
// Call remote agent from your agent
var agent = new Agent
{
SystemPrompt = "You coordinate research tasks",
Tools = [new CallRemoteAgentTool(remoteAgentRepository), BuiltInTools.Write],
Model = "claude-sonnet-4-5"
};
await agent.SendAsync("Use python-researcher to analyze the latest ML papers");
// Agent automatically calls remote Python agent via A2A protocol
// Configure agent for A2A exposure in AgentConfig
var agentConfig = new AgentConfig
{
Id = "code-reviewer",
Name = "Code Review Agent",
SystemPrompt = "You review code for bugs and best practices",
ToolNames = ["read", "grep"],
ModelId = "claude-sonnet-4-5",
// A2A exposure settings
A2A = new A2AExposureSettings
{
Enabled = true,
SkillsOverride = ["code_review", "security_analysis", "performance_optimization"],
RequiredAuth = new AuthenticationConfig { Type = "Bearer" }
}
};
// Agent is now discoverable via (default server base path):
// GET https://your-domain.com/portal/a2a/agents/code-reviewer/.well-known/agent-card.json
// And callable via:
// POST https://your-domain.com/portal/a2a/agents/code-reviewer (JSON-RPC 2.0)
The /portal prefix comes from the default AgentCircuits:Portal:BasePath setting. If you mount the portal at a different path (or at /), the A2A route prefix changes accordingly.
A2A uses agent cards for discovery (similar to OpenAPI specs):
{
"name": "code-reviewer",
"description": "Reviews code for bugs and best practices",
"url": "https://your-domain.com/portal/a2a/agents/code-reviewer",
"skills": ["code_review", "security_analysis", "performance_optimization"],
"authentication": {
"type": "bearer"
}
}
The Portal provides full A2A management:
Remote Agents Page:
Exposed Agents Page:
Dashboard:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ AgentCircuits A2A Integration โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ โโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโ โ
โ โ A2A Client โโโโโโโโโโโโโโโบโ Remote Agents โ โ
โ โ (Outbound) โ โ (Python/Java/JS) โ โ
โ โโโโโโโโโโโโโโโโโค โโโโโโโโโโโโโโโโโโโโ โ
โ โโข AgentCircuits โ โ
โ โ Client โ โโโโโโโโโโโโโโโโโโโโ โ
โ โโข Agent Card โ โ A2A Server โ โ
โ โ Cache โโโโโโโโโโโโโโโบโ (Inbound) โ โ
โ โโข CallRemote โ โโโโโโโโโโโโโโโโโโโโค โ
โ โ AgentTool โ โโข Discovery โ โ
โ โโโโโโโโโโโโโโโโโ โ Endpoints โ โ
โ โโข Message Handler โ โ
โ โโข Task Tracking โ โ
โ โโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ Official A2A .NET SDK (v0.3.3-preview) โ
โ โข JSON-RPC 2.0 protocol โ
โ โข Agent card generation โ
โ โข Authentication โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Key Components:
See Also:
Add cross-cutting concerns like security, logging, and cost tracking. Hooks are agent-level, allowing multi-tenant scenarios with different policies per agent:
// Create agent with hooks
var agent = new Agent
{
Model = "claude-sonnet-4-5",
// Security: Block dangerous operations
BeforeToolUse = async ctx =>
{
if (ctx.Tool.Name == "Bash" && ctx.Arguments["command"].ToString().Contains("rm -rf"))
return ToolResult.Denied("Dangerous command blocked");
return null; // Allow
},
// Auto-backup: Save files before modifications
AfterToolUse = async ctx =>
{
if (ctx.Tool.Name is "Write" or "Edit" && ctx.Result?.IsSuccess == true)
{
var filePath = ctx.Arguments["file_path"].ToString();
await CreateBackupAsync(filePath);
}
return null;
},
// Cost tracking: Monitor LLM spend
AfterModel = async ctx =>
{
if (ctx.Response?.Usage?.TotalCostUsd is decimal cost)
{
totalCost += cost;
Console.WriteLine($"[Cost: ${cost:F4} | Total: ${totalCost:F2}]");
}
return null;
}
};
Fluent Hook Builder for common patterns:
// Composable hook configuration
agent
.BlockTool("bash", "Security policy prohibits shell access")
.AllowOnlyTools("read", "write", "grep")
.OnBeforeToolUse(MyHooks.ValidateFilePaths);
Sample Hook Patterns (see SdkShowcase/HookPatterns/):
SecurityHooks - Validate and block operationsCostTrackingHooks - Track API spendingAuditLoggingHooks - Log all tool usesFileBackupHooks - Auto-backup before editsRateLimitingHooks - Throttle API callsUsageStatisticsHooks - Collect metricsUse regex-based patterns to target hooks at specific tools:
// Block tools matching a pattern (regex supported)
agent.BlockToolPattern("Bash|Edit", "Destructive tools are disabled");
// Hook only fires for matching tools
agent.OnBeforeToolUse("Write|Edit", async ctx =>
{
// Only called for Write or Edit tools
Console.WriteLine($"File operation: {ctx.Tool.Name}");
return null;
});
// After-hook with pattern matching
agent.OnAfterToolUse(".*Fetch.*", async ctx =>
{
// Matches WebFetch, DataFetch, etc.
Console.WriteLine($"Fetch completed: {ctx.Result?.Content?.Length ?? 0} chars");
return null;
});
Pattern Syntax:
"Bash" - matches only the Bash tool"Bash|Edit|Write" - matches any of the listed tools"^File.*" - matches tools starting with "File"Intercept, validate, or modify user prompts before they are processed:
var agent = new Agent
{
Model = "claude-sonnet-4-5",
// Validate and modify user prompts
OnUserPrompt = async ctx =>
{
// Block inappropriate content
if (ctx.Prompt.Contains("password", StringComparison.OrdinalIgnoreCase))
{
return new UserPromptResult
{
Block = true,
Reason = "Prompts containing passwords are not allowed"
};
}
// Add context to the prompt
return new UserPromptResult
{
AdditionalContext = $"Current directory: {ctx.WorkingDirectory}",
ModifiedPrompt = null // Keep original prompt
};
}
};
UserPromptResult Options:
Block - When true, the prompt is rejected and not processedReason - Message explaining why the prompt was blockedModifiedPrompt - Replace the original prompt textAdditionalContext - Inject additional context alongside the promptUserPromptHookContext Properties:
SessionId - Current session identifierAgentName - Name of the agent receiving the promptWorkingDirectory - Current working directoryPrompt - The original user prompt textFullMessage - Full Message object for multimodal inputs (null for text-only)Monitor sub-agent lifecycle events for logging, metrics, or coordination:
var orchestrator = new Agent
{
Model = "claude-sonnet-4-5",
SubAgents = [securityAgent, performanceAgent],
// Called when a subagent starts
OnSubagentStart = async ctx =>
{
Console.WriteLine($"[{ctx.Timestamp:HH:mm:ss}] Starting subagent: {ctx.SubagentName}");
Console.WriteLine($" Parent: {ctx.ParentAgentName}");
Console.WriteLine($" Prompt: {ctx.Prompt}");
},
// Called when a subagent completes
OnSubagentStop = async ctx =>
{
Console.WriteLine($"[{ctx.Timestamp:HH:mm:ss}] Subagent completed: {ctx.SubagentName}");
if (ctx.Result != null)
{
Console.WriteLine($" Success: {ctx.Result.Success}");
Console.WriteLine($" Output: {ctx.Result.TextOutput.Length} chars");
if (!string.IsNullOrEmpty(ctx.Result.ErrorMessage))
Console.WriteLine($" Error: {ctx.Result.ErrorMessage}");
}
}
};
SubagentHookContext Properties:
SessionId - Parent session identifierParentAgentName - Name of the orchestrating agentSubagentName - Name of the sub-agent being executedPrompt - Prompt passed to the sub-agentResult - Execution result (only in OnSubagentStop, null in OnSubagentStart)Timestamp - UTC timestamp of the lifecycle eventExecute external shell scripts as hooks, enabling polyglot hook implementations in Python, Node.js, Bash, or any language:
using AgentCircuits.Hooks;
// Use a Python script as a tool hook
agent.BeforeToolUse = CommandHook.FromCommand("python3 ./hooks/validate.py");
// Use a Node.js script for user prompt validation
agent.OnUserPrompt = CommandHook.FromCommandForUserPrompt("node ./hooks/prompt-filter.js");
// Use a shell script for subagent monitoring
agent.OnSubagentStart = CommandHook.FromCommandForSubagent("./hooks/audit-subagent.sh");
JSON Protocol for Tool Hooks:
The hook command receives context as JSON on stdin:
{
"session_id": "abc123",
"agent_name": "code-reviewer",
"tool_name": "Bash",
"tool_input": { "command": "ls -la" },
"working_directory": "/home/user/project"
}
Exit Code Semantics:
| Exit Code | Meaning |
|---|---|
| 0 | Success - allow execution (parse stdout for modifications) |
| 2 | Block - deny the operation (stderr contains reason) |
| Other | Warning - proceed but log stderr |
JSON Output for Modifications (stdout, exit 0):
{
"block": false,
"reason": "optional reason",
"modified_result": {
"content": "override content",
"is_success": true,
"error_message": null
}
}
Example Python Hook:
#!/usr/bin/env python3
import json
import sys
# Read context from stdin
context = json.load(sys.stdin)
# Block dangerous rm commands
if context['tool_name'] == 'Bash':
cmd = context['tool_input'].get('command', '')
if 'rm -rf' in cmd:
print('Blocked dangerous rm -rf command', file=sys.stderr)
sys.exit(2) # Block
# Allow execution
sys.exit(0)
User Prompt Hooks via CommandHook:
#!/usr/bin/env python3
import json
import sys
context = json.load(sys.stdin)
prompt = context['prompt']
# Modify the prompt
if 'todo' in prompt.lower():
print(json.dumps({
"modified_prompt": prompt,
"additional_context": "Remember to update the task list when done."
}))
sys.exit(0)
# Block sensitive content
if 'api_key' in prompt.lower():
print(json.dumps({
"block": True,
"reason": "Prompts containing API keys are not allowed"
}))
sys.exit(0)
sys.exit(0)
Subagent Hooks via CommandHook:
Subagent hooks are fire-and-forget (informational only):
{
"session_id": "abc123",
"parent_agent_name": "orchestrator",
"subagent_name": "security_scanner",
"prompt": "Scan for vulnerabilities",
"result": {
"success": true,
"error_message": null,
"text_output": "No vulnerabilities found"
}
}
Key Benefits:
AgentCircuits uses Microsoft.Extensions.Logging for structured, high-performance logging throughout the SDK. Configure log levels via appsettings.json:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"AgentCircuits": "Information",
"AgentCircuits.Internal.TurnExecutor": "Debug",
"AgentCircuits.Internal.ToolExecutor": "Debug",
"AgentCircuits.Host": "Information"
}
}
}
Log Categories:
| Category | Event ID Range | Description |
|---|---|---|
AgentCircuits |
1000-1099 | Agent lifecycle (start, complete, cancel) |
AgentCircuits.Internal.TurnExecutor |
1100-1199 | Turn execution details |
AgentCircuits.Internal.ToolExecutor |
1200-1299 | Tool execution (start, complete, not found) |
AgentCircuits.Sessions |
1300-1399 | Session/context events, compaction |
AgentCircuits.Internal.McpManager |
1400-1499 | MCP server initialisation |
AgentCircuits.Hooks |
1500-1599 | Hook invocations |
AgentCircuits.Host |
2000-2199 | AgentHost and AsyncOperationService |
Recommended Log Levels:
Information - Agent lifecycle, operation resultsDebug - Turn/tool execution detailsTrace - Full context, parameters, payloadsKey Events:
[Information] Agent execution started. SessionId=abc, Agent=reviewer, Model=claude-sonnet-4-5
[Debug] Tool execution started. Tool=read, SessionId=abc
[Debug] Tool execution completed. Tool=read, Success=True, DurationMs=45
[Information] Agent execution completed. SessionId=abc, TotalTurns=3, TotalTokens=1250, DurationMs=4500
Connect to Model Context Protocol servers for extended capabilities:
using AgentCircuits.Mcp;
var agent = new Agent
{
Model = "claude-sonnet-4-5",
Tools = BuiltInTools.Safe, // Built-in tools
McpServers = new()
{
["filesystem"] = new McpServerConfig
{
Type = McpTransportType.Stdio,
Command = "npx",
Args = ["-y", "@modelcontextprotocol/server-filesystem", "/my/project"]
},
["github"] = new McpServerConfig
{
Type = McpTransportType.Http,
Url = "https://api.githubcopilot.com/mcp/"
}
}
};
// MCP tools are automatically loaded and available alongside built-in tools
await agent.SendAsync("Read the README.md file");
MCP Support:
The agent naturally stops when the LLM signals completion, with MaxIterations as a safety limit:
// Default behavior - natural stopping
var agent1 = new Agent
{
MaxIterations = 50 // Default, safety limit
};
// Strict limit for simple tasks
var agent2 = new Agent
{
MaxIterations = 10
};
// Higher limit for complex research
var agent3 = new Agent
{
MaxIterations = 200
};
// Long-form content generation
var agent4 = new Agent
{
ContinueOnMaxTokens = true,
MaxIterations = 100
};
The agent automatically stops when the LLM returns FinishReason.Stop with no tools used.
Persist and resume conversations with powerful session helpers:
// Save session for later
var sessionId = agent.SessionId;
// Resume in new process/request
var session = await sessionService.GetSessionAsync(sessionId);
var resumedAgent = new Agent
{
Session = session,
Model = "claude-sonnet-4-5"
};
await resumedAgent.SendAsync("Continue from where we left off");
// Fork a session to try different approaches
var experimentalSession = SessionHelpers.Fork(session);
// Rewind by removing last 10 events
var rewindedSession = SessionHelpers.Rewind(session, 10);
// Resume at a specific event
var checkpointSession = SessionHelpers.ResumeAt(session, eventIndex: 42);
// Remove failed tool uses for retry logic
var cleanedSession = SessionHelpers.RemoveFailedTools(session);
// Create checkpoints
var checkpointId = await SessionHelpers.CreateCheckpoint(session,
sessionService,
name: "before_risky_operation");
Session Storage Options:
// In-memory (development)
var sessionService = SessionServiceFactory.InMemory();
// JSON file (simple persistence)
var sessionService = SessionServiceFactory.JsonFile("./sessions");
For multi-user production deployments, use AgentCircuits.Storage.Sql for PostgreSQL-backed storage with session ownership and access control:
dotnet add package AgentCircuits.Storage.Sql
using AgentCircuits.Storage.Sql;
// With Portal - use UseCustomStorage + AddAgentCircuitsSqlStorage
builder.Services.AddAgentCircuitsPortal(portal =>
{
portal.UseCustomStorage(); // Disable built-in file storage
});
builder.Services.AddAgentCircuitsSqlStorage(options =>
{
options.ConnectionString = "Host=localhost;Database=agentcircuits;Username=user;Password=pass";
options.Schema = "public"; // Optional, defaults to "public"
});
Features:
Sessions can be automatically named using LLM-generated summaries for easy identification:
// Register in DI
services.Configure<SessionSummariserOptions>(options =>
{
options.Model = "claude-haiku-4"; // Fast, cheap model for summarisation
});
services.AddSingleton<ISessionSummariser, SessionSummariser>();
// Use via injection
var session = await sessionService.GetSessionAsync(sessionId);
var summary = await sessionSummariser.GenerateSummaryAsync(session);
// Returns: "Debugging Authentication Flow" or "API Rate Limiting Discussion"
// Update session with the generated name
session.Name = summary;
await sessionService.UpdateSessionAsync(session);
// Sessions in the portal/UI now show meaningful names instead of IDs
Key Features:
Use different LLMs for different tasks:
using AgentCircuits.Providers.Anthropic;
using AgentCircuits.Providers.OpenAI;
using AgentCircuits.Providers.Gemini;
using AgentCircuits.Providers.Grok;
using AgentCircuits.Providers.Ollama;
using AgentCircuits.Providers.Bedrock;
// Classify task complexity with fast model
var classification = await Agent.Query(
$"Is this complex? {userQuery}",
model: Anthropic.LanguageModel("claude-haiku-4"),
systemPrompt: "Classify as 'simple' or 'complex'. Respond with one word only."
);
// Route to appropriate model based on complexity
var model = classification.Contains("complex")
? OpenAI.LanguageModel("gpt-4o") // Complex: expensive model
: Gemini.LanguageModel("gemini-2.5-flash"); // Simple: fast & cheap
var result = await Agent.Query(userQuery, model: model);
// For AWS infrastructure, use Bedrock
var bedrockResult = await Agent.Query(
userQuery,
model: Bedrock.LanguageModel("amazon.nova-lite-v1:0") // AWS Bedrock
);
// For xAI Grok models
var grokResult = await Agent.Query(
userQuery,
model: Grok.LanguageModel("grok-3") // xAI Grok
);
// For local/privacy-sensitive tasks, use Ollama
var localResult = await Agent.Query(
userQuery,
model: Ollama.LanguageModel("llama3") // Localhost:11434 by default
);
AgentCircuits is built around the Agent as the central orchestrator. All capabilitiesโtools, providers, sessions, hooks, and observabilityโconnect through the agent runtime:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ YOUR APPLICATION โ
โ Agent.Query(...) โ
โ agent.SendAsync(...) โ
โ agent.ReceiveAsync() โ
โ agent.GetContextStats()โ
โโโโโโโโโโโโโโฌโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโ
โ โ
โ ๐ค AGENT CORE โ
โ (Conversation Engine & Runtime) โ
โ โ
โโโโฆโโโโโโฆโโโโโโฆโโโโโโฆโโโโโโฆโโโโโโฆโโโโโโฆโโ
โ โ โ โ โ โ โ
โโโโโโโโโโโโโโโโจโ โโโโจโโโโโโโโโ โ โ โ โ
โ LLM PROVIDERS โ โ TOOLS โ โ โ โ โ
โโโโโโโโโโโโโโโโโค โโโโโโโโโโโโโโค โ โ โ โ
โ โข Anthropic โ โ Built-in: โ โ โ โ โ
โ โข OpenAI โ โ Read โ โ โ โ โ
โ โข Gemini โ โ Write โ โ โ โ โ
โ โข Grok โ โ Edit โ โ โ โ โ
โ โข Ollama โ โ Bash โ โ โ โ โ
โ โข Bedrock โ โ Grep/Glob โ โ โ โ โ
โ โ โ Task* โ โ โ โ โ
โ ModelRegistry โ โ Memory โ โ โ โ โ
โ (context โ โ WebFetch โ โ โ โ โ
โ limits) โ โ WebSearch โ โ โ โ โ
โโโโโโโโโโโโโโโโโ โ Base64E/D โ โ โ โ โ
โ AskUser โ โ โ โ โ
โ (20 total)โ โ โ โ โ
โ โ โ โ โ โ
โ Custom: โ โ โ โ โ
โ [Tool] โ โ โ โ โ
โ MCP Srvs โ โ โ โ โ
โโโโโโโโโโโโโโ โ โ โ โ
โ โ โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโจโ โโโโจโโโโโโโโโโ โ
โ SESSIONS โ โ EVENTS โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโค โโโโโโโโโโโโโโค โ
โ Storage: โ โ TextEvent โ โ
โ โข InMemory โ โ ToolUse โ โ
โ โข JSON File โ โ ToolResult โ โ
โ โข PostgreSQL (Sql) โ โ SystemEvt โ โ
โ โ โ TurnMetric โ โ
โ Helpers: โ โ โ โ
โ โข Fork (experiment) โ โIAsyncEnum โ โ
โ โข Rewind (undo) โ โ streaming โ โ
โ โข Checkpoint (save) โ โโโโโโโโโโโโโโ โ
โ โข RemoveFailedTools โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโจโ
โ OBSERVABILITY โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ Context Monitoring: Performance Metrics: โ
โ โข agent.GetContextStats() โข TurnMetricsEvent โ
โ โข Token usage & limits โข Throughput (t/s) โ
โ โข Distribution breakdown โข TTFT (streaming) โ
โ โข Recommended actions โข Tool statistics โ
โ โ
โ Auto-Compaction: Triggers @ 90% of context limit โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โโโโโโโโโโโโโโโโโโโโจโโโ โโโโโโโโโโโโโโโโโโจโโโโ โโโโโโโโโโโโโโโโโโโโจโโโ
โ AGENT-LEVEL HOOKS โ โ SUB-AGENTS โ โ DEEP AGENTS โ
โ (Per-Agent Policy) โ โ (Multi-Agent) โ โ (Long-Horizon) โ
โโโโโโโโโโโโโโโโโโโโโโโค โโโโโโโโโโโโโโโโโโโโโโค โโโโโโโโโโโโโโโโโโโโโโโค
โ โข BeforeModel โ โ โข Orchestrator โ โ โข Memory Tool โ
โ โข AfterModel โ โ โข Specialist โ โ (cross-session โ
โ โข BeforeToolUse โ โ โข Task delegation โ โ learning) โ
โ โข AfterToolUse โ โ โข Event aggregate โ โ โข TaskCreate/Update โ
โ โข OnSessionStart โ โ โข Session isolate โ โ (planning) โ
โ โข OnAgentStop โ โโโโโโโโโโโโโโโโโโโโโโ โ โข High MaxIter โ
โ โ โ (200+) โ
โ Fluent Builder: โ โ โข Auto-compaction โ
โ BlockTool() โ โ (context mgmt) โ
โ AllowOnlyTools() โ โ โข Multi-day tasks โ
โ OnBeforeToolUse() โ โ โข Research agents โ
โโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโ
Key Design Principles:
AgentCircuits comes with comprehensive example projects in the agentcircuits.samples package, demonstrating all major features.
Interactive demonstrations of all SDK capabilities:
cd agentcircuits.samples/src/SdkShowcase
dotnet run
12 Comprehensive Demos:
Agent.Query() with read-only tools[Tool] attribute and IToolContext usageThinkingEvent and multi-turn continuityIToolContextProvider for interactive promptsEach demo is self-contained and heavily commented for learning.
AutoCompactionDemo - Automatic Context Management
cd agentcircuits.samples/src/SessionsDemo
dotnet run
ContextBuildersDemo - Token Optimization
cd agentcircuits.samples/src/ContextDemo
dotnet run
ControlFlowDemo - Turn-by-Turn Execution
cd agentcircuits.samples/src/WorkflowsDemo
dotnet run
MultimodalDemo - Vision & Image Analysis
cd agentcircuits.samples/src/MultimodalDemo
dotnet run
ToolsDemo - Custom Tool Creation
cd agentcircuits.samples/src/ToolsDemo
dotnet run
WebSearchDemo - Research Assistant
cd agentcircuits.samples/src/AgentDemo
dotnet run
Demonstrates the Memory Tool's unique capability to learn and apply knowledge across separate sessions:
cd agentcircuits.samples/src/SdkShowcase
dotnet run
# Select Demo 6: Memory Learning
3 Essential Demos:
1. Cross-Conversation Learning
/memories/patterns/concurrency.md2. Multi-Model Compatibility
MemoryToolHandler works with Anthropic, OpenAI, Google, local modelstype: "memory_20250818" for automatic memory checking (native support)3. Memory Organization
patterns/, stats/, preferences/, knowledge/, decisions/Key Insights:
Shows session manipulation patterns:
cd agentcircuits.samples/src/SessionsDemo
dotnet run
Basic usage examples for each LLM provider (see agentcircuits.samples/src/ProvidersDemo):
Code Review Assistant with Memory:
using AgentCircuits;
using AgentCircuits.Tools;
using AgentCircuits.Tools.BuiltIn;
var memoryHandler = new MemoryToolHandler();
var memoryTool = new MemoryTool(memoryHandler);
var reviewer = new Agent
{
SystemPrompt = """
You are a code reviewer that learns from experience.
Check your memory for similar bugs you've seen before.
Store new patterns when you find interesting issues.
""",
Tools = [BuiltInTools.Read, memoryTool],
Model = "claude-sonnet-4-5"
};
await reviewer.SendAsync("Review AuthController.cs");
// Agent checks /memories/patterns/ for known bugs, applies learned patterns
Deep Research Agent (Long-Horizon Tasks):
var researcher = new Agent
{
SystemPrompt = """
You conduct multi-day research projects.
1. Create tasks with TaskCreate for each phase
2. Update task status as you progress (TaskUpdate)
3. Save findings to files
4. Synthesise final report
""",
Tools = [
BuiltInTools.TaskCreate,
BuiltInTools.TaskUpdate,
BuiltInTools.TaskList,
BuiltInTools.Read,
BuiltInTools.Write,
BuiltInTools.Bash
],
MaxIterations = 200, // Long-running research tasks
Model = "claude-sonnet-4-5"
};
See Full Documentation:
Current Version: 0.7.0 Completion: ~98% of planned features (SDK + Portal + Chat UI + A2A Protocol + Thinking Mode + Image Generation + SQL Storage + User Management + Task Management + Channels) Stability: Production-ready core with comprehensive observability, web portal, A2A protocol integration, extended thinking support, image generation, user management, task tracking, and multi-channel routing
StepAsync() for approval workflows, debugging, multi-day operationsThinkingConfig for reasoning mode with streaming ThinkingEvent, multi-turn signature preservation, and interleaved thinking for Claude 4Agent.UseStreaming with partial events for character-by-character displayDocumentContent typeImageEvent streamingagent.GetContextStats(), distribution breakdown (including thinking tokens)TurnMetricsEvent with throughput (tok/s), TTFT, tool statisticsLlmProviders.GetModel()[Tool] and [ToolParam]Critical (production-blocking):
Medium Priority:
Low Priority:
AgentCircuits has comprehensive test coverage across all packages.
# Run all .NET tests (xUnit)
dotnet test agentcircuits.sln
# Run tests for a specific project
dotnet test agentcircuits.core/tests/AgentCircuits.Core.Tests.csproj
# Run UI tests (Vitest)
cd agentcircuits.ui && npx vitest run
| Suite | Tests | Framework | Description |
|---|---|---|---|
| .NET (xUnit) | 4,337 | xUnit | Core SDK, Portal, providers, storage, channels, A2A, server |
| UI (Vitest) | 651 | Vitest | SvelteKit chat interface unit tests |
| E2E (Playwright) | 287 | Playwright | Browser-based end-to-end validation (38 spec files) |
.NET breakdown by project:
| Project | Tests |
|---|---|
| AgentCircuits (Core) | 2,400 |
| AgentCircuits.Portal | 813 |
| AgentCircuits.Storage.Sql | 328 |
| AgentCircuits.Server | 239 |
| AgentCircuits.Providers.Gemini | 141 |
| AgentCircuits.Providers.Anthropic | 115 |
| AgentCircuits.Providers.Bedrock | 115 |
| AgentCircuits.Channels | 68 |
| AgentCircuits.Providers.OpenAI | 49 |
| AgentCircuits.Providers.Ollama | 36 |
| AgentCircuits.Providers.Grok | 20 |
| AgentCircuits.A2A | 13 |
The E2E tests live in agentcircuits.e2e/ and run against a live server instance. They cover the Portal admin UI and the Chat interface across 38 Playwright spec files, testing everything from agent CRUD and session lifecycle to streaming resilience, concurrent sessions, and cross-surface workflows. They require the server to be running and are run separately from the unit/integration tests above.
We welcome contributions! Please see our for details.
MIT License - see for details.
Built with โค๏ธ for the .NET community
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 net10.0 is compatible. net10.0-android net10.0-android was computed. net10.0-browser net10.0-browser was computed. net10.0-ios net10.0-ios was computed. net10.0-maccatalyst net10.0-maccatalyst was computed. net10.0-macos net10.0-macos was computed. net10.0-tvos net10.0-tvos was computed. net10.0-windows net10.0-windows was computed. |
Showing the top 5 NuGet packages that depend on AgentCircuits.Core:
| Package | Downloads |
|---|---|
|
AgentCircuits.Providers.Anthropic
Anthropic Claude provider for AgentCircuits agent framework. Supports Claude 3.5 Sonnet, Opus, and Haiku with streaming, tool calling, and prompt caching. |
|
|
AgentCircuits.Portal
Web-based management portal for AgentCircuits. Provides dashboard, agent configuration UI, session viewer, and interactive playground. |
|
|
AgentCircuits.Providers.Bedrock
AWS Bedrock provider for AgentCircuits agent framework. Enterprise-grade AWS integration with IAM credentials for Claude and other Bedrock models. |
|
|
AgentCircuits.Providers.OpenAI
OpenAI provider for AgentCircuits agent framework. Supports GPT-4o, GPT-4, GPT-3.5 Turbo with streaming, tool calling, and Azure OpenAI. |
|
|
AgentCircuits.Providers.Gemini
Google Gemini provider for AgentCircuits agent framework. Supports Gemini 2.0 Flash, 2.5 Flash, and 2.5 Pro with streaming and tool calling. |
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.7.135-routed-input-auth.1 | 85 | 3/22/2026 |
| 0.7.134 | 213 | 3/19/2026 |
| 0.7.134-tool-auth.1 | 94 | 3/18/2026 |
| 0.7.133 | 187 | 3/19/2026 |
| 0.7.133-surface-bootstrap-c... | 182 | 3/18/2026 |
| 0.7.133-rc.1 | 83 | 3/17/2026 |
| 0.7.132 | 219 | 3/16/2026 |
| 0.7.131 | 213 | 3/15/2026 |
| 0.7.130 | 214 | 3/13/2026 |
| 0.7.130-beta.1 | 91 | 3/13/2026 |
| 0.7.130-auth.1 | 82 | 3/13/2026 |
| 0.7.129 | 206 | 3/12/2026 |
| 0.7.128 | 210 | 3/11/2026 |
| 0.7.127 | 204 | 3/11/2026 |
| 0.7.126 | 202 | 3/11/2026 |
| 0.7.125 | 208 | 3/11/2026 |
| 0.7.124 | 210 | 3/10/2026 |
| 0.7.123 | 208 | 3/10/2026 |
| 0.7.121 | 219 | 3/10/2026 |
| 0.7.119 | 193 | 3/8/2026 |
Core agent framework with streaming, multimodal input, auto-compaction, token monitoring, multi-agent orchestration, MCP integration, and web management portal.