Delibera — Quick Start
This guide walks you through your first AI deliberation with Delibera in a few minutes. By the end you'll have a multi-model debate running locally and a Markdown transcript on disk.
1. Prerequisites#
- .NET 10 SDK (≥ 10.0.301) — the project targets
net10.0and builds withLangVersion=previewto enable C# 15 features. See NET10-Upgrade.md for the full migration notes. - A running Ollama instance — either:
- Local: install Ollama and run
ollama serve, or - Cloud: sign up for Ollama Cloud and grab an API key (no install, no GPU required).
- Local: install Ollama and run
- Optional — for the Operator role: Node.js + npx to launch MCP servers
(e.g.
@playwright/mcp,@marp-team/marp-cli). See section 7.
Models to install#
Pick one of the model sets below based on your hardware. The 🟡 Standard set is recommended for most use cases.
🟢 Minimal — low-end hardware / smoke tests (≈ 2 GB total)#
ollama pull llama3.2:1b
ollama pull qwen2.5:1.5b
ollama pull nomic-embed-text🟡 Standard — recommended (≈ 7 GB total)#
ollama pull llama3.2:3b
ollama pull qwen2.5:7b
ollama pull nomic-embed-text🔴 High-Performance — GPU with ≥ 24 GB VRAM or Ollama Cloud (≈ 30+ GB)#
ollama pull llama3.1:8b
ollama pull qwen2.5:14b
ollama pull mistral:7b
ollama pull nomic-embed-textℹ️ Ollama Cloud uses the same model names — no local download is required, just an API key.
2. Install the Package#
dotnet add package Delibera.CoreOr clone the repository and reference Delibera.Core directly:
git clone https://github.com/techbuzzz/Delibera.git
cd DeliberaTry It From the Command Line#
🆕 The fastest way to try Delibera is the delibera CLI — no console project, no composition root:
dotnet run --project src/Delibera.Cli -- run "Microservices vs Monolith?" --rounds 3It reads the same Delibera configuration section the server does, so the models you already
configured are the models it debates with:
{
"Delibera": {
"Providers": {
"ollama": {
"Models": {
"llama3": { "Endpoint": "http://localhost:11434" },
"qwen": { "Endpoint": "http://localhost:11434" }
}
}
},
"Ollama": { "ApiKey": "" }
}
}A model entry needs an Endpoint; entries without one are skipped. Supported provider keys are
ollama and yandex — Delibera:Ollama:ApiKey and Delibera:Yandex:ApiKey supply the key. With
nothing configured the command explains what is missing rather than silently returning an empty debate.
| Command | What it does |
|---|---|
delibera run "<question>" --strategy s --rounds N --temperature T --output path --stream --json --verbose |
Runs a debate and prints the result |
delibera resume <debate-id> --store <dir> --json |
Reconstructs a DebateResult from a checkpoint's completed rounds |
delibera compare <baseline.json> <comparison.json> --output path --html |
Diffs two saved results |
delibera benchmark "<question>" --iterations N --rounds R |
Reports the spread across repeated runs |
# Stream rounds as they complete, and keep the machine-readable result
delibera run "Should we ship on Friday?" --stream --json --output result.md
# Compare two runs of the same question
delibera run "Should we ship on Friday?" --json > baseline.json
delibera run "Should we ship on Friday?" --json > today.json
delibera compare baseline.json today.json --output diff.md
# Repeat a question and see how stable the answer is
delibera benchmark "Should we ship on Friday?" --iterations 5 --rounds 3📌
resumereports progress, it does not continue the debate. A checkpoint holds the rounds that completed plus the options they ran under — not a finishedDebateResult— so the CLI reconstructs and prints it, markedIsCompleted = false. Continuing the run means rehydrating the provider set from that options snapshot, which is the host's job.
run exits 0 on success, 1 on a handled failure, and 2 on a usage error.
3. Your First Deliberation#
Create a console project and paste the following:
// Program.cs
using Delibera.Core.Council;
using Delibera.Core.Providers;
using var factory = new ProviderFactory();
var ollama = factory.CreateOllama("http://localhost:11434");
var result = await new CouncilBuilder()
.AddMember("llama3.2:3b", ollama, "Analyst")
.AddMember("qwen2.5:7b", ollama, "Strategist")
.SetChairman(Chairman.CreateStandard("qwen2.5:7b", ollama))
.WithStandardDebate()
.WithSystemPrompt("You are a software architecture expert.")
.WithUserPrompt("Microservices vs Monolith for a 5-person startup?")
.WithMaxRounds(4)
.SaveResultTo("./deliberation.md")
.Build()
.ExecuteAsync();
Console.WriteLine(result.FinalVerdict);Run it:
dotnet runDelibera will run a structured, multi-round debate and write the full transcript and the
Chairman's verdict to deliberation.md.
☁️ Using Ollama Cloud? Replace the endpoint with
https://api.ollama.comand pass the API key:factory.CreateOllama("https://api.ollama.com", "YOUR_API_KEY").
4. Using Dependency Injection#
using Delibera.Core.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
var services = new ServiceCollection();
// Binds the "Delibera" section of appsettings.json
services.AddDelibera(configuration, "Delibera");
var provider = services.BuildServiceProvider();
var builder = provider.GetRequiredService<ICouncilBuilder>();appsettings.json:
{
"Delibera": {
"Strategy": "Standard",
"MaxRounds": 4,
"Temperature": 0.7,
"SystemPrompt": "You are a knowledgeable AI expert participating in a council debate.",
"Providers": {
"DefaultType": "Ollama",
"DefaultEndpoint": "http://localhost:11434",
"ApiKey": "",
"EmbeddingModel": "nomic-embed-text"
},
"Compression": {
"Enabled": true,
"Strategy": "Hybrid",
"TargetRatio": 0.5,
"EnableCache": true,
"MaxCacheEntries": 256
},
"Rag": {
"Enabled": false,
"ProviderType": "Qdrant",
"Host": "localhost",
"Port": 6334,
"CollectionName": "council_knowledge",
"ConnectionString": null
},
"Output": {
"Directory": "./debate_results",
"SeparateFiles": true,
"FilePrefix": null
}
}
}For Ollama Cloud, set
Providers:DefaultEndpointtohttps://api.ollama.comandProviders:ApiKeyto your key.
5. Adding Context Compression#
using Delibera.Core.Compression;
using Delibera.Core.Providers.LLM;
var ollama = new OllamaProvider("http://localhost:11434");
var embeddings = new OllamaEmbeddingProvider(ollama, "nomic-embed-text");
var result = await new CouncilBuilder()
.AddMember("llama3.2:3b", ollama, "Analyst")
.AddMember("qwen2.5:7b", ollama, "Strategist")
.SetChairman(Chairman.CreateStandard("qwen2.5:7b", ollama))
.WithCompression(CompressionStrategy.Hybrid,
llmProvider: ollama,
modelName: "llama3.2:3b",
embeddingProvider: embeddings)
.WithCompressionCache()
.WithUserPrompt("Analyze our architecture options...")
.WithMaxRounds(4)
.Build()
.ExecuteAsync();
Console.WriteLine(result.TokenStats?.ToSummary());Compression saves a measured 11.7–12.1% of prompt tokens without losing the meaning of the debate context. See performance-measurements.md.
6. Adding RAG with Qdrant#
docker run -d -p 6333:6333 -p 6334:6334 qdrant/qdrantusing Delibera.Core.Council;
using Delibera.Core.Models;
using Delibera.Core.Providers.LLM;
using Delibera.Core.Providers.RAG;
var ollama = new OllamaProvider("http://localhost:11434");
var embeddings = new OllamaEmbeddingProvider(ollama, "nomic-embed-text");
var ragFactory = new VectorStoreFactory();
var rag = ragFactory.CreateQdrant(embeddings, "localhost", 6334);
var kkMember = new CouncilMember("llama3.2:3b", ollama, "Knowledge Keeper");
var keeper = new KnowledgeKeeper(rag, kkMember, "architecture_kb");
await keeper.IndexFileAsync("./docs/architecture.md");
var result = await new CouncilBuilder()
.AddMember("llama3.2:3b", ollama, "Backend Expert")
.AddMember("qwen2.5:7b", ollama, "DevOps Expert")
.SetChairman(Chairman.CreateStandard("qwen2.5:7b", ollama))
.WithKnowledgeKeeper(keeper)
.WithStandardDebate()
.WithUserPrompt("Our startup has 4 developers. Microservices or monolith?")
.WithMaxRounds(4)
.Build()
.ExecuteAsync();💡 Swap
CreateQdrant(...)forCreatePgVector(embeddings, connectionString)to use PostgreSQL/pgvector instead.
7. Using the Operator (MCP Tools)#
The Operator is a micro-agent that connects the council to external tools through
MCP (Model Context Protocol) servers — web browsing,
file system access, Marp presentation generation, and more. Participants delegate a task at any
moment by writing a [[OPERATOR: ...]] marker, and the Operator runs the right tools and feeds the
result back into the next round.
7.1. Install the prerequisites#
The example servers below are launched via npx, so you need Node.js + npx:
# Verify Node.js / npx are available
node --version
npx --version
# For the browser server, install the Playwright engines once:
npx playwright installYou also need Ollama running with a cheap model for the Operator (e.g. llama3.2) — the
Operator uses its own model, separate from the council members.
7.2. Configure MCP servers#
Each MCP server is described by an McpServerConfig. Use Stdio(...) for local processes or
Http(...) for remote servers:
using Delibera.Core.Models;
var servers = new[]
{
// 🌐 Browser — navigation, page reading, clicks, screenshots
McpServerConfig.Stdio(
name: "browser",
command: "npx",
arguments: new[] { "-y", "@playwright/mcp@latest", "--headless" }),
// 🎯 Marp — generate HTML/PDF/PPTX presentations from Markdown
McpServerConfig.Stdio(
name: "marp",
command: "npx",
arguments: new[] { "-y", "@marp-team/marp-cli", "--server", "./out" }),
// …or a remote HTTP/SSE MCP server with auth headers:
// McpServerConfig.Http(
// name: "remote",
// endpoint: "https://my-mcp-host.example.com/mcp",
// additionalHeaders: new Dictionary<string, string> { ["Authorization"] = "Bearer <token>" }),
};| Server | Transport | Launch command | What it provides |
|---|---|---|---|
🌐 browser |
stdio | npx -y @playwright/mcp@latest --headless |
Site navigation, page reading, clicks, screenshots |
🎯 marp |
stdio | npx -y @marp-team/marp-cli --server <dir> |
Generating presentations (HTML/PDF/PPTX) from Markdown |
7.3. Direct usage (without a council)#
using Delibera.Core.Council;
using Delibera.Core.Interfaces;
using Delibera.Core.Models;
using Delibera.Core.Providers.LLM;
using Delibera.Core.Providers.Mcp;
var ollama = new OllamaProvider("http://localhost:11434");
await using var @operator = new Operator(
new CouncilMember("llama3.2", ollama, "Operator"),
new IMcpClient[] { new McpClientAdapter(servers[0]), new McpClientAdapter(servers[1]) });
await @operator.InitializeAsync(); // connects to servers and discovers tools
// 1) Browse a website and summarize it
var browse = await @operator.ExecuteTaskAsync(
"Open https://modelcontextprotocol.io and briefly summarize what MCP is.");
Console.WriteLine(browse.FinalAnswer);
// 2) Generate a 3-slide presentation and save it as deck.html
var deck = await @operator.ExecuteTaskAsync(
"Generate a 3-slide Marp presentation about .NET 10 and save it as deck.html.");
Console.WriteLine(deck.FinalAnswer);
await usingreleases the MCP clients through the Operator'sValueTask DisposeAsync.
7.4. Operator inside a debate#
Add the Operator to a council with the convenience WithOperator(...) overload. Participants then
delegate work via the [[OPERATOR: ...]] marker, and their results are injected into later rounds:
var result = await new CouncilBuilder()
.AddMember("llama3.2:3b", ollama, "Researcher")
.AddMember("qwen2.5:7b", ollama, "Reviewer")
.SetChairman(Chairman.CreateStandard("qwen2.5:7b", ollama))
// Operator uses its own cheaper model; reuseCompression shares the council's compressor
.WithOperator("llama3.2", ollama, servers, reuseCompression: true)
.WithStandardDebate()
.WithSystemPrompt(
"You may delegate research or presentation tasks to the Operator by writing " +
"[[OPERATOR: <task>]] in your message.")
.WithUserPrompt("Research the latest .NET 10 features and prepare a short summary deck.")
.WithMaxRounds(4)
.SaveResultTo("./deliberation.md")
.Build()
.ExecuteAsync();
Console.WriteLine(result.FinalVerdict);A participant message might look like:
I think we should highlight the runtime improvements.
[[OPERATOR: open https://learn.microsoft.com/dotnet/core/whats-new/dotnet-10 and list the top 5 features]]All Operator interactions are recorded per round and rendered in the final Markdown report under a 🛠️ Operator Interactions block.
7.5. Configure via appsettings.json#
{
"Delibera": {
"Operator": {
"Enabled": true,
"ModelName": "llama3.2",
"ReuseCompression": true,
"McpServers": [
{
"Name": "browser",
"Transport": "Stdio",
"Command": "npx",
"Arguments": [ "-y", "@playwright/mcp@latest", "--headless" ]
},
{
"Name": "marp",
"Transport": "Stdio",
"Command": "npx",
"Arguments": [ "-y", "@marp-team/marp-cli", "--server", "./out" ]
}
]
}
}
}▶️ Run the full demo:
dotnet run --project src/Delibera.ConsoleApp -- --operator-mcp. SeeOperatorMcpToolsExample.csand NET10-Upgrade.md for details.
8. Saving Separate Output Files#
var (resultPath, statsPath, logsPath) = await result.SaveAllAsync("./output");
// → debate_<timestamp>_result.md
// → debate_<timestamp>_statistics.md
// → debate_<timestamp>_logs.mdEach file is plain Markdown — ideal for committing to git, posting to chat tools, or feeding back into another LLM.
9. Explore the ConsoleApp#
The repository ships with a runnable demo under
src/Delibera.ConsoleApp/ that exercises every feature:
| Example | Command | What it shows |
|---|---|---|
| Quick start | dotnet run |
Default debate from appsettings.json |
| Dependency injection | dotnet run -- --di |
AddDelibera() + ICouncilBuilder resolution |
| Separate file output | dotnet run -- --separate-files |
SaveAllAsync() — three Markdown files |
| Context compression | dotnet run -- --compression |
All 4 strategies + cache + token stats |
| Multi-provider | dotnet run -- --multiprovider |
Cloud + local models in one council |
| RAG (Qdrant) | dotnet run -- --rag |
Document indexing + Knowledge Keeper |
| RAG (pgvector) | dotnet run -- --pgvector |
PostgreSQL-backed vector search |
| Operator (basics) | dotnet run -- --operator |
Operator role with MCP tools |
| Operator + MCP | dotnet run -- --operator-mcp |
🆕 Browser + Marp MCP servers in a council |
| Microsoft.Extensions.AI | dotnet run -- --msai |
🆕 IChatClient/IEmbeddingGenerator + middleware |
Tool Use (Members Calling Functions)#
🆕 A member can call tools during its turn — read a file, fetch a URL, or reach an MCP server.
using Delibera.Core.Interfaces;
using Delibera.Core.Tools;
// Rooted at a directory, rejects traversal, caps each read at 1 MB by default
var files = new FileSystemToolProvider("./repo");
// Only these hosts are reachable; plain HTTP is refused unless you opt in
var http = new HttpToolProvider(httpClient, ["docs.example.com", "api.github.com"]);
// Or bridge an MCP server's tools
var mcp = new McpToolProvider(mcpClient);
var result = await new CouncilBuilder()
.AddMember("qwen2.5:7b", ollama, "Analyst")
.WithUserPrompt("What does CouncilBuilder.Build() validate?")
.WithTools(files, http)
.WithMaxToolIterations(5)
.Build()
.ExecuteAsync();IToolProvider is a two-member interface: a Name, and
GetToolsAsync(CancellationToken) returning AIFunctions. Implement it to expose your own tools.
Which transport carries the call depends on the member's provider:
| Provider | Transport | Why |
|---|---|---|
Wraps a real IChatClient |
ToolCallTransport.Native |
Real function-calling traffic on the wire |
| String-only adapter | ToolCallTransport.Marker |
A string adapter flattens every message through message.Text, so structured function calls cannot survive it — the call is expressed as [[TOOL: name {json}]] in the response text instead |
The invocation loop itself is M.E.AI's FunctionInvokingChatClient, not a hand-rolled retry. Both
transports write the same log, so you do not branch on transport to read what happened:
foreach (var call in result.ToolCalls) // and per round: round.ToolCalls
{
Console.WriteLine($"{call.MemberName} → {call.ToolName} ({call.Transport}, {call.Duration})");
}ToolCallLog also carries Arguments, Result, Succeeded, ErrorMessage and RoundNumber.
Cost Limits and Rate Limits#
🆕 Cap what a debate may spend. Exceeding a ceiling does not throw — the debate returns a degraded result carrying the spend so far, because a ceiling that throws leaves you with no report.
using Delibera.Core.Cost;
// A money ceiling needs prices: spend only exists once tokens are priced
var pricing = new ModelPricingRegistry("""
[
{ "model": "qwen2.5:7b", "inputPerMillion": 0.30, "outputPerMillion": 0.40 },
{ "model": "llama3.2:3b", "inputPerMillion": 0.10, "outputPerMillion": 0.10 }
]
""");
var result = await new CouncilBuilder()
.WithPricingRegistry(pricing)
.WithCostLimit(0.50m) // Abort at the ceiling (default)
.WithRateLimit(20, TimeSpan.FromMinutes(1)) // 20 calls per minute, per model
// ...
.Build()
.ExecuteAsync();
if (result.IsDegraded)
{
Console.WriteLine($"Stopped early: {result.CostEstimate?.WasTruncated}");
foreach (var failure in result.FailedMembers) { /* why */ }
}WithCostLimit(decimal, CostLimitBehavior) defaults to Abort; WarnAndContinue and Ignore are
also available. WithTokenBudget(long, …) bounds tokens instead and needs no price list.
⚠️
WithCostLimit(...)withoutWithPricingRegistry(...)makesBuild()throwInvalidOperationException. Without prices every call bills zero, so the ceiling is configured, logged, and can never fire.
DebateResult.CostEstimate is a CostEstimate record: TotalCost, TotalPromptTokens,
TotalCompletionTokens, Members, IsEstimate, WasTruncated. IsEstimate is set when a model had
no registered price, so a projection is never mistaken for an invoice.
Beyond the builder shorthands you can supply your own ICostGate (WithCostGate) or IRateLimiter
(WithRateLimiter); BudgetCostGate, TokenBucketRateLimiter, ModelPricingRegistry and
CostLedger are the built-in implementations. RateLimitBehavior is Queue (default), Throw or
Drop; RateLimitScope is Global, PerProvider, PerModel (default) or PerMember.
Comparing Two Debates#
🆕 Diff says how far apart two runs of the same question are:
using Delibera.Core.Debate;
DebateDiff diff = baseline.Diff(today);
Console.WriteLine($"Verdict similarity: {diff.VerdictSimilarity:P0}");
Console.WriteLine($"Verdict changed: {diff.VerdictChanged}");
Console.WriteLine($"Missing rounds: {string.Join(", ", diff.MissingRoundNumbers)}");
foreach (var round in diff.Rounds)
{
foreach (var member in round.Members)
{
// word-level, rendered as **added** / ~~removed~~
Console.WriteLine(member.InlineDiff);
}
}
await diff.SaveToMarkdownAsync("diff.md");
await diff.SaveToHtmlAsync("diff.html"); // self-containedRounds are matched on round number and members on display name, never on list position — so
comparing a four-round run against a three-round run reports the missing round instead of shifting
every later comparison onto the wrong member. Diff also reports AddedRoundNumbers,
MembersOnlyInOld, MembersOnlyInNew and IsEmpty, and every level can be rendered or saved:
DebateDiff → ToMarkdown(), ToHtml(), SaveToMarkdownAsync(path, ct), SaveToHtmlAsync(path, ct)
RoundDiff → RoundNumber, RoundName, Members, MemberSimilarity, VerdictChanged
MemberDiff → Member, OldText, NewText, Similarity, InlineDiff
gRPC Transport#
🆕 Delibera.Grpc and Delibera.Grpc.Client expose the same council over HTTP/2 and protobuf, for
callers that want a typed client rather than SSE:
| Service | RPCs |
|---|---|
DebateService |
CreateDebate, GetDebate, ListDebates, CancelDebate, StreamDebate (server streaming) |
ScenarioService |
RunScenario, ValidateScenario |
CorpusService |
ListCorpora, ListDocuments, IndexDocument |
builder.Services.AddGrpc();
builder.Services.AddSingleton(sp => new DebateServiceImpl(
sp.GetRequiredService<IDebateOrchestrator>(),
() => BuildCouncil(),
sp.GetRequiredService<ILogger<DebateServiceImpl>>()));
app.MapGrpcService<DebateServiceImpl>();The service sits on top of IDebateOrchestrator, not beside it — so a debate executed over gRPC
takes the same distributed-execution and result-caching path as one executed over REST. See
src/Delibera.Grpc/README.md for the client-side walkthrough.
Next Steps#
- Read the README for the full feature overview, architecture, and design patterns.
- See docs/Server.md for the ASP.NET Core hosted service with REST API and SSE streaming.
- See docs/caching.md for result caching and docs/distributed-debates.md for distributed debate orchestration.
- See CONTRIBUTING.md if you'd like to contribute.
⚖️ Delibera — Thoughtful AI Decisions
Source: docs/QuickStart.md on GitHub — edit it there and this page updates on the next deploy.
