<?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: 李超杰</title>
    <description>The latest articles on DEV Community by 李超杰 (@choukelee).</description>
    <link>https://dev.to/choukelee</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3969909%2Ffe39b714-e3d8-48ec-8840-df9b94af22ca.jpg</url>
      <title>DEV Community: 李超杰</title>
      <link>https://dev.to/choukelee</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/choukelee"/>
    <language>en</language>
    <item>
      <title>I Let AI Run a Restaurant for 3 Months. Here's the Architecture That Kept It Safe.</title>
      <dc:creator>李超杰</dc:creator>
      <pubDate>Fri, 05 Jun 2026 13:31:52 +0000</pubDate>
      <link>https://dev.to/choukelee/i-let-ai-run-a-restaurant-for-3-months-heres-the-architecture-that-kept-it-safe-4bcp</link>
      <guid>https://dev.to/choukelee/i-let-ai-run-a-restaurant-for-3-months-heres-the-architecture-that-kept-it-safe-4bcp</guid>
      <description>&lt;h1&gt;
  
  
  I Let AI Run a Restaurant for 3 Months. Here's the Architecture That Kept It Safe.
&lt;/h1&gt;

&lt;p&gt;Three months ago, I started building an AI restaurant ordering system. Customers talk to it in English, French, or Chinese. The AI takes their order through natural conversation — like a real waiter.&lt;/p&gt;

&lt;p&gt;I thought the hard part would be making the AI understand what customers want.&lt;/p&gt;

&lt;p&gt;I was wrong. The hard part was stopping the AI from doing things it shouldn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  The moment I realized I had a problem
&lt;/h2&gt;

&lt;p&gt;Week one. A customer types: "I want the lobster roll."&lt;/p&gt;

&lt;p&gt;The AI searches the menu. The lobster roll is sold out. But the AI calls &lt;code&gt;add_to_cart("lobster-roll")&lt;/code&gt; anyway.&lt;/p&gt;

&lt;p&gt;Why? Because LLMs don't "know" things. They predict the next token. And the most likely next token after a search result is... acting on it.&lt;/p&gt;

&lt;p&gt;I fixed the prompt. "Never add a sold-out item." It worked. For a day.&lt;/p&gt;

&lt;p&gt;Then the AI started hallucinating item IDs. "smash-burger" became "smash-burger-with-extra-pickles". The tool call failed, the AI got confused, the customer got nothing.&lt;/p&gt;

&lt;p&gt;Then the AI added a negative quantity. -1 burger. Because the customer said "I don't want a burger" and the AI reasoned: remove = add with negative quantity.&lt;/p&gt;

&lt;p&gt;Three failures. Three different root causes. All from trusting the AI too much.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rule that changed everything
&lt;/h2&gt;

&lt;p&gt;I wrote one sentence on a sticky note and put it on my screen:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;AI understands intent. Code executes. Never let AI directly control state.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Every tool the AI can call now has two layers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;add_to_cart&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Add an item to the customer's order&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;inputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="c1"&gt;// Layer 1: VALIDATE — runs before execution, EVERY time&lt;/span&gt;
  &lt;span class="nx"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;item_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ValidationError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Item not found&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;available&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ValidationError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Sold out&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ValidationError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Price not set&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="c1"&gt;// Layer 2: EXECUTE — only runs if validate passes&lt;/span&gt;
  &lt;span class="nx"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;item_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;quantity&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Added to cart&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The AI decides what tool to call and with what parameters. But it cannot bypass &lt;code&gt;validate()&lt;/code&gt;. Ever. By design.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this catches in production
&lt;/h2&gt;

&lt;p&gt;After three months, here's what the validation layer has blocked:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What the AI tried&lt;/th&gt;
&lt;th&gt;Why it happened&lt;/th&gt;
&lt;th&gt;Caught by&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Add lobster roll (sold out)&lt;/td&gt;
&lt;td&gt;Ignored the [SOLD OUT] tag in search results&lt;/td&gt;
&lt;td&gt;&lt;code&gt;validate: !item.available&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Add "smash-burger-deluxe" (not real)&lt;/td&gt;
&lt;td&gt;Hallucinated a variant that doesn't exist&lt;/td&gt;
&lt;td&gt;&lt;code&gt;validate: !item&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Add -1 burger&lt;/td&gt;
&lt;td&gt;Interpreted "don't want" as negative quantity&lt;/td&gt;
&lt;td&gt;&lt;code&gt;validate: quantity &amp;lt; 1&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Confirm order with no items&lt;/td&gt;
&lt;td&gt;Got confused mid-conversation&lt;/td&gt;
&lt;td&gt;&lt;code&gt;validate: cart.length === 0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Not once did bad data reach the cart. The AI made mistakes — as all LLMs do. But it never corrupted state.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;🧑 Customer message: "I want the lobster roll and a salad"
        ↓
🤖 AI: [searches menu, decides: add_to_cart("lobster-roll"), add_to_cart("quinoa-salad")]
        ↓
🛡️ VALIDATION WALL
    ├── lobster-roll → ❌ REJECTED ("Sold out today")
    └── quinoa-salad → ✅ PASSED
        ↓
⚡ EXECUTION
    └── quinoa-salad added to cart
        ↓
🤖 AI: "I'm sorry, the lobster roll is sold out. I added the quinoa salad.
     Can I suggest something similar — perhaps the shrimp avocado bowl?"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key insight: the validation error goes BACK to the AI. The AI reads it and recovers. It apologizes. It suggests alternatives. It behaves like a real waiter who checked with the kitchen and came back with bad news.&lt;/p&gt;

&lt;p&gt;This is better than silently swallowing the error. The AI learns from the feedback in real time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why middleware is the wrong approach
&lt;/h2&gt;

&lt;p&gt;Most AI agent frameworks use middleware: a function that runs before every tool call.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// The middleware pattern — what everyone else does&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;middleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;toolName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;toolName&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;add_to_cart&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ... some check&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This doesn't scale. Middleware knows WHAT tool was called. It doesn't know WHY — what business condition this specific tool needs. After 5 tools, your middleware is a 200-line switch statement.&lt;/p&gt;

&lt;p&gt;Per-tool &lt;code&gt;validate()&lt;/code&gt; scales infinitely. Every tool carries its own safety rules. Add a new tool, add its validation. Remove a tool, its validation leaves with it. No central file to maintain.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I built: Agent Kit
&lt;/h2&gt;

&lt;p&gt;I extracted this pattern into a small, open-source TypeScript framework:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;github.com/ChoukeLee/agent-kit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's 4 files. No dependencies except the AI provider you choose. Works with Claude, OpenAI, DeepSeek — anything that supports tool calling.&lt;/p&gt;

&lt;p&gt;The demo is a restaurant ordering bot. You can run it in 5 minutes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install
export &lt;/span&gt;&lt;span class="nv"&gt;ANTHROPIC_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your-key"&lt;/span&gt;
npx tsx smoke-test.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Building AI products isn't about making the AI smarter. It's about acknowledging that the AI will make mistakes — and designing your system so those mistakes can't hurt anything.&lt;/p&gt;

&lt;p&gt;The validation wall isn't just a safety net. It's a design philosophy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AI does what it's good at:&lt;/strong&gt; understanding messy human language&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code does what it's good at:&lt;/strong&gt; enforcing rules with 100% reliability&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;They communicate through a defined interface:&lt;/strong&gt; tool calls return results. Validation errors return corrections.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That sticky note is still on my screen. If you're building anything with AI tool calling, write it on yours too.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I'm building &lt;a href="https://github.com/ChoukeLee/agent-kit" rel="noopener noreferrer"&gt;Agent Kit&lt;/a&gt; — a lightweight framework for safe AI tool-calling. Also building &lt;a href="https://github.com/ChoukeLee/maison-gourmande" rel="noopener noreferrer"&gt;Maison Gourmande&lt;/a&gt;, an AI restaurant concierge. Based in Abidjan.&lt;/em&gt;&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.amazonaws.com%2Fuploads%2Farticles%2F7lkvjyz2jsar0gknqqil.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.amazonaws.com%2Fuploads%2Farticles%2F7lkvjyz2jsar0gknqqil.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>typescript</category>
      <category>programming</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
