cd /news/large-language-models/aie-2-1-building-it · home topics large-language-models article
[ARTICLE · art-134674] src=heymeraki.substack.com ↗ pub= topic=large-language-models verified=true sentiment=· neutral

aie_2.1: building it

A developer published a tutorial showing how to build a Python-based conversation engine that gives a large language model the appearance of memory by resending the full message history with every API call. The script maintains a growing messages list alongside a system prompt and loops in the terminal, sending the entire conversation to Anthropic's API on each turn so the model can reference earlier statements. The writeup follows an earlier lesson establishing that the model itself retains no memory between calls.

by read10 min views1 publishedSep 19, 2026
aie_2.1: building it
Image: Heymeraki (auto-discovered)

In the last lesson, we established how the model appears to remember things. It does not have memory. What is happening is that every time you send a message, you are sending the full conversation history alongside it. The model reads all of it fresh and responds as if it remembers. Because in that moment, it has everything it needs to.

All of that was theory, in this lesson we’ll see what that looks like in practice.

What you are going to build is a conversation engine. A script where you can type messages back and forth with the model and it holds the full context of everything said. By the end of the conversation, the model will be able to reference something you said at the very start because you gave it back to it every time.

What we are building

A Python script that runs a conversation loop in your terminal. You type a message, the model responds, you type another, it responds again. With every exchange, the messages array grows and gets sent in full on the next call. That is the only thing making the conversation feel continuous, making it seems it remembers.

What you need

Everything you set up in aie_1.0 build. The same folder, the same virtual environment, the same API key. If you have not done that yet, start there and come back.

Create the file

Inside your ai-engineering folder, create a new folder for this build and a file inside it:

mkdir 02
touch 02/conversation_engine.py

Open 02/conversation_engine.py in your editor. We will build this piece by piece.

Step 1: the setup

The first few lines are the same as the last build. You need to load your API key and create the client, the connection to Anthropic's API that every call goes through.

from dotenv import load_dotenv
import os
from anthropic import Anthropic

load_dotenv()

client = Anthropic(api_key=os.getenv("ANTHROPIC_API_KEY"))

If you did aie_1.0 build, you have seen all of this. A quick recap:

  • load_dotenv() reads your.env file and makes the API key available to the script.
  • os.getenv("ANTHROPIC_API_KEY") reads that key out of the environment.
  • Anthropic(api_key=...) creates the client using it.

Step 2: the messages list and the system prompt

This is where this build starts to differ from the last one.

messages = []

SYSTEM_PROMPT = "You are a helpful assistant. You remember everything said earlier in the conversation."
  • messages = [] creates an empty list. In Python, square brackets with nothing inside them is an empty list. This list is the conversation history. It starts empty, and every exchange, your messages and the model's responses, gets added to it as the conversation progresses.This list is the reason the model will seem to remember things. By the end of the conversation, it will have grown to hold every message sent in both directions. And every time you send a new message, this whole list goes with it.
  • SYSTEM_PROMPT is the system message from aie_2.0. It sits at the top of every call, before any conversation begins. It tells the model who it is and how to behave. Here it is just a helpful assistant. In a real product, this is where you would define the model's role, its rules, and its boundaries.

Step 3: keeping the conversation going

Here is the thing about a conversation. It does not end after one message. You say something, the other person responds, you say something else. It keeps going until one of you decides to stop.

The script needs to work the same way. It needs to keep waiting for your input, sending it to the model, printing the response, and then waiting again. Over and over, until you decide to end it.

In code, something that repeats until a condition is met is called a loop. You tell it to keep going, and it does, until you give it a reason to stop.

Add this below what you have:

print("Conversation started. Type 'quit' to exit.")
print()

while True:
    user_input = input("You: ")

Start with just these four lines. Do not add anything else yet.

  • print("Conversation started. Type 'quit' to exit.") prints a message to your terminal when the script starts so you know it is running. The secondprint() with nothing inside it just adds an empty line underneath for spacing.
  • while True is the loop. The wordwhile means*"keep doing this as long as the following condition is true."True is always true. Sowhile True means"keep doing this forever."* The script will keep waiting for your input, sending it to the model, and printing the response, over and over, with no end.
  • input("You: ") is what makes the script and wait. When Python gets to this line, everything stops. The textYou: appears in your terminal and the script waits until you type something and press Enter. Whatever you typed gets saved into a variable calleduser_input so the script can use it in the next steps.

Now you need a way to stop the loop. Add this directly below user_input = input("You: "):

    if user_input.lower() == "quit":
        print("Conversation ended.")
        break

This checks what you typed before doing anything else.

  • user_input.lower() converts whatever you typed to all lowercase letters first.*This means “Quit”, “QUIT”, and “quit” all get treated the same way.*If what you typed matches “quit”, two things happen. The script prints “Conversation ended.” to the terminal. Then break runs.
  • break is a specific Python keyword that means*“stop the loop immediately and do not run it again.”* Withoutbreak ,while True would go on forever.break is the exit door. When Python sees it, the loop ends right there and the script finishes.

Step 4: adding your message to the history

The loop is running and it has your message stored in user_input. Before anything gets sent to the model, that message needs to go into the messages list.

Remember that the messages list is the full conversation history. Every message you send and every response the model sends back lives in that list. When you call the model, the entire list goes with it and we know that this is the reason it seems the model remembers what you said a couple messages back.

So before every API call, your latest message needs to be added to the list. Add this line inside the loop, below the quit check:

    messages.append({"role": "user", "content": user_input})
  • .append() is a Python method that adds one new item to the end of a list. Think of it like adding a new entry to a running log. Each time you type a message and press Enter, this line runs and adds it tomessages .

To make this concrete, here is what the list looks like after your first message:

[
    {"role": "user", "content": "Tell me about phoenixes."}
]

After the model replies and you send a second message:

[
    {"role": "user", "content": "Tell me about phoenixes."},
    {"role": "assistant", "content": "Phoenixes are mythological creatures..."},
    {"role": "user", "content": "What do you think of the name smysthich?"}
]

You can see it growing. Every exchange adds two entries, one from you and one from the model. By the third call, six entries go to the model at once and that is how it knows what was said at the start.

Step 5: calling the model

Now that your message is in the list, it is time to send the list to the model. Add this below the append line:

    response = client.messages.create(
        model="claude-haiku-4-5",
        max_tokens=1024,
        system=SYSTEM_PROMPT,
        messages=messages
    )

This is the same API call structure from the last build. The only difference worth noting is messages=messages. In the last build, this was a list with one item inside it, your single question. Here it is the full history of the conversation. Everything said so far, by both you and the model, goes to the API in one call.

Now we’ll pull the model’s response out as plain text, we’ll call is assistant_message:

    assistant_message = response.content[0].text
  • response.content is a list of what the model sent back. In almost every case there is only one item in it, but it is always structured as a list.[0] reaches in and takes the first item.
  • .text pulls the words out of it as a string you can use.

Step 6: adding the model’s response to the history

The model has responded. Before the loop starts again and waits for your next message, that reply needs to go into the messages list too. Remember how the messages list works, it contains your message and that of the model. If it does not, the model will have no record of what it previously said and the conversation will feel broken.

So we’ll add this:

    messages.append({"role": "assistant", "content": assistant_message})

Same .append() method, same structure. The difference is the role is assistant this time instead of user. The model’s response goes into the list labelled as coming from the assistant, so when the next call is made, the model can read back what it said before.

Let’s display the response in the terminal:

    print(f"Assistant: {assistant_message}")
    print()
  • print(f"Assistant: {assistant_message}") prints the model’s reply withAssistant: in front of it so it is clear who is speaking.
  • The f before the quotation mark turns this into an f-string.An f-string is Python’s way of mixing variables into text. Wherever you write{assistant_message} inside the string, Python replaces it with the value of that variable, the model’s words.
  • The empty print() below it just adds a blank line for spacing, so each exchange is visually separated in the terminal.

After this, the loop goes back to the top. input("You: ") runs again. The script waits for your next message. And the cycle continues.

The full script

Before you run it, here is what the complete file should look like:

from dotenv import load_dotenv
import os
from anthropic import Anthropic

load_dotenv()

client = Anthropic(api_key=os.getenv("ANTHROPIC_API_KEY"))

messages = []

SYSTEM_PROMPT = "You are a helpful assistant. You remember everything said earlier in the conversation."

print("Conversation started. Type 'quit' to exit.")
print()

while True:
    user_input = input("You: ")

    if user_input.lower() == "quit":
        print("Conversation ended.")
        break

    messages.append({"role": "user", "content": user_input})

    response = client.messages.create(
        model="claude-haiku-4-5",
        max_tokens=1024,
        system=SYSTEM_PROMPT,
        messages=messages
    )

    assistant_message = response.content[0].text

    messages.append({"role": "assistant", "content": assistant_message})

    print(f"Assistant: {assistant_message}")
    print()

Read through it to understand before running. You should be able to trace what each part does, the setup at the top, the empty messages list, the loop that keeps the conversation going, the two appends on either side of the API call, and the print at the bottom that shows you the response. Let me know if anything feels fuzzy.

If you’re all good, run it.

Run it

python3 02/conversation_engine.py

When the script starts, Conversation started. Type 'quit' to exit. will appear. From there, just talk. Ask something, wait for the response, ask something else.

When you are a few exchanges in, try asking the model if it remembers something you said at the beginning. Here is what that looked like when I ran it:

You: Tell me about phoenixes. Are they real or a myth?
Assistant: Phoenixes are mythological creatures...

You: Not really, just curious. Now tell me what you 
think about the name smysthich for a mythical creature 
in my book.
Assistant: Here are my impressions of "Smysthich"...

You: Do you remember my reason for asking about phoenixes?
Assistant: You said you were "just curious" about 
phoenixes...

That last response is the point of this build. The model correctly recalled something from the very first message. By that point the messages list had six entries in it, three from you, three from the model, and all six were sent to the model together on that call. It read through all of them and found your answer.

Type “quit” when you are done.

What you just built

The script you wrote is a working conversation engine. What makes it work is that it is a list that grows with every exchange and gets sent in full on every call. That is the mechanism behind every AI chat interface you have ever used.

In the next lesson, aie_2.2, we’ll go deeper into what the API can do. Structured outputs, function calling, and streaming responses, these become essential the moment you start building features for real users.

── more in #large-language-models 4 stories · sorted by recency
── more on @anthropic 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/aie-2-1-building-it] indexed:0 read:10min 2026-09-19 ·