Live Platform URL: (https://holmes-teal.vercel.app)
📊 Presentation: View on Canva
Holmes is a production-style verification platform for journalists, trust-and-safety teams, investigators, and moderation backends that need to triage suspicious text, URLs, images, and video quickly. It combines asynchronous tool intelligence, adversarial multi-agent reasoning, and deterministic caching to produce explainable verdicts with bounded latency and controlled API spend. Instead of a single opaque LLM answer, Holmes persists auditable evidence and debate traces in PostgreSQL and exposes them through a FastAPI API and Next.js frontend.
Misinformation and synthetic media now scale faster than manual review teams, and single-signal detectors fail when attackers vary payload type (text, URL, deepfake image/video). Most pipelines either overfit one modality or return non-explainable scores that are hard to operationalize. Holmes is different because it fuses heterogeneous signals, runs adversarial reasoning, and returns a concise user-facing verdict plus structured evidence for downstream systems.
┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ CLIENT LAYER │
│ Next.js 14 frontend (frontend/) │
│ - Auth, verify, upload-file, pricing, history, admin dashboard │
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
│ HTTPS / JSON / JWT
▼
┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ FASTAPI BACKEND (app/main.py) │
│ /api/v1 router composition (app/api/router.py) │
│ │
│ Routers: │
│ • /auth (register/login/me) │
│ • /upload (verify + verify-file) │
│ • /history (user verification history) │
│ • /admin (dashboard + user management) │
│ • /stripe (checkout + webhook + unsubscribe) │
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────── Tier Switch ───────────────────────────────────────────────────┐
│ upload_router.verify_content(): │
│ if current_user.is_premium or current_user.is_admin → WorkflowManager (premium) │
│ else → FreeWorkflowManager (free) │
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
│ │
│ │
▼ ▼
┌──────────────────────────────────────────────┐ ┌──────────────────────────────────────────────┐
│ FREE PIPELINE (text/url) │ │ PREMIUM PIPELINE │
│ Zenserp + single Groq verdict call │ │ 1) Redis cache check │
│ (no image/video/audio) │ │ 2) asyncio.gather tool fan-out │
└──────────────────────────────────────────────┘ │ 3) URL source intel (Ninja, conditional) │
│ 4) DebateManager (parallel defense/prosecution)
│ 5) JudgeAgent verdict + heuristic fallback │
│ 6) Persist + cache write │
└──────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────── Tool Intelligence Layer ───────────────────────────────────────┐
│ sightengine_tool.py zenserp_tool.py virustotal_tool.py ninja_tool.py │
│ hf_text_tool.py bitmind_image_tool.py bitmind_video_tool.py │
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────── Agent Layer ───────────────────────────────────────────────────┐
│ prosecution_agent.py defense_agent.py judge_agent.py (+ agent_utils.py evidence summarization) │
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
│
┌────────────────────────────────────────────────────────┴────────────────┐
▼ ▼
┌──────────────────────────────────────────────┐ ┌───────────────────────────────────┐
│ Redis cache (CacheManager) │ │ Neon PostgreSQL │
│ key: verification:{content_type}:{content} │ │ users / verification_history /tasks│
│ (+ :b64:{sha256} for uploaded bytes) │ │ SQLAlchemy async ORM │
│ TTL: 300s │ │ persistent audit trail │
└──────────────────────────────────────────────┘ └───────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Stripe Flow │
│ create-checkout-session → Stripe Checkout → /api/v1/stripe/webhook → User.is_premium toggle │
│ customer.subscription.deleted / invoice.payment_failed → downgrade (except admin users) │
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Background Worker │
│ Celery app (app/worker/background_tasks.py) with Redis broker/backend; │
│ process_verification task currently scaffolded for queued async expansion. │
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
Free tier supports only ContentType.TEXT and ContentType.URL (ALLOWED_FREE_CONTENT_TYPES in FreeWorkflowManager). The pipeline runs Zenserp web-presence lookup and then a single Groq call (_groq_analyze) with a strict JSON-only prompt. This path is cheap and fast because it avoids multi-tool fan-out and multi-agent debate. Output is still persisted as VerificationHistory with verdict, confidence, and free-tier details including rationale.
Step 1 — Cache Check (Redis)
WorkflowManager._build_cache_key()uses deterministic keys:- text/url:
verification:{content_type}:{content} - uploaded bytes:
verification:{content_type}:{content}:b64:{sha256(content_b64)}
- text/url:
- TTL is set via
CacheManager.set_json(..., ttl_seconds=300). - Cache hit short-circuits all external APIs and agent calls.
Step 2 — Parallel Tool Execution (asyncio.gather)
WorkflowManager.run_verification()launches 6 tool coroutines concurrently:sightengine,zenserp,virustotal,hf_text,bitmind_image,bitmind_video.- Each tool self-selects by
content_typeand returnsstatus in {ok, skipped, degraded, error}. - Conditional activation by modality:
- Text: HuggingFace RoBERTa (
HFTextTool) + Zenserp - URL: VirusTotal + Zenserp + SightEngine (URL-based visual check where applicable)
- Image: BitMind Image + SightEngine (+ Zenserp only if routed via text/url pathways)
- Video: BitMind Video + SightEngine
- Text: HuggingFace RoBERTa (
- URL also gets API Ninjas source intelligence as an additional step (below).
- Net effect: parallel fan-out compresses end-to-end latency versus serial external calls.
Step 3 — Confidence Gate (typosquatting + domain signals)
- URL workflow calls
NinjaTool.analyze_source()for WHOIS/IP intelligence. agent_utils.detect_typosquatting()performs leet-speak normalization (LEET_MAP) and known-brand proximity checks (KNOWN_BRANDS).- Signals injected into downstream reasoning include:
- Typosquatting warnings
domain_age_days(new domain risk)- WHOIS availability/risk flags (
whois_unavailable,new_domain)
Step 4 — Parallel Agent Debate (asyncio.gather)
DebateManager.evaluate()executes defense and prosecution in parallel.- Agents do not consume each other’s outputs before writing their own positions.
- Both sides receive normalized plain-language evidence via
agent_utils.summarize_tool_findings()(no raw JSON payloads, no provider names, skipped/error states filtered out).
Step 5 — Judge Verdict
JudgeAgent.decide()reads both analyst positions plus evidence summary.- Prompt guidance is conditionally injected (
ai_guidance,url_guidance,text_guidance), including:- AI detection score ≥ 75% → critical manipulation guidance
- Typosquatting warnings → strong deception guidance
- Clean VirusTotal profile (many harmless, zero malicious) → legitimacy signal
- Very new domains (
domain_age_days <= 30) → elevated risk guidance
- Returns structured JSON-like verdict:
label,confidence,rationale. - If parsing or LLM output quality fails,
_fallback_from_signals()computes heuristic label/confidence.
Step 6 — Persistence + Cache Write
- Result is stored in
verification_historyvia_store_history(). - Response payload is cached in Redis for 300 seconds.
- API returns a full
VerificationResponsemapped fromVerificationHistory.
-
SightEngine — genai/deepfake visual analysis
SightengineToolcallshttps://api.sightengine.com/1.0/check.jsonwithmodels=genai,deepfake(ordeepfakefor video). It provides visual synthetic/manipulation evidence from public media URLs and attempts redirect resolution via_resolve_final_url(). It activates primarily forimage,video, and URL-backed media checks; if credentials are absent it returnsstatus="degraded", and if content is unsupported/unreachable it returnsstatus="skipped"with explicit summary. -
Zenserp — web presence and source verification
ZenserpToolcallshttps://app.zenserp.com/api/v2/searchand returnsorganic_resultscounts plus raw search metadata. It provides corroboration/absence signals (presence across multiple sources vs zero discoverability). It activates only fortextandurl. Missing API key yieldsdegraded; HTTP failures yielderror. -
VirusTotal — domain reputation (72+ vendor-style consensus output)
VirusTotalToolcallshttps://www.virustotal.com/api/v3/domains/{domain}and surfaceslast_analysis_stats(malicious,suspicious,harmless,undetected). It provides domain risk/cleanliness signals and activates only forurl. Missing key degrades gracefully; non-URL input is explicitly skipped. -
API Ninjas (Ninja) — WHOIS domain age + risk flags
NinjaToolcalls API Ninjas/whoisand/iplookup, extractsdomain_created_at, computesdomain_age_days, and emits risk flags (new_domain,whois_unavailable). It activates for URL source analysis (analyze_source) after core tool fan-out in premium flow. Missing key returnsdegradedrather than raising. -
HuggingFace RoBERTa — AI-generated text detection
HFTextToolusesInferenceClient.text_classification()with modelopenai-community/roberta-base-openai-detector, producing normalizedlabel_scores(fake,real). It activates only fortext, and can optionally enrich output with Gemini explanations. IfHF_TOKENis missing, it degrades or falls back to Gemini-only explanation mode when configured. -
BitMind Image — image deepfake detection (rich mode)
BitmindImageToolcallshttps://api.bitmind.ai/oracle/v1/34/detect-imagewith{"rich": true}and supports directcontent_b64or downloaded URL bytes. It provides synthetic-image probability plus optional Gemini explanation. It activates only forimage; missing key degrades, wrong modality skips, and runtime exceptions returnerror. -
BitMind Video — video deepfake detection
BitmindVideoToolcallshttps://api.bitmind.ai/detect-videowith multipart video bytes and returns confidence-oriented detection JSON. It provides temporal/deepfake risk evidence forvideoonly. Missing key degrades, unsupported content skips, and failures are contained aserror.
Holmes uses adversarial reasoning because a single LLM completion is prone to anchoring and overconfident hallucination, while two constrained positions plus a judge improve calibration and auditability. DebateManager.evaluate() runs defense and prosecution concurrently (asyncio.gather) so neither agent conditions on the other’s narrative, reducing first-answer bias.
agent_utils.summarize_tool_findings() is a key abstraction layer: it filters out skipped/degraded/error tool states, removes provider-level implementation details, and converts raw signals into plain-language evidence bullets. Agents therefore reason over stable semantics instead of raw JSON schema noise.
DefenseAgent contains a hard surrender policy via AI_DETECTION_SURRENDER_THRESHOLD = 0.75; when max AI score exceeds this threshold, it explicitly concedes strong synthetic evidence instead of manufacturing a weak authenticity argument. JudgeAgent then applies additional content-aware prompt injections (ai_guidance, url_guidance, text_guidance) and, if the LLM output is malformed or inconclusive, falls back to _fallback_from_signals() deterministic scoring.
Usermodel includesis_premiumandis_adminbooleans.- API routing split is a single branch in
upload_router.verify_content(): premium/admin →WorkflowManager; free users →FreeWorkflowManager. POST /api/v1/stripe/create-checkout-sessioncreates Stripe subscription sessions (or instant upgrade in simulation mode).POST /api/v1/stripe/webhookupgrades users on successful subscription/payment events by settingis_premium=True.- Unsubscribe/downgrade paths set
is_premium=Falsefor non-admin users (/stripe/unsubscribeand cancellation/payment-failure events). - Admin users always retain premium capabilities (
is_adminbypass in upload and unsubscribe logic). - Free tier is intentionally constrained to text+URL to control external API spend and reserve high-cost deepfake analysis for subscribed users.
Holmes uses Redis via CacheManager as a read-through/write-through acceleration layer on verification outputs.
- Deterministic key format:
verification:{content_type}:{content}(or...:b64:{sha256}for uploaded bytes). - TTL is 300 seconds to balance freshness (rapidly changing URLs/content) against repeated-query cost reduction.
- Cache hit path: returns immediately from cached verdict payload and avoids all third-party calls and debate inference.
- Cache miss path: runs full premium pipeline, persists DB record, then writes cached payload.
CacheManageris dependency-injected inWorkflowManager.__init__(cache: CacheManager | None = None)and defaults to Redis fromsettings.redis_urlwhen omitted.
Base prefix: /api/v1
| Method | Path | Auth | Description | Request | Response |
|---|---|---|---|---|---|
| POST | /auth/register |
No | Create user account | { email, password } |
UserRead (id,email,is_active,is_admin,is_premium,created_at) |
| POST | /auth/login |
No | Authenticate and return JWT | { email, password } |
Token (access_token, token_type, is_admin, is_premium) |
| GET | /auth/me |
Bearer | Return current profile + tier flags | None | { id,email,is_admin,is_premium,is_active,created_at } |
| Method | Path | Auth | Description | Request | Response |
|---|---|---|---|---|---|
| POST | /upload/verify |
Bearer | Verify text/url/image/video/audio reference | VerificationRequest { content_type, content, content_b64? } |
VerificationResponse |
| POST | /upload/verify-file |
Bearer (premium/admin) | Upload image/video/audio file then verify | multipart/form-data: content_type + file |
VerificationResponse |
| Method | Path | Auth | Description | Request | Response |
|---|---|---|---|---|---|
| GET | /history |
Bearer | List current user’s latest 100 verifications | None | VerificationResponse[] sorted by created_at desc |
| Method | Path | Auth | Description | Request | Response |
|---|---|---|---|---|---|
| GET | /admin/dashboard |
Admin Bearer | Aggregate totals across users/verifications/tasks | None | DashboardResponse { total_users,total_verifications,total_tasks } |
| GET | /admin/users |
Admin Bearer | List users for admin control | None | AdminUsersListResponse |
| POST | /admin/users |
Admin Bearer | Create user as admin | AdminUserCreateRequest |
AdminUserResponse |
| PATCH | /admin/users/{user_id} |
Admin Bearer | Toggle user flags | AdminUserUpdateRequest |
AdminUserResponse |
| DELETE | /admin/users/{user_id} |
Admin Bearer | Delete user (self-delete blocked) | None | { status, id } |
| Method | Path | Auth | Description | Request | Response |
|---|---|---|---|---|---|
| POST | /stripe/create-checkout-session |
Bearer | Create Stripe subscription checkout session | None | { checkout_url } |
| POST | /stripe/webhook |
Stripe signature header | Process Stripe lifecycle events and sync tier flags | Raw webhook payload | { status: "ok" } |
| POST | /stripe/unsubscribe |
Bearer | Downgrade current user (admins remain premium) | None | { status, is_premium, message? } |
┌──────────────────────────────────────────────────────────────────────────────┐
│ users │
├───────────────────────┬───────────────────────────────┬──────────────────────┤
│ column │ type │ notes │
├───────────────────────┼───────────────────────────────┼──────────────────────┤
│ id │ UUID (PK) │ default uuid4 │
│ email │ VARCHAR(320) │ unique, indexed │
│ hashed_password │ VARCHAR(255) │ required │
│ is_active │ BOOLEAN │ default true │
│ is_admin │ BOOLEAN │ default false │
│ is_premium │ BOOLEAN │ default false │
│ created_at │ TIMESTAMPTZ │ server default now() │
│ updated_at │ TIMESTAMPTZ │ onupdate now() │
└───────────────────────┴───────────────────────────────┴──────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│ verification_history │
├───────────────────────┬───────────────────────────────┬──────────────────────┤
│ column │ type │ notes │
├───────────────────────┼───────────────────────────────┼──────────────────────┤
│ id │ UUID (PK) │ default uuid4 │
│ user_id │ UUID (FK → users.id) │ indexed, CASCADE │
│ content_type │ ENUM(content_type) │ text/image/video/audio/url │
│ input_reference │ TEXT │ required │
│ verdict │ VARCHAR(64) │ required │
│ confidence │ FLOAT │ required │
│ details │ JSON │ tool + debate payload │
│ created_at │ TIMESTAMPTZ │ server default now() │
└───────────────────────┴───────────────────────────────┴──────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│ tasks │
├───────────────────────┬───────────────────────────────┬──────────────────────┤
│ column │ type │ notes │
├───────────────────────┼───────────────────────────────┼──────────────────────┤
│ id │ UUID (PK) │ default uuid4 │
│ user_id │ UUID (FK → users.id) │ nullable, SET NULL │
│ task_type │ VARCHAR(64) │ required │
│ status │ ENUM(task_status) │ pending/running/... │
│ progress │ INTEGER │ default 0 │
│ payload │ JSON │ required │
│ result │ JSON │ nullable │
│ error_message │ TEXT │ nullable │
│ created_at │ TIMESTAMPTZ │ server default now() │
│ updated_at │ TIMESTAMPTZ │ onupdate now() │
└───────────────────────┴───────────────────────────────┴──────────────────────┘
Relationships:
- `users 1 ── * verification_history`
- `users 1 ── * tasks`
Indexes:
- `users.email` (unique + index)
- `verification_history.user_id` (index)
- `tasks.user_id` (index)
- composite `ix_tasks_status_created_at(status, created_at)`
| Technology | Purpose |
|---|---|
| Core Framework | |
| FastAPI | API framework and dependency injection |
Uvicorn (uvicorn[standard]) |
ASGI server |
Pydantic v2 + pydantic-settings |
Request/response validation and env config |
| HTTPX | Async HTTP client for external APIs |
| Python 3.14 | Runtime |
| Next.js 14 + React 18 + TypeScript | Frontend client |
| Database | |
| SQLAlchemy 2 (async ORM) | Data access layer |
| asyncpg | PostgreSQL async driver |
| Alembic | Schema migration support |
| Neon PostgreSQL | Serverless primary relational store |
| Caching / Queueing | |
Redis (redis.asyncio) |
Verification cache |
| Celery | Background task orchestration scaffold |
| AI / ML | |
| Groq Chat Completions | Defense/prosecution/judge reasoning + free-tier verdict |
Hugging Face Hub (InferenceClient) |
RoBERTa text detector inference |
Google Generative AI (google-generativeai) |
Explanations for HF/BitMind outputs |
| External APIs | |
| SightEngine API | Image/video genAI + deepfake checks |
| Zenserp API | Web search corroboration |
| VirusTotal API | Domain reputation intelligence |
| API Ninjas WHOIS/IP APIs | Domain age + source risk flags |
| BitMind APIs | Image and video deepfake detection |
| Auth | |
python-jose[cryptography] |
JWT signing/verification |
passlib[bcrypt] + bcrypt |
Password hashing and verification |
email-validator |
Email validation in schemas |
| Payments | |
| Stripe Python SDK | Checkout + webhook subscription lifecycle |
| Testing | |
| Pytest + pytest-asyncio | Async integration/service tests |
Holmes uses integration-heavy backend tests with selective monkey-patching for deterministic service behavior.
-
tests/conftest.py- Initializes real schema via
init_db(). - Provides
AsyncClientagainst the ASGI app. - Uses real async DB sessions (not mocked).
- Cleans test users and seeded verification rows after each test.
- Initializes real schema via
-
tests/test_api_auth_admin.py- Covers register/login JWT flow.
- Validates admin authorization boundaries for
/admin/dashboard.
-
tests/test_workflow_manager.py- Validates
WorkflowManagerorchestration behavior: cache hit/miss, URL-only Ninja usage, tool fan-out, debate integration. - Uses monkey-patching (
monkeypatch.setattr+ async fakes) to isolate orchestration logic while preserving async control flow. - Includes modality guard tests for tool self-selection and
content_b64paths.
- Validates
-
tests/test_tier_and_stripe.py- Verifies tier flags in auth responses and premium gating for file upload.
- Validates Stripe endpoints fail gracefully when unconfigured.
- Validates simulation mode upgrade path.
-
tests/test_seed.py- Ensures seeding creates baseline admin and starter history records.
-
tests/live/- Real third-party integration tests (
@pytest.mark.live,@pytest.mark.integration). - Run only when
RUN_LIVE_TESTS=1and keys are present.
- Real third-party integration tests (
Run commands:
# fast local suites (skip live providers)
pytest tests/ -m "not live" -v
# focused suites
pytest tests/test_api_auth_admin.py -v
pytest tests/test_workflow_manager.py -v
pytest tests/test_tier_and_stripe.py -v
pytest tests/test_seed.py -v
# live provider checks (cost + network dependent)
RUN_LIVE_TESTS=1 pytest tests/live -vCurrent coverage focus is API auth/admin/tier controls, workflow orchestration, and tool-routing behavior. Intentionally not deeply tested yet: frontend UI logic, full Stripe production webhook signature permutations, and Celery worker execution beyond scaffold behavior.
-
Clone
git clone <your-repo-url> cd holmes
-
Create virtualenv
python3.14 -m venv env source env/bin/activate -
Install backend dependencies
pip install -r backend/requirements.txt
-
Create environment file
cp .env.example .env
Fill in required keys (see tables below).
-
Environment variables Use the
Environment Variablessection in this README for each variable, purpose, and source. -
Start Redis (Docker)
docker run --name holmes-redis -p 6379:6379 -d redis:7
-
Initialize database schema
cd backend python -c "import asyncio; from app.db.database import init_db; asyncio.run(init_db())"
-
Seed initial data
python seed.py
-
Start backend
uvicorn app.main:app --reload
-
Start frontend
cd ../frontend npm install npm run dev -
Run tests
cd ../backend pytest tests/ -m "not live" -v
| Variable | Required | Description | Where to get it |
|---|---|---|---|
APP_NAME |
No | FastAPI app title | Local config |
ENVIRONMENT |
No | Runtime environment name (development, etc.) |
Local config |
DEBUG |
No | Enable debug logging/behavior | Local config |
API_V1_PREFIX |
No | API prefix (default /api/v1) |
Local config |
ALLOWED_ORIGINS |
No | CORS origin list | Local config |
SECRET_KEY |
Yes | JWT signing secret | Generate securely (openssl rand -base64 32) |
JWT_ALGORITHM |
No | JWT algorithm (default HS256) |
Local config |
ACCESS_TOKEN_EXPIRE_MINUTES |
No | JWT access token TTL | Local config |
DATABASE_URL |
Yes | Async PostgreSQL DSN (Neon recommended) | Neon project dashboard |
REDIS_URL |
Yes | Redis DSN for cache | Local Redis / Upstash |
CELERY_BROKER_URL |
No | Celery broker URL (falls back to REDIS_URL) |
Redis/Upstash |
CELERY_RESULT_BACKEND |
No | Celery result backend URL (falls back to REDIS_URL) |
Redis/Upstash |
GROQ_API_KEY |
Yes for LLM verdicts | Groq API key | Groq console |
GROQ_BASE_URL |
No | Groq API base URL | Groq docs |
GROQ_DEFAULT_MODEL |
No | Preferred Groq model hint | Groq docs |
SIGHTENGINE_API_USER |
Optional (required for SightEngine) | SightEngine account user id | SightEngine dashboard |
SIGHTENGINE_API_SECRET |
Optional (required for SightEngine) | SightEngine API secret | SightEngine dashboard |
ZENSERP_API_KEY |
Optional (required for Zenserp) | Zenserp search API key | Zenserp dashboard |
VIRUSTOTAL_API_KEY |
Optional (required for VirusTotal) | VirusTotal API key | VirusTotal account |
NINJA_API_KEY |
Optional (required for WHOIS/IP intel) | API Ninjas key | API Ninjas dashboard |
HF_TOKEN |
Optional (required for RoBERTa detection) | HuggingFace token | Hugging Face settings |
BITMIND_API_KEY |
Optional (required for image/video deepfake APIs) | BitMind key | BitMind dashboard |
GEMINI_API_KEY |
Optional | Gemini key for explanation generation | Google AI Studio |
STRIPE_SECRET_KEY |
Optional (required for real checkout) | Stripe secret key | Stripe dashboard |
STRIPE_PRICE_ID |
Optional (required for real checkout) | Stripe subscription price id | Stripe products/pricing |
STRIPE_WEBHOOK_SECRET |
Optional (required for webhook verify) | Stripe webhook signing secret | Stripe webhook settings |
STRIPE_SUCCESS_URL |
No | Redirect URL after successful checkout | Frontend URL |
STRIPE_CANCEL_URL |
No | Redirect URL after canceled checkout | Frontend URL |
STRIPE_SIMULATION_MODE |
No | Simulate premium upgrade without live Stripe (true/false) |
Local testing |
DEFAULT_ADMIN_EMAIL |
No | Seeded initial admin email | Local config |
DEFAULT_ADMIN_PASSWORD |
No | Seeded initial admin password | Local config |
- Holmes is already deployed in a live environment.
- Live platform URL: holmes-teal.vercel.app
- Production data/services: Neon PostgreSQL, Redis cache, and Stripe webhook integration are wired through environment variables in the backend settings model.
Holmes ships with a Chrome extension (ai-detector-extension/) that lets users run URL, page-media, image, and video checks directly from a popup UI or right-click context menu, then view confidence/explainability and scan history without leaving the browser; the extension connects to Holmes by posting detections to ${backendBase}/api/detect/{type} (default base is holmes-teal.vercel.app/ and can be overridden in extension Settings, with optional Authorization / X-API-Key headers), installation is chrome://extensions → enable Developer mode → Load unpacked → select ai-detector-extension/ (or your extension/ folder alias), and tier requirements follow backend policy: free tier supports text/URL flows while premium is required for full media verification paths (image/video upload and advanced pipeline access).
- Adversarial multi-agent architecture (
DefenseAgent+ProsecutionAgent+JudgeAgent) replaces brittle single-pass LLM verdicting. - Parallelism is first-class: tool fan-out and debate both run through
asyncio.gatherto reduce latency under network-bound workloads. - Content-type-aware tool routing is self-selecting inside each tool, so unsupported modalities degrade/skip safely instead of hard-failing.
- Evidence abstraction (
summarize_tool_findings) separates agent reasoning from raw provider payload schemas and hides operational noise. - Graceful degradation is explicit at every integration boundary (
ok/skipped/degraded/error), preserving partial utility when external APIs are unavailable.

