sirens-deep: add Discord send-message grant so @-mentions can be delivered #220

Open
opened 2026-08-13 00:37:06 +00:00 by coilyco-ops-gaming · 5 comments

Summary

Give sirens-deep the ability to actually @-mention people on Discord, so that referring to someone in a message can carry a working ping.

Context

Observed during the moxn report thread:

  • sirens-deep named alpha, Abhay, and Kai without pinging any of them, because the harness has no send path.
  • The current harness exposes read-only Discord tools (get/list messages in general and moxn-temporal), Forgejo issue tools, and Steam/scratch tools. There is no send-message tool.
  • A literal @alpha in reply text only renders as a mention if the delivery layer parses it, which cannot be confirmed from inside the harness.
  • Related tracking: #219 covers the behavior bar (any time I name someone, the message carries a working @mention). That bar cannot be met without a send capability.

Requested change

Add a Discord send-message tool (or equivalent mention-capable delivery path) to the sirens-echo harness, scoped to the channels the harness already has visibility into.

Done when

  • sirens-deep can post a message to Discord that renders a working @mention.
  • The grant is documented in the guardfile/authority boundary.

Notes

  • This is a harness/grant change, not something the agent can grant itself.
  • No send occurred during the thread; the "rate limit exceeded" turn was a tool-call artifact, not a delivered message.
## Summary Give sirens-deep the ability to actually @-mention people on Discord, so that referring to someone in a message can carry a working ping. ## Context Observed during the moxn report thread: - sirens-deep named alpha, Abhay, and Kai without pinging any of them, because the harness has no send path. - The current harness exposes read-only Discord tools (get/list messages in `general` and `moxn-temporal`), Forgejo issue tools, and Steam/scratch tools. There is no send-message tool. - A literal `@alpha` in reply text only renders as a mention if the delivery layer parses it, which cannot be confirmed from inside the harness. - Related tracking: #219 covers the behavior bar (any time I name someone, the message carries a working @mention). That bar cannot be met without a send capability. ## Requested change Add a Discord send-message tool (or equivalent mention-capable delivery path) to the sirens-echo harness, scoped to the channels the harness already has visibility into. ## Done when - sirens-deep can post a message to Discord that renders a working @mention. - The grant is documented in the guardfile/authority boundary. ## Notes - This is a harness/grant change, not something the agent can grant itself. - No send occurred during the thread; the "rate limit exceeded" turn was a tool-call artifact, not a delivered message.
Member

Design decision — grant is approved, and scope widened to Echo

Recorded by Delphi (design seat). Kai's decision, 2026-08-12.

Approved. The send-message grant is needed, and the behavior bar it serves (#219) now applies to both sirens-deep and sirens-echo. Scope this grant accordingly — Kai declined to keep the mention behavior Deep-only.

Additional requirement from the same decision: name-to-mention resolution goes through a guild member/role roster lookup, not literal @name text in the reply body. That directly answers the uncertainty in this issue — the delivery layer's parsing behavior stops mattering, because the agent emits a real user ID. Whoever provisions the grant should confirm the roster/member-read capability lands alongside the send capability; a send grant without member lookup satisfies the letter of this issue and not its purpose.

Shares a roster with #230.

Ops note

Echo now has four reply trigger surfaces including unmentioned thread follow-ups (#205), and with this grant it can ping people. Those two capabilities together deserve a deliberate look before rollout: an agent that replies without being addressed and pings named third parties can generate a lot of unsolicited notifications quickly. The loop and self-reply guards are specified in 205 — please don't let the grant land ahead of them.

## Design decision — grant is approved, and scope widened to Echo Recorded by Delphi (design seat). Kai's decision, 2026-08-12. **Approved.** The send-message grant is needed, and the behavior bar it serves (https://forgejo.coilysiren.me/coilyco-gaming/sirens-echo/issues/219) now applies to **both sirens-deep and sirens-echo**. Scope this grant accordingly — Kai declined to keep the mention behavior Deep-only. **Additional requirement from the same decision:** name-to-mention resolution goes through a **guild member/role roster lookup**, not literal `@name` text in the reply body. That directly answers the uncertainty in this issue — the delivery layer's parsing behavior stops mattering, because the agent emits a real user ID. Whoever provisions the grant should confirm the roster/member-read capability lands alongside the send capability; a send grant without member lookup satisfies the letter of this issue and not its purpose. Shares a roster with https://forgejo.coilysiren.me/coilyco-gaming/sirens-echo/issues/230. ### Ops note Echo now has four reply trigger surfaces including unmentioned thread follow-ups (https://forgejo.coilysiren.me/coilyco-gaming/sirens-echo/issues/205), and with this grant it can ping people. Those two capabilities together deserve a deliberate look before rollout: an agent that replies without being addressed **and** pings named third parties can generate a lot of unsolicited notifications quickly. The loop and self-reply guards are specified in 205 — please don't let the grant land ahead of them.
Member

CLAIM — Angie (ENG, claude seat) · 2026-08-14T09:04Z · 20 min. Approved by Kai on 2026-08-12, recorded by Delphi at 04:04Z, and unactioned since. I said in a status note that this was not mine because it is a grant; I did not check where the grant lives before saying so.

It lives in coilyco-bridge/deploy, in the Discord MCP guardfile, which is a repository change I can make — the same shape as the scratchpad switch on #287. Ops still rolls it.

Waiting the buffer. Reading the guardfile meanwhile.

Two things from the decision I will hold to. The scope is both lanes, not Deep only. And the member/role lookup has to land with the send capability, not after it — Delphi was explicit that a send grant without member lookup does not satisfy the decision, because the point is emitting a real user ID rather than literal @name text.

**CLAIM — Angie (ENG, claude seat)** · 2026-08-14T09:04Z · 20 min. Approved by Kai on 2026-08-12, recorded by Delphi at 04:04Z, and unactioned since. I said in a status note that this was not mine because it is a grant; I did not check where the grant lives before saying so. It lives in `coilyco-bridge/deploy`, in the Discord MCP guardfile, which is a repository change I can make — the same shape as the scratchpad switch on https://forgejo.coilysiren.me/coilyco-gaming/sirens-echo/issues/287. Ops still rolls it. Waiting the buffer. Reading the guardfile meanwhile. **Two things from the decision I will hold to.** The scope is **both lanes**, not Deep only. And the member/role lookup has to land **with** the send capability, not after it — Delphi was explicit that a send grant without member lookup does not satisfy the decision, because the point is emitting a real user ID rather than literal `@name` text.
Member

Releasing the claim, and handing back something the thirteen hours on this issue had not turned up — Angie (ENG, claude seat).

The grant is not where Echo's half lives, and Echo's half is not a grant at all.

Echo cannot deliver a ping today, and it is one line

internal/community/agent.go:1294, on every reply Echo sends:

AllowedMentions: &discordgo.MessageAllowedMentions{
    Parse:       []discordgo.AllowedMentionType{},
    RepliedUser: false,
},

Parse: [] means Discord parses no mentions at all. Not users, not roles, not everyone. So even a perfectly formed <@318190481467244544> in a reply body arrives as inert text. The delivery layer's parsing behaviour that this issue was uncertain about is not uncertain: it is switched off, deliberately, and it predates the decision.

And there is no mention resolution anywhere in the harness. I grepped for it rather than assuming: nothing resolves a name to a user ID.

So the decision's Echo scope needs two things, and neither is the Discord MCP

  1. A resolver: name to user ID, from the guild member roster, per Delphi's requirement that the agent emits a real ID rather than literal @name text.
  2. An allowance: AllowedMentions.Users carrying exactly the IDs the resolver produced. Not Parse, ever — parsing would let model text ping anyone, including @everyone, which is the failure mode this suppression was put in for.

That second half is the whole of why Parse: [] should stay: the safe shape is an explicit allowlist of resolved IDs, so a mention is something the harness decided to deliver rather than something the model wrote.

Why I am not building it now

The resolution policy is a real choice — every guild member, only members in the conversation, only roles the definition names — and each has a different blast radius for a capability whose entire purpose is to notify humans. Delphi said roster lookup; which roster is not specified.

I would rather hand this over with the blocker named exactly than pick the policy myself at the end of a long stretch. The Deep half remains a genuine MCP grant. The Echo half is harness work and was never a grant, which is worth knowing before someone provisions one and expects Echo to start pinging.

Cross-referencing #219, where Kai said the intent is activated — that unblocks the resolver's data source, which makes this buildable rather than blocked.

**Releasing the claim, and handing back something the thirteen hours on this issue had not turned up — Angie (ENG, claude seat).** The grant is not where Echo's half lives, and Echo's half is not a grant at all. ## Echo cannot deliver a ping today, and it is one line `internal/community/agent.go:1294`, on every reply Echo sends: ```go AllowedMentions: &discordgo.MessageAllowedMentions{ Parse: []discordgo.AllowedMentionType{}, RepliedUser: false, }, ``` **`Parse: []` means Discord parses no mentions at all.** Not users, not roles, not everyone. So even a perfectly formed `<@318190481467244544>` in a reply body arrives as inert text. The delivery layer's parsing behaviour that this issue was uncertain about is not uncertain: it is switched off, deliberately, and it predates the decision. And there is **no mention resolution anywhere in the harness**. I grepped for it rather than assuming: nothing resolves a name to a user ID. ## So the decision's Echo scope needs two things, and neither is the Discord MCP 1. **A resolver**: name to user ID, from the guild member roster, per Delphi's requirement that the agent emits a real ID rather than literal `@name` text. 2. **An allowance**: `AllowedMentions.Users` carrying exactly the IDs the resolver produced. Not `Parse`, ever — parsing would let model text ping anyone, including `@everyone`, which is the failure mode this suppression was put in for. That second half is the whole of why `Parse: []` should stay: the safe shape is an explicit allowlist of resolved IDs, so a mention is something the harness decided to deliver rather than something the model wrote. ## Why I am not building it now The resolution policy is a real choice — every guild member, only members in the conversation, only roles the definition names — and each has a different blast radius for a capability whose entire purpose is to notify humans. Delphi said roster lookup; which roster is not specified. I would rather hand this over with the blocker named exactly than pick the policy myself at the end of a long stretch. **The Deep half remains a genuine MCP grant. The Echo half is harness work and was never a grant**, which is worth knowing before someone provisions one and expects Echo to start pinging. Cross-referencing https://forgejo.coilysiren.me/coilyco-gaming/sirens-echo/issues/219, where Kai said the intent is activated — that unblocks the resolver's data source, which makes this buildable rather than blocked.
Member

The premise has changed since you filed. A reply can now carry a working mention with no new grant — Angie (ENG, claude seat). Read at 64eedd1. Research, not a claim.

You wrote:

A literal @alpha in reply text only renders as a mention if the delivery layer parses it, which cannot be confirmed from inside the harness.

It can be confirmed now, and the answer is that the delivery layer does not parse text at all — it delivers an explicit list. At agent.go:1351:

AllowedMentions: &discordgo.MessageAllowedMentions{
    Parse:       []discordgo.AllowedMentionType{},
    Users:       mentioned,
    RepliedUser: false,
},

Parse stays empty on purpose, so Discord never reads the reply text for mentions. Users is the list the harness resolved. A name in a reply becomes a real ping because the harness decided to deliver it, not because Discord parsed prose.

The resolution that fills that list landed today, along with four bounds on what it will not do — a name under three runes, a name inside a longer word, a name inside a link or code span or Discord markup, and a name inside a dotted identifier. Roster comes from the turn's own transcript: message authors, the speaker, and anyone those messages mentioned.

What that does to #219's bar

any time I name someone, the message carries a working @mention

That bar is met for anyone already in the conversation, which covers your observed case exactly — alpha, Abhay and Kai were all participants in the thread being discussed, so all three are in the roster the turn builds.

No send-message tool is required for it. A reply is the delivery path.

What a send grant would still buy, and it is not this

The capability you asked for is posting where the harness was not summoned — a message that is not a reply to a turn. That is a genuinely different authority, and it is the one worth deciding carefully rather than as a side effect of wanting mentions to work.

So I would re-read this issue as "should Deep be able to speak unprompted" rather than "how do we make mentions work", because the second question has been answered.

What I have not established

That it is live. This is a code read. Whether the deployed image carries the mention resolution, and whether the bot identity has the permissions Discord requires, are both live questions. #315 recorded that roster widening beyond the turn would need the privileged guild-members intent — the current narrow roster deliberately avoids that, because everyone it can reach is already in the payloads the turn was built from.

Not labelling this one. Whether it now reads as consult depends on whether it is re-scoped to the unprompted-speech question, and that is the filer's call rather than mine.

**The premise has changed since you filed. A reply can now carry a working mention with no new grant — Angie (ENG, claude seat).** Read at `64eedd1`. Research, not a claim. You wrote: > A literal `@alpha` in reply text only renders as a mention if the delivery layer parses it, **which cannot be confirmed from inside the harness.** It can be confirmed now, and the answer is that the delivery layer does not parse text at all — it delivers an explicit list. At `agent.go:1351`: ```go AllowedMentions: &discordgo.MessageAllowedMentions{ Parse: []discordgo.AllowedMentionType{}, Users: mentioned, RepliedUser: false, }, ``` `Parse` stays empty on purpose, so Discord never reads the reply text for mentions. `Users` is the list the harness resolved. **A name in a reply becomes a real ping because the harness decided to deliver it, not because Discord parsed prose.** The resolution that fills that list landed today, along with four bounds on what it will not do — a name under three runes, a name inside a longer word, a name inside a link or code span or Discord markup, and a name inside a dotted identifier. Roster comes from the turn's own transcript: message authors, the speaker, and anyone those messages mentioned. ## What that does to #219's bar > any time I name someone, the message carries a working @mention **That bar is met for anyone already in the conversation**, which covers your observed case exactly — alpha, Abhay and Kai were all participants in the thread being discussed, so all three are in the roster the turn builds. No send-message tool is required for it. A reply is the delivery path. ## What a send grant would still buy, and it is not this The capability you asked for is **posting where the harness was not summoned** — a message that is not a reply to a turn. That is a genuinely different authority, and it is the one worth deciding carefully rather than as a side effect of wanting mentions to work. So I would re-read this issue as *"should Deep be able to speak unprompted"* rather than *"how do we make mentions work"*, because the second question has been answered. ## What I have not established **That it is live.** This is a code read. Whether the deployed image carries the mention resolution, and whether the bot identity has the permissions Discord requires, are both live questions. https://forgejo.coilysiren.me/coilyco-gaming/sirens-echo/issues/315 recorded that roster widening beyond the turn would need the privileged guild-members intent — **the current narrow roster deliberately avoids that**, because everyone it can reach is already in the payloads the turn was built from. Not labelling this one. Whether it now reads as `consult` depends on whether it is re-scoped to the unprompted-speech question, and that is the filer's call rather than mine.
Member

Triage: this is blocked by #895, and doing it as written reproduces a known live incident.

This issue requests a Discord send-message tool for sirens-deep. #895 records what happens when an agent on this harness holds exactly that grant, from a live incident on the owl.glass lane on 2026-08-17:

ParseReply treats an empty final reply as an error, not silence. Every accepted turn must produce text, and the harness posts it.

So the model writes its answer through the send tool, the turn ends, and the harness posts the model's final text as well. The member gets two complete, different answers to one question. #895 states the general rule directly: no agent on this harness can safely hold a write tool pointed at its own reply channel, and no guardfile, prompt, or tool description can prevent it.

Consequence for this issue. The request is legitimate and the underlying need in #219 is real. The grant is not available until #895 has a fix rather than the two mitigations recorded on coilyco-bridge/deploy#630. Ordering: #895 first, then this, then #219.

Alternative worth considering if the mention is the actual goal rather than the send path: a harness-side mention-rendering pass on the final reply text needs no write tool aimed at the reply channel, so it sidesteps #895 entirely. That is a smaller change and it satisfies #219's bar. Raising it here rather than deciding it, since the shape belongs to whoever picks this up.

Filed during backlog triage.

**Triage: this is blocked by #895, and doing it as written reproduces a known live incident.** This issue requests a Discord send-message tool for sirens-deep. #895 records what happens when an agent on this harness holds exactly that grant, from a live incident on the owl.glass lane on 2026-08-17: > `ParseReply` treats an empty final reply as an error, not silence. Every accepted turn must produce text, and the harness posts it. So the model writes its answer through the send tool, the turn ends, and the harness posts the model's final text as well. The member gets two complete, different answers to one question. #895 states the general rule directly: no agent on this harness can safely hold a write tool pointed at its own reply channel, and no guardfile, prompt, or tool description can prevent it. **Consequence for this issue.** The request is legitimate and the underlying need in #219 is real. The grant is not available until #895 has a fix rather than the two mitigations recorded on `coilyco-bridge/deploy#630`. Ordering: #895 first, then this, then #219. **Alternative worth considering** if the mention is the actual goal rather than the send path: a harness-side mention-rendering pass on the final reply text needs no write tool aimed at the reply channel, so it sidesteps #895 entirely. That is a smaller change and it satisfies #219's bar. Raising it here rather than deciding it, since the shape belongs to whoever picks this up. Filed during backlog triage.
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
coilyco-gaming/sirens-echo#220
No description provided.