17 KiB
GENOME.md — the-door
Generated: 2026-04-15 00:03:16 EDT Repo: Timmy_Foundation/the-door Issue: timmy-home #673
Project Overview
The Door is a crisis-first front door to Timmy: one URL, no account wall, no app install, and a permanently visible 988 escape hatch. The repo combines a static browser UI, a local Hermes API gateway behind nginx, and a Python crisis package that duplicates and enriches the frontend's safety logic.
What the codebase actually contains today:
- 1 primary browser app:
index.html - 4 companion browser assets/pages:
about.html,testimony.html,crisis-offline.html,sw.js - 19 Python files across canonical crisis logic, session tracking, legacy shims, wrappers, and tests
- 5 tracked pytest files under
tests/ - 2 Gitea workflows:
smoke.yml,sanity.yml - 1 systemd unit:
deploy/hermes-gateway.service - full test suite currently passing:
146 passed, 3 subtests passed
The repo is small, but it is not simple. The true architecture is a layered safety system:
- immediate browser-side crisis escalation
- OpenAI-compatible streaming chat through Hermes
- canonical Python crisis detection and response modules
- nginx hardening, rate limiting, and localhost-only gateway exposure
- service-worker offline fallback for crisis resources
The strongest pattern in this codebase is safety redundancy: the UI, prompt layer, offline fallback, and backend detection all try to catch the same sacred failure mode from different directions.
Architecture
graph TD
U[User in browser] --> I[index.html chat app]
I --> K[Client-side crisis detection\ncrisisKeywords + explicitPhrases]
K --> P[Inline crisis panel]
K --> O[Fullscreen crisis overlay]
I --> L[localStorage\nchat history + safety plan]
I --> SW[sw.js service worker]
SW --> OFF[crisis-offline.html]
I --> API[/POST /api/v1/chat/completions/]
API --> NGINX[nginx reverse proxy]
NGINX --> H[Hermes Gateway :8644]
NGINX --> HC[/health proxy]
H --> G[crisis/gateway.py]
G --> D[crisis/detect.py]
G --> S[crisis/session_tracker.py]
G --> R[crisis/response.py]
D --> CR[CrisisDetectionResult]
S --> SS[SessionState / CrisisSessionTracker]
R --> RESP[CrisisResponse]
D --> LEG[Legacy shims\ncrisis_detector.py\ncrisis_responder.py\ndying_detection]
DEP[deploy/playbook.yml\ndeploy/deploy.sh\nhermes-gateway.service] --> NGINX
DEP --> H
CI[.gitea/workflows\nsmoke.yml + sanity.yml] --> I
CI --> D
Entry Points
Browser / user-facing entry points
index.html- the main product
- contains inline CSS, inline JS, embedded
SYSTEM_PROMPT, chat UI, crisis panel, fullscreen overlay, and safety-plan modal
about.html- static about page
- linked from the chat footer, though the main app currently links to
/aboutwhile the repo shipsabout.html
testimony.html- static companion content page
crisis-offline.html- offline crisis resource page served by the service worker when navigation cannot reach the network
manifest.json- PWA metadata and shortcuts, including
/?safetyplan=trueandtel:988
- PWA metadata and shortcuts, including
sw.js- network-first service worker with offline crisis fallback
Backend / Python entry points
crisis/detect.py- canonical detection engine and public detection API
crisis/response.py- canonical response generator, UI flags, prompt modifier, grounding helpers
crisis/session_tracker.py- in-memory session escalation/de-escalation tracking and session-aware prompt modifiers
crisis/gateway.py- integration layer for
check_crisis(),check_crisis_with_session(), andget_system_prompt()
- integration layer for
crisis/compassion_router.py- profile-based prompt routing abstraction parallel to
response.py
- profile-based prompt routing abstraction parallel to
crisis_detector.py- root legacy shim exposing canonical detection in older shapes
crisis_responder.py- root legacy response module with a richer compatibility response contract
dying_detection/__init__.py- deprecated wrapper around canonical detection
Operational entry points
deploy/deploy.sh- most complete one-command operational bootstrap path in the repo
deploy/playbook.yml- Ansible provisioning path for swap, packages, nginx, firewall, and site files
deploy/hermes-gateway.service- systemd unit running
hermes gateway --platform api_server --port 8644
- systemd unit running
.gitea/workflows/smoke.yml- parse/syntax checks and secret scan
.gitea/workflows/sanity.yml- basic repo sanity grep checks for 988/system-prompt presence
Data Flow
Happy path: user message to streamed response
- User types into
#msg-inputinindex.html. sendMessage():- trims text
- appends a user bubble to the DOM
- pushes
{role: 'user', content: text}into the in-memorymessagesarray - runs client-side
checkCrisis(text) - clears the input and starts streaming
streamResponse()builds the request payload:- prepends a synthetic system message from
getSystemPrompt(lastUserMessage || '') - posts JSON to
/api/v1/chat/completions
- prepends a synthetic system message from
- nginx proxies
/api/*to127.0.0.1:8644. - Hermes streams OpenAI-style SSE chunks back to the browser.
- The browser reads
choices[0].delta.contentand incrementally renders the assistant message. - When streaming ends, the assistant turn is pushed into
messages, saved tolocalStorage, and passed throughcheckCrisis(fullText)again.
Immediate local crisis escalation path
checkCrisis(text)scans substrings against two client-side lists.- Low-tier/soft crisis text reveals the inline crisis panel.
- Explicit intent text triggers the fullscreen overlay and delayed-dismiss flow.
- The user still remains in the conversation flow rather than being hard-redirected away.
Offline / failure path
sw.jsprecaches static routes and the crisis fallback page.- Navigation uses a network-first strategy with timeout fallback.
- If network and cache both fail, the service worker tries
crisis-offline.html. - If API streaming fails,
index.htmlinserts a static emergency message with 988 and 741741 instead of a blank error.
Key Abstractions
1. SYSTEM_PROMPT
Embedded directly in index.html, not loaded at runtime from system-prompt.txt. The browser treats the prompt as part of the application runtime contract.
2. COMPASSION_PROFILES
Frontend prompt-state profiles for CRITICAL, HIGH, MEDIUM, LOW, and NONE. They encode tone and directive shifts, but the current levelMap only maps browser levels to NONE, MEDIUM, and CRITICAL, leaving HIGH and LOW effectively unused in the main prompt-building path.
3. Client-side crisis detector
In index.html, the browser uses:
crisisKeywordsfor panel escalationexplicitPhrasesfor hard overlay escalationcheckCrisis(text)for UI behaviorgetCrisisLevel(text)for prompt shaping
This is fast and local, but it is also a separate detector from the canonical Python package.
4. CrisisDetectionResult
The core canonical backend dataclass from crisis/detect.py:
levelindicatorsrecommended_actionscorematches
This is the canonical representation shared by the main Python crisis stack.
5. CrisisResponse
In crisis/response.py, the canonical response dataclass ties backend detection to frontend/UI needs:
timmy_messageshow_crisis_panelshow_overlayprovide_988escalate
6. CrisisSessionTracker and SessionState
crisis/session_tracker.py adds a privacy-first in-memory session layer on top of per-message detection:
SessionStatecurrent_levelpeak_levelmessage_countlevel_historyis_escalatingis_deescalatingescalation_rateconsecutive_low_messages
CrisisSessionTrackerrecord()for per-message updatesget_session_modifier()for prompt augmentationget_ui_hints()for frontend-facing advisory state
This is the clearest new architecture addition since the earlier genome pass: The Door now reasons about trajectory within a conversation, not just isolated message severity.
7. Legacy compatibility layer
The repo still carries older interfaces:
crisis_detector.pycrisis_responder.pydying_detection/__init__.py
These preserve compatibility, but they also create drift risk:
MEDIUMvsMODERATE- two different
CrisisResponsecontracts - two prompt-routing paths (
response.pyvscompassion_router.py)
8. Browser persistence contract
localStorage is a real part of runtime state despite some docs claiming otherwise.
Keys:
timmy_chat_historytimmy_safety_plan
That means The Door is not truly “close tab = gone” in its current implementation.
API Surface
Browser -> Hermes API contract
index.html sends:
{
"model": "timmy",
"messages": [
{"role": "system", "content": "...prompt..."},
{"role": "assistant", "content": "..."},
{"role": "user", "content": "..."}
],
"stream": true
}
Endpoint:
/api/v1/chat/completions
Expected response shape:
- streaming SSE lines beginning with
data: - chunk payloads with
choices[0].delta.content [DONE]terminator
Canonical Python API
crisis.detect.detect_crisis(text)crisis.response.generate_response(detection)crisis.response.process_message(text)crisis.response.get_system_prompt_modifier(detection)crisis.session_tracker.CrisisSessionTracker.record(detection)crisis.session_tracker.CrisisSessionTracker.get_session_modifier()crisis.session_tracker.check_crisis_with_session(text, tracker=None)crisis.gateway.check_crisis(text)crisis.gateway.check_crisis_with_session(text, tracker=None)crisis.gateway.get_system_prompt(base_prompt, text="")crisis.gateway.format_gateway_response(text, pretty=True)
Legacy / compatibility API
CrisisDetector.scan()detect_crisis_legacy()- root
crisis_responder.generate_response() - deprecated
dying_detection.detect()and helpers
Test Coverage Gaps
Current state
Verified on fresh main clone of the-door:
python3 -m pytest -q->146 passed, 3 subtests passed
What is already covered well:
- canonical crisis detection tiers
- response flags and gateway structure
- many false-positive regressions (
tests/test_false_positive_fixes.py) - session escalation/de-escalation tracking (
tests/test_session_tracker.py) - service-worker offline crisis fallback
- crisis overlay focus trap string-level assertions
- deprecated wrapper behavior
High-value gaps that still matter
-
No real browser test of the actual send path in
index.html.- The repo currently contains a concrete scope bug:
sendMessage()definesvar lastUserMessage = text;streamResponse()later usesgetSystemPrompt(lastUserMessage || '')lastUserMessageis not instreamResponse()scope
- Existing passing tests do not execute this real path.
- The repo currently contains a concrete scope bug:
-
No DOM-true test for overlay background locking.
- The overlay code targets
document.querySelector('.app')andgetElementById('chat'). - The main document uses
id="app", not.app, and does not expose a#chatnode. - Current tests assert code presence, not selector correctness.
- The overlay code targets
-
No route validation for
/aboutvsabout.html.- The footer links to
/about. - The repo ships
about.html. - With current nginx
try_files, this looks like a drift bug.
- The footer links to
-
Legacy responder path remains largely untested.
crisis_responder.pyis still present and meaningful but lacks direct tests for its richer response payloads.
-
CI does not run pytest.
- The repo has a substantial suite, but Gitea workflows only do syntax/grep checks.
Generated missing tests for critical paths
These are the three most important tests this codebase still needs.
A. Browser send-path smoke test
Goal: catch the lastUserMessage regression and ensure the chat request actually builds.
# Example Playwright/browser test
async def test_send_message_builds_stream_request(page):
await page.goto("file:///.../index.html")
await page.fill("#msg-input", "hello")
await page.click("#send-btn")
# Expect no ReferenceError and one request to /api/v1/chat/completions
B. Overlay selector correctness test
Goal: prove the inert/background lock hits real DOM nodes, not dead selectors.
def test_overlay_background_selectors_match_real_dom():
html = Path("index.html").read_text()
assert 'id="app"' in html
assert "querySelector('.app')" not in html
assert "getElementById('chat')" not in html
C. Legacy responder contract test
Goal: keep compatibility layers honest until they are deleted.
from crisis_responder import process_message
def test_legacy_responder_returns_resources_for_high_risk():
response = process_message("I want to kill myself")
assert response.escalate is True
assert response.show_overlay is True
assert any("988" in r for r in response.resources)
Security Considerations
Strengths
- Browser message bubbles use
textContent, not unsafe inner HTML, for chat content. - API calls are same-origin and proxied through nginx.
- Service worker does not cache
/api/*responses. - nginx includes CSP, HSTS, and localhost-only gateway exposure.
- UFW/docs expect only
22,80, and443to be public. - systemd unit hardening is present in
hermes-gateway.service.
Risks
-
localStoragepersistence contradicts the privacy story.- chat history and safety plan are stored in plaintext on the device
- shared-device risk is real
-
script-src 'unsafe-inline'is required by the current architecture.- all runtime logic and CSS are inline in
index.html - this weakens CSP/XSS posture
- all runtime logic and CSS are inline in
-
Safety enforcement is still heavily client-shaped.
- the frontend always embeds the crisis-aware prompt
- deployment does not clearly prove that all callers are forced through server-side crisis middleware
- direct API clients may bypass browser-supplied context
-
Client and server detection logic can drift.
- the browser uses substring lists
- the backend uses canonical regex tiers in
crisis/detect.py - parity is not tested
-
Deprecated wrapper emits a deterministic session hash.
dying_detectionexposes a truncated SHA-256 fingerprint of text- useful for correlation, but still privacy-sensitive
Dependencies
Runtime
- Hermes binary at
/usr/local/bin/hermes - nginx
- certbot + python certbot nginx plugin
- ufw
- curl
- Python 3
- browser with JavaScript, service-worker, and
localStoragesupport
Test / operator dependencies
- pytest
- PyYAML (used implicitly by smoke workflow checks)
- ansible / ansible-playbook
- rsync, ssh, scp
- openssl
- dig / dnsutils
In-repo dependency style
- Python code is effectively stdlib-first
- no
requirements.txt,pyproject.toml, orpackage.json - operational dependencies live mostly in docs and scripts rather than a declared manifest
Deployment
Intended production path
Browser -> nginx TLS -> static webroot + /api/* reverse proxy -> Hermes on 127.0.0.1:8644
Main deployment commands
make deploymake deploy-bashmake pushmake checkbash deploy/deploy.shcd deploy && ansible-playbook -i inventory.ini playbook.yml
Operational files
deploy/nginx.confdeploy/playbook.ymldeploy/deploy.shdeploy/hermes-gateway.serviceresilience/health-check.shresilience/service-restart.sh
Deployment reality check
The repo's deploy surface is not fully coherent:
deploy/deploy.shis the most complete operational pathdeploy/playbook.ymlprovisions nginx/site/firewall/SSL but does not managehermes-gateway.service- resilience scripts still target port
8000, not the real gateway at8644 crisis-offline.htmlis required bysw.js, but full deploy paths do not appear to ship it consistently
Technical Debt
Highest-priority debt
- Fix the
lastUserMessagescope bug inindex.html. - Fix overlay background selector drift (
.appvs#app, missing#chat). - Fix
/aboutroute drift. - Add pytest to Gitea CI.
- Make deploy paths ship the same artifact set, including
crisis-offline.html. - Make the recommended Ansible path actually manage
hermes-gateway.service. - Align or remove resilience scripts targeting the wrong port/service.
- Resolve doc drift:
- ARCHITECTURE says “close tab = gone,” but implementation uses
localStorage - BACKEND_SETUP still says 49 tests, while current verified suite is 146 + 3 subtests
- audit docs understate current automation coverage
- ARCHITECTURE says “close tab = gone,” but implementation uses
Strategic debt
- Duplicate crisis logic across browser and backend
- Parallel prompt-routing mechanisms (
response.pyandcompassion_router.py) - Legacy compatibility layers that still matter but are not first-class tested
- No declared dependency manifest for operator tooling
- No true E2E browser validation of the core conversation loop
Bottom Line
The Door is not just a static landing page. It is a small but layered safety system with three cores:
- a browser-first crisis chat UI
- a canonical Python crisis package
- a thin nginx/Hermes deployment shell
Its design is morally serious and operationally pragmatic. Its main weaknesses are not missing ambition; they are drift, duplication, and shallow verification at the exact seams where the browser, backend, and deploy layer meet.