| name | telegram |
| description | Use when reading or sending Telegram messages. Triggered by requests to check recent messages, search conversation history, or send messages/media to chats. |
| allowed-tools | Bash |
| user-invocable | false |
Telegram
Unified interface for reading and sending Telegram messages.
PM Tool vs CLI Tool
There are two sending interfaces. Use the correct one for your context:
| Tool | Context | How It Works |
|---|
python tools/send_message.py | Agent session (PM/Teammate) | Queues via the canonical TelegramRelayOutputHandler pipeline (drafter validation → redundancy/RTR filters → telegram:outbox:{session_id}), records msg_id for summarizer bypass |
valor-telegram send | Human operator / CLI | Operator CLI — not an agent delivery path. Queues via the same Redis relay (bridge/telegram_relay.py) but skips the canonical handler pipeline and summarizer-bypass recording |
Agent sessions should always use tools/send_message.py. It supports text, single file attachments, and multi-file albums via --file (repeatable, max 10 files), and prints the delivery outcome (sent / suppressed / deferred). valor-telegram send is the human-operator CLI: using it from an agent session would skip the canonical pipeline and summarizer-bypass recording and break has_pm_messages() tracking.
Agent Tool Examples
python tools/send_message.py "Status update message"
python tools/send_message.py "Screenshot attached" --file /path/to/screenshot.png
python tools/send_message.py "PR review screenshots" --file before.png --file during.png --file after.png
python tools/send_message.py --file /path/to/document.pdf
valor-telegram send remains available as the human-operator CLI (see below); it is not an agent delivery path.
Reading Messages
CLI: valor-telegram
valor-telegram read --chat "Dev: Valor" --limit 10
valor-telegram read --chat "Tom" --limit 5
valor-telegram read --chat "Dev: Valor" --search "deployment"
valor-telegram read --chat "Dev: Valor" --since "1 hour ago"
valor-telegram read --chat "Dev: Valor" --limit 5 --json
valor-telegram read --chat-id -1001234567 --limit 10
valor-telegram read --user tom --limit 10
valor-telegram read --project psyoptimal --limit 20
--chat, --chat-id, --user, and --project are mutually exclusive -- pick one.
Freshness Header
Every successful read prints a header line BEFORE the messages so you can
tell which chat was queried and whether it is active:
[Dev: Valor · chat_id=-1001234567 · last activity: 3m ago]
Age format: <1m ago / Xm ago / Xh ago / Xd ago / never. If the
freshness header says 3d ago but you expect recent activity, that is a
signal you resolved to the wrong chat -- re-run with --chat-id or a more
specific --chat.
Handling Ambiguity (default: pick most recent + warn)
If --chat matches more than one chat, the default behavior is to pick
the most recently active candidate, log a warning to stderr listing all
candidates, and proceed with exit 0. The freshness header is still printed,
so you can tell which chat was actually used:
WARNING: Ambiguous chat name 'PsyOptimal' matched 2 candidates; picking most recent:
-1001234567 PM: PsyOptimal last: 3m ago <-- selected
-1009876543 PsyOptimal last: 2d ago
[PM: PsyOptimal · chat_id=-1001234567 · last activity: 3m ago]
... messages ...
Strict mode (--strict): exit 1 with candidate list
For scripted callers that need a hard failure on ambiguity, pass --strict
to the read subcommand. Under --strict, the CLI exits non-zero and
prints the candidate list on stdout instead of picking:
valor-telegram read --chat "PsyOptimal" --strict --limit 10
Ambiguous chat name. 2 candidates (most recent first):
-1001234567 PM: PsyOptimal last: 3m ago
-1009876543 PsyOptimal last: 2d ago
Re-run with --chat-id <id> or a more specific --chat string.
Parsing tip: the first column is the chat_id (leading - for groups,
no prefix for users). Pick the right row and re-run with --chat-id <id>.
--strict is only on read; send always uses the most-recent default.
Handling Zero-Match ("did you mean")
If no chat matched, the tool prints up to 3 nearest chats sorted by recency:
No chat matched 'PsyTeem'. Did you mean:
-1001234567 PsyTeam last: 3m ago
-1009876543 PsyArchive last: 2d ago
Cross-Chat Project Reads (--project)
A project (e.g. PsyOPTIMAL) often spans multiple Telegram chats. Pass
--project PROJECT_KEY to union messages across every chat with the
matching Chat.project_key, interleaved chronologically:
valor-telegram read --project psyoptimal --limit 20
Output starts with a one-line project freshness header summarizing the
unioned chat set:
[project=psyoptimal · 3 chats: PsyOPTIMAL, PM: PsyOptimal, Dev: PsyOPTIMAL · last activity: 3m ago]
[2026-04-25 09:30] [PsyOPTIMAL] alice: kicking off the sprint
[2026-04-25 09:32] [PM: PsyOptimal] tom: I'll grab the standup notes
[2026-04-25 10:15] [Dev: PsyOPTIMAL] bob: shipped the auth fix
Each line is tagged with the originating [chat_name] (truncated to 25
chars for long names). --limit applies to the merged total, not per
chat — --limit 20 returns the 20 most recent across the union.
--json output enriches each message dict with chat_id and chat_name.
The single-chat JSON shape is unchanged — these fields appear only under
--project.
--strict is rejected with --project (it has no name to resolve). To
discover which chats would be unioned, run valor-telegram chats --project KEY.
Sending Messages (CLI -- human operator only)
valor-telegram send --chat "Dev: Valor" "Hello world"
valor-telegram send --chat "Tom" "Check this screenshot" --file ./screenshot.png
valor-telegram send --chat "Dev: Valor" --image ./photo.jpg "Caption here"
valor-telegram send --chat "Dev: Valor" --audio ./recording.mp3
valor-telegram send --chat "Forum Group" --reply-to 123 "Message to topic"
valor-telegram send --chat "Dev: Valor" --voice-note --cleanup-after-send --audio /tmp/out.ogg
valor-telegram send --chat 8837490628 --await-reply --timeout 900 "deploy status?"
--await-reply is only valid against a bot registered under
projects.<key>.telegram.bots[]; add --json for a structured transcript.
Listing Known Chats
valor-telegram chats
valor-telegram chats --search "psy"
valor-telegram chats --search "PM psy"
valor-telegram chats --project psyoptimal
valor-telegram chats --project psyoptimal --search "dev"
valor-telegram chats --search "psy" --json
Notes
- Chat names are resolved from the history database (groups) and DM whitelist (users); use
valor-telegram chats --search PATTERN if unsure of the exact name
- Reads hit Redis via Popoto ORM (TelegramMessage model); sends route through the bridge relay (requires the bridge to be running)