Skip to main content
Cross-platform sessions let one user keep a single conversation across every messaging platform. The user continues the same conversation on Telegram, then Discord; identity linking mirrors session history across platforms.

How It Works

Quick Start

1

One platform, one user

from praisonai.bots import Bot
from praisonaiagents import Agent

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

Two platforms, one user (recommended)

from praisonai.bots import BotOS, StoreBackedIdentityResolver
from praisonaiagents import Agent

agent = Agent(name="assistant", instructions="Be helpful.")

resolver = StoreBackedIdentityResolver.from_env()

botos = BotOS(
    agent=agent,
    platforms=["telegram", "discord"],
    identity_resolver=resolver,
)
await botos.start()
3

In-process testing / ephemeral

from praisonai.bots import BotOS
from praisonaiagents import Agent
from praisonaiagents.session import InMemoryIdentityResolver

agent = Agent(name="assistant", instructions="Be helpful.")

resolver = InMemoryIdentityResolver()  # Ephemeral for tests
resolver.link("telegram", "12345", "alice")
resolver.link("discord", "snowflake-1", "alice")

botos = BotOS(
    agent=agent,
    platforms=["telegram", "discord"],
    identity_resolver=resolver,
)

Choosing Your Resolver


StoreBackedIdentityResolver

StoreBackedIdentityResolver extends the core FileIdentityResolver with a read-through to the gateway pairing store, so users who have already paired share a session out of the box. Resolution order:
  1. Explicit link registered via link() (highest priority)
  2. Pairing-store label for (user_id, platform) when the channel is paired and carries a non-empty label
  3. f"{platform}:{user_id}" — safe per-platform fallback (no merging)
from praisonai.bots import StoreBackedIdentityResolver

# Defaults: ~/.praisonai/identity.json + gateway pairing store
resolver = StoreBackedIdentityResolver.from_env()

# Override paths
resolver = StoreBackedIdentityResolver.from_env(
    path="/etc/praisonai/identity.json",
    store_dir="/var/lib/praisonai/gateway",
)

# Direct construction with an existing pairing store
from praisonai.gateway.pairing import PairingStore
resolver = StoreBackedIdentityResolver(
    path="~/.praisonai/identity.json",
    pairing_store=PairingStore(),
    use_pairing_label=True,   # default
)
# After channels have been paired with non-empty labels via the pairing CLI:
count = resolver.link_paired()
print(f"Materialised {count} paired channels into explicit links.")
Useful when the pairing store is volatile or about to be rotated — this pins the canonical-id mapping into the resolver’s own JSON file.
If you already use praisonai pairing approve <platform> <code> --label <canonical>, switching to StoreBackedIdentityResolver.from_env() is a one-line upgrade: paired users immediately share one session across all their channels, with no separate praisonai identity link calls required. Run praisonai identity import once to pin those mappings into the explicit link map.

CLI: praisonai identity

Four subcommands manage identity links from the command line.
CommandPurpose
praisonai identity link <platform> <user_id> <canonical>Map a (platform, user_id) to a canonical identity
praisonai identity unlink <platform> <user_id>Remove a mapping
praisonai identity list [canonical]Show all links, or links for a single canonical id
praisonai identity importMaterialise paired channels as explicit identity links
Each subcommand accepts:
OptionDefaultDescription
--path~/.praisonai/identity.json (or $PRAISONAI_IDENTITY_PATH)Override the link-map JSON path
--store-dirgateway defaultOverride the pairing store directory
# Link Alice's Telegram + WhatsApp identities to one canonical id
praisonai identity link telegram 12345 alice
praisonai identity link whatsapp "+44123456789" alice

# Inspect
praisonai identity list alice
# Links for alice:
#   telegram:12345
#   whatsapp:+44123456789

# Or list every mapping
praisonai identity list

# Unlink one channel
praisonai identity unlink telegram 12345

# Promote paired channels (labelled via `praisonai pairing approve ... --label`) into explicit links
praisonai identity import
# ✅ Imported 3 identity link(s) from pairing store

SessionContext for Tools

Any tool the agent calls can read who is messaging:
from praisonaiagents import Agent
from praisonaiagents.session import get_session_context

def whoami() -> str:
    ctx = get_session_context()
    return f"You are {ctx.user_name or ctx.user_id} on {ctx.platform}"

agent = Agent(name="assistant", instructions="Use whoami when asked.", tools=[whoami])

SessionContext Fields

FieldTypeDescription
platformstr"telegram", "discord", "slack", …
chat_idstrPlatform chat / channel id
chat_namestrHuman-readable channel name
thread_idstrThread / topic id (Slack threads, Telegram topics)
user_idstrRaw platform user id
user_namestrDisplay name
unified_user_idstrResult of IdentityResolver.resolve()
originOptional[Origin]NonePlatform-aware origin info — see Platform-Aware Agents
reachable_targetsOptional[List[ReachableTarget]]NoneChannels the agent can deliver to — see Platform-Aware Agents
Use set_session_context / clear_session_context for advanced custom adapters. Returns a token; reset in a finally block.

Mirror for Outbound Deliveries

Cron jobs, scheduled deliveries, and cross-platform replies need to mirror the assistant’s outbound message into the user’s history:
from praisonai.bots import mirror_to_session

# After sending a notification programmatically
mirror_to_session(
    session_mgr=bot._session,
    user_id="alice",
    message_text="Reminder: your standup is in 5 minutes.",
    source_label="cron",
)

Parameters

ParameterTypeDescription
session_mgrBotSessionManagerSession manager instance
user_idstrUnified user ID
message_textstrMessage content to mirror
source_labelstrSource identifier ("cron", "web", "cross_platform")
metadatadictOptional extra metadata
lockthreading.RLockOptional lock for synchronization
Errors are swallowed and logged — a mirror failure must never break the outbound delivery itself.

Storage & Privacy

FileIdentityResolver defaults to ~/.praisonai/identity.json (override via PRAISONAI_IDENTITY_PATH env var or constructor path=). File is written atomically and chmod 0o600.
Identity links are explicit and opt-in. No automatic linking — wire the resolver only after a verified DM-pairing flow confirms the same human controls both accounts.
Without a resolver, the legacy bot_{platform}_{user_id} storage key is preserved bit-for-bit — fully backward compatible.

Best Practices

For production wrapper deployments, use StoreBackedIdentityResolver.from_env() — it picks up paired channels automatically and falls back to the same safe platform:user_id default. Plain FileIdentityResolver is the right choice only when you do not use the pairing system.
from praisonai.bots import StoreBackedIdentityResolver

resolver = StoreBackedIdentityResolver.from_env()
For multi-process/multi-host deployments, back it with SQLite, Redis, or a database:
from praisonaiagents.session import IdentityResolverProtocol

class DatabaseIdentityResolver:
    def resolve(self, platform: str, platform_user_id: str) -> str:
        # Query your database
        pass
    
    def link(self, platform: str, platform_user_id: str, unified_user_id: str) -> None:
        # Store in your database
        pass
Never auto-link based on display name. Always require a DM-verified pairing flow:
# After DM verification succeeds
resolver.link("telegram", telegram_user_id, verified_unified_id)
resolver.link("discord", discord_user_id, verified_unified_id)
Instead of os.environ, read SessionContext — concurrent message handlers won’t trample each other:
def get_user_platform() -> str:
    ctx = get_session_context()
    return ctx.platform  # Thread-safe, context-aware

BotOS

Multi-platform bot orchestrator

Messaging Bots

Platform-specific bot guides

Bot Pairing

Secure unknown-user onboarding

Unknown-User Pairing

Inline-button approval for bots