send_photo, Slack files_upload_v2, Discord file send, and more.
Outbound media now honours threaded targets: when the delivery route names a thread — Slack
thread_ts, Telegram forum topic, or Discord thread — the attachment lands in the same thread as the text reply. Adapters that don’t support threads are completely unaffected.Outbound media uploads share the same retry/backoff path as text replies —
DeliveryRouter.send_media wraps the upload in deliver_with_retry, reusing
the adapter’s configured BackoffPolicy (or a sensible default). See
Retry & backoff on transient failures
below and Outbound Resilience for the
underlying policy config.Quick Start
1
Simple Usage
2
With Configuration
How It Works
When an agent returns a media file path, the gateway’s outbound binding validates it through the path safety guard and delivers it via the adapter’s native primitive:Path safety guard
validate_media_delivery_path() runs two checks before any file is sent:
System directory denylist (always blocked): /etc, /proc, /sys, ~/.ssh, ~/.aws, ~/.gnupg, gateway secret/pairing directories.
Credential basename denylist (always blocked): .env, id_rsa, id_ecdsa, id_ed25519, .npmrc, .netrc, and similar credential filenames — matched from any directory.
Configuration (media_delivery YAML block)
OutboundMediaPolicy fields
Platform Support
PlatformCapabilities.supports_media is checked per adapter — platforms that don’t support file uploads receive text-only replies and the attachment is skipped with a log entry.
Threaded media delivery
Attachments follow the sameplatform:channel_id:thread_id grammar the text path uses. When a route names a thread, both the text reply and any uploaded file land in that thread.
DeliveryRouter.send_media resolves the thread from the target and forwards it to deliver_media_to_adapter, which passes it through each transport’s native keyword only when the primitive accepts it — so non-threaded adapters never raise.
Retry & backoff on transient failures
Media uploads go through the samedeliver_with_retry wrapper as text
replies. A transient transport failure — HTTP 5xx, rate limit, connection
reset — is retried with bounded exponential backoff (honouring a
server-mandated Retry-After hint) instead of silently dropping the
attachment on the first blip.
Which backoff policy is used?
DeliveryRouter.send_media reuses the adapter’s already-configured
_outbound_backoff BackoffPolicy if one is set (matching the text
path). If the adapter exposes no policy, a sensible default is used:
To customise, set
outbound_resilience on the channel in praisonai.yml
(the same block that tunes text retries — see
Outbound Resilience). The media
upload picks up the same values automatically.
Non-retryable outcomes are preserved
Prior to this fix the Discord native upload branch caught every
exception locally and returned
False, which bypassed the retry wrapper
entirely — a transient blip on Discord silently dropped the file. That
branch now only catches the missing optional discord dependency; every
other transport error propagates so deliver_with_retry can apply the
same backoff Slack and Telegram already got.Common Patterns
Chart generation bot
Multi-tenant / hosted gateway (strict mode)
For hosted environments where multiple users share the same gateway, enable strict mode with an explicit output directory:Disable media delivery
Summary Messages
When media delivery completes,BotOutboundMessenger.send appends a summary:
Best Practices
Write agent output to a dedicated directory
Write agent output to a dedicated directory
Configure your agent to save all generated files to a single directory like
/tmp/agent-media/ and set that as the allowlist_roots entry. This keeps the path guard simple and predictable.Enable strict mode for production
Enable strict mode for production
In production or multi-tenant gateways, set
strict: true with explicit allowlist_roots. This prevents agents from accidentally delivering files from unexpected locations.Set a reasonable size cap
Set a reasonable size cap
Default is 25 MB. Telegram and Slack have their own size limits — set
max_size_mb below those limits to fail fast at the gateway rather than at the adapter.Never save credentials near the output directory
Never save credentials near the output directory
The basename denylist blocks common credential filenames, but keep your output directory completely separate from config directories. Symlink attacks are also blocked (symlinks are resolved before path checks).
Target the thread for reports that belong to a conversation
Target the thread for reports that belong to a conversation
Use
platform:channel_id:thread_id — not just platform:channel_id — for reports and charts that belong to an ongoing thread. Text and attachment now land together in the same thread; the :channel_id form still posts to the parent chat.Related
Messaging Bots
Full bot setup and configuration guide
Channels Gateway
Gateway channel routing and configuration
Gateway
Gateway architecture and YAML reference
Bot Inbound Media
Handle images and files sent by users

