A ConversationBufferMemory subclass that keeps only the last k turns, using a collections.deque(maxlen=k) instead of an unbounded list. Use this for long-running conversations where only recent context matters and you want automatic, allocation-free eviction of old turns.
Constructor
Python
def__init__(self, k: int = 5, **kwargs)
Parameter
Type
Required
Default
Notes
k
int
No
5
Window size. Must be > 0 — raises ValueError("k must be a positive integer") otherwise.
**kwargs
—
No
—
Forwarded to ConversationBufferMemory.__init__ (return_messages, input_key, output_key, memory_key).
After calling super().__init__(**kwargs) (which sets self.chat_memory = []), the constructor replacesself.chat_memory with deque(maxlen=k), discarding the empty list from the parent constructor.
Inherited behavior
save_context, load_memory_variables, clear, and the async wrappers are all inherited unmodified from ConversationBufferMemory. Because self.chat_memory is now a deque(maxlen=k), .append() in save_context automatically evicts the oldest entry once the deque is full — no explicit eviction logic exists in this subclass.
[!DANGER] get_messages(limit=N) crashes on this class
get_messages is inherited from ConversationBufferMemory and does self.chat_memory[-limit:] when limit is truthy. deque does not support slice indexing in Python, so:
Python · 5 lines
from zerda.memory import ConversationBufferWindowMemory
mem = ConversationBufferWindowMemory(k=5)
mem.save_context({"input": "hi"}, {"output": "hello"})
mem.get_messages(limit=3)
# TypeError: sequence index must be integer, not 'slice'
get_messages() (no limit) works fine, since it falls through to .copy(), which deque supports. Only the limit-provided branch is broken. See Known Issues — this is the most impactful bug found in the memories subpackage since it's triggered by ordinary, documented usage (get_messages(limit=...) is a public method with no warning against calling it this way on a window memory).
Additional public methods
get_memory_stats() -> Dict[str, Any]
Extends the parent's stats dict with type: "ConversationBufferWindowMemory", window_size: self.k, and is_full: len(self.chat_memory) >= self.k.
Same caveats as ConversationBufferMemory — no explicit locking. deque.append() is itself thread-safe at the C level for single appends/pops from either end (CPython implementation detail), but the class provides no compound-operation safety guarantees beyond that.
Complexity
save_context: O(1) — deque append/evict is O(1) regardless of k.
get_window_info: O(1) (uses len(), which is O(1) for deque).
Examples
Python · 26 lines
from zerda.memory import ConversationBufferWindowMemory
# 1. Basic
mem = ConversationBufferWindowMemory(k=3)
for i inrange(5):
mem.save_context({"input": f"msg {i}"}, {"output": f"reply {i}"})
print(len(mem.get_messages())) # 3 — only the last 3 survive# 2. Intermediate — inspect window state
info = mem.get_window_info()
print(info) # {'current_size': 3, 'max_size': 3, 'available_slots': 0, 'is_full': True}# 3. Advanced — custom I/O keys forwarded via **kwargs
mem2 = ConversationBufferWindowMemory(k=10, input_key="q", output_key="a")
mem2.save_context({"q": "2+2?"}, {"a": "4"})
# 4. Error handling — invalid ktry:
ConversationBufferWindowMemory(k=0)
except ValueError as e:
print(e) # "k must be a positive integer"# 5. Production — DO NOT do this (will crash):# mem.get_messages(limit=2) # TypeError# Instead, slice the full list yourself:
recent_2 = mem.get_messages()[-2:] # works — get_messages() returns a plain list via deque.copy()...
[!WARNING] Example 5 caveat
deque.copy() returns another deque, not a list — so mem.get_messages()[-2:]also raises TypeError for the same reason. To safely get the last N messages from a window memory, convert explicitly: list(mem.get_messages())[-2:].
Best practices
Never call get_messages(limit=...) on this class — use get_window_info() for size introspection, and list(mem.get_messages())[-n:] if you need a sub-slice.
Pick k based on your model's context window and the typical length of a single turn, not just a round number.
Common mistakes
Calling get_messages(limit=N) on a ConversationBufferWindowMemory instance, expecting the same behavior as on ConversationBufferMemory — it crashes.
Assuming get_messages()'s return value supports slicing directly — it's a deque, so wrap in list(...) first.