VB
Victor Buzin
Back to Blog
Delibera: a field note on the shape of the product
2026-10-217 min read

Delibera: a field note on the shape of the product

The first posts about Delibera described early releases: a library, a couple of debate strategies, LLM providers. Since then the project has gone through fourteen NuGet versions, and the shape of the product changed noticeably. This is a note from inside the work: what the product turned into, which decisions shaped it, and where it honestly stops.

This is not an announcement. It is a description of the state at a specific point: version 10.5.3, .NET 10, MIT licence.

What is wrong with a decision made in a chat

The problem the project grew out of is not that a model answers badly. The problem is that a decision made in a chat lives in a conversation thread. It has no owner, no version, no list of participants, and a month later nobody can reconstruct which options were considered and why this one won.

A council of several independent models solves part of that: a transcript appears, participants see each other's arguments, and the final verdict is written by a separate model that reads the whole analysis. But a council on its own is not a product. A product starts where versions, metrics, reproducible measurements and a clear line of responsibility appear.

Three constraints the shape grew from

An interface is what survives a change of model

Provider models change every month, and the decision logic must not change with them. Hence the split: access to LLMs lives behind ILLMProvider (and behind Microsoft.Extensions.AI), while debate strategies, the Knowledge Keeper, compression and the Operator work on top of interfaces and do not know who answers.

The side effect turned out better than we expected: when a provider changes, only the adapter changes. The debate strategy, the round protocol and the verdict format stay as they are — and that is precisely the layer where a product accumulates its logic.

Measure before you claim

The project rule is simple: a number appears on the site only after it has been measured. The project site renders Markdown straight from the repository, so a page physically cannot advertise a version, a feature or a number the code does not have.

That turned out to matter more than we expected. The "30–70% saved by context compression" claim was written into the README long before anyone measured it. When we finally measured, we found that the compressor was never invoked at all: ten runs reported exactly 0.00% at zero overhead. The docs now carry the measured figure — 11.7–12.1% — and the site will not let it be quietly replaced.

For a product whose value is that it substitutes for human judgement, the discipline of measurement is not a quality department. It is part of the definition.

The detailed numbers and the method live in the "Measured Behaviour" section of the project site, and the architectural decisions in the roadmap's "Measured Rounds": we named release 10.5.0 exactly that because running real debates and finding what breaks was the content of the release.

A version is a promise, not a number

We version the public API as NET_MAJOR.FEATURE.PATCH: no breaking changes inside the tenth line, and a break only at the boundary of a major feature. Within a line, what integrators depend on stays stable and what is inside changes.

For a .NET audience that is expected and almost trivial. For an AI framework it is not a detail: libraries in this class tend to ship a prerelease with "experimental API" and rewrite it the next month. We decided that a stable core must survive a change of model provider — and therefore, a change of the secondary features around it.

What the product contains today

The core is the Delibera.Core library on .NET 10 (C# 15 preview):

  • a council of several models, three round strategies (StandardDebate, CritiqueDebate, ConsensusDebate) plus your own through IDebateStrategy;
  • a Chairman — a separate model that synthesises the final verdict;
  • a Knowledge Keeper — targeted retrieval over a vector corpus (Qdrant or pgvector) as the debate proceeds;
  • an Operator — a micro-agent with access to MCP servers;
  • context compression between rounds (semantic, deduplication, summarization, hybrid);
  • AutoChunking — progressive distribution of large documents across rounds;
  • a result cache (InMemory, File, Redis) with a deterministic key;
  • round streaming over SSE, debate persistence and resume, structured output, OpenTelemetry observability;
  • a library of debate templates: ArchitectureReview, RiskAssessment, CodeReview, ProductDecision, SecurityAudit, DataArchitecture.

On top of the core sit Delibera.Server (ASP.NET Core Minimal API: REST + SSE) and Delibera.Redis (orchestration and caching in Redis). Both are published on NuGet.

There is a web front end on Nuxt 4: a list of debates, creation, live rounds over SSE, a verdict with token statistics, Markdown export, cancellation. It reaches the API only through a server-side Nitro proxy, because the server registers no CORS policy.

Tests: 620 passing (493 Core, 117 Server, 10 gRPC), none failing and none skipped, plus 34 web UI tests. The release build is clean under -warnaserror.

What it looks like in code — the smallest useful council:

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()
    .WithUserPrompt("Microservices vs Monolith for a 5-person startup?")
    .WithMaxRounds(4)
    .Build()
    .ExecuteAsync();

How to start

The packages are on NuGet:

dotnet add package Delibera.Core
dotnet add package Delibera.Server
dotnet add package Delibera.Redis

The images are on Docker Hub, linux/amd64 and linux/arm64:

docker pull techbuzzz/delibera-server:10.5.3
docker pull techbuzzz/delibera-webui:10.5.3

The full stack comes up with one command from a clone of the repository: docker compose up -d starts the server, the web UI, Redis, Qdrant, PostgreSQL with pgvector, and Ollama.

On security. The debate API is unauthenticated and unthrottled, and a debate spends real credits with your provider. That is why every published port binds to 127.0.0.1, and that binding is the enforcement, not decoration. Put an authenticating reverse proxy in front before exposing this stack to any network you do not control. The measurements quoted above were taken against Ollama Cloud — that is, against a provider somebody pays for.

Where the product's boundary runs

Some things in the product are not finished, and it is more honest to say so than to leave it in the README.

Redis does not give you distributed execution of rounds. The server has a Delibera:Redis:Enabled switch that registers the Redis orchestrator and, optionally, the result cache. But DebateWorkerService is a deliberate no-op: rounds are not distributed to workers. Everything Redis gives today is round events published to Streams.

The web UI is not deployed on GitHub Pages. The project site is static and has no server runtime, while the UI depends on the same-origin proxy at /api/delibera/**. A static export without that proxy would need a separate CORS-enabled API origin. The techbuzzz/delibera-webui image exists precisely to run next to Delibera.Server.

VerdictDto in the web UI is mostly a shell. The mapper fills in recommendation and rawJson; confidence, riskLevel, risks and conditions are never filled in. Structured output does exist, but it is template-specific and lives inside rawJson.

The Operator is not reachable through the API. The measurements showed operatorInteractions: 0 on every debate measured through the server: the server attaches a Knowledge Keeper, not an Operator. The MCP tooling works inside the library, but not through that surface.

Pruning orphaned vectors is not implemented. If the source document shrinks, the old points stay in the index: it needs a filter-delete on IVectorStore, which is a breaking interface change.

Round channels are not bounded. Channel.CreateUnbounded is still used in the local and Redis orchestrators: a consumer that stops draining will grow memory without limit.

Cache-hit duration semantics are off. On a cache hit, TotalDuration reports where the result came from, not how long the caller waited. That is a documented product decision rather than a bug, but it deserves care before you describe it as "0.0 s".

The gRPC packages are not in the GA publish matrix. Delibera.Grpc and Delibera.Grpc.Client exist, but they are not published as part of the release line.

Conclusion — what comes next

The roadmap is short, and it has places worth saying out loud.

Release 10.6.0 — "Harden the Edges": pruning orphaned vector points, bounded round channels, and a decision on the two product questions — WithCache on ICouncilBuilder, and cache-hit duration semantics.

11.0.0 — migration to .NET 11 once it leaves preview. That will be a breaking release, and we will move only what genuinely changed into it.

Until then we keep measuring: the next round of measurements covers verdict quality, and how often a council of several models actually produces a better result than one strong prompt. The protocol and the method are in the "Measured Behaviour" section of the project site.

In this series: Back — Multi-LLM deliberation in .NET: architecture and measurements

Sources

If you are deciding whether your team needs a council of models, start with measurements on your own questions rather than with the documentation. Discuss an implementation → techbuzzz.ru

Related Articles