// TEMPLATE: PromptRepository.cs
// PURPOSE: Loads agent system prompts from the agent_prompts table, with in-memory caching.
// READS STANDARD: ai-agents-prompt-sources
// PLACEHOLDERS:
// {{PROJECT_NAMESPACE}} — root namespace of the project
// LAST-VERIFIED: 2026-05-19
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
namespace {{PROJECT_NAMESPACE}}.AI;
// ---------------------------------------------------------------------------
// Entity — map this to the agent_prompts table (EF Core convention mapping).
// Alternatively, configure via Fluent API in your DbContext.OnModelCreating.
// ---------------------------------------------------------------------------
///
/// EF Core entity for the agent_prompts table.
/// Content is immutable once written — use INSERT + flip is_active for versioning.
///
public sealed class AgentPrompt
{
public Guid Id { get; init; } = Guid.CreateVersion7();
public required string AgentKey { get; init; }
public int Version { get; init; }
public required string Content { get; init; }
public bool IsActive { get; set; }
public DateTimeOffset CreatedAt { get; init; } = DateTimeOffset.UtcNow;
}
// ---------------------------------------------------------------------------
// Interface
// ---------------------------------------------------------------------------
///
/// Provides the active system-prompt content for a named agent.
///
public interface IPromptRepository
{
///
/// Returns the content of the currently active prompt for .
///
///
/// Thrown when no row with is_active = true exists for .
///
Task GetActivePromptAsync(string agentKey, CancellationToken ct = default);
}
// ---------------------------------------------------------------------------
// Implementation
// ---------------------------------------------------------------------------
// NOTE: Register in Program.cs (or a bootstrap extension):
// builder.Services.AddMemoryCache();
// builder.Services.AddScoped();
//
// The DbContext must expose: public DbSet AgentPrompts { get; set; }
// Replace YourDbContext below with the actual DbContext type of the project.
///
/// Reads the active agent prompt from the database with an in-memory cache
/// (default TTL: 5 minutes sliding). Cache invalidation is time-based;
/// for immediate invalidation after a prompt update call
/// IMemoryCache.Remove($"agent_prompt:{agentKey}").
///
public sealed class PromptRepository(YourDbContext db, IMemoryCache cache) : IPromptRepository
{
private static readonly TimeSpan CacheTtl = TimeSpan.FromMinutes(5);
public async Task GetActivePromptAsync(string agentKey, CancellationToken ct = default)
{
var cacheKey = $"agent_prompt:{agentKey}";
if (cache.TryGetValue(cacheKey, out string? cached) && cached is not null)
return cached;
var content = await db.AgentPrompts
.Where(p => p.AgentKey == agentKey && p.IsActive)
.Select(p => p.Content)
.FirstOrDefaultAsync(ct);
if (content is null)
throw new InvalidOperationException(
$"No active prompt found for agent '{agentKey}'. " +
"Insert a row in agent_prompts with is_active = true for this agent_key.");
cache.Set(cacheKey, content, new MemoryCacheEntryOptions
{
SlidingExpiration = CacheTtl
});
return content;
}
}