Skip to main content
Filter, redact, or reject messages from Telegram, Slack, Discord, WhatsApp, and email before your agent sees them.
from praisonaiagents import Agent
from praisonaiagents.hooks import HookRegistry, HookEvent, HookResult
from praisonai.bots import TelegramBot

registry = HookRegistry()
ALLOWED = {"12345", "67890"}

@registry.on(HookEvent.MESSAGE_RECEIVED)
def gate(event_data):
    if event_data.sender_id not in ALLOWED:
        return HookResult.deny("not on allowlist")
    return HookResult.allow()

agent = Agent(name="GatedBot", instructions="Be helpful.", hooks=registry)
bot = TelegramBot(token="...", agent=agent)

import asyncio
asyncio.run(bot.start())

Quick Start

1

Allowlist — drop messages from unknown senders

from praisonaiagents import Agent
from praisonaiagents.hooks import HookRegistry, HookEvent, HookResult

ALLOWED = {"12345", "67890"}
registry = HookRegistry()

@registry.on(HookEvent.MESSAGE_RECEIVED)
def gate(event_data):
    if event_data.sender_id not in ALLOWED:
        return HookResult.deny("not on allowlist")
    return HookResult.allow()

agent = Agent(name="GatedBot", instructions="Be helpful.", hooks=registry)
agent.start("Hello")
2

PII redaction — rewrite content before the LLM sees it

import re
from praisonaiagents import Agent
from praisonaiagents.hooks import HookRegistry, HookEvent, HookResult

SSN = re.compile(r"\b\d{3}-\d{2}-\d{4}\b")
registry = HookRegistry()

@registry.on(HookEvent.MESSAGE_RECEIVED)
def redact(event_data):
    cleaned = SSN.sub("[SSN]", event_data.content)
    if cleaned != event_data.content:
        return HookResult(decision="allow", modified_input={"content": cleaned})
    return HookResult.allow()

agent = Agent(name="SafeBot", instructions="Be helpful.", hooks=registry)
agent.start("My SSN is 123-45-6789")

How It Works


Decision Table

Hook returnsdropcontent sent to agentEffect
HookResult.allow() (or no hook)falseoriginalMessage flows unchanged
HookResult.deny("...")trueAdapter skips dispatch — bot stays silent
HookResult(decision="allow", modified_input={"content": "…"})falseredacted textLLM sees rewritten content
Hook raises exceptionfalseoriginalNon-fatal — message passes through
Hook exceptions are non-fatal. Raising an exception does not drop the message — the message passes through to the agent. Use HookResult.deny(...) to explicitly block.

Supported Adapters

Every built-in messaging adapter honours the gate decision:
AdapterModule
Telegrampraisonai.bots.telegram
Slackpraisonai.bots.slack
Discordpraisonai.bots.discord
WhatsApppraisonai.bots.whatsapp (regular + status paths)
Emailpraisonai.bots.email
AgentMailpraisonai.bots.agentmail
Custom adapters must call fire_message_received and check the returned drop / content fields to participate in the gate.

Common Patterns

Allowlist

from praisonaiagents.hooks import HookRegistry, HookEvent, HookResult

ALLOWED = {"user_id_1", "user_id_2"}
registry = HookRegistry()

@registry.on(HookEvent.MESSAGE_RECEIVED)
def allowlist(event_data):
    if event_data.sender_id not in ALLOWED:
        return HookResult.deny("sender not allowed")
    return HookResult.allow()

Rate Limiter

import time
from collections import defaultdict
from praisonaiagents.hooks import HookRegistry, HookEvent, HookResult

registry = HookRegistry()
_counts: dict = defaultdict(list)
LIMIT = 5
WINDOW = 60

@registry.on(HookEvent.MESSAGE_RECEIVED)
def rate_limit(event_data):
    now = time.time()
    timestamps = [t for t in _counts[event_data.sender_id] if now - t < WINDOW]
    if len(timestamps) >= LIMIT:
        return HookResult.deny("rate limit exceeded")
    timestamps.append(now)
    _counts[event_data.sender_id] = timestamps
    return HookResult.allow()

Keyword Ban

from praisonaiagents.hooks import HookRegistry, HookEvent, HookResult

BANNED = {"spam", "scam"}
registry = HookRegistry()

@registry.on(HookEvent.MESSAGE_RECEIVED)
def keyword_ban(event_data):
    for word in BANNED:
        if word in event_data.content.lower():
            return HookResult.deny("banned keyword")
    return HookResult.allow()

Combined Gate + Redaction

import re
from praisonaiagents import Agent
from praisonaiagents.hooks import HookRegistry, HookEvent, HookResult
from praisonai.bots import TelegramBot

registry = HookRegistry()
BLOCKED = {"1234567"}
SSN = re.compile(r"\b\d{3}-\d{2}-\d{4}\b")

@registry.on(HookEvent.MESSAGE_RECEIVED)
def gate(event_data):
    if event_data.sender_id in BLOCKED:
        return HookResult.deny("blocked sender")
    scrubbed = SSN.sub("[SSN]", event_data.content)
    if scrubbed != event_data.content:
        return HookResult(decision="allow", modified_input={"content": scrubbed})
    return HookResult.allow()

agent = Agent(name="SafeBot", instructions="Be helpful.", hooks=registry)
bot = TelegramBot(token="...", agent=agent)

import asyncio
asyncio.run(bot.start())
Behaviour:
  • Blocked user’s messages: agent stays silent, no reply sent.
  • Messages containing an SSN pattern: LLM sees [SSN] instead of the digits.
  • All other messages: unchanged.

User Interaction Flow

A Telegram user sends a message. The gate runs before the agent responds:
Messagesender_idHook resultAgent seesBot reply
”Hello!”12345 (allowed)allow”Hello!”Normal response
”ping”99999 (blocked)deny(silent)
“SSN: 123-45-6789”12345 (allowed)modified_input”SSN: [SSN]“Answers without digits

Symmetry with Outbound

MESSAGE_RECEIVED (inbound) and MESSAGE_SENDING (outbound) share the same deny / modified_input contract. Use the same pattern for both directions:
@registry.on(HookEvent.MESSAGE_SENDING)
def redact_outbound(event_data):
    cleaned = SSN.sub("[SSN]", event_data.content)
    if cleaned != event_data.content:
        return HookResult(decision="allow", modified_input={"content": cleaned})
    return HookResult.allow()

Best Practices

The hook runs on every inbound message. Avoid blocking I/O or heavy computation — use in-memory lookups and pre-compiled regex patterns.
Raising an exception lets the message through. Explicitly call HookResult.deny(...) if you mean to block.
The LLM will see the modified_input content and may quote it back in replies. Only rewrite when necessary (e.g. PII scrubbing).
deny prevents agent dispatch entirely. modified_input still dispatches — use it only when you want the agent to respond to the sanitised content.

Hook Events

Complete reference for all hook events and input types

Bot Lifecycle Hooks

Gateway, session, and schedule lifecycle hooks