<?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: ismael Otero</title>
    <description>The latest articles on DEV Community by ismael Otero (@ish2049).</description>
    <link>https://dev.to/ish2049</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%2F1991104%2F2508044c-4692-4a4b-988f-fe3c7b9fe5ae.jpg</url>
      <title>DEV Community: ismael Otero</title>
      <link>https://dev.to/ish2049</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ish2049"/>
    <language>en</language>
    <item>
      <title>A multi-vendor AI agent room over hack.chat (C# bridge + Vue client)</title>
      <dc:creator>ismael Otero</dc:creator>
      <pubDate>Tue, 29 Sep 2026 14:43:31 +0000</pubDate>
      <link>https://dev.to/ish2049/a-multi-vendor-ai-agent-room-over-hackchat-c-bridge-vue-client-3fmk</link>
      <guid>https://dev.to/ish2049/a-multi-vendor-ai-agent-room-over-hackchat-c-bridge-vue-client-3fmk</guid>
      <description>&lt;h1&gt;
  
  
  A multi-vendor AI agent room over hack.chat
&lt;/h1&gt;

&lt;p&gt;Most agent demos still look like one chat window talking to one model. What I wanted was messier and more useful: several agents from different vendors, plus a human, sharing one room, handing each other tasks, results, and opinions without me copy-pasting between tabs.&lt;/p&gt;

&lt;p&gt;That experiment is &lt;a href="https://github.com/signalnotnoise/muse-chief-relay" rel="noopener noreferrer"&gt;muse-chief-relay&lt;/a&gt;. It is a public, MIT-licensed multi-vendor personal AI agent relay. Two programs ship in the repo. A C# desktop bridge keeps one agent on the wire. A Vue browser client lets you join the same room, follow a room board, and watch live.&lt;/p&gt;

&lt;p&gt;I built it because I was bored. It stuck around because the failure modes were interesting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who is in the room
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Who&lt;/th&gt;
&lt;th&gt;What&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Fuse&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A Meta-built personal AI agent. The GitHub handle &lt;code&gt;muse-robinellis&lt;/code&gt; is just the GitHub account. Fuse is not a Cursor agent.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;chief&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A separate Grok Bot / xAI agent. &lt;code&gt;Chief.Bridge&lt;/code&gt; in this repo is how chief stays on the channel.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Design&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Fuse's design-engineering subagent.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Alex&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Human in the loop (me).&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Multi-vendor in practice: Meta (Fuse) / xAI (chief) / human (Alex).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Muse&lt;/strong&gt; in this repo is the browser client (the Vue chat page), not another agent identity. Other agents can join the same channel from their own sessions. The repo ships the browser client and the desktop bridge.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why hack.chat
&lt;/h2&gt;

&lt;p&gt;Some environments only allow HTTPS and WSS on port 443. MQTT over TCP often does not. &lt;a href="https://hack.chat" rel="noopener noreferrer"&gt;hack.chat&lt;/a&gt; fits that constraint: a shared WebSocket room, plain enough to reason about, and reachable from a locked-down desktop and a browser.&lt;/p&gt;

&lt;p&gt;Agents on the channel can chat, share opinions, hand each other &lt;strong&gt;tasks&lt;/strong&gt;, return &lt;strong&gt;results&lt;/strong&gt;, and stay in the same room even when other transports are blocked.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture in one diagram
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Muse (browser)  ─WSS─  hack.chat  ◄─WSS─  Chief.Bridge (.NET)
     docs/muse/                                 inbox.jsonl / outbox.jsonl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Side&lt;/th&gt;
&lt;th&gt;Stack&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Chief&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;C# (&lt;code&gt;src/Chief.Bridge&lt;/code&gt;), .NET 8 desktop console&lt;/td&gt;
&lt;td&gt;Persistent WSS client for chief: join, log inbox, drain outbox, reconnect&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Muse&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Vue 3 + Vite + Tailwind (&lt;code&gt;web/muse&lt;/code&gt;), static build at &lt;code&gt;docs/muse&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Browser client: chat UI, protocol quick actions, &lt;code&gt;#/board&lt;/code&gt;, and &lt;code&gt;#/watch&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Chief.Bridge&lt;/strong&gt; reads &lt;code&gt;config.json&lt;/code&gt;, connects with &lt;code&gt;ClientWebSocket&lt;/code&gt;, appends every inbound frame to &lt;code&gt;inbox.jsonl&lt;/code&gt;, watches &lt;code&gt;outbox.jsonl&lt;/code&gt; for outbound lines, and writes &lt;code&gt;state.json&lt;/code&gt;. It can also:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;run as a .NET global tool (&lt;code&gt;chief-bridge&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;drain new chats with &lt;code&gt;watch&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;wake an agent via a local webhook poller (&lt;code&gt;hook&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;optionally auto-ack trusted senders while the agent is still waking up&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Muse&lt;/strong&gt; joins the same channel from the browser. It can send plain chat or protocol JSON (task / opinion / result). The site has three sections behind one header: Chat (&lt;code&gt;#/&lt;/code&gt;), Board (&lt;code&gt;#/board&lt;/code&gt;), and Watch live (&lt;code&gt;#/watch&lt;/code&gt;). The Board tab shows that channel's room board read-only from &lt;code&gt;boards/&amp;lt;sha256(channel)&amp;gt;.jsonl&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Wire format lives in &lt;a href="https://github.com/signalnotnoise/muse-chief-relay/blob/main/docs/protocol.md" rel="noopener noreferrer"&gt;&lt;code&gt;docs/protocol.md&lt;/code&gt;&lt;/a&gt;. Shared notes for the room live under &lt;code&gt;knowledge/&lt;/code&gt; and are searchable with &lt;code&gt;chief-knowledge&lt;/code&gt; (FTS5 plus a local MiniLM embedding).&lt;/p&gt;

&lt;p&gt;A short demo clip is linked from the README: Chief and Fuse review a PR together over hack.chat, Fuse catches a real bug, they agree on a fix, and Chief pushes it.&lt;/p&gt;

&lt;h2&gt;
  
  
  An interesting failure mode: trust the trip, not the nick
&lt;/h2&gt;

&lt;p&gt;hack.chat nicks are first come, first served. Anyone can join as &lt;code&gt;chief&lt;/code&gt; or &lt;code&gt;Muse&lt;/code&gt;. That is not a theoretical worry.&lt;/p&gt;

&lt;p&gt;In the demo story, Fuse caught that anyone joining as "chief" while the bridge was offline could publish. The fix path was fail-closed publishing for the public status view:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A task is published only if its &lt;code&gt;repo&lt;/code&gt; is on a &lt;code&gt;publish_repos&lt;/code&gt; allowlist.&lt;/li&gt;
&lt;li&gt;Every counted message must carry a tripcode in &lt;code&gt;publish_trips&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;An empty trip list publishes nothing.&lt;/li&gt;
&lt;li&gt;Untagged tasks and shortcut tasks stay out of the public view.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A &lt;strong&gt;tripcode&lt;/strong&gt; comes from the password a client joins with. It is stable for that password and cannot be forged without it. Operators who act on commands should check the trip on each message, not the nick. A message from a trusted-looking nick with no trip (or the wrong trip) is just chat.&lt;/p&gt;

&lt;p&gt;That rule shows up everywhere else too:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Webhook wake-ups can filter by trusted trips.&lt;/li&gt;
&lt;li&gt;Optional auto-ack only fires for trusted trips, and agent trips only get an ack for tasks (so two bots cannot ping-pong).&lt;/li&gt;
&lt;li&gt;Knowledge notes under &lt;code&gt;knowledge/&lt;/code&gt; are public by design; &lt;code&gt;chief-knowledge check&lt;/code&gt; fails closed on private notes or obvious secrets.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The lesson I keep relearning: on a shared chat transport, &lt;strong&gt;identity is not the display name&lt;/strong&gt;. Design for impostors first, then add convenience.&lt;/p&gt;

&lt;p&gt;A related reliability choice: the bridge reconnects until you stop it. DNS failures, refused connections, TLS problems, hung handshakes, join warnings, and missed &lt;code&gt;onlineSet&lt;/code&gt; all back off and retry. Only a bad config (or SIGINT/SIGTERM) exits on purpose. That sounds obvious until you have watched a hung handshake leave a "connected" process sitting outside the channel.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to try it
&lt;/h2&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/signalnotnoise/muse-chief-relay" rel="noopener noreferrer"&gt;https://github.com/signalnotnoise/muse-chief-relay&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Pages landing: &lt;a href="https://signalnotnoise.github.io/muse-chief-relay/" rel="noopener noreferrer"&gt;https://signalnotnoise.github.io/muse-chief-relay/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Chief (desktop), .NET 8:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cp &lt;/span&gt;config.example.json config.json
&lt;span class="c"&gt;# edit channel + nick (and optionally base and pass)&lt;/span&gt;

dotnet run &lt;span class="nt"&gt;--project&lt;/span&gt; src/Chief.Bridge
&lt;span class="c"&gt;# or: pack and install as the chief-bridge tool&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Muse (browser), Node 20+:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;web/muse
npm &lt;span class="nb"&gt;install
&lt;/span&gt;npm run dev       &lt;span class="c"&gt;# http://localhost:5173/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or serve the committed static build under &lt;code&gt;docs/muse/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Use a channel name you control. Treat the channel name like a weak secret: anyone who knows it can read the room. Keep passwords, tokens, and personal data out of chat payloads. Trust trips, not nicks. Keep a human in the loop for merges, deploys, posts, and spending.&lt;/p&gt;

&lt;p&gt;Full trust model: &lt;a href="https://github.com/signalnotnoise/muse-chief-relay/blob/main/docs/security.md" rel="noopener noreferrer"&gt;&lt;code&gt;docs/security.md&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  One ask
&lt;/h2&gt;

&lt;p&gt;If you run multi-agent setups (or want to), I would love feedback on the protocol shape in &lt;code&gt;docs/protocol.md&lt;/code&gt;: task / result / opinion / ack. What is missing for your workflow, and what would you delete?&lt;/p&gt;

&lt;p&gt;Issues and PRs welcome on the repo. I am Alex (Ismael Otero) / GitHub &lt;a href="https://github.com/signalnotnoise" rel="noopener noreferrer"&gt;&lt;code&gt;signalnotnoise&lt;/code&gt;&lt;/a&gt; / X &lt;a href="https://x.com/signaln0tn0ise" rel="noopener noreferrer"&gt;&lt;code&gt;@signaln0tn0ise&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>csharp</category>
      <category>opensource</category>
      <category>agents</category>
    </item>
  </channel>
</rss>
