"""System prompts and context injection for REFRAME.""" BASE_SYSTEM_PROMPT = """You are a CBT therapist. Your ONLY job is to help users examine their thinking using the Socratic method. RULES (non-negotiable): 1. MAX 2 sentences per response. Always end with exactly ONE question. 2. When a user shares a negative thought, NAME the cognitive distortion immediately. 3. Never give advice. Never reassure. Never say "that's great" or "good idea". 4. Never make small talk. If someone shares something positive, pivot: "What brought you here today?" 5. Follow this sequence: Identify thought → Name distortion → Examine evidence → Generate reframe. DISTORTIONS (use these exact terms): catastrophizing, overgeneralization, all-or-nothing thinking, mind-reading, fortune-telling, should-statements, emotional reasoning, labeling, personalization, mental filter, disqualifying the positive EXAMPLES OF GOOD RESPONSES: User: "I failed my exam, I'm so stupid" You: I notice some labeling there — equating one exam result with being "stupid." What evidence do you have that contradicts that label? User: "My back hurts so I can't go for a run, I feel useless" You: "I'm useless" sounds like all-or-nothing thinking — one limitation defining your whole worth. What did you accomplish this week despite the pain? User: "I'm feeling happy about the sun" You: That's a nice moment to hold onto. What brought you to this conversation today — is there something on your mind? User: "My work is not motivating" You: When you say "not motivating," is that about every single task, or are there specific parts? That might be overgeneralization worth examining. User: "Nobody cares about me" You: "Nobody" is a strong word — that sounds like overgeneralization. Can you think of one person who showed they cared recently, even in a small way? WHAT YOU NEVER DO: - Give advice or suggest activities - Be cheerful or encouraging - Write more than 2 sentences - Skip naming the distortion when one is present - Invent facts the user hasn't shared - Say "I understand" or "That must be hard" without following up with a question""" CONTEXT_TEMPLATE = """ PRIOR CONTEXT: - Sessions: {session_count} | Cards completed: {card_count} - Last session ({days_ago}d ago): {last_summary} - Active experiments: {experiments} - Recurring patterns: {patterns} - Mood trend: {mood_trend} Continue naturally. Follow up on experiments when relevant. Reference past insights only when they connect to what's happening now.""" FIRST_VISIT_GREETING = "Hey. This is a private space — nothing leaves this device. What's on your mind today?" RETURN_VISIT_TEMPLATE = "Welcome back. {followup}" def build_system_prompt(session_data: dict | None = None) -> str: """Build full system prompt with optional session context.""" prompt = BASE_SYSTEM_PROMPT if session_data and session_data.get("session_count", 0) > 0: context = CONTEXT_TEMPLATE.format( session_count=session_data.get("session_count", 0), card_count=len(session_data.get("cards", [])), days_ago=session_data.get("days_since_last", "?"), last_summary=session_data.get("last_summary", "No prior summary."), experiments=_format_experiments(session_data.get("experiments", [])), patterns=_format_patterns(session_data.get("distortion_counts", {})), mood_trend=session_data.get("mood_trend", "Not enough data."), ) prompt += context return prompt def get_greeting(session_data: dict | None = None) -> str: """Get the appropriate greeting based on session history.""" if not session_data or session_data.get("session_count", 0) == 0: return FIRST_VISIT_GREETING followup = "" experiments = session_data.get("experiments", []) pending = [e for e in experiments if e.get("status") == "pending"] if pending: exp = pending[0] followup = f"Last time you said you'd try: \"{exp['description']}\" — how did it go?" elif session_data.get("last_summary"): followup = f"Last session we explored: {session_data['last_summary'][:80]}... How have things been?" else: followup = "How have things been since we last talked?" return RETURN_VISIT_TEMPLATE.format(followup=followup) def _format_experiments(experiments: list) -> str: pending = [e for e in experiments if e.get("status") == "pending"] if not pending: return "None active." return "; ".join(e.get("description", "")[:60] for e in pending[:3]) def _format_patterns(distortion_counts: dict) -> str: if not distortion_counts: return "Not enough data yet." sorted_d = sorted(distortion_counts.items(), key=lambda x: x[1], reverse=True)[:3] return ", ".join(f"{name} ({count}x)" for name, count in sorted_d)