Skip to content

Duplicate session-manager setup logic across bot platform adapters #2134

Description

@MervinPraison

Summary

Each bot platform adapter in the praisonai wrapper independently re-implements the same BotSessionManager setup block — session-store acquisition, reset-policy extraction, and the backward-compatible max_history resolution. The block is near-identical across 7+ adapters, so any fix or change to session-config resolution must currently be applied in every adapter by hand. This is a wrapper-health / maintenance concern, not a feature cut.

Current behaviour

The same setup sequence is copied verbatim (only the platform= string and an optional run_control= differ) in each adapter constructor:

  • src/praisonai/praisonai/bots/telegram.py:135-156
  • src/praisonai/praisonai/bots/slack.py:106-126
  • src/praisonai/praisonai/bots/discord.py:~95-114
  • src/praisonai/praisonai/bots/whatsapp.py:~139-158
  • src/praisonai/praisonai/bots/email.py
  • src/praisonai/praisonai/bots/linear.py
  • src/praisonai/praisonai/bots/agentmail.py

Representative block (from slack.py):

try:
    from praisonaiagents.session import get_default_session_store
    _store = get_default_session_store()
except Exception:
    _store = None

# Extract reset policy from config
reset_policy = None
if hasattr(self.config, 'session') and self.config.session:
    if hasattr(self.config.session, 'reset') and self.config.session.reset:
        from ._reset_policy import SessionResetPolicy
        reset_policy = SessionResetPolicy.from_dict(self.config.session.reset.model_dump())

# Support backward compatibility with max_history at channel level
max_history = 100
if hasattr(self.config, 'max_history') and self.config.max_history is not None:
    max_history = self.config.max_history
elif hasattr(self.config, 'session') and self.config.session:
    if hasattr(self.config.session, 'max_history') and self.config.session.max_history is not None:
        max_history = self.config.session.max_history

self._session: BotSessionManager = BotSessionManager(
    max_history=max_history,
    store=_store,
    platform="slack",
    reset_policy=reset_policy,
)

The telegram.py copy additionally threads a run_control= argument, but the store/reset-policy/max_history resolution is identical.

Why it matters

  • Maintenance / drift risk: the backward-compatible max_history precedence (channel-level vs session.max_history) and the reset-policy extraction are duplicated 7+ times. A bug fix or a new config key must be replicated everywhere; one missed copy silently diverges a single platform's session behaviour.
  • Duplication: ~15–20 lines × 7 adapters of identical config-resolution logic.

This is a maintenance/duplication concern only — there is no measurable import or hot-path cost.

Category

Duplicate

Capability preserved

  • All bot platforms (Telegram, Discord, Slack, WhatsApp, Email, Linear, AgentMail) keep identical runtime behaviour.
  • BotSessionManager, reset policies, run_control, max_history backward compatibility, and session-store wiring are all unchanged.
  • No CLI, YAML, or Python surface changes; bot reliability/session handling untouched.

Proposed approach

Move the shared resolution into a single helper alongside BotSessionManager in bots/_session.py, and have each adapter call it. Pure refactor — no behavioural change.

Resolution sketch

# bots/_session.py  (new module-level helper, same owner as BotSessionManager)
def build_session_manager(config, platform, *, run_control=None) -> "BotSessionManager":
    try:
        from praisonaiagents.session import get_default_session_store
        store = get_default_session_store()
    except Exception:
        store = None

    reset_policy = None
    if getattr(config, "session", None) and getattr(config.session, "reset", None):
        from ._reset_policy import SessionResetPolicy
        reset_policy = SessionResetPolicy.from_dict(config.session.reset.model_dump())

    max_history = 100
    if getattr(config, "max_history", None) is not None:
        max_history = config.max_history
    elif getattr(config, "session", None) and getattr(config.session, "max_history", None) is not None:
        max_history = config.session.max_history

    return BotSessionManager(
        max_history=max_history,
        store=store,
        platform=platform,
        reset_policy=reset_policy,
        run_control=run_control,
    )
# Each adapter (e.g. slack.py)
# Before: ~20 lines of store/reset/max_history boilerplate + constructor
# After:
from ._session import build_session_manager
self._session = build_session_manager(self.config, platform="slack")
# telegram.py passes run_control=run_control

Layer placement

  • Primary layer: wrapper (src/praisonai/praisonai/bots/)
  • Touches core/tools/plugins: none — continues to consume praisonaiagents.session.get_default_session_store exactly as today.
  • 3-way surface (CLI + YAML + Python): preserved — yes (no surface change).

Severity

Low — maintenance/duplication only; no import or hot-path impact, but removes a real config-drift risk across 7 adapters.

Validation

  • Traced: identical block confirmed in telegram.py, slack.py, discord.py, whatsapp.py (and present in email.py, linear.py, agentmail.py); each adapter constructs BotSessionManager independently with no shared __init__.
  • Not intentional robustness: the duplication adds no safety; it is copy-paste boilerplate.
  • No user-facing regression: behaviour is byte-for-byte preserved; only the call site changes.

Keep unchanged

  • Centralised _session.BotSessionManager.chat() (already single source of truth) — do not touch.
  • Platform-specific approval backends, rate limiters, debouncer/ack init, pairing wiring — these are platform-specific and correctly separated.
  • Lazy SDK imports (python-telegram-bot, discord.py, slack_bolt) inside start() — keep as-is.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingclaudeAuto-trigger Claude analysisenhancementNew feature or requestrefactor

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions