333 lines
11 KiB
Python
333 lines
11 KiB
Python
"""
|
|
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)
|