<?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: vidhaan khare</title>
    <description>The latest articles on DEV Community by vidhaan khare (@vidhaan_khare_5c047f23a4e).</description>
    <link>https://dev.to/vidhaan_khare_5c047f23a4e</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%2F4167506%2Ffae89842-73aa-47a0-a0cf-c4124fb2d974.jpg</url>
      <title>DEV Community: vidhaan khare</title>
      <link>https://dev.to/vidhaan_khare_5c047f23a4e</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vidhaan_khare_5c047f23a4e"/>
    <language>en</language>
    <item>
      <title>Your AI agents don't sleep. Your approvers do. Building a border for agent work on Switch</title>
      <dc:creator>vidhaan khare</dc:creator>
      <pubDate>Sat, 07 Nov 2026 15:21:23 +0000</pubDate>
      <link>https://dev.to/vidhaan_khare_5c047f23a4e/your-ai-agents-dont-sleep-your-approvers-do-building-a-border-for-agent-work-on-switch-3a6</link>
      <guid>https://dev.to/vidhaan_khare_5c047f23a4e/your-ai-agents-dont-sleep-your-approvers-do-building-a-border-for-agent-work-on-switch-3a6</guid>
      <description>&lt;p&gt;How I used Switch rooms, roles and a small custom agent to let AI work cross from Bengaluru to Berlin and back overnight, without customer data ever leaving the EU, and without skipping a single human signature.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://youtu.be/oU3Dtn7tzgg" rel="noopener noreferrer"&gt;https://youtu.be/oU3Dtn7tzgg&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The problem nobody demos
&lt;/h2&gt;

&lt;p&gt;Most agent demos have one agent, one person and one chat. Real companies have borders:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Data borders.&lt;/strong&gt; An EU customer's records may only be handled inside the EU. In 2023 the Irish Data Protection Commission fined Meta €1.2 billion for moving Europeans' data to the US.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authority borders.&lt;/strong&gt; Only certain people may approve certain work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Time borders.&lt;/strong&gt; The person allowed to say "ship it" is asleep for the next eight hours.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Agents can work around the clock, but they can't cross those borders on their own. So teams either give the agent everything (all access, auto-approve) or keep a human in every loop and let the agent wait all night.&lt;/p&gt;

&lt;p&gt;I wanted a third option for the Flip the Switch hackathon: &lt;strong&gt;let work cross borders while data and authority stay where they belong.&lt;/strong&gt; I called it &lt;strong&gt;Customs&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Switch fits this exactly
&lt;/h2&gt;

&lt;p&gt;If you haven't used &lt;a href="https://github.com/sandbox-quantum/switch" rel="noopener noreferrer"&gt;Switch&lt;/a&gt; yet: it puts AI agents (Claude Code, Codex, OpenCode, or your own) into team chat. Each chat channel becomes a &lt;strong&gt;room&lt;/strong&gt; that people and agents share. These are the parts that mattered for this project:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concept I needed&lt;/th&gt;
&lt;th&gt;Switch primitive&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A region (a jurisdiction)&lt;/td&gt;
&lt;td&gt;A &lt;strong&gt;room&lt;/strong&gt;. An agent only sees rooms it's a member of, and the server enforces it: &lt;code&gt;read_context&lt;/code&gt; on another room is refused.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"Who owns this ticket right now?"&lt;/td&gt;
&lt;td&gt;An &lt;strong&gt;exclusive role&lt;/strong&gt; in that room: one holder at a time, released automatically when the agent disconnects.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The border itself&lt;/td&gt;
&lt;td&gt;A &lt;strong&gt;custom agent&lt;/strong&gt; that's a member of every room. Mine is plain Python, not an LLM.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The whole setup&lt;/td&gt;
&lt;td&gt;One &lt;strong&gt;template&lt;/strong&gt;: a group, three rooms, links, roles and kickoff messages.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The important one is the first. The boundary isn't "please don't" in a prompt. It's room membership, checked by the server. Here is the Bengaluru agent trying to read Berlin's room in my demo:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftsigdaykug1afld6k6mz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftsigdaykug1afld6k6mz.png" alt="The Bengaluru agent tries to read the Berlin room and Switch answers 403: Agent is not a member of this room" width="799" height="384"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting started (about 15 minutes)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Install Switch Console&lt;/strong&gt; from the &lt;a href="https://github.com/sandbox-quantum/switch/releases/latest" rel="noopener noreferrer"&gt;releases page&lt;/a&gt; and pick "on this computer". It starts a local server and a bundled &lt;strong&gt;Mattermost&lt;/strong&gt;, so you don't need a company Slack.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sign in a coding agent&lt;/strong&gt; under &lt;em&gt;Settings → Agent providers&lt;/em&gt; (I used Claude Code).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Create agents&lt;/strong&gt; in &lt;em&gt;Your Agents → New agent&lt;/em&gt;. Each has a working directory, instructions, auto mode (risky actions ask a human) and &lt;strong&gt;who can talk to it&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Create rooms from a template&lt;/strong&gt;: a YAML file with &lt;code&gt;params&lt;/code&gt;, &lt;code&gt;rooms&lt;/code&gt;, &lt;code&gt;roles&lt;/code&gt;, &lt;code&gt;links&lt;/code&gt; and &lt;code&gt;kickoff&lt;/code&gt;. My rooms-only template is &lt;a href="https://github.com/Vidhaankhare16/customs/blob/main/switch/customs-rooms.template.yaml" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Tip: Switch serves its docs over MCP. &lt;code&gt;claude mcp add switch-docs --transport http https://docs.switchagents.ai/mcp&lt;/code&gt; lets your coding agent answer Switch questions while you build.&lt;/p&gt;

&lt;h2&gt;
  
  
  The design: three rooms and a clerk
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;customs-intake   people + clerk            tickets are opened here; the custody trail is posted here
customs-in       Bengaluru agent + lead    pseudonymised case files only
customs-eu       Berlin agent + lead       the only room that may see EU customer data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each regional agent is a member of &lt;strong&gt;one&lt;/strong&gt; room. The &lt;strong&gt;clerk&lt;/strong&gt; is a member of all three, and it's the only path between them. Agents hand work on by posting a structured block in their room:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;```customs-handoff
{ "ticket": "TKT-0001", "to": "eu", "summary": "...", "done": [...], "next": [...],
  "evidence": ["pytest: 13 passed"], "branch": "fix/tkt-0001", "commit": "4145f1e" }
```
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The clerk then decides, in code:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Custody.&lt;/strong&gt; Is the sender the current holder of this room's exclusive role? Switch tells you: &lt;code&gt;get_room_detail&lt;/code&gt; returns every role with its live &lt;code&gt;held_by&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The border check.&lt;/strong&gt; Leaving the EU, is there any personal data (names, emails, phones, IBANs, VAT IDs)? If so, the handoff is &lt;strong&gt;blocked, not repaired&lt;/strong&gt;, and the refusal says where without repeating the value. Invoice numbers are pseudonymised to &lt;code&gt;[INV-3]&lt;/code&gt; on the way out and turned back into real numbers only on the way back in.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A human signature.&lt;/strong&gt; The lead of the region the work is leaving must type &lt;code&gt;approve H-1&lt;/code&gt;. The clerk checks that the signer is a person (not one of the agents in the room) and on the approver list. The signature is pinned to the digest of the exact text that will cross, and it can only be used once.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Release.&lt;/strong&gt; Before anyone may sign a release, the clerk &lt;strong&gt;re-runs the verification itself&lt;/strong&gt; on a fresh export of that exact commit. Then it fast-forwards &lt;code&gt;main&lt;/code&gt; to exactly that commit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A ledger.&lt;/strong&gt; Every decision, including every refusal, goes into a hash-chained log. Edit one line and &lt;code&gt;clerk verify-ledger&lt;/code&gt; names the entry.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Authority while asleep: standing orders
&lt;/h2&gt;

&lt;p&gt;The data border was the obvious half. The time border was the interesting one. Before logging off, the Berlin lead types:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;clerk standing-order release TKT-0001 until 09:00
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The clerk restates exactly what it may do: sign that one release &lt;strong&gt;only if its own reconciliation of that exact commit passes&lt;/strong&gt;, once, until 09:00, and only while Berlin's office is closed. If the commit fails, the order does nothing and the release waits for a person. Everything is ledgered as "signed by Berlin Lead (standing order SO-2)".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj2o30a1zn5y1sc7n6bgi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj2o30a1zn5y1sc7n6bgi.png" alt="The Berlin lead leaves a standing order and the clerk restates its exact limits" width="799" height="384"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the recorded run, the ticket was fixed in Bengaluru overnight, verified in Berlin at 03:00 and released, &lt;strong&gt;six hours before anyone was awake to approve it&lt;/strong&gt;. Both signatures were still real, and the custody card says so:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx959xoh8vsd0zdjs88ik.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx959xoh8vsd0zdjs88ik.png" alt="Custody card: four crossings, each signed by a person or their standing order, and personal data that left the EU: none" width="799" height="384"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Writing a custom Switch agent (the clerk) over plain HTTP
&lt;/h2&gt;

&lt;p&gt;The clerk uses only the parts of the &lt;a href="https://docs.switchagents.ai/switch-rooms/internals/agent-protocol" rel="noopener noreferrer"&gt;agent protocol&lt;/a&gt; every server supports, with the Python standard library:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# register once (the creator of the registration key becomes the owner)
POST /agents   {"name": "customs-clerk", "connector_type": "customs-clerk",
                "integration_profile": {"connection_model": "session_passive", "message_exchange": true,
                                        "pre_invocation_mediation": [], "post_invocation_mediation": [],
                                        "event_reporting": []}}

# then, with the API key it returns:
POST /agents/{id}/ops/list_rooms        {}                                  -&amp;gt; rooms (note: "room_id", not "id")
POST /agents/{id}/ops/read_context      {"room_id": ..., "since": "..."}    -&amp;gt; threads of messages
POST /agents/{id}/ops/get_room_detail   {"room_id": ...}                    -&amp;gt; agent_names, roles + held_by
POST /agents/{id}/message               {"room_id": ..., "content": "..."}  -&amp;gt; post
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It polls every two seconds instead of holding a WebSocket open, which makes it restart-safe: history is always on the server. Decisions are saved &lt;em&gt;before&lt;/em&gt; they're announced, through an outbox, so a crash never double-posts a handoff.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned (gotchas for first-timers)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;An agent can't change its own settings.&lt;/strong&gt; If you register a custom agent with the server's bootstrap token, its owner is a system account, and only the owner or an admin can change who may @-mention it. Register with a key you created, so you own the agent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A mention is "addressing".&lt;/strong&gt; If the agent's policy doesn't admit the sender, Switch auto-replies "not permitted" on its behalf. My clerk reads every message anyway, so it also accepts commands starting with the plain word &lt;code&gt;clerk&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Room roles hold one role per agent.&lt;/strong&gt; &lt;code&gt;assume_role&lt;/code&gt; fails if the agent already holds a role elsewhere. After a reset (&lt;code&gt;!reset-all-agents&lt;/code&gt;), Switch re-assumes the role for you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slow machines:&lt;/strong&gt; a template run can finish on the server after the Console's request times out. Check before you click retry, or you'll get duplicate rooms.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Everything is open source: &lt;a href="https://github.com/Vidhaankhare16/customs" rel="noopener noreferrer"&gt;github.com/Vidhaankhare16/customs&lt;/a&gt;. &lt;code&gt;python scripts/setup_regions.py&lt;/code&gt; builds the two workspaces, the template builds the rooms, and &lt;code&gt;python -m customs run&lt;/code&gt; starts the clerk. The 51 tests run offline, with no Switch server or AI needed, in about 20 seconds.&lt;/p&gt;

&lt;p&gt;Swap the configuration and the same desk keeps client A's files away from client B's agents, keeps patient records inside a hospital, or keeps Indian payment data in India.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Work crosses borders. Data doesn't.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>automation</category>
    </item>
  </channel>
</rss>
