Bot Intentional Silence
Let agents stay silent in group chats by returning NO_REPLY
With allow_silence: true, an agent can return NO_REPLY (or a custom token) to send nothing — no message, no typing indicator, no error.
from praisonaiagents import Agent
agent = Agent(name="group-bot", instructions="Reply in group chats only when helpful.")
agent.start("Should I respond to this off-topic message?")The user posts in a group chat; with allow_silence: true, the agent can return NO_REPLY so nothing is sent.
Quick Start
- YAML
yaml channels: telegram: token: ${TELEGRAM_BOT_TOKEN} group_policy: respond_all allow_silence: true - Python
python from praisonai.bots import Bot from praisonaiagents import Agent agent = Agent( name="Group Helper", instructions="Reply only when asked a direct question. Otherwise return exactly NO_REPLY.", ) bot = Bot("telegram", agent=agent, allow_silence=True) bot.run()
How It Works
| Marker | Honoured when allow_silence=true |
|---|---|
NO_REPLY | ✅ default token |
[SILENT] | ✅ |
SILENT | ✅ |
Custom silence_token | ✅ exact match only |
| Prose containing “NO_REPLY” | ❌ not treated as silence |
Scheduled agents
An unattended monitor with nothing to report returns a silence marker, and the scheduled run skips delivery to chat while still recording as succeeded.
Scheduled and automation runs honour the same silence contract — a scheduled job whose whole output is NO_REPLY, [SILENT], or SILENT skips delivery while the run still completes and is recorded as succeeded in history.
Intentional silence is a recorded non-outcome (SUPPRESSED), whereas a real delivery failure now flips the run to on_failure and bumps undelivered_deliveries — see Scheduler Delivery → Delivery outcomes.
An intentional-silence run also skips the continuable-session seed — nothing was delivered, so nothing is claimed to have been. The next inbound message lands as a fresh turn, same as before. See Scheduler Delivery → Continuable Delivery.
from praisonaiagents import Agent
monitor = Agent(
name="log-watcher",
instructions=(
"Check the error log summary. If there are new errors, list them. "
"If the summary is empty, reply with exactly: NO_REPLY"
),
)
# Wire this agent into a scheduled job (see /docs/features/scheduled-run-policy).
# On empty-log runs, nothing lands in chat; job still shows 'succeeded' in history.| Marker | Ambient / group chat (allow_silence: true) | Scheduled / automation delivery (always) |
|---|---|---|
NO_REPLY | ✅ default token | ✅ |
[SILENT] | ✅ | ✅ |
SILENT | ✅ | ✅ |
Whitespace-wrapped marker (" NO_REPLY ") | ✅ | ✅ |
Custom silence_token (ambient-only opt-in) | ✅ exact match | not applicable — scheduled path uses default markers only |
Prose containing "NO_REPLY" | ❌ | ❌ |
Configuration
| Field | Type | Default | Description |
|---|---|---|---|
allow_silence | bool | false | Honour silence markers (opt-in) |
silence_token | str | None | Override marker; when set, only this exact string triggers silence |
Combine with group_policy: respond_all so the agent may respond, then chooses silence via NO_REPLY.
If you want the bot to look at every message but only speak when addressed, prefer group_policy: observe (no agent runs on unmentioned messages, transcript still captures them) over respond_all + NO_REPLY (agent runs on every message, then chooses silence). Observe is cheaper (no LLM calls) and safer (no risk of accidental replies).
Scheduled Runs
The same silence markers also suppress delivery on scheduled runs — AgentScheduler, AsyncAgentScheduler, and the full-gateway ScheduledAgentExecutor all honour NO_REPLY / [SILENT] / SILENT.
from praisonaiagents import Agent
from praisonai.scheduler import AgentScheduler
agent = Agent(
name="Watchdog",
instructions=(
"Every hour, check for new alerts. "
"If there are none, reply with exactly: NO_REPLY."
),
)
AgentScheduler(agent, task="Hourly check", deliver="telegram:123456").start("hourly")As on the chat path, only an exact marker triggers silence — prose mentioning NO_REPLY is delivered normally.
See Scheduler Delivery → Intentional Silence for the full flow.
Visible-Outcome Guarantee for Empty Finals
Blank replies and [tool_calls: …] placeholders now get a recorded fallback instead of silently dropping.
classify_final() is the single decision point every adapter (Slack, Telegram, Discord, IRC, custom) uses to classify an agent’s final reply. A deliberate NO_REPLY is still suppressed, but an empty final — blank, whitespace-only, or the machine [tool_calls: …] placeholder — now gets a visible fallback message so no adapter ends a turn with nothing shown.
| Final reply | classify_final() | Category | Delivery |
|---|---|---|---|
NO_REPLY, [SILENT], SILENT | "silence" | Deliberate silence | Suppressed — the no-reply contract |
Blank, whitespace-only, [tool_calls: …] | "empty" | Empty, not silence | Fallback message substituted |
| Any real content | "text" | Normal reply | Delivered as-is |
Override the fallback message
The default fallback is "Task completed — no message to show.". Override it via either an empty_final_message key on the existing BotConfig.metadata block (recommended — no new typed knob) or a direct config.empty_final_message attribute.
channels:
slack:
type: slack
token: env:SLACK_BOT_TOKEN
metadata:
empty_final_message: "The agent completed the task but produced no reply text."from praisonai.bots import BotConfig
config = BotConfig(token="env:SLACK_BOT_TOKEN")
config.empty_final_message = "Task done — nothing to display."Every fallback emission is logged at INFO level so the recorded non-outcome is operator-visible:
Empty-final resolution: substituted fallback for a blank/placeholder reply on <platform> (visible-outcome guarantee)A tool-only run now still delivers a visible reply — the custom fallback if configured, the default otherwise:
from praisonaiagents import Agent
agent = Agent(
instructions="Help the user with quick tasks.",
# A run that only calls tools now still delivers a visible reply
# (custom fallback if configured, default otherwise).
)
agent.start("Just log this event, no reply needed.")Best Practices
Opt in explicitly
allow_silence defaults to false — existing bots behave unchanged.
Teach the agent the contract
Instructions should say when to return exactly NO_REPLY vs a normal reply.
Use for ambient group channels
Reduces noise when the bot listens to everything but should rarely speak.
Related
Guardrails and silent runs for unattended scheduled agents
Bot Loop ProtectionBreak runaway bot-to-bot reply loops
GatewayChannel configuration reference
Messaging BotsMulti-platform bot setup
Scheduler DeliverySilence markers also suppress scheduled pushes
CardFailure-path counterpart of classify_final — visible replies for failed turns