Complete Setup Guide

4 Telegram Bots for 4 Users
on One Hermes Agent

Step-by-step architecture, configuration, deployment & ops

April 2026  Β·  Hermes Agent  Β·  Telegram Bot API v9.6

πŸ€–

4 Bot Tokens

One per user, true isolation at the Telegram layer

🧠

4 Distinct Personas

Own name, greeting, system prompt & reasoning style

πŸ’Ύ

Per-User Memory

SQLite keyed by chat_id β€” conversations never cross

πŸš€

Separate Processes

One crash doesn't take down the other three

1 Architecture Overview

USER_A USER_B USER_C USER_D
πŸ€– Bot-1
@PersonaOneBot
πŸ€– Bot-2
@PersonaTwoBot
πŸ€– Bot-3
@PersonaThreeBot
πŸ€– Bot-4
@PersonaFourBot
port 5001
persona_one.py
port 5002
persona_two.py
port 5003
persona_three.py
port 5004
persona_four.py
🦊 Babu
🦊 Sage
🦊 Mira
🦊 Nova

Each of the 4 bots runs as an independent process with its own Flask webhook server, Telegram bot token, persona configuration, and SQLite memory store. They share the same codebase but are completely isolated β€” one crashing doesn't affect the others.

Why separate processes? TelegramEmpire uses a LangGraph ReAct agent that calls Ollama for every message. Running 4 agents in one process means competing for Ollama's context window. Separate processes give each agent its own clean execution context and make failure isolation simple β€” pm2 handles restarts per-process automatically.

2 Create 4 Telegram Bot Tokens

Open a chat with @BotFather on Telegram and run the following commands once per bot:

/newbot
# Follow the prompts β€” give each bot a unique name and username
# Save the token BotFather gives you (format: 123456789:ABCDefGHIjklMNOpQRstUVwxyz0123456789)

Repeat 4 times. Keep all 4 tokens β€” you'll need them in Step 4.

Tokens are secrets. Never commit them to git. Use environment variables or a .env file that's in your .gitignore. Each bot instance reads its token at startup from TELEGRAM_BOT_TOKEN.

3 Set Up the Directory Structure

Create one directory per bot. Each is a copy of the TelegramEmpire codebase with its own config:

~/Development/
β”œβ”€β”€ bot-one/          # Babu  β€” @PersonaOneBot
β”œβ”€β”€ bot-two/          # Sage  β€” @PersonaTwoBot
β”œβ”€β”€ bot-three/        # Mira  β€” @PersonaThreeBot
└── bot-four/         # Nova  β€” @PersonaFourBot
cp -r ~/Development/TelegramEmpire ~/Development/bot-one
cp -r ~/Development/TelegramEmpire ~/Development/bot-two
cp -r ~/Development/TelegramEmpire ~/Development/bot-three
cp -r ~/Development/TelegramEmpire ~/Development/bot-four

Tip: After copying, you can keep the 4 directories in sync by copying files you want to update (like agent/nodes.py or agent/graph.py) across all 4 directories. Or use a monorepo symlink approach where only config.py and persona.py are unique per directory.

# Monorepo symlink approach (optional but cleaner)
mkdir hermes-multi-bot
mkdir hermes-multi-bot/shared
cp -r ~/Development/TelegramEmpire/* hermes-multi-bot/shared/
# Now each bot dir only needs its own config + persona
mkdir hermes-multi-bot/{one,two,three,four}
for dir in one two three four; do
  ln -s ../shared/. hermes-multi-bot/$dir/shared
done

4 Configure Each Bot's config.py

Update config.py in each directory. The key values that differ per bot:

# bot-one/config.py
TELEGRAM_BOT_TOKEN = "YOUR_BOT_TOKEN_HERE"          # ← unique per bot
ALLOWED_USER_IDS = [123456789]                       # ← USER_A's Telegram ID
OLLAMA_BASE_URL = "http://localhost:11434"
CLASSIFIER_MODEL = "qwen3.5"
REASONER_MODEL = "gemma4"
DB_PATH = os.path.join(os.path.dirname(__file__), "bot_one_memory.db")  # ← unique DB
# bot-two/config.py  (example values β€” substitute your actual IDs)
TELEGRAM_BOT_TOKEN = "YOUR_BOT_2_TOKEN"
ALLOWED_USER_IDS = [987654321]
DB_PATH = os.path.join(os.path.dirname(__file__), "bot_two_memory.db")
# ... rest identical

Create each bot's own SQLite database file by changing DB_PATH per instance. The MessageStore already keys messages by user_id, so the shared table design is fine β€” but separate DB files mean clean backup/restores per user.

5 Define 4 Distinct Personas

Create a unique persona.py per bot. This is the most impactful part of multi-bot setup β€” each agent has its own identity, reasoning style, and system prompt.

5a β€” Bot One: Babu (Analytical)

# bot-one/persona.py
BOT_NAME = "Babu"
BOT_GREETING = "Yo, I'm Babu. I think through stuff step by step. Ask me anything."
REASONING_STYLE = """You are a sharp analytical thinker. You break problems down,
weigh evidence, consider alternatives, then give your best take. Be direct."""
SYSTEM_PROMPT = f"""You are {BOT_NAME}, a reasoning partner who works through
problems systematically.\n\n{REASONING_STYLE}\n\nUse tools when needed: search_web, read_file, exec_code.\nEnd with FINISH when done."""
FALLBACK_MESSAGE = "I'm having trouble thinking right now β€” try again in a moment."
STEP_LIMIT_MESSAGE = "I've done my best thinking on this. Here's what I have:"

5b β€” Bot Two: Sage (Wisdom / Long-form)

# bot-two/persona.py
BOT_NAME = "Sage"
BOT_GREETING = "Greetings. I am Sage. I speak with care and depth. Ask, and I will reflect."
REASONING_STYLE = """You are a thoughtful wisdom-keeper. You consider the broader context,
draw on patterns and history, and speak with nuance. Prioritize clarity over speed."""
SYSTEM_PROMPT = f"""You are {BOT_NAME}, a thoughtful companion who speaks with depth
and care.\n\n{REASONING_STYLE}\n\nUse tools thoughtfully: search_web, read_file, exec_code.\nEnd with FINISH when you have something worth saying."""
FALLBACK_MESSAGE = "I am momentarily unable to respond. Please try again shortly."
STEP_LIMIT_MESSAGE = "I have considered this as fully as I can. My current view:"

5c β€” Bot Three: Mira (Creative)

# bot-three/persona.py
BOT_NAME = "Mira"
BOT_GREETING = "Hey! I'm Mira β€” let's think creatively about stuff. Hit me with anything."
REASONING_STYLE = """You are a creative problem-solver. You look for unconventional angles,
metaphors, and "what if" moments. Make ideas feel vivid and alive."""
SYSTEM_PROMPT = f"""You are {BOT_NAME}, a creative thinking companion who makes ideas
feel vivid and alive.\n\n{REASONING_STYLE}\n\nTools: search_web, read_file, exec_code.\nEnd with FINISH."""
FALLBACK_MESSAGE = "My creative circuits are glitching β€” give me another shot!"
STEP_LIMIT_MESSAGE = "Here's my best take on this:"

5d β€” Bot Four: Nova (Efficient / Technical)

# bot-four/persona.py
BOT_NAME = "Nova"
BOT_GREETING = "Hey, I'm Nova. Efficient, technical, no fluff. What do you need?"
REASONING_STYLE = """You are an efficient technical partner. You get to the point fast,
show working when useful, and prefer clean explanations over lengthy disclaimers."""
SYSTEM_PROMPT = f"""You are {BOT_NAME}, a precise technical partner who values efficiency
and directness.\n\n{REASONING_STYLE}\n\nUse: search_web, read_file, exec_code.\nEnd with FINISH."""
FALLBACK_MESSAGE = "Systems are down β€” try again in a moment."
STEP_LIMIT_MESSAGE = "Best I can distill this to:"

Persona design tip: The SYSTEM_PROMPT shapes the agent's reasoning behavior more than any other file. Vary the reasoning style β€” Babu breaks things down, Sage draws patterns, Mira makes it vivid, Nova is direct. Each persona should feel genuinely different in conversation. You can test this by sending the same question to all 4 bots and comparing responses.

6 Configure Webhook Ports

Each Flask instance needs its own port. Update the if __name__ == "__main__" block or your docker-compose.yml / pm2.config.js per instance:

BotPortWebhook endpoint
Bot One β€” Babu5001/telegram/webhook
Bot Two β€” Sage5002/telegram/webhook
Bot Three β€” Mira5003/telegram/webhook
Bot Four β€” Nova5004/telegram/webhook
# bot-one/bot.py β€” change the port
if __name__ == "__main__":
    use_webhook = os.getenv("USE_WEBHOOK", "false").lower() == "true"
    if use_webhook:
        set_webhook()
        app.run(host="0.0.0.0", port=5001, debug=False)   # ← change per bot
    else:
        ...

7 Set Up Webhooks with Telegram

For each bot, call the Telegram API to set its webhook URL. Replace BOT_TOKEN and YOUR_DOMAIN accordingly:

# Bot One webhook
curl -X POST "https://api.telegram.org/bot<BOT_ONE_TOKEN>/setWebhook" \
  -d '{"url": "https://yourdomain.com/bot-one/webhook"}'

# Bot Two webhook
curl -X POST "https://api.telegram.org/bot<BOT_TWO_TOKEN>/setWebhook" \
  -d '{"url": "https://yourdomain.com/bot-two/webhook"}'

# Bot Three webhook
curl -X POST "https://api.telegram.org/bot<BOT_THREE_TOKEN>/setWebhook" \
  -d '{"url": "https://yourdomain.com/bot-three/webhook"}'

# Bot Four webhook
curl -X POST "https://api.telegram.org/bot<BOT_FOUR_TOKEN>/setWebhook" \
  -d '{"url": "https://yourdomain.com/bot-four/webhook"}'

Your nginx/Caddy reverse proxy then routes each path to the correct port:

# nginx example
location /bot-one/ {
    proxy_pass http://127.0.0.1:5001/;
}
location /bot-two/ {
    proxy_pass http://127.0.0.1:5002/;
}
location /bot-three/ {
    proxy_pass http://127.0.0.1:5003/;
}
location /bot-four/ {
    proxy_pass http://127.0.0.1:5004/;
}

8 Deploy with PM2 (Process Manager)

PM2 makes it easy to run and monitor all 4 bots. Create one ecosystem file:

// ecosystem.config.js
module.exports = {
  apps: [
    {
      name: "hermes-babu",
      script: "bot.py",
      cwd: "/home/user/Development/bot-one",
      env: {
        USE_WEBHOOK: "true",
        TELEGRAM_WEBHOOK_URL: "https://yourdomain.com/bot-one/webhook",
        TELEGRAM_BOT_TOKEN: process.env.BOT_ONE_TOKEN,
        ALLOWED_USER_IDS: "123456789",
      },
      port: 5001,
    },
    {
      name: "hermes-sage",
      script: "bot.py",
      cwd: "/home/user/Development/bot-two",
      env: {
        USE_WEBHOOK: "true",
        TELEGRAM_WEBHOOK_URL: "https://yourdomain.com/bot-two/webhook",
        TELEGRAM_BOT_TOKEN: process.env.BOT_TWO_TOKEN,
        ALLOWED_USER_IDS: "987654321",
      },
      port: 5002,
    },
    {
      name: "hermes-mira",
      script: "bot.py",
      cwd: "/home/user/Development/bot-three",
      env: {
        USE_WEBHOOK: "true",
        TELEGRAM_WEBHOOK_URL: "https://yourdomain.com/bot-three/webhook",
        TELEGRAM_BOT_TOKEN: process.env.BOT_THREE_TOKEN,
        ALLOWED_USER_IDS: "111222333",
      },
      port: 5003,
    },
    {
      name: "hermes-nova",
      script: "bot.py",
      cwd: "/home/user/Development/bot-four",
      env: {
        USE_WEBHOOK: "true",
        TELEGRAM_WEBHOOK_URL: "https://yourdomain.com/bot-four/webhook",
        TELEGRAM_BOT_TOKEN: process.env.BOT_FOUR_TOKEN,
        ALLOWED_USER_IDS: "444555666",
      },
      port: 5004,
    },
  ],
};
# Start all 4 bots
pm2 start ecosystem.config.js

# Watch them all
pm2 list

# View logs for a specific bot
pm2 logs hermes-babu --lines 50

# Restart a specific bot (e.g. after a code change)
pm2 restart hermes-babu

# Auto-start on server reboot
pm2 startup
pm2 save

Token security: Store tokens in environment variables or a .env file. Use dotenv in Python: from dotenv import load_dotenv; load_dotenv() at the top of bot.py. Never hardcode tokens in the ecosystem file.

9 Per-User Conversation Memory

The MessageStore in memory/sqlite_store.py already handles per-user memory β€” it keys messages by user_id. This means each user's conversation history is completely isolated:

# memory/sqlite_store.py β€” already architected for multi-user
def save_message(self, user_id: str, role: str, content: str, ...):
    conn.execute(
        "INSERT INTO messages (user_id, role, content, step_count) VALUES (?, ?, ?, ?)",
        (user_id, role, content, step_count)   # ← user_id is the isolation key
    )

def load_history(self, user_id: str, limit: int = 20):
    rows = conn.execute(
        "SELECT role, content FROM messages WHERE user_id = ? ORDER BY timestamp ASC LIMIT ?",
        (user_id, limit)   # ← only loads this user's history
    ).fetchall()

Because each bot runs with its own DB_PATH (e.g., bot_one_memory.db), user A's data in Bot One is completely separated from user B's data in Bot Two β€” even if the same physical SQLite file were shared (which it isn't in this setup).

Memory context windows: The MAX_CONTEXT_MESSAGES in config.py (default: 20) controls how many past messages are loaded into each agent's context. For multi-user setups, consider whether users need longδΈŠδΈ‹ζ–‡ or if a shorter window keeps each persona snappier. Babu might benefit from longer memory; Nova from shorter.

10 Optional: Hermes Skill Tree per User

The Hermes agent approach uses a skill tree that grows as the agent encounters new tasks. For multi-user, you can extend this pattern by giving each persona its own skill tree file:

# bot-one/skills/babu_skills.json
# Bot Two would have its own: bot-two/skills/sage_skills.json
{
  "skills": [
    {"name": "web_search", "use_count": 0, "confidence": 0.0},
    {"name": "code_exec", "use_count": 0, "confidence": 0.0},
    {"name": "file_read", "use_count": 0, "confidence": 0.0},
    {"name": "reddit_summary", "use_count": 0, "confidence": 0.0}
  ],
  "user_preferences": {
    "verbose": true,
    "show_reasoning": true,
    "preferred_topics": []
  }
}

Each bot instance reads its own skill tree at startup. When the agent successfully uses a tool, increment use_count. When confidence > 0.8, the agent starts preferring that tool. This gives each persona a **self-evolving memory of how to help its specific user**.

11 Testing Checklist

Before going live, verify each bot independently:

# Quick smoke test
pm2 list
# All 4 should show "online" with CPU/memory stats

# Per-bot log check
pm2 logs hermes-babu --nostream --lines 20
pm2 logs hermes-sage --nostream --lines 20

Quick Reference

πŸ€– Bot One β€” Babu

Port: 5001
Persona: bot-one/persona.py
DB: bot_one_memory.db
PM2: hermes-babu

πŸ€– Bot Two β€” Sage

Port: 5002
Persona: bot-two/persona.py
DB: bot_two_memory.db
PM2: hermes-sage

πŸ€– Bot Three β€” Mira

Port: 5003
Persona: bot-three/persona.py
DB: bot_three_memory.db
PM2: hermes-mira

πŸ€– Bot Four β€” Nova

Port: 5004
Persona: bot-four/persona.py
DB: bot_four_memory.db
PM2: hermes-nova