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.

python
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.

Diagram

observe → record passive, no run

NO_REPLY

Normal text

📨 Group message

group_policy gate

🤖 Agent

📝 Passive context

🔇 No outbound message

💬 Reply sent

Quick Start

  1. YAML
    yaml
    channels:
      telegram:
        token: ${TELEGRAM_BOT_TOKEN}
        group_policy: respond_all
        allow_silence: true
  2. 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

Diagram
AgentBotUserAgentBotUserallow_silence=true → send nothingambient group messageforward (after group_policy)NO_REPLY
MarkerHonoured 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.

python
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.
Diagram
HistoryDeliveryis_intentional_silence_responseAgentSchedulerHistoryDeliveryis_intentional_silence_responseAgentScheduleralt[exact silence marker][normal text]run due joboutputskiprecord ‘succeeded’deliverrecord ‘succeeded’
MarkerAmbient / 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 matchnot applicable — scheduled path uses default markers only
Prose containing "NO_REPLY"❌❌

Configuration

FieldTypeDefaultDescription
allow_silenceboolfalseHonour silence markers (opt-in)
silence_tokenstrNoneOverride 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.

python
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.

Diagram

silence

empty

text

Agent final

classify_final

Suppress send

Fallback message

Deliver as-is

Final replyclassify_final()CategoryDelivery
NO_REPLY, [SILENT], SILENT"silence"Deliberate silenceSuppressed — the no-reply contract
Blank, whitespace-only, [tool_calls: …]"empty"Empty, not silenceFallback message substituted
Any real content"text"Normal replyDelivered 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.

agents.yaml
channels:
  slack:
    type: slack
    token: env:SLACK_BOT_TOKEN
    metadata:
      empty_final_message: "The agent completed the task but produced no reply text."
programmatic
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:

python
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