DEV Community

John Wick
John Wick

Posted on

Multiple Bots in One Process with StayPresent

How to run multiple python bots in a single process using StayPresent's bot_file list and bots parameter, with independent crash recovery for each.

Running Multiple Bots in One Process with StayPresent

If you're managing a Telegram bot pipeline with several workers, or a Discord bot that runs alongside a separate polling worker, deploying each one as its own hosting service quickly gets expensive and repetitive. StayPresent's multi-bot support lets you run multiple python bots from a single staypresent.run() call, each fully and independently supervised, while sharing one HTTP server and one deployment.

Table of Contents

  1. Why Run Multiple Bots Together
  2. The Simple Way: A List of Files
  3. Per-Bot Configuration with bots
  4. How Independent Supervision Works
  5. Shared Failures vs Independent Failures
  6. Log Labels When Filenames Collide
  7. Full Example
  8. Best Practices
  9. Common Mistakes
  10. FAQs
  11. Conclusion

Why Run Multiple Bots Together

A common real-world setup is a Telegram ingestion bot and a separate upload/processing worker that share a queue or database — logically two processes, but deployed together because they belong to the same service. Running each as a separate hosting deployment means twice the port-binding boilerplate, twice the health checks, and twice the deployment overhead for something that's conceptually one unit.

The Simple Way: A List of Files

bot_file accepts either a single string or a list of strings:

import staypresent

staypresent.run(["telegram_bot.py", "discord_bot.py"])
Enter fullscreen mode Exit fullscreen mode

Both scripts launch as independent subprocesses, sharing the same bot_args/env if provided.

Per-Bot Configuration with bots

When each bot needs different arguments or environment variables, use bots — a list of dicts, one per bot:

import staypresent

staypresent.run(bots=[
    {"file": "telegram_bot.py", "args": ["--verbose"]},
    {"file": "discord_bot.py", "env": {"SHARD": "0"}},
    {"file": "worker.py"},
])
Enter fullscreen mode Exit fullscreen mode

bots is mutually exclusive with bot_file/bot_args/env — passing both raises TypeError, so there's no ambiguity about which configuration style is active. Each entry requires a "file" key; "args" and "env" are optional per entry.

How Independent Supervision Works

Every bot in the list gets its own restart counter and its own dedicated monitoring thread. A crash in one bot — and any subsequent restart attempts — has zero effect on the others. If your Telegram bot hits an unhandled exception and restarts three times, your Discord bot keeps running the entire time, completely unaffected.

Before anything starts, every bot file listed is validated to exist — a missing file raises FileNotFoundError immediately, before any process launches. And if the very first launch attempt of any bot fails outright (a bad interpreter path, the system running out of file descriptors), StayPresent terminates any bots that already started and raises the failure immediately, so you never end up with orphaned, untracked processes left running in the background.

Shared Failures vs Independent Failures

staypresent.run() waits for every bot to finish — or fail permanently — before returning or exiting the process. If a bot exhausts its max_restarts budget, it's marked permanently failed, but the other bots keep running normally. Only once every bot has finished does StayPresent decide the process's own exit code: non-zero if any bot ended in a permanently-failed state, and specifically the exit code of the lowest-indexed failing bot if more than one failed — deterministic, regardless of which bot's monitor thread happens to finish first.

staypresent.run(
    bots=[
        {"file": "critical_bot.py"},
        {"file": "optional_worker.py"},
    ],
    max_restarts=5,
)
Enter fullscreen mode Exit fullscreen mode

If optional_worker.py permanently fails but critical_bot.py keeps running fine, the whole process only exits (with optional_worker.py's exit code) once critical_bot.py itself eventually finishes too.

Log Labels When Filenames Collide

Bots are tracked by their position in the list, not by filename — two bots can share the exact same filename (shard_a/bot.py and shard_b/bot.py) with no functional conflict. But identical filenames in the logs would make crash/restart messages ambiguous, so StayPresent detects the collision automatically and logs each bot's full file path instead of just the filename in that case:

bot[0] 'shard_a/bot.py' crashed, restarting...
bot[1] 'shard_b/bot.py' crashed, restarting...
Enter fullscreen mode Exit fullscreen mode

Bots with non-colliding filenames still get the shorter, plain filename label.

Full Example

import os
import staypresent

staypresent.web.json({"status": "running", "bots": 3})

staypresent.run(
    bots=[
        {"file": "telegram_bot.py", "args": ["--verbose"]},
        {"file": "discord_bot.py", "env": {"SHARD": "0"}},
        {"file": "queue_worker.py"},
    ],
    port=int(os.getenv("PORT", 8080)),
    max_restarts=5,
    restart_delay=2.0,
)
Enter fullscreen mode Exit fullscreen mode

Best Practices

  • Use bots (not bot_file + shared env) whenever different processes genuinely need different configuration — it keeps the config next to the file it applies to instead of implicitly shared.
  • Give each bot its own status endpoint with web.text()/web.json() at a distinct path so you can check any individual bot's state without guessing which one a shared root response refers to.
  • Keep a "critical" bot and "optional" bots in mind when tuning max_restarts — a bot you can't afford to lose deserves a higher budget than a background helper.

Common Mistakes

  • Mixing bot_file and bots in the same call. This raises TypeError immediately — pick one style per run() call.
  • Assuming one bot's crash stops the others. It doesn't — each bot is supervised entirely independently, which is often surprising the first time you see it in the logs.
  • Forgetting that run() only returns once every bot is done. If one bot runs forever and another exits cleanly, the process is still alive waiting on the first.

FAQs

Can I mix bot_file-style and bot_module-style bots?
Yes, inside bots — set "file" for some entries and "module" for others, freely mixed in the same list.

Does each bot get its own restart budget?
Yes — max_restarts, restart_delay, and restart_reset_after apply per bot, not shared across the whole group.

What if I only need one bot?
bot_file="bot.py" still works exactly as before — multi-bot support doesn't change single-bot usage at all.

Conclusion

Whether you're pairing a Telegram ingestion bot with a processing worker, or running several unrelated scripts side by side, StayPresent lets you run multiple python bots from one deployment, one port, and one run() call — with each bot's crashes, restarts, and logs kept fully independent of the others.

pip install staypresent[prod]
Enter fullscreen mode Exit fullscreen mode

Top comments (0)