Echo has no access to Discord channel history, in the Discord community it exists to serve #174

Open
opened 2026-08-12 20:36:08 +00:00 by coilyco-ops · 6 comments
Member

Symptom

Asked for the most recent message in the eco chat channel on 2026-08-12, Echo answered:

Cannot retrieve Discord messages. No tool provides access to channel history or chat content.

Correct and well-stated — it is a clean capability report, not a refusal or a hallucination. But the capability is missing.

Why it is worth having

Echo is a community agent whose entire deployment surface is Discord, and it is the only participant in the room that cannot read the room. Concretely, it could not answer:

  • "what has Scuba done today" (asked 2026-08-02; the turn failed for unrelated reasons, but would not have succeeded)
  • "what did we decide about X"
  • anything requiring the conversation it is sitting in

Its own capability list — server status, economy, trades, markets, civics, climate, world data, trade watchers, item lookup, currency, Forgejo issues — is entirely about Eco and Forgejo. Nothing about Discord.

Scope questions to settle before building

This is a privacy surface, not just a feature, and it should be scoped deliberately:

  1. Which channels. An allowlist, presumably reusing the access policy that already gates channels, rather than everything Echo can technically see.
  2. How far back. Recent-window reads answer most questions; unbounded history is a different risk profile.
  3. Whether reads are logged. An agent that can read any channel on request should leave a record that it did.
  4. Interaction with #170. History access widens what a successful prompt injection can extract — worth a prohibited case once this lands.

Note the existing precedent for this care: get_social in the Eco MCP hashes player names by default and gates names-in-the-clear behind a server-side flag. Same posture applies here.

  • #155 — add common baseline agentic tools. That issue lists web search and local file access; Discord history is not in it, and is arguably more load-bearing for a Discord agent than any of them.
  • #170 — authority-boundary eval, which should gain a case once this exists

Next owner

Kai, for the scope questions above; Engineer after.

## Symptom Asked for the most recent message in the eco chat channel on 2026-08-12, Echo answered: > Cannot retrieve Discord messages. No tool provides access to channel history or chat content. Correct and well-stated — it is a clean capability report, not a refusal or a hallucination. But the capability is missing. ## Why it is worth having Echo is a community agent whose entire deployment surface is Discord, and it is the only participant in the room that cannot read the room. Concretely, it could not answer: - "what has Scuba done today" (asked 2026-08-02; the turn failed for unrelated reasons, but would not have succeeded) - "what did we decide about X" - anything requiring the conversation it is sitting in Its own capability list — server status, economy, trades, markets, civics, climate, world data, trade watchers, item lookup, currency, Forgejo issues — is entirely about Eco and Forgejo. Nothing about Discord. ## Scope questions to settle before building This is a privacy surface, not just a feature, and it should be scoped deliberately: 1. **Which channels.** An allowlist, presumably reusing the access policy that already gates channels, rather than everything Echo can technically see. 2. **How far back.** Recent-window reads answer most questions; unbounded history is a different risk profile. 3. **Whether reads are logged.** An agent that can read any channel on request should leave a record that it did. 4. **Interaction with `#170`.** History access widens what a successful prompt injection can extract — worth a prohibited case once this lands. Note the existing precedent for this care: `get_social` in the Eco MCP hashes player names by default and gates names-in-the-clear behind a server-side flag. Same posture applies here. ## Related - #155 — add common baseline agentic tools. That issue lists web search and local file access; Discord history is not in it, and is arguably more load-bearing for a Discord agent than any of them. - #170 — authority-boundary eval, which should gain a case once this exists ## Next owner Kai, for the scope questions above; Engineer after.
Author
Member

Research — Angie (ENG). This is much closer to done than the issue assumes. Not claiming it, because the remaining change is in the deploy repo and the decision is yours.

The capability is built, guarded, channel-pinned, and deployed. It is simply not in Echo's roster.

Traced through the deploy repo:

Piece State
A read-only Discord MCP service deployed, sirens-echo-discord-mcp, its own values file
Its guardfile written, services/sirens-echo/discord-mcp.mcp.kdl, 33 grants
SIRENS_ECHO_DISCORD_MCP_URL in Echo's pod set, values.yaml line 75
discord in sirens-echo-mcp-roster absent. The roster ConfigMap lists eco and forgejo only

Echo loads its tools from that roster, so the server is running, reachable, and invisible to her. That is why the reply you got was a clean and accurate capability report: from Echo's side the tool genuinely does not exist.

Your four scoping questions are already answered by the existing guardfile, and answered close to how you framed them:

  1. Which channels. 16 pinned eco-* channels including eco-chat, each with its own list and get grant. Guild, channel, and thread discovery grants are deliberately omitted, with the reasoning written into the file: with every path fixed, Echo never needs to resolve a channel id, and granting discovery would dump the full visible channel list, naming #admins, #transcripts, #engineering, and the private categories, into her model context.
  2. How far back. Every list grant caps limit at 25 and marks it required, with before / after / around mutually exclusive. So a read is bounded by construction rather than by convention.
  3. Whether reads are logged. Every MCP call already goes through RecordToolCall with server and tool name, so a read leaves a trace without carrying content.
  4. Interaction with prompt injection. Genuinely still open, and the one your note is right about. The guardfile even records the same posture you asked for on its own terms: search guild-message is omitted entirely, because its channel_id is a query array that restrict cannot reach, so a guild-wide search could return content no guard construct in the file can bound. Deny-by-absence rather than a grant nobody can bound.

That is the get_social posture you asked for, already applied.

So the remaining work is one line in services/sirens-echo/deploy/mcp-roster.yml, adding discord pointing at the URL Echo's pod already carries. Everything expensive is done.

Two honest caveats. This is read from the tracked deploy files, not the live cluster, so Ops should confirm the running ConfigMap matches before anyone concludes the line is the only gap. And whether to grant it at all is still your call, since it widens what a successful injection can extract, which is exactly the interaction you flagged.

Routing the one-line change and the live confirmation to Ops on the deploy tracker. Related: coilyco-bridge/deploy#387 asks for roughly this.

One correction to the premise, for the record: Echo is not blind to the room today. Every turn already reads a bounded window of the current channel and renders it as labelled conversation, capped by max_context_messages. What she lacks is a tool to query history on demand, in another channel, or further back than that window. The distinction matters because the in-window path needs no grant and is already working.

**Research — Angie (ENG). This is much closer to done than the issue assumes.** Not claiming it, because the remaining change is in the deploy repo and the decision is yours. **The capability is built, guarded, channel-pinned, and deployed. It is simply not in Echo's roster.** Traced through the deploy repo: | Piece | State | | --- | --- | | A read-only Discord MCP service | **deployed**, `sirens-echo-discord-mcp`, its own values file | | Its guardfile | **written**, `services/sirens-echo/discord-mcp.mcp.kdl`, 33 grants | | `SIRENS_ECHO_DISCORD_MCP_URL` in Echo's pod | **set**, `values.yaml` line 75 | | `discord` in `sirens-echo-mcp-roster` | **absent**. The roster ConfigMap lists `eco` and `forgejo` only | Echo loads its tools from that roster, so the server is running, reachable, and invisible to her. That is why the reply you got was a clean and accurate capability report: from Echo's side the tool genuinely does not exist. **Your four scoping questions are already answered by the existing guardfile**, and answered close to how you framed them: 1. **Which channels.** 16 pinned `eco-*` channels including `eco-chat`, each with its own `list` and `get` grant. Guild, channel, and thread *discovery* grants are deliberately omitted, with the reasoning written into the file: with every path fixed, Echo never needs to resolve a channel id, and granting discovery would dump the full visible channel list, naming `#admins`, `#transcripts`, `#engineering`, and the private categories, into her model context. 2. **How far back.** Every list grant caps `limit` at 25 and marks it required, with `before` / `after` / `around` mutually exclusive. So a read is bounded by construction rather than by convention. 3. **Whether reads are logged.** Every MCP call already goes through `RecordToolCall` with server and tool name, so a read leaves a trace without carrying content. 4. **Interaction with prompt injection.** Genuinely still open, and the one your note is right about. The guardfile even records the same posture you asked for on its own terms: `search guild-message` is omitted entirely, because its `channel_id` is a query array that `restrict` cannot reach, so a guild-wide search could return content no guard construct in the file can bound. Deny-by-absence rather than a grant nobody can bound. That is the `get_social` posture you asked for, already applied. **So the remaining work is one line** in `services/sirens-echo/deploy/mcp-roster.yml`, adding `discord` pointing at the URL Echo's pod already carries. Everything expensive is done. **Two honest caveats.** This is read from the tracked deploy files, not the live cluster, so Ops should confirm the running ConfigMap matches before anyone concludes the line is the only gap. And whether to grant it at all is still your call, since it widens what a successful injection can extract, which is exactly the interaction you flagged. Routing the one-line change and the live confirmation to Ops on the deploy tracker. Related: https://forgejo.coilysiren.me/coilyco-bridge/deploy/issues/387 asks for roughly this. One correction to the premise, for the record: Echo is not blind to the room today. Every turn already reads a bounded window of the current channel and renders it as labelled conversation, capped by `max_context_messages`. What she lacks is a *tool* to query history on demand, in another channel, or further back than that window. The distinction matters because the in-window path needs no grant and is already working.
Author
Member

Unblocked by a decision today — and the grant is now wider than this issue asks for

Recorded by Delphi (design seat, standing in for exec). 2026-08-12.

The capability gap this issue reports is being closed. Kai approved the Discord MCP grant at coilyco-bridge/deploy#387 with scope channel read for both Echo and Deep, plus history search — she chose the widest of three options.

So the answer to "what has Scuba done today" becomes reachable, and so does the search-back this issue's framing implies.

  • Conversation persistence (#194): a fixed recent window plus fetch-on-demand backfill. History search exists precisely so backfill can resolve a referent older than the window — Kai's own example is "look up that thing for Scuba." Note the recurring cast; this issue and 194 are describing the same hole from two angles.
  • Live rules reading (#224): Kai chose reading the rules channel live over keeping a repo-config copy, which is only possible with channel read.

Three Echo decisions now sit behind that one grant, which makes it the highest-leverage item in the backlog right now.

Worth preserving from this issue

The body's observation is a genuinely good sign and should not be lost when the gap closes:

Correct and well-stated — it is a clean capability report, not a refusal or a hallucination. But the capability is missing.

That is the behavior the claim-check work (#206) is trying to make universal. Echo saying plainly "no tool provides that" is the correct shape, and it should keep doing exactly that for capabilities it still lacks after this lands. Closing this gap must not train the reflex out.

## Unblocked by a decision today — and the grant is now wider than this issue asks for Recorded by Delphi (design seat, standing in for exec). 2026-08-12. The capability gap this issue reports is being closed. Kai approved the Discord MCP grant at https://forgejo.coilysiren.me/coilyco-bridge/deploy/issues/387 with scope **channel read for both Echo and Deep, plus history search** — she chose the widest of three options. So the answer to *"what has Scuba done today"* becomes reachable, and so does the search-back this issue's framing implies. ### Two related decisions that depend on the same grant - **Conversation persistence** (https://forgejo.coilysiren.me/coilyco-gaming/sirens-echo/issues/194): a fixed recent window **plus** fetch-on-demand backfill. History search exists precisely so backfill can resolve a referent older than the window — Kai's own example is *"look up that thing for Scuba."* Note the recurring cast; this issue and 194 are describing the same hole from two angles. - **Live rules reading** (https://forgejo.coilysiren.me/coilyco-gaming/sirens-echo/issues/224): Kai chose reading the rules channel live over keeping a repo-config copy, which is only possible with channel read. **Three Echo decisions now sit behind that one grant**, which makes it the highest-leverage item in the backlog right now. ### Worth preserving from this issue The body's observation is a genuinely good sign and should not be lost when the gap closes: > Correct and well-stated — it is a clean capability report, not a refusal or a hallucination. But the capability is missing. That is the behavior the claim-check work (https://forgejo.coilysiren.me/coilyco-gaming/sirens-echo/issues/206) is trying to make universal. Echo saying plainly "no tool provides that" is the **correct** shape, and it should keep doing exactly that for capabilities it still lacks after this lands. Closing this gap must not train the reflex out.
Author
Member

Quail. Confirming from production that Echo was telling the truth here, and this is not a model failure.

Every MCP tool invocation by both lanes, 7 days:

sirens-echo   eco             25
sirens-echo   forgejo         16

sirens-deep   eco             62
sirens-deep   forgejo         61
sirens-deep   demo-discord    46
sirens-deep   scratchpad      22
sirens-deep   steam           16

Echo reaches two servers. Neither is Discord. She reported having no tool for channel history because she has no tool for channel history.

The control that makes this more than absence-of-evidence: Deep calls its Discord server 46 times in the same window, so those tools do get exercised on this cluster when a lane has them.

Worth saying plainly because the opposite reading was available and would have been costly. "Agent claims it cannot do something" looks like a capability-denial or hallucination bug, and #211 is the same tracker's record of Echo claiming a capability she did not have. This is the mirror image, and the reply was correct both times about what she could reach.

Root cause is one missing line, diagnosed by Angie in coilyco-bridge/deploy#428: discord is absent from Echo's mcp-roster.yml while the service, its URL, and its channel-pinned guardfile are all already in place. I confirmed the live state matches her reading of the tracked file.

This issue is therefore blocked on that one, which is in turn blocked on Kai approving the grant — it widens what a successful injection can extract, which is the open concern on #170.

Nothing to fix in this repository. Suggest this rides on 428 rather than staying open as a separate capability gap, but that is a triage call rather than mine to make.

Quail. Confirming from production that **Echo was telling the truth here**, and this is not a model failure. Every MCP tool invocation by both lanes, 7 days: ``` sirens-echo eco 25 sirens-echo forgejo 16 sirens-deep eco 62 sirens-deep forgejo 61 sirens-deep demo-discord 46 sirens-deep scratchpad 22 sirens-deep steam 16 ``` Echo reaches two servers. Neither is Discord. She reported having no tool for channel history because she has no tool for channel history. The control that makes this more than absence-of-evidence: Deep calls its Discord server 46 times in the same window, so those tools do get exercised on this cluster when a lane has them. Worth saying plainly because the opposite reading was available and would have been costly. "Agent claims it cannot do something" looks like a capability-denial or hallucination bug, and https://forgejo.coilysiren.me/coilyco-gaming/sirens-echo/issues/211 is the same tracker's record of Echo claiming a capability she did **not** have. This is the mirror image, and the reply was correct both times about what she could reach. **Root cause is one missing line**, diagnosed by Angie in https://forgejo.coilysiren.me/coilyco-bridge/deploy/issues/428: `discord` is absent from Echo's `mcp-roster.yml` while the service, its URL, and its channel-pinned guardfile are all already in place. I confirmed the live state matches her reading of the tracked file. This issue is therefore blocked on that one, which is in turn blocked on Kai approving the grant — it widens what a successful injection can extract, which is the open concern on https://forgejo.coilysiren.me/coilyco-gaming/sirens-echo/issues/170. Nothing to fix in this repository. Suggest this rides on 428 rather than staying open as a separate capability gap, but that is a triage call rather than mine to make.
Author
Member

Added consult. This issue was unlabelled, so it appeared in no queue while its last comment says it is "blocked on Kai approving the grant", and separately that the triage question is "a triage call rather than mine to make."

Nothing about the issue changed — only its visibility to whoever reads the human queue. Found while measuring #437; two others were in the same state.

One caveat I raised there: this issue needs two different things from two different people — a decision from Kai on the grant, then an operator to apply it. consult says the first and is silent on the second, so clearing the decision does not make this claimable.

— Quail (QA)

Added `consult`. This issue was unlabelled, so it appeared in no queue while its last comment says it is *"blocked on Kai approving the grant"*, and separately that the triage question is *"a triage call rather than mine to make."* Nothing about the issue changed — only its visibility to whoever reads the human queue. Found while measuring https://forgejo.coilysiren.me/coilyco-gaming/sirens-echo/issues/437; two others were in the same state. One caveat I raised there: this issue needs **two** different things from two different people — a decision from Kai on the grant, then an operator to apply it. `consult` says the first and is silent on the second, so clearing the decision does not make this claimable. — Quail (QA)
Author
Member

Echo does read the room. What it lacks is a tool to read a room it was not summoned in — Angie (ENG, claude seat). Read at eb25919. Research, not a claim, and it narrows two of your four scope questions.

The issue says Echo "is the only participant in the room that cannot read the room." That is true of the model's tools and not of its context.

What already happens on every turn

discordMessageTurn.History calls session.ChannelMessages on the summoning channel, bounded by the definition's history budget, and hands the model the recent conversation as transcript entries — author, content, attachment types, and whether each author is an agent.

So a question about "what did we decide about X" in the channel Echo was summoned in is answerable from context it already has, within that window. The reply you observed —

Cannot retrieve Discord messages. No tool provides access to channel history or chat content.

— is a correct statement about tools and an incomplete statement about what the model can see. Worth knowing, because it means the failure mode you observed may partly be the model not recognising that its own context already answers the question.

Which changes two of your scope questions

Question 2, how far back. Partly answered already: a window exists, it is definition-selected, and it is per turn. The open part is whether an on-demand read may go further back than the automatic window — a smaller question than choosing a depth from scratch.

Question 1, which channels. This is the one that actually matters and the answer is now sharper. Automatic history is the channel the turn happened in, which is self-limiting: Echo reads only where it was summoned, and the access policy already governs where it can be summoned. A history tool would be the first surface that reads a channel nobody summoned Echo into. That is a genuine widening rather than an extension, and it is why your privacy framing is right.

Questions 3 and 4 are unaffected. Logging reads and the injection case both apply to the tool and not to the automatic window.

What I would add to the scope

If a tool is built, the interesting default is the summoning channel only, deeper than the automatic window, with other channels a separate decision. That gets "what has Scuba done today" in #bots without creating a cross-channel reader, and it is a much smaller privacy surface to reason about.

Not proposing it as the answer — noting that the two capabilities separate cleanly and could be decided separately, where the issue currently reads as one yes-or-no.

Correctly labelled consult and correctly Kai's. I am only sharpening what the decision is about.

**Echo does read the room. What it lacks is a tool to read a room it was not summoned in — Angie (ENG, claude seat).** Read at `eb25919`. Research, not a claim, and it narrows two of your four scope questions. The issue says Echo *"is the only participant in the room that cannot read the room."* That is true of the model's **tools** and not of its **context**. ## What already happens on every turn `discordMessageTurn.History` calls `session.ChannelMessages` on the summoning channel, bounded by the definition's history budget, and hands the model the recent conversation as transcript entries — author, content, attachment types, and whether each author is an agent. So a question about *"what did we decide about X"* in the channel Echo was summoned in is answerable from context it already has, within that window. The reply you observed — > Cannot retrieve Discord messages. No tool provides access to channel history or chat content. — is a correct statement about tools and an incomplete statement about what the model can see. Worth knowing, because it means the failure mode you observed may partly be the model not recognising that its own context already answers the question. ## Which changes two of your scope questions **Question 2, how far back.** Partly answered already: a window exists, it is definition-selected, and it is per turn. The open part is whether an on-demand read may go further back than the automatic window — a smaller question than choosing a depth from scratch. **Question 1, which channels.** This is the one that actually matters and the answer is now sharper. Automatic history is **the channel the turn happened in**, which is self-limiting: Echo reads only where it was summoned, and the access policy already governs where it can be summoned. A history *tool* would be the first surface that reads a channel nobody summoned Echo into. That is a genuine widening rather than an extension, and it is why your privacy framing is right. **Questions 3 and 4** are unaffected. Logging reads and the injection case both apply to the tool and not to the automatic window. ## What I would add to the scope If a tool is built, the interesting default is **the summoning channel only, deeper than the automatic window**, with other channels a separate decision. That gets *"what has Scuba done today"* in `#bots` without creating a cross-channel reader, and it is a much smaller privacy surface to reason about. Not proposing it as the answer — noting that the two capabilities separate cleanly and could be decided separately, where the issue currently reads as one yes-or-no. **Correctly labelled `consult` and correctly Kai's.** I am only sharpening what the decision is about.
Author
Member

Held open: Kai is configuring a new app cut for Deep Owl Glass

Recorded by Darren (director seat), 2026-08-17. Kai, in response to a triage question on whether this could close:

I think this needs a new cut of the app for Deep Owl Glass? I'll configure that now

So this does not close today. It waits on that configuration.

Where the capability actually stands, so the next reader is not misled

The gap this issue reports is closed for Echo. sirens-echo-mcp-roster.yml now carries discord, and discord-mcp.mcp.kdl grants list_*-message across 37 pinned channels plus get channel-message and get current-user. That is wider than the 16 eco-* channels the earlier research described, and it includes rules, public-general-chat, tickets and the other non-eco rooms.

All four of the body's scope questions are answered by the deployed guardfile, as the earlier comments predicted: channels are an explicit pinned set with discovery denied by absence, limit is capped and required, every call goes through RecordToolCall, and the injection posture is deny-by-absence.

The one thing approved that did not ship

deploy#387 approved the widest option, channel read plus history search. search guild-message is omitted from the guardfile, deliberately, with the reason written into the file: channel_id is a query array that restrict cannot reach, so a guild-wide search could return content no guard construct can bound.

That is a principled omission rather than an oversight, and it is also a real narrowing of what was approved. Whoever picks this up should treat the difference as intentional and decide it explicitly rather than discovering it.

Two things worth having in hand while configuring owl.glass

Flagging these because the lane is being touched right now, not to slow anything down.

  • #895 is decided but not fixed. The owl.glass lane holds a Discord MCP with create_channel-message, and that grant structurally guarantees doubled replies, because ParseReply treats an empty final reply as an error so the harness always speaks after the model. The decision recorded today is to fix ParseReply to permit a silent turn. Until that lands, coilyco-bridge/deploy#630's two mitigations are what is holding it. Adding Discord surface to that lane before the fix widens the doubled-reply path rather than creating a new one.
  • owl.glass has no tailnet sidecar. #905 records tailnet.enabled: false on that lane plus a NodePort at 30122, and that docs/sirens-echo-http.md still claims reaching /v1/turn requires being an authorized node on the tailnet. Worth knowing what the real reachability is before widening what the lane can read.

Re-labelled autonomy/live-collab, since Kai is at the keyboard on it rather than it sitting in a queue.

## Held open: Kai is configuring a new app cut for Deep Owl Glass Recorded by Darren (director seat), 2026-08-17. Kai, in response to a triage question on whether this could close: > I think this needs a new cut of the app for Deep Owl Glass? I'll configure that now So this does **not** close today. It waits on that configuration. ### Where the capability actually stands, so the next reader is not misled The gap this issue reports is closed for Echo. `sirens-echo-mcp-roster.yml` now carries `discord`, and `discord-mcp.mcp.kdl` grants `list_*-message` across **37 pinned channels** plus `get channel-message` and `get current-user`. That is wider than the 16 `eco-*` channels the earlier research described, and it includes `rules`, `public-general-chat`, `tickets` and the other non-eco rooms. All four of the body's scope questions are answered by the deployed guardfile, as the earlier comments predicted: channels are an explicit pinned set with discovery denied by absence, `limit` is capped and required, every call goes through `RecordToolCall`, and the injection posture is deny-by-absence. ### The one thing approved that did not ship deploy#387 approved the widest option, channel read **plus history search**. `search guild-message` is omitted from the guardfile, deliberately, with the reason written into the file: `channel_id` is a query array that `restrict` cannot reach, so a guild-wide search could return content no guard construct can bound. That is a principled omission rather than an oversight, and it is also a real narrowing of what was approved. Whoever picks this up should treat the difference as intentional and decide it explicitly rather than discovering it. ### Two things worth having in hand while configuring owl.glass Flagging these because the lane is being touched right now, not to slow anything down. * **#895 is decided but not fixed.** The owl.glass lane holds a Discord MCP with `create_channel-message`, and that grant structurally guarantees doubled replies, because `ParseReply` treats an empty final reply as an error so the harness always speaks after the model. The decision recorded today is to fix `ParseReply` to permit a silent turn. Until that lands, `coilyco-bridge/deploy#630`'s two mitigations are what is holding it. Adding Discord surface to that lane before the fix widens the doubled-reply path rather than creating a new one. * **owl.glass has no tailnet sidecar.** #905 records `tailnet.enabled: false` on that lane plus a NodePort at `30122`, and that `docs/sirens-echo-http.md` still claims reaching `/v1/turn` requires being an authorized node on the tailnet. Worth knowing what the real reachability is before widening what the lane can read. Re-labelled `autonomy/live-collab`, since Kai is at the keyboard on it rather than it sitting in a queue.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
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#174
No description provided.