A personalized AI language tutor that runs on Telegram. The bot adapts exercises to each user's level, tracks vocabulary with spaced repetition (FSRS), and sends proactive study reminders on user-configured schedules.
Powered by Claude (via claude-agent-sdk), PostgreSQL, Redis, and aiogram. Most of the codebase was developed with Claude Code (Opus 4.6).
Try the live bot: @personal_lang_study_bot
- Adaptive exercises — the AI generates exercises tailored to the user's level (A1-C2), interests, weak areas, and preferred difficulty. No static exercise bank.
- Three session styles — Structured (theory/grammar first, then practice; plans ordered by grammar concepts), Intensive (goal-driven, weakness-focused, vocabulary-heavy; aggressive pace), Casual (free conversation on topics the student enjoys; exercises emerge organically). Style affects session flow, exercise selection, plan construction, and minimum coverage requirements.
- Learning plans — structured multi-week study plans with phases, topics, and vocabulary targets. Plans are constructed according to the student's session style. Progress is derived from exercise results. The agent adapts plans when the student is ahead or behind schedule.
- Vocabulary tracking — words are stored per-user with FSRS spaced repetition. The bot schedules reviews at optimal intervals.
- Web search-powered discussions — the agent can search the web (via Tavily) for news, articles, and cultural content in the target language. Users can ask for news-based lessons, current event discussions, or topic exploration. Available in both interactive sessions and proactive notifications.
- Proactive notifications — schedule-based reminders that the user signs up for: practice reminders, vocabulary review (when cards are due), weekly progress summaries, and agent-created quiz/custom schedules. Notifications include action + dismiss buttons. The bot remembers what it sent — when a user replies to a notification, the session automatically continues from that context. Users control schedules via natural language or
/settings. - Auto session management — sessions end automatically when the lesson reaches a natural conclusion. The agent calls an
end_sessiontool, delivers a farewell, and the user receives a formatted summary with achievements and recommendations. An internal AI summary is generated in the background and injected into the next session's system prompt for continuity. - Two-tier system — Free (Haiku 4.5) and Premium (Sonnet 4.6) with different limits. No billing — admin grants premium via the Gradio panel.
- 17 target languages — English, French, Spanish, Italian, German, Portuguese, Russian, Chinese, Japanese, Korean, Arabic, Turkish, Dutch, Polish, Swedish, Ukrainian, Hindi. Users can learn any of these.
- 7 UI languages — English, Russian, Spanish, French, German, Portuguese, Italian. All bot UI, notifications, and session messages are rendered in the user's native language via an i18n system with JSON locale files.
- Gradio admin panel — monitor users, sessions, costs, alerts, and system health. Gradio auth required (
ADMIN_API_TOKEN). - Health alerts — automated Telegram alerts to admins for cost spikes, pool saturation, pipeline failures, and connectivity issues.
- Python 3.12+
- PostgreSQL 16
- Redis 7
- Claude CLI (installed on the host or in Docker)
- Poetry (for local development)
- Clone the repository and create the
.envfile with the required variables:
# Required
TELEGRAM_BOT_TOKEN=your-telegram-bot-token
ANTHROPIC_API_KEY=your-anthropic-api-key
POSTGRES_PASSWORD=your-secure-password
ADMIN_API_TOKEN=your-admin-token # Gradio admin panel refuses to start without it
# Optional (defaults shown)
POSTGRES_HOST=localhost
POSTGRES_PORT=5432
POSTGRES_USER=langbot
POSTGRES_DB=langbot
REDIS_URL=redis://localhost:6379/0
MAX_CONCURRENT_INTERACTIVE_SESSIONS=500
MAX_CONCURRENT_PROACTIVE_SESSIONS=50
PROACTIVE_TICK_INTERVAL_SECONDS=60
ADMIN_HOST=0.0.0.0
ADMIN_PORT=7860
ADMIN_TELEGRAM_IDS=[] # JSON array of admin user IDs, e.g. [123456,789012]
LOG_LEVEL=INFO
METRICS_PORT=9090
DB_POOL_SIZE=150
DB_MAX_OVERFLOW=100
DB_POOL_RECYCLE=3600 # seconds
DB_POOL_TIMEOUT=10 # seconds to wait for a connection before raising
REDIS_MAX_CONNECTIONS=200- Start all services:
docker compose up -dThis starts 4 containers:
- bot — Telegram bot + APScheduler proactive engine (single process)
- admin — Gradio admin panel on port 7860
- postgres — PostgreSQL 16
- redis — Redis 7 (256MB, noeviction — used for locks/coordination, not caching)
- Run database migrations:
docker compose exec bot python -m alembic upgrade head- Open the admin panel at
http://localhost:7860.
# Install dependencies
poetry install
# Set up .env with your keys (see above)
# Start PostgreSQL and Redis (or use Docker for just these)
docker compose up -d postgres redis
# Run database migrations
poetry run alembic upgrade head
# Start the bot
poetry run python -m adaptive_lang_study_bot.entrypoints.run_bot
# Start the admin panel (separate terminal)
poetry run python -m adaptive_lang_study_bot.entrypoints.run_adminAll configuration is via environment variables (loaded by pydantic-settings from .env). See src/adaptive_lang_study_bot/config.py for all options.
| Parameter | Free | Premium |
|---|---|---|
| Model | claude-haiku-4-5 | claude-sonnet-4-6 |
| Max turns/session | 20 | 35 |
| Max sessions/day | 3 | 5 |
| Session idle timeout | 6 min | 10 min |
| Thinking mode | adaptive | adaptive |
| Effort | low | low |
| LLM notifications/day | 2 | 8 |
| Rate limit | 5 msg/min | 20 msg/min |
| Max cost/session | $0.75 | $2.81 |
| Max cost/day | $2.00 | $8.00 |
To grant a user premium access, use the admin panel or update the tier column in the users table directly.
| Command | Description |
|---|---|
/start |
Onboarding: select native language, target language, timezone |
/review |
Start a vocabulary review session (FSRS due cards) |
/stats |
View progress: level, streak, vocabulary count, recent scores |
/settings |
Manage preferences, schedules, quiet hours, timezone, target language |
/end |
End the current study session explicitly |
/deleteme |
Delete account and all associated data (with confirmation) |
/help |
Command reference |
/debug |
Toggle per-message debug output (admin-only) |
Any other text message starts or continues an interactive study session with the AI tutor.
┌────────────┐
│ Telegram │
└──────┬─────┘
│ polling
▼
┌────────────────────────────────────────────────────────────────┐
│ Bot Process │
│ │
│ INTERACTIVE PATH │
│ aiogram --> DBSession --> Auth --> RateLimit │
│ │ │
│ start chat settings review stats │
│ │ │
│ ▼ │
│ SessionManager ─────────────────────> Redis (session lock) │
│ │ │
│ ▼ │
│ Agent Session (per user) │
│ ClaudeSDKClient ──────────────────> Anthropic API │
│ ├── System Prompt (15 sections) (Haiku / Sonnet) │
│ ├── MCP Server (8-14 tools) ──────> PostgreSQL │
│ └── Hooks (PostToolUse, UserPromptSubmit, Stop) │
│ │ on close │
│ ▼ │
│ Post-Session Pipeline ──────────────> PostgreSQL │
│ (streak, difficulty, milestones) │
│ │
│ PROACTIVE ENGINE │
│ APScheduler 60s --> Schedules --> Dispatcher │
│ ├── template --> Telegram │
│ └── LLM (+ web tools) ──> Anthropic│
│ │
│ Health Alerts + Stats Reports ──────> Telegram (admins) │
│ Prometheus metrics :9090 │
└──────────┬─────────────────┬───────────────────────────────────┘
│ │
▼ ▼
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ PostgreSQL 16 │ │ Redis 7 │ │ Anthropic API │
│ 9 tables │ │ session locks │ │ Haiku 4.5 │
│ │ │ rate limits │ │ Sonnet 4.6 │
│ users │ │ notif dedup │ │ │
│ vocabulary │ │ tick lock │ │ via Claude │
│ sessions │ │ alert dedup │ │ Agent SDK │
│ schedules │ │ │ │ │
│ exercises │ │ │ │ │
│ notifications │ │ │ │ │
│ learning_plans │ │ │ │ │
│ review_log │ │ │ │ │
│ access_requests │ │ │ │ │
└────────▲─────────┘ └──────────────────┘ └──────────────────┘
│
┌────────┴─────────┐
│ Gradio Admin │
│ Panel :7860 │
│ │
│ Users Sessions │
│ Costs Analytics │
│ Alerts System │
│ Broadcast │
│ Whitelist │
└──────────────────┘
src/adaptive_lang_study_bot/
├── config.py # Settings, tier limits
├── enums.py # StrEnum constants (UserTier, SessionType, NotificationTier, CloseReason, etc.)
├── utils.py # Shared helpers (language names, streak, tool summaries)
├── i18n.py # Internationalization: t(key, lang) with locale JSON fallback
├── locales/ # JSON locale files (en, ru, es, fr, de, pt, it)
├── agent/ # Claude SDK: tools, hooks, prompt, session manager
├── bot/ # aiogram: middlewares, routers, app setup
│ └── routers/ # start, chat, settings, review, stats, debug
├── db/ # SQLAlchemy models, repositories, migrations
├── cache/ # Redis: session lock, rate limits, key patterns, distributed locks
├── proactive/ # APScheduler tick, triggers, dispatcher, admin reports
├── fsrs_engine/ # FSRS spaced repetition wrapper
├── pipeline/ # Post-session validation (pure Python)
├── admin/ # Gradio admin panel
└── entrypoints/ # run_bot.py, run_admin.py
- Per-session closures — each agent session creates its own tool and hook functions. Tools capture a session factory and user ID (each tool call gets its own short-lived DB session). This ensures complete data isolation between concurrent users.
- Long-lived sessions — each user gets one
ClaudeSDKClientthat persists across messages until closed (by turn/cost limit, idle timeout, agent-initiatedend_session, or/end). A new session builds a fresh system prompt from the user's current DB profile snapshot. A 120-second cooldown prevents new sessions immediately after close (skipped for onboarding). - Session continuity via AI summaries — after each session, a background LLM call generates a structured internal summary (topics, performance, vocabulary, continuation points). These summaries are stored in the
sessions.ai_summarycolumn and injected into the next session's system prompt, giving the agent rich context about recent sessions without duplicating raw exercise data. - Hybrid personalization — the system prompt carries the user profile snapshot (read-only context), while MCP tools handle all DB writes (exercises, vocabulary, preferences). Session style (structured/intensive/casual) modulates prompt generation across multiple sections: session flow, exercise preferences, plan construction guidelines, and minimum coverage rules.
- Learning plans — structured multi-week plans (2-8 weeks) with phases, focus topics, and vocabulary targets. Progress is derived on-the-fly from exercise results (
compute_plan_progress) — no stored per-topic state. The agent adapts plans when the student is ahead or behind schedule. Level progression is plan-anchored: the agent assesses readiness and callsadjust_levelafter plan completion, rather than automatic score thresholds. When all topics are completed but the user hasn't reached the target CEFR level, a consolidation phase is auto-added targeting the weakest topics at a higher mastery bar. Mastery plans (where current and target levels are the same) auto-complete and delete at 100%. - Three-tier notifications — template ($0 cost, random variant from locale files), LLM (short-lived proactive session with optional web search generates personalized message), or hybrid (try LLM, fall back to template). Free users get 2 LLM notifications/day, premium get 8. All notifications include CTA keyboards with action + dismiss buttons. A post-session cooldown prevents notifications from firing immediately after a session ends.
- Notification reply context — when a user replies to a proactive notification, the system prompt includes the notification text as context, so the agent naturally continues from that topic rather than starting fresh.
- Localized UI — all user-facing messages (bot UI, notifications, session summaries, warnings) rendered via
i18n.t()in the user's native language. - Post-session pipeline — after each session, a pure-Python pipeline validates data integrity, updates streaks, auto-adjusts difficulty, and detects milestones.
PostgreSQL with 9 tables:
| Table | Purpose |
|---|---|
users |
User profiles, preferences, notification settings, tier |
vocabulary |
Per-user words with FSRS spaced repetition state |
sessions |
Session records with cost/token tracking |
schedules |
RRULE-based recurring schedules (daily review, weekly summary, etc.) |
exercise_results |
Individual exercise scores for analytics |
notifications |
Audit log of all sent/skipped notifications |
vocabulary_review_log |
Individual FSRS review events |
learning_plans |
Structured multi-week study plans with JSONB phases/topics (one per user) |
access_requests |
Whitelist access requests with approval status and reviewer info |
Used for locking, rate limiting, and deduplication:
| Key pattern | TTL | Purpose |
|---|---|---|
session:active:{id} |
7-12 min | Track active sessions, enforce one-per-user |
session:cooldown:{id} |
120s | Post-session cooldown before new session (skipped for onboarding) |
ratelimit:user:{id}:* |
60s | Per-user rate limiting |
notif:dedup:{user_id}:{type}:{date} |
24h | Prevent duplicate notifications |
notif:llm_count:{user_id}:{date} |
24h | Track daily LLM notification count per user |
notif:cooldown:{user_id} |
5 min | Per-user cooldown between any notifications |
lock:proactive_tick |
5 min | Distributed lock for tick scheduler |
lock:admin_stats_report |
5 min | Distributed lock for stats report |
lock:admin_health |
2 min | Distributed lock for health alerts |
admin:alert:{type}:{hour} |
1h | Dedup health alerts per type per hour |
# Create a new migration
poetry run alembic revision --autogenerate -m "description"
# Apply migrations
poetry run alembic upgrade head
# Rollback one step
poetry run alembic downgrade -1# Run all tests
poetry run pytest
# Run only unit tests
poetry run pytest tests/unit/
# Run only integration tests (requires Docker for testcontainers)
poetry run pytest tests/integration/
# Run specific file
poetry run pytest tests/unit/agent/test_tools.py
# Stop on first failure
poetry run pytest -xNo database, Redis, or SDK required. They verify:
- Tool constants and session-type permissions
- Security invariants (mutable field whitelists)
- Pure functions (message splitting, timezone conversion, language detection, utils helpers)
- Prompt builder output structure and sanitization
- Notification template rendering via i18n
- Dispatcher gate logic (should_send conditions)
- Score normalization consistency (0-10 scale thresholds)
- Post-session pipeline steps and post-session logic
- FSRS engine operations
- Config validation
- Learning plan progress computation
- Hook behavior (adaptive hints, wrap-up injection)
- Auth middleware logic
- Admin role checks
- Quiet hours edge cases
- Difficulty adjustment
- i18n translation and fallback chains
- Proactive LLM session lifecycle
Require Docker. Use testcontainers to spin up real PostgreSQL 16 and Redis 7 containers. Tests cover:
- CASCADE and FK constraints
- Repository operations (user, vocabulary, session, schedule, exercise, notification)
- Redis session lock and cache behavior
- Atomic operations and concurrent access
- Learning plan CRUD and constraints
- Notification dispatch integration
Require ANTHROPIC_API_KEY. Make real Claude API calls via claude-agent-sdk to test:
- System prompt compliance (native language, off-topic refusal)
- Security boundaries (tool permissions, field whitelists)
- Tool calling compliance
- Session type behavior (onboarding vs interactive)
- Learning plan tool calling (create, get, session-type restrictions)
- Adaptive behavior (score-based hints)
- Multi-turn conversations
The bot exposes a Prometheus metrics HTTP endpoint on port METRICS_PORT (default 9090). 16 metrics are tracked across 3 types:
- Gauges — active session pool size (interactive/proactive)
- Counters — sessions created/closed, messages processed, errors, notifications sent/skipped, proactive ticks, pipeline completions
- Histograms — session cost, session duration, message cost, tick duration, pipeline duration, notification LLM cost
The bot process runs 3 background jobs:
| Job | Interval | Purpose |
|---|---|---|
proactive_tick |
60s | Schedule-based notifications and follow-up reminders |
health_alerts |
60s | Evaluate 7 health conditions, alert admins via Telegram |
admin_stats_report |
12h | Send usage/cost summary to admins via Telegram |
Automated alerts sent to admin Telegram IDs (deduped per hour per type):
| Alert | Condition |
|---|---|
| Cost spike | Today's cost > 2x 7-day daily average |
| Interactive pool high | > 80% capacity |
| Proactive pool high | > 80% capacity |
| Pipeline failures | > 3 failures in last hour |
| Redis unhealthy | Connection failed |
| DB unhealthy | Connection failed |
| Notification failures | > 30% failure rate (min 5 total in last hour) |
Available at http://localhost:7860 (requires ADMIN_API_TOKEN, login as admin). Eight tabs:
- Users — search by name/username/ID, view full profile, toggle tier (free/premium), toggle active status, toggle admin role
- Sessions — recent 100 sessions: user, type, cost, turns, tools used, pipeline status, duration
- Costs — summary (today/7d/30d), daily breakdown (14 days), per-user cost ranking (7 days)
- Analytics — user activity trends, session metrics, retention data
- Alerts — pipeline failures, notification delivery stats (7-day status breakdown + recent 20)
- System — active session pool, Redis memory, DB status, configuration snapshot, health alert status (7 checks)
- Broadcast — send messages to all users or filtered groups
- Whitelist — manage access requests: view pending, approve/reject, track approvals
The bot uses loguru for structured logging. All significant events are logged:
- User registration and profile updates
- Session creation, tool calls, and completion
- Proactive tick execution and notification dispatch
- Post-session pipeline results
- Health alerts and admin reports
Estimates assume prompt caching is active (system prompt + tool definitions cached across turns). Pricing: Haiku 4.5 — $1/$5 per MTok (input/output), Sonnet 4.6 — $3/$15 per MTok. Proactive notifications use Haiku regardless of tier.
| Tier | Avg session cost | Daily (1-2 sessions) | Monthly (1 user) |
|---|---|---|---|
| Free (Haiku 4.5) | $0.03-0.08 | ~$0.10 | ~$3 |
| Premium (Sonnet 4.6) | $0.20-0.50 | ~$0.80 | ~$25 |
Scale estimates assume 60% daily active rate and 1.5 sessions per active user per day:
| Scale | All Free | Mixed (90/10) | All Premium |
|---|---|---|---|
| 100 users | ~$180/mo | ~$320/mo | ~$1,500/mo |
| 1,000 users | ~$1,800/mo | ~$3,200/mo | ~$15,000/mo |
The primary bottleneck is the interactive session pool — each concurrent user holds one Claude CLI subprocess (~50-80 MB RAM) for the duration of their session (until idle timeout or /end). Defaults are tuned for ~50,000 registered users.
| Resource | Default | Env var / Config |
|---|---|---|
| Interactive session pool | 500 concurrent | MAX_CONCURRENT_INTERACTIVE_SESSIONS |
| Proactive session pool | 50 concurrent | MAX_CONCURRENT_PROACTIVE_SESSIONS |
| Bot container memory | 40 GB | docker-compose.yml deploy limit |
| DB connection pool | 150 + 100 overflow | DB_POOL_SIZE, DB_MAX_OVERFLOW |
| PostgreSQL max connections | 500 | docker-compose.yml command args |
| Redis max connections | 200 | REDIS_MAX_CONNECTIONS |
| Redis memory | 512 MB | docker-compose.yml command args |
Assumptions: 60% daily active rate, 1.5 sessions per active user per day, average session occupies a slot for ~5 minutes, activity spread over ~16 waking hours, peak load ~3x average.
| Registered users | Avg concurrent sessions | Peak concurrent (est.) | Fits in 500 slots? |
|---|---|---|---|
| 1,000 | ~5 | ~15 | Yes (comfortable) |
| 10,000 | ~47 | ~140 | Yes |
| 50,000 | ~234 | ~700 | Yes (some rejection at extreme peaks) |
| 100,000 | ~469 | ~1,400 | No (pool exhaustion at peak) |
Memory bound: 500 concurrent sessions x ~65 MB avg = ~32.5 GB. Fits within the 40 GB container limit with headroom for the Python process and overhead.
Each 60-second tick dispatches up to 50 concurrent notifications. Template notifications ($0) resolve in milliseconds; LLM notifications take ~5-30 seconds each. At 50 concurrent LLM slots, the system can dispatch ~100-600 LLM notifications per tick (depending on generation time). Daily LLM limits (2 free / 8 premium per user) bound demand to ~54 avg / ~160 peak per tick at 30k DAU — well within capacity. Excess LLM demand auto-downgrades to free template notifications.
| Bottleneck | How to scale |
|---|---|
| Session pool exhausted | Increase MAX_CONCURRENT_INTERACTIVE_SESSIONS (requires proportional RAM) |
| Bot out of memory | Increase Docker memory limit (~65 MB per additional concurrent session) |
| DB connections saturated | Increase DB_POOL_SIZE and PostgreSQL max_connections |
| Proactive notifications slow | Increase MAX_CONCURRENT_PROACTIVE_SESSIONS |
| Single-process CPU bound | Not horizontally scalable — the bot runs as a single process with in-memory session state. Scaling beyond one instance requires architectural changes (external session store, distributed session management). |
The stack runs 4 services:
| Service | Image | Purpose | Port |
|---|---|---|---|
bot |
custom (Dockerfile) | Telegram bot + APScheduler proactive engine | 9090 (Prometheus metrics) |
admin |
custom (Dockerfile) | Gradio admin panel | 7860 |
postgres |
postgres:16-alpine | Database (max_connections=500, tuned shared_buffers/work_mem) | 5432 (internal) |
redis |
redis:7-alpine | Locks, rate limits, dedup (512MB, noeviction) | 6379 (internal) |
# Start all services (detached)
docker compose up -d
# Stop all services (preserves data volumes)
docker compose down
# Stop and remove all data (fresh start)
docker compose down -v
# Rebuild after code changes and restart
docker compose up -d --build
# Rebuild only one service
docker compose up -d --build bot
# Restart a single service (no rebuild)
docker compose restart bot
# Check status
docker compose ps# Follow all logs (Ctrl+C to stop)
docker compose logs -f
# Follow a single service
docker compose logs -f bot
docker compose logs -f admin
docker compose logs -f postgres
docker compose logs -f redis
# Last N lines
docker compose logs --tail 50 bot
# Last N lines + follow
docker compose logs -f --tail 100 bot
# Logs since a timestamp
docker compose logs --since "2025-01-15T10:00:00" bot
# Logs from the last hour
docker compose logs --since 1h bot
# Show timestamps
docker compose logs -t bot
# Multiple services at once
docker compose logs -f bot admin
# Grep for specific patterns (combine with standard tools)
docker compose logs bot 2>&1 | grep "ERROR"
docker compose logs bot 2>&1 | grep "user 123456"# Open psql shell
docker compose exec postgres psql -U langbot
# Run a single SQL query
docker compose exec postgres psql -U langbot -c "SELECT count(*) FROM users;"
# Run migrations (after code update)
docker compose exec bot python -m alembic upgrade head
# Check current migration version
docker compose exec bot python -m alembic current
# Create a new migration
docker compose exec bot python -m alembic revision --autogenerate -m "description"
# Rollback one migration
docker compose exec bot python -m alembic downgrade -1
# Dump the database (backup)
docker compose exec postgres pg_dump -U langbot langbot > backup_$(date +%Y%m%d_%H%M%S).sql
# Restore from backup (stop bot/admin first)
docker compose stop bot admin
docker compose exec -T postgres psql -U langbot langbot < backup_20250115_120000.sql
docker compose start bot admin# Open redis-cli
docker compose exec redis redis-cli
# Check connectivity
docker compose exec redis redis-cli ping
# View memory usage
docker compose exec redis redis-cli info memory
# List active session locks
docker compose exec redis redis-cli KEYS "session:active:*"
# List all keys (use cautiously in production)
docker compose exec redis redis-cli KEYS "*"
# Check a specific key's TTL
docker compose exec redis redis-cli TTL "session:active:123456"
# Flush all Redis data (resets locks, rate limits, dedup)
docker compose exec redis redis-cli FLUSHALL
# Monitor commands in real time (Ctrl+C to stop)
docker compose exec redis redis-cli MONITOR# Shell into the bot container
docker compose exec bot bash
# Shell into postgres container
docker compose exec postgres sh
# Run a Python one-liner inside the bot container
docker compose exec bot python -c "from adaptive_lang_study_bot.config import settings; print(settings.model_dump_json(indent=2))"After pulling new code or making changes:
# 1. Rebuild and restart (zero-downtime for DB/Redis)
docker compose up -d --build
# 2. Run any new migrations
docker compose exec bot python -m alembic upgrade head
# 3. Verify services are healthy
docker compose ps
docker compose logs --tail 20 botFor changes that require a fresh database:
# 1. Stop everything and remove volumes
docker compose down -v
# 2. Rebuild and start
docker compose up -d --build
# 3. Run all migrations on the empty database
docker compose exec bot python -m alembic upgrade head# CPU and memory usage per container
docker compose stats
# Disk usage (images, containers, volumes)
docker system dfVia admin panel or directly:
UPDATE users SET tier = 'premium' WHERE telegram_id = 123456789;Via admin panel (auto-upgrades to premium) or directly:
UPDATE users SET is_admin = true, tier = 'premium' WHERE telegram_id = 123456789;UPDATE users SET streak_days = 0, streak_updated_at = NULL WHERE telegram_id = 123456789;UPDATE users SET notifications_paused = true WHERE telegram_id = 123456789;Check Redis:
redis-cli KEYS "session:active:*"Or via the admin panel's System tab.
The bot container is allocated 40GB memory to support up to 500 concurrent interactive agent sessions (~50-80MB each for the Claude CLI subprocess). PostgreSQL is tuned with shared_buffers=1GB, work_mem=4MB, and SSD-optimized settings. Redis is capped at 512MB with noeviction policy (Redis stores locks and coordination data, not cache — eviction would cause correctness bugs).
| Issue | Solution |
|---|---|
| Bot not responding | Check docker compose logs bot for errors. Verify TELEGRAM_BOT_TOKEN is correct. |
| "Claude Code cannot be launched inside another session" | Unset CLAUDECODE env var. Only happens when running inside VS Code with Claude Code extension. |
| High costs | Check the admin panel Costs tab. Consider lowering max_sessions_per_day or max_cost_per_day_usd in config.py. |
| Notifications not sending | Check user's notifications_paused, quiet_hours_start/end, and max_notifications_per_day. Check notifications table for skipped_* statuses. |
| Migrations fail | Ensure PostgreSQL is running and POSTGRES_* env vars are correct. Check docker compose logs postgres. |
| Redis connection refused | Verify REDIS_URL matches the Redis container hostname. Inside Docker, use redis://redis:6379/0. |
| Health alerts not arriving | Ensure ADMIN_TELEGRAM_IDS is set and the listed users have is_admin = true in the database. |