ralph-telegram
Telegram integration for human-in-the-loop orchestration in Ralph.
Enables bidirectional communication between AI agents and humans during orchestration loops:
- AI to Human: Agents emit
human.interactevents; the bot sends questions to Telegram - Human to AI: Humans reply or send proactive
human.guidancevia Telegram messages
Setup
1. Create a Telegram Bot
- Open Telegram and message @BotFather
- Send
/newbotand follow the prompts - Copy the bot token (format:
123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11)
2. Configure Ralph
Option A: Environment variable (recommended)
Option B: Config file
# ralph.yml
RObot:
enabled: true
timeout_seconds: 300
telegram:
bot_token: "your-bot-token"
api_url: "http://localhost:8081" # Optional: custom Bot API URL
The environment variable takes precedence over the config file value.
Custom API URL
To target a mock Telegram server (e.g., for CI or local HIL testing), set a custom API URL:
Or use RObot.telegram.api_url in the config file. See the Telegram guide for a full walkthrough.
3. Start a Loop
When the bot starts, it sends a greeting message to your Telegram chat. The chat ID is auto-detected from the first message you send to the bot.
How It Works
Bot Commands
Available commands while a loop is running:
/status— current loop status/tasks— open tasks/memories— recent memories/tail— last 20 events/model— current backend/model (runtime or config fallback)/models— configured model options found inralph*.yml/restart— restart the loop/stop— stop the loop at the next iteration boundary/help— list available commands
human.interact Flow
When an agent emits a human.interact event:
- The bot sends the question to Telegram with context (hat name, iteration, loop ID)
- The event loop blocks waiting for a reply
- The human replies in Telegram
- The reply is published as a
human.responseevent on the bus - The next iteration receives the response in its context
If no response arrives within timeout_seconds, the loop continues without a response.
human.guidance Flow
Humans can send messages at any time (not as replies to questions):
- Message is written as a
human.guidanceevent toevents.jsonl - On the next iteration, guidance events are collected and squashed
- A
## ROBOT GUIDANCEsection is injected into the agent's prompt
Parallel Loop Routing
With multiple loops running, messages are routed by:
- Reply-to: Replying to a bot question routes to the loop that asked it
- @prefix: Starting a message with
@loop-idroutes to that loop - Default: Messages without routing go to the primary loop
Architecture
TelegramService (lifecycle management)
├── BotApi / TelegramBot (Teloxide wrapper, send messages)
├── StateManager (chat ID, pending questions, reply routing)
├── MessageHandler (incoming messages → events.jsonl)
└── retry_with_backoff (exponential retry for sends)
Key Types
| Type | Purpose |
|---|---|
TelegramService |
Lifecycle: start, stop, send questions, wait for responses |
BotApi |
Trait for send_message; TelegramBot is the real impl, MockBot for tests |
StateManager |
Persists state to .ralph/telegram-state.json |
MessageHandler |
Writes human.response / human.guidance events to JSONL |
TelegramError |
Typed errors: MissingBotToken, Startup, Send, Receive, ResponseTimeout, State |
Error Handling
- Send failures: Retried with exponential backoff (3 attempts: 1s, 2s, 4s delays)
- All retries exhausted: Logged to diagnostics, treated as timeout (loop continues)
- Missing bot token: Clear error message listing both config and env var options
- Response timeout: Configurable via
timeout_seconds; loop continues without response
Testing