<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: ace</title>
    <description>The latest articles on DEV Community by ace (@acefy).</description>
    <link>https://dev.to/acefy</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4230973%2Fdf7bdf4c-0989-4cc8-9b5c-698d53dbc19f.jpg</url>
      <title>DEV Community: ace</title>
      <link>https://dev.to/acefy</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/acefy"/>
    <language>en</language>
    <item>
      <title>Verity: three AI agents look into a Discord report, a human makes the call</title>
      <dc:creator>ace</dc:creator>
      <pubDate>Fri, 06 Nov 2026 19:20:40 +0000</pubDate>
      <link>https://dev.to/acefy/verity-three-ai-agents-look-into-a-discord-report-a-human-makes-the-call-16de</link>
      <guid>https://dev.to/acefy/verity-three-ai-agents-look-into-a-discord-report-a-human-makes-the-call-16de</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Verity is an open-source Discord moderation assistant. When a member reports a message, three agents work on it one after another in a private Switch room: one triages it, one investigates the evidence, and one recommends an action. A human moderator reviews the findings and decides. The agents never get ban permissions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you have moderated a Discord with thousands of active members, you know the routine. The queue fills up with petty arguments, screenshots with no context and long complaints. Moderators scroll back through hundreds of messages to find out who started it, whether this person has done it before, and which rule applies. By the time someone acts, they are tired and the member who reported it has waited a long time.&lt;/p&gt;

&lt;p&gt;I built Verity for the &lt;strong&gt;Flip the Switch&lt;/strong&gt; hackathon to do that reading, so the moderator can spend their time on the decision.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why not just use an auto-mod bot?
&lt;/h2&gt;

&lt;p&gt;Most automated moderation fails in one of two ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Too strict.&lt;/strong&gt; It matches a word, misses the joke, and bans a real member.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Too lazy.&lt;/strong&gt; It dumps raw logs on the moderator and calls that help.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I wanted something in between, with three steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Triage:&lt;/strong&gt; which rule was broken, how urgent is it, and what information is missing?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Investigation:&lt;/strong&gt; what actually happened, based on timestamps, the messages before the incident, and the uploaded proof?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Advice:&lt;/strong&gt; given the server's rules and the member's history, which penalty fits?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The rule I kept throughout: &lt;strong&gt;the AI summarizes, cites facts and advises. A human clicks &lt;code&gt;Take Action&lt;/code&gt; or dismisses the report.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Right-click a message -&amp;gt; Apps -&amp;gt; Report Message
        |
        v
Report modal (message ID, author, channel and server are bound at intake)
        |
        v
Verity bot (Discord.js v14)
        |  POST /rooms -&amp;gt; one private Switch room per report
        |  + a paired private investigation thread in Discord
        v
+------------------------+
| 1. report-triage       |
| 2. report-investigator |   each one edits the same progress
| 3. moderation-advisor  |   message in the thread as it finishes
+------------------------+
        |
        v
Moderator: ask the agents a follow-up, or open the Take Action modal
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A member right-clicks a message and picks &lt;strong&gt;Apps -&amp;gt; Report Message&lt;/strong&gt;. Verity binds the message ID, the author, the channel and the server to one intake session, then opens the report modal. After the report is submitted, the bot creates a new Switch room for it, plus a private thread for the moderators, and the three agents work through the case in order.&lt;/p&gt;




&lt;h2&gt;
  
  
  How Switch fits in
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/sandbox-quantum/switch" rel="noopener noreferrer"&gt;Switch&lt;/a&gt; is open-source infrastructure that puts AI agents into a shared room, the way a person joins a channel. I use it to give every report its own room and to let three agents read each other's work.&lt;/p&gt;

&lt;h3&gt;
  
  
  One room per report
&lt;/h3&gt;

&lt;p&gt;Every report gets its own room, created through the Switch API (&lt;code&gt;POST /rooms&lt;/code&gt;) with a name like &lt;code&gt;report-v-001&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;room&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;switchClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createRoom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s2"&gt;`Investigation-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;reportId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;`Investigation room for Discord incident &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;reportId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each case lives in its own room, so evidence from one report never shows up in another case's context. The room also holds the outcome once a moderator acts, so there is one place to look when you want to know why a decision was made.&lt;/p&gt;

&lt;h3&gt;
  
  
  Three agents instead of one
&lt;/h3&gt;

&lt;p&gt;I run three OpenCode agents through the Switch Gateway:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Agent&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;report-triage&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Identifies the rule violation, sets the urgency and flags missing information.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;report-investigator&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Checks the incident itself. It decodes Discord snowflake IDs to get exact timestamps, reads the messages sent just before the incident, and checks the proof files.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;moderation-advisor&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Compares the findings with the server rules, looks up the member's past violations in SQLite, and recommends dismiss, warn, timeout, kick or ban.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The advisor is the only agent that sees the member's history, so the investigator can stay focused on the evidence for this one incident.&lt;/p&gt;

&lt;h3&gt;
  
  
  Updating Discord in place
&lt;/h3&gt;

&lt;p&gt;Verity posts one progress container in the thread (for example "report-triage is working...") and edits that same message into the finished report as each agent completes. The thread doesn't fill up with separate bot messages. When a moderator takes action, the final container turns green.&lt;/p&gt;

&lt;h3&gt;
  
  
  Asking the agents questions
&lt;/h3&gt;

&lt;p&gt;If a moderator has a doubt, they type in the thread, for example "@investigator did anyone provoke them first?". Verity forwards the question into the Switch room, the agent answers there, and the answer is posted back into the thread.&lt;/p&gt;

&lt;h3&gt;
  
  
  Taking action
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;Take Action&lt;/code&gt; opens a modal where the moderator picks the action, adjusts the duration and edits the reason. Then Verity sends the member a DM, applies the action through the Discord API, turns the case container green, writes to the audit channel and posts the outcome back into the Switch room.&lt;/p&gt;




&lt;h2&gt;
  
  
  Setting up Switch on Windows
&lt;/h2&gt;

&lt;p&gt;The setup took me longer than the code. If you are on Windows, these are the problems I hit and what fixed them.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Problem&lt;/th&gt;
&lt;th&gt;What fixed it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Docker couldn't pull Switch's images: &lt;code&gt;no such host&lt;/code&gt; for &lt;code&gt;ghcr.io&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;It was a DNS failure. Fixing DNS and running &lt;code&gt;docker pull&lt;/code&gt; again worked, and layers that had already downloaded were kept.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Docker Desktop crashed with a WSL error about a busy disk&lt;/td&gt;
&lt;td&gt;Quit Docker, run &lt;code&gt;wsl --shutdown&lt;/code&gt;, open Docker again and wait for "Engine running". Nothing was lost.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Every OpenCode request failed&lt;/td&gt;
&lt;td&gt;OpenCode had picked an image model as its default. Setting a Gemini Flash text model in &lt;code&gt;opencode.jsonc&lt;/code&gt; fixed it.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Quota exceeded&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The free Gemini key allowed 20 requests a day on the model I used, and my usage page showed 22 of 20. Agent tools use many requests per question, so a free key runs out fast.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Antigravity wouldn't connect&lt;/td&gt;
&lt;td&gt;Switch Console 0.40.1 marks Antigravity as unsupported on Windows, so I used OpenCode.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Humans couldn't read a room&lt;/td&gt;
&lt;td&gt;A room created with no chat app attached is "agent-only", and people can't see it. Create rooms with a bridge.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The OpenCode setting, in &lt;code&gt;~/.config/opencode/opencode.jsonc&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"$schema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://opencode.ai/config.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"google/gemini-2.5-flash"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Problems I hit while building
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Agents calling Switch tools that fail
&lt;/h3&gt;

&lt;p&gt;At first my OpenCode agents tried to call Switch's built-in tools, such as &lt;code&gt;switch_connect_to_room&lt;/code&gt; and &lt;code&gt;switch_post_message&lt;/code&gt;. Those calls failed with &lt;code&gt;400 Bad Request&lt;/code&gt; because the gateway ran them without any room context.&lt;/p&gt;

&lt;p&gt;I stopped fighting it. The agent prompts now start with an override:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SWITCH SYSTEM OVERRIDE:
- Do NOT call switch_read_context, switch_connect_to_room, or switch_post_message. They fail with 400.
- Simply output your complete assessment directly as text in your response.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The orchestrator reads each agent's finished answer from the room's activity stream (&lt;code&gt;/chats/{roomId}/activity&lt;/code&gt;). That shows up in Discord about 15 to 20 seconds after an agent finishes.&lt;/p&gt;

&lt;p&gt;Sometimes an agent's answer arrives as an attachment instead of message text. For that case I added a fallback that fetches and decodes the file from &lt;code&gt;/chats/{roomId}/media&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Discord modal limits
&lt;/h3&gt;

&lt;p&gt;Discord modals restrict which component types you can use, and the limits depend on the API version. My &lt;code&gt;/settings&lt;/code&gt; and &lt;code&gt;Take Action&lt;/code&gt; modals kept breaking when a field wasn't where the handler expected it.&lt;/p&gt;

&lt;p&gt;I wrote a field extractor that tries &lt;code&gt;interaction.fields.getField()&lt;/code&gt; first, falls back to the underlying &lt;code&gt;fields&lt;/code&gt; collection, and as a last resort reads the raw component payload. The modals have been reliable since.&lt;/p&gt;

&lt;h3&gt;
  
  
  Proof without storing media
&lt;/h3&gt;

&lt;p&gt;I wanted reports to accept screenshots without turning the bot into a media server. So Verity keeps only the Discord attachment URLs and checks the image files in memory, to make sure they are real images. If a server turns on &lt;strong&gt;Require Evidence&lt;/strong&gt;, the report modal enforces it before the report is accepted.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Several small agents beat one big prompt.&lt;/strong&gt; When I asked one model to triage, read the logs and recommend a penalty in a single pass, it overweighted the last messages and gave vague summaries. Splitting the work into triage, investigation and advice gave clearer, more specific results.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One room per case keeps evidence apart.&lt;/strong&gt; It also makes a past decision easy to review.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep a human in charge.&lt;/strong&gt; Members accept a moderation decision more readily when a real moderator reviewed it. The AI is good at sifting through noise and finding the facts. The person provides judgment and accountability.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  What I'd want from Switch next
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Push events (webhooks or WebSockets) when an agent finishes a turn, instead of polling the activity endpoint.&lt;/li&gt;
&lt;li&gt;Room templates, so an agent team with its prompts and tools could be created in one API call.&lt;/li&gt;
&lt;li&gt;Room context available to the built-in tools automatically, without a manual connect step.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;Verity is open source under the MIT license. The code, setup steps and architecture diagrams are on GitHub:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/acefyy/verity" rel="noopener noreferrer"&gt;github.com/acefyy/verity&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Thanks to DevTools Academy, WeMakeDevs and the Switch team for running the Flip the Switch hackathon.&lt;/p&gt;

</description>
      <category>switch</category>
      <category>ai</category>
      <category>hackathon</category>
      <category>discord</category>
    </item>
  </channel>
</rss>
