An AI-powered Telegram bot playing "el gordo" — a blunt, politically incorrect Argentine character who answers everything in a single lowercase phrase using Argentine slang.
- AI chat: configurable personality with web search, powered by Qwen via OpenRouter
- Streaming responses: AI replies stream token-by-token to Telegram (when no tools are active)
- Chat memory with RediSearch: persistent conversation history, full-text search, automatic compaction
- Incremental summaries:
/resumenstreams conversation summaries using Minimax, with automatic context compaction - Agentic tools: AI can call tools (price lookup, calculator, web fetch, task scheduling) via function calling
- Market data:
/prices,/usd,/petroleo,/devo,/powerlaw,/rainbow,/rulo,/eleccion - BCRA economic data:
/bcra,/variables - Media: audio transcription (Whisper via Groq, with OpenRouter fallback) and image description (OpenRouter)
- Scheduled tasks:
/tareas,/tasks— create, list, and delete one-shot or recurring reminders via AI or inline buttons - AI credits billing: Telegram Stars (
/topup,/balance,/transfer) - Link enrichment: URLs get metadata injected into AI context; social links auto-replaced (fxTwitter, fixupx, etc.)
- Context injection: market data, weather, Hacker News top stories, and Buenos Aires time in every system prompt
- Response cleanup: deduplication, prefix stripping, identity leak prevention
uv sync --locked
cp .env.example .env
# Edit .env with your keys
uv run --locked python run_polling.py| Variable | Description |
|---|---|
BOT_SYSTEM_PROMPT |
Complete AI personality prompt |
BOT_TRIGGER_WORDS |
Comma-separated keywords that trigger responses in groups |
TELEGRAM_TOKEN |
Bot token from @BotFather |
TELEGRAM_USERNAME |
Bot username |
REDIS_HOST / REDIS_PORT / REDIS_PASSWORD |
Redis cache (requires RediSearch) |
SUPABASE_POSTGRES_URL |
Pooled Supabase Postgres URL (for AI credits) |
COINMARKETCAP_KEY |
CoinMarketCap API key |
GROQ_API_KEY |
Paid Groq API key for transcription |
GROQ_FREE_API_KEY |
Optional free-tier Groq key for transcription |
OPENROUTER_API_KEY |
OpenRouter API key for chat/vision |
CF_AIG_TOKEN |
Cloudflare AI Gateway token forwarded to OpenRouter requests |
GIPHY_API_KEY |
Giphy API key for /gm and /gn |
ADMIN_CHAT_ID |
Telegram chat ID for error reports |
FRIENDLY_INSTANCE_NAME |
Instance name for admin reports |
| Use | Provider | Model |
|---|---|---|
| Chat | OpenRouter | qwen/qwen3.6-plus |
| Vision | OpenRouter | google/gemini-3.1-flash-lite-preview |
| Transcription | Groq → OpenRouter fallback | whisper-large-v3 → google/gemini-3.1-flash-lite-preview |
| Summary | OpenRouter | minimax/minimax-m2.7 |
Streaming: token streaming only when no tools/web-search are active. Tool-enabled requests return complete responses.
| Command | Aliases | Description |
|---|---|---|
/ask |
/pregunta, /che, /gordo |
AI chat (streaming) |
/resumen |
/summary |
Stream conversation summary |
/transcribe |
/describe |
Transcribe audio / describe image |
/prices |
/price, /precios, /precio, /presio(s), /bresio(s), /brecio(s), /crypto, /criptos |
Crypto prices |
/dolar |
/dollar, /usd |
Dollar rates (CriptoYa) |
/petroleo |
/oil |
Oil prices |
/acciones |
/stocks |
Stock prices |
/eleccion |
/elecciones, /election, /elections |
Global Polymarket elections by liquidity |
/mundial |
/worldcup |
Next 10 World Cup games on Polymarket |
/devo |
- | Arbitrage calculator (tarjeta vs crypto) |
/rulo |
- | Dollar arbitrage chains |
/powerlaw |
- | Bitcoin power law |
/rainbow |
- | Bitcoin rainbow chart |
/satoshi |
/sat, /sats |
Satoshi value |
/bcra |
/variables |
BCRA economic variables |
/random |
- | Random choice or number |
/convertbase |
- | Number base conversion |
/comando |
/command |
Text → Telegram command |
/time |
- | Unix timestamp |
/config |
- | Chat settings (admin only in groups) |
/topup |
- | Buy AI credits with Telegram Stars |
/balance |
- | Show credit balance |
/transfer |
- | Transfer credits to group |
/tareas |
/tasks |
Manage scheduled reminders |
/gm |
- | Good morning GIF |
/gn |
- | Good night GIF |
/help |
- | Command reference |
/instance |
- | Instance name |
ProviderChain— tries providers in order until one succeedsOpenRouterProvider— streaming + completion, primary chat model
TelegramMessageStreamer edits Telegram messages every 400ms or 15+ new chars. Token streaming from OpenRouterProvider.stream() when no tools active. Falls back to complete response for tool-enabled requests.
AIService orchestrates credit reservation → model call → billing settlement:
- Reserve: holds worst-case credits before AI call
- Settle: calculates actual cost, charges/refunds difference
- Refund: full return on failure, fallback, or empty response
COMPACTION_THRESHOLD = 20— compact when delta > 20 messagesCOMPACTION_KEEP = 25— retain last 25 messagesCOMPACTION_THRESHOLD = 40— compact when 40+ new messages- Incremental summaries from delta messages + prior summary
- RediSearch index (
idx:chat_messages) for full-text search and RAG retrieval
- User credits — personal balance
- Group credits — shared pool subsidizing creditless users
- Onboarding — 3 free credits for new users
- Hourly limit —
creditless_user_hourly_limitcaps free messages per user per hour - Credit packs (Telegram Stars): 50→2500 credits with 50% bonus tiers
Sequential cleanup:
- Remove "gordo:" prefix
- Strip echoed context strings
- Remove identity leak prefixes (
@user:) - Deduplicate consecutive lines/sentences
Every system prompt includes:
- Market: top 3 cryptos + dollar rates (oficial, blue, mep, tarjeta, usdt)
- Weather: Buenos Aires temp, rain probability, cloud cover
- Hacker News: top 5 stories (title, points, comments)
- Time: current Buenos Aires datetime
api/- application codeapi/admin/- admin commands, reporting, authorizationapi/ai/- AI orchestration, prompting, pricing, response cleanupapi/billing/- credits, settlement, billing commands, Stars callbacksapi/bot/- Telegram adapter, handlers, routing, streaming, chat configapi/cache/- HTTP and Redis cachingapi/core/- configuration, constants, loggingapi/links/- URL metadata, replacement, and enrichmentapi/markets/- crypto, dollar, stocks, Polymarket, weatherapi/media/- image, audio, video, transcription, media cacheapi/memory/- chat history, retrieval, compaction, summariesapi/providers/- AI provider abstraction (OpenRouter, ProviderChain)api/tasks/- task execution and schedulingapi/tools/- agentic tool registry (crypto, calculator, web fetch, tasks)api/services/- persistence and low-level external adaptersapi/utils/- reusable helpersapi/index.py- application composition root and compatibility exports
quadlets/- Podman Quadlet container definitionssystemd/- systemd service and timer unitsrun_polling.py- bot entrypointrun_maintenance.py- maintenance entrypointtests/- test suiteContainerfile- container image definition
sudo apt install -y podman uidmap dbus-user-session slirp4netns fuse-overlayfs
sudo useradd -m -s /bin/bash respondedorbot
sudo loginctl enable-linger respondedorbotgit clone https://github.com/astrovm/respondedorbot
cd respondedorbot
mkdir -p ~/.config/containers/systemd
cp quadlets/* ~/.config/containers/systemd/
mkdir -p ~/respondedorbot/workspace
cp .env.example ~/respondedorbot/.env
# Create ~/respondedorbot/workspace/SOUL.md and RULES.md manually.
# Edit ~/respondedorbot/.env - set REDIS_HOST=respondedorbot-redis
# Quadlet Redis uses redis-stack-server because the bot needs RediSearch (FT.CREATE / FT.SEARCH)
export XDG_RUNTIME_DIR=/run/user/$(id -u)
export DBUS_SESSION_BUS_ADDRESS=unix:path=${XDG_RUNTIME_DIR}/bus
systemctl --user daemon-reload
systemctl --user start respondedorbot-redis.service
systemctl --user start respondedorbot.serviceThe bot container mounts ~/respondedorbot/workspace read-only at
/app/workspace. Both SOUL.md and RULES.md live outside Git and must be
created on the VPS before starting the service. Alternatively,
BOT_SYSTEM_PROMPT can provide the complete prompt through the environment.
The bundled Redis Quadlet uses the pinned
redis/redis-stack-server:7.4.0-v8 image, not plain Redis, because chat memory
search and compaction require RediSearch commands. Redis does not auto-update;
upgrade the pinned version only after testing FT.CREATE and FT.SEARCH. The
unit intentionally does not override the container command; it passes Redis
tuning through REDIS_ARGS so the image can boot Redis Stack with its modules
enabled.
The CI workflow runs Ruff, mypy, and the complete test suite before building
the bot image. A successful push to main publishes both:
ghcr.io/astrovm/respondedorbot:latestfor normal Podman auto-updates.ghcr.io/astrovm/respondedorbot:sha-<full-commit-sha>for rollback.
To temporarily roll back the VPS to a known-good commit:
ROLLBACK_SHA=<full-commit-sha>
podman pull "ghcr.io/astrovm/respondedorbot:sha-${ROLLBACK_SHA}"
sed -i \
"s|^Image=.*|Image=ghcr.io/astrovm/respondedorbot:sha-${ROLLBACK_SHA}|" \
~/.config/containers/systemd/respondedorbot.container
systemctl --user daemon-reload
systemctl --user restart respondedorbot.service
systemctl --user status respondedorbot.service --no-pagerThe SHA tag prevents Podman auto-update from moving the bot forward. After the problem is fixed, return to automatic releases:
sed -i \
"s|^Image=.*|Image=ghcr.io/astrovm/respondedorbot:latest|" \
~/.config/containers/systemd/respondedorbot.container
systemctl --user daemon-reload
systemctl --user restart respondedorbot.servicesystemctl --user enable fails on Quadlet-generated units on some distros — use symlinks instead:
mkdir -p ~/.config/systemd/user/default.target.wants
ln -sf ~/.config/containers/systemd/respondedorbot.container \
~/.config/systemd/user/default.target.wants/respondedorbot.container
ln -sf ~/.config/containers/systemd/respondedorbot-redis.container \
~/.config/systemd/user/default.target.wants/respondedorbot-redis.container
systemctl --user daemon-reloadcp systemd/respondedorbot-maintenance.* ~/.config/systemd/user/
cp systemd/respondedorbot-podman-prune.* ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now respondedorbot-maintenance.timer
systemctl --user enable --now respondedorbot-podman-prune.timerjournalctl --user -fu respondedorbot.service
systemctl --user status respondedorbot.service --no-pager
systemctl --user stop respondedorbot.service respondedorbot-redis.service
systemctl --user enable --now podman-auto-update.timer
podman exec systemd-respondedorbot python /app/run_maintenance.pyuv run --locked pytest -q