Stateful Memory Serialization
architect, critique, revise, code, testModule 4: Stateful Memory Serialization
Goal: Implement a conversation history class that saves logs to disk and prunes history to keep LLM context sizes small.
What you need to know first
The Stateless Border: Large Language Models are completely stateless. They do not remember what you asked them three seconds ago. To build a continuous chat conversation, you must feed the entire history of past questions and answers back into the API on every new request.
The SDK Serialization Trap: The Gemini SDK uses rich Python classes (like GenerateContentResponse) to hold results. If you try to save these classes directly to disk using json.dump(), Python will crash with a TypeError because the classes are not JSON-serializable.
- You must extract the raw text and role names and convert them into flat Python dictionaries (like
{"role": "user", "text": "..."}) before serialization.
Token Bloat & Rate Limits: The more messages you append to your history, the more tokens you send. This will quickly hit the free-tier rate limits. A standard solution is a sliding-window buffer that keeps only the last few messages in memory.
The SOLO idea
Automation logic must be testable. Under Pillar 05: Test-Driven, we build a memory manager class separate from the LLM client, allowing us to write unit tests that verify historical pruning without executing slow API requests.
Lab 4: Local Memory Management
Estimated AI conversations: 2-3
- Create a script named
memory.py. - Step A: Plan the Memory Class. Open Copilot Chat and type:
Follow #file:workflows/architect.md — Plan a sliding-window conversation memory manager in
memory.py.- Write a class
ChatMemorythat loads and saves messages to a localhistory.jsonfile. - Messages should be stored as a list of flat dictionaries:
[{"role": "user", "text": "..."}, {"role": "model", "text": "..."}]. - Implement an
add_message(role, text)method. - Implement a
get_context()method that returns only the last 5 messages, automatically pruning older entries.
- Write a class
- Step B: Critique serialization boundaries. In the same conversation, type:
Follow #file:workflows/critique.md — Critique the memory manager design. What happens if the
history.jsonfile is corrupted or contains invalid JSON syntax? Does the class handle loading empty files gracefully? - Step C: Revise.
Follow #file:workflows/revise.md — Revise the code to add fallback try-except blocks for corrupted files, returning a clean history array.
- Step D: Implement.
Follow #file:workflows/code.md — Write the code in
memory.py. - Review your class implementation, ensuring it maps messages to flat list types:
import json import os class ChatMemory: def __init__(self, filename="history.json", max_turns=5): self.filename = filename self.max_turns = max_turns self.history = self.load_history() def load_history(self) -> list: if not os.path.exists(self.filename): return [] try: with open(self.filename, "r") as f: return json.load(f) except (json.JSONDecodeError, IOError): print(f"[Warning] Corrupted memory file {self.filename}. Initializing fresh.") return [] def save_history(self): try: with open(self.filename, "w") as f: json.dump(self.history, f, indent=2) except IOError as e: print(f"[Error] Failed to write memory: {e}") def add_message(self, role: str, text: str): self.history.append({"role": role, "text": text}) # Sliding window: keep only the last max_turns messages if len(self.history) > self.max_turns * 2: # 5 turns = 10 messages (user + model) self.history = self.history[-(self.max_turns * 2):] self.save_history() def get_context(self) -> list: return self.history - Write Unit Tests: Create a test file
test_memory.pyand write tests to assert:- Adding a 11th message automatically trims the oldest message from
history.json. - Corrupted JSON syntax in
history.jsonis handled without throwing crashes.
- Adding a 11th message automatically trims the oldest message from
- Run your tests locally:
pytest -v test_memory.py
Checkpoint
Create checkpoint_04.md:
- Paste the test suite code from
test_memory.py. - Paste your terminal's
pytestpassing output. - Explain in 1 sentence why converting SDK response objects to flat dictionaries is necessary before calling
json.dump().