Short-Term Memory Basics
8 examples to get you started with short-term agent memory - 5 basic and 3 intermediate.
You will keep a message list across turns, bound its size, add a tiny scratchpad, and sketch session keys for isolation.
Prerequisites
- Python 3.11+ recommended
- An API key for any OpenAI-compatible chat endpoint
- Comfort with lists and dicts
python -m venv .venv && source .venv/bin/activate
pip install openai
export OPENAI_API_KEY="sk-..."
# Optional gateway:
# export OPENAI_BASE_URL="https://openrouter.ai/api/v1"Basic Examples
1. One-Shot Call Has No Memory
A single completion does not retain prior turns unless you resend them.
from openai import OpenAI
client = OpenAI() # uses OPENAI_API_KEY; set base_url for gateways
model = "gpt-4o-mini" # verify at build
r1 = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": "My name is Ada. Reply in one short sentence."}],
)
print(r1.choices[0].message.content)
r2 = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": "What is my name?"}],
)
print(r2.choices[0].message.content) # model has no prior turn- Each call is independent.
- Without a history list, turn two is a blank slate.
- This is the baseline problem short-term memory solves.
Related: Short-Term Memory: What an Agent Actually Needs Within One Run
2. Append History Across Turns
Keep a messages list and send the full list every turn.
messages = [
{"role": "system", "content": "You are a concise assistant. Remember user facts stated in this chat."},
]
def say(user_text: str) -> str:
messages.append({"role": "user", "content": user_text})
resp = client.chat.completions.create(model=model, messages=messages)
text = resp.choices[0].message.content or ""
messages.append({"role": "assistant", "content": text})
return text
print(say("My name is Ada."))
print(say("What is my name?"))- Append user, then assistant, after each call.
- The model only "remembers" what remains in
messages. - System policy stays first for stable behavior.
3. Inspect What You Are Actually Sending
Debug memory by printing roles and lengths, not only the last reply.
def show_history():
for i, m in enumerate(messages):
content = m.get("content") or ""
print(f"{i}: {m['role']:10} {len(content):4} chars {content[:60]!r}")
show_history()- Token cost tracks total content, not turn count alone.
- Spot accidental double-appends early.
- Later you will measure tokens instead of chars.
4. Bound History With a Sliding Window
Drop oldest non-system messages when the list grows.
def trim_messages(msgs: list, max_messages: int = 12) -> list:
system = [m for m in msgs if m["role"] == "system"]
rest = [m for m in msgs if m["role"] != "system"]
if len(rest) > max_messages:
rest = rest[-max_messages:]
return system + rest
# After each turn:
# messages[:] = trim_messages(messages, max_messages=12)- Protects the context budget with a simple rule.
- May drop early facts (name, constraints) if the window is too tight.
- Prefer token budgets in production; message count is a teaching step.
Related: Managing Conversation History Within a Context Budget
5. Session Dict: History Plus Scratchpad
Separate chat transcript from structured task state.
from typing import Any
session: dict[str, Any] = {
"id": "sess_demo_1",
"messages": [
{"role": "system", "content": "Help the user complete a small checklist."},
],
"scratchpad": {
"goal": None,
"steps_done": [],
},
}
def update_goal(goal: str) -> None:
session["scratchpad"]["goal"] = goal
session["scratchpad"]["steps_done"] = []
update_goal("Plan a 3-item grocery list")
session["messages"].append({
"role": "user",
"content": f"Goal: {session['scratchpad']['goal']}. Suggest three items.",
})
resp = client.chat.completions.create(model=model, messages=session["messages"])
session["messages"].append({"role": "assistant", "content": resp.choices[0].message.content})
session["scratchpad"]["steps_done"].append("suggested_items")
print(session["scratchpad"])- Scratchpad is for machines; messages are for dialogue.
- You control when scratchpad fields change.
- Inject scratchpad into the prompt only when useful.
Intermediate Examples
6. Key Sessions by Tenant and User
Never store all users under one global list.
from collections import defaultdict
# In-process demo only; use Redis or DB in production
STORE: dict[tuple[str, str, str], dict] = defaultdict(lambda: {
"messages": [{"role": "system", "content": "You are helpful and brief."}],
"scratchpad": {},
})
def get_session(tenant_id: str, user_id: str, session_id: str) -> dict:
return STORE[(tenant_id, user_id, session_id)]
a = get_session("acme", "u1", "s1")
b = get_session("acme", "u2", "s1")
a["messages"].append({"role": "user", "content": "secret-from-u1"})
assert b["messages"][-1]["content"] != "secret-from-u1" if len(b["messages"]) > 1 else True- Composite keys prevent cross-user reads.
- Same
session_idstring under different users must not collide. - Production isolation is covered in the cheatsheet later.
Related: Session Isolation
7. Rehydrate a Session After a Process Restart
Serialize messages so a new worker can continue the chat.
import json
from pathlib import Path
path = Path("/tmp/agent_session_demo.json")
path.write_text(json.dumps(session), encoding="utf-8")
restored = json.loads(path.read_text(encoding="utf-8"))
print(restored["id"], len(restored["messages"]))- Disk or Redis both work; format is your contract.
- Validate schema on load (roles, types).
- Add TTL so abandoned sessions expire.
8. Inject a Compact Scratchpad Into Context
Project structured state without dumping the whole store.
def messages_for_model(sess: dict) -> list:
pad = json.dumps(sess["scratchpad"], ensure_ascii=False)
extra = {
"role": "system",
"content": f"Current scratchpad (authoritative JSON):\n{pad}",
}
# Keep base system + scratchpad projection + dialogue
base = [m for m in sess["messages"] if m["role"] == "system"][:1]
dialogue = [m for m in sess["messages"] if m["role"] != "system"]
return base + [extra] + dialogue
# resp = client.chat.completions.create(model=model, messages=messages_for_model(session))- One small JSON blob beats re-parsing long chat for status.
- Keep scratchpad fields tight to avoid prompt bloat.
- Do not put secrets in scratchpad unless required and protected.
Related: Scratchpad Patterns
Related
- Short-Term Memory: What an Agent Actually Needs Within One Run - history vs scratchpad vs cache
- Managing Conversation History Within a Context Budget - trim and summarize recipes
- Redis for Fast Short-Term Agent State - shared session store
- Short-Term Memory Best Practices - production habits
- Long-Term Memory Basics - facts that survive sessions
Stack versions: Pins from the category manifest (verify at build): OpenRouter (~315+ models, July 2026 pricing/fees); LangGraph 1.0+; CrewAI 1.14+; Microsoft Agent Framework 1.0; Vercel AI SDK 6; Pydantic AI (latest); LlamaIndex (latest); OpenAI Agents SDK (latest + MCP); MCP (Linux Foundation governance); A2A (HTTP+SSE+JSON-RPC 2.0); Solana
@solana/web3.js+@solana/spl-token.