Helios

Architecture

Three layers (apps / core / infrastructure), data flow from MTProto Update to delivered notification, multi-tenancy via RLS.

Read CLAUDE.md first. This page expands on the layered architecture and the data flow from MTProto Update to delivered notification.

Layers

apps/   ──→  entrypoints (bot, api, miniapp, userbot, workers)
core/   ──→  pure domain logic, policies, settings, events  — NO I/O
infrastructure/  ──→  DB, Redis, storage, search, crypto, HTTP adapters
plugins/  ──→  payments, enrichment, delivery — Protocol-based
shared/  ──→  cross-cutting utilities (logging, redaction, time, metrics)
i18n/   ──→  ICU translations
config/  ──→  seed defaults for the parameters registry

core/ knows nothing about Postgres, Redis, or aiogram. Adapters in infrastructure/ implement Protocols defined in core/. Tests in tests/unit/ run without any container.

Data flow

   ┌─────────────┐
   │  userbots   │  pyrotgfork Clients wrapped by stealth.guard
   └──────┬──────┘
          │ Update* events

   ┌─────────────┐
   │ Redis Streams│
   └──────┬──────┘

   ┌─────────────┐
   │  ingester   │   ──→ Postgres (TimescaleDB hypertables)
   └──────┬──────┘

   ┌─────────────┐
   │  aggregator │   ──→ Meilisearch index (multi-tenant wrapped)
   │  analytics  │
   └──────┬──────┘

   ┌─────────────┐         ┌─────────────┐
   │ logging_router│ ─→ ─→  │  delivery/* │ ─→ bot / channel / webhook / ...
   └─────────────┘         └─────────────┘

Multi-tenancy

Every client-scoped table has a client_id column and a Postgres RLS policy keyed on current_setting('app.client_id'). The GUC is set by apps/bot/middlewares/context.py and apps/api/middlewares/.... Bypassing RLS is a P0 incident.

Search uses infrastructure/search/meilisearch.py which always injects client_ids = <client_id> into every query.

Settings registry

Every value that could differ between environments, tiers, clients, targets, chats, routes, userbots, or personas is a Parameter in core/settings/parameters.py. Reads are O(1) once warmed (in-memory LRU keyed by (key, scope_ctx)). Writes go to settings_overrides and broadcast a Redis pub/sub event that invalidates every process's cache.

Stealth model

The userbot cluster has exactly three visibility classes for Pyrogram methods:

  • Zero-visibility (allowed): import_contacts, getFullUser, getUser, getCommonChats, receiving UpdateUserStatus, getHistory in a shared chat, media download.
  • Passive-visibility (allowed only if persona is plausible): userbot present in member list, mutual contacts, userbot's own online status.
  • Active-visibility (ABSOLUTELY FORBIDDEN): any RPC that notifies the target. See Stealth thesis.

On this page