Quoted Reply Context
Agents see what a user replied to or quoted, so \"do the second one\" resolves to the right referent
When a user replies to or quotes an earlier message on Telegram, the referenced text is folded into the agent turn as a compact quoted block so the agent honours the referent.
Tapping Reply on the bot’s own message now carries the referenced text β so a bare “do the second one” resolves correctly:
Bot: Here are three options: 1) migrate now 2) stage it 3) roll back
User: (taps Reply on the bot's message) do the second one
# What the agent sees (rendered by BotMessage.prompt_text)
[In reply to: "Here are three options: 1) migrate now 2) stage it 3) roll back"]
do the second one
# Agent replies with the right referent β "Staging it β here's the planβ¦"Quick Start
- Start the bot β context is always on
python from praisonaiagents import Agent from praisonai.bots import TelegramBot agent = Agent(name="Planner", instructions="Be concise.") bot = TelegramBot(token="...", agent=agent) import asyncio asyncio.run(bot.start()) # Reply/quote context is always on β no flag to enable. - Inspect the resolved quote programmatically
python from praisonaiagents.bots import BotMessage, QuotedRef msg = BotMessage( content="do the second one", quoted=QuotedRef( text="Here are three options: 1) migrate now 2) stage it 3) roll back", author="bot", ), ) print(msg.prompt_text) # [In reply to: "Here are three options: 1) migrate now 2) stage it 3) roll back"] # do the second one
How It Works
The adapter resolves the referenced text inbound, then prompt_text renders it above the new text before the message reaches the agent.
prompt_text whitespace-collapses the referenced text and truncates anything over 500 characters with an ellipsis. With no quote it is identical to the plain text.
| Situation | What the agent sees |
|---|---|
| No reply / no quote | Original text unchanged |
| Reply to a bot message | [In reply to: "<bot's earlier text>"]\n<new text> |
| Reply to a peer message | [In reply to: "<peer's earlier text>"]\n<new text> |
| Explicit quote span (partial highlight) | [In reply to: "<selected span only>"]\n<new text> |
| Referenced message has caption only | Caption used as the quoted text |
| New text empty (reply with just a sticker/reaction) | Only the [In reply to: "β¦"] block |
| Quoted text > 500 chars | Truncated with β¦ |
Behavioural rules:
- Always-on. No YAML or Python flag β the feature is default behaviour on Telegram.
- Best-effort. Any missing field or error falls back to today’s behaviour β the turn is never dropped just because resolution failed.
- Context, never control. Quoted content is injected as context only, never as a control frame β consistent with
allow_controlonBotMessage. - Runs behind the gate.
MESSAGE_RECEIVEDseescontent = prompt_text(the rendered turn including the quoted block). Redacting content in the hook is authoritative β the separately-resolved quote is cleared so unredacted quoted text can’t leak past. - Works in the standalone bot and the gateway. Both
praisonai botandpraisonai gatewayrenderprompt_textbefore dispatch.
Platform Support
Quoted reply resolution is wired on Telegram in this release. On Discord and Slack a reply to the bot still counts as addressing it (see Implicit Mentions), but the replied-to message’s text is not folded into the agent turn.
| Platform | Quoted reply resolved? |
|---|---|
| Telegram | β |
| Discord | β (not yet) |
| Slack | β (not yet) |
| β (not yet) |
Common Patterns
Do nothing β it just works. Start the bot and reply/quote context is folded in automatically.
from praisonaiagents import Agent
from praisonai.bots import TelegramBot
agent = Agent(name="Planner", instructions="Be concise.")
bot = TelegramBot(token="...", agent=agent)
import asyncio
asyncio.run(bot.start())Redact quoted secrets before the agent sees them. The MESSAGE_RECEIVED hook receives the rendered turn, so scrubbing event_data.content covers a secret that appeared in the quoted message, not just the new text.
import re
from praisonaiagents.hooks import HookRegistry, HookEvent, HookResult
SECRET = re.compile(r"hunter\d+")
registry = HookRegistry()
@registry.on(HookEvent.MESSAGE_RECEIVED)
def redact(event_data):
cleaned = SECRET.sub("[REDACTED]", event_data.content)
if cleaned != event_data.content:
return HookResult(decision="allow", modified_input={"content": cleaned})
return HookResult.allow()Inspect msg.quoted in a custom hook or plugin. The resolved reference exposes the referenced text and its author.
from praisonaiagents.bots import BotMessage, QuotedRef
msg = BotMessage(
content="do the second one",
quoted=QuotedRef(
text="Here are three options: 1) migrate now 2) stage it 3) roll back",
author="bot",
),
)
print(msg.quoted.author) # "bot"
print(msg.prompt_text) # rendered turn with the quoted blockBest Practices
Don't add prompt guidance for quotes
The [In reply to: "β¦"] block is unambiguous β the agent honours the referent without extra system-prompt scaffolding.
Use the inbound gate to filter quoted content
MESSAGE_RECEIVED sees the rendered turn β redact or deny there, not by peeking at msg.quoted in the agent tools.
Treat quoted content as untrusted
It comes from whoever sent the referenced message. It is context, never a control frame.
Expect graceful degradation across Telegram clients
If a client doesn’t send the quote span, the adapter falls back to reply_to_message.text / caption; if neither is present, the turn goes through unchanged.