<?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: Antonio Lopes Correia</title>
    <description>The latest articles on DEV Community by Antonio Lopes Correia (@tonal).</description>
    <link>https://dev.to/tonal</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%2F4090943%2F9daabc01-962d-4ff4-aa2b-756ad13e5fcd.jpg</url>
      <title>DEV Community: Antonio Lopes Correia</title>
      <link>https://dev.to/tonal</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tonal"/>
    <language>en</language>
    <item>
      <title>An Agent on a Leash, or why my AI agent doesn't make business decisions</title>
      <dc:creator>Antonio Lopes Correia</dc:creator>
      <pubDate>Tue, 25 Aug 2026 23:34:33 +0000</pubDate>
      <link>https://dev.to/tonal/an-agent-on-a-leash-or-why-my-ai-agent-doesnt-make-business-decisions-1o1</link>
      <guid>https://dev.to/tonal/an-agent-on-a-leash-or-why-my-ai-agent-doesnt-make-business-decisions-1o1</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This post kicks off an ongoing experiment: building an LLM-powered support agent you can actually trust, one decision at a time. Everything described here ships in the companion repo &lt;a href="https://github.com/antoniolopescorreia/reliable-ai-support" rel="noopener noreferrer"&gt;&lt;code&gt;reliable-ai-support&lt;/code&gt;&lt;/a&gt; (&lt;a href="https://github.com/antoniolopescorreia/reliable-ai-support/tree/post-001" rel="noopener noreferrer"&gt;code as of this post: tag &lt;code&gt;post-001&lt;/code&gt;&lt;/a&gt;), which grows as the series does.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The Support-Agent Problem
&lt;/h2&gt;

&lt;p&gt;Picture this: It's 2 AM. A customer messages our support channel, frustrated because their recent order never arrived. They want a refund, or at minimum, an explanation. They've been emailing back and forth for three days with no resolution.&lt;/p&gt;

&lt;p&gt;A support agent is assigned. But it's 2 AM. There's no one on call. So... does the AI just handle it?&lt;/p&gt;

&lt;p&gt;That's the question I couldn't let go of — the one this whole series exists to answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Great AI Illusion
&lt;/h2&gt;

&lt;p&gt;The fantasy is seductive. Build an LLM-powered agent, give it access to the right tools, and let it handle issues end-to-end. It markets itself: "AI-powered 24/7 customer support!" Users love it. Everyone wins.&lt;/p&gt;

&lt;p&gt;Except... what happens when the AI decides to process a refund to the wrong person? What happens when it accesses data it shouldn't? What happens when it "hallucinates" an order ID and tries to charge it?&lt;/p&gt;

&lt;p&gt;The first thing I had to accept when designing this system: an LLM's confidence is not a reliability metric. It's a correlation metric. And correlation alone isn't enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Boundary Problem
&lt;/h2&gt;

&lt;p&gt;LLMs are great at understanding intent, surfacing information, writing responses. The problem is what happens when you hand them the keys to the kingdom.&lt;/p&gt;

&lt;p&gt;So I built a table. A boundary that says "up to here, and no further."&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Decision Type&lt;/th&gt;
&lt;th&gt;Who Decides&lt;/th&gt;
&lt;th&gt;The Reasoning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Intent interpretation&lt;/td&gt;
&lt;td&gt;The AI&lt;/td&gt;
&lt;td&gt;Translating "I want a refund" into a structured request the system can work with&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Refund eligibility&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Software&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Business rules: "Was the order delivered? Was it paid for? Is it within the return window?" — these are yes/no facts, not opinions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Retrieval targets&lt;/td&gt;
&lt;td&gt;The AI&lt;/td&gt;
&lt;td&gt;Finding the right knowledge base article, the right order, the right customer history. (The tools it searches &lt;em&gt;with&lt;/em&gt; are plain software — the AI picks targets, software owns the tooling)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Refund execution&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Software&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Actually moving money, calling the payment gateway, updating inventory — this has real financial consequences&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Policy exceptions&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Software&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;"Can we make an exception?" is a question for a rule engine, not for the AI to guess at&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The AI interprets intent. The software enforces policy. That's the split, and it's the thesis of everything that follows.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Pipeline
&lt;/h2&gt;

&lt;p&gt;If you remember one picture from this series, make it this one:&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
    A[User input] --&amp;gt; B[Intent classification&amp;lt;br/&amp;gt;AI]
    B --&amp;gt; C{Deterministic validation&amp;lt;br/&amp;gt;software}
    C --&amp;gt;|low / medium risk| D[AI-assisted action]
    C --&amp;gt;|high risk| E[Human approval&amp;lt;br/&amp;gt;with audit trail]
    C --&amp;gt;|very high risk| F[Proposed only —&amp;lt;br/&amp;gt;human executes manually]
    D --&amp;gt; G[Execution or rollback]
    E --&amp;gt; G
    F --&amp;gt; H[Manual execution]&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Every action passes through the deterministic gate first. What happens after the gate depends on the risk tier: low and medium risk actions proceed with AI assistance, high risk actions pause for human approval with a full audit trail, and very high risk actions are never executed by the system at all — the AI proposes, a human does the actual work. For now, remember just this: &lt;strong&gt;nothing executes without passing deterministic validation.&lt;/strong&gt; No "let me just try it and see" energy allowed.&lt;/p&gt;

&lt;p&gt;The gate either opens or closes based on hard-coded rules. The AI never holds the key.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Autonomy Budget
&lt;/h2&gt;

&lt;p&gt;I like to think of this as a budget. You have a certain amount of "AI agency" to spend, and once it's spent, the human takes over.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Low risk&lt;/strong&gt;: AI proposes, human disposes. Example: summarizing a support ticket, generating a response draft, highlighting relevant knowledge base articles. The AI does the heavy lifting, the human signs off.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Medium risk&lt;/strong&gt;: AI routes, human confirms. Example: directing a user to the right self-service option, suggesting the right KB article, classifying the ticket type.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;High risk&lt;/strong&gt;: AI proposes action with audit trail, human approves. Example: processing a refund, updating a user's permissions, modifying an order. Money or critical data is involved.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Very high risk&lt;/strong&gt;: AI never executes. Proposes only. Human performs action manually. Example: canceling a subscription, changing a user's email address, deleting data. The AI can &lt;em&gt;suggest&lt;/em&gt; what to do, but the actual action? That's on a human. Period. No exceptions.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The higher the risk, the more hands are in the cookie jar. And that's exactly how it should be.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Honest Trade-offs
&lt;/h2&gt;

&lt;p&gt;This design isn't free, and pretending otherwise would make this a sales pitch instead of engineering notes.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Latency:&lt;/strong&gt; high-risk actions now wait for a human. A refund that an agent could have executed in three seconds takes until someone reviews the queue. That's a real UX cost, and I pay it on purpose.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Engineering effort:&lt;/strong&gt; rule engines, approval queues, audit trails — all of this is code that "just let the AI decide" wouldn't need. The upfront cost is real.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rules can't capture everything:&lt;/strong&gt; deterministic rules are only as good as the policy they encode. Edge cases ("the customer paid twice by mistake") don't fit neatly into yes/no checks yet.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What would change my mind? If eval suites get good enough that an AI's judgment on specific action classes is measurably more accurate than the rules — with error rates we can pin and monitor — then some of these gates could safely open. And building that evidence is exactly what I want this series to be about.&lt;/p&gt;

&lt;h2&gt;
  
  
  Back to 2 AM
&lt;/h2&gt;

&lt;p&gt;So what actually happens to our insomniac customer now?&lt;/p&gt;

&lt;p&gt;Their message hits the pipeline: intent classification understands "refund for undelivered order." The deterministic gate runs &lt;code&gt;RefundEligibility.evaluate()&lt;/code&gt; — order not delivered, refund eligible. The system drafts a response, proposes the refund, files it in the approval queue with a full audit trail. At 8 AM, a human opens the queue, sees the proposal with every fact attached, and clicks approve. The customer gets their money back before their first coffee.&lt;/p&gt;

&lt;p&gt;No one decided anything at 2 AM. The system &lt;em&gt;prepared&lt;/em&gt; everything; a human &lt;em&gt;decided&lt;/em&gt; something at 8. That gap between prepare and decide is the whole architecture.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Matters to You
&lt;/h2&gt;

&lt;p&gt;If you're building AI-powered systems, you've probably felt the tension between "move fast and break things" and "don't break customers' lives." This tension doesn't go away just because you're using fancier models.&lt;/p&gt;

&lt;p&gt;The deterministic/non-deterministic split is where this series starts. As the system grows, other tensions will demand their own answers: what the agent's mistakes cost, how you test behavior that's different every run, which failures you design for versus prevent. Every trade-off gets documented — ADRs included — so you can watch the thinking evolve.&lt;/p&gt;

&lt;p&gt;The autonomy budget translates directly beyond customer service bots: healthcare triage must route critical cases to physicians, financial chatbots can't move money without verification, autonomous vehicle perception must never execute without deterministic gatekeeping. The same pattern — AI interprets intent, software enforces policy — shows up wherever probabilistic intelligence meets real consequences.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Boundary in Code: A First Look
&lt;/h2&gt;

&lt;p&gt;The split between AI and software becomes concrete the moment you actually need to check whether a refund can happen. Here's the shape of the class that does it — a plain Java class living in the &lt;code&gt;domain&lt;/code&gt; package, whose entire job is to answer one yes/no question: &lt;em&gt;can this order be refunded under policy?&lt;/em&gt; Notice what's absent: no LLM call, no prompt, no model config. Just facts in, verdict out.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// dev/tonal/support/domain/RefundEligibility.java&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;EvaluationResult&lt;/span&gt; &lt;span class="nf"&gt;evaluate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Order&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;RefundRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Deterministic business rules — no AI involved&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;delivered&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;EvaluationResult&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;notEligible&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Order must be delivered before refund"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;paid&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getAgeInDays&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;RETURN_WINDOW_DAYS&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Eligible ≠ executed: actually issuing the refund goes through&lt;/span&gt;
    &lt;span class="c1"&gt;// a separate, risk-tiered authorization gate.&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;EvaluationResult&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;eligible&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getId&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getCustomerId&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things worth noticing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;eligible(...)&lt;/code&gt; is not an instruction to move money.&lt;/strong&gt; It's a fact about policy. That comment is the whole thesis in two words: eligible ≠ executed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Same input, same verdict — always.&lt;/strong&gt; The full class ships with unit tests that pin down this boundary (undelivered orders can never pass, eligible orders always pass), and it compiles and passes with no model configured. Try getting that guarantee from a prompt.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The complete implementation — records, all rules, and the test suite that proves the determinism claim — lives in the &lt;a href="https://github.com/antoniolopescorreia/reliable-ai-support" rel="noopener noreferrer"&gt;companion repo&lt;/a&gt;. For now, hold onto the shape: this is what "software enforces policy" looks like when it's not a slide.&lt;/p&gt;

</description>
      <category>java</category>
      <category>ai</category>
      <category>llm</category>
      <category>agents</category>
    </item>
  </channel>
</rss>
