<?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: Adi</title>
    <description>The latest articles on DEV Community by Adi (@adgapar).</description>
    <link>https://dev.to/adgapar</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%2F3305475%2F0791dbb9-7cef-4c1c-8d44-bf56ed362c9b.jpg</url>
      <title>DEV Community: Adi</title>
      <link>https://dev.to/adgapar</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/adgapar"/>
    <language>en</language>
    <item>
      <title>how to do agentic evals</title>
      <dc:creator>Adi</dc:creator>
      <pubDate>Sat, 18 Jul 2026 18:26:14 +0000</pubDate>
      <link>https://dev.to/adgapar/how-to-do-agentic-evals-4mji</link>
      <guid>https://dev.to/adgapar/how-to-do-agentic-evals-4mji</guid>
      <description>&lt;p&gt;When your agent schedules an interview, sends a message to a candidate, or updates an application status, you can't evaluate it the way you test a function. The agent changes the world. Testing whether it changed the world correctly means building a world where it can safely do that.&lt;/p&gt;

&lt;p&gt;Standard evals don't solve this. Static test environments miss how agents behave when conditions shift. Single runs miss non-determinism, even at temperature 0. Single-turn tests miss the steep performance drop when conversations go multi-turn.&lt;/p&gt;

&lt;p&gt;In January, Anthropic published &lt;a href="https://www.anthropic.com/engineering/demystifying-evals-for-ai-agents" rel="noopener noreferrer"&gt;Demystifying Evals for AI Agents&lt;/a&gt;. I like their engineering posts, and this one gives a solid overview: terminology, grader types, an implementation roadmap. This article goes deeper into one corner: building the eval system for conversational agents that take real actions. I &lt;a href="https://theworkingprototype.substack.com/p/build-for-reliability" rel="noopener noreferrer"&gt;wrote about&lt;/a&gt; why model capability doesn't translate to agent reliability. This is about what it takes to get there.&lt;/p&gt;

&lt;h2&gt;
  
  
  evals
&lt;/h2&gt;

&lt;p&gt;When most people say "evals," they mean a unit test for model behavior: give it an input, check the output. The checking can be deterministic: string matching or a rule. Or you can point another LLM, often called a judge, at the output and ask whether it's close enough to the expected answer. Platforms like Langfuse and Pydantic Logfire offer this as a playground: see a completion, tweak the input prompt, compare outputs.&lt;/p&gt;

&lt;p&gt;We run these too. We use them to test safety: send a hundred off-topic or dangerous messages (politics, religion, protected characteristics) and verify the agent redirects every one. We use them to test FAQ accuracy: clients define how the agent should answer specific topics, and the eval tests whether it does across different phrasings. We use them to iterate on prompts: change the prompt, run the same batch, compare what changed in the completions.&lt;/p&gt;

&lt;p&gt;These evals are useful, but they evaluate the surface. &lt;strong&gt;They tell you what the agent said. They don't tell you if it's true.&lt;/strong&gt; An agent can say "your interview is confirmed for tomorrow" and sound perfectly helpful, while no interview was ever scheduled in the database. An agent that takes actions across multiple turns, changes state, and reasons internally about what to do next needs deeper instruments.&lt;/p&gt;

&lt;h2&gt;
  
  
  the harness
&lt;/h2&gt;

&lt;p&gt;To know whether the agent actually scheduled that interview, you need to run it in a world where it can take actions safely. Not a mock, not a stub: a world that behaves like production without being production. &lt;strong&gt;That means the agent must run its full production logic.&lt;/strong&gt; If you mock the agent, you're not testing the agent. If you mock the world too aggressively, you're testing behavior in a world that doesn't exist.&lt;/p&gt;

&lt;p&gt;The agent doesn't need to know it's being tested. As long as the interfaces behave the same way, it doesn't matter what's behind them. A &lt;a href="https://arxiv.org/abs/2602.11224" rel="noopener noreferrer"&gt;recent paper on agent evaluation&lt;/a&gt; calls this the contract principle: correctness is defined by the interface, not the implementation. Swap the providers: messages go to memory instead of the real messaging service, external API calls hit mocks that honor the same contracts, time is injected so the agent perceives the correct historical moment.&lt;/p&gt;

&lt;p&gt;The harness should never run in production, but the data inside it should be as close to production as possible. That can mean exporting real context from production, enough for the agent to behave correctly, with all PII stripped. Or it can mean generating synthetic scenarios that are realistic enough to surface the same failures real users would. Either way, what the agent sees inside the harness should be indistinguishable from what it sees in production. Save this data as a frozen template: the starting scenario, the user, their conversation history, everything the agent needs as context.&lt;/p&gt;

&lt;p&gt;Once you have a template, you need to make sure each run starts clean. &lt;strong&gt;If the agent schedules an interview during one run, the next run can't start with that interview already on the calendar.&lt;/strong&gt; For each eval run, you clone the template into a disposable copy. The agent mutates the clone freely, and the clone is discarded after. Run it ten times, and all ten start from the same state.&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%2Fnnnab95q6pxfigysjh4z.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%2Fnnnab95q6pxfigysjh4z.png" alt=" " width="800" height="515"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If your agent has time-dependent logic, each clone also needs the correct historical moment injected. An agent making scheduling decisions needs to think it's March 3rd at 2pm, not whenever the eval runs. And anything that would reach the outside world, notifications, webhooks, downstream processes, must be suppressed. Otherwise an eval run could send a real message or trigger a real workflow. If you run evals concurrently, each run also needs its own isolated providers so one run's state doesn't leak into another.&lt;/p&gt;

&lt;p&gt;Anthropic's guide describes all of the above in one bullet: "build a robust eval harness." In practice, it's the most engineering-intensive part of the whole system. Once it exists, it unlocks deeper eval modes.&lt;/p&gt;

&lt;h2&gt;
  
  
  replay
&lt;/h2&gt;

&lt;p&gt;The simplest thing you can do with the harness is replay a situation. Take a specific moment, a user, a message, the state around it, freeze it as a template, and run the agent against it. Did it do the right thing?&lt;/p&gt;

&lt;p&gt;Now run it 3, 5, 8 times. Agents are non-deterministic, so running the same situation repeatedly is how you distinguish reliability from luck. Did the agent succeed at least once? That's &lt;a href="mailto:pass@k"&gt;pass@k&lt;/a&gt;. Did it succeed every single time? That's pass^k. &lt;strong&gt;For &lt;a href="https://theworkingprototype.substack.com/p/build-for-reliability" rel="noopener noreferrer"&gt;reliable agents in production&lt;/a&gt;, pass^k is what we should worry about.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I did this when &lt;a href="https://theworkingprototype.substack.com/p/in-character" rel="noopener noreferrer"&gt;testing role coherence under adversarial pressure&lt;/a&gt;: freeze a conversation turn, replay it fifty times, observe the variance. The same principle applies broadly. It works for failures and for happy paths. A passing result that holds across every run is a pattern you can trust. A failure that repeats is a pattern you can fix.&lt;/p&gt;

&lt;p&gt;Replay is a simple and important tool for testing reliability under a specific situation. But real users don't send one message and stop. They have a conversation.&lt;/p&gt;

&lt;h2&gt;
  
  
  simulation
&lt;/h2&gt;

&lt;p&gt;Simulation is a full multi-turn conversation where an LLM plays the user. The simulated user needs a persona, a scenario, and a goal. &lt;a href="https://arxiv.org/abs/2505.06120" rel="noopener noreferrer"&gt;A study from Microsoft and Salesforce Research&lt;/a&gt; tested 15 models across over 200,000 simulated conversations and found that performance drops from around 90% in single-turn to 65% in multi-turn, a 25-point gap consistent from small open-source models to frontier. When LLMs take a wrong turn, they don't recover. Simulation is how you test whether your agent does.&lt;/p&gt;

&lt;p&gt;The simulated user's &lt;strong&gt;persona&lt;/strong&gt; matters as much as the task. &lt;a href="https://arxiv.org/abs/2510.04491" rel="noopener noreferrer"&gt;Researchers at Collinear AI&lt;/a&gt; found 2 to 30% performance degradation when they varied user traits: impatience, incoherence, skepticism. Standard benchmarks use cooperative, patient users, but real users are often neither. An eager user surfaces different failures than a skeptical one.&lt;/p&gt;

&lt;p&gt;Where do the &lt;strong&gt;scenarios&lt;/strong&gt; come from? When I &lt;a href="https://theworkingprototype.substack.com/p/in-character" rel="noopener noreferrer"&gt;tested role coherence&lt;/a&gt; with Promptfoo, the red-teaming tool generated attack scenarios from a description of the agent's purpose. There are many commercial platforms that offer generation of simulation scenarios using your agent's system prompt as input. Frankly, you can achieve more with any reasoning model by providing the full picture: system prompt, tool definitions, domain context, and the specific constraints your agent operates under.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;goal&lt;/strong&gt; of simulation can be structured or open-ended. A simulated user can take the conversation in directions no test script would. &lt;a href="https://arxiv.org/abs/2510.13982" rel="noopener noreferrer"&gt;Fully open-ended simulation is the ideal&lt;/a&gt;, but in practice most systems sit somewhere in between: scenarios are predefined, personas configured in advance, evaluation against an expected outcome. My agents operate in a bounded domain with defined goals and a constrained conversation space. If yours do too, structured simulation with varied personas covers most of what you need to test. What it won't catch is what falls outside your scenario set, and pushing toward more open-ended exploration over time is the right direction.&lt;/p&gt;

&lt;h2&gt;
  
  
  the judge
&lt;/h2&gt;

&lt;p&gt;Just as an agent in production should leave &lt;a href="https://theworkingprototype.substack.com/p/the-legible-agent" rel="noopener noreferrer"&gt;traces you can review&lt;/a&gt;, so should an agent in the harness. Not only the conversation, but how it changed the world and what it was thinking when it did.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Capture the full state before and after each run, and diff them.&lt;/strong&gt; A &lt;a href="https://arxiv.org/abs/2602.11224" rel="noopener noreferrer"&gt;recent paper&lt;/a&gt; calls these state-diff contracts. The diff is your ground truth: what actually changed, and what changed that shouldn't have. Some of this you can verify deterministically, without an LLM: was the status updated, was the event created, does the diff match what was expected? That's the most reliable evaluation you can build.&lt;/p&gt;

&lt;p&gt;For more complex outcomes, you need a judge. The standard approach, LLM-as-a-Judge, gives an LLM the conversation and asks it to score. But that's evaluating the final result without seeing how the agent got there. A &lt;a href="https://arxiv.org/abs/2410.10934" rel="noopener noreferrer"&gt;Meta and KAUST research group&lt;/a&gt; proposed Agent-as-a-Judge: give the evaluator the intermediate reasoning and the state changes, not only the conversation. They found 90% alignment with human consensus, versus 70% without. We do the same. The more context the judge has, the better it evaluates.&lt;/p&gt;

&lt;h2&gt;
  
  
  the flywheel
&lt;/h2&gt;

&lt;p&gt;The judge evaluates a simulation run and reveals a failure. You see the state diff, you see the reasoning. Now what?&lt;/p&gt;

&lt;p&gt;Promote that failure to a template. Trim the conversation to the critical turn, freeze the state. Now you can replay that exact situation with repetitions to confirm whether the failure is a pattern or a one-off. If it's a pattern, you fix it. After you fix it, the template stays. It becomes a regression test: every future change to the agent gets tested against this situation automatically.&lt;/p&gt;

&lt;p&gt;This is how the system compounds: simulation discovers failures, the judge and human review explain them, and replay locks them down.&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%2F71f9tste4wxwu4p7hy88.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%2F71f9tste4wxwu4p7hy88.png" alt=" " width="799" height="388"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Over time, the library of situations your agent must handle grows with every failure you find and every fix you ship. Now you have the tools to ship reliably.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>riding the wave of ai</title>
      <dc:creator>Adi</dc:creator>
      <pubDate>Sat, 18 Jul 2026 18:18:10 +0000</pubDate>
      <link>https://dev.to/adgapar/riding-the-wave-of-ai-303m</link>
      <guid>https://dev.to/adgapar/riding-the-wave-of-ai-303m</guid>
      <description>&lt;p&gt;I remember when coding interviews happened at a whiteboard, no computer, no internet, just a marker and whatever you could hold in your head. When AI tools arrived, using one in an interview was the red flag. Now the red flag is the candidate who doesn't use AI enough. In about two years, the same tool went from forbidden to expected.&lt;/p&gt;

&lt;p&gt;And it only gets faster. Every few months another wave rolls in, a model out of some lab, a tool that does something it couldn't last year, and it resets what counts as normal. You ride it, or you let it break over you. Plenty of people imagine a third option, waiting on the beach with their arms crossed until the water goes calm, but the water never goes calm, and it was never going to wait for them.&lt;/p&gt;

&lt;h2&gt;
  
  
  all in
&lt;/h2&gt;

&lt;p&gt;I've decided to ride it, in the most literal way I have. All of my code is written by AI now (is it mine at this point?), and somewhere along the way I stopped treating that as a threat to be managed and started treating it as leverage to be spent. I know the engineers who went the other way, who made a personality out of dismissing the tools, and the tools got better anyway while they just fell further behind.&lt;/p&gt;

&lt;p&gt;Going all in wasn't one decision; it's one I make again every few weeks. A better model ships, and I rebuild a working agent on top of it instead of staying on the old version, because the result comes out better and cheaper. Relying on older models would have been easier, and most people do. Riding means doing that over and over, long after the novelty wears off.&lt;/p&gt;

&lt;p&gt;I get why people stop trying to keep up. The volume is genuinely insane, more launches in a week than you could try in a month, and it isn't only engineers feeling it now, it's anyone whose work runs on a keyboard. People are overwhelmed and often giving up. But you don't have to keep up with everything; you just can't ignore it all.&lt;/p&gt;

&lt;h2&gt;
  
  
  keep the thinking
&lt;/h2&gt;

&lt;p&gt;It means handing a lot of work to AI, and I hand over more every month. It writes code, drafts the first version of almost everything, reads what I don't have time to read. That part I'm glad to give away.&lt;/p&gt;

&lt;p&gt;The part I won't give away is the thinking. The moment you let the model do your reasoning, make your calls, decide for you what's good, you stop being the one person in the loop who can tell whether it's right. You end up accepting whatever it decides.&lt;/p&gt;

&lt;p&gt;Take the writing. You can write with AI for AI: text that fills the page, looks finished, yet no actual human would want to read it. Or you can use it to write something a person understands, which only works if you understood it yourself first. So optimize for human understanding, both yours and the reader.&lt;/p&gt;

&lt;h2&gt;
  
  
  the catch
&lt;/h2&gt;

&lt;p&gt;Keeping the thinking is the easy part for me, because I already did the work that builds it. I know good from merely plausible because I spent years getting it wrong and seeing why. When the model hands me something confident and wrong, I can challenge it. That knowledge was expensive, and it's the only reason any of this works for me.&lt;/p&gt;

&lt;p&gt;But the work that built that knowledge, the slow and repetitive part, doing it badly until you can tell the difference, is exactly the work the tool now does for people starting out. They get the output without the years of effort. They ship things that look right and feel nothing when they aren't, and it has nothing to do with how smart they are. The same wave that compounds for me, because I bring judgment to it, may quietly keep them from ever building their own.&lt;/p&gt;

&lt;p&gt;The wave is working for me now. I know it eventually takes my whole job, not just the parts I hand it. I ride it anyway, while it still needs me, because betting against it is the worse bet.&lt;/p&gt;

&lt;p&gt;The code stopped being mine a while ago, but the thinking hasn't. I keep doing that part myself even when the tools would let me skip it, the way Mario Zechner argued in &lt;a href="https://mariozechner.at/posts/2026-03-25-thoughts-on-slowing-the-fuck-down/" rel="noopener noreferrer"&gt;slowing the fuck down&lt;/a&gt;. I'll keep riding because I learned how to slow down.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>career</category>
      <category>interview</category>
      <category>programming</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Adi</dc:creator>
      <pubDate>Tue, 01 Jul 2025 12:48:03 +0000</pubDate>
      <link>https://dev.to/adgapar/-10i6</link>
      <guid>https://dev.to/adgapar/-10i6</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/adgapar/a-loop-is-all-you-need-building-conversation-ai-agents-1039" class="crayons-story__hidden-navigation-link"&gt;a loop is all you need: building conversation ai agents&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/adgapar" class="crayons-avatar  crayons-avatar--l  "&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%2Fuser%2Fprofile_image%2F3305475%2F0791dbb9-7cef-4c1c-8d44-bf56ed362c9b.jpg" alt="adgapar profile" class="crayons-avatar__image" width="400" height="400"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/adgapar" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Adi
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Adi
                
              
              &lt;div id="story-author-preview-content-2635787" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/adgapar" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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%2Fuser%2Fprofile_image%2F3305475%2F0791dbb9-7cef-4c1c-8d44-bf56ed362c9b.jpg" class="crayons-avatar__image" alt="" width="400" height="400"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Adi&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/adgapar/a-loop-is-all-you-need-building-conversation-ai-agents-1039" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jun 29 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/adgapar/a-loop-is-all-you-need-building-conversation-ai-agents-1039" id="article-link-2635787"&gt;
          a loop is all you need: building conversation ai agents
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/tutorial"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;tutorial&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/startup"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;startup&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/howto"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;howto&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/adgapar/a-loop-is-all-you-need-building-conversation-ai-agents-1039" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;2&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/adgapar/a-loop-is-all-you-need-building-conversation-ai-agents-1039#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              2&lt;span class="hidden s:inline"&gt;&amp;nbsp;comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            10 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>ai</category>
      <category>tutorial</category>
      <category>startup</category>
      <category>howto</category>
    </item>
    <item>
      <title>coding = writing text: approach it like an essay</title>
      <dc:creator>Adi</dc:creator>
      <pubDate>Sun, 29 Jun 2025 17:27:21 +0000</pubDate>
      <link>https://dev.to/adgapar/coding-writing-text-approach-it-like-an-essay-5830</link>
      <guid>https://dev.to/adgapar/coding-writing-text-approach-it-like-an-essay-5830</guid>
      <description>&lt;p&gt;Every person who writes code believes it is clean. What a fallacy!&lt;/p&gt;

&lt;p&gt;I used to think the same until my colleague Manuel asked me to review his Pull Request (&lt;em&gt;PR&lt;/em&gt;) for a project we were working on. As I reviewed his code, I realized that his code is so much cleaner than mine.&lt;/p&gt;

&lt;p&gt;What made his code cleaner? Uff, I wouldn't be able to tell, but it felt better. It is like reading your own university essay and then reading one by Paul Graham or Malcom Gladwell - you just know there's a difference.&lt;/p&gt;

&lt;p&gt;At first, I thought it was just my lack of experience with JavaScript/TypeScript (&lt;em&gt;as a data scientist I mostly wrote Python before&lt;/em&gt;). But in reality, it was just me not paying enough attention to and not recognizing all patterns of clean code.&lt;/p&gt;

&lt;p&gt;Manuel recommended me a book by Robert C. Martin and I bought it immediately. The book is called &lt;code&gt;The Clean Code: A Handbook of Agile Software Craftsmanship&lt;/code&gt;. It's an extensive and detailed book that you should not read as a novel from start to finish within a day or two. Instead, it serves as a handbook - read a chapter, reflect on it, and ideally apply it in your work. While the code examples are in Java, most of the lessons are universal to other languages.&lt;/p&gt;

&lt;p&gt;In this blog post I won't be summarizing or paraphrasing the entire book. First, honestly I don't think I internalized the entire wisdom written there to be able to share it effectively. Second, I encourage everyone to read the book and draw their own conclusions from original source. &lt;strong&gt;This advice applies to any topic - always go to the original source&lt;/strong&gt;. Finally, the book has so many valuable insights that a single blog post can't capture them all.&lt;/p&gt;

&lt;p&gt;Instead, in this blog, I'll share the first key takeaway I gained after reading and applying some of its principles.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;write code like you're writing an essay, not just a script&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Let me give one example of code and how to rewrite it if you think about the code as an essay. &lt;/p&gt;

&lt;h2&gt;
  
  
  code example
&lt;/h2&gt;

&lt;p&gt;Suppose that we have a pipeline or a workflow that executes a set of checks. First it runs &lt;code&gt;external_check&lt;/code&gt;. If this external check returns &lt;code&gt;APPROVED&lt;/code&gt;, then it proceeds with the second step where the status is determined based on metrics.&lt;/p&gt;

&lt;p&gt;Here is some dummy implementation of this workflow:&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="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;enum&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Enum&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;some_file&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BaseWorkflow&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WorkflowStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Enum&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;CREATED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CREATED&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
  &lt;span class="n"&gt;APPROVED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;APPROVED&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
  &lt;span class="n"&gt;REJECTED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;REJECTED&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
  &lt;span class="n"&gt;PENDING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PENDING&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CheckOutcome&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Enum&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;CREATED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CREATED&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
  &lt;span class="n"&gt;PASSED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PASSED&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
  &lt;span class="n"&gt;FAILED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FAILED&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
  &lt;span class="n"&gt;PENDING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PENDING&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Workflow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseWorkflow&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;external_check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="c1"&gt;# some operation, returns { outcome: CheckOutcome }
&lt;/span&gt;    &lt;span class="k"&gt;pass&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fetch_metrics&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
     &lt;span class="c1"&gt;# Does some operations
&lt;/span&gt;     &lt;span class="c1"&gt;# trigger data processing if it was not triggered
&lt;/span&gt;     &lt;span class="c1"&gt;# convert preprocessed data to metrics
&lt;/span&gt;     &lt;span class="c1"&gt;# returns metrics
&lt;/span&gt;     &lt;span class="k"&gt;pass&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;WorkflowStatus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;check&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;external_check&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;outcome&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;check&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;outcome&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;WorkflowStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;APPROVED&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;WorkflowStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CREATED&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch_metrics&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WorkflowStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PENDING&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="n"&gt;metrics&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch_metrics&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;

  &lt;span class="nd"&gt;@staticmethod&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;outcome&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;CheckOutcome&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;WorkflowStatus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WorkflowStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PENDING&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;outcome&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
       &lt;span class="c1"&gt;# Logic based on outcome
&lt;/span&gt;       &lt;span class="k"&gt;pass&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;metrics&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
       &lt;span class="c1"&gt;# Logic based on metrics
&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  improvement #1:  write from top to bottom
&lt;/h2&gt;

&lt;p&gt;The main method in this class is &lt;code&gt;.run()&lt;/code&gt; which is how this workflow will be used in other codebase. Imagine you see somewhere in the codebase &lt;code&gt;workflow.run()&lt;/code&gt; and you want to know what it does. &lt;/p&gt;

&lt;p&gt;You scroll down to the middle of the file to find &lt;code&gt;.run()&lt;/code&gt; implementation. It references three other methods: &lt;code&gt;.external_check()&lt;/code&gt;, &lt;code&gt;fetch_metrics()&lt;/code&gt; and &lt;code&gt;get_status()&lt;/code&gt;.  So,  you scroll up a bit to check implementation of the first two methods. Where is &lt;code&gt;get_status()&lt;/code&gt;? Ahh you scroll down again, this time to the bottom of the file, to find its implementation.&lt;/p&gt;

&lt;p&gt;Quite a lot of scrolling to follow the code. Imagine reading an essay where the introduction is halfway down the page, the main arguments are at start, and the resolution is in the bottom. We can structure our code better than that. &lt;/p&gt;

&lt;p&gt;How about reorganizing the code so that as you follow it, you only need to scroll down? This way, everything flows naturally, just like reading a well-structured document.&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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Workflow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseWorkflow&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;WorkflowStatus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# calls external_check()
&lt;/span&gt;    &lt;span class="c1"&gt;# calls fetch_metrics()
&lt;/span&gt;    &lt;span class="c1"&gt;# calls get_status()
&lt;/span&gt;    &lt;span class="k"&gt;pass&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;external_check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fetch_metrics&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
     &lt;span class="k"&gt;pass&lt;/span&gt;

  &lt;span class="nd"&gt;@staticmethod&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;outcome&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;CheckOutcome&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;WorkflowStatus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  improvement #2: group into chapters
&lt;/h2&gt;

&lt;p&gt;The code inside &lt;code&gt;.run()&lt;/code&gt; handles several tasks at varying levels of abstraction: it calls an external check, fetches metrics, and derives the status from both. However, at its core, it’s a two-step process. First, it performs an &lt;code&gt;external_check&lt;/code&gt; (&lt;em&gt;Step 1&lt;/em&gt;), and only if the first step is &lt;code&gt;APPROVED&lt;/code&gt; does it proceed with the rest of the code (&lt;em&gt;Step 2&lt;/em&gt;). If this were a book, these would be two distinct chapters: one for each step.&lt;/p&gt;

&lt;p&gt;So, let's break &lt;code&gt;.run()&lt;/code&gt; into two separate methods: &lt;code&gt;execute_first_step()&lt;/code&gt; and &lt;code&gt;execute_second_step()&lt;/code&gt;. This division allows us to enhance each step with more complex logic without altering the original &lt;code&gt;.run()&lt;/code&gt; implementation.&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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Workflow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseWorkflow&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;WorkflowStatus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;first_step_status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute_first_step&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;first_step_status&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;WorkflowStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;APPROVED&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;first_step_status&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;

    &lt;span class="n"&gt;second_step_status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute_second_step&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;second_step_status&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;execute_first_step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;WorkflowStatus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;check&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;external_check&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;outcome&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;check&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;outcome&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;execute_second_step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;WorkflowStatus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;WorkflowStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CREATED&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch_metrics&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WorkflowStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PENDING&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="n"&gt;metrics&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch_metrics&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;external_check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fetch_metrics&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
     &lt;span class="k"&gt;pass&lt;/span&gt;

  &lt;span class="nd"&gt;@staticmethod&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;outcome&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;CheckOutcome&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;WorkflowStatus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  improvement #3:  have intentional naming
&lt;/h2&gt;

&lt;p&gt;Now let's take a closer look at the other methods: &lt;code&gt;get_status()&lt;/code&gt; and &lt;code&gt;fetch_metrics()&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;The &lt;code&gt;get_status()&lt;/code&gt; method's name suggests it returns a &lt;code&gt;status&lt;/code&gt;, which is clear and straightforward. However, we notice that &lt;code&gt;get_status()&lt;/code&gt; takes in two unrelated arguments: &lt;code&gt;outcome&lt;/code&gt; and &lt;code&gt;metrics&lt;/code&gt;. To understand what this method does, how it uses each argument, and what arguments it actually accepts, you'll need to dive into its implementation—especially if you plan to use it elsewhere.&lt;/p&gt;

&lt;p&gt;To make the code more intuitive, let's improve the naming and split it into two methods: &lt;code&gt;get_status_from_outcome()&lt;/code&gt; and &lt;code&gt;get_status_from_metrics()&lt;/code&gt;. This not only simplifies the functions and ensures each does only one thing, but also makes it clear what each method accepts and returns, just from their names:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;get_status_from_outcome&lt;/code&gt;: accepts outcome and returns status&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;get_status_from_metrics&lt;/code&gt;: accepts metrics and returns status&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Otherwise, instead of creating &lt;code&gt;get_status_from_outcome&lt;/code&gt;, we could also consider &lt;code&gt;get_status_from_check&lt;/code&gt; which would allow us to pass the entire &lt;code&gt;check&lt;/code&gt; variable directly, rather than just check['outcome']. &lt;/p&gt;

&lt;p&gt;Now let's take a closer look at &lt;code&gt;execute_second_step()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This method uses &lt;code&gt;fetch_metrics&lt;/code&gt;. From the name, you'd expect it to simply fetch metrics from somewhere. When the current status isn't &lt;code&gt;CREATED&lt;/code&gt;, the method is used exactly that way: it fetches metrics, stores them in a &lt;code&gt;metrics&lt;/code&gt; variable, and then the &lt;code&gt;status&lt;/code&gt; is determined from these &lt;code&gt;metrics&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;However, when the status is &lt;code&gt;CREATED&lt;/code&gt;, the function is called without saving its output. This suggests that &lt;code&gt;fetch_metrics&lt;/code&gt; is doing something more than just fetching metrics, contrary to what the name implies.&lt;/p&gt;

&lt;p&gt;To address this confusion, we need to dive into the implementation of &lt;code&gt;fetch_metrics&lt;/code&gt;. Upon inspection, we find that it actually performs two tasks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It triggers a data pre-processing job if it hasn't been triggered already.&lt;/li&gt;
&lt;li&gt;It fetches the pre-processed data and converts it into metrics.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The solution is to refactor fetch_metrics so that the data pre-processing logic is separated into its own function, e.g. &lt;code&gt;trigger_data_preprocessing()&lt;/code&gt;. This will make the code more understandable and prevent any misconceptions about what &lt;code&gt;fetch_metrics&lt;/code&gt; is doing.&lt;/p&gt;

&lt;p&gt;Ultimately, the names of functions and variables should reflect their purpose and intention clearly. By choosing meaningful names, you're essentially translating your plan—"first, I will trigger data processing, then I will fetch metrics, and finally, I will derive the status from these metrics"—into the code itself. The specific details, such as how processing or fetching is implemented, become secondary and abstracted away. &lt;/p&gt;




&lt;p&gt;To conclude, the core plan remains clear and easy to follow, ensuring that anyone reading the code understands the overall structure and flow at a glance, much like skimming a book by chapters and then diving into each chapter by sections.&lt;/p&gt;

&lt;p&gt;Does it look better? What else can be improved?&lt;/p&gt;

</description>
      <category>programming</category>
      <category>beginners</category>
      <category>python</category>
      <category>coding</category>
    </item>
    <item>
      <title>a loop is all you need: building conversation ai agents</title>
      <dc:creator>Adi</dc:creator>
      <pubDate>Sun, 29 Jun 2025 16:52:44 +0000</pubDate>
      <link>https://dev.to/adgapar/a-loop-is-all-you-need-building-conversation-ai-agents-1039</link>
      <guid>https://dev.to/adgapar/a-loop-is-all-you-need-building-conversation-ai-agents-1039</guid>
      <description>&lt;p&gt;After months of building AI agents, I've come to a counterintuitive conclusion: those fancy agent frameworks everyone seems to be using? You probably don't need them.&lt;/p&gt;

&lt;p&gt;Let me explain how I got here.&lt;/p&gt;

&lt;h2&gt;
  
  
  essential complexity is not in tech
&lt;/h2&gt;

&lt;p&gt;A few months ago, I joined an AI startup as founding engineer.&lt;/p&gt;

&lt;p&gt;Coming from years of doing data science, I thought I knew what I was getting into. I'd worked with libraries like LangChain, CrewAI, AutoGen, Pydantic AI and taught these agent frameworks at AI engineering bootcamps. I had notebooks full of multi-agent systems, chain-of-thought prompting experiments, and RAG pipelines.&lt;/p&gt;

&lt;p&gt;Instead, I found myself reading "Conversations with Things: UX Design for Chat and Voice" by Diana Deibel and Rebecca Evanhoe, published in 2021, long before ChatGPT and modern AI wave.&lt;/p&gt;

&lt;p&gt;The book opened my eyes to something I'd been missing: &lt;strong&gt;building conversational AI agents isn't primarily a technical challenge. It's a conversation design challenge. And conversations are messy, cultural, and deeply human.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  turn-taking is difficult
&lt;/h2&gt;

&lt;p&gt;The first thing that hit me was how bad we developers are at teaching our systems the most basic human skill: knowing when to let the AI speak and when to wait.&lt;/p&gt;

&lt;p&gt;In AI there's this concept called "turn-taking" - the invisible dance of who speaks when. Humans are masters at this. We pick up on tiny cues: a slight intake of breath, a change in posture, the way someone's voice drops at the end of a thought. We know instinctively when it's our turn.&lt;/p&gt;

&lt;p&gt;But when you're building an agent, you have to explicitly program these decisions: has the user finished their thought? Should the system respond now or wait for more input?&lt;/p&gt;

&lt;h3&gt;
  
  
  the messaging problem
&lt;/h3&gt;

&lt;p&gt;Modern messaging systems introduces its own weird dynamics. You know that typing indicator - "Adilet is typing..."? That little social contract that says "hold on, I'm not done." But AI agents interact through APIs. They can't see when someone's typing, and they don't trigger the typing indicator themselves. So you get situations like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User: Hi I have a question
User: It's about my order  
User: The one from last week
AI: [Already responding to the first message]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Some users write novels in single messages. Others send thoughts like morse code - tap, tap, tap. After weeks of experimentation, I still don't have a perfect solution. Do you wait a few seconds after each message to see if more are coming? Do you analyze message length patterns? Do you look for linguistic cues that suggest completion?&lt;/p&gt;

&lt;h3&gt;
  
  
  cultural turn-taking
&lt;/h3&gt;

&lt;p&gt;I noticed something fascinating from my own multilingual background. Growing up in Kazakhstan, but then studying and working abroad, I've experienced how differently cultures handle conversational pauses.&lt;/p&gt;

&lt;p&gt;In Spanish conversations, people overlap constantly - it's not rude, it's engaged. In Kazakhstan, those longer pauses aren't awkward: they're respectful. Now imagine programming a system to handle both styles. A 3-second pause might signal "I'm done" in one culture and "I'm thinking" in another.&lt;/p&gt;

&lt;p&gt;The best solution so far I've found combines multiple strategies:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Looking for linguistic completion markers&lt;/li&gt;
&lt;li&gt;Waiting fixed amount of time before replying&lt;/li&gt;
&lt;li&gt;When it inevitably responds too early, to handle interruptions gracefully&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This focus on handling mistakes gracefully brings me to the most important lesson in building conversations AI agents.&lt;/p&gt;

&lt;h2&gt;
  
  
  the art of failing gracefully
&lt;/h2&gt;

&lt;p&gt;"Conversations with Things" introduced me to the concept of "repair" or what I call recovery. Human conversations go off the rails constantly, but we fix them together almost unconsciously. We clarify, we backtrack, we laugh it off.&lt;/p&gt;

&lt;p&gt;Your AI will fail. Not occasionally, but regularly. The question isn't how to prevent failures; it's how to recover from them.&lt;/p&gt;

&lt;p&gt;Let me give you a theoretical example. Imagine an AI agent for a food delivery service. A customer says they ordered "the usual" but the system has no record of previous orders (for whatever reason). The agent could crash and burn with "I don't understand 'the usual'" or it could recover gracefully: "I'd love to get you your usual! Could you remind me what that is? I want to make sure I get your order exactly right."&lt;/p&gt;

&lt;p&gt;The difference? One leaves the customer frustrated (and makes AI agent seem stupid), the other makes them feel heard while smoothly getting the information needed.&lt;/p&gt;

&lt;p&gt;💡 People are surprisingly forgiving of AI mistakes if you handle the recovery well.&lt;/p&gt;

&lt;p&gt;This isn't just about having a few canned "sorry" responses. It's about building recovery into every interaction:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Acknowledgment: Don't pretend the error didn't happen&lt;/li&gt;
&lt;li&gt;Empathy: Show you understand the inconvenience&lt;/li&gt;
&lt;li&gt;Humor (when appropriate): A little self-deprecation goes a long way&lt;/li&gt;
&lt;li&gt;Action: Actually fix the problem or offer an alternative&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once you've mastered recovery, you need to think about who exactly is doing the recovering - and that's where personality comes in.&lt;/p&gt;

&lt;h2&gt;
  
  
  well-defined personality isn't optional
&lt;/h2&gt;

&lt;p&gt;Building a conversational AI agent is not just merely using LLMs to generate correct responses. We all can notice the difference when we interact with Amazon Alexa, Apple Siri, ChatGPT, Yandex Alice (or even Claude Chat / Mistral LeChat – anyone?), but often we cannot explicitly tell what actual differences are. Yet, we all perceive that these assistants have different personalities, which are accurately constructed by developers and designers that make us, humans, feel in an unique way.&lt;/p&gt;

&lt;p&gt;People infer personality from the set of things: word choices, voice (sound) and behavior (more about this in the next section). Here's something that surprised me: the personality you give your AI agent fundamentally changes how users interact with it. This isn't about making your bot "fun" or "quirky." It's about creating consistent, predictable interactions that users can adapt to.&lt;/p&gt;

&lt;p&gt;This is called "accommodation" or "mirroring" - how people naturally adjust their communication style to match their conversation partner. I noticed users actually start mirroring the agent's communication style. Formal agents get formal responses. Friendly agents get friendly responses. It's like watching a dance where one partner subtly leads and the other follows.&lt;/p&gt;

&lt;h2&gt;
  
  
  behavior and intents are the key
&lt;/h2&gt;

&lt;p&gt;The trick isn't writing vague instructions like "be friendly and professional." It's defining specific behaviors for specific situations, and from these behaviors emerge the intents your system needs to handle.&lt;/p&gt;

&lt;p&gt;But first, what are intents? In conversational AI, an intent represents what the user wants to accomplish or what action the system should take. Instead of trying to handle infinite variations of user input, you define specific set of intents that capture the core actions your agent can perform.&lt;/p&gt;

&lt;h3&gt;
  
  
  from vague prompts to specific intents
&lt;/h3&gt;

&lt;p&gt;Here's what many developers do wrong. They write prompts like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You are a helpful customer support agent. Be empathetic and professional.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is too vague. What does "empathetic" mean in practice? Instead, define specific behaviors that create empathy.&lt;br&gt;
For example, when you want the agent to be "empathetic," what you actually want is the behavior of "always acknowledge the customer's emotion before providing solutions."&lt;/p&gt;

&lt;p&gt;This behavior naturally leads to creating intents like&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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AcknowledgeEmotion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Recognize and validate customer feelings&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;emotion_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Literal&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;frustrated&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;confused&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;happy&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;angry&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;intensity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Literal&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mild&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;moderate&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;severe&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RequestClarification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Ask for more details when the issue isn&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t clear&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;unclear_aspect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;suggested_questions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These intents emerged from defining how the agent should behave in emotional situations. The desired behavior drives the technical implementation, not the other way around.&lt;/p&gt;

&lt;h3&gt;
  
  
  conditional intents
&lt;/h3&gt;

&lt;p&gt;Intents can also be conditionally available based on context. For a customer support agent that handles both free and paid users, we can add intent of &lt;code&gt;RedirectToHuman&lt;/code&gt; for paid users and &lt;code&gt;CannotRedirectToHuman&lt;/code&gt; intent for free users.&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;account_type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;paid&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;intents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RedirectToHuman&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;intents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CannotRedirectToHuman&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This prevents the agent from promising human support to someone who hasn't bought anything yet, while still letting it explain why it can't help with certain requests.&lt;/p&gt;

&lt;h3&gt;
  
  
  useful intents to consider
&lt;/h3&gt;

&lt;p&gt;Besides adding intents based on condition and context, there are some intents that you might consider adding always.&lt;/p&gt;

&lt;p&gt;One useful intent might be to handle off-topics:&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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RedirectOffTopic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Handle conversations that drift from support issues&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;topic_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Literal&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;personal_chat&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;competitor_questions&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;philosophical&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;redirect_strategy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Literal&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gentle_reminder&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;humor&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;firm_boundary&lt;/span&gt;&lt;span class="sh"&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 came from the behavior of maintaining professional boundaries while keeping customers engaged. The &lt;code&gt;humor&lt;/code&gt; strategy is particularly effective - when someone's clearly testing the system with "What's the meaning of life?", a playful "That's above my pay grade! But I'm great at tracking orders 😊" works much better than a robotic rejection "I cannot talk about this, I can only talk about support" from LLM guardrails system.&lt;/p&gt;

&lt;p&gt;Another useful intent might be needed to handle cases where agent doesn't know or cannot provide the answer. This intent will minimize hallucinations better than adding phrases like "Never invent information that is not in your context" to your prompt.&lt;/p&gt;

&lt;p&gt;Now that we understand intents and behaviors, let's see how to implement them without complex frameworks.&lt;/p&gt;

&lt;h2&gt;
  
  
  understanding agents from first principles
&lt;/h2&gt;

&lt;p&gt;Here's where I probably lose half of you: after all this work, I built everything from scratch using just Python and the OpenAI SDK. No LangChain, no CrewAI, no new fancy frameworks.&lt;/p&gt;

&lt;p&gt;I've been heavily influenced by &lt;a href="https://www.anthropic.com/news/building-effective-agents?ref=adgapar.dev" rel="noopener noreferrer"&gt;Anthropic's definition of agents&lt;/a&gt; and &lt;a href="https://github.com/humanlayer/12-factor-agents/tree/main?ref=adgapar.dev" rel="noopener noreferrer"&gt;HumanLayer's 12-factor agent principles&lt;/a&gt;. Both essentially say the same thing: &lt;strong&gt;agents are just models using tools in a loop.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Anthropic's definition:&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;env&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;tools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Tools&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;system_prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Goals, constraints, and how to act&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;system_prompt&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;HumanLayer's definition:&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;initial_event&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;message&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="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;initial_event&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;next_step&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;determine_next_step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;next_step&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;next_step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;intent&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;done&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;next_step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;final_answer&lt;/span&gt;

    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;execute_step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;next_step&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is the same pattern: &lt;strong&gt;observe context, decide action, execute, update state, repeat&lt;/strong&gt;. That's it. Everything else is implementation details.&lt;/p&gt;

&lt;h3&gt;
  
  
  back to basics
&lt;/h3&gt;

&lt;p&gt;There's something beautifully ironic about this journey. When I taught Python for data science at bootcamps, one of the first concepts was loops:&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="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Simple, right? But then when you get to data analysis (&lt;em&gt;pandas, numpy&lt;/em&gt;), you spend weeks learning to avoid loops. "Never iterate over DataFrame rows!" we'd say. "Use vectorized operations!"&lt;/p&gt;

&lt;p&gt;Now with AI agents, we're back to the most basic pattern:&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="k"&gt;while&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;done&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;observe&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;think&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;act&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sometimes the oldest patterns are the best patterns. There's a certain poetry to it - the most advanced AI technology we have, operating on the most fundamental programming construct.&lt;/p&gt;

&lt;h3&gt;
  
  
  why not frameworks?
&lt;/h3&gt;

&lt;p&gt;When you use a framework to build an agent, you're adding layers of abstraction over this simple loop. These frameworks are built for maximum flexibility - they need to handle every possible use case from chatbots to autonomous research agents.&lt;br&gt;
But when you only have a specific use case and specific needs, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fine-grained monitoring of every decision&lt;/li&gt;
&lt;li&gt;Precise conversation flow control&lt;/li&gt;
&lt;li&gt;Custom error handling for conversation-specific edge cases&lt;/li&gt;
&lt;li&gt;Integration with your specific communication channels and your particular tech stack&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;... those abstractions become obstacles. You spend more time fighting the framework than solving actual problem. &lt;/p&gt;

&lt;p&gt;According to HumanLayer's research, most companies building production agents end up rolling their own. After trying both approaches, I understand why. &lt;strong&gt;The complexity isn't in the agent loop - it's in your conversation design, intent and error handling, and integration needs&lt;/strong&gt;. Frameworks can't abstract those away.&lt;/p&gt;
&lt;h2&gt;
  
  
  customer support agent using first principles
&lt;/h2&gt;

&lt;p&gt;We have been talking about the customer support agent as an example previously. &lt;/p&gt;

&lt;p&gt;From the first principles and conversation design ideas, here is the draft implementation:&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="k"&gt;continue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;intents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;determine_intents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;intents&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;process_intents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;intents&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_final&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;continue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;execute_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  example flows
&lt;/h3&gt;

&lt;p&gt;Let's walk through two scenarios to see how this works:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Simple FAQ example - "How to cancel my subscription?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In customer support there is always a set of questions that users ask the most, called FAQs. We can preload the FAQs into LLM prompt, so that agent can respond immediately.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Agent determines intent: &lt;code&gt;ReplyToUser&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Processes this intent which results in action of sending message. Action of sending message is final, so the loop will end after execution&lt;/li&gt;
&lt;li&gt;Executes the action by sending this message via API&lt;/li&gt;
&lt;li&gt;Updates context (conversational history) with this message&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Complex example - "When my subscription renews?":&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Agent determines intent: &lt;code&gt;FetchUserSubscriptionData&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Executes fetch for subscription data&lt;/li&gt;
&lt;li&gt;Updates context with user's renewal date&lt;/li&gt;
&lt;li&gt;Loop continues with context now containing user data&lt;/li&gt;
&lt;li&gt;Agent determines intent: &lt;code&gt;ReplyToUser&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Generates and sends personalized response with exact date&lt;/li&gt;
&lt;li&gt;Marks action as final, loop ends&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The beauty is that more complex scenarios just mean more loop iterations, not fundamentally different code.&lt;/p&gt;

&lt;h2&gt;
  
  
  context is everything
&lt;/h2&gt;

&lt;p&gt;The final piece in the conversational AI agent development is &lt;strong&gt;context engineering&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;This term, coined by Dex at HumanLayer, has gained traction recently with endorsements from &lt;a href="https://x.com/tobi/status/1935533422589399127?ref=adgapar.dev" rel="noopener noreferrer"&gt;Shopify CEO Tobi Lütke&lt;/a&gt; and even &lt;a href="https://x.com/karpathy/status/1937902205765607626?ref=adgapar.dev" rel="noopener noreferrer"&gt;Andrej Karpathy&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;The same user message means completely different things depending on context.&lt;/p&gt;

&lt;p&gt;User: "Yes". &lt;br&gt;
Without context, this is meaningless. With context:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;After "Would you like to see our menu?" → User wants to browse options&lt;/li&gt;
&lt;li&gt;After "Is this your current address?" → User is confirming information&lt;/li&gt;
&lt;li&gt;After "Should I cancel your order?" → User wants to cancel&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Consider the following scenario where context engineering is more than just prompt engineering:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Someone from the customer support team tries to call the user about the issue, but the call fails. We can log that event and add it to our customer support agent's context.&lt;/li&gt;
&lt;li&gt;Then our customer support agent sees this within own context and naturally acknowledges it with proper intent: "Hey, it seems like we tried calling you earlier but couldn't reach you. Is now a better time to call?". &lt;/li&gt;
&lt;li&gt;After user confirmation, the human from the customer support team calls again and all conversation is transcribed and saved. &lt;/li&gt;
&lt;li&gt;If we pass this transcript to customer support agent's context, then the agent can say things like "As you discussed on the phone yesterday with ..."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Crafting the context like that makes the experience feel cohesive. &lt;/p&gt;

&lt;h2&gt;
  
  
  key takeaways
&lt;/h2&gt;

&lt;p&gt;I'm still early in this journey. Every day brings new edge cases, new failures to recover from, new patterns to recognize. But here is my learnings so far:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Conversation design &amp;gt; technical complexity: Understanding turn-taking, cultural differences, and recovery strategies matters more than the latest framework&lt;/li&gt;
&lt;li&gt;Recovery &amp;gt; perfection: Users forgive mistakes if you handle them gracefully. Build recovery into every interaction.&lt;/li&gt;
&lt;li&gt;Context &amp;gt; features: A simple agent with rich context beats a complex one without it&lt;/li&gt;
&lt;li&gt;Simple loops &amp;gt; complex frameworks: Most production agents are just while loops with good intent handling&lt;/li&gt;
&lt;li&gt;Behavior drives implementation: Define specific behaviors first, then derive the technical intents from them&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The future of conversational AI isn't in complex frameworks or ever-larger models. It's in understanding the fundamentals of conversation, context, and human interaction. It's in building systems that fail gracefully, adapt to their users, and remember that at the end of the day, they're having a conversation with a human who just wants to be understood.&lt;br&gt;
And sometimes, that's as simple as a while loop that knows when to listen.&lt;/p&gt;




&lt;p&gt;These ideas are based on personal experience building production agents. All examples are illustrative, and any views are my own—not those of my employer. If you're building conversational AI, I'd love to hear your thoughts. Are you team framework or team from-scratch?&lt;/p&gt;

</description>
      <category>ai</category>
      <category>tutorial</category>
      <category>startup</category>
      <category>howto</category>
    </item>
  </channel>
</rss>
