<?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: Shivansh Karan</title>
    <description>The latest articles on DEV Community by Shivansh Karan (@spacetesla).</description>
    <link>https://dev.to/spacetesla</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%2F1912800%2F21e81b1b-9506-4704-84b4-7895a7916c1b.jpeg</url>
      <title>DEV Community: Shivansh Karan</title>
      <link>https://dev.to/spacetesla</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/spacetesla"/>
    <language>en</language>
    <item>
      <title>What Actually Makes a Chatbot an Agent?</title>
      <dc:creator>Shivansh Karan</dc:creator>
      <pubDate>Fri, 17 Jul 2026 03:39:00 +0000</pubDate>
      <link>https://dev.to/spacetesla/what-actually-makes-a-chatbot-an-agent-47ik</link>
      <guid>https://dev.to/spacetesla/what-actually-makes-a-chatbot-an-agent-47ik</guid>
      <description>&lt;h3&gt;
  
  
  About this series
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Agentic AI from First Principles&lt;/em&gt; is a hands-on series where we build an AI agent from scratch, without relying on frameworks. Instead of starting with abstractions, every abstraction appears only after we've hit the problem it solves.&lt;/p&gt;

&lt;p&gt;If you're joining from the beginning, welcome. If not, you can find the previous chapters in the series here.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;Everyone seems to be building AI agents these days.&lt;/p&gt;

&lt;p&gt;But nobody seems to agree on what an agent actually &lt;em&gt;is&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Ask five engineers and you'll get five answers: "an LLM with tools," "a loop," "something with memory," "basically LangChain." All half right. None of them satisfying.&lt;/p&gt;

&lt;p&gt;So instead of starting with a definition, I decided to start with the dumbest possible thing I could build, and see what I ran into.&lt;/p&gt;

&lt;p&gt;I'll use the OpenAI SDK. Whether you're pointing it at OpenAI or a local Ollama model, the code looks almost identical.&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;openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AsyncOpenAI&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AsyncOpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:11434/v1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ollama&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# can be any non-empty string
&lt;/span&gt;&lt;span class="p"&gt;)&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;main&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;user_message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;User &amp;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;response&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;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gemma4:e2b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;messages&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;role&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;user&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;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user_message&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
            &lt;span class="n"&gt;reasoning_effort&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;none&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Assistant &amp;gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="si"&gt;}&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;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;asyncio&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="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;I ran it, and it worked about like I expected:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User &amp;gt; Hi I am Shivansh
Assistant &amp;gt; Hi Shivansh! It's nice to meet you. How can I help you today? 😊
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Twenty lines of code. A working chatbot. Kind of anticlimactic, honestly.&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%2F8ikiqgtx8dj4rs8qctoj.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%2F8ikiqgtx8dj4rs8qctoj.png" alt="stateless-chatbot-diagram" width="799" height="89"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So I poked at it a little more.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User &amp;gt; What is my name?
Assistant &amp;gt; As an AI, I do not have access to your personal information, so I do not know your name.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Wait, what?&lt;/p&gt;

&lt;p&gt;It knew my name ten seconds ago. Now it's acting like we've never met.&lt;/p&gt;

&lt;p&gt;My first instinct was that it forgot. That's the natural way to describe it. But "forgot" implies it once knew something and then lost it, like walking out of a room and losing the thread. Did that actually happen here, or was something else going on?&lt;/p&gt;

&lt;p&gt;I didn't want to guess, so I went and looked at exactly what I was sending.&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;response&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;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gemma4:e2b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;messages&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;role&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;user&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;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user_message&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;And there it was. Every single time I called this, I built a brand-new &lt;code&gt;messages&lt;/code&gt; list from scratch, containing nothing but whatever had just been typed. When I said "Hi I am Shivansh," the model received:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hi I am Shivansh"&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Ten seconds later, when I asked "What is my name?", the model received:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"What is my name?"&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;I stared at that second payload for a second longer than I'd like to admit. The first message wasn't trimmed or summarized or forgotten in any active sense. It just wasn't there. I had never sent it a second time.&lt;/p&gt;

&lt;p&gt;From the model's point of view, these weren't two turns in one conversation. They were two total strangers walking up and asking unrelated questions. There was no forgetting, because there was never anything to forget in the first place. Nothing carried over from one call to the next.&lt;/p&gt;

&lt;p&gt;Which gave me my first real insight, and it landed harder than I expected for something this simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The model only knows what you send it.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is what people mean when they say LLMs are &lt;em&gt;stateless&lt;/em&gt;. The model isn't a person sitting there, quietly tracking your conversation between messages. It's a function. Call it with an input, get an output, and it has no idea it was ever called before.&lt;/p&gt;

&lt;p&gt;So the obvious follow-up question in my head was: if the model has no memory, how does ChatGPT clearly remember what I said five messages ago?&lt;/p&gt;

&lt;p&gt;The answer turned out to be almost anticlimactic once I saw it. The model isn't remembering anything. The &lt;em&gt;application&lt;/em&gt; is. It's quietly collecting the conversation and re-sending the whole thing back to the model, every single time, as if for the first time.&lt;/p&gt;

&lt;p&gt;I hadn't set out to build "memory." I was just trying to make the forgetting stop. Once I knew why it was forgetting though, the fix basically wrote itself. If the model only knows what I send it, I just need to send it more.&lt;/p&gt;

&lt;p&gt;Instead of this:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"What is my name?"&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;I sent this:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hi I am Shivansh"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"assistant"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hi Shivansh! It's nice to meet you."&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"What is my name?"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now the model could see the entire exchange, not just the latest fragment of it.&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;messages&lt;/span&gt; &lt;span class="o"&gt;=&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;user_message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;User &amp;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;messages&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&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;user&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;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user_message&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="n"&gt;response&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;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gemma4:e2b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;assistant_message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;
    &lt;span class="n"&gt;messages&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&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;assistant&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;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;assistant_message&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Assistant &amp;gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;assistant_message&lt;/span&gt;&lt;span class="si"&gt;}&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User &amp;gt; Hi I am Shivansh
Assistant &amp;gt; Hi Shivansh!

User &amp;gt; What is my name?
Assistant &amp;gt; Your name is Shivansh.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;There it is.&lt;/p&gt;

&lt;p&gt;But notice what I didn't do. I didn't give the model memory. The model is exactly as stateless as it was thirty seconds ago, still a pure function, still forgetting everything the instant the response comes back.&lt;/p&gt;

&lt;p&gt;What changed is that my application now keeps a growing transcript and replays it on every turn. The illusion of memory isn't happening inside the model at all. It's happening in a Python list sitting in my process.&lt;/p&gt;

&lt;p&gt;I used to assume, without ever really examining it, that when ChatGPT "remembers" something from earlier in a conversation, the model is doing something clever. It isn't. Some code, somewhere, is just handing it the same conversation over and over, one message longer each time.&lt;/p&gt;

&lt;p&gt;I didn't sit down that day to implement conversation memory. I sat down annoyed that my chatbot had amnesia. The memory system was just what was left over after I fixed that.&lt;/p&gt;

&lt;p&gt;Okay. So now my chatbot has a memory of sorts.&lt;/p&gt;

&lt;p&gt;Was I done? Did I have an agent yet?&lt;/p&gt;

&lt;p&gt;Not even close. The next thing I tried made that painfully obvious.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Chatbot Hits a Wall
&lt;/h3&gt;

&lt;p&gt;I created a file in my project folder:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;README.md&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;This project is a simple AI chatbot.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Then I asked my memory-having chatbot a completely reasonable question.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User &amp;gt; Summarize the contents of README.md
Assistant &amp;gt; Please provide the content of the README.md file.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Huh.&lt;/p&gt;

&lt;p&gt;It's not that the model is bad at summarizing. It's genuinely very good at that. The problem was more basic, and once I saw it, kind of obvious. The model had never seen the file. It lives on my machine. The model lives wherever it lives. As far as it was concerned, &lt;code&gt;README.md&lt;/code&gt; was just six characters that happened to appear in a sentence.&lt;/p&gt;

&lt;p&gt;I had given it memory of the conversation. I hadn't given it anything about the world.&lt;/p&gt;

&lt;p&gt;Every capability I take for granted with tools like Claude Code or ChatGPT plugins, reading files, running commands, checking the weather, has to come from somewhere. The model can't reach out and grab it. Somebody has to hand it over.&lt;/p&gt;

&lt;p&gt;This is the wall every chatbot eventually hits. It can talk about anything, but it can't do anything.&lt;/p&gt;
&lt;h3&gt;
  
  
  Giving It Hands
&lt;/h3&gt;

&lt;p&gt;If the model couldn't read the file itself, fine. I'd write a function that could, and describe that function to the model.&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;def&lt;/span&gt; &lt;span class="nf"&gt;read_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&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="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Read a UTF-8 text file and return its contents.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&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;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;I also needed to tell the model this function exists, since it can't inspect my codebase either. I did that with a tool schema, basically a spec sheet describing the function's name, purpose, and arguments:&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;tools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&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;type&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;function&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;function&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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;name&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;read_file&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;description&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;Read a text file from disk.&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;parameters&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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;type&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;object&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;properties&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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;path&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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;type&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;string&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;description&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;Path to the file&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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;required&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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;path&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;response&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;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gemma4:e2b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;messages&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="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;I expected the model to just read the file. Run the function. Hand back the contents.&lt;/p&gt;

&lt;p&gt;That is not what happened. Here's what actually came back:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"assistant"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tool_calls"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"call_teaunoqm"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"function"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"function"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"read_file"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"arguments"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"README.md"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;I remember staring at this for a second, genuinely confused. There's no file content anywhere in here. The model didn't call &lt;code&gt;read_file()&lt;/code&gt;. It didn't even attempt to. What it produced instead reads more like a memo: someone please call &lt;code&gt;read_file("README.md")&lt;/code&gt; and get back to me.&lt;/p&gt;

&lt;p&gt;This is where I ran into probably the single biggest misconception I had going into agent-building. The model never executes a tool. It only decides that a tool should be called, and with what arguments.&lt;/p&gt;

&lt;p&gt;The actual execution, opening the file, running the command, hitting the API, has to happen entirely in my code. The model is more like a manager scribbling a request on a sticky note. My runtime is the one who actually gets up and does it.&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%2Fpqisw2n25ntpuwkvdhit.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%2Fpqisw2n25ntpuwkvdhit.png" alt="agent-with-tool-call" width="800" height="228"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So that's what I did next. Pulled the tool call out of the response, ran the real Python function, fed the result back in.&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;tool_fns&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;read_file&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;read_file&lt;/span&gt;&lt;span class="p"&gt;}&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;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;=&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;user_message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;User &amp;gt; &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;user_message&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/bye&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;

        &lt;span class="n"&gt;messages&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&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;user&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;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user_message&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

        &lt;span class="n"&gt;response&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;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gemma4:e2b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;messages&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="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;reasoning_effort&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;none&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;messages&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;message&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;finish_reason&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tool_calls&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;tc&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tool_calls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;tool_fn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tool_fns&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;tc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
                &lt;span class="n"&gt;tool_args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;tool_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;tool_fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;tool_args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

                &lt;span class="n"&gt;messages&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&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;tool&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;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;tool_result&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

                &lt;span class="n"&gt;response&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;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gemma4:e2b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;messages&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="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;reasoning_effort&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;none&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Assistant &amp;gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="si"&gt;}&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;Now the whole thing actually worked. The model asked for the file, my code read it, I handed the contents back, and the model summarized it like it had the file all along.&lt;/p&gt;

&lt;p&gt;I hadn't set out to build "tool calling" either. I was just trying to get one file into the model's hands. Somewhere along the way I'd built the exact mechanism every agent framework calls tool use.&lt;/p&gt;

&lt;p&gt;At this point I had memory, tools, and tool execution. The model could inspect files, reason about what it found, and answer using information it never had at training time.&lt;/p&gt;

&lt;p&gt;This felt like an agent. Genuinely close.&lt;/p&gt;

&lt;p&gt;But there was a crack in the foundation, and it showed up the moment I asked for something slightly harder.&lt;/p&gt;
&lt;h3&gt;
  
  
  One Tool Call Isn't Enough
&lt;/h3&gt;

&lt;p&gt;Say my folder didn't just have a &lt;code&gt;README.md&lt;/code&gt;. It also had a &lt;code&gt;CONTRIBUTING.md&lt;/code&gt;, a &lt;code&gt;CHANGELOG.md&lt;/code&gt;, maybe a few more. Instead of asking about one file, I asked something more realistic:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Read the docs in this folder and write me a summary.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;I ran it, and the model did exactly what I'd trained it to do in the previous section. It called a tool.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;list_directory()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;→&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"README.md"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CONTRIBUTING.md"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CHANGELOG.md"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Great, I thought. Now it knows which files exist.&lt;/p&gt;

&lt;p&gt;Except it doesn't know what's in any of them. It knows three filenames. That's it. To actually summarize anything, it needs to read each one, which means calling &lt;code&gt;read_file()&lt;/code&gt; three more times.&lt;/p&gt;

&lt;p&gt;And here's where I hit the wall for real. My runtime didn't let it do that.&lt;/p&gt;

&lt;p&gt;Go look at the shape of the code I'd written:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Tool
  ↓
Answer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;One shot. The model gets exactly one chance to call a tool. I run it, hand back the result, and then immediately force it to a final answer, ready or not.&lt;/p&gt;

&lt;p&gt;So that's exactly what happened. The model called &lt;code&gt;list_directory()&lt;/code&gt;, got its three filenames back, and then had nowhere to go. It wasn't allowed to act on what it had just learned. It had to answer right then, holding nothing but a list of names and zero idea what any of them contained.&lt;/p&gt;

&lt;p&gt;I sat there looking at the output for a minute before it actually clicked. This wasn't a prompting problem. It wasn't a model-capability problem. The model had figured out exactly the right next step, go read those three files, and my architecture simply refused to let it take that step. The bottleneck wasn't the model anymore. It was the code I'd written around it.&lt;/p&gt;

&lt;p&gt;The one-shot ceiling wasn't a bug I'd introduced by accident either. It was baked into the shape of the code. I had hardcoded one tool call, then answer, without ever asking whether one would be enough. Turns out, for almost anything interesting, it isn't.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Loop
&lt;/h3&gt;

&lt;p&gt;If the model needs to act, observe the result, and act again, as many times as the task actually requires, then the architecture can't be a straight line. It has to be a loop.&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="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;call_model&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;tool_calls&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;answer&lt;/span&gt;

    &lt;span class="nf"&gt;execute_tools&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That's genuinely the whole idea. Keep calling the model. If it wants to use a tool, run the tool, feed the result back, and let it decide what to do next. Including deciding it needs another tool, or that it finally has enough to answer.&lt;/p&gt;

&lt;p&gt;I want to be honest about how I got here, because I think it matters. I didn't set out to build anything with a name. I wasn't chasing a pattern from a paper. I was solving one specific, annoying problem: the model needed to take a second action based on what the first one revealed, and my code didn't let it. The loop was just the shape that problem forced onto the code.&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%2Ff0v8bnwlyz5e9tpow1r2.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%2Ff0v8bnwlyz5e9tpow1r2.png" alt="react-agent-with-loop" width="798" height="229"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It was only in hindsight, looking at what I'd built, that I realized it already had a name. This is the &lt;a href="https://react-lm.github.io/" rel="noopener noreferrer"&gt;ReAct pattern&lt;/a&gt; (Reason + Act, not to be confused with React.js), and it looks pretty close to what I'd just backed into:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Reason → Act → Observe → Reason → Act → Observe → ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;I didn't implement ReAct. I rediscovered it, one dead end at a time, which as far as I can tell is roughly how it got discovered the first time too. Nobody sits down and decides today I will invent a reasoning loop. You sit down annoyed that your chatbot can only do one thing, and a loop is what falls out.&lt;/p&gt;

&lt;p&gt;That's been the pattern through this whole article, now that I look back at it. I never woke up wanting to build memory, or tools, or ReAct. I woke up with a chatbot that forgot my name, then one that couldn't read a file, then one that got stuck after a single tool call. Every piece of "architecture" here is really just scar tissue from a specific, annoying failure.&lt;/p&gt;

&lt;p&gt;And with that, my twenty-line chatbot had quietly crossed a line. It's no longer just something that talks. It can decide what to do, do it, look at what happened, and decide again.&lt;/p&gt;

&lt;p&gt;That's the difference between a chatbot and an agent, as far as I can tell. Not a framework, not a new model. A loop, and the willingness to let the model act more than once.&lt;/p&gt;



&lt;p&gt;If you want to see the full implementation, including the parts trimmed out of this write-up for length, the code is here: &lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/SpaceTesla" rel="noopener noreferrer"&gt;
        SpaceTesla
      &lt;/a&gt; / &lt;a href="https://github.com/SpaceTesla/react-agent" rel="noopener noreferrer"&gt;
        react-agent
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A minimal ReAct agent built from first principles.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;react-agent&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;A minimal ReAct agent built from first principles.&lt;/p&gt;

&lt;p&gt;This repository is the companion project for the &lt;strong&gt;Agentic AI from First Principles&lt;/strong&gt; series, where we build an AI agent step by step without relying on frameworks like LangChain or LangGraph.&lt;/p&gt;

&lt;p&gt;Instead of starting with abstractions, we start with problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A chatbot that forgets everything&lt;/li&gt;
&lt;li&gt;A chatbot that can't interact with the world&lt;/li&gt;
&lt;li&gt;A chatbot that can only take a single action&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And by solving those problems one at a time, we eventually arrive at a ReAct agent.&lt;/p&gt;

&lt;p&gt;If you came here from the article, welcome 👋&lt;/p&gt;

&lt;p&gt;This repository contains the exact implementation discussed in the series, along with the code needed to experiment, modify, and extend it yourself.&lt;/p&gt;




&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What Is This?&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;At its core, this project is a simple agent runtime built on top of the OpenAI-compatible API.&lt;/p&gt;

&lt;p&gt;The agent can:&lt;/p&gt;


&lt;ul&gt;

&lt;li&gt;Maintain conversation history&lt;/li&gt;

&lt;li&gt;Call tools&lt;/li&gt;

&lt;li&gt;Execute tool requests&lt;/li&gt;

&lt;li&gt;Observe…&lt;/li&gt;

&lt;/ul&gt;&lt;/div&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/SpaceTesla/react-agent" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;

&lt;/h2&gt;


&lt;p&gt;Here's the thing I keep coming back to though.&lt;/p&gt;

&lt;p&gt;At the start of this article, my chatbot forgot everything. Every message, gone the moment it was sent. That was clearly broken, so I fixed it. I made it remember everything.&lt;/p&gt;

&lt;p&gt;But remembering everything isn't actually the finish line. It just moves the problem somewhere else. Right now, every tool result my agent has ever produced gets appended to &lt;code&gt;messages&lt;/code&gt; and never removed. Ask it to read ten files instead of three, and all ten now live in the conversation permanently, resent to the model on every single subsequent call whether they're still relevant or not.&lt;/p&gt;

&lt;p&gt;A chatbot that forgets everything is broken. Turns out an agent that forgets nothing is also broken, just more slowly and more expensively. Context windows fill up. Costs climb. Latency climbs with them. Eventually the model has to reason around a pile of stuff it doesn't need anymore, which hurts the quality of its answers too.&lt;/p&gt;

&lt;p&gt;So that's the next wall. Not "can it act more than once," we just solved that. It's "can it act more than once without drowning in its own history."&lt;/p&gt;

&lt;p&gt;Underneath both of those specific problems, though, there's a bigger one, and it's the real reason I wanted to write this article the way I did.&lt;/p&gt;

&lt;p&gt;I didn't sit down planning to build memory. I didn't sit down planning to build tool calling. I definitely didn't sit down planning to build a ReAct agent. Every piece of this architecture exists because something broke, and I fixed the thing that broke. Nothing more.&lt;/p&gt;

&lt;p&gt;Memory showed up because the model forgot. Tools showed up because the model couldn't touch anything outside the conversation. The loop showed up because acting once wasn't enough.&lt;/p&gt;

&lt;p&gt;Nobody handed me these abstractions. I didn't start with them. I started with problems, and the abstractions were just what was left standing after I solved them.&lt;/p&gt;

&lt;p&gt;That's usually where the good ones come from.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>llm</category>
      <category>python</category>
    </item>
    <item>
      <title>Inside ChatGPT: What Really Happens Inside an LLM?</title>
      <dc:creator>Shivansh Karan</dc:creator>
      <pubDate>Fri, 17 Jul 2026 02:39:00 +0000</pubDate>
      <link>https://dev.to/spacetesla/inside-chatgpt-what-really-happens-inside-an-llm-3a5p</link>
      <guid>https://dev.to/spacetesla/inside-chatgpt-what-really-happens-inside-an-llm-3a5p</guid>
      <description>&lt;h3&gt;
  
  
  About this series
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Agentic AI from First Principles&lt;/em&gt; is a hands-on series where we build an AI agent from scratch, without relying on frameworks. Instead of starting with abstractions, every abstraction appears only after we've hit the problem it solves.&lt;/p&gt;

&lt;p&gt;If you're joining from the beginning, welcome. If not, you can find the previous chapters in the series here.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;You ask ChatGPT a question.&lt;/p&gt;

&lt;p&gt;A few seconds pass.&lt;/p&gt;

&lt;p&gt;And suddenly, a well-structured answer appears on your screen.&lt;/p&gt;

&lt;p&gt;It feels almost magical.&lt;/p&gt;

&lt;p&gt;The model seems to understand your question, reason about it, and respond intelligently.&lt;/p&gt;

&lt;p&gt;But what actually happens inside?&lt;/p&gt;

&lt;p&gt;Before we start building AI agents, we need to understand the engine that powers them: the Large Language Model (LLM).&lt;/p&gt;

&lt;p&gt;Instead of diving into equations and research papers, let's follow a single prompt as it travels through the model.&lt;/p&gt;

&lt;p&gt;Our prompt:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Explain how a large language model actually works."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's see where it goes.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. The Gate: Tokenization
&lt;/h2&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%2Fk1abnd6b40gag9uuumdo.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%2Fk1abnd6b40gag9uuumdo.png" alt="tokenization-gate" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Your prompt arrives at the entrance of the model.&lt;/p&gt;

&lt;p&gt;But there's a problem.&lt;/p&gt;

&lt;p&gt;LLMs don't understand text.&lt;/p&gt;

&lt;p&gt;At least, not directly.&lt;/p&gt;

&lt;p&gt;They only understand &lt;strong&gt;tokens&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A tokenizer sits at the gate and breaks your sentence into smaller pieces from the model's vocabulary.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Explain how a large language model actually works.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;["Explain", " how", " a", " large", " language",
 " model", " actually", " works", "."]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each token is then converted into a numeric ID.&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%2Fcoenrq2wnldlolaeb99o.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%2Fcoenrq2wnldlolaeb99o.png" alt="tokens-by-openai-tokenizer" width="800" height="203"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Token&lt;/th&gt;
&lt;th&gt;Ex&lt;/th&gt;
&lt;th&gt;plain&lt;/th&gt;
&lt;th&gt;how&lt;/th&gt;
&lt;th&gt;a&lt;/th&gt;
&lt;th&gt;large&lt;/th&gt;
&lt;th&gt;language&lt;/th&gt;
&lt;th&gt;model&lt;/th&gt;
&lt;th&gt;actually&lt;/th&gt;
&lt;th&gt;works&lt;/th&gt;
&lt;th&gt;.&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Token ID&lt;/td&gt;
&lt;td&gt;849&lt;/td&gt;
&lt;td&gt;21435&lt;/td&gt;
&lt;td&gt;1268&lt;/td&gt;
&lt;td&gt;264&lt;/td&gt;
&lt;td&gt;3544&lt;/td&gt;
&lt;td&gt;4221&lt;/td&gt;
&lt;td&gt;1646&lt;/td&gt;
&lt;td&gt;3604&lt;/td&gt;
&lt;td&gt;4375&lt;/td&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The model never sees your original sentence again.&lt;/p&gt;

&lt;p&gt;From this point onward, it only sees numbers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why tokenization exists
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Gives the model a fixed vocabulary&lt;/li&gt;
&lt;li&gt;Handles rare words efficiently&lt;/li&gt;
&lt;li&gt;Converts messy human language into predictable units&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At this stage, the model doesn't understand meaning.&lt;/p&gt;

&lt;p&gt;It simply has a sequence of token IDs.&lt;/p&gt;

&lt;p&gt;The journey continues.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. The Embedding Hall
&lt;/h2&gt;

&lt;p&gt;The token IDs now enter a massive chamber called the embedding layer.&lt;/p&gt;

&lt;p&gt;Here, every token is transformed into a vector.&lt;/p&gt;

&lt;p&gt;Something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"model"
↓
[0.18, -0.42, 0.07, ...]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Feroi53hs46798ywaqcs1.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%2Feroi53hs46798ywaqcs1.png" alt="vector-embeddings" width="798" height="212"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Think of embeddings as coordinates in a giant semantic map.&lt;/p&gt;

&lt;p&gt;Words with similar meanings tend to end up near each other.&lt;/p&gt;

&lt;p&gt;But there's still a limitation.&lt;/p&gt;

&lt;p&gt;The embedding for "model" doesn't yet know whether we're talking about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a fashion model&lt;/li&gt;
&lt;li&gt;a machine learning model&lt;/li&gt;
&lt;li&gt;a mathematical model&lt;/li&gt;
&lt;li&gt;a 3D model&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The token has some notion of meaning.&lt;/p&gt;

&lt;p&gt;But it doesn't yet know the meaning in &lt;em&gt;this sentence&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;For that, we need context.&lt;/p&gt;

&lt;p&gt;And context lives inside the Transformer.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. The Transformer Core
&lt;/h2&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%2F3dsy266fwugnq39uhxbw.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%2F3dsy266fwugnq39uhxbw.png" alt="transformer-core" width="800" height="534"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is where the real magic happens.&lt;/p&gt;

&lt;p&gt;Imagine a giant room where every token can look at every other token.&lt;/p&gt;

&lt;p&gt;The token &lt;strong&gt;"model"&lt;/strong&gt; starts asking questions:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Which other tokens matter to me?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It notices:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"large"&lt;/li&gt;
&lt;li&gt;"language"&lt;/li&gt;
&lt;li&gt;"actually"&lt;/li&gt;
&lt;li&gt;"works"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those surrounding words provide clues.&lt;/p&gt;

&lt;p&gt;Suddenly, the meaning becomes clear.&lt;/p&gt;

&lt;p&gt;We're talking about a &lt;strong&gt;large language model&lt;/strong&gt;, not a fashion model.&lt;/p&gt;

&lt;p&gt;This process is powered by something called &lt;strong&gt;self-attention&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You don't need to understand the math yet.&lt;/p&gt;

&lt;p&gt;The intuition is enough:&lt;/p&gt;

&lt;p&gt;Every token continuously gathers information from other relevant tokens.&lt;/p&gt;

&lt;p&gt;As information flows between them, the sentence becomes clearer and clearer.&lt;/p&gt;

&lt;h3&gt;
  
  
  What the Transformer accomplishes
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Resolves ambiguity&lt;/li&gt;
&lt;li&gt;Understands relationships&lt;/li&gt;
&lt;li&gt;Captures context&lt;/li&gt;
&lt;li&gt;Builds a representation of meaning&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After several transformer layers, the model has a rich understanding of your prompt.&lt;/p&gt;

&lt;p&gt;Now it's ready to respond.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. The Response Factory
&lt;/h2&gt;

&lt;p&gt;This is the part that surprises most people.&lt;/p&gt;

&lt;p&gt;The model does &lt;strong&gt;not&lt;/strong&gt; generate an entire paragraph at once.&lt;/p&gt;

&lt;p&gt;It generates &lt;strong&gt;one token at a time&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That's it.&lt;/p&gt;

&lt;p&gt;The process looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Look at the prompt&lt;/li&gt;
&lt;li&gt;Predict the most likely next token&lt;/li&gt;
&lt;li&gt;Add it to the conversation&lt;/li&gt;
&lt;li&gt;Repeat&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"A"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;becomes&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"A large"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;then&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"A large language"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;then&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"A large language model"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and so on.&lt;/p&gt;

&lt;p&gt;Every newly generated token is fed back into the model.&lt;/p&gt;

&lt;p&gt;The model repeatedly asks:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Given everything I've seen so far, what token should come next?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Thousands of times per response.&lt;/p&gt;

&lt;p&gt;This simple loop is responsible for everything:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;explanations&lt;/li&gt;
&lt;li&gt;stories&lt;/li&gt;
&lt;li&gt;code&lt;/li&gt;
&lt;li&gt;essays&lt;/li&gt;
&lt;li&gt;conversations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything emerges from repeated next-token prediction.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. The Exit Gate
&lt;/h2&gt;

&lt;p&gt;Eventually the model decides it has finished.&lt;/p&gt;

&lt;p&gt;Now we have a sequence of generated tokens:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;["A", " large", " language", " model", " is", ...]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These tokens pass through the detokenizer.&lt;/p&gt;

&lt;p&gt;The pieces are stitched back together into readable text.&lt;/p&gt;

&lt;p&gt;The result becomes:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"A large language model is..."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And that's the answer you see on your screen.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. The Entire Journey
&lt;/h2&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%2F6qkvzco2h7vgkahgjeen.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%2F6qkvzco2h7vgkahgjeen.png" alt="the-journey" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's compress everything into a single flow:&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Tokenization
&lt;/h3&gt;

&lt;p&gt;Text → Tokens&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Embeddings
&lt;/h3&gt;

&lt;p&gt;Tokens → Vectors&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Transformer
&lt;/h3&gt;

&lt;p&gt;Vectors → Contextual Understanding&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Generation
&lt;/h3&gt;

&lt;p&gt;Predict the next token repeatedly&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: Detokenization
&lt;/h3&gt;

&lt;p&gt;Tokens → Human-readable text&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 6: Final Response
&lt;/h3&gt;

&lt;p&gt;Answer appears on your screen&lt;/p&gt;




&lt;h2&gt;
  
  
  What Most People Miss
&lt;/h2&gt;

&lt;p&gt;At this point, something important should stand out.&lt;/p&gt;

&lt;p&gt;Throughout this entire journey, we never gave the model:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;memory&lt;/li&gt;
&lt;li&gt;tools&lt;/li&gt;
&lt;li&gt;a browser&lt;/li&gt;
&lt;li&gt;a calculator&lt;/li&gt;
&lt;li&gt;access to files&lt;/li&gt;
&lt;li&gt;the ability to take actions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All it did was:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Process tokens&lt;/li&gt;
&lt;li&gt;Understand context&lt;/li&gt;
&lt;li&gt;Predict the next token&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's it.&lt;/p&gt;

&lt;p&gt;An LLM is incredibly powerful.&lt;/p&gt;

&lt;p&gt;But by itself, it's still just a next-token prediction engine.&lt;/p&gt;

&lt;p&gt;This realization becomes extremely important once we start building AI agents.&lt;/p&gt;

&lt;p&gt;Because many of the capabilities we associate with modern AI systems don't come from the LLM alone.&lt;/p&gt;

&lt;p&gt;They come from the systems built around it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Recap
&lt;/h2&gt;

&lt;p&gt;If you remember only one sentence from this article, make it this one:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;An LLM converts text into tokens, uses attention to understand context, and generates a response one token at a time.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Or even shorter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Text → Tokens → Context → Prediction → Text
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the core idea.&lt;/p&gt;

&lt;p&gt;And now that we understand what an LLM actually does, we can start exploring what it &lt;em&gt;doesn't&lt;/em&gt; do.&lt;/p&gt;

&lt;p&gt;In the next article, we'll build a chatbot from scratch and discover a surprising limitation that every LLM has.&lt;/p&gt;

&lt;p&gt;One that ultimately leads us toward agents.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
