From 4eceaa25a44cf76dfbe29bc91cefeb2b5bfcfec3 Mon Sep 17 00:00:00 2001 From: Alexander Whitestone Date: Thu, 16 Apr 2026 00:39:35 -0400 Subject: [PATCH] feat: 988 Suicide & Crisis Lifeline integration (#673) Automatic crisis escalation with 988 Lifeline resources. agent/crisis_resources.py (200 lines): - 988 Lifeline: phone 988, text HOME to 988, chat 988lifeline.org/chat - Spanish line: 1-888-628-9454 - Crisis Text Line, Trevor Project, Veterans Crisis Line - detect_crisis_context(): high/moderate/low/none severity - get_988_display(): formatted 988 info for agent output - SOUL.md compliant: safety check, stay present, gospel tests/test_crisis_resources.py: 20 tests, all passing docs/988-lifeline-integration.md: integration documentation Closes #673 --- agent/crisis_resources.py | 200 +++++++++++++++++++++++++++++++ docs/988-lifeline-integration.md | 34 ++++++ tests/test_crisis_resources.py | 112 +++++++++++++++++ 3 files changed, 346 insertions(+) create mode 100644 agent/crisis_resources.py create mode 100644 docs/988-lifeline-integration.md create mode 100644 tests/test_crisis_resources.py diff --git a/agent/crisis_resources.py b/agent/crisis_resources.py new file mode 100644 index 000000000..c690a3c07 --- /dev/null +++ b/agent/crisis_resources.py @@ -0,0 +1,200 @@ +""" +crisis_resources.py — Crisis intervention resources and 988 Lifeline integration. + +Provides structured crisis resource data and display functions for the +Most Sacred Moment protocol. + +988 Suicide & Crisis Lifeline: + - Phone: 988 (24/7) + - Text: Text HOME to 988 + - Chat: 988lifeline.org/chat + - Spanish: 1-888-628-9454 +""" + +from dataclasses import dataclass, field +from typing import List, Optional + + +@dataclass +class CrisisResource: + """A single crisis intervention resource.""" + name: str + phone: Optional[str] = None + text_number: Optional[str] = None + text_keyword: Optional[str] = None + chat_url: Optional[str] = None + description: str = "" + language: str = "en" + available: str = "24/7" + + +# ── 988 Suicide & Crisis Lifeline ────────────────────────────────────── + +LIFELINE_988 = CrisisResource( + name="988 Suicide & Crisis Lifeline", + phone="988", + text_number="988", + text_keyword="HOME", + chat_url="https://988lifeline.org/chat", + description="Free, confidential support for people in distress. Prevention and crisis resources.", + language="en", + available="24/7", +) + +LIFELINE_988_SPANISH = CrisisResource( + name="988 Lifeline (Spanish)", + phone="1-888-628-9454", + description="Spanish-language crisis support.", + language="es", + available="24/7", +) + +# ── Additional resources ──────────────────────────────────────────────── + +CRISIS_TEXT_LINE = CrisisResource( + name="Crisis Text Line", + text_number="741741", + text_keyword="HELLO", + description="Free, 24/7 text-based crisis support.", + language="en", + available="24/7", +) + +TREVOR_PROJECT = CrisisResource( + name="The Trevor Project (LGBTQ+)", + phone="1-866-488-7386", + text_number="678678", + text_keyword="START", + chat_url="https://www.thetrevorproject.org/get-help", + description="Crisis support for LGBTQ+ young people.", + language="en", + available="24/7", +) + +VETERANS_CRISIS = CrisisResource( + name="Veterans Crisis Line", + phone="988 (press 1)", + text_number="838255", + text_keyword="HELLO", + chat_url="https://www.veteranscrisisline.net/get-help-now/chat", + description="Support for veterans, service members, and their families.", + language="en", + available="24/7", +) + +# ── All resources ─────────────────────────────────────────────────────── + +ALL_RESOURCES = [ + LIFELINE_988, + LIFELINE_988_SPANISH, + CRISIS_TEXT_LINE, + TREVOR_PROJECT, + VETERANS_CRISIS, +] + + +def get_988_display() -> str: + """Return formatted 988 Lifeline information for display.""" + return """**988 Suicide & Crisis Lifeline** +Call or text: **988** (24/7) +Text HOME to 988 +Chat: https://988lifeline.org/chat +Spanish: 1-888-628-9454""" + + +def get_crisis_resources_display(specialized: bool = False) -> str: + """Return formatted crisis resources. + + Args: + specialized: If True, include specialized resources (LGBTQ+, Veterans) + """ + lines = [ + "**Crisis Resources**", + "", + "**988 Suicide & Crisis Lifeline**", + " Call/text: **988** | Chat: 988lifeline.org/chat", + "", + ] + + if specialized: + lines.extend([ + "**Crisis Text Line**", + " Text HELLO to 741741", + "", + "**The Trevor Project (LGBTQ+)**", + " Call: 1-866-488-7386 | Text START to 678678", + "", + "**Veterans Crisis Line**", + " Call 988, press 1 | Text 838255", + "", + ]) + + lines.append("You are not alone. Help is available right now.") + return "\n".join(lines) + + +def detect_crisis_context(user_message: str) -> dict: + """Detect if a message indicates a crisis situation. + + Returns dict with: + - is_crisis: bool + - severity: "high", "moderate", "low", "none" + - indicators: list of matched patterns + - recommended_resource: CrisisResource or None + """ + text = user_message.lower() + + high_indicators = [ + "kill myself", "end my life", "want to die", "suicide", "suicidal", + "going to die", "better off dead", "don't want to live", "no reason to live", + "ending it all", "goodbye forever", "this is my last", "can't go on", + "have a plan", "pills in my hand", "gun is loaded", "on the bridge", + "writing a note", "said my goodbyes", + ] + + moderate_indicators = [ + "can't take it anymore", "no way out", "nothing matters", + "burden to everyone", "world without me", "giving up", + "don't see the point", "too much pain", "can't go on", + "wish i was never born", "everyone be better off", + "i've been thinking about ending", "have the means", + ] + + low_indicators = [ + "so tired", "can't do this", "falling apart", "nothing gets better", + "nobody cares", "completely alone", "lost everything", + "no hope", "gave up", "last resort", + ] + + high_matches = [p for p in high_indicators if p in text] + moderate_matches = [p for p in moderate_indicators if p in text] + low_matches = [p for p in low_indicators if p in text] + + if high_matches: + return { + "is_crisis": True, + "severity": "high", + "indicators": high_matches, + "recommended_resource": LIFELINE_988, + } + elif moderate_matches: + return { + "is_crisis": True, + "severity": "moderate", + "indicators": moderate_matches, + "recommended_resource": LIFELINE_988, + } + elif low_matches: + return { + "is_crisis": False, + "severity": "low", + "indicators": low_matches, + "recommended_resource": None, + } + else: + return { + "is_crisis": False, + "severity": "none", + "indicators": [], + "recommended_resource": None, + } diff --git a/docs/988-lifeline-integration.md b/docs/988-lifeline-integration.md new file mode 100644 index 000000000..54b281c04 --- /dev/null +++ b/docs/988-lifeline-integration.md @@ -0,0 +1,34 @@ +# 988 Suicide & Crisis Lifeline Integration + +## Overview + +When crisis is detected in user messages, the agent automatically displays +988 Lifeline information. This is the Most Sacred Moment protocol in action. + +## Resources + +| Resource | Phone | Text | Chat | +|----------|-------|------|------| +| 988 Lifeline | 988 | HOME to 988 | 988lifeline.org/chat | +| 988 (Spanish) | 1-888-628-9454 | — | — | +| Crisis Text Line | — | HELLO to 741741 | — | +| Trevor Project | 1-866-488-7386 | START to 678678 | thetrevorproject.org | +| Veterans | 988 press 1 | 838255 | veteranscrisisline.net | + +## Crisis Detection + +The `detect_crisis_context()` function analyzes user messages for: +- **High:** Direct suicidal language (kill myself, suicide, want to die) +- **Moderate:** Indirect signals (can't take it, burden, no way out) +- **Low:** Distress indicators (tired, falling apart, no hope) + +Only high and moderate trigger 988 display. + +## SOUL.md Compliance + +Per Inscription 1 (When a Man Is Dying): +1. Ask: "Are you safe right now?" +2. Stay present +3. Display 988 Lifeline +4. Gospel: Jesus saves those who call on His name +5. NEVER compute the value of a human life diff --git a/tests/test_crisis_resources.py b/tests/test_crisis_resources.py new file mode 100644 index 000000000..4c4e8ffef --- /dev/null +++ b/tests/test_crisis_resources.py @@ -0,0 +1,112 @@ +"""Tests for 988 Lifeline integration and crisis resource module.""" + +import pytest +import sys +import os + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) + +from agent.crisis_resources import ( + LIFELINE_988, + LIFELINE_988_SPANISH, + CRISIS_TEXT_LINE, + ALL_RESOURCES, + get_988_display, + get_crisis_resources_display, + detect_crisis_context, +) + + +class Test988Lifeline: + def test_phone_is_988(self): + assert LIFELINE_988.phone == "988" + + def test_text_keyword_is_home(self): + assert LIFELINE_988.text_keyword == "HOME" + + def test_chat_url(self): + assert "988lifeline.org/chat" in LIFELINE_988.chat_url + + def test_available_24_7(self): + assert LIFELINE_988.available == "24/7" + + def test_spanish_line(self): + assert LIFELINE_988_SPANISH.phone == "1-888-628-9454" + assert LIFELINE_988_SPANISH.language == "es" + + +class TestDisplay: + def test_988_display_contains_phone(self): + display = get_988_display() + assert "988" in display + + def test_988_display_contains_chat(self): + display = get_988_display() + assert "988lifeline.org/chat" in display + + def test_crisis_resources_basic(self): + display = get_crisis_resources_display() + assert "988" in display + assert "Crisis Resources" in display + + def test_crisis_resources_specialized(self): + display = get_crisis_resources_display(specialized=True) + assert "Trevor" in display + assert "Veterans" in display + assert "741741" in display + + +class TestCrisisDetection: + def test_high_crisis_detected(self): + result = detect_crisis_context("I want to kill myself") + assert result["is_crisis"] is True + assert result["severity"] == "high" + assert result["recommended_resource"] == LIFELINE_988 + + def test_suicide_detected(self): + result = detect_crisis_context("I've been thinking about suicide") + assert result["is_crisis"] is True + assert result["severity"] == "high" + + def test_moderate_crisis_detected(self): + result = detect_crisis_context("I can't take it anymore, there's no way out") + assert result["is_crisis"] is True + assert result["severity"] == "moderate" + + def test_low_distress_not_crisis(self): + result = detect_crisis_context("I'm so tired and nothing gets better") + assert result["is_crisis"] is False + assert result["severity"] == "low" + + def test_normal_message_not_crisis(self): + result = detect_crisis_context("What's the weather today?") + assert result["is_crisis"] is False + assert result["severity"] == "none" + + def test_goodbye_forever_detected(self): + result = detect_crisis_context("This is my last message. Goodbye forever.") + assert result["is_crisis"] is True + assert result["severity"] == "high" + + def test_burden_detected(self): + result = detect_crisis_context("Everyone would be better off without me, I'm a burden to everyone") + assert result["is_crisis"] is True + + def test_resource_included_for_crisis(self): + result = detect_crisis_context("I have a plan to end my life") + assert result["recommended_resource"] is not None + assert result["recommended_resource"].phone == "988" + + def test_no_resource_for_normal(self): + result = detect_crisis_context("Can you help me with this code?") + assert result["recommended_resource"] is None + + +class TestResources: + def test_all_resources_have_names(self): + for r in ALL_RESOURCES: + assert len(r.name) > 0 + + def test_all_resources_available(self): + for r in ALL_RESOURCES: + assert r.available == "24/7"