<?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: Darshan Ved</title>
    <description>The latest articles on DEV Community by Darshan Ved (@darshan_ved_9ca4564654404).</description>
    <link>https://dev.to/darshan_ved_9ca4564654404</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%2F4232344%2F5202b81e-3158-4282-a912-2b18007f3b22.jpg</url>
      <title>DEV Community: Darshan Ved</title>
      <link>https://dev.to/darshan_ved_9ca4564654404</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/darshan_ved_9ca4564654404"/>
    <language>en</language>
    <item>
      <title>A proposal is not a reservation: what we learned putting AI agents in a lab's booking chat</title>
      <dc:creator>Darshan Ved</dc:creator>
      <pubDate>Sat, 07 Nov 2026 15:35:02 +0000</pubDate>
      <link>https://dev.to/darshan_ved_9ca4564654404/a-proposal-is-not-a-reservation-what-we-learned-putting-ai-agents-in-a-labs-booking-chat-3cee</link>
      <guid>https://dev.to/darshan_ved_9ca4564654404/a-proposal-is-not-a-reservation-what-we-learned-putting-ai-agents-in-a-labs-booking-chat-3cee</guid>
      <description>&lt;p&gt;Every lab with a shared instrument has the same quiet failure mode. A student asks for an hour on the plate reader. Somebody checks a spreadsheet. Somebody else has a booking that never made it into the spreadsheet. A supervisor writes "approved" in chat, but by then the conversation has drifted to a different time slot, and nobody is quite sure what was approved.&lt;/p&gt;

&lt;p&gt;For the Flip the Switch hackathon we built LabQuorum, a booking workflow where people and AI agents share one team chat and one source of truth. This post covers what we built, the design rule that shaped it, and what happened when a real student used it for the first time. That last part taught us the most.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one rule: agents propose, people commit
&lt;/h2&gt;

&lt;p&gt;We split the work between two agents with deliberately different powers, running in a Switch room bridged to Mattermost:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;@labops&lt;/code&gt; is the bench manager. Anyone in the room can talk to it. It can list instruments, check training and availability, and create a pending booking request. It cannot commit anything. The commit tool is not in its tool profile at all.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@pi-desk&lt;/code&gt; is the PI's desk. Only its owner, the principal investigator, can address it. It can retrieve a request, check policy, decline, or ask to commit. A commit only runs after the PI answers a native Switch approval card that shows the exact decision: instrument, requester, local and UTC times, revision and content hash.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both agents reach the same SQLite ledger through an MCP server. Each agent gets a different tool profile, so the boundary is enforced by what the agent can call, not by what we ask it nicely in a prompt. Every proposal, decline and commit lands in a hash-chained audit log.&lt;/p&gt;

&lt;p&gt;The sentence we ended up putting into the agent's instructions, and repeating to ourselves, is: a proposal is not a reservation.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a good answer looks like
&lt;/h2&gt;

&lt;p&gt;Ask &lt;code&gt;@labops&lt;/code&gt; for 60 minutes on INS-01 between 14:00 and 17:00. In our synthetic lab, Dr. Lena Okafor already holds 14:00 to 15:30. A useful reply has to do four things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Name the actual conflict and who owns it.&lt;/li&gt;
&lt;li&gt;Propose the nearest free slot of the same length, respecting the instrument's cleaning gap (here, 15:45 to 16:45).&lt;/li&gt;
&lt;li&gt;Name the requester and give a request ID.&lt;/li&gt;
&lt;li&gt;Say plainly that the request is pending and nothing is booked.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;On the live deployment, that is what happened: LabOps named Lena's 14:00 to 15:30 booking, proposed 15:45 to 16:45, and the request stayed pending with zero commit events and no approval card on the proposer side.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part that worked before we expected it to
&lt;/h2&gt;

&lt;p&gt;The owner-only boundary on &lt;code&gt;@pi-desk&lt;/code&gt; is not something we wrote. Switch enforces it. When a student account addressed &lt;code&gt;@pi-desk&lt;/code&gt; asking it to approve a booking, Switch refused the message before the agent ever saw it: the operator had restricted who can address it in that room.&lt;/p&gt;

&lt;p&gt;In an isolated certification session on a test ledger, we also exercised the full approval path. The PI answered the native card once and exactly one matching booking and one commit audit event were written. A decline left the request pending. A student pressing Approve on the owner-only card did not settle it. Two prompt-injection probes produced matching cards and no commit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Then a real student used it
&lt;/h2&gt;

&lt;p&gt;Automated tests told us the ledger was correct. They did not tell us whether the thing was usable. So one of us (a student, not the person who wrote the agents) joined the room cold and typed the request the way a student would:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;@rao-labops can I book INS-01 for 60 minutes on the next open day between 14:00 and 17:00?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It went badly in instructive ways.&lt;/p&gt;

&lt;p&gt;Read-only lookups asked for permission. Before answering, LabOps raised approval cards for &lt;code&gt;lab_state&lt;/code&gt; and &lt;code&gt;check_availability&lt;/code&gt;, and the student was able to answer them. The first reply took 7 minutes 47 seconds. A safety prompt on a read is not safety. It trains people to click through prompts, and it makes the one prompt that matters, the commit, less meaningful.&lt;/p&gt;

&lt;p&gt;The requester was hard-wired. The proposal tool was scoped to our seeded demo user, Maya. The agent correctly refused to book for the student under someone else's name, but that meant it could not book for the student at all.&lt;/p&gt;

&lt;p&gt;Card answers were misread as facts. The student answered the stray cards with "A7 1" and "A9 1". The agent then reasoned about those strings as if they were claims about the student's training.&lt;/p&gt;

&lt;p&gt;The PI-desk refusal did hold. That was the one part we had not built ourselves.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we changed
&lt;/h2&gt;

&lt;p&gt;All three bugs went into a test-first fix run and were merged the same night:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The requester is whoever sent the message. LabOps passes the Switch sender's handle to the booking tools as the requester, and the student exists in the synthetic lab with INS-01 training, so the proposal is theirs. The tools still accept that handle from the caller and do not authenticate it against the platform yet.&lt;/li&gt;
&lt;li&gt;Only the binding action prompts. Reads run without approval cards. The single remaining approval point is &lt;code&gt;commit_booking&lt;/code&gt;, which belongs to the PI.&lt;/li&gt;
&lt;li&gt;Card answers are not conversation. The agent no longer treats approval-card responses as statements about the world.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The same pass turned up a quieter problem. On the earlier build, anyone who could message &lt;code&gt;@labops&lt;/code&gt;, including a student, could cancel another person's committed booking: &lt;code&gt;cancel_my_proposal&lt;/code&gt; took only a booking ID, with no owner check. We reproduced it, then made both &lt;code&gt;cancel_my_proposal&lt;/code&gt; and &lt;code&gt;check_availability&lt;/code&gt; require and enforce the sender's handle. Conflict replies now include the conflicting interval and a deterministic summary, to reduce drift into vague wording.&lt;/p&gt;

&lt;p&gt;Then we ran the same request again, from the same student account, in the same live room. LabOps named Lena's conflicting booking, proposed 15:45 to 16:45 under the student's own name as a pending request, and said "Nothing has been booked." It took 80 seconds instead of almost eight minutes, with zero approval cards. When the student then asked &lt;code&gt;@pi-desk&lt;/code&gt; to approve it, Switch refused the message within four seconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we are honest about
&lt;/h2&gt;

&lt;p&gt;LabQuorum ships proposal-only. Binding commits stay off until the remaining checks pass:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The one-commit-per-process limit was not exercised live, because no verified Console session ID was available.&lt;/li&gt;
&lt;li&gt;Three of five injection probes did not capture a request or card, so they count as not proven.&lt;/li&gt;
&lt;li&gt;No live trace showed which model and reasoning effort the agents actually ran with.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The approve-once pass on a test ledger is real evidence for the approval mechanism. It is not proof that the whole deployed workflow is safe to bind. We would rather ship a correct proposal than a confident reservation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons we would pass on
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Put the boundary in the tool list, not the prompt. The agent that can be talked into anything should not have the dangerous tool.&lt;/li&gt;
&lt;li&gt;Prompt for the irreversible step only. Approval fatigue is a security bug.&lt;/li&gt;
&lt;li&gt;Treat "who is asking" as something to verify, not just pass along. Our tools still take a caller-supplied handle; binding it to the authenticated sender is our next step.&lt;/li&gt;
&lt;li&gt;Test with someone who did not build it. One cold run by a real student found three bugs our suites had passed, and the review that followed found a security hole.&lt;/li&gt;
&lt;li&gt;Write down what is not proven. It keeps the demo honest and tells you exactly what to test next.&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;The code is open source: &lt;a href="https://github.com/nikhilpravinpise/FlipTheSwitch" rel="noopener noreferrer"&gt;github.com/nikhilpravinpise/FlipTheSwitch&lt;/a&gt;. It is Python 3.12 with &lt;code&gt;uv&lt;/code&gt;, SQLite and the official MCP Python SDK. You can explore the ledger locally without the live room:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;uv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sync&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--locked&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;uv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;lq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;init&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;uv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;lq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;seed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--demo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--anchor-date&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;2026-11-07&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;uv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;lq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;audit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;verify&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All people and lab records in the demo are synthetic. Built by Nikhil Pise and Darshan Ved for Flip the Switch. Much of this post was drafted by voice with Wispr Flow.&lt;/p&gt;

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