Skip to main content
Every inbound message ends in one of two recorded states: a visible reply, or a deliberate silence — a blank or placeholder final is never dropped.

Quick Start

1

It's on by default

The guarantee is always active — you don’t enable it. A normal agent already benefits:
2

Customise the fallback sentence

Set empty_final_message in BotConfig.metadata to change the sentence shown when a turn produces no visible text:
Before this guarantee, three real bugs could each end an inbound turn with no reply: Slack silently dropped a blank send, Telegram parked an empty send in the DLQ, and any adapter could leak a raw [tool_calls: search_web] placeholder to the user.

How It Works

Classification and substitution happen once, at fire_message_sending() — the single delivery funnel every adapter shares — so no adapter re-decides “blank” differently. A blank, whitespace-only, or [tool_calls: …] final is treated as empty, not silence and substituted with the fallback. A blank reply is never confused with a deliberate NO_REPLY.

classify_final

classify_final is the single decision the bot layer uses to tell a real reply, an empty final, and a deliberate silence apart.

Configuration Options

The fallback sentence is the only knob. Precedence: direct attribute > metadata > default.
Operator visibility. Every substitution is logged at INFO on the wrapper logger with the platform name:
Repeated hits usually mean an agent prompt or tool graph is finishing without composing a reply.

Best Practices

The same sentence may ship on Slack, Telegram, Discord, WhatsApp, email, or AgentMail. Avoid platform-specific formatting or references.
To make the bot say nothing on purpose, return the exact NO_REPLY / [SILENT] token — see Intentional Silence. Returning whitespace will not silence the bot; it still substitutes the fallback.
Frequent "Empty-final resolution: substituted fallback…" lines signal an agent finishing without a reply. Treat it as a prompt/tool-graph smell, not a delivery problem.
metadata.empty_final_message is the declared seam for platform-agnostic extras and needs no code change — set it in YAML rather than adding a typed knob.

Intentional Silence

Deliberate NO_REPLY / [SILENT] — suppress the send on purpose

Bot Gateway

Where the visible-outcome guarantee is enforced

Inbound Journal

Crash-replay — the other half of “no inbound ends without a recorded outcome”

Channel Descriptor

Declare per-channel config, prompt hints, and setup in one place
Introduced in PraisonAI PR #3622 (fixes #3621).