> ## Documentation Index
> Fetch the complete documentation index at: https://praison.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Cross-Platform Sessions

> One conversation across Telegram, Discord, Slack — unified per-user history with opt-in identity linking

Cross-platform sessions let one user keep a single conversation across every messaging platform.

<Tabs>
  <Tab title="Recommended (with pairing)">
    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    from praisonai.bots import BotOS, StoreBackedIdentityResolver
    from praisonaiagents import Agent

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

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

    BotOS(
        agent=agent,
        platforms=["telegram", "whatsapp", "discord", "slack"],
        identity_resolver=resolver,
    ).run()
    ```
  </Tab>

  <Tab title="Explicit links only">
    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    from praisonai.bots import BotOS
    from praisonaiagents import Agent
    from praisonaiagents.session import FileIdentityResolver

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

    resolver = FileIdentityResolver()                        # ~/.praisonai/identity.json
    resolver.link("telegram", "12345",       "alice")
    resolver.link("discord",  "snowflake-1", "alice")

    BotOS(
        agent=agent,
        platforms=["telegram", "discord"],
        identity_resolver=resolver,
    ).run()
    ```
  </Tab>
</Tabs>

The user continues the same conversation on Telegram, then Discord; identity linking mirrors session history across platforms.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph LR
    subgraph "Cross-Platform Mirror"
        T[📱 Telegram: 12345] --> R[🪪 IdentityResolver]
        D[💬 Discord: snowflake-1] --> R
        S[💼 Slack: U-A] --> R
        R --> U[👤 alice]
        U --> H[💾 One Session History]
    end

    classDef input fill:#6366F1,stroke:#7C90A0,color:#fff
    classDef process fill:#F59E0B,stroke:#7C90A0,color:#fff
    classDef output fill:#10B981,stroke:#7C90A0,color:#fff

    class T,D,S input
    class R process
    class U,H output

```

## How It Works

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
sequenceDiagram
    participant Alice as 👤 Alice
    participant TG as 📱 Telegram
    participant DC as 💬 Discord
    participant Bot as 🤖 BotOS
    participant Hist as 💾 Unified History

    Alice->>TG: "My favourite colour is octarine."
    TG->>Bot: chat(user_id=12345)
    Bot->>Hist: append (key=alice)
    Bot-->>TG: "Got it!"
    Note over Alice,Hist: Hours later, different platform

    Alice->>DC: "What's my favourite colour?"
    DC->>Bot: chat(user_id=snowflake-1)
    Bot->>Hist: load (key=alice — same!)
    Bot-->>DC: "Octarine."
```

## Quick Start

<Steps>
  <Step title="One platform, one user">
    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    from praisonai.bots import Bot
    from praisonaiagents import Agent

    agent = Agent(name="assistant", instructions="Be helpful.")
    bot = Bot("telegram", agent=agent)
    await bot.start()
    ```
  </Step>

  <Step title="Two platforms, one user (recommended)">
    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    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()
    ```
  </Step>

  <Step title="In-process testing / ephemeral">
    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    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,
    )
    ```
  </Step>
</Steps>

***

## Choosing Your Resolver

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph TB
    Q{What do you need?} -->|tests / single proc| IM[InMemoryIdentityResolver]
    Q -->|production + paired users| SB[StoreBackedIdentityResolver<br/>⭐ recommended]
    Q -->|production, no pairing| FI[FileIdentityResolver]
    Q -->|multi-process / multi-host| Custom[Custom IdentityResolverProtocol<br/>SQLite / Redis / DB]

    classDef opt fill:#189AB4,stroke:#7C90A0,color:#fff
    classDef star fill:#10B981,stroke:#7C90A0,color:#fff
    class IM,FI,Custom opt
    class SB star
```

***

## 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.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph TB
    Start[📨 Incoming message<br/>platform + user_id] --> L1{Explicit link?}
    L1 -->|Yes| Out[👤 canonical id]
    L1 -->|No| L2{Paired with<br/>non-empty label?}
    L2 -->|Yes| Out
    L2 -->|No| Fallback[🪪 platform:user_id]

    classDef input fill:#6366F1,stroke:#7C90A0,color:#fff
    classDef decide fill:#F59E0B,stroke:#7C90A0,color:#fff
    classDef good fill:#10B981,stroke:#7C90A0,color:#fff
    classDef neutral fill:#189AB4,stroke:#7C90A0,color:#fff

    class Start input
    class L1,L2 decide
    class Out good
    class Fallback neutral
```

**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)

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
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
)
```

### link\_paired() — Promote Paired Users to Explicit Links

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# 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.

<Tip>
  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.
</Tip>

***

## CLI: praisonai identity

Four subcommands manage identity links from the command line.

| Command                                                    | Purpose                                                |
| ---------------------------------------------------------- | ------------------------------------------------------ |
| `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 import`                                | Materialise paired channels as explicit identity links |

Each subcommand accepts:

| Option        | Default                                                      | Description                          |
| ------------- | ------------------------------------------------------------ | ------------------------------------ |
| `--path`      | `~/.praisonai/identity.json` (or `$PRAISONAI_IDENTITY_PATH`) | Override the link-map JSON path      |
| `--store-dir` | gateway default                                              | Override the pairing store directory |

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# 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:

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
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

| Field               | Type                              | Description                                        |                                                                                                  |
| ------------------- | --------------------------------- | -------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
| `platform`          | `str`                             | `"telegram"`, `"discord"`, `"slack"`, …            |                                                                                                  |
| `chat_id`           | `str`                             | Platform chat / channel id                         |                                                                                                  |
| `chat_name`         | `str`                             | Human-readable channel name                        |                                                                                                  |
| `thread_id`         | `str`                             | Thread / topic id (Slack threads, Telegram topics) |                                                                                                  |
| `user_id`           | `str`                             | Raw platform user id                               |                                                                                                  |
| `user_name`         | `str`                             | Display name                                       |                                                                                                  |
| `unified_user_id`   | `str`                             | Result of `IdentityResolver.resolve()`             |                                                                                                  |
| `origin`            | `Optional[Origin]`                | `None`                                             | Platform-aware origin info — see [Platform-Aware Agents](/features/platform-aware-agents)        |
| `reachable_targets` | `Optional[List[ReachableTarget]]` | `None`                                             | Channels the agent can deliver to — see [Platform-Aware Agents](/features/platform-aware-agents) |

<Note>
  Use `set_session_context` / `clear_session_context` for advanced custom adapters. Returns a token; reset in a `finally` block.
</Note>

***

## 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:

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
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

| Parameter      | Type                | Description                                               |
| -------------- | ------------------- | --------------------------------------------------------- |
| `session_mgr`  | `BotSessionManager` | Session manager instance                                  |
| `user_id`      | `str`               | Unified user ID                                           |
| `message_text` | `str`               | Message content to mirror                                 |
| `source_label` | `str`               | Source identifier (`"cron"`, `"web"`, `"cross_platform"`) |
| `metadata`     | `dict`              | Optional extra metadata                                   |
| `lock`         | `threading.RLock`   | Optional lock for synchronization                         |

<Note>
  Errors are swallowed and logged — a mirror failure must never break the outbound delivery itself.
</Note>

***

## Storage & Privacy

<Note>
  `FileIdentityResolver` defaults to `~/.praisonai/identity.json` (override via `PRAISONAI_IDENTITY_PATH` env var or constructor `path=`). File is written atomically and chmod 0o600.
</Note>

<Warning>
  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.
</Warning>

<Tip>
  Without a resolver, the legacy `bot_{platform}_{user_id}` storage key is preserved bit-for-bit — fully backward compatible.
</Tip>

***

## Best Practices

<AccordionGroup>
  <Accordion title="Use StoreBackedIdentityResolver for production (with pairing)">
    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.

    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    from praisonai.bots import StoreBackedIdentityResolver

    resolver = StoreBackedIdentityResolver.from_env()
    ```
  </Accordion>

  <Accordion title="Use FileIdentityResolver for explicit-only links">
    For production single-host bots without pairing integration, `FileIdentityResolver` provides persistent storage with atomic writes and proper file permissions.

    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    from praisonaiagents.session import FileIdentityResolver

    resolver = FileIdentityResolver()  # ~/.praisonai/identity.json
    ```
  </Accordion>

  <Accordion title="Implement custom IdentityResolverProtocol for scale">
    For multi-process/multi-host deployments, back it with SQLite, Redis, or a database:

    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    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
    ```
  </Accordion>

  <Accordion title="Pair before linking">
    Never auto-link based on display name. Always require a DM-verified pairing flow:

    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    # After DM verification succeeds
    resolver.link("telegram", telegram_user_id, verified_unified_id)
    resolver.link("discord", discord_user_id, verified_unified_id)
    ```
  </Accordion>

  <Accordion title="Read SessionContext in tools">
    Instead of `os.environ`, read `SessionContext` — concurrent message handlers won't trample each other:

    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    def get_user_platform() -> str:
        ctx = get_session_context()
        return ctx.platform  # Thread-safe, context-aware
    ```
  </Accordion>
</AccordionGroup>

***

## Related

<CardGroup cols={2}>
  <Card title="BotOS" icon="robot" href="/docs/features/botos">
    Multi-platform bot orchestrator
  </Card>

  <Card title="Messaging Bots" icon="message-circle" href="/docs/features/messaging-bots">
    Platform-specific bot guides
  </Card>

  <Card title="Bot Pairing" icon="handshake" href="/docs/features/bot-pairing">
    Secure unknown-user onboarding
  </Card>

  <Card title="Unknown-User Pairing" icon="user-check" href="/docs/features/bot-unknown-user-pairing">
    Inline-button approval for bots
  </Card>
</CardGroup>
