From 9a1dca0b6cff3e273d4b6dfa194ce79e5329c271 Mon Sep 17 00:00:00 2001 From: Leodanis Pozo Ramos Date: Tue, 20 Oct 2026 19:12:11 +0000 Subject: [PATCH 1/3] Sample code for: AI Agent Memory in Python: A Primer Co-Authored-By: Claude Opus 5.5 (1M context) --- ai-agent-memory/README.md | 56 +++++++++++++++++++ ai-agent-memory/long_term.py | 101 ++++++++++++++++++++++++++++++++++ ai-agent-memory/short_term.py | 31 +++++++++++ 3 files changed, 188 insertions(+) create mode 100644 ai-agent-memory/README.md create mode 100644 ai-agent-memory/long_term.py create mode 100644 ai-agent-memory/short_term.py diff --git a/ai-agent-memory/README.md b/ai-agent-memory/README.md new file mode 100644 index 0000000000..7ea372d024 --- /dev/null +++ b/ai-agent-memory/README.md @@ -0,0 +1,56 @@ +# AI Agent Memory in Python: A Primer + +This folder contains the sample code for the Real Python tutorial [AI Agent Memory in Python: A Primer](https://realpython.com/ai-agent-memory/). + +## Setup + +Both scripts call the OpenAI API, so they need an `OPENAI_API_KEY` and make paid API calls: + +```console +$ export OPENAI_API_KEY="your-api-key" +``` + +Each script declares its dependency, `openai==3.35.1`, in an inline script metadata block, so [uv](https://docs.astral.sh/uv/) installs it for you. If you don't use uv, install the pinned package with `python -m pip install openai==3.35.1` and run the scripts with `python`. + +## Short-Term Memory + +`short_term.py` asks the same question with and without the conversation history: + +```console +$ uv run short_term.py +``` + +## Self-Managed Long-Term Memory + +`long_term.py` gives the model four memory tools and stores what it saves in `memories.json`. Each command is a fresh session, so the agent remembers only what's in the file. Delete `memories.json` to start over. + +Here's the full five-message run from the tutorial. The model's wording, its memories, and even its choice of tools vary from run to run: + +```console +$ uv run long_term.py "Hi! I'm Ana, I'm learning Python, and I prefer short answers with code examples." + tool: save_memory({'text': 'User name is Ana. She is learning Python and prefers short answers with code examples.'}) +Hi Ana! I’ll keep answers short and include code examples. +$ uv run long_term.py "I moved from Lisbon to Berlin last month." + tool: save_memory({'text': 'User moved from Lisbon to Berlin last month.'}) +Got it. +$ uv run long_term.py "Forget that I prefer short answers." + tool: search_memory({'query': 'prefer short answers'}) + tool: update_memory({'id': '1', 'text': 'User name is Ana. She is learning Python and no longer prefers short answers.'}) +Got it — I’ve removed the short-answer preference. +$ uv run long_term.py "Where do I live, and what do you know about me?" + tool: search_memory({'query': 'user personal facts location preferences bio'}) +I know that you’re Ana, you’re learning Python, and you moved from Lisbon to Berlin last month. + +So, you likely live in Berlin now. +$ uv run long_term.py "What's the capital of France?" +The capital of France is Paris. +``` + +After this run, `memories.json` holds two entries: + +```json +{ + "1": "User name is Ana. She is learning Python and no longer prefers short answers.", + "2": "User moved from Lisbon to Berlin last month." +} +``` diff --git a/ai-agent-memory/long_term.py b/ai-agent-memory/long_term.py new file mode 100644 index 0000000000..5d7ef65564 --- /dev/null +++ b/ai-agent-memory/long_term.py @@ -0,0 +1,101 @@ +# /// script +# requires-python = ">=3.11" +# dependencies = ["openai==3.35.1"] +# /// +import json +import sys +from pathlib import Path + +from openai import OpenAI + +client = OpenAI() +MODEL = "gpt-5.4-mini" +STORE = Path("memories.json") +memories = json.loads(STORE.read_text()) if STORE.exists() else {} + + +def save_memory(text): + memories[str(max(map(int, memories), default=0) + 1)] = text + return "saved" + + +def search_memory(query): + return json.dumps(memories) # Tiny store: return everything + + +def update_memory(id, text): + if id not in memories: + return "not found" + memories[id] = text + return "updated" + + +def delete_memory(id): + if id not in memories: + return "not found" + del memories[id] + return "deleted" + + +def spec(fn, description, *params): + props = {name: {"type": "string"} for name in params} + schema = { + "type": "object", + "properties": props, + "required": list(params), + "additionalProperties": False, + } + return { + "type": "function", + "function": { + "name": fn.__name__, + "description": description, + "parameters": schema, + "strict": True, + }, + } + + +TOOLS = { + f.__name__: f + for f in (save_memory, search_memory, update_memory, delete_memory) +} +SPECS = [ + spec(save_memory, "Store a durable fact about the user.", "text"), + spec(search_memory, "Read the stored memories.", "query"), + spec(update_memory, "Replace an outdated memory.", "id", "text"), + spec(delete_memory, "Delete a wrong or unneeded memory.", "id"), +] + +if len(sys.argv) != 2: + sys.exit('Usage: uv run long_term.py "Your message"') + +messages = [ + { + "role": "system", + "content": "You have long-term memory tools. Decide yourself " + "when to save, search, update, or delete memories. Search first to get " + "ids before you update or delete. Answer briefly.", + }, + {"role": "user", "content": sys.argv[1]}, +] +for _ in range(10): # The agent loop, capped at ten rounds + reply = ( + client.chat.completions.create( + model=MODEL, messages=messages, tools=SPECS + ) + .choices[0] + .message + ) + messages.append(reply) + if not reply.tool_calls: + break + for call in reply.tool_calls: + args = json.loads(call.function.arguments) + print(f" tool: {call.function.name}({args})") + result = TOOLS[call.function.name](**args) + STORE.write_text(json.dumps(memories, indent=2)) # Save every change + messages.append( + {"role": "tool", "tool_call_id": call.id, "content": result} + ) +print(reply.content) diff --git a/ai-agent-memory/short_term.py b/ai-agent-memory/short_term.py new file mode 100644 index 0000000000..fefa4e8ac4 --- /dev/null +++ b/ai-agent-memory/short_term.py @@ -0,0 +1,31 @@ +# /// script +# requires-python = ">=3.11" +# dependencies = ["openai==3.35.1"] +# /// +from openai import OpenAI + +client = OpenAI() +MODEL = "gpt-5.4-mini" + + +SYSTEM = {"role": "system", "content": "Answer in one short sentence."} + + +def chat(messages): + response = client.chat.completions.create( + model=MODEL, messages=[SYSTEM, *messages] + ) + return response.choices[0].message.content + + +history = [] +for turn in ["Hi, I'm Ana and I'm learning Python.", "What's my name?"]: + history.append({"role": "user", "content": turn}) + reply = chat(history) # The whole history goes in every call + history.append({"role": "assistant", "content": reply}) + print(f"With history: {reply}") + +# Same question, but without the earlier turns +print( + "Without history:", chat([{"role": "user", "content": "What's my name?"}]) +) From 765c423d74d29c4f6713eeb0ab94271acf0b9844 Mon Sep 17 00:00:00 2001 From: Leodanis Pozo Ramos Date: Fri, 23 Oct 2026 17:50:13 +0000 Subject: [PATCH 2/3] Update agent memory sample code --- ai-agent-memory/README.md | 36 +++++---- .../{long_term.py => managed_memory.py} | 76 ++++++++++++++++--- 2 files changed, 85 insertions(+), 27 deletions(-) rename ai-agent-memory/{long_term.py => managed_memory.py} (56%) diff --git a/ai-agent-memory/README.md b/ai-agent-memory/README.md index 7ea372d024..ea50bbfdff 100644 --- a/ai-agent-memory/README.md +++ b/ai-agent-memory/README.md @@ -20,37 +20,41 @@ Each script declares its dependency, `openai==3.35.1`, in an inline script metad $ uv run short_term.py ``` -## Self-Managed Long-Term Memory +## Agent-Managed Memory -`long_term.py` gives the model four memory tools and stores what it saves in `memories.json`. Each command is a fresh session, so the agent remembers only what's in the file. Delete `memories.json` to start over. +`managed_memory.py` gives the model four memory tools and stores what it saves in `memories.json`. Each memory has text and topic tags, which the search tool uses to find matching entries. Each command is a fresh session, so the agent remembers only what's in the file. Delete `memories.json` to start over. -Here's the full five-message run from the tutorial. The model's wording, its memories, and even its choice of tools vary from run to run: +Here's the four-message run from the tutorial. The model's wording, its memories, and even its choice of tools vary from run to run: ```console -$ uv run long_term.py "Hi! I'm Ana, I'm learning Python, and I prefer short answers with code examples." - tool: save_memory({'text': 'User name is Ana. She is learning Python and prefers short answers with code examples.'}) +$ uv run managed_memory.py "Hi! I'm Ana, I'm learning Python, and I prefer short answers with code examples." + tool: save_memory({'text': 'User name is Ana. She is learning Python and prefers short answers with code examples.', 'tags': 'name, learning, preferences'}) Hi Ana! I’ll keep answers short and include code examples. -$ uv run long_term.py "I moved from Lisbon to Berlin last month." - tool: save_memory({'text': 'User moved from Lisbon to Berlin last month.'}) +$ uv run managed_memory.py "I moved from Lisbon to Berlin last month." + tool: save_memory({'text': 'User moved from Lisbon to Berlin last month.', 'tags': 'location'}) Got it. -$ uv run long_term.py "Forget that I prefer short answers." - tool: search_memory({'query': 'prefer short answers'}) - tool: update_memory({'id': '1', 'text': 'User name is Ana. She is learning Python and no longer prefers short answers.'}) +$ uv run managed_memory.py "Forget that I prefer short answers." + tool: search_memory({'query': 'preferences'}) + tool: update_memory({'id': '1', 'text': 'User name is Ana. She is learning Python and no longer prefers short answers.', 'tags': 'name, learning, preferences'}) Got it — I’ve removed the short-answer preference. -$ uv run long_term.py "Where do I live, and what do you know about me?" - tool: search_memory({'query': 'user personal facts location preferences bio'}) +$ uv run managed_memory.py "Where do I live, and what do you know about me?" + tool: search_memory({'query': 'name learning location'}) I know that you’re Ana, you’re learning Python, and you moved from Lisbon to Berlin last month. So, you likely live in Berlin now. -$ uv run long_term.py "What's the capital of France?" -The capital of France is Paris. ``` After this run, `memories.json` holds two entries: ```json { - "1": "User name is Ana. She is learning Python and no longer prefers short answers.", - "2": "User moved from Lisbon to Berlin last month." + "1": { + "text": "User name is Ana. She is learning Python and no longer prefers short answers.", + "tags": ["learning", "name", "preferences"] + }, + "2": { + "text": "User moved from Lisbon to Berlin last month.", + "tags": ["location"] + } } ``` diff --git a/ai-agent-memory/long_term.py b/ai-agent-memory/managed_memory.py similarity index 56% rename from ai-agent-memory/long_term.py rename to ai-agent-memory/managed_memory.py index 5d7ef65564..d61eeade41 100644 --- a/ai-agent-memory/long_term.py +++ b/ai-agent-memory/managed_memory.py @@ -3,6 +3,7 @@ # dependencies = ["openai==3.35.1"] # /// import json +import re import sys from pathlib import Path @@ -14,19 +15,56 @@ memories = json.loads(STORE.read_text()) if STORE.exists() else {} -def save_memory(text): - memories[str(max(map(int, memories), default=0) + 1)] = text +def save_memory(text, tags): + new_id = str(max(map(int, memories), default=0) + 1) + memories[new_id] = {"text": text, "tags": _parse_tags(tags)} return "saved" +def _parse_tags(tags): + return sorted( + {tag.strip().lower() for tag in tags.split(",") if tag.strip()} + ) + + def search_memory(query): - return json.dumps(memories) # Tiny store: return everything + query_words = _words(query) + matches = { + id: memory + for id, memory in memories.items() + if query_words + & (_words(memory["text"]) | _words(" ".join(memory["tags"]))) + } + return json.dumps(matches) + + +STOP_WORDS = { + "a", + "an", + "and", + "are", + "do", + "i", + "is", + "me", + "my", + "of", + "the", + "to", + "user", + "what", + "where", +} + + +def _words(text): + return set(re.findall(r"[a-z0-9]+", text.lower())) - STOP_WORDS -def update_memory(id, text): +def update_memory(id, text, tags): if id not in memories: return "not found" - memories[id] = text + memories[id] = {"text": text, "tags": _parse_tags(tags)} return "updated" @@ -61,21 +99,37 @@ def spec(fn, description, *params): for f in (save_memory, search_memory, update_memory, delete_memory) } SPECS = [ - spec(save_memory, "Store a durable fact about the user.", "text"), - spec(search_memory, "Read the stored memories.", "query"), - spec(update_memory, "Replace an outdated memory.", "id", "text"), + spec( + save_memory, + "Store a fact with comma-separated topic tags.", + "text", + "tags", + ), + spec( + search_memory, + "Search memories by topic or keywords, such as location or preferences.", + "query", + ), + spec( + update_memory, + "Replace a memory and its comma-separated tags.", + "id", + "text", + "tags", + ), spec(delete_memory, "Delete a wrong or unneeded memory.", "id"), ] if len(sys.argv) != 2: - sys.exit('Usage: uv run long_term.py "Your message"') + sys.exit('Usage: uv run managed_memory.py "Your message"') messages = [ { "role": "system", "content": "You have long-term memory tools. Decide yourself " - "when to save, search, update, or delete memories. Search first to get " - "ids before you update or delete. Answer briefly.", + "when to save, search, update, or delete memories. Give each memory " + "topic tags, such as location or preferences. Search by topic first " + "to get ids before you update or delete. Answer briefly.", }, {"role": "user", "content": sys.argv[1]}, ] From bffb74986d7f83597cfd9ae98023f8f0a6efd670 Mon Sep 17 00:00:00 2001 From: Leodanis Pozo Ramos Date: Fri, 23 Oct 2026 18:02:37 +0000 Subject: [PATCH 3/3] Clarify agent memory system prompt --- ai-agent-memory/managed_memory.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ai-agent-memory/managed_memory.py b/ai-agent-memory/managed_memory.py index d61eeade41..3b23a7e973 100644 --- a/ai-agent-memory/managed_memory.py +++ b/ai-agent-memory/managed_memory.py @@ -126,7 +126,7 @@ def spec(fn, description, *params): messages = [ { "role": "system", - "content": "You have long-term memory tools. Decide yourself " + "content": "You have tools to manage your own long-term memory. Decide " "when to save, search, update, or delete memories. Give each memory " "topic tags, such as location or preferences. Search by topic first " "to get ids before you update or delete. Answer briefly.",