Prompt Manager
File:src/cofounder_agent/services/prompt_manager.py
Tested by: src/cofounder_agent/tests/unit/services/test_prompt_manager.py
Last reviewed: 2026-07-04
What it does
UnifiedPromptManager is the resolution chain for every LLM prompt the worker uses. After phase 2 of poindexter#47 retired the prompt_templates DB layer, and poindexter#825 made the shipped defaults authoritative, the chain is:
SKILL.mdpack defaults — the authoritative production prompts, loaded at process start fromskills/<pack>/<skill>/SKILL.md(the legacyprompts/*.yamlloader still runs first but the repo ships no YAML files). Edited in the repo via PR; mirrored into Langfuse for review bySyncPromptCatalogToLangfuseJob.- Langfuse production label — consulted FIRST only when
langfuse_prompt_overrides_enabled='true'(defaultfalse). Legacy override layer for deliberate live experiments; leaving it on re-introduces the masking trap (stale Langfuse copies shadowing SKILL.md edits).
app_settings) or the lookup fails (network/auth/missing prompt name), the call falls through to the pack defaults — operators without a Langfuse install always get working prompts.
Key methods
get_prompt(key, **kwargs) -> str— sync. Resolves and.format()s the template. RaisesKeyErrorif the key isn’t in any source.load_from_db(pool, *, site_config=None) -> int— async, called once at worker startup. Despite the name, this no longer loads fromprompt_templates(that table was dropped — phase 2 of poindexter#47). Today it capturessite_configon the instance + pre-fetches the encryptedlangfuse_secret_keywhile the async event loop is available, so the sync Langfuse-init path insideget_prompthas the secret in hand. Returns0(no rows loaded). Thepoolarg is kept on the signature so the dozen callers don’t churn._fetch_from_langfuse(key)/_init_langfuse_client()— internal. Lazy-initializes the Langfuse client on firstget_prompt. Readslangfuse_host,langfuse_public_key,langfuse_secret_keyfromapp_settingsvia the injected SiteConfig. When any setting is missing or empty, returnsNoneand logs a single info message — fallback to YAML is the documented OSS path.
DI seams
The module-levelservices.site_config.site_config singleton and the per-module set_site_config fan-out are both retired (#788 capstone). UnifiedPromptManager receives its SiteConfig as a constructor arg (DI) — the AppContainer composition root builds the manager with the process-wide SiteConfig. The actual loaded SiteConfig is captured on self._site_config and used by the lazy Langfuse init.
The pre-fetched secret cache (self._langfuse_secret_key) exists because _init_langfuse_client runs from the sync get_prompt path and can’t await site_config.get_secret(...). Pre-fetching during the async load_from_db step puts the value in hand by the time the lazy init runs.
Reads from / writes to
- Reads:
- YAML files under
prompts/(loaded once at__init__via_initialize_prompts). app_settingsvia SiteConfig:langfuse_host,langfuse_public_key,langfuse_secret_key.
- YAML files under
- Writes: nothing. Pure read path. Operator edits land in Langfuse via the UI, not through this module.
- External APIs: Langfuse via the SDK (
langfuse.Langfuse(host, public_key, secret_key)+client.get_prompt(name=key, label='production')).
Failure modes
- Prompt key not in any source —
KeyError(f"Prompt 'X' not found. Available: ..."). Lists available keys in the message so the typo is obvious. - Format kwargs missing —
KeyError(f"Prompt 'X' missing required variable: Y"). Tells the caller exactly which kwarg the template needed. - Langfuse unreachable — debug-level log + fall through to YAML. No operator alert; the YAML version is the documented fallback.
- Langfuse
auth_checkfails — same as above. The operator sees the “Langfuse not configured” info log on the next worker restart and can fix the keys via theactivate_langfuse.pyone-shot.
Activation flow (operator)
For an OSS install standing up Langfuse for the first time:scripts/import_prompts_to_langfuse.py, a manual bulk import — was retired by poindexter#825; the mirror job supersedes it and treats the script’s leftover imported_by versions as its own.)
See also
docs/architecture/prompt-management.md— the full architecture: SKILL.md packs, the read-only Langfuse mirror, and the masking-trap history.feedback_prompts_must_be_db_configurable(operator design note) — why every prompt routes through this manager.feedback_module_singleton_gotcha(operator design note) — DI gotcha that bit phase 1 of #47.scripts/activate_langfuse.py— operator activation tool (credentials only).services/migrations/0000_baseline.py— seeds placeholder rows for the three Langfuse credentials (originally migration 0153, folded into the baseline by the 2026-06-22 squash).