Skip to content

feat(talon): add Telegram channel adapter, CLI wiring, and offset persistence - #4097

Merged
Mason Daugherty (mdrxy) merged 49 commits into
mainfrom
jk/talon/telegram-channel-adapter
Jun 26, 2026
Merged

feat(talon): add Telegram channel adapter, CLI wiring, and offset persistence#4097
Mason Daugherty (mdrxy) merged 49 commits into
mainfrom
jk/talon/telegram-channel-adapter

Conversation

@jkennedyvz

@jkennedyvz John Kennedy (jkennedyvz) commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Implements a Telegram Bot API channel for Talon and updates the shared channel policy so Telegram and WhatsApp use the same exposure rules.


Telegram channel

Adds a pure-Python Telegram channel adapter that uses the Bot API directly rather than a local bridge. It supports long polling, persisted update offsets, inbound media downloads, outbound text and media, typing indicators, message edits, and plain-text message delivery.

Telegram configuration is wired through DEEPAGENTS_TALON_TELEGRAM_*, with TELEGRAM_BOT_TOKEN accepted as a convenience alias. The host can run Telegram, WhatsApp, or both channels against the same Talon runtime.

Shared Channel Exposure

Moves provider-specific exposure parsing into the shared channel abstraction via ChannelExposureEnv and channel_exposure_from_env. Telegram and WhatsApp now share the same handling for self, allowlist, open, mention patterns, open-exposure acknowledgement, and operator identity.

ChannelExposure now supports multiple operator IDs while preserving the existing single operator_id API. The existing DEEPAGENTS_TALON_TELEGRAM_OPERATOR_ID and DEEPAGENTS_TALON_WHATSAPP_OPERATOR_ID env vars accept comma-separated IDs, so self exposure can allow more than one trusted operator without duplicating logic in each provider.

Media And Runtime Handling

Adds shared media helpers for inbound media metadata, outbound media validation, and configured media-size caps. Telegram and WhatsApp both use the common cap handling, while WhatsApp keeps a lower provider-specific cap because its bridge materializes downloads in memory.

The host and runtime wiring now preserve channel metadata, support channel-specific approval handling, and allow Telegram channel posts through the same serialized agent execution path as other Talon channels.

Example App

Refreshes the example app from a WhatsApp-only example into a general Talon Docker example. The example now documents running WhatsApp, Telegram, or both; adds placeholder-only env settings for Telegram allowlists and channel toggles; keeps local secrets in an ignored .env; and updates the Docker setup so the packaged WhatsApp bridge and Talon runtime work together from the same container.

Review Notes

The exposure changes are intentionally centralized in the shared channel layer. Provider modules should only declare their env var names and provider-specific requirements, such as Telegram requiring an operator ID for self exposure.

The example app changes are mostly naming and configuration surface updates so it reflects the new multi-channel Talon host rather than only WhatsApp.

@github-actions github-actions Bot added feature New feature/enhancement or request for one internal User is a member of the `langchain-ai` GitHub organization size: XL 1000+ LOC talon labels Jun 20, 2026

@open-swe open-swe Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Open SWE Review found 3 potential issues.

Open in WebView Open SWE trace

Comment thread libs/talon/deepagents_talon/channels/telegram.py
Comment thread libs/talon/deepagents_talon/channels/telegram.py Outdated
Comment thread libs/talon/deepagents_talon/channels/telegram.py Outdated

@corridor-security corridor-security Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security Issues

  • Missing Authentication / Exposure Bypass
    The Telegram adapter marks any private Telegram message as from_self by comparing sender_id to chat_id. In Telegram private chats, those IDs match for every user, so the default self/operator exposure mode can treat arbitrary Telegram users as trusted operators. An attacker who can message the bot could trigger the Talon agent with local host access unless an explicit operator ID or allowlist is configured.

Recommendations

  • Do not infer operator trust from chat.id == from.id for Telegram private chats.
  • Require an explicit DEEPAGENTS_TALON_TELEGRAM_OPERATOR_ID or allowlist before enabling the channel, or persistently bind the first operator only through an explicit local approval/setup flow.
  • Ensure ChannelExposure checks Telegram sender IDs against a trusted configured/captured operator ID, not the from_self metadata derived here.

Comment thread libs/talon/deepagents_talon/channels/telegram.py Outdated

@open-swe open-swe Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Open SWE Review found 1 potential issue.

Open in WebView Open SWE trace

Comment thread libs/talon/deepagents_talon/channels/telegram.py

@open-swe open-swe Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Open SWE Review found 1 potential issue.

Open in WebView Open SWE trace

Comment thread examples/talon-docker/README.md Outdated

@corridor-security corridor-security Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security Issues

  • Uncontrolled Resource Consumption (DoS) via unbounded inbound media downloads
    Inbound Telegram media are downloaded from the Bot API without any size checks or streaming limits. An attacker who can send messages to the bot (e.g., when exposure mode is set to open, or a malicious/compromised allowed user/channel) can repeatedly send large files to exhaust disk space and memory (the code reads the entire response into memory before writing). The code ignores the file_size field returned by getFile and does not enforce any maximum size during download or impose a streaming cap, creating a clear and realistic DoS vector.

Recommendations

  • Before downloading, read and validate result["file_size"] from getFile against a strict cap (e.g., the same MAX_DOCUMENT_BYTES or a lower inbound-specific limit).
  • Stream the download in bounded chunks and abort if a cumulative byte cap is exceeded (do not read the entire response into memory).
  • Consider limiting total concurrent downloads and rate limiting per chat/user to reduce abuse impact.

Comment thread libs/talon/deepagents_talon/channels/telegram.py Outdated

@open-swe open-swe Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Open SWE Review found 1 potential issue.

Open in WebView Open SWE trace

Comment thread libs/talon/deepagents_talon/channels/whatsapp_bridge/bridge.js Outdated
Deep Agent added 12 commits June 20, 2026 12:04
…sistence

Implements a pure-Python Telegram Bot API channel adapter for the talon
host, following the same patterns as the existing WhatsApp channel.

**Telegram channel adapter** ():
-  with env-var mapping (
  prefix) and  alias
-  using  — no bridge subprocess needed
- Long-polling via  with configurable timeout and interval
- MarkdownV2 message formatting with automatic plain-text fallback
-  for images,  for other media types
-  typing indicator
-  for inbound media downloads
- Operator auto-capture from first inbound message
- Configurable exposure modes (default, allowlist, open)

**Offset persistence** (ticket 23):
-  in the session directory
- Atomic writes via
- Loads persisted offset on startup, saves after each polling cycle
- Graceful handling of missing/corrupt/invalid offset files

**CLI wiring** ():
-  flag to enable the Telegram channel
- Updated  factory to accept both  and
- Both channels can coexist simultaneously

**Config** ():
- Added  to
- Added  to  for alias capture

**Tests**:
- 36 unit tests covering config, MarkdownV2 escaping, polling, exposure,
  outbound text/media, edit, typing, inbound media, status, offset
  persistence
- 7 integration tests for  factory and simultaneous channel
  coexistence

Closes #21, #22, #23
@jkennedyvz
John Kennedy (jkennedyvz) force-pushed the jk/talon/telegram-channel-adapter branch from 63ae07f to 4f3a6e4 Compare June 20, 2026 19:05
@jkennedyvz
John Kennedy (jkennedyvz) enabled auto-merge (squash) June 21, 2026 03:21
Address code review feedback on PR #4097:

- Unify / via shared
- Remove redundant  — callers already validate
- Move  to  and import from both adapters
- Centralize open-exposure warning in ,
  eliminating provider-specific  wrappers
- Convert  from stored field to computed
   returning
- Simplify  using  with a key function
- Reuse  in  instead of
  reimplementing the size check
- Inline trivial  property and
   one-liner
- Extract  helper in  for duplicated truthy-set
- Fix  tmp file path to use  +  suffix
- Document intentional  behavior in
   docstring
- Extract shared  helper for identical  methods
- Consolidate test  into shared

@open-swe open-swe Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Open SWE Review found 1 potential issue.

Open in WebView Open SWE trace

Comment thread libs/talon/deepagents_talon/channels/telegram.py Outdated

@open-swe open-swe Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Open SWE Review found 1 potential issue.

Open in WebView Open SWE trace

Comment thread libs/talon/deepagents_talon/interfaces.py
Deep Agent added 2 commits June 21, 2026 20:45
… size limit

- Prefix module-internal symbols with underscore: ,
  , , ,
  ,
- Remove  from
- Remove per-type media constants (,
  in base.py; , ,
  in telegram.py) and  — all channels now use a
  single  from config controlled by

- Simplify  to check only
- Update tests to match renamed symbols and use config-based size limit

@open-swe open-swe Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Open SWE Review found 1 potential issue.

Open in WebView Open SWE trace

Comment thread libs/talon/deepagents_talon/host.py
Deep Agent added 2 commits June 22, 2026 11:43
send_with_retry only handled SendResult failures, not exceptions. Both
built-in adapters can raise from their transport paths (_TelegramError,
_WhatsAppBridgeError), so an upload/send failure escaped
_send_channel_media and aborted the channel task instead of adding the
item to failed and delivering the text fallback.

Wrap each send_fn call in _safe_send, which catches exceptions and
converts them to failed SendResult objects (retryable=True). This keeps
media delivery failures non-fatal at every call site, not just
_send_channel_media.

Add tests verifying exception-to-SendResult conversion and retry after
exception.
Comment thread libs/talon/deepagents_talon/channels/base.py Outdated
Comment thread libs/talon/deepagents_talon/channels/base.py Outdated
@mdrxy

Copy link
Copy Markdown
Member

what's the user story here?

Deep Agent and others added 6 commits June 22, 2026 15:25
…annels

Remove WhatsApp-specific shared config from .env.example and docker-compose.yml.
Both channel configs are now commented-out templates the operator enables via env
vars. ASR (voice transcription) defaults to enabled. The Dockerfile COPY path and
CMD are updated to drop the talon-whatsapp reference and --whatsapp flag. README
is rewritten to cover both channels with separate setup and exposure reference
sections.
Co-authored-by: Mason Daugherty <mason@langchain.dev>
Signed-off-by: John Kennedy <65985482+jkennedyvz@users.noreply.github.com>
Mason's feedback (PR #4097 discussion_r3455630256): move the
ChannelExposureEnv and ChannelExposure Args blocks to
attribute-level docstring form so ref-doc parsers (griffe/mkdocstrings)
pick them up as field documentation rather than only as constructor
params.

Also applies the nit on env_prefix (single backticks per project
docstring convention).
Use cp -n (no-clobber) so the AGENTS.md template is only seeded on
first run. Previously, cp overwrote the agent's accumulated memory
with the template on every container start.
@github-actions github-actions Bot added the dependencies Pull requests that update a dependency file label Jun 24, 2026
Deep Agent added 3 commits June 23, 2026 22:14
Broaden ASR eligibility so that video files and audio-typed documents
sent via Telegram or WhatsApp are transcribed when voice packages are
installed and enabled. Previously only `voice` media type triggered
transcription.

- `_is_voice_message` now matches `media_type == "video"` and
  checks `media_mime_types` for `audio/*` MIME types
- `_document_media_type` classifies documents with audio MIME types
  or audio file extensions as `voice` instead of `document`
- `message_with_media_paths` sets `voice_path` for `video` media
  so the transcriber can locate the file
- ffmpeg's `_convert_to_wav` already extracts audio from video
  containers, so no change needed there
- Move `_ASR_ELIGIBLE_MEDIA_TYPES` constant from `speech.py` to `base.py`
  as `ASR_ELIGIBLE_MEDIA_TYPES` and import it in `speech.py`, eliminating
  the duplicated `{"voice", "video"}` set
- Collapse `_document_media_type` from 4 sequential if-blocks to a
  2-iteration loop over `(mime_type, guessed)`
- Stop setting `voice_path` for video in `message_with_media_paths` —
  video transcription still works via `ASR_ELIGIBLE_MEDIA_TYPES` check in
  `_is_voice_message` and `media_path` fallback in `_voice_path`
- Remove the dead MIME-type scan branch from `_is_voice_message` —
  `_document_media_type` now classifies audio documents as `"voice"`
  upstream, making the scan unreachable in the real channel pipeline
@mdrxy
Mason Daugherty (mdrxy) merged commit 7c87cec into main Jun 26, 2026
50 checks passed
@mdrxy
Mason Daugherty (mdrxy) deleted the jk/talon/telegram-channel-adapter branch June 26, 2026 18:47
John Kennedy (jkennedyvz) pushed a commit that referenced this pull request Jun 30, 2026
> [!CAUTION]
> Merging this PR will automatically publish to **PyPI** and create a
**GitHub release**.

For the full release process, see
[`.github/RELEASING.md`](https://github.com/langchain-ai/deepagents/blob/main/.github/RELEASING.md).

---

_Everything below this line will be the GitHub release body._

---


##
[0.0.2](deepagents-talon==0.0.1...deepagents-talon==0.0.2)
(2026-06-30)


### Features

* **talon:** `DEEPAGENTS_TALON_RECURSION_LIMIT` env var
([#4354](#4354))
([82d1eac](82d1eac))
* **talon:** add reaction approval routing
([#4345](#4345))
([3fe8c0c](3fe8c0c))
* **talon:** add Telegram channel adapter, CLI wiring, and offset
persistence
([#4097](#4097))
([7c87cec](7c87cec))
* **talon:** add tool approval env override
([#4349](#4349))
([d26481d](d26481d))
* **talon:** audit reaction approval attempts
([#4348](#4348))
([d7895c4](d7895c4))
* **talon:** ingest Telegram approval reactions
([#4346](#4346))
([437af0b](437af0b))


### Bug Fixes

* **talon:** default workspace to current directory
([#4099](#4099))
([5e337ae](5e337ae))

---

_Everything above this line will be the GitHub release body._

---

> [!NOTE]
> A **New Contributors** section is appended to the GitHub release notes
automatically at publish time (see [Release
Pipeline](https://github.com/langchain-ai/deepagents/blob/main/.github/RELEASING.md#release-pipeline),
step 2).

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Marcelo5444 pushed a commit to Marcelo5444/deepagents that referenced this pull request Jul 30, 2026
…sistence (langchain-ai#4097)

Implements a Telegram Bot API channel for Talon and updates the shared
channel policy so Telegram and WhatsApp use the same exposure rules.

---

## Telegram channel

Adds a pure-Python Telegram channel adapter that uses the Bot API
directly rather than a local bridge. It supports long polling, persisted
update offsets, inbound media downloads, outbound text and media, typing
indicators, message edits, and plain-text message delivery.

Telegram configuration is wired through `DEEPAGENTS_TALON_TELEGRAM_*`,
with `TELEGRAM_BOT_TOKEN` accepted as a convenience alias. The host can
run Telegram, WhatsApp, or both channels against the same Talon runtime.

## Shared Channel Exposure

Moves provider-specific exposure parsing into the shared channel
abstraction via `ChannelExposureEnv` and `channel_exposure_from_env`.
Telegram and WhatsApp now share the same handling for `self`,
`allowlist`, `open`, mention patterns, open-exposure acknowledgement,
and operator identity.

`ChannelExposure` now supports multiple operator IDs while preserving
the existing single `operator_id` API. The existing
`DEEPAGENTS_TALON_TELEGRAM_OPERATOR_ID` and
`DEEPAGENTS_TALON_WHATSAPP_OPERATOR_ID` env vars accept comma-separated
IDs, so `self` exposure can allow more than one trusted operator without
duplicating logic in each provider.

## Media And Runtime Handling

Adds shared media helpers for inbound media metadata, outbound media
validation, and configured media-size caps. Telegram and WhatsApp both
use the common cap handling, while WhatsApp keeps a lower
provider-specific cap because its bridge materializes downloads in
memory.

The host and runtime wiring now preserve channel metadata, support
channel-specific approval handling, and allow Telegram channel posts
through the same serialized agent execution path as other Talon
channels.

## Example App

Refreshes the example app from a WhatsApp-only example into a general
Talon Docker example. The example now documents running WhatsApp,
Telegram, or both; adds placeholder-only env settings for Telegram
allowlists and channel toggles; keeps local secrets in an ignored
`.env`; and updates the Docker setup so the packaged WhatsApp bridge and
Talon runtime work together from the same container.

## Review Notes

The exposure changes are intentionally centralized in the shared channel
layer. Provider modules should only declare their env var names and
provider-specific requirements, such as Telegram requiring an operator
ID for `self` exposure.

The example app changes are mostly naming and configuration surface
updates so it reflects the new multi-channel Talon host rather than only
WhatsApp.

---------

Signed-off-by: John Kennedy <65985482+jkennedyvz@users.noreply.github.com>
Co-authored-by: Deep Agent <agent@deepagents.dev>
Co-authored-by: Mason Daugherty <mason@langchain.dev>
Marcelo5444 pushed a commit to Marcelo5444/deepagents that referenced this pull request Jul 30, 2026
> [!CAUTION]
> Merging this PR will automatically publish to **PyPI** and create a
**GitHub release**.

For the full release process, see
[`.github/RELEASING.md`](https://github.com/langchain-ai/deepagents/blob/main/.github/RELEASING.md).

---

_Everything below this line will be the GitHub release body._

---


##
[0.0.2](langchain-ai/deepagents@deepagents-talon==0.0.1...deepagents-talon==0.0.2)
(2026-06-30)


### Features

* **talon:** `DEEPAGENTS_TALON_RECURSION_LIMIT` env var
([langchain-ai#4354](langchain-ai#4354))
([82d1eac](langchain-ai@82d1eac))
* **talon:** add reaction approval routing
([langchain-ai#4345](langchain-ai#4345))
([3fe8c0c](langchain-ai@3fe8c0c))
* **talon:** add Telegram channel adapter, CLI wiring, and offset
persistence
([langchain-ai#4097](langchain-ai#4097))
([7c87cec](langchain-ai@7c87cec))
* **talon:** add tool approval env override
([langchain-ai#4349](langchain-ai#4349))
([d26481d](langchain-ai@d26481d))
* **talon:** audit reaction approval attempts
([langchain-ai#4348](langchain-ai#4348))
([d7895c4](langchain-ai@d7895c4))
* **talon:** ingest Telegram approval reactions
([langchain-ai#4346](langchain-ai#4346))
([437af0b](langchain-ai@437af0b))


### Bug Fixes

* **talon:** default workspace to current directory
([langchain-ai#4099](langchain-ai#4099))
([5e337ae](langchain-ai@5e337ae))

---

_Everything above this line will be the GitHub release body._

---

> [!NOTE]
> A **New Contributors** section is appended to the GitHub release notes
automatically at publish time (see [Release
Pipeline](https://github.com/langchain-ai/deepagents/blob/main/.github/RELEASING.md#release-pipeline),
step 2).

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file feature New feature/enhancement or request for one internal User is a member of the `langchain-ai` GitHub organization size: XL 1000+ LOC talon

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants