Inbound Platform Events

Receive reactions, edits, deletions, member changes, and thread creations from bot platforms

Inbound platform events let your agent hear more than text — reactions, edits, deletions, member changes, and thread creations from Discord, Telegram, Slack, and other supported platforms.

Diagram

Inbound Platform Events

👤 User reacts / edits / joins

🔌 Platform SDK

⚙️ Adapter normalises

🪝 fire_platform_event

✅ Your Hook

Quick Start

Register a hook on the agent, then opt the channel in from the gateway config.

python
from praisonaiagents import Agent
from praisonaiagents.hooks import add_hook, HookEvent

@add_hook(HookEvent.REACTION_RECEIVED)
def on_reaction(evt):
    if evt.emoji == "✅" and evt.kind == "reaction_added":
        print(f"{evt.user_id} approved message {evt.message_id}")

agent = Agent(name="ApprovalBot", instructions="Approve requests reacted to with ✅.")
agent.start("Approve incoming requests when a teammate reacts with ✅.")
yaml
# gateway.yaml — opt in per channel
channels:
  my-discord-bot:
    platform: discord
    token: "${DISCORD_BOT_TOKEN}"
    events: [reactions]      # NEW — opt in to inbound reactions

Opt-In Classes

  1. reactions

    Hear when a user adds or removes a reaction. Branch on evt.kind to tell add from remove.

    yaml
    channels:
      my-discord-bot:
        platform: discord
        token: "${DISCORD_BOT_TOKEN}"
        events: [reactions]
    python
    from praisonaiagents.hooks import add_hook, HookEvent
    
    @add_hook(HookEvent.REACTION_RECEIVED)
    def on_reaction(evt):
        if evt.kind == "reaction_added":
            print(f"{evt.user_id} added {evt.emoji} to {evt.message_id}")
        else:
            print(f"{evt.user_id} removed {evt.emoji} from {evt.message_id}")
  2. edits

    Hear message edits and deletions.

    yaml
    channels:
      my-discord-bot:
        platform: discord
        token: "${DISCORD_BOT_TOKEN}"
        events: [edits]
    python
    from praisonaiagents.hooks import add_hook, HookEvent
    
    @add_hook(HookEvent.MESSAGE_EDITED)
    def on_edit(evt):
        print(f"{evt.message_id} is now: {evt.new_text}")
    
    @add_hook(HookEvent.MESSAGE_DELETED)
    def on_delete(evt):
        print(f"{evt.user_id} deleted {evt.message_id}")
  3. members

    Hear when a user joins or leaves. Requires the privileged members gateway intent, which the adapter enables only when you request this class.

    yaml
    channels:
      my-discord-bot:
        platform: discord
        token: "${DISCORD_BOT_TOKEN}"
        events: [members]
    python
    from praisonaiagents.hooks import add_hook, HookEvent
    
    @add_hook(HookEvent.MEMBER_JOINED)
    def on_join(evt):
        print(f"Welcome {evt.user_id} to {evt.chat_id}")
    
    @add_hook(HookEvent.MEMBER_LEFT)
    def on_leave(evt):
        print(f"{evt.user_id} left {evt.chat_id}")
  4. threads

    Hear when a new thread is created. thread_id and the parent chat_id are populated.

    yaml
    channels:
      my-discord-bot:
        platform: discord
        token: "${DISCORD_BOT_TOKEN}"
        events: [threads]
    python
    from praisonaiagents.hooks import add_hook, HookEvent
    
    @add_hook(HookEvent.THREAD_CREATED)
    def on_thread(evt):
        print(f"New thread {evt.thread_id} in {evt.chat_id}")

How It Works

A native platform event is normalised into a portable PlatformEvent, which fires the matching HookEvent.

Diagram
AgentREACTION_RECEIVED HookDiscordBotDiscord SDKUserAgentREACTION_RECEIVED HookDiscordBotDiscord SDKUserReact ✅ on message m1on_raw_reaction_add(payload)Skip if payload.user_id == bot.idBuild PlatformEvent(kind=”reaction_added”, …)fire_platform_event(evt)agent.start(“Approved: …”)Acknowledgement

Every adapter emits through one DRY seam, fire_platform_event, which maps PlatformEvent.kind onto the matching HookEvent. Reaction add and remove both fire REACTION_RECEIVED — the kind field disambiguates.


Which Event Fires When?

Pick the class that matches what you want to hear.

Diagram

User reactions 👍/✅

User edits / deletes a message

Join / leave the channel

New thread created

What do you want to hear?

events: reactions → REACTION_RECEIVED

events: edits → MESSAGE_EDITED + MESSAGE_DELETED

events: members → MEMBER_JOINED + MEMBER_LEFT

events: threads → THREAD_CREATED


Configuration Options

Each class token subscribes the adapter to a set of native handlers.

Class tokenEnabled eventsNative handlersNotes
reactionsREACTION_RECEIVEDon_raw_reaction_add, on_raw_reaction_removekind field disambiguates add vs remove; the bot’s own reactions are ignored
editsMESSAGE_EDITED, MESSAGE_DELETEDon_message_edit, on_message_deleteBot-authored edits are skipped
membersMEMBER_JOINED, MEMBER_LEFTon_member_join, on_member_removeRequires the privileged members gateway intent — enabled only when this class is requested
threadsTHREAD_CREATEDon_thread_createthread_id and parent chat_id are populated

Hook Payload (PlatformEventInput)

Every inbound event carries a PlatformEventInput.

FieldTypeDefaultDescription
kindstr""One of reaction_added / reaction_removed / message_edited / message_deleted / member_joined / member_left / thread_created
platformstr""Emitting platform ("discord" today; "telegram"/"slack" when their adapters wire it)
chat_idstr""Channel/chat where the event occurred
user_idstr""User who caused the event
message_idOptional[str]NoneTarget message id (reactions/edits/deletes)
emojiOptional[str]NoneReaction emoji, for reaction add/remove
new_textOptional[str]NoneNew content, for an edit (truncated to 500 chars in to_dict())
thread_idOptional[str]NoneThread id, for thread creation

Import it directly when you need the type:

python
from praisonaiagents.hooks import PlatformEventInput
from praisonaiagents.bots import PlatformEvent, PlatformEventKind

Platform Coverage

Only Discord is wired today. Other adapters translate their native events into the same PlatformEvent in follow-up releases.

PlatformReactionsEditsDeletesMembersThreads
Discord✅✅✅✅ (privileged intent)✅
Telegram⏳ Not yet⏳⏳⏳⏳
Slack⏳ Not yet⏳⏳⏳⏳
WhatsApp⏳ Not yet⏳⏳——
OthersSee adapter

Common Patterns

Reaction-as-approval — react ✅ to approve, no typing required.

python
from praisonaiagents.hooks import add_hook, HookEvent

@add_hook(HookEvent.REACTION_RECEIVED)
def approve(evt):
    if evt.emoji == "✅" and evt.kind == "reaction_added":
        print(f"Approved {evt.message_id} by {evt.user_id}")

Undo-on-delete — retract a stored plan when the source message disappears.

python
from praisonaiagents.hooks import add_hook, HookEvent

@add_hook(HookEvent.MESSAGE_DELETED)
def undo(evt):
    print(f"Clearing follow-ups tied to {evt.message_id}")

Welcome-on-join — greet new members.

python
from praisonaiagents.hooks import add_hook, HookEvent

@add_hook(HookEvent.MEMBER_JOINED)
def welcome(evt):
    print(f"Sending welcome to {evt.user_id} in {evt.chat_id}")

Thread-scoped session — open a new agent session tied to a thread.

python
from praisonaiagents.hooks import add_hook, HookEvent

@add_hook(HookEvent.THREAD_CREATED)
def open_session(evt):
    print(f"Starting a session for thread {evt.thread_id}")

Best Practices

Enable only the classes you need

Every unlisted class stays unsubscribed. members in particular pulls the privileged Discord members intent — request it only when you use it.

Safe to combine with status reactions

The bot’s own reactions and edits are filtered out automatically, so internal ack/done housekeeping is never mistaken for a user reaction. You can enable reactions alongside status reactions without loops.

Hook errors are non-fatal

A raised exception inside a hook is logged at debug and swallowed — it never breaks the adapter’s event loop.

Route by kind inside REACTION_RECEIVED

Reaction add and remove both fire REACTION_RECEIVED. Branch on evt.kind (reaction_added vs reaction_removed) to distinguish them.

Remember new_text is truncated in to_dict()

PlatformEventInput.to_dict() truncates new_text to 500 chars for observability. The in-memory evt.new_text attribute is not truncated.


Related