Skip to main content
Send Policy controls which channels an agent is allowed to deliver messages to, blocking unauthorised sends before they reach any messenger.

Quick Start

1

Default-Deny Allowlist

Permit only specific targets — everything else is blocked.
from praisonaiagents import Agent
from praisonai.bots import Bot
from praisonaiagents.gateway.protocols import SendPolicy

agent = Agent(
    name="Support Bot",
    instructions="Reply where you were asked. Escalate to ops-alerts if a user reports an outage.",
)

Bot(
    "telegram",
    agent=agent,
    send_policy=SendPolicy(
        default="deny",
        allow=["origin", "ops-alerts"],
    ),
).start()
The agent can only send to origin (the conversation it came from) or the ops-alerts alias. Any other target returns a clean denial string instead of delivering.
2

Default-Allow with Denylist

Allow everything except specific channels you want to protect.
from praisonaiagents import Agent
from praisonai.bots import Bot
from praisonaiagents.gateway.protocols import SendPolicy

agent = Agent(
    name="Assistant",
    instructions="Help the user with their questions.",
)

Bot(
    "slack",
    agent=agent,
    send_policy=SendPolicy(
        default="allow",
        deny=["slack:#exec", "slack:#board"],
    ),
).start()
The agent can send to any target except the protected executive channels.
3

YAML Configuration

Set the policy in gateway.yaml without writing Python:
channels:
  telegram:
    send_policy:
      default: deny
      allow:
        - origin
        - "slack:#ops"
        - ops-alerts

How It Works

Denial is model-readable. A well-instructed agent reads the failure string and recovers gracefully — for example, routing to an allowed channel or asking the user to confirm before proceeding.
ComponentRole
SendPolicyConfig-driven allow/deny evaluator registered per turn
SendPolicyProtocolInterface for custom policy back-ends (RBAC, time-windows, audit)
SendDecisionImmutable result from evaluate()allow bool + optional reason
send_message toolChecks the active policy before invoking the messenger
The check sits in core (praisonaiagents), so every messenger implementation is constrained — not just one adapter. Absent a policy, all sends are allowed (backwards compatible).

Configuration Options

SendPolicy

Import: from praisonaiagents.gateway.protocols import SendPolicy
ArgumentTypeDefaultDescription
defaultstr"allow"Posture for targets not listed. "deny" blocks all except allow list; "allow" permits all except deny list.
allowlist[str] | NoneNoneTargets explicitly permitted. Active allowlist when default="deny".
denylist[str] | NoneNoneTargets explicitly blocked. Takes precedence over allow.
Matching uses exact string comparison against the symbolic target token (e.g. "origin", "slack:#ops", a friendly alias). Passing any value other than "allow" or "deny" for default raises ValueError at construction time.

SendDecision

Import: from praisonaiagents.gateway.protocols import SendDecision
FieldTypeDefaultDescription
allowboolTrue if the send is permitted; False blocks it.
reasonstr""Optional explanation shown to the model when denied.
SendDecision is a frozen dataclass — immutable once created.

SendPolicyProtocol

Import: from praisonaiagents.gateway.protocols import SendPolicyProtocol
class SendPolicyProtocol(Protocol):
    def evaluate(
        self,
        target: str,
        *,
        agent_id: str = "",
        session_id: str = "",
        origin: str | None = None,
    ) -> SendDecision: ...
Implement this protocol for richer back-ends. The keyword arguments are populated best-effort from the active SessionContext.

Common Patterns

Default-Deny Allowlist (Production)

Safest posture: only explicitly listed targets can receive messages.
from praisonaiagents.gateway.protocols import SendPolicy

policy = SendPolicy(
    default="deny",
    allow=[
        "origin",       # Always reply where the conversation came from
        "ops-alerts",   # Named alias for the ops channel
    ],
)

Default-Allow with Denylist

Lighter-weight for trusted environments where you only want to protect a few channels.
from praisonaiagents.gateway.protocols import SendPolicy

policy = SendPolicy(
    default="allow",
    deny=["slack:#exec", "slack:#board", "slack:#payroll"],
)

Custom Protocol — Time-Window Restriction

import datetime
from praisonaiagents.gateway.protocols import SendDecision

class BusinessHoursPolicy:
    def evaluate(self, target, *, agent_id="", session_id="", origin=None):
        hour = datetime.datetime.now().hour
        if target.startswith("slack:#exec") and not (9 <= hour < 18):
            return SendDecision(
                allow=False,
                reason="exec channel restricted to business hours (9–18)",
            )
        return SendDecision(allow=True)
Register it the same way as SendPolicy:
from praisonai.bots import Bot
from praisonaiagents import Agent

agent = Agent(name="Assistant", instructions="Help the user.")
Bot("slack", agent=agent, send_policy=BusinessHoursPolicy()).start()

Custom Protocol — Per-Agent RBAC

from praisonaiagents.gateway.protocols import SendDecision

ALLOWED_CHANNELS: dict[str, list[str]] = {
    "support-bot": ["origin", "slack:#support"],
    "ops-bot":     ["origin", "slack:#ops", "slack:#incidents"],
}

class RBACPolicy:
    def evaluate(self, target, *, agent_id="", session_id="", origin=None):
        allowed = ALLOWED_CHANNELS.get(agent_id, ["origin"])
        if target in allowed:
            return SendDecision(allow=True)
        return SendDecision(
            allow=False,
            reason=f"agent '{agent_id}' is not permitted to send to '{target}'",
        )

Threat Model

send_message lets the model choose the target. Content reaching the model — including user messages and tool results — can steer an agent into delivering to a channel the operator never intended, exfiltrating conversation data or spamming privileged channels.The send-policy guard blocks this at the core layer, before any messenger is invoked, so a steered or prompt-injected agent cannot bypass it.
Concretely:
  1. Prompt injection in a retrieved document instructs the agent to send_message("slack:#exec", "<sensitive data>").
  2. Without a send policy, the message is delivered.
  3. With SendPolicy(default="deny", allow=["origin"]), the call fails with a clean denial string. The agent reads it, cannot comply, and the data stays contained.

Best Practices

Start with the most restrictive posture and explicitly list every channel the agent legitimately needs. This gives you a clear audit trail and prevents unexpected delivery as new channels are added.
SendPolicy(default="deny", allow=["origin", "ops-alerts"])
Unless you specifically want to prevent the agent from replying to the conversation it came from, include "origin" in the allow list. Without it, default="deny" will block even simple replies.
# Don't forget origin!
SendPolicy(default="deny", allow=["origin", "slack:#ops"])
Whether you use Python or YAML config, commit your send policy alongside the rest of your gateway configuration. This ensures policy changes go through code review and are tracked in history.
# gateway.yaml — commit this
channels:
  telegram:
    send_policy:
      default: deny
      allow:
        - origin
        - ops-alerts
If your rules depend on runtime state (user roles, time of day, session metadata), implement SendPolicyProtocol rather than rebuilding a SendPolicy instance per request. The agent_id, session_id, and origin kwargs give you the context you need.

Behaviour Reference

ScenarioResult
No policy registeredAll sends permitted (backwards-compatible)
default="deny", target not in allowBlocked — "Failed to send to <target>: target '<target>' is not permitted by send_policy"
default="allow", target in denyBlocked — same format
Target in both allow and denydeny takes precedence — blocked
action="list"Always allowed — policy does not affect target listing
Policy .evaluate() raises an exceptionFails closed — send blocked, "send_policy evaluation error"
Policy returns something other than SendDecisionFails closed — treated as denial

Send Message Tool

Let agents proactively deliver messages to users mid-task.

Messaging Bots

Deploy AI agents to Telegram, Discord, Slack, and WhatsApp.

Bot Rate Limiting

Throttle how often agents can respond per user or channel.

Guardrails

Add input and output safety checks to your agents.