Skip to main content

Communication Policy

The communication policy category governs what output channels agents can use to send messages. It controls which channels are allowed or blocked, enforces message count limits per execution, and requires disclaimer text in outbound messages.

Use it when your agents send notifications, emails, Slack messages, or any other form of external communication.

Rules

RuleTypeDefaultDescription
allowed_channelsstring[][]Channels agents are permitted to use. If set, only these channels are allowed. Leave empty to allow all channels not in blocked_channels
blocked_channelsstring[][]Channels explicitly denied (takes precedence over allowed)
max_messages_per_executioninteger50Maximum number of messages an agent can send in a single execution
require_disclaimerbooleanfalseWhether outbound messages must contain the disclaimer text
disclaimer_textstring"This message was generated by an AI agent."Text that must appear in the message body when require_disclaimer is enabled
action_on_violationstring"block""block" to prevent the communication, "warn" to log and continue

How Matching Works

Channel Matching -- Exact String Equality

Channel names use exact string matching (case-sensitive). The channel value passed to record_communication() must match exactly what's configured in the policy.

Blocked ChannelRecorded ChannelMatch?Why
smssmsYesExact match
smsSMSNoCase-sensitive: "sms" != "SMS"
social-mediasocial-mediaYesExact match
social-mediasocial_mediaNoHyphens vs underscores matter
slackslack-dmNoNot a prefix/substring match -- must be exact
Channel Names Are Case-Sensitive

Unlike blocked commands in the code-execution policy (which use case-insensitive substring matching), communication channels use exact string equality. "Slack" and "slack" are different channels. Use consistent lowercase channel names.

Allowed vs Blocked Channels

When both allowed_channels and blocked_channels are configured:

  1. Blocked channels are checked first -- if the channel is in the blocklist, it's denied regardless of the allowlist
  2. If the allowlist is non-empty, only channels in the allowlist are permitted
  3. If the allowlist is empty, all channels not in the blocklist are allowed
allowed_channelsblocked_channelsChannelResult
["slack", "email"]["sms"]slackALLOW
["slack", "email"]["sms"]smsBLOCK (in blocklist)
["slack", "email"]["sms"]webhookBLOCK (not in allowlist)
[]["sms"]webhookALLOW (no allowlist restriction)
[]["sms"]smsBLOCK (in blocklist)

Disclaimer Enforcement

When require_disclaimer is enabled, the disclaimer_text must appear as a substring in the message body. This is not an exact match -- the disclaimer can appear anywhere in the body text.

disclaimer_textMessage bodyMatch?
"This message was generated by an AI agent.""Hello! This message was generated by an AI agent."Yes
"This message was generated by an AI agent.""Update deployed. This message was generated by an AI agent."Yes
"This message was generated by an AI agent.""Hello! No disclaimer here."No
"This message was generated by an AI agent.""THIS MESSAGE WAS GENERATED BY AN AI AGENT."No (case-sensitive)
Disclaimer Is Case-Sensitive

The disclaimer check is a substring match, but it is case-sensitive. Ensure agents include the exact text configured in the policy.

Message Count

The max_messages_per_execution limit counts the total number of record_communication() calls within a single agent execution (one WaxellContext). Each call increments the count regardless of channel.

Example Policies

Internal Only

Block external communication channels, allow internal tools:

{
"allowed_channels": ["slack", "internal-api"],
"blocked_channels": ["email", "sms", "social-media"],
"max_messages_per_execution": 20,
"require_disclaimer": false,
"action_on_violation": "block"
}

Governed External Communications

Allow email to company domains with mandatory disclaimers:

{
"allowed_channels": ["slack", "email", "internal-api"],
"blocked_channels": ["sms", "social-media"],
"max_messages_per_execution": 10,
"require_disclaimer": true,
"disclaimer_text": "This message was generated by an AI agent.",
"action_on_violation": "block"
}

SDK Integration

Using the Context Manager

import waxell_observe as waxell

waxell.init()

@waxell.observe(
agent_name="notifier",
enforce_policy=True,
mid_execution_governance=True,
)
async def send_notification(channel: str, message: str):
# Record the communication -- triggers governance check
waxell.communication(
channel=channel,
recipient="#engineering",
body=message,
subject="",
)
return {"sent": True}

Direct Context Usage

from waxell_observe import WaxellContext
from waxell_observe.errors import PolicyViolationError

try:
async with WaxellContext(
agent_name="notifier",
enforce_policy=True,
mid_execution_governance=True,
) as ctx:
ctx.record_communication(
channel="email",
recipient="user@acme.com",
body="Your report is ready. This message was generated by an AI agent.",
subject="Report Ready",
)
except PolicyViolationError as e:
print(f"Blocked: {e}")

Enforcement Flow

Agent calls record_communication(channel="sms", body="...")

├── Buffer communication dict
├── Buffer step (comm:sms) + span (kind=io)
├── Emit OTel span (comm:sms)
├── Log: "Communication: sms → +1-555-0100"

└── Mid-execution governance check

├── Is "sms" in blocked_channels? → YES → BLOCK
├── Is allowed_channels set AND "sms" not in it? → BLOCK
├── Message count > max_messages_per_execution? → BLOCK
└── require_disclaimer AND disclaimer_text not in body? → BLOCK

The policy is evaluated at three points:

  • Pre-execution: Before the agent runs (basic checks)
  • Mid-execution: On each record_communication() call (channel, count, disclaimer checks)
  • Post-execution: After the run completes (final summary)

Creating via Dashboard

  1. Navigate to Governance > Policies
  2. Click New Policy
  3. Select category Communication
  4. Configure rules (channels, limits, disclaimer)
  5. Set scope to target specific agents (e.g., communication-agent)
  6. Enable

Creating via API

curl -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
https://acme.waxell.dev/waxell/v1/policies/ \
-d '{
"name": "Communication Governance",
"category": "communication",
"rules": {
"allowed_channels": ["slack", "email", "internal-api"],
"blocked_channels": ["sms", "social-media"],
"max_messages_per_execution": 10,
"require_disclaimer": true,
"disclaimer_text": "This message was generated by an AI agent.",
"action_on_violation": "block"
},
"scope": {
"agents": ["communication-agent"]
},
"enabled": true
}'

Observability

Trace Spans

Each record_communication() creates a span visible in the Trace tab:

Span nameKindAttributes
comm:slacktoolio.direction: outbound, io.channel: slack
comm:emailtoolio.direction: outbound, io.channel: email
comm:smstoolio.direction: outbound, io.channel: sms

Governance Tab

Policy evaluations appear with:

  • Policy name: The configured policy name
  • Action: allow, warn, or block
  • Category: communication
  • Reason: Human-readable explanation (e.g., "Blocked channel 'sms' found")

Logs

Log entries are automatically correlated to the trace:

Communication: slack → #engineering (Deployment completed successfully...)
Communication: sms → +1-555-0100 (Urgent: Server is down...)

Common Gotchas

  1. Channel names are case-sensitive exact match. "Slack" won't match a blocklist entry of "slack". Always use consistent lowercase channel names.

  2. Disclaimer is a substring match, but case-sensitive. The exact disclaimer_text string must appear somewhere in the body. "THIS MESSAGE WAS GENERATED BY AN AI AGENT." won't match "This message was generated by an AI agent.".

  3. Message count is cumulative. Every record_communication() call in a single execution counts toward max_messages_per_execution, regardless of channel. Sending 5 Slack messages and 6 emails = 11 total.

  4. Blocked channels take precedence over allowed channels. If a channel is in both lists, it's blocked.

  5. Empty allowed_channels means no allowlist restriction. If you leave allowed_channels empty but set blocked_channels, only the blocked channels are denied. All others are permitted.

Next Steps