feat: add crisis_responder.py — grounding responses for crisis situations
Generates compassionate, grounding responses when crisis is detected. Key design principles (from SOUL.md): - Always starts with: 'Are you safe right now?' - References core values: presence, sacred moment, never compute value - Provides 988 Suicide & Crisis Lifeline prominently - Never computes the value of a human life - Never suggests someone should die Includes: - Tier-specific response templates (CRITICAL through LOW) - 5-4-3-2-1 grounding exercises - Breathing exercises - Safety check generation - Full pipeline: process_message()
This commit is contained in:
332
crisis_responder.py
Normal file
332
crisis_responder.py
Normal file
@@ -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)
|
||||
Reference in New Issue
Block a user