Overview
Version: v10.5.0 Scale & Reliability
Effort: S · 2–3 days
Branch: feature/s03-result-caching
Dependencies: S-01 (Redis infrastructure optional)
Add a pluggable caching layer for DebateResult to avoid re-running identical debates. Cache key is derived from the debate's content hash (question + members + strategy + knowledge). Three built-in implementations: in-memory, file system, Redis.
Core Interface
public interface IDebateCache
{
Task<DebateResult?> GetAsync(string cacheKey, CancellationToken ct = default);
Task SetAsync(string cacheKey, DebateResult result, TimeSpan? ttl = null, CancellationToken ct = default);
Task InvalidateAsync(string cacheKey, CancellationToken ct = default);
Task<bool> ExistsAsync(string cacheKey, CancellationToken ct = default);
}
Cache Key Generation
public static class DebateCacheKeyGenerator
{
// Deterministic SHA-256 hash of debate-defining inputs
public static string Generate(ScenarioRequest request)
{
var content = JsonSerializer.Serialize(new
{
request.Question,
Members = request.Members.OrderBy(m => m.Role),
request.Strategy,
request.MaxRounds,
request.SystemPrompt,
KnowledgeHash = request.KnowledgeText is null ? null
: Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(request.KnowledgeText)))
});
return Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(content)))[..16];
}
}
Implementations
InMemoryDebateCache
// Uses IMemoryCache with optional TTL
// Suitable for single-instance development and testing
public class InMemoryDebateCache : IDebateCache
{
public InMemoryDebateCache(IMemoryCache cache, TimeSpan? defaultTtl = null) { ... }
}
FileDebateCache
// Persists DebateResult as JSON files in a configured directory
// File name = cacheKey + ".cache.json"
// Suitable for local development, CLI benchmark caching
public class FileDebateCache : IDebateCache
{
public FileDebateCache(string cacheDirectory, TimeSpan? defaultTtl = null) { ... }
}
RedisDebateCache
// Uses StackExchange.Redis with optional TTL (SETEX)
// Suitable for distributed/production deployments
// Shares Redis connection with RedisDebateOrchestrator (S-01)
public class RedisDebateCache : IDebateCache
{
public RedisDebateCache(IConnectionMultiplexer redis, TimeSpan? defaultTtl = null) { ... }
}
CacheBehavior Enum
public enum CacheBehavior
{
/// Cache disabled entirely (default when no IDebateCache registered)
Disabled,
/// Return cached result if hit; execute and cache if miss
ReadWrite,
/// Return cached result if hit; do NOT cache new results
ReadOnly,
/// Always execute; always overwrite cache with new result
WriteThrough,
/// Bypass cache for this run; do not read or write
Bypass
}
CouncilBuilder Integration
// Register cache globally
services.AddDelibera()
.UseInMemoryCache(ttl: TimeSpan.FromHours(1))
// or
.UseFileCache(directory: "./debate-cache", ttl: TimeSpan.FromDays(7))
// or
.UseRedisCache(connectionString: "localhost:6379", ttl: TimeSpan.FromHours(24));
// Per-debate cache behavior
councilBuilder.WithCacheBehavior(CacheBehavior.ReadWrite);
councilBuilder.WithCacheBehavior(CacheBehavior.Bypass); // force fresh run
Execution Flow in CouncilExecutor
1. Generate cacheKey from ScenarioRequest
2. If behavior is ReadWrite or ReadOnly:
a. Call cache.GetAsync(cacheKey)
b. If hit → return cached DebateResult (add CacheHit=true flag)
3. Execute debate normally
4. If behavior is ReadWrite or WriteThrough:
a. Call cache.SetAsync(cacheKey, result, ttl)
5. Return result
DebateResult Cache Metadata
// DebateResult gains:
public bool CacheHit { get; init; } // true if returned from cache
public string? CacheKey { get; init; } // key used for this result
public DateTime? CachedAt { get; init; } // when it was originally cached
appsettings.json
"Delibera": {
"Cache": {
"Provider": "InMemory", // InMemory | File | Redis | None
"DefaultTtlMinutes": 60,
"FileDirectory": "./debate-cache",
"RedisTtlHours": 24
}
}
Tasks
Acceptance Criteria
Labels
enhancement, v10.5.0, reliability, caching
Overview
Version: v10.5.0 Scale & Reliability
Effort: S · 2–3 days
Branch:
feature/s03-result-cachingDependencies: S-01 (Redis infrastructure optional)
Add a pluggable caching layer for
DebateResultto avoid re-running identical debates. Cache key is derived from the debate's content hash (question + members + strategy + knowledge). Three built-in implementations: in-memory, file system, Redis.Core Interface
Cache Key Generation
Implementations
InMemoryDebateCache
FileDebateCache
RedisDebateCache
CacheBehavior Enum
CouncilBuilder Integration
Execution Flow in CouncilExecutor
DebateResult Cache Metadata
appsettings.json
Tasks
IDebateCacheinDelibera.Core.InterfacesDebateCacheKeyGenerator(SHA-256, deterministic)CacheBehaviorenumInMemoryDebateCacheusingIMemoryCacheFileDebateCache(JSON serialization, TTL via file metadata)RedisDebateCacheusing StackExchange.RedisCouncilExecutorCacheHit,CacheKey,CachedAttoDebateResultWithCacheBehavior(...)toICouncilBuilder+CouncilBuilderUseInMemoryCache,UseFileCache,UseRedisCacheappsettings.jsoninDelibera.Servercache_hitfield toDebateResponseDTOdebate.cache_hitcounter metricdocs/caching.mdAcceptance Criteria
ScenarioRequestgenerates identicalcacheKeyacross runsCacheBehavior.ReadWrite: second identical debate returns cached result immediatelyCacheBehavior.Bypass: always executes, never reads/writes cacheFileDebateCacheTTL expires correctly (stale files are not returned)RedisDebateCacheusesSETEXwith correct TTLDebateResult.CacheHit = truewhen returned from cacheIDebateCacheis not registered (default behavior unchanged)debate.cache_hitOpenTelemetry metric emittedLabels
enhancement,v10.5.0,reliability,caching