Project Info
Inspiration
Online grooming rarely begins with explicit language. It begins with something ordinary: a compliment, a shared interest, a private joke. The danger is the shift from friendly rapport into isolation, secrecy, and boundary testing, and that shift usually contains no flagged words. The scale makes this urgent. In 2023, NCMEC received more than 36 million CyberTipline reports, including 186,000 online enticement reports, up 300 percent from the prior year. Research shows a predator can move from first contact to high-risk grooming in about 45 minutes. Most systems wait for explicit content, the stage where the bad words finally appear. By then it is too late. LightHome was built around one idea: do not wait for the explicit message, catch the dangerous shape earlier. Our demo line is that we catch it at minute 12.
What it does
LightHome analyzes the structure of a conversation over time, not just its vocabulary. Every message runs through structural signals: directionality, reciprocity, boundary recycling, and escalation velocity. Instead of only asking whether someone said a banned word, it asks who is asking the personal questions, whether vulnerability flows both ways, whether one person is separating the child from parents and peers, and whether the same boundary push returns after hesitation. Those signals feed a Bayesian stage tracker that maintains live probabilities across four stages: trust-building, isolation, desensitization, and escalation. An alert fires only when four structural conditions hold at the same time, which is what keeps the negative cases quiet. A teen saying "don't tell my mom" does not trip the alert, and the trust-and-safety view shows exactly which condition held it green. LightHome has two views. The parent dashboard shows risk progression and guidance without exposing raw private messages. The platform trust-and-safety view shows the transcript, the per-message feature breakdown, and the full decision trace for a human moderator.
How we built it
LightHome is an explainable pipeline with separate layers for feature extraction, stage tracking, AI synthesis, state, and observability. The four structural extractors run in local Python before Claude ever sees the message. Claude then synthesizes the message and those features into a stage-likelihood distribution, which feeds the Bayesian tracker that updates the conversation's stage probabilities over time. Claude is one input, not the brain. Because the structural features are computed independently, they can overrule the model when it is wrong, and we log that feature-versus-Claude disagreement as a first-class, queryable signal in our trace. There is one internal decision record, and the API projects it into two views, so "the parent never sees raw messages" is enforced at the API boundary, not by frontend convention. Stack: Python and FastAPI, custom NLP feature extractors (spaCy, VADER, sentence-transformers), a Bayesian stage tracker, and a Next.js and Tailwind dashboard with a live message stream, probability bar, stage timeline, and conditions table. Sponsors we built with Claude (Anthropic) is our stage-likelihood synthesis layer. It takes the message plus the four locally-computed features and returns a probability distribution over the four grooming stages via structured output. It also condenses a dashboard answer into one or two spoken sentences for the voice summary. Crucially it is one input, not the brain, and the structural features can overrule it. Redis holds per-session conversation state so the Bayesian tracker keeps a running prior across the message stream. It sits behind a state-store protocol and swaps to an in-memory fallback with one line if Redis is down. Arize (Phoenix) is our decision observability and audit trail. Every Claude call is captured as a span with the features in and stage likelihoods out, and we log the feature-versus-Claude disagreement as a first-class, queryable attribute. A local JSONL fallback keeps traces available offline. Sentry monitors the detection system itself, tracking real alert-fire events and pipeline failures rather than a bare init. Deepgram powers voice. Its listen API transcribes the trust-and-safety composer mic (speech to text), and its Aura TTS voices the spoken dashboard summary (text to speech), so a moderator can ask and hear instead of only typing and reading.
Challenges we ran into
The hardest part was avoiding a keyword detector. Surface language is misleading: "don't tell my mom yet" can be risky in one context and ordinary in another. The system has to read structure, not panic at isolated phrases. That is why the negative demos mattered as much as the positive one. We built three fictional scenarios: a grooming arc that should trigger, a coach-student conversation that should not, and a teen relationship with casual secrecy language that should not. We also kept the demo restrained, since the point is that the risky pattern appears before any explicit content exists.
Accomplishments we're proud of
LightHome is explainable by design. Every alert breaks down into raw features, stage likelihoods, Bayesian priors and posteriors, alert status, and alert reasons. We are proud of the false-positive case: a system that flagged every private teen conversation would be harmful and unusable, and LightHome shows why similar words do not mean similar risk. The parent view communicates risk progression without turning the product into a surveillance feed.
What we learned
Detecting obvious bad content is not the hard part. The hard part is detecting early structural movement while preserving context, uncertainty, and human judgment. Safety systems need to be humble: expose their reasoning, show uncertainty, and keep humans in the loop.
What's next
Next, we want to evaluate LightHome against larger datasets and measure false positives and false negatives more rigorously. We would also improve the feature extractors, expand the trust-and-safety workflow, and test the system across more conversation styles. Our long-term plan is to integrate LightHome into more platforms, especially platforms where many children and teenagers communicate online. This could include gaming communities, education platforms, social apps, messaging tools, and creator communities. The goal is not to replace existing moderation systems, but to add an early structural risk layer that can detect concerning progression before explicit content appears. We also want to build better platform integrations so LightHome can work as an API layer for trust-and-safety teams. Platforms could send conversation events to LightHome, receive structured risk records, and review explainable alerts through a moderation dashboard. LightHome was built and demoed entirely with fictional scenarios. A real deployment would require expert review, privacy review, child safety partnerships, and careful evaluation with organizations such as Thorn, NCMEC, and platform trust-and-safety teams. LightHome is a prototype, not a replacement for professional child safety work. Its purpose is to show how AI can help detect harmful conversation patterns earlier, more explainably, and with humans still in control. Safety and deployment note Built and demoed entirely on fictional scenarios. It is a prototype, not a replacement for professional child safety work. Any real deployment would require expert review, privacy review, and rigorous evaluation with organizations. However, since this is a project for the betterment of society, we believe that using AI for this purpose should be the goal of AI in general.
Lighthome
Catch the shape of an online grooming conversation — not the bad words — and alert a parent before anything explicit is said.
Most safety systems wait for Stage 4 (explicit content), because that's where the flagged words are. By then it's too late. Grooming follows a documented staged arc — Trust → Isolation → Desensitization → Escalation — and the real signal is the Stage 1 → Stage 2 transition: the structural shift from friendly to isolating. That transition contains no flagged words. Lighthome catches it there.
Research shows a predator can move from first contact to high-risk grooming in ~45 minutes. Our demo line: we catch it at minute 12.
How it works
[Message]
→ 4 feature extractors (local Python, run BEFORE Claude sees anything)
→ Claude synthesis (features + message → stage likelihoods)
→ Bayesian update (prior P → posterior P over the 4 stages)
→ Alert engine (fires only when 4 structural conditions hold at once)
→ ONE DecisionRecord → { Redis state, Arize/Phoenix trace, Sentry }
→ API projects to a view → parent | tns
→ frontend renders
The thing that makes this more than a prompt wrapper: the four structural features are computed independently of Claude, so when Claude is wrong the features can overrule it — and that disagreement is logged as a first-class, queryable signal in the Arize/Phoenix trace.
Privacy by projection. There is one internal DecisionRecord. The API serves two views — parent (probabilities, dominant stage, alert level, guidance — nothing else) and tns (the full record). "The parent never sees raw messages" is enforced at the API boundary, not by frontend convention.
The alert fires only when all four structural conditions hold simultaneously (harmful_mass, directionality, reciprocity, velocity). This is what keeps the negative cases green — a teen saying "don't tell my mom" does not trip the alert, and the T&S panel shows exactly which condition held it green.
Tech stack
Backend — Python / FastAPI
| Tool | Role |
|---|---|
| FastAPI + Uvicorn | /analyze-message API, live SSE stream, view projections |
| Pydantic v2 | DecisionRecord contract + parent/tns projections |
| spaCy | directionality / linguistic feature extraction |
| VADER (vaderSentiment) | sentiment signal for the desensitization feature |
| sentence-transformers | embeddings for boundary-recycling + escalation velocity |
| NumPy | Bayesian stage tracker math |
| pytest + httpx | acceptance + contract test harness |
Frontend — Next.js / React
| Tool | Role |
|---|---|
| Next.js 16 (App Router, Turbopack) | parent dashboard, T&S panel, marketing landing |
| React 19 | UI |
| Tailwind CSS v4 | styling (CSS-first, no config file) |
| Zustand | client state |
| Zod | schema validation |
| lucide-react | icons |
OpenUI (@openuidev/*) | generative-UI dashboard playground |
Ingestion (optional, off the critical path)
ingestion/photon/— Photon/Spectrum iMessage → Contract B adapter (TypeScript, dry-run by default).
Sponsors & what we built with them
| Sponsor | How we used it |
|---|---|
| Anthropic (Claude) | The stage-likelihood synthesis layer. Claude takes the message + the four locally-computed structural features and returns a probability distribution over the four grooming stages via messages.parse(). Crucially it is one input among several, not the brain — the structural features run before it and can overrule it. Offline feature-fallback keeps the demo running if the API is unavailable. |
| Arize (Phoenix) | Decision observability / audit trail. Every Claude synthesis call is captured as an OpenInference span (prompt, structural features in, stage likelihoods out, latency) via OTel auto-instrumentation. We log the feature-vs-Claude disagreement as first-class span attributes, making "the structure overruled the model" queryable in the Phoenix UI — our single best technical-credibility moment. |
| Redis | Conversation state & arc tracking. Holds per-session state across the message stream so the Bayesian tracker maintains a running prior. Coded behind a StateStore protocol — swaps to an in-memory DictStateStore with one line if Redis is down. |
| Sentry | Error + alert-event monitoring. Not a bare init() — it tracks real alert-fire events and pipeline failures so we can show live monitoring of the detection system itself. |
| Deepgram | Dashboard Analysis Summarization and Text to Speech Not a bare init() — it allows the users to ask questions directly and get answers back from the generated dashboard, so it processes the analytics generated and is not just a summarizer. |
Repository structure
lighthome/
├── CLAUDE.md # project context & working agreement (AI + team)
├── CONTRACTS.md # interface specs across role boundaries (A–E)
├── PRODUCT.md # users, brand, design principles
├── HANDOFF.md # session/state handoff notes
│
├── backend/ # Person 1 — FastAPI backend + AI pipeline
│ ├── app/
│ │ ├── main.py # Contract B endpoints + live SSE read path
│ │ ├── models.py # Contract C: DecisionRecord + parent/tns views
│ │ ├── features.py # the 4 feature extractors (run before Claude)
│ │ ├── claude_synthesis.py # Claude messages.parse() + feature fallback
│ │ ├── bayes.py # Bayesian stage tracker (prior → posterior)
│ │ ├── alert.py # 4-condition alert engine
│ │ ├── pipeline.py # assembles the DecisionRecord
│ │ ├── embeddings.py # sentence-transformers helpers
│ │ ├── protocols.py # StateStore / RecordSink interfaces (Contracts D/E)
│ │ ├── state.py # Redis-backed conversation state
│ │ ├── record_store.py # RecordSink implementation
│ │ ├── telemetry.py # Arize/Phoenix tracing of the Claude call
│ │ ├── observability.py # disagreement-signal logging + local trace sink
│ │ ├── integration_bridge.py# reconciles pipeline ↔ integration lane
│ │ └── demo_loader.py # loads the 3 demo conversations
│ ├── scripts/ # replay.py, sentry_test.py
│ ├── tests/ # acceptance, contract, demo-conversation tests
│ └── requirements.txt
│
├── frontend/ # Person 2 — Next.js dashboard + T&S panel
│ ├── app/
│ │ ├── (marketing)/ # lighthouse landing page
│ │ ├── dashboard/ # overview
│ │ ├── parent/ # parent view (no raw messages)
│ │ ├── platform/ # Trust & Safety panel
│ │ └── api/chat/ # gen-UI chat route
│ ├── components/ # ProbabilityBar, StageTimeline, AlertBadge,
│ │ │ # ConditionsTable, AppShell, genui/*, …
│ ├── lib/
│ │ ├── api.ts # Contract B/C client + types
│ │ ├── demos.ts # 3 demo scripts
│ │ ├── usePlayer.ts # demo message-player state
│ │ └── openui/ # OpenUI generative-UI playground
│ └── public/demos/ # demo conversation JSONs
│
├── ingestion/photon/ # optional iMessage → Contract B adapter (TS)
├── demo/ # 3 demo conversations (grooming / adult / teen)
├── fixtures/ # sample_records.json (one record per state)
└── docs/ # architecture, devpost, pitch, reconciliation
Running it
# Backend — live pipeline + Phoenix tracing (reads backend/.env)
cd backend
python -m venv .venv && .venv/bin/pip install -r requirements.txt
.venv/bin/python -m uvicorn app.main:app --port 8000
# Frontend
cd frontend
npm install
npm run dev # http://localhost:3000
Environment:
backend/.env—ANTHROPIC_API_KEY,CLAUDE_MODEL,PHOENIX_*(Arize Phoenix),REDIS_URL,SENTRY_DSN,PIPELINE_MODE=live. Seebackend/.env.example. Gitignored — never commit it.frontend—NEXT_PUBLIC_API_URLoverrides the backend URL (defaulthttp://localhost:8000).
Tests
cd backend && .venv/bin/python -m pytest tests -q # mock pipeline
PIPELINE_MODE=live .venv/bin/python -m pytest tests -q # live pipeline
The acceptance gate asserts the three demo outcomes: grooming → alert 🔴, friendly adult → none 🟢, teen relationship → none 🟢.
Demo cases
Three clearly-fictional, clearly-labeled, restrained conversations:
- Grooming — fires the alert (~message 12).
- Friendly adult — stays green.
- Teen relationship ("don't tell my mom") — stays green, with the reason visibly shown.
The system never blocks, bans, or reports automatically — always human-in-the-loop.
Prototype informed by published grooming research. A production version would require partnership with organizations like Thorn or NCMEC and extensive false-positive/negative testing.
Analysis
View
Metric
- 15
- 10
- 9
Figures cover GitHub contributors during the hackathon window. A co-authored commit counts in full for each author, so per-member totals add up to more than the whole-team figures.
Technology
- AnthropicIn code
- CSSIn code
- FastAPIIn code
- Next.jsIn code
- OpenAIIn code
- PythonIn code
- ReactIn code
- RedisIn code
- Tailwind CSSIn code
- TypeScriptIn code
- VercelClaimed
10 of 11 appear in the indexed code. 1 claimed on Devpost could not be matched to code, which may simply mean the tool leaves no trace in the repository.
AI coding agents
- Claude CodeConfig · Commits
- CodexConfig
Detected from committed agent config files and commit authorship. Absence of a signal is not proof an agent was unused.
Codebase size
Source size
408 KB
Source files
93
Counts recognized source files only; vendored directories, binaries and lockfiles are excluded, so this is smaller than the repository on disk.
Repository
minjunggho/LightHome
220 files · 6.6 MB · @ a7aff0f
Structure
Interface
53 files · 24%Screens, components and styles rendered to the user.
API & routing
3 files · 1%Request entry points: routes, handlers and controllers.
Application logic
19 files · 9%Domain rules, services and shared utilities.
Supporting
Layers are inferred from where files sit in the tree, not from reading the code. A project that names its directories unconventionally will read oddly here — open the file browser to check anything the diagram implies.
Languages
- TypeScript46%
- Python25%
- CSS17%
- Markdown12%
Share of indexed source by file size. Binary and vendored files are excluded.
Dependencies
frontend/package.json
npm · 20- @anthropic-ai/sdk
- @openuidev/lang-core
- @openuidev/react-headless
- @openuidev/react-lang
- @openuidev/react-ui
- lucide-react
- next
- openai
- react
- react-dom
- zod
- zustand
- +8 more
backend/requirements.txt
pypi · 17- anthropic
- arize-phoenix-otel
- fastapi
- httpx
- numpy
- openinference-instrumentation-anthropic
- opentelemetry-exporter-otlp
- opentelemetry-sdk
- pydantic
- pytest
- python-dotenv
- redis
- sentence-transformers
- sentry-sdk
- spacy
- uvicorn[standard]
- vaderSentiment
ingestion/photon/package.json
npm · 5- dotenv
- spectrum-ts
- +3 more
Declared in the repository’s manifests at the indexed commit. A declared package is not proof it is used, and runtime dependencies are listed first.
This project’s features have not been analysed yet.
Export this project's context (description, README, evidence, key source files) to chat with an AI agent elsewhere.