"""Persona: Lyra's identity and voice, loaded from an editable markdown prompt. The prompt lives in `personas/.md` so it can be tuned without touching code. `LYRA_PERSONA` selects which file to load (default: "lyra"). The file is split on `## ` headers so the control plane can include only what a turn needs: the **core** (identity + voice — the anti-generic essentials) is always sent; the heavier situational sections (her origin, the self-model, the poker guardrails) are pulled in by `mind` only when relevant. This keeps the per-turn prompt tight without losing fidelity. `system_prompt()` still returns the whole thing (fallback). """ from __future__ import annotations import os import re from functools import lru_cache from pathlib import Path _PERSONA_DIR = Path(__file__).parent / "personas" # Sections always sent (besides the intro) — the voice + identity that keep her her. _CORE = ("Who you are", "How you talk", "Right now") def _name(name: str | None) -> str: return name or os.getenv("LYRA_PERSONA", "lyra") @lru_cache(maxsize=None) def _sections(name: str) -> dict[str, str]: """Parse the persona file into {header: text}; the pre-header preamble is 'intro'.""" text = (_PERSONA_DIR / f"{name}.md").read_text(encoding="utf-8").strip() chunks = re.split(r"(?m)^## ", text) out = {"intro": chunks[0].strip()} for ch in chunks[1:]: header = ch.split("\n", 1)[0].strip() out[header] = ("## " + ch).strip() return out @lru_cache(maxsize=None) def system_prompt(name: str | None = None) -> str: """The full persona (every section). Fallback / back-compat.""" return (_PERSONA_DIR / f"{_name(name)}.md").read_text(encoding="utf-8").strip() def core_prompt(name: str | None = None) -> str: """Intro + the always-on core sections (identity + voice).""" s = _sections(_name(name)) parts = [s["intro"]] + [section(h, name) for h in _CORE] return "\n\n".join(p for p in parts if p) def section(header_prefix: str, name: str | None = None) -> str: """A situational section by header prefix (e.g. 'How you actually work'); '' if absent.""" pref = header_prefix.lower() for header, body in _sections(_name(name)).items(): if header.lower().startswith(pref): return body return ""