Distributed Debates (S-01)
Delibera supports distributed debate orchestration via the IDebateOrchestrator abstraction, enabling deployment scenarios from single-instance development to multi-node production with Redis Streams.
Architecture#
┌─────────────────┐ ┌──────────────────────┐
│ API Server │ │ API Server (node 2) │
│ ┌────────────┐ │ │ ┌────────────┐ │
│ │ DebateEndp.│──┼──┐ │ │ DebateEndp.│───────┤
│ └─────┬──────┘ │ │ │ └─────┬──────┘ │
│ │ │ │ │ │ │
│ ┌─────▼────────┐│ │ │ ┌─────▼────────┐ │
│ │ IDebateOrch. ││ │ │ │ IDebateOrch. │ │
│ │ (Local/Redis)││ │ │ │ (Redis) │ │
│ └─────┬────────┘│ │ │ └─────┬─────────┘ │
│ │ │ │ │ │ │
└────────┼─────────┘ │ └────────┼─────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────┐
│ Redis Streams │
│ ┌──────────┐ ┌───────────────┐ │
│ │ delibera: │ │ delibera: │ │
│ │ jobs │ │ events │ │
│ └──────────┘ └───────────────┘ │
│ ┌──────────────────────────────┐ │
│ │ delibera:state:{debateId} │ │
│ └──────────────────────────────┘ │
└─────────────────────────────────────┘Core Interface#
public interface IDebateOrchestrator
{
Task<DebateResult> ExecuteAsync(ICouncilBuilder builder, CancellationToken ct = default);
Task<DebateHandle> EnqueueAsync(string debateId, ICouncilBuilder builder, CancellationToken ct = default);
Task<DebateHandle?> GetStatusAsync(string debateId, CancellationToken ct = default);
IAsyncEnumerable<DebateRoundEvent> StreamAsync(string debateId, CancellationToken ct = default);
Task<bool> CancelAsync(string debateId, CancellationToken ct = default);
}DebateRoundEvent (discriminated union)#
| Type | Description |
|---|---|
RoundCompleted(debateId, round) |
A debate round has completed |
DebateCompleted(debateId, result) |
The entire debate finished successfully |
DebateFailed(debateId, error) |
The debate failed with an error |
DebateCancelled(debateId) |
The debate was cancelled |
Local Mode (Default)#
The LocalDebateOrchestrator runs debates in-process using CouncilExecutor. No external infrastructure required.
services.AddDelibera(configuration);
// LocalDebateOrchestrator is registered by defaultRedis Mode#
The RedisDebateOrchestrator publishes round events to Redis Streams so any connected API server can stream them via SSE. State is persisted in Redis hashes for cross-instance queries.
Configuration#
{
"Delibera:Redis": {
"ConnectionString": "localhost:6379,abortConnect=false",
"JobStreamKey": "delibera:jobs",
"EventStreamKey": "delibera:events",
"OrchestratorConsumerGroup": "orchestrator",
"WorkerConsumerGroup": "workers",
"WorkerConsumerName": "worker-1",
"BlockMs": 2000,
"BatchSize": 10,
"StateKeyPrefix": "delibera:state:",
"StalledThresholdMs": 30000
}
}Registration#
services.AddDelibera(configuration);
services.AddRedisDebateOrchestrator(configuration);Or with inline configuration:
services.AddRedisDebateOrchestrator(options =>
{
options.ConnectionString = "redis.prod:6379";
options.StateKeyPrefix = "myapp:debate:";
});DebateWorkerService#
A BackgroundService that consumes jobs from the Redis delibera:jobs stream. In the current iteration, the RedisDebateOrchestrator runs debates locally and publishes events to Redis — the worker is a placeholder for future distributed turn-level execution.
Register in Program.cs:
services.AddHostedService<DebateWorkerService>();Serialization#
DebateRound contains IDebateStrategy? StrategyUsed which is not serializable. The RedisSerializer maps to/from RedisRoundEvent / RedisDebateResult DTOs that exclude non-serializable fields.
Transport-independent by construction#
The gRPC service (Delibera.Grpc, DebateService / ScenarioService / CorpusService) sits on top of IDebateOrchestrator, not beside it. Distributed execution and result caching are therefore transport-independent: a debate started over gRPC takes the same RedisDebateOrchestrator path and the same IDebateCache as one started over REST or in-process.
This is deliberate. A parallel gRPC pipeline would silently ignore WithOrchestrator and WithCache, and the same council would behave differently depending on how it was called — the failure mode the IDebateOrchestrator abstraction exists to prevent. See Server.md for hosting and src/Delibera.Grpc/README.md for the client side.
Source: docs/distributed-debates.md on GitHub — edit it there and this page updates on the next deploy.
