Memory across conversations
State carries facts you chose to save. Memory is different: it stores whole past conversations and lets an agent search them.
Two objects make it work, and they are created side by side. A session service holds conversations while they happen, and a memory service holds the finished ones.
A first conversation
listener = LlmAgent(name="listener", model=PretendModel(replies=[say("Noted.")]),
instruction="Listen to the customer.")
runner = Runner(agent=listener, app_name="demo",
session_service=sessions, memory_service=memory)Runner rather than InMemoryRunner here, because both services are being passed in explicitly. That is the only difference.
first = await sessions.create_session(app_name="demo", user_id="u1")
said = types.Content(role="user", parts=[types.Part(text="I prefer email over phone")])
async for _ in runner.run_async(user_id="u1", session_id=first.id, new_message=said):
pass
print("the conversation happened, and is still only a session")Saving it
finished = await sessions.get_session(app_name="demo", user_id="u1", session_id=first.id)
await memory.add_session_to_memory(finished)
print("saved to memory")Nothing is remembered automatically. You decide when a finished conversation is worth keeping, which is the right way round: most are not.
Searching it later
recaller = LlmAgent(
name="recaller",
model=PretendModel(replies=[call("load_memory", query="email"),
say("You told us you prefer email.")]),
instruction="Use memory when the customer refers to something from before.",
tools=[load_memory],
)load_memory is a tool ADK ships. It goes in the tools list like any other, and the model calls it with a query.
later = Runner(agent=recaller, app_name="demo",
session_service=sessions, memory_service=memory)
second = await sessions.create_session(app_name="demo", user_id="u1")
asked = types.Content(role="user", parts=[types.Part(text="How should we contact you?")])
async for event in later.run_async(user_id="u1", session_id=second.id, new_message=asked):
for part in (event.content.parts if event.content else []):
if part.function_response:
for item in part.function_response.response["result"].memories:
text = "".join(p.text or "" for p in item.content.parts)
print("memory found:", item.author, "said", repr(text))
elif part.text:
print("answer: ", part.text.strip())A new conversation found the old one and answered from it. The memory belongs to the user, so a different customer searching the same words finds nothing.
What the in-memory service actually does
It matches keywords. Search for a word that appears in the stored conversation and you get a hit; search for a phrase that means the same thing in different words and you get nothing. Fine for learning, and wrong for production, where the managed memory service searches properly.
| State | Memory | |
|---|---|---|
| What it holds | Facts you chose to save | Whole past conversations |
| How it is read | A template, or a tool reading it | A search, through load_memory |
| Written by | Your code, deliberately | You, when you add a finished session |
| Good for | The order id, the customer's plan | What was said last month |
- Search for a word that is not in the conversation and print the empty result.
- Store two conversations and search for something in both.
Slow is fine. Stopping is the only problem.