This is the full learning implementation of the example from the article Agent Memory: What It Remembers and Why.
If you have not read the article yet, start there. Here the focus is only on code and the practical difference between short-term and long-term memory.
What This Example Demonstrates
- How short-term memory can lose early instructions due to limited context
- How long-term memory preserves settings across tasks
- Why the same request can produce different results depending on memory type
- How to build a simple memory-aware agent without unnecessary complexity
Project Structure
foundations/
βββ agent-memory/
βββ python/
βββ main.py # runs two scenarios and compares output
βββ agent.py # agent logic with short/long memory
βββ memory.py # short-term and long-term stores
βββ tools.py # simple data sources and report rendering
βββ requirements.txt
How to Run
1. Clone the repository and go to the folder:
git clone https://github.com/AgentPatterns-tech/agentpatterns.git
cd foundations/agent-memory/python
2. Install dependencies (this example has no external packages):
pip install -r requirements.txt
3. Run the demo:
python main.py
What We Build in Code
We build a minimal agent that creates a weekly report in two modes.
SCENARIO 1: short-term memory only (context is limited and can "forget" early prefs)SCENARIO 2: short-term + long-term memory (prefs are preserved across tasks)
Key idea: task context and cross-task memory have different roles.
Code
memory.py - short-term and long-term memory
from dataclasses import dataclass, field
from typing import Any
@dataclass
class ShortMemory:
max_items: int = 6
items: list[dict[str, Any]] = field(default_factory=list)
def add(self, role: str, content: str) -> None:
self.items.append({"role": role, "content": content})
if len(self.items) > self.max_items:
self.items = self.items[-self.max_items :]
def snapshot(self) -> list[dict[str, Any]]:
return list(self.items)
def clear(self) -> None:
self.items.clear()
@dataclass
class LongMemoryStore:
_prefs: dict[str, dict[str, str]] = field(default_factory=dict)
def save_prefs(self, user_key: str, prefs: dict[str, str]) -> None:
self._prefs[user_key] = dict(prefs)
def load_prefs(self, user_key: str) -> dict[str, str]:
return dict(self._prefs.get(user_key, {}))
tools.py - data and report formatting
def get_sales_total(user_id: int) -> float:
_ = user_id
return 12400.0
def get_orders_count(user_id: int) -> int:
_ = user_id
return 31
def render_report(*, total: float, orders: int, currency: str, report_format: str) -> str:
if report_format == "short-bullets":
return (
f"- Total sales: {total:.2f} {currency}\n"
f"- Orders: {orders}\n"
"- Status: stable"
)
return (
f"Sales report: total={total:.2f} {currency}, "
f"orders={orders}, status=stable"
)
agent.py - preference resolution logic
from memory import LongMemoryStore, ShortMemory
from tools import get_orders_count, get_sales_total, render_report
DEFAULT_PREFS = {
"report_format": "default",
"currency": "USD",
}
def save_user_preferences(
*,
user_key: str,
prefs: dict[str, str],
short_memory: ShortMemory,
long_memory: LongMemoryStore,
) -> None:
short_memory.add("user", f"Save prefs: {prefs}")
long_memory.save_prefs(user_key, prefs)
short_memory.add("assistant", "Preferences saved to long-term memory")
def parse_prefs_from_short_memory(short_memory: ShortMemory) -> dict[str, str]:
# Simplified parser for learning: looks for lines like "pref:key=value".
parsed: dict[str, str] = {}
for item in short_memory.snapshot():
content = item["content"]
if "pref:" not in content:
continue
payload = content.split("pref:", 1)[1]
if "=" not in payload:
continue
key, value = payload.split("=", 1)
parsed[key.strip()] = value.strip()
return parsed
def build_weekly_report(
*,
user_id: int,
user_key: str,
request: str,
short_memory: ShortMemory,
long_memory: LongMemoryStore,
use_long_memory: bool,
) -> dict:
trace: list[str] = []
short_memory.add("user", request)
trace.append(f"request={request}")
short_prefs = parse_prefs_from_short_memory(short_memory)
trace.append(f"short_prefs={short_prefs}")
long_prefs = long_memory.load_prefs(user_key) if use_long_memory else {}
trace.append(f"long_prefs={long_prefs}")
prefs = {**DEFAULT_PREFS, **short_prefs, **long_prefs}
trace.append(f"resolved_prefs={prefs}")
total = get_sales_total(user_id)
orders = get_orders_count(user_id)
report = render_report(
total=total,
orders=orders,
currency=prefs["currency"],
report_format=prefs["report_format"],
)
short_memory.add("assistant", f"Report generated with prefs={prefs}")
return {
"prefs": prefs,
"report": report,
"trace": trace,
"short_memory_snapshot": short_memory.snapshot(),
}
main.py - comparison of two scenarios
from agent import build_weekly_report, save_user_preferences
from memory import LongMemoryStore, ShortMemory
USER_ID = 42
USER_KEY = "user:anna"
def print_result(title: str, result: dict) -> None:
print(f"\n=== {title} ===")
print("Resolved prefs:", result["prefs"])
print("\nReport:")
print(result["report"])
print("\nTrace:")
for line in result["trace"]:
print(" ", line)
def main() -> None:
long_memory = LongMemoryStore()
# Scenario 1: only short-term memory, early instruction falls out of context.
short_memory_1 = ShortMemory(max_items=4)
short_memory_1.add("user", "pref:report_format=short-bullets")
short_memory_1.add("user", "pref:currency=EUR")
short_memory_1.add("assistant", "working...")
short_memory_1.add("assistant", "still working...")
short_memory_1.add("assistant", "collecting data...") # pushes out old prefs
result_short_only = build_weekly_report(
user_id=USER_ID,
user_key=USER_KEY,
request="Build weekly sales report",
short_memory=short_memory_1,
long_memory=long_memory,
use_long_memory=False,
)
print_result("SCENARIO 1: SHORT MEMORY ONLY", result_short_only)
# Scenario 2: persist prefs in long-term memory, then start a new task.
short_memory_2 = ShortMemory(max_items=4)
save_user_preferences(
user_key=USER_KEY,
prefs={"report_format": "short-bullets", "currency": "EUR"},
short_memory=short_memory_2,
long_memory=long_memory,
)
short_memory_2.clear() # new task, short memory resets
result_with_long = build_weekly_report(
user_id=USER_ID,
user_key=USER_KEY,
request="Build weekly sales report like last time",
short_memory=short_memory_2,
long_memory=long_memory,
use_long_memory=True,
)
print_result("SCENARIO 2: WITH LONG MEMORY", result_with_long)
if __name__ == "__main__":
main()
requirements.txt
# No external dependencies for this learning example.
Example Output
python main.py
=== SCENARIO 1: SHORT MEMORY ONLY ===
Resolved prefs: {'report_format': 'default', 'currency': 'USD'}
Report:
Sales report: total=12400.00 USD, orders=31, status=stable
Trace:
request=Build weekly sales report
short_prefs={}
long_prefs={}
resolved_prefs={'report_format': 'default', 'currency': 'USD'}
=== SCENARIO 2: WITH LONG MEMORY ===
Resolved prefs: {'report_format': 'short-bullets', 'currency': 'EUR'}
Report:
- Total sales: 12400.00 EUR
- Orders: 31
- Status: stable
Trace:
request=Build weekly sales report like last time
short_prefs={}
long_prefs={'report_format': 'short-bullets', 'currency': 'EUR'}
resolved_prefs={'report_format': 'short-bullets', 'currency': 'EUR'}
What You See in Practice
| Short memory only | Short + long memory | |
|---|---|---|
| Keeps prefs across tasks | β | β |
| Resilient to early-context loss | β | β |
| Format/currency reproduced "like last time" | β | β |
What to Change in This Example
- Reduce
max_itemsto2and see how quickly prefs are lost in short memory - Add a third user preference (for example,
timezone) and propagate it into the report - Save
last_report_totalin long memory and compare trend in the next task - Add a rule: do not allow empty prefs in
save_user_preferences
Full Code on GitHub
The repository contains the full version of this demo: short-term/long-term memory, scenario comparison, and step trace.
View full code on GitHub β