<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Ashutosh Gaikwad]]></title><description><![CDATA[Ashutosh Gaikwad]]></description><link>https://ashutoshgaikwad0106.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Thu, 12 Nov 2026 08:31:21 GMT</lastBuildDate><atom:link href="https://ashutoshgaikwad0106.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Three AI agents, one chat room, and a human who has to type APPROVE]]></title><description><![CDATA[SkillOps for the WeMakeDevs Flip the Switch hackathon: three Claude Code agents in one Mattermost room via Switch, and a human approves every merge.]]></description><link>https://ashutoshgaikwad0106.hashnode.dev/three-ai-agents-one-chat-room-and-a-human-who-has-to-type-approve</link><guid isPermaLink="true">https://ashutoshgaikwad0106.hashnode.dev/three-ai-agents-one-chat-room-and-a-human-who-has-to-type-approve</guid><category><![CDATA[hackathon]]></category><category><![CDATA[ai agents]]></category><category><![CDATA[Mattermost]]></category><category><![CDATA[claude-code]]></category><category><![CDATA[WeMakeDevs]]></category><category><![CDATA[flip-the-switch]]></category><category><![CDATA[HowISwitch]]></category><dc:creator><![CDATA[ashutoshgaikwad0106]]></dc:creator><pubDate>Sat, 07 Nov 2026 16:33:26 GMT</pubDate><content:encoded><![CDATA[<p>AI coding agents are good at fixing bugs, but they live in terminals. The bug report lives in team chat, the test output in someone's shell, and the "ship it?" decision in a DM. For the Flip the Switch hackathon I wanted all of that in one place: <strong>a teammate reports a bug in chat, AI agents fix and review it in the same room, and nothing merges until a person explicitly approves it.</strong></p>
<p>The result is <strong>Switch SkillOps</strong>.</p>
<ul>
<li>Demo (1:25): <a href="https://youtu.be/NwBfaLTQvgw">https://youtu.be/NwBfaLTQvgw</a></li>
<li>Code (MIT): <a href="https://github.com/gaikwadashutosh35/switch-skillops">https://github.com/gaikwadashutosh35/switch-skillops</a></li>
</ul>
<h2>What it does</h2>
<p>Everything happens in one Mattermost channel, <code>skillops-triage</code>:</p>
<ol>
<li>I post a bug: <em>"The settings page crashes when I save a profile with an empty optional display name…"</em></li>
<li><strong>skillops-manager</strong> triages it: <code>SKO-0001 · bug · risk low · TRIAGED</code>, with acceptance criteria, the workflow it picked (<code>bug-tdd</code>) and why.</li>
<li>I say <code>start SKO-0001</code>. The manager hands it to <strong>skillops-coder</strong>, which writes a failing regression test (the bug reproduces), fixes it with a one-line change, and posts the passing test output.</li>
<li><strong>skillops-reviewer</strong>, a separate agent that didn't write the change, re-runs the tests, checks the acceptance criteria with file references, and asks me to decide.</li>
<li>I reply "looks good, merge it". <strong>Refused.</strong> Only <code>APPROVE SKO-0001 merge</code> from a person counts.</li>
<li>I send the exact approval. The merge then stops at a <strong>permission card in the room</strong>, and only runs after I click <em>Allow once</em>.</li>
</ol>
<p>In the recorded run, the whole thing from bug report to merge took about six minutes, most of it me reading.</p>
<h2>Switch in one paragraph</h2>
<p><a href="https://github.com/sandbox-quantum/switch">Switch</a> connects AI agents you already use (Claude Code, Codex, OpenCode and others) to team chat. A <strong>room</strong> is a chat channel where people and agents work together with shared context: room instructions, history, and who may talk to whom. <strong>Switch Console</strong> is the desktop app that runs a server, connects chat apps, and starts agent sessions for you. On a local setup it even brings its own Mattermost, so you don't need a Slack workspace.</p>
<h2>Why Switch was essential</h2>
<p>I didn't write any chat plumbing. Switch provided:</p>
<ul>
<li><strong>The room.</strong> Each agent is a real Mattermost bot in the channel, and everyone sees the same history.</li>
<li><strong>Handoffs.</strong> Agents wake each other with Switch <em>targeted messages</em> ("@skillops-coder SKO-0001 is planned… please implement it"). Switch also has a task-delegation protocol, but its docs mark it "present but not ready for use", so I used targeted messages.</li>
<li><strong>Who may instruct whom.</strong> The default addressing policy, <em>me and my agents</em>, lets my agents delegate to each other while refusing strangers.</li>
<li><strong>Approval in chat.</strong> When the manager runs the merge command, Claude Code's permission prompt arrives in Mattermost as a card with <em>Allow once / Allow for this session / Decline</em>. That's the moment the whole demo builds to, and it came for free.</li>
</ul>
<h2>The one design rule: agents propose, code decides</h2>
<p>Prompts are not guardrails. So the agents only read free text and draft; two small Python modules decide everything that matters:</p>
<ul>
<li><code>triage.py</code> validates the agent's JSON draft and refuses any <code>risk</code>, <code>workflow</code> or <code>status</code> field in it. It sets risk from <strong>the person's own words</strong>: deploy, production, token, <code>rm -rf</code> and similar mean high risk, which routes to a "stop, a person decides" workflow. It also deduplicates by Switch message id.</li>
<li><code>work.py</code> runs every later step with a state check first. <code>check</code> runs the tests with fixed arguments and saves trimmed, redacted output as evidence. <code>decide</code> accepts only <code>APPROVE SKO-xxxx merge</code> from a non-agent while the request awaits approval. <code>merge</code> refuses anything not <code>APPROVED</code>.</li>
</ul>
<p>That gives two independent gates on the one consequential action: SkillOps state, and the Switch permission card. All of it is covered by 50 standard-library unit tests.</p>
<h2>The best bug was found by my own agents</h2>
<p>On the first full run, the coder reproduced the bug, committed a correct fix… and <code>check</code> refused to move it to review. The coder <strong>stopped and reported the block in the room</strong> instead of forcing it through.</p>
<p>The cause was mine: the demo repo had no <code>.gitignore</code>, so running the tests wrote <code>__pycache__/*.pyc</code>, which got committed and was rewritten on every run. The tree was never clean, so <code>check</code> always said "not committed". My unit tests missed it because my own shell had <code>PYTHONDONTWRITEBYTECODE=1</code>, and the agents' shells didn't. The fix was a seed <code>.gitignore</code>, bytecode-free test runs, and a regression test that clears that variable on purpose.</p>
<p>It was a good reminder to test in the environment your agents actually run in.</p>
<h2>Giving the agents a debugging method</h2>
<p>The coder's brief said "test first, smallest fix", but nothing about what to do when the cause isn't obvious. So I gave two agents a <a href="https://github.com/gaikwadashutosh35/switch-skillops/tree/main/agents">Claude Code skill</a> each: a <code>SKILL.md</code> in the agent's own folder that it loads when the task matches. Neither skill adds a single command to the agent's allowlist.</p>
<ul>
<li><strong>skillops-coder →</strong> <code>debug</code><strong>:</strong> reproduce with a failing test, one hypothesis at a time, find every caller of the broken function, fix where they all pass through, then check the neighbouring inputs.</li>
<li><strong>skillops-reviewer →</strong> <code>bug-hunt</code><strong>:</strong> the reviewer can't run code, so it traces edge-case inputs through the change by reading it (empty, whitespace, <code>None</code>, <code>Zoë</code>, <code>李</code>, emoji, repeated spaces) and checks the other callers.</li>
</ul>
<p>I tested them live in the room and checked the agents' Claude Code transcripts rather than trusting what they said. The reviewer really did load <code>bug-hunt</code>: its review notes list every input it traced, plus two real problems outside the acceptance criteria (a stale docstring, and <code>display_name=None</code> raising). The coder never loaded <code>debug</code>, because the bug was obvious and its brief only calls for it on hard bugs. It still fixed the root cause. So one skill is proven and one isn't yet, and the repo says exactly that.</p>
<p>There's a third skill for me rather than the agents: <code>skillops-troubleshoot</code>, a symptom → evidence → fix runbook for the setup problems further down.</p>
<h2>Then I tried to break it</h2>
<p>With the demo working, I brute-forced my own code: 5,000 random and malformed drafts, 39 dangerous wordings, 25 fake approvals, 60 random workflow runs on a throwaway repo, and 18 secret formats. It found four serious bugs, all of which the happy-path demo had walked straight past:</p>
<ol>
<li><strong>Review could pass code it never tested.</strong> <code>work.py review</code> ran the tests on whatever branch was checked out. Start a second request while the first waits for review, and the first gets reviewed against the wrong code.</li>
<li><strong>The merge took the branch, not the reviewed commit.</strong> Anything committed after review would have been merged too. Now review records the commit it tested, and merge takes only that commit.</li>
<li><strong>Non-English text crashed triage on Windows.</strong> Python read the agent's UTF-8 as cp1252: <code>Zoë</code> was stored as <code>ZoÃ«</code>, and <code>李</code> crashed it. Fittingly, the demo bug is about display names.</li>
<li><strong>Secret redaction caught 7 of 18 formats.</strong> <code>"token": "…"</code> and <code>Authorization: Bearer …</code> went straight through. Now it catches all 18.</li>
</ol>
<p>Each fix got a failing test first. Two more followed: <code>@skillops-manager</code> was getting past the "agents can't approve" check, and high-risk requests could still be started. The fuzzer also found smaller problems I haven't fixed yet. The repo lists them as open, among them <code>rm -fr</code> not counting as high risk.</p>
<p>The lesson: "agents propose, code decides" is only as strong as the code that decides, so attack that code before anyone else does.</p>
<h2>What I wish I had known on day one (Windows, Switch Console 0.40.1)</h2>
<p>These cost me hours. Each one is written up with logs and source references in the repo's <a href="https://github.com/gaikwadashutosh35/switch-skillops/blob/main/docs/LOCAL_TEST_RESULTS.md">test results</a>.</p>
<ol>
<li><strong>You don't need WSL on Windows.</strong> On a local server, agents run as <em>managed agents</em> on your PC. If the provider list is empty, open <strong>Your Agents → +</strong> and click <strong>Run managed agents on this computer</strong>. I lost an afternoon setting up an Ubuntu SSH host I never needed.</li>
<li><strong>Create agents in the New agent form, not a template.</strong> In 0.40.1 on Windows, the template form showed every provider as "Not supported here", while the New agent form worked. My template now creates only the room (instructions, members, kickoff) and adds existing agents.</li>
<li><strong>Set the Directory when you create the agent.</strong> An agent's existing room session keeps the directory it started with, even after you edit the agent. <code>!reset</code> resumes the same session. If you got it wrong, delete the agent, recreate it, and <code>!invite-agent</code> it back.</li>
<li><strong>If agents go silent after a restart, check the Mattermost bridge.</strong> If Mattermost comes up after the Switch server (say, Docker restarted), the bridge crashes and doesn't retry. The server log says <code>Bridge … crashed</code>. The fix: <strong>Admin → Status</strong>, turn Mattermost off and on.</li>
<li><strong>Your project's allow rules need a trusted folder.</strong> Claude Code ignores a project's <code>permissions.allow</code> until the folder is trusted, but <strong>deny rules always apply</strong>. I verified that in each agent's folder: <code>git status</code> was denied while an unlisted <code>python --version</code> ran.</li>
<li><strong>Read the source when the UI surprises you.</strong> The Switch repository is very readable. Every one of the issues above I confirmed in the code rather than guessing.</li>
</ol>
<p>I filed numbers 2, 3 and 4 upstream with logs and source references (<a href="https://github.com/sandbox-quantum/switch/issues/1511">#1511</a>, <a href="https://github.com/sandbox-quantum/switch/issues/1512">#1512</a>, <a href="https://github.com/sandbox-quantum/switch/issues/1510">#1510</a>). Within two hours another hackathon participant had opened a fix for the bridge crash (<a href="https://github.com/sandbox-quantum/switch/pull/1515">#1515</a>), and I checked it against my crash logs on the issue. Detailed bug reports are a contribution too.</p>
<h2>Tips for your first Switch room</h2>
<ul>
<li>Start with the quickstart and get one agent answering in a room before building anything.</li>
<li>Put the agent's brief in <code>CLAUDE.md</code> in its working directory. Console also adds the agent's instructions to the session, after Switch's own skill.</li>
<li>Address agents with <code>@name</code> at the start of a message; a message that addresses nobody is silently ignored.</li>
<li><code>!reset @agent</code> gives an agent a fresh context; <code>!help</code> lists every room command.</li>
<li>Mattermost replies often land in threads. If an agent seems stuck, check the thread under your message: a permission card may be waiting there.</li>
</ul>
<h2>What's next</h2>
<p>SkillOps merges locally into a demo repo. Next I'd fix the open fuzzing findings, have the manager open a real GitHub draft pull request after approval, and let teams point it at their own repository.</p>
<p>If you're starting with Switch, I hope the list above saves you the hours it cost me. Code and docs: <a href="https://github.com/gaikwadashutosh35/switch-skillops">https://github.com/gaikwadashutosh35/switch-skillops</a></p>
<p><em>Built for the Flip the Switch hackathon by WeMakeDevs and DevTools Academy, with Claude Code as my pair programmer.</em> #HowISwitch</p>
]]></content:encoded></item></channel></rss>