Skip to main content
A declared supports_* capability is verified once, at adapter build time β€” an unbacked flag fails loudly before any turn instead of raising mid-conversation. An agent runs behind a Bot; when you build that bot with a custom adapter, the contract catches a broken declaration before the first message.

Quick Start

1

Declare what you support truthfully

Set supports_edit=True and override edit_message β€” the adapter passes verification.
2

The verifier catches a broken promise

Remove the override and the same declaration fails at build time β€” not mid-turn.

How It Works

The check iterates CAPABILITY_BACKING and asks, per declared flag, whether the backing method is overridden.

The two check entry points

Two functions read the same contract; one reports, one raises. verify_capability_contract(adapter) -> list[str] returns a list of human-readable violation strings β€” empty when the contract holds β€” and never raises. Use it to log a warning without failing startup.
enforce_capability_contract(adapter) -> None raises a single CapabilityContractError naming every violation (joined by "; "), or does nothing. It is called automatically by Bot._build_adapter and by the gateway’s _build_channel_adapter.

Where the check runs automatically

Both build paths enforce the contract for you β€” there is no flag or environment variable to opt in. When you build a Bot(...) with a wrapper adapter (praisonai-bot), the contract is verified right after adapter construction, guarded by a lazy import so an older core release simply skips the check. On the gateway construction path (gateway/server.py::_build_channel_adapter), a violation is caught the same way and the channel is recorded as degraded β€” visible in gateway status β€” rather than surfacing later inside a live turn.

Backward compatibility

Built-in adapters already honour the contract, so nothing needs to change. All built-in adapters that declare supports_edit=True (Slack, Telegram, Discord) already override edit_message. No built-in adapter declares supports_delete=True, so no built-in adapter trips the contract. Capabilities left False continue to degrade cleanly, unchanged.
_resolve_capabilities prefers the typed platform_capabilities property; a legacy dict-shaped capabilities attribute is ignored so it is never misread as the transport descriptor. This is what makes wrapper bots like Slack / Telegram / Discord verify correctly.

Adding a new capability flag

Extend the contract by pairing a new flag with a base method that has a default.
1

Add the supports_* field

Add the field to PlatformCapabilities (dataclass plus to_dict / from_dict).
2

Pick a backing method

Choose a method the base class provides a default for.
3

Add the pair to CAPABILITY_BACKING

Register "supports_new": "new_method" in the fail-closed map.
4

Gate the base default (optional)

If the base default should raise on a declared-but-unbacked call (parity with edit_message), gate the default on the flag and raise NotImplementedError otherwise.

Best Practices

A lie in PlatformCapabilities is now caught immediately at build time. Declare supports_edit or supports_delete only when a real implementation backs them.
enforce_capability_contract fails startup loudly. verify_capability_contract returns violations without raising, so dev tooling can log a warning and keep going.
Every entry in CAPABILITY_BACKING is a fail-closed guard. Adding a flag with its backing method keeps future regressions from advertising an unimplemented capability.

Bot Platform Capabilities

The PlatformCapabilities descriptor whose flags this contract verifies.

Bot Platform Adapter

Build a channel β€” declare capabilities and back them honestly.

Bot Platform Plugins

Runtime registration and discovery of platform adapters.

Custom Gateway Channel (Zero-Code)

The gateway construction path that records a violation as a degraded channel.