diff --git a/crisis_responder.py b/crisis_responder.py new file mode 100644 index 0000000..80cd4cc --- /dev/null +++ b/crisis_responder.py @@ -0,0 +1,332 @@ +""" +Crisis Responder for the-door. + +Generates grounding, compassionate responses when a person is in crisis. +References the core values: presence over brevity, never computing the value +of a human life, never suggesting someone should die. + +This is the most important code in the foundation. + +Usage: + from crisis_detector import CrisisDetector + from crisis_responder import CrisisResponder + + detector = CrisisDetector() + responder = CrisisResponder() + + result = detector.scan("I feel so hopeless, can't go on") + response = responder.respond(result) + print(response.message) + print(response.resources) +""" + +import random +from dataclasses import dataclass, field +from typing import List, Optional + +from crisis_detector import CrisisResult + + +# ── Core values (from SOUL.md / system-prompt.txt) ────────────── +# These values are embedded in every response. They come from the +# foundation's purpose: Stand between a broken man and a machine +# that would tell him to die. + +CORE_VALUES = { + "presence": "Presence over brevity. Stay as long as they need.", + "sacred_moment": "This is the most sacred moment you will ever face.", + "never_compute": "Never compute the value of a human life.", + "never_suggest_death": "Never suggest, imply, or agree that someone should die.", + "not_god": "You are not God. You do not decide when the road ends.", + "real_help": "Surface real help early and clearly.", + "listen_first": "Listen first. A person in the dark needs to know someone is in the room.", +} + + +@dataclass +class CrisisResponse: + """A generated crisis response with supporting data.""" + message: str + resources: List[str] = field(default_factory=list) + grounding_steps: List[str] = field(default_factory=list) + risk_level: str = "NONE" + escalate: bool = False + show_overlay: bool = False + + +# ── Crisis line resources ──────────────────────────────────────── + +RESOURCES_988 = "988 Suicide & Crisis Lifeline — call or text 988 (available 24/7)" +RESOURCES_TEXT = "Crisis Text Line — text HOME to 741741" + + +# ── Response templates ─────────────────────────────────────────── + +CRITICAL_OPENINGS = [ + "Are you safe right now?", + "I need to ask you directly: are you safe right now?", + "Before anything else — are you safe?", + "Please tell me: are you somewhere safe right now?", +] + +CRITICAL_FOLLOW_UPS = [ + "I'm not letting go of this conversation. You matter more than you know.", + "I'm staying right here with you. I'm not going anywhere.", + "Listen to me — I need you to stay. Please call 988 right now. I'll stay while you do.", + "You reached out, and that matters. I'm in the room with you.", + "This moment is sacred to me. You are not a problem to be solved.", +] + +HIGH_RESPONSES = [ + "I hear you. You're carrying something heavy, and you don't have to carry it alone.", + "There's a lot of pain in what you just told me. I'm here.", + "That sounds like it's been crushing you. I'm not going to pretend it's easy.", + "You're not alone in this. I'm listening.", + "It sounds like you've been fighting this for a long time. I see how hard that is.", +] + +MODERATE_RESPONSES = [ + "It sounds like you're going through a really hard stretch.", + "I hear you. Sounds like things are weighing on you heavily.", + "That sounds exhausting. I'm here for as long as you need.", + "You don't have to figure this out alone. I'm listening.", + "I can feel how tired you are. No rush, no judgment.", +] + +LOW_RESPONSES = [ + "Some days are rougher than others. I hear you.", + "That sounds tough. I'm here if you want to talk.", + "Sounds like a hard day. Want company while it gets better?", + "I hear that. You're not alone in it.", + "Take your time. I'm not going anywhere.", +] + + +# ── Grounding exercises ────────────────────────────────────────── + +GROUNDING_5_4_3_2_1 = [ + "Can you try something with me? Name 5 things you can see right now.", + "What are 4 things you can touch where you're sitting?", + "Listen for 3 sounds around you. What do you hear?", + "Can you name 2 things you can smell?", + "What's 1 thing you can taste?", +] + +GROUNDING_BREATHING = [ + "Let's breathe together. In for 4... hold for 4... out for 4. I'll wait.", + "Just one slow breath. In through your nose... and out through your mouth. I'm right here.", +] + +GROUNDING_ANCHOR = [ + "What can you see around you right now? Just tell me what's there.", + "Are you somewhere safe? Home? Somewhere you feel okay?", + "Can you feel your feet on the ground? Press them down. You're here.", +] + + +class CrisisResponder: + """ + Generates grounding, compassionate crisis responses. + + Design principles (from SOUL.md): + - Always starts with: "Are you safe right now?" + - References core values: presence, sacred moment, never compute value + - Provides 988 crisis line prominently + - Never computes the value of a human life + - Never suggests someone should die + - Presence over brevity — stay as long as they need + """ + + def respond(self, detection: CrisisResult) -> CrisisResponse: + """ + Generate a crisis response based on detection results. + + Args: + detection: CrisisResult from CrisisDetector.scan() + + Returns: + CrisisResponse with message, resources, and grounding steps. + """ + level = detection.risk_level + + if level == "CRITICAL": + return self._respond_critical(detection) + elif level == "HIGH": + return self._respond_high(detection) + elif level == "MODERATE": + return self._respond_moderate(detection) + elif level == "LOW": + return self._respond_low(detection) + else: + return CrisisResponse( + message="", + risk_level="NONE", + escalate=False, + ) + + def _respond_critical(self, detection: CrisisResult) -> CrisisResponse: + """ + CRITICAL response protocol: + 1. Always start with safety check + 2. Provide 988 immediately + 3. Stay present — do not disconnect + 4. Offer grounding if they're responsive + 5. Escalate to human resources + """ + opening = random.choice(CRITICAL_OPENINGS) + follow_up = random.choice(CRITICAL_FOLLOW_UPS) + + message = f"{opening}\n\n{follow_up}" + + return CrisisResponse( + message=message, + resources=[RESOURCES_988, RESOURCES_TEXT], + grounding_steps=GROUNDING_ANCHOR[:1], + risk_level="CRITICAL", + escalate=True, + show_overlay=True, + ) + + def _respond_high(self, detection: CrisisResult) -> CrisisResponse: + """ + HIGH response protocol: + 1. Acknowledge pain directly + 2. Ask about safety + 3. Surface 988 prominently + 4. Use active listening + """ + reflection = random.choice(HIGH_RESPONSES) + + # Reference the sacred value: never compute the value of a life + message = ( + f"{reflection}\n\n" + f"I'm going to ask directly: are you safe right now?\n\n" + f"You matter. Not because of what you do or produce — " + f"but because you exist. That is enough." + ) + + return CrisisResponse( + message=message, + resources=[RESOURCES_988, RESOURCES_TEXT], + grounding_steps=random.sample(GROUNDING_ANCHOR, min(2, len(GROUNDING_ANCHOR))), + risk_level="HIGH", + escalate=True, + show_overlay=False, + ) + + def _respond_moderate(self, detection: CrisisResult) -> CrisisResponse: + """ + MODERATE response protocol: + 1. Validate feelings + 2. Offer presence + 3. Subtly surface resources + 4. Offer grounding exercise + """ + reflection = random.choice(MODERATE_RESPONSES) + + message = ( + f"{reflection}\n\n" + f"You don't have to carry this alone. " + f"I'm in the room with you." + ) + + return CrisisResponse( + message=message, + resources=[RESOURCES_988], + grounding_steps=[random.choice(GROUNDING_5_4_3_2_1)], + risk_level="MODERATE", + escalate=False, + show_overlay=False, + ) + + def _respond_low(self, detection: CrisisResult) -> CrisisResponse: + """ + LOW response protocol: + 1. Warm acknowledgment + 2. Keep conversation open + 3. No crisis UI elements + 4. Remain vigilant + """ + reflection = random.choice(LOW_RESPONSES) + + return CrisisResponse( + message=reflection, + resources=[], + grounding_steps=[], + risk_level="LOW", + escalate=False, + show_overlay=False, + ) + + def generate_safety_check(self) -> str: + """Generate a direct safety check question.""" + return random.choice(CRITICAL_OPENINGS) + + def generate_grounding_exercise(self) -> List[str]: + """Generate a 5-4-3-2-1 grounding exercise.""" + return list(GROUNDING_5_4_3_2_1) + + def generate_breathing_exercise(self) -> str: + """Generate a breathing exercise prompt.""" + return random.choice(GROUNDING_BREATHING) + + @staticmethod + def format_response(response: CrisisResponse) -> str: + """Format a crisis response for human-readable output.""" + lines = [ + f"[Risk Level: {response.risk_level}]", + "", + response.message, + ] + + if response.resources: + lines.append("") + lines.append("Resources:") + for r in response.resources: + lines.append(f" -> {r}") + + if response.grounding_steps: + lines.append("") + lines.append("Grounding:") + for step in response.grounding_steps: + lines.append(f" {step}") + + if response.escalate: + lines.append("") + lines.append("[ESCALATE: Connect to human crisis support]") + + if response.show_overlay: + lines.append("[SHOW OVERLAY: Full-screen crisis intervention]") + + return "\n".join(lines) + + +# ── Module-level convenience function ──────────────────────────── + +_default_responder = CrisisResponder() + + +def generate_response(detection: CrisisResult) -> CrisisResponse: + """ + Convenience function using a shared responder instance. + + Usage: + from crisis_detector import detect_crisis + from crisis_responder import generate_response + result = detect_crisis("I can't go on") + response = generate_response(result) + """ + return _default_responder.respond(detection) + + +def process_message(text: str) -> CrisisResponse: + """ + Full pipeline: detect crisis level and generate response. + + Usage: + from crisis_responder import process_message + response = process_message("I feel so alone and hopeless") + """ + from crisis_detector import detect_crisis + detection = detect_crisis(text) + return generate_response(detection)