feat(talon): add reaction approval routing - #4345
Merged
John Kennedy (jkennedyvz) merged 1 commit intoJun 28, 2026
Merged
Conversation
John Kennedy (jkennedyvz)
marked this pull request as ready for review
June 27, 2026 22:47
John Kennedy (jkennedyvz)
enabled auto-merge (squash)
June 27, 2026 22:56
John Kennedy (jkennedyvz)
disabled auto-merge
June 27, 2026 22:56
This was referenced Jun 27, 2026
John Kennedy (jkennedyvz)
deleted the
john/talon/reaction-approval-contract
branch
June 28, 2026 22:12
John Kennedy (jkennedyvz)
added a commit
that referenced
this pull request
Jun 28, 2026
Stacked on #4345. Adds `DEEPAGENTS_TALON_INTERRUPT_ON_TOOLS` so Talon operators can force channel approval for named tools at runtime without changing Fleet exports. The overlay is additive with imported Fleet `interrupt_on` config and is also applied on the non-Fleet MCP runtime path. The env value is parsed as a comma-separated list, trims surrounding whitespace, ignores empty entries, and maps each listed tool to the default `True` interrupt configuration. Fleet reloads reapply the same overlay. ## When this is useful Some tools are safe to run autonomously in one deployment but risky in another (e.g. `bash`, `execute`, `github_create_pr`). Rather than re-exporting a Fleet agent or editing `interrupt_on` in code every time the risk profile changes, an operator can flip a single env var at deploy/runtime to force those tools through Talon's channel approval flow. The override is additive, so Fleet-provided approvals keep working and the env list just layers extra guarded tools on top. This also covers the non-Fleet (local/MCP) runtime path, which previously had no equivalent knob. ## Example Force `bash` and `github_create_pr` to require channel approval: ```bash DEEPAGENTS_TALON_INTERRUPT_ON_TOOLS=bash,execute,github_create_pr ``` Whitespace and empty entries are tolerated, so `" bash, execute, , github_create_pr "` parses to the same three tools. When the agent then calls `bash`, Talon posts an approval prompt in the channel: ``` 🤖 Tool approval required. 1. bash(command="rm -rf /tmp/build") React 👍 to approve or 👎 to reject. ``` Because this PR stacks on #4345, the operator can approve/deny by reacting with 👍/👎 on that exact prompt (scoped to provider, conversation, prompt message id, and sender) — or by typing `approve`/`deny` as before. With `fleet_tool` already configured via Fleet, the merged `interrupt_on` becomes `{"fleet_tool": True, "bash": True, "execute": True, "github_create_pr": True}`, and Fleet reloads reapply the same overlay so the gate survives credential refreshes. Tests cover the parser, Fleet merge behavior, Fleet reload components, non-Fleet runtime wiring, empty env behavior, and graph construction. --------- Co-authored-by: Deep Agent <agent@deepagents.dev>
John Kennedy (jkennedyvz)
added a commit
that referenced
this pull request
Jun 28, 2026
Stacked on #4345. Adds Telegram `message_reaction` ingestion for approval reactions. The adapter now requests `message_reaction` updates, extracts concrete-user emoji reactions into `ChannelReaction`, applies the existing Telegram exposure policy before dispatch, ignores anonymous or senderless reactions, and preserves offset advancement for processed or dropped reaction updates. Normal message and channel-post parsing remain unchanged. ## Approving by reaction When a tool requires approval the host posts a prompt like: ``` Tool approval required. 1. `shell` Args: `{"cmd": "make deploy production"}` Reply `👍` / `approve` to run or `👎` / `deny` to skip. ``` The operator can now respond two ways: - **Text reply** — `approve` / `deny` (unchanged). - **Emoji reaction** — tap 👍 on the prompt message to approve, 👎 to reject. Reactions are gated the same way as messages: only operators in the exposure policy's `operator_ids` or the channel's `allowed_user_ids` are honored, and the reaction must land on the exact approval prompt in the matching conversation. Anonymous or senderless reactions are dropped. 👍 maps to `approve`, 👎 to `reject`; everything else is ignored. This is most useful on mobile, where a single tap on the prompt beats typing a reply, and for high-volume approval queues where the operator is triaging many tool calls quickly. Validation run: - `uv run --group test pytest --disable-socket --allow-unix-socket tests/channels/test_telegram.py --timeout 10` - `make lint_diff` - `make test` <img width="936" height="738" alt="Screenshot 2026-06-27 at 4 30 02 PM" src="https://github.com/user-attachments/assets/58756f30-c8ff-4bd8-8057-84c8681b6687" /> --------- Co-authored-by: Deep Agent <agent@deepagents.dev>
John Kennedy (jkennedyvz)
added a commit
that referenced
this pull request
Jun 28, 2026
Stacked on #4345. Adds host-side `tool_approval.reaction` audit events for reaction approval attempts. The event records provider, stable hashed channel conversation/prompt/sender references, emoji, decision, match status, and resolution mode. Raw provider IDs are included only when `DEEPAGENTS_TALON_APPROVAL_LOG_RAW_IDS=true`; raw provider metadata, approval prompt text, arbitrary message text, and tool arguments are not logged. Matched reaction approvals use `operator_reaction` as the resolution so they can be distinguished from text approvals. ## When this is useful Reaction approvals are a one-tap action that's easy to fire off and easy to miss-context (wrong message, wrong channel, stale prompt). Without an audit trail there's no way to answer "did someone try to approve that tool call, and why didn't it take?" after the fact. These events let operators and security reviewers trace every reaction attempt — matched or ignored — back to a specific provider, conversation, prompt message, and sender, without leaking raw IDs or prompt/message contents by default. Distinguishing `operator_reaction` from text `approve`/`deny` also makes it possible to monitor how often the reaction path is actually used versus the text fallback. ## Example An operator reacts 👍 to the approval prompt. The host emits a structured `tool_approval.reaction` event: ```json { "event": "tool_approval.reaction", "provider": "slack", "channel_conversation_ref": "9f1c…a4e2", "prompt_message_ref": "b7d3…0c91", "reacting_sender_ref": "e2a8…77f0", "emoji": "👍", "decision": "approve", "match_status": "matched", "resolution": "operator_reaction" } ``` Conversation/prompt/sender references are stable hashes (via `stable_log_ref`), so the same IDs correlate across events without exposing the underlying Slack IDs. Ignored attempts are logged too, with a precise `resolution` explaining why: - `no_pending_approval` — reaction arrived with no pending tool approval - `unsupported_emoji` — emoji wasn't 👍/👎 - `missing_prompt_message_id` — channel didn't supply a prompt message id (falls back to text) - `provider_mismatch` / `conversation_mismatch` / `message_mismatch` — reaction didn't target the prompt that issued the request - `sender_missing` / `sender_mismatch` — reactor wasn't the initiating operator (when sender is known) For debugging a specific incident, set `DEEPAGENTS_TALON_APPROVAL_LOG_RAW_IDS=true` to additionally include `raw_channel_conversation_id`, `raw_prompt_message_id`, and `raw_reacting_sender_id`. Sensitive values — provider metadata, approval prompt text, arbitrary message text, and tool arguments — are never logged regardless of that flag. Validation run: - `uv run --group test pytest --disable-socket --allow-unix-socket tests/test_host.py --timeout 10` - `make lint_diff` - `make test` Co-authored-by: Deep Agent <agent@deepagents.dev>
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
Adds a provider-neutral `ChannelReaction` contract and optional reaction handler registration surface for Talon channels. The host now binds pending tool approvals to provider, channel conversation, agent conversation, prompt message id, and initiating sender before accepting thumbs-up or thumbs-down reactions. Reactions without prompt message ids fall back to the existing text approval flow, and existing text replies remain unchanged. ## When this is useful Chat platforms like Slack, Discord, or Teams let users react to a message with an emoji instead of typing a reply. Reaction approval lets an operator approve or deny a pending tool call with a single 👍/👎 on the approval prompt — no extra typing, and the reaction stays scoped to the exact message that asked for approval. This is especially handy in busy channels where typed `approve`/`deny` replies are easy to miss or get crossed with other conversation. ## Example An agent requests tool approval and the host posts the prompt (recording the returned message id): ``` 🤖 Tool approval required. 1. send_email(to="ops@example.com", subject="Deploy complete") React 👍 to approve or 👎 to reject. ``` The operator reacts to that prompt with 👍. Skin tones and variation selectors are normalized, so 👍🏽 and 👎️ work too. The host resolves the reaction to the pending approval only when **all** of the following line up: - the emoji is 👍 (approve) or 👎 (reject) - the channel/provider matches the one that issued the prompt - the reaction is on the same conversation and the exact prompt message - the reactor is the same sender who triggered the run (when the sender is known) If anything doesn't match — wrong message, different sender, unrelated emoji — the reaction is ignored and the pending approval keeps waiting. Channels that don't expose message ids (or don't implement `ReactionChannelAdapter`) keep using the existing text `approve`/`deny` flow unchanged. Validation run: - `uv run --group test pytest --disable-socket --allow-unix-socket tests/test_host.py --timeout 10` - `make lint_diff` - `make test` Co-authored-by: Deep Agent <agent@deepagents.dev>
Marcelo5444
pushed a commit
to Marcelo5444/deepagents
that referenced
this pull request
Jul 30, 2026
Stacked on langchain-ai#4345. Adds `DEEPAGENTS_TALON_INTERRUPT_ON_TOOLS` so Talon operators can force channel approval for named tools at runtime without changing Fleet exports. The overlay is additive with imported Fleet `interrupt_on` config and is also applied on the non-Fleet MCP runtime path. The env value is parsed as a comma-separated list, trims surrounding whitespace, ignores empty entries, and maps each listed tool to the default `True` interrupt configuration. Fleet reloads reapply the same overlay. ## When this is useful Some tools are safe to run autonomously in one deployment but risky in another (e.g. `bash`, `execute`, `github_create_pr`). Rather than re-exporting a Fleet agent or editing `interrupt_on` in code every time the risk profile changes, an operator can flip a single env var at deploy/runtime to force those tools through Talon's channel approval flow. The override is additive, so Fleet-provided approvals keep working and the env list just layers extra guarded tools on top. This also covers the non-Fleet (local/MCP) runtime path, which previously had no equivalent knob. ## Example Force `bash` and `github_create_pr` to require channel approval: ```bash DEEPAGENTS_TALON_INTERRUPT_ON_TOOLS=bash,execute,github_create_pr ``` Whitespace and empty entries are tolerated, so `" bash, execute, , github_create_pr "` parses to the same three tools. When the agent then calls `bash`, Talon posts an approval prompt in the channel: ``` 🤖 Tool approval required. 1. bash(command="rm -rf /tmp/build") React 👍 to approve or 👎 to reject. ``` Because this PR stacks on langchain-ai#4345, the operator can approve/deny by reacting with 👍/👎 on that exact prompt (scoped to provider, conversation, prompt message id, and sender) — or by typing `approve`/`deny` as before. With `fleet_tool` already configured via Fleet, the merged `interrupt_on` becomes `{"fleet_tool": True, "bash": True, "execute": True, "github_create_pr": True}`, and Fleet reloads reapply the same overlay so the gate survives credential refreshes. Tests cover the parser, Fleet merge behavior, Fleet reload components, non-Fleet runtime wiring, empty env behavior, and graph construction. --------- Co-authored-by: Deep Agent <agent@deepagents.dev>
Marcelo5444
pushed a commit
to Marcelo5444/deepagents
that referenced
this pull request
Jul 30, 2026
Stacked on langchain-ai#4345. Adds Telegram `message_reaction` ingestion for approval reactions. The adapter now requests `message_reaction` updates, extracts concrete-user emoji reactions into `ChannelReaction`, applies the existing Telegram exposure policy before dispatch, ignores anonymous or senderless reactions, and preserves offset advancement for processed or dropped reaction updates. Normal message and channel-post parsing remain unchanged. ## Approving by reaction When a tool requires approval the host posts a prompt like: ``` Tool approval required. 1. `shell` Args: `{"cmd": "make deploy production"}` Reply `👍` / `approve` to run or `👎` / `deny` to skip. ``` The operator can now respond two ways: - **Text reply** — `approve` / `deny` (unchanged). - **Emoji reaction** — tap 👍 on the prompt message to approve, 👎 to reject. Reactions are gated the same way as messages: only operators in the exposure policy's `operator_ids` or the channel's `allowed_user_ids` are honored, and the reaction must land on the exact approval prompt in the matching conversation. Anonymous or senderless reactions are dropped. 👍 maps to `approve`, 👎 to `reject`; everything else is ignored. This is most useful on mobile, where a single tap on the prompt beats typing a reply, and for high-volume approval queues where the operator is triaging many tool calls quickly. Validation run: - `uv run --group test pytest --disable-socket --allow-unix-socket tests/channels/test_telegram.py --timeout 10` - `make lint_diff` - `make test` <img width="936" height="738" alt="Screenshot 2026-06-27 at 4 30 02 PM" src="https://github.com/user-attachments/assets/58756f30-c8ff-4bd8-8057-84c8681b6687" /> --------- Co-authored-by: Deep Agent <agent@deepagents.dev>
Marcelo5444
pushed a commit
to Marcelo5444/deepagents
that referenced
this pull request
Jul 30, 2026
Stacked on langchain-ai#4345. Adds host-side `tool_approval.reaction` audit events for reaction approval attempts. The event records provider, stable hashed channel conversation/prompt/sender references, emoji, decision, match status, and resolution mode. Raw provider IDs are included only when `DEEPAGENTS_TALON_APPROVAL_LOG_RAW_IDS=true`; raw provider metadata, approval prompt text, arbitrary message text, and tool arguments are not logged. Matched reaction approvals use `operator_reaction` as the resolution so they can be distinguished from text approvals. ## When this is useful Reaction approvals are a one-tap action that's easy to fire off and easy to miss-context (wrong message, wrong channel, stale prompt). Without an audit trail there's no way to answer "did someone try to approve that tool call, and why didn't it take?" after the fact. These events let operators and security reviewers trace every reaction attempt — matched or ignored — back to a specific provider, conversation, prompt message, and sender, without leaking raw IDs or prompt/message contents by default. Distinguishing `operator_reaction` from text `approve`/`deny` also makes it possible to monitor how often the reaction path is actually used versus the text fallback. ## Example An operator reacts 👍 to the approval prompt. The host emits a structured `tool_approval.reaction` event: ```json { "event": "tool_approval.reaction", "provider": "slack", "channel_conversation_ref": "9f1c…a4e2", "prompt_message_ref": "b7d3…0c91", "reacting_sender_ref": "e2a8…77f0", "emoji": "👍", "decision": "approve", "match_status": "matched", "resolution": "operator_reaction" } ``` Conversation/prompt/sender references are stable hashes (via `stable_log_ref`), so the same IDs correlate across events without exposing the underlying Slack IDs. Ignored attempts are logged too, with a precise `resolution` explaining why: - `no_pending_approval` — reaction arrived with no pending tool approval - `unsupported_emoji` — emoji wasn't 👍/👎 - `missing_prompt_message_id` — channel didn't supply a prompt message id (falls back to text) - `provider_mismatch` / `conversation_mismatch` / `message_mismatch` — reaction didn't target the prompt that issued the request - `sender_missing` / `sender_mismatch` — reactor wasn't the initiating operator (when sender is known) For debugging a specific incident, set `DEEPAGENTS_TALON_APPROVAL_LOG_RAW_IDS=true` to additionally include `raw_channel_conversation_id`, `raw_prompt_message_id`, and `raw_reacting_sender_id`. Sensitive values — provider metadata, approval prompt text, arbitrary message text, and tool arguments — are never logged regardless of that flag. Validation run: - `uv run --group test pytest --disable-socket --allow-unix-socket tests/test_host.py --timeout 10` - `make lint_diff` - `make test` Co-authored-by: Deep Agent <agent@deepagents.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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds a provider-neutral
ChannelReactioncontract and optional reaction handler registration surface for Talon channels. The host now binds pending tool approvals to provider, channel conversation, agent conversation, prompt message id, and initiating sender before accepting thumbs-up or thumbs-down reactions. Reactions without prompt message ids fall back to the existing text approval flow, and existing text replies remain unchanged.When this is useful
Chat platforms like Slack, Discord, or Teams let users react to a message with an emoji instead of typing a reply. Reaction approval lets an operator approve or deny a pending tool call with a single 👍/👎 on the approval prompt — no extra typing, and the reaction stays scoped to the exact message that asked for approval. This is especially handy in busy channels where typed
approve/denyreplies are easy to miss or get crossed with other conversation.Example
An agent requests tool approval and the host posts the prompt (recording the returned message id):
The operator reacts to that prompt with 👍. Skin tones and variation selectors are normalized, so 👍🏽 and 👎️ work too. The host resolves the reaction to the pending approval only when all of the following line up:
If anything doesn't match — wrong message, different sender, unrelated emoji — the reaction is ignored and the pending approval keeps waiting. Channels that don't expose message ids (or don't implement
ReactionChannelAdapter) keep using the existing textapprove/denyflow unchanged.Validation run:
uv run --group test pytest --disable-socket --allow-unix-socket tests/test_host.py --timeout 10make lint_diffmake test