Try this first
Run this and predict the answer before you look.
from llm import connect
llm = connect("ollama:gemma4")
llm.send([{"role": "user", "content": "My name is Priya."}])
second = llm.send([{"role": "user", "content": "What is my name?"}])
print(second.text)
It does not know. It has no idea who Priya is.
What you just did
You made two separate calls, and the second one had no connection to the first.
These APIs are stateless. There is no conversation stored on a server somewhere with an ID
you are attached to. Each call is complete and independent. The model remembers nothing
between calls, because there is nothing to remember with.
So how does a conversation work? You send the whole thing, every time.
messages = [
{"role": "user", "content": "My name is Priya."},
{"role": "assistant", "content": "Nice to meet you, Priya."},
{"role": "user", "content": "What is my name?"},
]
That list is the memory. You hold it. You append to it. If you drop it, the conversation is
gone.
Appending correctly
In our loop there are exactly two things to append.
The assistant’s reply, including the tool calls.
messages.append({"role": "assistant", "content": reply.text,
"tool_calls": reply.tool_calls})
Both parts. If you append only the text, you throw away the record of what the model asked
for, and it loses track of its own request. The next call then fails, or behaves strangely,
and the cause is three lines further up than where the error appears.
The tool results, matched by id.
messages.append({
"role": "tool_results",
"results": [{"id": call.id, "content": "the output, as a string", "is_error": False}],
})
The id must match the id on the tool call you are answering. That is how the model knows
which request this result belongs to, and it matters as soon as there is more than one call in
flight.
Why this shape and not the provider’s
You may have noticed that tool_results is not a role any provider actually has. OpenAI wants
one message per result with role: "tool". Anthropic wants all results inside a single user
message. Gemini wants function_response parts.
We keep a neutral shape and let the adapter translate. That is not tidiness — it is what makes
Module 6 possible. When you start clearing old results and summarising history, you will be
editing this list directly, and you want to be editing something you designed rather than a
provider’s wire format.
The cost consequence
Every turn resends the entire list. Turn twenty sends turns one to nineteen along with it.
This is why a long agent session gets slower and more expensive as it goes, even when the
individual messages stay short. You are not paying for the last message. You are paying for
all of them, again.
Module 6 is entirely about this problem. For now, just know why it happens.
The API remembers nothing. The
messageslist is the entire memory of your agent, and
every call resends all of it.
Try this before the next lesson
Fix the two-call program above. Build a messages list, append the assistant’s reply, then ask
the follow-up question.
Then print len(messages) and reply.usage after each turn. Watch both numbers climb
together. That relationship is the whole of Module 6.