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

# Gateway Runtime Contract

> Let your custom channel adapter receive the gateway's five reliability seams — admission control, delivery routing, identity resolution, identity canonicalization, per-turn locking

Custom channel adapters implement `SupportsGatewayRuntime` to receive the gateway's five reliability seams; miss the contract and the gateway fails loudly instead of silently dropping admission control, delivery routing, and cross-platform turn locking. The runtime methods `Bot` / `BotOS` drive (`start`, `stop`, `send_message`, `probe`, `health`) are already inherited from [`BasePlatformAdapter`](https://praison.ai/docs/features/bot-platform-adapter); this page covers the five gateway seams on top of them.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph LR
    subgraph "Gateway Runtime Contract"
        G[🛡️ Gateway] --> S[📦 GatewayRuntimeSeams]
        S --> A[🔌 attach_gateway_runtime]
        A --> W[✅ 5 Seams Wired]
    end

    classDef gateway fill:#8B0000,stroke:#7C90A0,color:#fff
    classDef carrier fill:#6366F1,stroke:#7C90A0,color:#fff
    classDef adapter fill:#189AB4,stroke:#7C90A0,color:#fff
    classDef wired fill:#10B981,stroke:#7C90A0,color:#fff

    class G gateway
    class S carrier
    class A adapter
    class W wired
```

## Quick Start

Adapters that build a `BotSessionManager` satisfy the contract for free; others implement one method.

**1. You don't have to do anything**

Delegate to `BotSessionManager` — it already implements the contract, so your adapter inherits it through `self._session`.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonai_bot.bots._session import BotSessionManager


class MyBot:
    def __init__(self, agent):
        # The gateway finds attach_gateway_runtime on self._session
        # and wires all five seams automatically.
        self._session = BotSessionManager(agent=agent)
```

**2. Implement the contract yourself**

Define `attach_gateway_runtime` and copy each non-`None` seam exactly as `BotSessionManager` does — a `None` seam means "keep what you already have".

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents.bots import GatewayRuntimeSeams, SupportsGatewayRuntime


class MyBot:
    def attach_gateway_runtime(self, runtime: GatewayRuntimeSeams) -> None:
        if runtime.identity_resolver is not None:
            self._identity_resolver = runtime.identity_resolver
        if runtime.identity_canonicalizer is not None:
            self._identity_canonicalizer = runtime.identity_canonicalizer
        if runtime.delivery_router is not None:
            self._delivery_router = runtime.delivery_router
        if runtime.admission_gate is not None:
            self._admission_gate = runtime.admission_gate
        if runtime.turn_lock_map is not None:
            self._locks = runtime.turn_lock_map


# Runtime-checkable: isinstance confirms the contract is satisfied.
assert isinstance(MyBot(), SupportsGatewayRuntime)
```

***

## How It Works

The gateway builds each adapter once, then hands it a typed `GatewayRuntimeSeams` carrier through `attach_gateway_runtime`.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
sequenceDiagram
    participant User
    participant Gateway
    participant Bot as Bot._attach_gateway_runtime
    participant Adapter as adapter.attach_gateway_runtime

    User->>Gateway: start channel
    Gateway->>Bot: build adapter
    Note over Bot: all five seams None? → no-op return
    Bot->>Bot: import GatewayRuntimeSeams
    Bot->>Adapter: attach_gateway_runtime(seams)
    Adapter->>Adapter: apply non-None seams
    Adapter-->>Bot: seams wired
    Bot-->>Gateway: adapter ready
```

The gateway resolves the target in a fixed order:

| Step | Condition | Outcome |
| - | - | - |
| 1 | All five seams are `None` | Return immediately — pure no-op. |
| 2 | `from praisonaiagents.bots import ...` fails | Fall back to the pre-contract splices (compatibility only). |
| 3 | `adapter.attach_gateway_runtime` is callable | Preferred path — call it with the seams. |
| 4 | `adapter._session` / `adapter._session_mgr` implements it | Built-in path via `BotSessionManager`. |
| 5 | None of the above | Raise `GatewayAdapterContractError`. |

***

## The Five Seams

Each seam maps to a gateway reliability guarantee and a feature doc.

| Field | Type | Default | Description | Feature doc |
| - | - | - | - | - |
| `identity_resolver` | `Optional[Any]` | `None` | Unifies the same human across platforms onto one session key. | [Cross-Platform Mirror](https://praison.ai/docs/features/cross-platform-mirror.md) |
| `identity_canonicalizer` | `Optional[Any]` | `None` | Reconciles a volatile raw platform id (WhatsApp JID→LID, number↔UUID alias flip) to a stable canonical id **before** the `identity_resolver` runs, so the same human keeps one session instead of silently forking. Any object satisfying `IdentityCanonicalizerProtocol` — an active `BasePlatformAdapter` implements it. | [Identity Canonicalization](https://praison.ai/docs/features/gateway-identity-canonicalization.md) |
| `delivery_router` | `Optional[Any]` | `None` | Backing router for the `send_message` tool (proactive mid-turn delivery). | [Proactive Delivery](https://praison.ai/docs/features/proactive-delivery.md) |
| `admission_gate` | `Optional[Any]` | `None` | Gateway-wide concurrency ceiling / fair queue / backpressure on inbound runs. | [Gateway Admission Control](https://praison.ai/docs/features/gateway-admission-control.md) |
| `turn_lock_map` | `Optional[Any]` | `None` | Shared per-turn lock map for cross-platform turn serialisation on the resolved session id. See [Gateway Turn Lock](https://praison.ai/docs/features/gateway-turn-lock.md) for the pure protocol contract (`TurnLockProtocol` / `LocalTurnLock`) that this seam now conforms to. | [Cross-Platform Mirror](https://praison.ai/docs/features/cross-platform-mirror.md) |

A seam left `None` means the gateway has nothing to inject for it; leave whatever value you already hold untouched.

***

## The Contract

Three symbols define the contract, all imported from one place.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents.bots import (
    GatewayRuntimeSeams,
    SupportsGatewayRuntime,
    GatewayAdapterContractError,
)
```

`GatewayRuntimeSeams` is the typed carrier the gateway fills and passes to your adapter.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from dataclasses import dataclass
from typing import Any, Optional


@dataclass
class GatewayRuntimeSeams:
    identity_resolver: Optional[Any] = None
    identity_canonicalizer: Optional[Any] = None
    delivery_router: Optional[Any] = None
    admission_gate: Optional[Any] = None
    turn_lock_map: Optional[Any] = None
```

`SupportsGatewayRuntime` is the runtime-checkable Protocol your adapter satisfies.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from typing import Protocol, runtime_checkable


@runtime_checkable
class SupportsGatewayRuntime(Protocol):
    def attach_gateway_runtime(self, runtime: "GatewayRuntimeSeams") -> None:
        ...
```

`GatewayAdapterContractError` is the loud failure raised when seams cannot be delivered.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
class GatewayAdapterContractError(TypeError):
    """Raised when a channel adapter cannot receive the gateway runtime seams."""
```

***

## When `GatewayAdapterContractError` Fires

The gateway raises this error only when it has seams to inject but your adapter neither implements `attach_gateway_runtime` nor exposes a `_session` / `_session_mgr` that does — the raise is intentional, replacing a silent loss of reliability guarantees.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph TB
    Start[🔌 Adapter built] --> Q1{Seams to inject?}
    Q1 -->|No| NoOp[✅ No-op return]
    Q1 -->|Yes| Q2{Adapter implements<br/>attach_gateway_runtime?}
    Q2 -->|Yes| Call[✅ Call it with seams]
    Q2 -->|No| Q3{_session / _session_mgr<br/>implements it?}
    Q3 -->|Yes| Session[✅ Delegate to session]
    Q3 -->|No| Raise[❌ GatewayAdapterContractError]

    classDef start fill:#8B0000,stroke:#7C90A0,color:#fff
    classDef decision fill:#F59E0B,stroke:#7C90A0,color:#fff
    classDef ok fill:#10B981,stroke:#7C90A0,color:#fff
    classDef err fill:#8B0000,stroke:#7C90A0,color:#fff

    class Start start
    class Q1,Q2,Q3 decision
    class NoOp,Call,Session ok
    class Raise err
```

The fix is one of the two Quick Start options: build a `BotSessionManager` on `self._session`, or implement `attach_gateway_runtime` directly.

***

## Compatibility

The wrapper permits `praisonaiagents >= 1.6.152`; on a core that predates the contract the import in step 2 fails and the gateway transparently falls back to the pre-contract private-attribute splices (`_identity_resolver`, `_delivery_router`, `_admission_gate`, `_locks`) written onto `adapter._session` or `adapter._session_mgr`.

> [!NOTE]
>
> The legacy fallback is a compatibility shim, not a public API. Write to the contract (`attach_gateway_runtime`) — the fallback exists solely so older-but-supported installs keep wiring the seams.

***

## Best Practices

> [accordion] Delegate to BotSessionManager when you can
>
> If your adapter already builds a `BotSessionManager`, expose it as `self._session` and do nothing else. The gateway finds `attach_gateway_runtime` on the session and wires all five seams for free.

> [accordion] Apply only non-None seams
>
> Every seam is optional. A `None` seam means the gateway has nothing for it — leave your existing value in place. Copy each attribute only when it is not `None`, exactly as the reference implementation does.

> [accordion] Let the contract error fire — don't suppress it
>
> `GatewayAdapterContractError` means the gateway had reliability seams to inject and your adapter could not receive them. Fix the adapter rather than catching the error; suppressing it silently drops admission control, delivery routing, identity resolution, and per-turn locking.

> [accordion] Migrating an old duck-typed adapter
>
> If your adapter previously relied on the gateway splicing `_session._identity_resolver` and friends, add a single `attach_gateway_runtime` method that assigns each non-`None` seam to the same slot. This makes the wiring explicit and passes the `SupportsGatewayRuntime` isinstance check.

***

## Related

> [card] [Bot Platform Plugins](https://praison.ai/docs/features/bot-platform-plugins.md)
>
> Register a custom platform adapter with `register_platform(name, cls)`.

> [card] [Bot Platform Adapter](https://praison.ai/docs/features/bot-platform-adapter.md)
>
> Build a channel adapter by subclassing `BasePlatformAdapter`.

> [card] [Gateway Admission Control](https://praison.ai/docs/features/gateway-admission-control.md)
>
> The admission gate seam — concurrency ceiling and backpressure.

> [card] [Cross-Platform Mirror](https://praison.ai/docs/features/cross-platform-mirror.md)
>
> The identity resolver and turn lock map seams across platforms.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.