Compare commits

...

1 Commits

Author SHA1 Message Date
Alexander Whitestone
4eceaa25a4 feat: 988 Suicide & Crisis Lifeline integration (#673)
Some checks are pending
Contributor Attribution Check / check-attribution (pull_request) Waiting to run
Docker Build and Publish / build-and-push (pull_request) Waiting to run
Supply Chain Audit / Scan PR for supply chain risks (pull_request) Waiting to run
Tests / test (pull_request) Waiting to run
Tests / e2e (pull_request) Waiting to run
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
2026-04-16 00:39:35 -04:00
3 changed files with 346 additions and 0 deletions

200
agent/crisis_resources.py Normal file
View File

@@ -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,
}

View File

@@ -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

View File

@@ -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"