Long-Running Bots

Run bots 24/7 without memory leaks or silent corruption

Run Telegram, Discord, Slack, and other bots for weeks — bounded lock caches and agent-scoped locks keep memory stable with no extra configuration.

python
from praisonaiagents import Agent
from praisonai.bots import Bot

agent = Agent(name="assistant", instructions="Be helpful")
bot = Bot("telegram", agent=agent)
bot.run()

The user messages your bot channel; session locks stay bounded while the same agent handles concurrent chats.

Diagram

👤 User

🤖 Bot

BotSessionManager

Agent locks
WeakKeyDictionary

Per-user locks
LRU 10k / 1h TTL

How It Works

Diagram
Long-Running BotsAgentUserLong-Running BotsAgentUserRequestProcess requestResult Agent–>>User: Response

Quick Start

  1. Simple Usage
    python
    from praisonaiagents import Agent
    from praisonai.bots import Bot
    
    agent = Agent(name="assistant", instructions="Be helpful")
    bot = Bot("telegram", agent=agent)
    bot.run()  # Bounded locks are active by default

    Nothing extra is required — debounce, session, and run-control paths share the same bounded per-user lock cache.

  2. With Configuration
    python
    from praisonaiagents import Agent
    from praisonaiagents import BotConfig
    from praisonai.bots import Bot
    
    agent = Agent(name="assistant", instructions="Be helpful")
    config = BotConfig(session_ttl=86400)  # Reap idle sessions after 24h
    
    bot = Bot("telegram", agent=agent, config=config)
    bot.run()

What’s Bounded by Default

ResourceDefault limitWhen it cleans up
Per-user lock cache10,000 entries, 1 hour TTLIdle, unlocked entries evict automatically
Agent locksOne per agent instanceRemoved when the agent is garbage-collected
Session historiesOpt-in via session_ttlSee Session Persistence

Operational Knobs

For mid-run cancellation and stale session cleanup, see Bot Run Control — SessionRunControl.cleanup_stale_sessions(max_age_seconds=3600) removes abandoned run-control state.

Multi-Agent Safety

Earlier releases keyed agent locks on id(agent). CPython may reuse that integer after garbage collection, so long-running gateways that recreated agents per request could silently mix up two users’ histories. Agent locks now follow agent lifetime via WeakKeyDictionary; per-user locks stay bounded even if you never call cleanup helpers.

Best Practices

Reuse one agent instance per bot

Create the agent once at startup — agent locks follow instance lifetime via WeakKeyDictionary, so recreating agents per request is safe but wasteful.

Set session_ttl for busy channels

Default lock caches evict after 1 hour; set BotConfig(session_ttl=…) when conversations stay idle longer than your support SLA.

Run cleanup_stale_sessions periodically

Call SessionRunControl.cleanup_stale_sessions on a schedule in long-lived gateways to drop abandoned run-control state.

Upgrade after PR #1972

Ensure you are on a release that includes bounded agent locks — earlier builds could mix histories when id(agent) was reused.

Auto-reconnect (default)

Bot(...).run() now supervises its own inbound connection — if the socket drops, ChannelSupervisor reconnects with capped exponential backoff and restarts an unhealthy channel, matching the resilience BotOS and the gateway already provide.

Diagram

Bot(…).run()

drop

healthy

🤖 Bot start

🛡 ChannelSupervisor

🔌 Adapter inbound loop

⏱ capped backoff

✅ serving messages

  1. Default supervision on Slack
    python
    from praisonaiagents import Agent
    from praisonai_bot import Bot
    
    agent = Agent(name="Support", instructions="Answer support questions")
    Bot("slack", agent=agent).run()   # auto-reconnects on drop
  2. Same code, every platform

    The identical pattern works for Discord, Email, Linear, and WhatsApp — no extra flags:

    python
    from praisonaiagents import Agent
    from praisonai_bot import Bot
    
    agent = Agent(name="Support", instructions="Answer support questions")
    Bot("discord", agent=agent).run()   # supervised too
  3. Opt out under an external supervisor

    Set enable_supervision=False when embedding, testing, or running under systemd/k8s:

    python
    from praisonaiagents import Agent
    from praisonai_bot import Bot
    
    agent = Agent(name="Support", instructions="Answer support questions")
    Bot("slack", agent=agent, enable_supervision=False).run()

Which platforms are supervised by default?

PlatformSupervised by default?Why
Slack, Discord, WhatsApp, Email, Linear, AgentMail✅ YesStandard adapters — supervision wraps the inbound run loop.
Telegram❌ No — self-managedTelegram already runs its own reconnect loop; wrapping would double-drive.
Relay transport❌ No — externalOut-of-process reconnect + scale-to-zero dormancy.

When to opt out (enable_supervision=False)

Diagram

Who owns restarts?

Bot run alone in a process

systemd / k8s / external supervisor

Under a BotOS / gateway

Unit test with a fake adapter

Leave enable_supervision=True — default

enable_supervision=False

enable_supervision=False — BotOS supervises

Don't stack supervisors

If a Bot runs inside BotOS or under systemd/Kubernetes, set enable_supervision=False. Two supervisors on one connection means double reconnect attempts and double backoff — let the outer supervisor own restarts.

Email / Linear / WhatsApp / AgentMail — `Bot.run()` no longer returns immediately

Adapters whose start() returns immediately (Email IMAP poll, Linear/WhatsApp/AgentMail webhook servers) now stay supervised until they actually stop. Scripts that relied on .run() returning early should switch to await bot.start() and manage lifecycle explicitly, or spawn the run in a background task.

Related