"""The effective system prompts, as the process sees them. Prompts default to code (`app/prompts/defaults.py`); an admin may override any of them in `prompt_settings`, applied without a restart. This mirrors the LLM settings override pattern, with two simplifications: the reset target is the code default (prompts have no `.env` layer), and there is no bootstrap — a missing row simply means "use the default". `get_prompt` is called while rendering a prompt, so it reads a module-level cache rather than awaiting a query. The cache is filled at startup and refreshed on every admin write. Single-process by design (`--workers 1`); a multi-worker deployment would need a notification channel. """ import logging from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.models import PromptSetting from app.prompts.defaults import DEFAULTS logger = logging.getLogger("pablan.prompts") # Only holds the keys an admin has actually overridden. _config: dict[str, str] = {} async def load_config(db: AsyncSession) -> None: """Re-read every override row into the cache. Call at startup and after any admin write.""" rows = (await db.execute(select(PromptSetting))).scalars().all() _config.clear() _config.update({row.key: row.content for row in rows if row.key in DEFAULTS}) logger.info( "prompt settings loaded", extra={"event": "prompt_settings_loaded", "overridden": sorted(_config)}, ) def get_prompt(key: str) -> str: """The effective prompt: an admin override if present, else the code default. `key` must be a known prompt (a `KeyError` here is a programming error, not user input).""" return _config.get(key) or DEFAULTS[key] def is_overridden(key: str) -> bool: return key in _config def clear() -> None: """Drop the cache — used by tests between cases.""" _config.clear()