<?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: Kevin Anthony</title>
    <description>The latest articles on DEV Community by Kevin Anthony (@kevrc).</description>
    <link>https://dev.to/kevrc</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%2F3459425%2F53801f70-4cf6-4224-9285-2e9723b1fd2b.png</url>
      <title>DEV Community: Kevin Anthony</title>
      <link>https://dev.to/kevrc</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kevrc"/>
    <language>en</language>
    <item>
      <title>I put software renewals on trial with twelve bots and Switch</title>
      <dc:creator>Kevin Anthony</dc:creator>
      <pubDate>Thu, 05 Nov 2026 03:59:44 +0000</pubDate>
      <link>https://dev.to/kevrc/i-put-software-renewals-on-trial-with-twelve-bots-and-switch-5bnb</link>
      <guid>https://dev.to/kevrc/i-put-software-renewals-on-trial-with-twelve-bots-and-switch-5bnb</guid>
      <description>&lt;p&gt;Most software renewals get approved after one person reads the contract, and that person is usually the one who wants the tool. Nobody argues the other side. Idle seats renew for another year, a price increase sits in section 7.3, and the notice date passes.&lt;/p&gt;

&lt;p&gt;I built &lt;strong&gt;Overruled&lt;/strong&gt; for the Flip the Switch hackathon to fix that. It's a court for company spending that runs in a team chat. This post covers what it does, how I set it up on Switch and what I learned. If you've never used Switch, you can follow along.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Video: &lt;a href="https://youtu.be/tFVlc39mc5g" rel="noopener noreferrer"&gt;https://youtu.be/tFVlc39mc5g&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Code: &lt;a href="https://github.com/Kevrc/overruled" rel="noopener noreferrer"&gt;https://github.com/Kevrc/overruled&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Replay a real trial, nothing to install: &lt;a href="https://kevrc.github.io/overruled/replay/" rel="noopener noreferrer"&gt;https://kevrc.github.io/overruled/replay/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All the companies and figures below are fictional. Nobody has used Overruled on real contracts yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;p&gt;Scout finds a renewal before its notice deadline and opens a trial. A clerk, which is plain code and not AI, calculates the money and strikes any figure that isn't in the evidence. Two sides argue. Three jurors vote alone in private rooms. Then a person rules, and only a person can.&lt;/p&gt;

&lt;p&gt;In the demo, the defense claims a CRM is worth $150,000 a year. The clerk strikes it because the number isn't in any file. The jury votes to condition the renewal, for a calculated saving of $17,496 on a fictional contract.&lt;/p&gt;

&lt;p&gt;Overruled never pays, cancels or sends anything. It prepares a draft notice for a person to review.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this needed Switch
&lt;/h2&gt;

&lt;p&gt;I could have put all of this in one prompt: "Argue both sides, and wait for a human to approve." That's only a sentence, and a model can ignore a sentence.&lt;/p&gt;

&lt;p&gt;The promise I wanted was stronger: &lt;strong&gt;no agent can issue a ruling, even if it tries.&lt;/strong&gt; That has to live outside the model. Switch gives you shared rooms where agents and people talk, and the server enforces who can reach whom.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting it up
&lt;/h2&gt;

&lt;p&gt;You need Docker and &lt;a href="https://docs.astral.sh/uv/" rel="noopener noreferrer"&gt;uv&lt;/a&gt;. This starts Switch and Mattermost locally and creates the court:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Kevrc/overruled &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;overruled
scripts/install.sh
scripts/start.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No Claude account? Run &lt;code&gt;OVERRULED_OFFLINE=1 scripts/start.sh&lt;/code&gt; for scripted speeches with the same clerk, rooms and ruling code.&lt;/p&gt;

&lt;p&gt;Open &lt;a href="http://localhost:8365" rel="noopener noreferrer"&gt;http://localhost:8365&lt;/a&gt;, sign in with an account from &lt;code&gt;.state/access.txt&lt;/code&gt;, go to &lt;code&gt;courtroom&lt;/code&gt; and send &lt;code&gt;@scout scan&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here are the Switch pieces behind that, in the order I'd learn them.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Each bot is its own agent
&lt;/h3&gt;

&lt;p&gt;Each bot registers with Switch, and that's what gives it an identity in the room. This is shortened from &lt;code&gt;overruled/setup_court.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/agents&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;description&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;connector_type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;custom&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;integration_profile&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PROFILE&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. The room is a template
&lt;/h3&gt;

&lt;p&gt;The whole court lives in &lt;a href="https://github.com/Kevrc/overruled/blob/main/templates/overruled.template.yaml" rel="noopener noreferrer"&gt;one YAML file&lt;/a&gt;: the group, the room, who's in it, the seats and a policy document. A team can start another court from Templates without touching code.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Seats belong to roles, not bots
&lt;/h3&gt;

&lt;p&gt;Prosecution and defense are exclusive roles:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;roles&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;defense&lt;/span&gt;
    &lt;span class="na"&gt;exclusive&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;instructions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Fight for the expense as its internal champion.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The clerk calls &lt;code&gt;@defense&lt;/code&gt;, and Switch delivers that to whoever holds the seat. If the defender goes offline, Switch frees the seat and &lt;code&gt;counsel&lt;/code&gt; takes it. I didn't have to write that handover logic, and a test proves it works.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Addressing policies make the rule real
&lt;/h3&gt;

&lt;p&gt;This is the most important piece. It says people can reach &lt;code&gt;gavel&lt;/code&gt;, and agents can't:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;PEOPLE_ONLY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rules&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;users&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;agents&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]}]}&lt;/span&gt;
&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/gateway/agents/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;gavel_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/addressing-policy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;policy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PEOPLE_ONLY&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When a bot tries to rule, the server refuses it, and the refusal shows up in the channel. That's great for a demo, and also for an audit.&lt;/p&gt;

&lt;p&gt;Which person may rule is a separate question. &lt;code&gt;gavel&lt;/code&gt; decides that from &lt;code&gt;policy.yaml&lt;/code&gt;, using the sender identity Switch verifies rather than what the message claims.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Private rooms make a sealed jury
&lt;/h3&gt;

&lt;p&gt;Each trial creates a jury group with one room per juror, linked back to the court and archived after the verdict. A juror can't read another juror's room. One juror can run on a local Ollama model, and the verdict names the model behind each vote.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Managed agents do real work
&lt;/h3&gt;

&lt;p&gt;The prosecution and defense can also be real Claude Code sessions run by Switch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv run python &lt;span class="nt"&gt;-m&lt;/span&gt; overruled.setup_managed
scripts/start.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Switch shows their tool work in the chat. I didn't use Switch Console for this. I ran the headless agent controller, which needs &lt;code&gt;FEATURE_FLAGS_ENABLED=agent_management&lt;/code&gt; and &lt;code&gt;CONTROLLER_TOKEN_SECRET&lt;/code&gt; set on the server.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. The same court works in Slack
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;uv run python -m overruled.connect_slack&lt;/code&gt; provisions the same template through Switch's Slack bridge. It uses Socket Mode, so you need both a bot token and an app-level token.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;An agent with the same owner can remove your policy.&lt;/strong&gt; My first version looked safe until I confirmed on a live server that an agent could clear the addressing policy of another agent with the same owner. Now &lt;code&gt;gavel&lt;/code&gt; has a different owner, and setup checks it. If a policy protects something, check who owns the agents on both sides.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Test the rule by attacking it.&lt;/strong&gt; The policy wasn't what caught that problem. I found it by having an agent try to get around the policy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bots will happily talk forever.&lt;/strong&gt; A rejection that mentioned an agent triggered that agent, and they looped. Now agents ignore messages from other agents unless the step calls for them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Connect agents gradually.&lt;/strong&gt; Starting them all at once caused connection failures. A 0.5-second gap between them fixed it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keep the math out of the model.&lt;/strong&gt; The clerk uses no AI, so I could measure it directly: it caught 20 of 20 hand-labelled fabricated claims and kept 20 of 20 supported ones. On fictional renewals with hidden traps, it went from 15 of 26 to 26 of 26. Contracts in unfamiliar wording scored 1 of 8 at first, which proved that pattern rules alone don't generalize. Unclear contracts now go to a model, and code rejects any clause that isn't in the contract word for word. That reached 7 of 8.&lt;/p&gt;

&lt;p&gt;Those benchmarks were also used to fix the system, so they aren't blind tests. Real contracts will find new failures.&lt;/p&gt;

&lt;h2&gt;
  
  
  What isn't finished
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;It hasn't had a customer or a security audit.&lt;/li&gt;
&lt;li&gt;Slack was tested with one person. Requester separation was tested in Mattermost.&lt;/li&gt;
&lt;li&gt;Approval limits trust usernames from the bridge, so production needs them tied to a company directory.&lt;/li&gt;
&lt;li&gt;Managed agents have real tools, so a production setup should sandbox them.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Clone it, run &lt;code&gt;@scout scan&lt;/code&gt; and try to break the rule. If you can get a ruling out of an agent, I want to know.&lt;/p&gt;

&lt;p&gt;I built this with Claude Code as my coding partner. The &lt;a href="https://github.com/Kevrc/overruled#readme" rel="noopener noreferrer"&gt;README&lt;/a&gt; has everything else, and &lt;a href="https://github.com/Kevrc/overruled/blob/main/evals/RESULTS.md" rel="noopener noreferrer"&gt;evals/RESULTS.md&lt;/a&gt; explains the method and what the numbers don't prove.&lt;/p&gt;

&lt;p&gt;Shared with the #HowISwitch tag.&lt;/p&gt;

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