Skip to content

feat(discord-tool): add add_reaction / remove_reaction actions - #80817

Open
vel007-ai wants to merge 1 commit into
NousResearch:mainfrom
vel007-ai:feat/discord-tool-reactions
Open

feat(discord-tool): add add_reaction / remove_reaction actions#80817
vel007-ai wants to merge 1 commit into
NousResearch:mainfrom
vel007-ai:feat/discord-tool-reactions

Conversation

@vel007-ai

Copy link
Copy Markdown

What does this PR do?

The discord tool can pin, unpin, delete and thread a message, but not react to one. Adds the missing pair to the core (participate) action set, next to create_thread:

add_reaction(channel_id, message_id, emoji)
remove_reaction(channel_id, message_id, emoji)

Both target /channels/{cid}/messages/{mid}/reactions/{emoji}/@me, following the existing pin_message/unpin_message shape. Because they are scoped to @me, an agent can retract only what it placed and can never strip another user's reaction — including the adapter's own 👀/✅ lifecycle marks. The emoji is percent-encoded with : preserved, so both unicode emoji and the custom-emoji name:id form address correctly.

Why

An agent asked to react has nowhere to go today, and the failure is silent rather than loud:

  • The Discord adapter's reaction machinery (_add_reaction/_remove_reaction) is private and reachable only from the lifecycle hooks.
  • send_message(action="react") is deliberately not agent-callable — toolsets.py is explicit that outbound platform messaging is handled outside the agent loop, and send_message_tool.py says the same. (I opened feat(discord): agent-facing add_reaction/remove_reaction (photon parity) #79074 to give the Discord adapter the public add_reaction/remove_reaction pair for photon parity, which is worth having on its own — but it does not put reactions in reach of a model, because nothing in an agent's toolset dispatches to it.)
  • So the only Discord tools a model can actually call are discord and discord_admin, and neither could react.

Observed failure mode, from a real transcript: asked to reply with a verdict and react with it, the model searched its tools ("Discord reply to message and add reaction"), got back discord and discord_admin, found pin/delete/thread but nothing for reactions, and silently gave up — typing the emoji into its reply text instead, where it is prose rather than a reaction. Nothing errored; the capability just wasn't there.

With this change the same prompt works unmodified: the model finds add_reaction, calls it, and the reaction lands alongside the lifecycle ✅.

Related Issue

Partially addresses #29026, which asks for both directions. This is the outbound half (the agent placing reactions); inbound Discord reaction events remain #46855.

Changes Made

  • tools/discord_tool.py_add_reaction/_remove_reaction action handlers, a shared _reaction_path() encoder, both registered in _ACTIONS and _CORE_ACTION_NAMES, entries in _ACTION_MANIFEST and _REQUIRED_PARAMS, and an emoji parameter threaded through _run_discord_action + _HANDLER_DEFAULTS + the schema properties.
  • tests/tools/test_discord_tool.py — 5 tests: add, remove, custom-emoji name:id encoding, core-vs-admin placement, and schema exposure.

How to Test

  1. pytest tests/tools/test_discord_tool.py -q → 46 passed.
  2. Against a live guild, with DISCORD_BOT_TOKEN set:
    discord_core(action="add_reaction", channel_id="<cid>", message_id="<mid>", emoji="🟢")
    discord_core(action="remove_reaction", channel_id="<cid>", message_id="<mid>", emoji="🟢")
    Both return {"success": true, ...} and the reaction appears/disappears. I verified this on a real message, then end-to-end: an agent asked for a verdict reaction placed 🔴 itself via the new action, leaving the lifecycle ✅ untouched.

Checklist

Code

Documentation & Housekeeping

  • I've updated relevant documentation — the tool's own action manifest and schema, which is what the model reads
  • N/A — no config keys added (the actions respect the existing discord.server_actions allowlist)
  • N/A — no architecture or workflow change
  • I've considered cross-platform impact — pure Python + stdlib urllib
  • I've updated tool descriptions/schemas

The discord tool can pin, unpin, delete and thread a message, but not
react to one. An agent asked to react has nowhere to go: the adapter's
reaction machinery is private and reachable only from the 👀/✅
lifecycle hooks, and `send_message(action="react")` is deliberately not
agent-callable (toolsets.py: outbound messaging is handled outside the
agent loop). Observed failure mode is quiet - the model searches its
tools, finds pin/delete/thread but no react, and types the emoji into
its reply instead, where it is prose rather than a reaction.

Adds the pair to the core (participate) action set, next to
create_thread, following the pin_message/unpin_message shape:

  add_reaction(channel_id, message_id, emoji)
  remove_reaction(channel_id, message_id, emoji)

Both target the bot's own reaction (`/reactions/{emoji}/@me`), so an
agent can retract only what it placed and never strips another user's.
The emoji is percent-encoded with ':' preserved, so unicode emoji and
the custom-emoji `name:id` form both address correctly.
@alt-glitch alt-glitch added type/feature New feature or request comp/tools Tool registry, model_tools, toolsets platform/discord Discord bot adapter P3 Low — cosmetic, nice to have duplicate This issue or pull request already exists labels Aug 7, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Duplicate of #67008. Both PRs add the same agent-callable Discord REST add_reaction/remove_reaction actions with own-reaction-only removal; #67008 is the earlier open implementation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/tools Tool registry, model_tools, toolsets duplicate This issue or pull request already exists P3 Low — cosmetic, nice to have platform/discord Discord bot adapter type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants