Collective decision making through structured AI deliberation β with RAG, pgvector, Knowledge Keeper, π οΈ Operator (MCP tools), Chairman, π₯ Context Compression, βοΈ AutoChunking, π Dependency Injection & π Execution Logging
Delibera is a C# / .NET 10 framework that orchestrates multi-model deliberations between LLMs. Multiple AI models reason through a question across structured rounds, critique each other's answers, and a Chairman weighs the arguments to synthesise a balanced final verdict β enriched by a Knowledge Keeper backed by Qdrant or PostgreSQL/pgvector (RAG), with intelligent context compression to minimise token usage.
The name comes from deliberation β the careful weighing of evidence and viewpoints before reaching a decision. Delibera brings that discipline to AI, helping teams reach thoughtful, well-reasoned outcomes rather than single-model guesses.
| Feature | Description |
|---|---|
| ποΈ Multi-Model Councils | Orchestrate any number of LLM participants across structured debate rounds |
| βοΈ Chairman Synthesis | A dedicated moderator opens, regulates, and synthesises the final verdict |
| π Knowledge Keeper (RAG) | Per-round semantic retrieval with structured, cited responses |
| π οΈ Operator (MCP Tools) | A micro-agent that delegates tasks to MCP servers (web, files, Marp, Notion, β¦) on demand during the debate |
| π Qdrant + pgvector | Pluggable vector stores β use a dedicated DB or your existing PostgreSQL |
| ποΈ Context Compression | 4 strategies (Semantic, Deduplication, Summarization, Hybrid) applied to every round prompt β measured at 11.7β12.1% prompt-token savings |
| βοΈ AutoChunking | Progressive disclosure of large documents across rounds β respects model context windows |
| π Distributed Debates | IDebateOrchestrator with local and Redis backends β parallelize debates across machines |
| πΎ Result Caching | IDebateCache with in-memory, file, and Redis backends β skip re-running identical debates |
| π₯οΈ Delibera.Server | ASP.NET Core 10 Minimal API with REST + SSE streaming for debates over HTTP β now published as its own NuGet package |
| π§ Tool Use (Function Calling) | IToolProvider + AIFunction β members call tools mid-debate. Native function calling where the provider supports it, [[TOOL: β¦]] markers otherwise. Ships filesystem, HTTP and MCP providers |
| πΈ Cost Gates & Rate Limits | Hard spend ceilings and per-model call limits. A breach returns a degraded result carrying what was spent β never a thrown exception |
| π Debate Diff | baseline.Diff(candidate) β word-level Markdown/HTML comparison of two runs, matched by round number and member name |
| β¨οΈ delibera CLI | run, resume, compare, benchmark from the shell |
| π‘ gRPC Transport | Server-streaming debate events over gRPC, layered on IDebateOrchestrator so caching and distribution behave identically |
| π Dependency Injection | AddDelibera() extension for IServiceCollection with full options binding |
| π Execution Logging | ExecutionLog model with LogLevel β Chairman, KK, Compression & participant events |
| π Separate File Output | Export result.md, statistics.md, and logs.md independently |
| π Interface-First | Clean abstractions for providers, factories, builders and executors |
| π€ Microsoft.Extensions.AI | First-class support for IChatClient / IEmbeddingGenerator β plug in OpenAI, Azure OpenAI, Ollama or any compatible backend, with middleware (function calling, logging) |
| π Cooperative Cancellation | Every public async method accepts a CancellationToken; a host shutdown or user cancel aborts the debate mid-flight (rounds, LLM calls, MCP tools, RAG, file saves) |
| π§± Modern C# 15 (preview) | Built on .NET 10 with LangVersion=preview, file-scoped namespaces, records, span/SIMD hot paths |
Opt-in CORS for static browser clients, a project website on GitHub Pages, Web UI accessibility and behaviour fixes, server-side performance improvements, and a BuiltIn constants refactor. No breaking changes.
| Feature | Description |
|---|---|
| π Project website | Next.js static site on GitHub Pages β docs at any depth, release notes, measurements, debate templates extracted from source. The version is read from the csproj at build time and cannot be wrong. |
| π Opt-in CORS | Delibera:Server:Cors:AllowedOrigins registers a named CORS policy for deliberately static browser clients. Off by default β the Web UI uses its same-origin BFF route. Origins matched exactly; no wildcard switch. |
| βΏ Accessibility | Rounds announced to screen readers via a polite live region, every route titles itself, hover states and visible focus rings. |
| β‘ Performance | ValidationFilter caches validator type resolution, RedisDebateOrchestrator reuses the injected ConnectionMultiplexer, SSE writer assembles frames once and flushes once. |
| π§ BuiltIn constants | Single source of truth for HTTP headers, SSE event names, config sections, and role names across all Delibera projects. |
| π Web UI fixes | Cancel distinguishes "cancelled" from "already finished", filter change reloads from page 1, npm test builds first via pretest, proxy aborts upstream SSE on client disconnect (6 ms vs 2699 ms). |
| π CI improvements | Docker Hub publish behind approval environment dockerhub-prod, release pages built from WhatsNew docs, merge commits instead of squash. |
| β Fixed | Web UI typecheck was passing while silently checking less β Nuxt 4.6.1, vue-router ^5.4.0, vue-tsc 3.3, vitest 5. Lockfile regenerated under npm 10 to match Dockerfile pin. |
Full account: docs/WhatsNew-v10.5.3.md.
A browser front end for the debate workflow, Redis actually reachable from inside the server, Docker Hub images, and four Web UI fixes β one of which made creating a debate through the browser impossible.
Contains two breaking behaviour changes: published ports bind to 127.0.0.1 instead of
0.0.0.0, and Delibera.Server gains a transitive dependency on Delibera.Redis. Full account:
docs/WhatsNew-v10.5.2.md.
| Feature | Description |
|---|---|
| π₯οΈ Web UI | src/Delibera.WebUI β Nuxt 4, shipped as a second container. List, create, live SSE rounds, verdict with token stats, Markdown export, cancel. Reaches the API only through a Nitro proxy: the server registers no CORS policy, and DebateMapper builds absolute URLs without forwarded-header handling. SSE is forwarded unbuffered, asserted against a real socket. |
| π Redis in the server | Delibera:Redis:Enabled registers the Redis orchestrator and, optionally, the Redis result cache. Off by default. Previously Delibera.Server referenced only Delibera.Core, so a redis service in compose connected to nothing. Does not buy distributed turn execution β DebateWorkerService is a deliberate no-op. |
| π³ Docker Hub | techbuzzz/delibera-server and techbuzzz/delibera-webui, published from v* tags as linux/amd64 + linux/arm64. Ollama is not re-published. |
| π Localhost-only ports | Every published port binds to 127.0.0.1. The API is unauthenticated and unthrottled, and a debate spends real credits β so this binding is the enforcement, not decoration. |
Tests: 620 passing (493 Core + 117 Server + 10 gRPC), 0 failed, 0 skipped, plus 34 Web UI tests.
The release build is clean under -warnaserror.
Closes all six open issues and adds one fix that only a live database could surface. No breaking changes β everything is opt-in and no public interface gained a member. Full account: docs/WhatsNew-v10.5.1.md.
| Feature | Description |
|---|---|
| π§ Tool Use | IToolProvider + AIFunction. CouncilBuilder.WithTools(...). Native function calling where the provider wraps a real IChatClient; the [[TOOL: name {json}]] marker protocol elsewhere β because a string-only adapter cannot carry structured tool traffic. Loop is owned by FunctionInvokingChatClient, bounded by WithMaxToolIterations(n). Ships FileSystemToolProvider (rooted, traversal-rejecting), HttpToolProvider (host allow-list, refuses plain HTTP), McpToolProvider. Calls land on DebateRound.ToolCalls / DebateResult.ToolCalls. |
| πΈ Cost Gates & Rate Limits | WithCostLimit(decimal, CostLimitBehavior) and WithTokenBudget(long); WithRateLimit(n, window). DebateResult.CostEstimate reports spend per member with an IsEstimate flag. A denial returns a degraded result carrying the spend, never an exception β a ceiling that throws leaves you with no record of what was spent. Build() throws if a money ceiling is configured without a price registry, because that combination can never be enforced. |
| π Debate Diff | baseline.Diff(candidate) β DebateDiff with word-level **added** / ~~removed~~ diffs, verdict similarity, and explicit lists of rounds or members present on only one side. ToMarkdown(), ToHtml(), SaveToHtmlAsync(path). Rounds match by number and members by name, so a 4-round run against a 3-round run reports the missing round instead of shifting every later comparison. |
| β¨οΈ delibera CLI | delibera run | resume | compare | benchmark on System.CommandLine. compare diffs two saved results; benchmark reports the spread across runs rather than a single number. |
| π‘ gRPC | Delibera.Grpc + Delibera.Grpc.Client, layered on top of IDebateOrchestrator so caching and distributed execution behave identically. Server streaming ends with exactly one terminal event. |
| π¦ NuGet GA | Delibera.Core, Delibera.Server and Delibera.Redis are all publishable now. The workflow fails if any package ships without its README or icon. |
Two defects fixed here were invisible to the unit tests, because the tests used fakes:
| Fix | Description |
|---|---|
| π pgvector could not write or search | AddWithValue(new Vector(...)) boxes the value, so Npgsql had no type to map and the first upsert threw InvalidCastException. Declaring NpgsqlDbType.Unknown fails too β UseVector() only registers a mapping for the Npgsql version Pgvector was built against. The embedding now travels as pgvector's text form with a ::vector cast, understood by any version. Qdrant was unaffected. |
| πͺ Idempotent vector indexing | BaseRagProvider assigned Guid.NewGuid() per chunk, and both stores use the id as an upsert key β so re-indexing appended. Three runs over 24 chunks left 72 points. The id is now derived from the chunk's identity. Verified on live Qdrant and pgvector: three runs of a 6-chunk document leave 6. |
Five defects found by measuring real deliberations against Ollama Cloud. Two of them made headline features silently non-functional β full evidence in docs/performance-measurements.md.
| Fix | Description |
|---|---|
| ποΈ Compression now runs | CompressTextAsync was public API that nothing in the pipeline called; TokenStats stayed null and ten runs reported 0.00% saved at 0 ms overhead. The compressor is now applied to each round prompt and every pass is logged. Measured 11.7β12.1% β still well under the 30β70% previously claimed, which the docs now say. |
| π Ollama Cloud works at all | The provider defaulted to num_predict: -1, which the endpoint rejects outright (max_tokens must be positive). Every request failed before generating a token. A non-positive cap is now omitted from the request. |
A failed member used to be substituted into the transcript as "[ERROR: ...]" β read by the Chairman as an opinion. Failures are now excluded from the round and reported as DebateResult.FailedMembers + IsDegraded. |
|
| π Empty responses explained | New OllamaEmptyResponseException separates "the budget went into reasoning" from "the model returned nothing", with the remedy in the message. |
| π§ Reasoning is controllable | OllamaProvider(enableThinking:) sends Think explicitly; retryOnBudgetExhaustion retries once with a larger budget when generation was cut off mid-thought. |
| β±οΈ Durations were negative | TotalDuration came out as -0.0s on every debate because StartedAt was initialised after CompletedAt was stamped. |
| Feature | Description |
|---|---|
| π Distributed Debates | IDebateOrchestrator interface with LocalDebateOrchestrator (single-process) and RedisDebateOrchestrator (Redis pub/sub for round dispatch + result collection). DebateHandle, DebateOrchestrationStatus, DebateRoundEvent, DebateWorkerService, RedisOrchestratorOptions, RedisOrchestratorExtensions. Wire via ICouncilBuilder.WithOrchestrator(IDebateOrchestrator). |
| πΎ Result Caching | IDebateCache interface with InMemoryDebateCache, FileDebateCache, RedisDebateCache. CacheBehavior enum (UseCache, BypassCache, RefreshCache). DebateCacheKeyGenerator for deterministic cache keys. Cache metadata on DebateResult (CacheHit, CacheKey, CachedAt). ICouncilBuilder.WithCacheBehavior() / WithCache(). DI extensions + OTEL counter delibera.cache.hits/misses. |
| π₯οΈ Delibera.Server | ASP.NET Core 10 Minimal API hosting Delibera over HTTP. REST API (POST /api/debates, GET /api/debates/{id}, DELETE /api/debates/{id}), SSE streaming via GET /api/debates/{id}/stream powered by IDebateOrchestrator.StreamAsync(), background queue via IHostedService + System.Threading.Channels. |
Moderator β Chairman; removed IDebateStrategyWithOptions; ModelCapabilities non-nullable (use IsUnknown); RagProviderFactory β VectorStoreFactory; removed DebateStatus.Paused, DebateOrchestrationStatus.Pending; SseDebateStreamWriter rewritten for IDebateOrchestrator.StreamAsync(); removed DebateRecord._channel/RoundWriter/RoundReader. |
See CHANGELOG.md for the full release notes, docs/performance-measurements.md for what was measured, and docs/ROADMAP.md for the roadmap.
The same council shape fits a lot of decisions. These are the ones that measurably benefit from multiple models disagreeing rather than one model answering twice.
| Use case | Council shape | Why a council earns its cost |
|---|---|---|
| Architecture decision | 3 experts + chairman, 4 rounds, RAG over your ADRs | Different models surface different failure modes; the Chairman is forced to name where they disagree. Measured: an architecture verdict flagged two risks nobody raised β a licence change and the unasked question why the migration was wanted. |
| Code review before a human | Code specialist + 2 generalists, 3 rounds | Catches the two things a linter misses: a wrong fix and an unstated assumption. Measured: the cheapest roster answered "name the one defect" with 78,000 characters and still picked the weaker of two available answers. |
| Incident post-mortem | 3 experts, chairman, RAG over runbooks + chrome-devtools-mcp for live evidence |
Forces the "what do we know vs. what do we assume" split into the record. |
| Security and compliance review | 3 experts, chairman, IsDegraded checked |
A member that fails is a hole in the review, not a footnote β IsDegraded makes that impossible to miss. |
| Vendor and tool selection | 3 experts + chairman, criteria in the prompt | Turns a preference argument into an explicit trade-off table with dissent preserved. |
| Research with citations | Knowledge Keeper over your docs, 4 rounds | Measured: retrieval returned the right passage (top score 0.738) and verdicts cited documents by line range. |
| On-call decision support | 2β3 experts, 2 rounds, cached | Same incident shape twice costs one debate: measured cache hit served in 0.0 s against 154β209 s uncached. |
| Prompt and agent design review | 3 experts, chairman | Finds the prompt that lets the most confident model win rather than the best-reasoned one. |
When not to use it: single-fact lookups, anything where one strong model and a tool call beats three opinions, and latency budgets under a second. A measured 4-round debate runs 154β209 s.
- Use Cases
- Quick Start
- Dependency Injection
- Operator (MCP Tools)
- Context Compression
- AutoChunking
- RAG Integration
- Debate Strategies
- Output Files Structure
- Cancellation Support
- Measured Behaviour
- ConsoleApp Examples
- Installation & Build
- Architecture
- Contributing
- License
- .NET 10 SDK (β₯ 10.0.301) β the project targets
net10.0and builds withLangVersion=previewto enable C# 15 features. See docs/NET10-Upgrade.md for the full migration notes. - A running Ollama instance β local (
ollama serve) or Ollama Cloud (just an API key, no install). - The minimal set of models listed below.
- Optional β for the Operator role: Node.js + npx to launch MCP servers
(e.g.
@playwright/mcp,@marp-team/marp-cli). See Operator (MCP Tools).
Suitable for smoke-testing, low-end hardware, and quick CLI runs.
| Purpose | Model | Size | Pull command |
|---|---|---|---|
| Council member | llama3.2:1b |
1.3 GB | ollama pull llama3.2:1b |
| Council member | qwen2.5:1.5b |
1.1 GB | ollama pull qwen2.5:1.5b |
| Embeddings (RAG) | nomic-embed-text |
274 MB | ollama pull nomic-embed-text |
Good reasoning quality with low latency. This is the default set used throughout the README.
| Purpose | Model | Size | Pull command |
|---|---|---|---|
| Council member | llama3.2:3b |
2.0 GB | ollama pull llama3.2:3b |
| Council member | qwen2.5:7b |
4.7 GB | ollama pull qwen2.5:7b |
| Embeddings (RAG) | nomic-embed-text |
274 MB | ollama pull nomic-embed-text |
Heavier local models, recommended on GPUs with β₯ 24 GB VRAM or on Ollama Cloud.
| Purpose | Model | Size | Pull command |
|---|---|---|---|
| Council member | llama3.1:8b |
4.9 GB | ollama pull llama3.1:8b |
| Council member | qwen2.5:14b |
9.0 GB | ollama pull qwen2.5:14b |
| Council member | mistral:7b |
4.4 GB | ollama pull mistral:7b |
| Chairman | qwen2.5:14b (or larger) |
9.0 GB | ollama pull qwen2.5:14b |
| Embeddings (RAG) | nomic-embed-text |
274 MB | ollama pull nomic-embed-text |
π‘ Ollama Cloud has the same model names but doesn't require local disk space β you only need an API key. See the Configuration section for setting it.
Delibera ships as three NuGet packages and two Docker images. Pick the path that fits.
dotnet add package Delibera.Core # the framework β start here
dotnet add package Delibera.Server # ASP.NET Core host (Minimal API, SSE, MCP)
dotnet add package Delibera.Redis # distributed debates + shared result cacheAll three publish on every release at the same version. Delibera.Server pulls Delibera.Redis
transitively, so you only need to reference it explicitly if you use the Redis integration directly.
Redis stays off unless you set Delibera:Redis:Enabled.
Images are published on every v* tag for linux/amd64 and linux/arm64:
| Image | What it is |
|---|---|
techbuzzz/delibera-server |
API, SSE debate streaming, MCP endpoints |
techbuzzz/delibera-webui |
Nuxt 4 browser front end for the debate workflow |
curl -O https://raw.githubusercontent.com/techbuzzz/Delibera/main/deploy/docker-compose.hub.yml
export DELIBERA_VERSION=10.5.3 # pin a release; omit to track :latest
docker compose -f docker-compose.hub.yml up -d
β οΈ The API is unauthenticated and unthrottled, and a debate spends real LLM credits. Every published port binds to127.0.0.1. Put an authenticating reverse proxy in front before exposing this stack to any network you do not control.
The project site is published at https://techbuzzz.github.io/Delibera/ on every merge to
main. It renders this repository's own Markdown β the guides in docs/, the release notes, the
measured-behaviour report, the changelog, and the debate templates parsed from
Delibera.Core.cs β so a page cannot advertise a version, a feature or a number the code does
not have. See site/README.md.
The Web UI itself is not deployed there β it ships in the
techbuzzz/delibera-webui container image and
is meant to run next to Delibera.Server, which provides the same-origin BFF route
/api/delibera/** the UI depends on. A static export has no Nitro server and therefore no BFF,
so it would need a separate CORS-enabled API origin β a deliberate owner decision, not something
the Pages workflow configures silently.
git clone https://github.com/techbuzzz/Delibera.git
cd Delibera
dotnet build Delibera.slnx -c ReleaseFrom a clone, docker compose up -d starts the whole stack β server, Web UI, Ollama, Qdrant,
pgvector and Redis. See docs/QuickStart.md for the walkthrough.
π§βπ§ Publishing a release, or upgrading from an older one? See docs/ReleaseProcess.md β it documents the order that matters, and the two steps that fail silently rather than loudly.
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);π See docs/QuickStart.md for a step-by-step walkthrough.
dotnet runDelibera will run a structured, multi-round debate and write the full transcript and the
Chairman's verdict to deliberation.md.
Register all Delibera services in one line:
using Delibera.Core.DependencyInjection;
// Option A: With configuration binding (binds the "Delibera" section)
services.AddDelibera(configuration, "Delibera");
// Option B: With options delegate
services.AddDelibera(options =>
{
options.Strategy = "Standard";
options.MaxRounds = 4;
options.Temperature = 0.7f;
options.Compression.Enabled = true;
options.Compression.Strategy = "Hybrid";
options.Compression.TargetRatio = 0.5;
});
// Option C: Defaults only
services.AddDelibera();Resolves these interfaces from DI:
| Interface | Implementation | Lifetime |
|---|---|---|
ILLMProviderFactory |
ProviderFactory |
Singleton |
IVectorStoreFactory |
VectorStoreFactory |
Singleton |
ICompressionFactory |
CompressionService |
Singleton |
ICouncilBuilder |
CouncilBuilder |
Transient |
{
"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
}
}
}To use Ollama Cloud, set Providers:DefaultEndpoint to https://api.ollama.com and put your
key in Providers:ApiKey (or OllamaCloud:ApiKey in the DeliberaApp section used by the
console app β see ConsoleApp Examples).
The Operator is a lightweight micro-agent that connects the council to the outside world through MCP (Model Context Protocol) servers. It exposes whatever tools those servers provide β web browsing, file system access, Marp presentation generation, Notion, PostgreSQL, etc. β to the debate participants.
How it works
-
The Operator connects to one or more MCP servers and discovers their tools on
InitializeAsync. -
Participants are told (in their system prompt) what the Operator can do, and can delegate a task at any moment during the debate by writing a marker in their message:
[[OPERATOR: open https://modelcontextprotocol.io and summarize what MCP is]] -
The Operator interprets the request with its own (cheaper) LLM model, picks and calls the right MCP tools, interprets the results, and returns a concise answer that is injected into the next round.
-
If the council uses context compression, the Operator can reuse the same strategy to compress large tool outputs before they re-enter the debate. Its
DisposeAsyncis aValueTask(.NET 10IAsyncDisposable) so MCP clients are released without aTaskallocation.
All Operator interactions are recorded per round and rendered in the final Markdown report under a π οΈ Operator Interactions block.
using Delibera.Core.Models;
var servers = new[]
{
// stdio transport β launches a local MCP server process
McpServerConfig.Stdio(
name: "browser",
command: "npx",
arguments: new[] { "-y", "@playwright/mcp@latest", "--headless" }),
McpServerConfig.Stdio(
name: "marp",
command: "npx",
arguments: new[] { "-y", "@marp-team/marp-cli", "--server", "./out" }),
// β¦or an HTTP/SSE transport for a remote MCP server
// 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 |
using Delibera.Core.Council;
using Delibera.Core.Providers.LLM;
var ollama = new OllamaProvider("http://localhost:11434");
var council = new CouncilBuilder()
.AddMember("llama3.2:3b", ollama, "Optimist")
.AddMember("qwen2.5:7b", ollama, "Skeptic")
.SetChairman(Chairman.CreateStandard("qwen2.5:7b", ollama))
// Operator uses its own cheaper model; reuseCompression shares the council's compressor
.WithOperator("llama3.2:3b", ollama, servers, reuseCompression: true)
.WithStandardDebate()
.WithUserPrompt("Research the latest .NET 10 features and prepare a short summary.")
.WithMaxRounds(4)
.Build()
.ExecuteAsync();Prefer to build the Operator yourself? Pass a pre-built instance:
using Delibera.Core.Council;
using Delibera.Core.Interfaces;
using Delibera.Core.Models;
using Delibera.Core.Providers.Mcp;
var @operator = new Operator(
new CouncilMember("llama3.2:3b", ollama, "Operator"),
new IMcpClient[] { new McpClientAdapter(servers[0]), new McpClientAdapter(servers[1]) },
compressor: null, // optional IContextCompressor
compressionOptions: null); // optional CompressionOptions
var council = new CouncilBuilder()
/* β¦membersβ¦ */
.WithOperator(@operator)
.Build();await using var @operator = new Operator(
new CouncilMember("llama3.2:3b", ollama, "Operator"),
new IMcpClient[] { new McpClientAdapter(servers[0]), new McpClientAdapter(servers[1]) });
await @operator.InitializeAsync();
var result = await @operator.ExecuteTaskAsync(
"Open https://modelcontextprotocol.io and briefly summarize what MCP is.");
Console.WriteLine(result.FinalAnswer);Configure the Operator declaratively in appsettings.json under Delibera:Operator:
{
"Delibera": {
"Operator": {
"Enabled": true,
"ModelName": "llama3.2:3b",
"ReuseCompression": true,
"McpServers": [
{
"Name": "browser",
"Transport": "Stdio",
"Command": "npx",
"Arguments": [ "-y", "@playwright/mcp@latest", "--headless" ]
},
{
"Name": "remote",
"Transport": "Http",
"Endpoint": "https://my-mcp-host.example.com/mcp",
"AdditionalHeaders": { "Authorization": "Bearer <token>" }
}
]
}
}
}
βΆοΈ A complete, runnable demo lives inOperatorMcpToolsExample.cs. Run it withdotnet run --project src/Delibera.ConsoleApp -- --operator-mcp. For the full technical write-up, see docs/NET10-Upgrade.md.
Automatically compress context between deliberation rounds β 11.7β12.1% of prompt tokens measured, without losing meaning.
| Strategy | How It Works | Best For |
|---|---|---|
| Semantic | Embeds sentences, ranks by relevance to topic, keeps top-N | Large knowledge contexts |
| Deduplication | Removes semantically similar sentences across participants | Multi-model debates with overlap |
| Summarization | LLM produces a concise summary preserving key facts | Maximum compression ratio |
| Hybrid | Dedup β Semantic β Summarize pipeline | Best overall quality |
| None | Pass-through (when disabled) | Debugging |
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)
.WithCompressionOptions(new CompressionOptions { TargetRatio = 0.5 })
.WithCompressionCache()
.WithUserPrompt("Analyze our architecture options...")
.WithMaxRounds(4)
.Build()
.ExecuteAsync();
Console.WriteLine(result.TokenStats?.ToSummary());IContextCompressor
βββ SemanticCompressor β Embedding-based sentence ranking
βββ DeduplicationCompressor β Similarity-based duplicate removal
βββ SummarizationCompressor β LLM-powered summarization
βββ HybridCompressor β Multi-stage pipeline (Dedup β Semantic β Summarize)
βββ PassThroughCompressor β No-op (when disabled)
CompressionFactory β Static factory (Create by enum or string)
CompressionService β DI-friendly wrapper around CompressionFactory
CompressionCache β SHA-256 keyed LRU cache
TokenCounter β Heuristic token estimation
Automatically split large knowledge documents (contracts, reports, articles) into context-window-sized chunks distributed across debate rounds via progressive disclosure. When a document exceeds the smallest model's context window, the orchestrator creates a chunking plan and distributes chunks evenly β every model receives a complete view by the final round.
- Model discovery β queries each model's context window via
GetModelCapabilitiesAsync()(Ollama/api/show, or the built-in registry of 40+ models). - Overhead calculation β system prompt + user question + response buffer + history.
- Chunking plan β splits the document on semantic boundaries (Markdown headers β
paragraphs β sentences), each chunk β€
minWindow β overhead β safetyMargin. - Progressive disclosure β Round 1 gets chunks 1..N/3, Round 2 gets N/3+1..2N/3, etc.
Each round sees
[Chunk X/Y] SectionTitlemarkers + a summary of previous rounds.
// Path 1: Fluent API
var executor = new CouncilBuilder()
.WithAutoChunking(new AutoChunkingOptions
{
Strategy = ChunkingStrategy.SemanticBoundary,
SafetyMargin = 0.15,
MaxChunksPerRound = 3
})
/* β¦members, chairman, knowledgeβ¦ */
.Build();
// Path 2: CouncilOptions snapshot (from DI or manual)
var options = new CouncilOptions
{
AutoChunking = new AutoChunkingConfig { Enabled = true, Strategy = "SemanticBoundary" }
};
var executor = new CouncilBuilder(options) // or .WithOptions(options)
/* β¦ */
.Build();
// Path 3: Lambda configuration
var executor = new CouncilBuilder()
.WithOptions(o =>
{
o.AutoChunking.Enabled = true;
o.AutoChunking.MaxChunksPerRound = 2;
})
/* β¦ */
.Build();{
"Delibera": {
"AutoChunking": {
"Enabled": true,
"Strategy": "SemanticBoundary",
"SafetyMargin": 0.15,
"MaxChunksPerRound": 3,
"EnableMapReduce": true,
"EnableProgressiveDisclosure": true,
"ModelContextWindows": {
"my-custom-model": 65536
}
}
}
}// Query known models
var window = ModelContextWindowRegistry.GetContextWindow("llama3.2"); // β 131072
var window = ModelContextWindowRegistry.GetContextWindow("phi3:mini"); // β 4096
// Register custom models
ModelContextWindowRegistry.Register("my-fine-tuned-model", 65536);
βΆοΈ Run the demo:dotnet run --project src/Delibera.ConsoleApp -- --autochunking
Use a dedicated Qdrant instance or your existing PostgreSQL/pgvector database as a vector store.
using 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");
// pgvector β just add a connection string
var ragFactory = new VectorStoreFactory();
var rag = ragFactory.CreatePgVector(
embeddings,
"Host=localhost;Database=council_vectors;Username=postgres;Password=postgres");
await rag.IndexDocumentAsync("my_collection", documentText);
var results = await rag.SearchAsync("my_collection", "query", limit: 5);
// Wire into a Knowledge Keeper
var kkMember = new CouncilMember("llama3.2:3b", ollama, "Knowledge Keeper");
var keeper = new KnowledgeKeeper(rag, kkMember, "my_knowledge");
await keeper.IndexFileAsync("./docs/architecture.md");IRagProvider
βββ QdrantRagProvider
β βββ QdrantVectorStore β Qdrant gRPC
βββ PgVectorRagProvider
βββ PgVectorStore β PostgreSQL/pgvector
IEmbeddingProvider
βββ OllamaEmbeddingProvider
The Knowledge Keeper is then attached to the council via WithKnowledgeKeeper(...) β see
the RAG example for a full working demo.
| Strategy | Flow | Use Case |
|---|---|---|
| StandardDebate | Initial β Critique β Improved β Verdict | General analysis |
| CritiqueDebate | Position β Attack β Defence β Judge | Hypothesis testing |
| ConsensusDebate | Perspectives β Common Ground β Consensus β Facilitator | Optimal solution search |
Each strategy is implemented as an IDebateStrategy β see
src/Delibera.Core/Debate/ for the full source.
| Pattern | Usage |
|---|---|
| Factory | ProviderFactory, VectorStoreFactory, CompressionFactory, Chairman |
| Strategy | IDebateStrategy, IContextCompressor |
| Builder | CouncilBuilder fluent API |
| Template Method | DebateScenario abstract base class |
| Cache | CompressionCache with SHA-256 keys |
| Observer | OnRoundCompleted event on CouncilExecutor |
Each deliberation can be exported as a single file or as three separate Markdown documents:
var result = await executor.ExecuteAsync();
// Save to 3 separate files
var (resultPath, statsPath, logsPath) = await result.SaveAllAsync("./output");
// Creates: debate_20260604_120000_result.md
// debate_20260604_120000_statistics.md
// debate_20260604_120000_logs.md
// Or save individually
await result.SaveToMarkdownAsync("result.md");
await result.SaveStatisticsAsync("statistics.md");
await result.SaveLogsAsync("logs.md");| File | Contents |
|---|---|
*_result.md |
Full deliberation transcript, rounds, and the Chairman's final verdict |
*_statistics.md |
Token usage statistics with per-round breakdown |
*_logs.md |
Execution logs (ExecutionLog) for Chairman, KK, compression & participants |
Every public async method in Delibera honors a CancellationToken cooperatively. A single
cancel signal aborts the entire pipeline β rounds, chairman synthesis, LLM calls, MCP tools,
RAG queries and even file writes β via OperationCanceledException.
using var cts = new CancellationTokenSource(TimeSpan.FromMinutes(5));
var result = await executor.ExecuteAsync(cts.Token);ASP.NET Core / Worker Services can link the token to IHostApplicationLifetime.ApplicationStopping
via the Delibera.Core.Extensions.CouncilExecutorLifetimeExtensions.ExecuteAsync(executor, lifetime, ct)
helper. Console apps can wire Console.CancelKeyPress to the same CancellationTokenSource.
The Delibera.ConsoleApp includes a --cancellation demo that exercises this end-to-end.
See the Cancellation section in Delibera.Core README
for the full table of cancellable operations and the linked-token pattern.
Numbers below come from real deliberations against Ollama Cloud, not estimates. Method, caveats and the full evidence are in docs/performance-measurements.md.
| What | Measured |
|---|---|
| Framework overhead | 0.0 s β time no model call explains, across every run |
| Debate wall time | 154β209 s for 4 rounds, 3 members + chairman |
| Throughput | 840β1,144 chars/s, flat β wall time tracks output volume, not Delibera |
| Round cost profile | Critique + refinement are 62β77% of a debate; round 1 is the cheapest |
| Context compression | 11.7β12.1% prompt tokens saved |
| Knowledge Keeper | 3 retrieval queries per debate, steady across topics; probe top score 0.738 |
| Operator (MCP) | 1β3 delegated tasks per debate, scaling with how much the question needs looking up |
| Cache hit | 0.0 s against 154β209 s uncached |
| Output balance | One verbose model produced 68.8% of all member text β roster composition mattered more than the framework |
Two honest caveats worth knowing before you copy a number:
- Compression saves ~12%, not the 30β70% this README used to claim. The old figure was never measured. v10.5.0 corrects it.
- A debate is not fast. 154β209 s is the honest cost. If that does not fit your budget, use two rounds, three members, or the cache.
The repository ships with Delibera.ConsoleApp, a runnable demo
project that exercises every feature. Run it from the repo root:
# Clone the repository
git clone https://github.com/techbuzzz/Delibera.git
cd Delibera/src/Delibera.ConsoleApp
# Run a specific example
dotnet run -- --di # Dependency Injection
dotnet run -- --separate-files # Save result.md, statistics.md, logs.md
dotnet run -- --compression # Context compression demo
dotnet run -- --multiprovider # Multi-provider (cloud + local) council
dotnet run -- --rag # Qdrant-backed RAG with Knowledge Keeper
dotnet run -- --pgvector # pgvector-backed RAG
dotnet run -- --operator # Operator role basics (MCP tools)
dotnet run -- --operator-mcp # π Operator with browser + Marp MCP servers
dotnet run -- --autochunking # π AutoChunking demo (large documents)
# Or run the full default demo (reads appsettings.json)
dotnet runThe console app reads appsettings.json which uses
the DeliberaApp section β by default it points at Ollama Cloud and shows you how to wire
multiple providers, RAG, and compression in one place.
git clone https://github.com/techbuzzz/Delibera.git
cd Delibera
# Build the entire solution
dotnet build --configuration Release
# Run the console demo
cd src/Delibera.ConsoleApp
dotnet runSpin up the API + Web UI + Redis + Qdrant + PostgreSQL/pgvector in one shot. (Ollama is intentionally not included β install it natively so the GPU driver is used directly.)
# From the repo root
docker compose up -d
# Or, with Ollama on GPU inside Docker:
docker compose --profile ollama up -dThen open http://localhost:3000 for the web UI and http://localhost:5200 for the API.
| Service | URL | Purpose |
|---|---|---|
| Web UI (Nuxt 4) | http://localhost:3000 |
Debates: create, live rounds, verdict |
| REST API | http://localhost:5200 |
REST endpoints under /api/v1 |
| MCP endpoint | http://localhost:5200/mcp |
Model Context Protocol tools |
| Qdrant (REST / gRPC) | localhost:6333 / :6334 |
Vector store (used by Delibera) |
| PostgreSQL | localhost:5432 |
pgvector RAG store |
| Redis | internal only | Debate state (opt-in) |
Default credentials: postgres / postgres, database council_vectors.
β οΈ Security β read this before changing a port binding.The Delibera API is unauthenticated and unthrottled. Anyone who can reach the port can start debates that spend real LLM credits on your provider account. Every published port is bound to
127.0.0.1for exactly this reason. Do not change those bindings to0.0.0.0unless you have put an authenticating reverse proxy in front first.
If you already have these services running natively, just skip
docker composeand point the console app at them βappsettings.jsondefaults tolocalhost.
curl -O https://raw.githubusercontent.com/techbuzzz/Delibera/main/deploy/docker-compose.hub.yml
docker compose -f docker-compose.hub.yml up -dPulls techbuzzz/delibera-server and techbuzzz/delibera-webui from Docker Hub. Set
DELIBERA_VERSION=10.5.3 to pin a specific release.
src/Delibera.WebUI is a Nuxt 4 application covering the debate workflow:
list, create, live SSE rounds, verdict with token stats, Markdown export, cancel.
Two things are worth knowing if you extend it:
- The browser never calls the API directly. Every request goes through a Nitro server-side
proxy (
/api/delibera/**), because the API registers no CORS policy and a cross-origin browser call would be blocked. SSE is streamed through, not buffered β the test suite asserts a chunk arrives before the upstream stream ends. VerdictDtois mostly a shell.DebateMapperpopulates onlyrecommendationandrawJson;confidence,riskLevel,risksandconditionsare never filled in. The structured output that does exist is template-specific and lives insideverdict.rawJson.
Run it locally against a host API:
cd src/Delibera.WebUI
npm ci
npm run dev # http://localhost:3000, proxies to http://localhost:5200Set Delibera:Redis:Enabled=true (or DELIBERA_REDIS_ENABLED=true) to have the server publish
round events to a Redis Stream instead of keeping them in process. That lets several API instances
serve the same debates, and enables the Redis result cache via CacheEnabled.
It is off by default. It is also not distributed turn execution β
DebateWorkerService is a documented no-op placeholder, so each debate still runs in the process
that accepted it. See src/Delibera.Redis/README.md.
# Run with Qdrant (Docker)
docker run -d -p 6333:6333 -p 6334:6334 qdrant/qdrant
# Run with pgvector (Docker)
docker run -d -p 5432:5432 -e POSTGRES_PASSWORD=postgres pgvector/pgvector:pg16
# Then enable the pgvector extension
docker exec -it <container> psql -U postgres -d council_vectors -c "CREATE EXTENSION IF NOT EXISTS vector;"| Package | Purpose |
|---|---|
OllamaSharp |
Ollama API client |
Qdrant.Client |
Qdrant vector DB gRPC client |
Npgsql |
PostgreSQL ADO.NET provider |
Pgvector |
pgvector type support for Npgsql |
ModelContextProtocol |
MCP client for the Operator role |
Microsoft.Extensions.AI |
Unified IChatClient / IEmbeddingGenerator AI abstractions & middleware |
Microsoft.Extensions.* |
Configuration, DI and Options |
StackExchange.Redis |
Redis client for distributed debates and caching |
Delibera integrates with Microsoft.Extensions.AI
(v10.7.0) β the standard .NET abstraction layer for generative-AI services. This lets you drive a
council with any backend that ships an IChatClient (OpenAI, Azure OpenAI, Ollama, Anthropic,
LM Studio / LocalAI / vLLM, β¦) and compose a middleware pipeline (function/tool invocation,
logging, caching, telemetry) β all without writing a provider per vendor.
| Type | Role |
|---|---|
ChatClientLLMProvider |
Adapts any IChatClient to Delibera's ILLMProvider (with streaming via ChatStreamAsync) |
EmbeddingGeneratorProvider |
Adapts any IEmbeddingGenerator<string, Embedding<float>> to IEmbeddingProvider for RAG |
MicrosoftAIExtensions |
Bridge helpers: AsLLMProvider(), AsEmbeddingProvider(), AsChatClient(), WithMiddleware() |
ProviderFactory.CreateFromChatClient(...) |
Build a cached provider straight from an IChatClient |
AddDeliberaChatClient(...) / AddDeliberaEmbeddingGenerator(...) |
Register the standard AI services in DI |
using Delibera.Core.Extensions;
using Microsoft.Extensions.AI;
// 1) Bring your own IChatClient. For OpenAI:
// dotnet add package Microsoft.Extensions.AI.OpenAI
IChatClient client = new OpenAI.Chat.ChatClient("gpt-4o-mini", apiKey).AsIChatClient();
// 2) (optional) compose middleware β function calling + logging
client = client.WithMiddleware(enableFunctionInvocation: true, loggerFactory);
// 3) expose it to Delibera
ILLMProvider provider = client.AsLLMProvider("OpenAI");
var executor = new CouncilBuilder()
.AddMember("gpt-4o-mini", provider, "Architect")
.AddMember("gpt-4o-mini", provider, "Skeptic")
.SetChairman(Chairman.CreateStandard("gpt-4o-mini", provider))
.WithStandardDebate()
.WithUserPrompt("Modular monolith or microservices for a 5-person team?")
.Build();Ollama works out of the box because OllamaSharp's OllamaApiClient natively implements
IChatClient and IEmbeddingGenerator:
using var ollama = new OllamaProvider("http://localhost:11434");
IChatClient chat = ollama.AsChatClient(); // ready for middleware
ILLMProvider provider = chat.AsLLMProvider("Ollama");
IEmbeddingProvider embeddings = ollama.AsEmbeddingGenerator().AsEmbeddingProvider("nomic-embed-text");await foreach (var chunk in provider.ChatStreamAsync("gpt-4o-mini", systemPrompt, userPrompt))
Console.Write(chunk);ChatStreamAsync is an additive default method on ILLMProvider β providers backed by an
IChatClient stream token-by-token, while older providers transparently fall back to a single call.
services.AddDeliberaChatClient(
sp => new OpenAI.Chat.ChatClient("gpt-4o-mini", apiKey)
.AsIChatClient()
.WithMiddleware(enableFunctionInvocation: true),
providerName: "OpenAI");
services.AddDeliberaEmbeddingGenerator(
sp => /* your IEmbeddingGenerator */,
modelName: "text-embedding-3-small");Try it:
dotnet run --project src/Delibera.ConsoleApp -- --msai
Delibera.Core
βββ Council/ β CouncilBuilder, CouncilExecutor, Chairman, KnowledgeKeeper, Operator
βββ Debate/ β StandardDebate, CritiqueDebate, ConsensusDebate
βββ Orchestration/ β IDebateOrchestrator, LocalDebateOrchestrator, DebateHandle, DebateRoundEvent
βββ Cache/ β IDebateCache, InMemoryDebateCache, FileDebateCache, CacheBehavior, DebateCacheKeyGenerator
βββ Compression/ β Semantic / Deduplication / Summarization / Hybrid
βββ Chunking/ β AutoChunker, AutoChunkingOrchestrator, AutoChunkingOptions
βββ Providers/
β βββ LLM/ β OllamaProvider, ChatClientLLMProvider, EmbeddingGeneratorProvider
β βββ RAG/ β QdrantRagProvider, PgVectorRagProvider
β βββ Mcp/ β McpClientAdapter (Operator β MCP servers)
βββ Extensions/ β MicrosoftAIExtensions (IChatClient β ILLMProvider bridges)
βββ DependencyInjection/ β AddDelibera() / AddDeliberaChatClient() + CouncilOptions
βββ Knowledge/ β MarkdownKnowledgeBase
βββ Models/ β CouncilMember, DebateResult, DebateRound, TokenStatistics, ...
βββ Interfaces/ β ILLMProvider, IRagProvider, IContextCompressor, IOperator, IMcpClient, ...
Delibera.Redis
βββ Orchestration/ β RedisDebateOrchestrator, DebateWorkerService, RedisOrchestratorOptions, RedisOrchestratorExtensions
βββ Cache/ β RedisDebateCache
Delibera.Server
βββ Endpoints/ β Minimal API endpoints (POST /api/debates, GET /stream, etc.)
βββ Services/ β DebateOrchestrationService, SseDebateStreamWriter
βββ Configuration/ β AddDeliberaServer(), DeliberaServerOptions
Contributions are welcome! Please read CONTRIBUTING.md for guidelines on development setup, coding standards, and the pull-request process.
Delibera is released under the MIT License.
Copyright Β© 2026 Delibera Project.
βοΈ Delibera β Thoughtful AI Decisions
Built with care for AI-powered collective intelligence