feat(discord): add send_discord_message proactive-post tool#1026
Merged
Aaronontheweb merged 5 commits intoMay 18, 2026
Merged
Conversation
Adds a channel-only send_discord_message tool so the agent can start a new Discord conversation — for DeliveryKind.Channel reminder delivery and cross-channel outreach — at parity with send_slack_message. The tool posts a message, creates a thread off it, and wires the thread into the actor hierarchy via StartProactiveThread so user replies route back to a live session. The bot's posted message is recovered as adopted context on the first authorized reply through the existing deferred-hydration path, satisfying the thread-history-backfill conformance contract. DM (user_id) targets are deliberately deferred: Discord DMs are flat with no thread root, so the thread-history-backfill amnesia fix cannot apply. Recorded in the add-discord-proactive-post OpenSpec change and tracked in netclaw-dev#1025. Closes netclaw-dev#953
PostNewThreadAsync now forwards the CancellationToken to the underlying Discord.Net SendMessageAsync/CreateThreadAsync calls. The duplicated FindExistingThreadAsync helper is extracted into DiscordThreadHelpers and shared by the outbound and reply clients. SendDiscordMessageTool resolves the default channel id once instead of twice.
Discord reminders should not guess whether a snowflake is a user or a channel, and posted root messages should not look retry-safe when thread creation fails. Mark proactive threads as already created so later title outputs still rename the thread.
Aaronontheweb
commented
May 18, 2026
| // Channel-specific LLM tool: registered as an INetclawTool singleton. | ||
| // The gateway actor ref is resolved lazily via DiscordChannel since it | ||
| // is not available until StartAsync completes. | ||
| services.AddSingleton<SendDiscordMessageTool>(sp => |
Collaborator
Author
There was a problem hiding this comment.
dynamic tool registration - only register the discord tool when a discord channel is active.
| _rootMessageId = null; | ||
|
|
||
| _log.Info("Initializing proactive thread pipeline for session {0}", message.SessionId.Value); | ||
| await EnsureInitializedAsync(); |
Collaborator
Author
There was a problem hiding this comment.
ensures that the full messaging pipeline gets initialized
This was referenced May 19, 2026
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.
Summary
Adds a
send_discord_messageproactive-post tool so the agent can start a new Discord conversation forDeliveryKind.Channelreminder delivery and cross-channel outreach, with Discord thread/session wiring at parity withsend_slack_message. Closes #953.send_discord_messagebuiltin tool: posts a message to an allowed Discord channel and creates a thread off it, so the posted message becomes the thread root.StartProactiveThread/ProactiveThreadAckprotocol routed gateway -> conversation -> session-binding actor, so user replies in the created thread route back to a live session.thread-history-backfillcontract: the bot's posted message is recovered as adopted context on the first authorized reply via the existing deferred-hydration path.channel:<id>or<#id>. DM, user, and bare-snowflake targets are rejected up front.SessionTitleOutputrenames still apply.user_id) proactive posting is still deferred and tracked in Add DM (user_id) proactive-post support to send_discord_message #1025.add-discord-proactive-postOpenSpec change and updates thenetclaw-operationssystem skill.Test plan
dotnet test src/Netclaw.Actors.Tests/Netclaw.Actors.Tests.csproj --filter "FullyQualifiedName~SendDiscordMessageToolTests|FullyQualifiedName~DiscordReminderTargetResolverTests|FullyQualifiedName~SetReminderToolTests|FullyQualifiedName~DiscordSessionBindingContractTests"dotnet slopwatch analyzepwsh "./scripts/Add-FileHeaders.ps1" -Verify./evals/run-evals.shnot run locally; requires a model endpoint