windlass.providers.memory.conversation¶
conversation
¶
Conversation memory: buffer, sliding window and summarising strategies.
All three are recency-based — they replay recent turns into the prompt. The difference is what they do when the conversation outgrows the context window:
- :class:
BufferMemorykeeps everything and eventually overflows. Correct for short sessions, wrong for long ones. - :class:
WindowMemorykeeps the lastnturns. Cheap, predictable, forgets the beginning. - :class:
SummaryMemorysummarises what falls out of the window, so the model keeps a compressed account of the whole conversation.
All are keyed by thread_id and safe to share across concurrent sessions.
Example
from windlass.core.types import Message m = WindowMemory(window=2) m.add([Message.user("one"), Message.user("two"), Message.user("three")]) [msg.content for msg in m.get()]['two', 'three']
BufferMemory
¶
Bases: Memory
Stores the complete transcript for each thread.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
max_messages
|
int | None
|
Ceiling on how many messages :meth: |
None
|
max_stored
|
int
|
Hard ceiling on messages retained per thread, to bound memory in a long-running process. Oldest messages are dropped. |
1000
|
**config
|
Any
|
Forwarded to :class: |
{}
|
Example
from windlass.core.types import Message m = BufferMemory() m.add(Message.user("hi"), thread_id="a") m.get(thread_id="a")[0].content 'hi' m.get(thread_id="b") []
Source code in src\windlass\providers\memory\conversation.py
aadd
async
¶
Append messages to a thread.
Source code in src\windlass\providers\memory\conversation.py
aget
async
¶
aget(
*,
thread_id: str = DEFAULT_THREAD,
query: str | None = None,
limit: int | None = None
) -> list[Message]
Return the thread's messages, oldest first.
Source code in src\windlass\providers\memory\conversation.py
aclear
async
¶
Forget one thread, or all of them.
WindowMemory
¶
Bases: BufferMemory
Keeps a sliding window of recent messages.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
window
|
int
|
How many messages to retain and return. Counted in messages, not turns, so a user/assistant exchange is two. |
20
|
**config
|
Any
|
Forwarded to :class: |
{}
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Note
A window that cuts between an assistant's tool call and the matching
tool result produces a transcript some providers reject. :meth:aget
detects that and widens the window to keep the pair together.
Example
from windlass.core.types import Message m = WindowMemory(window=1) m.add([Message.user("a"), Message.user("b")]) [x.content for x in m.get()]['b']
Source code in src\windlass\providers\memory\conversation.py
aget
async
¶
aget(
*,
thread_id: str = DEFAULT_THREAD,
query: str | None = None,
limit: int | None = None
) -> list[Message]
Return the most recent messages, keeping tool pairs intact.
Source code in src\windlass\providers\memory\conversation.py
SummaryMemory
¶
SummaryMemory(
*, llm: LLM | None = None, window: int = 10, summarize_every: int = 6, **config: Any
)
Bases: BufferMemory
Keeps recent turns verbatim and a running summary of everything older.
When the transcript exceeds window, the overflow is folded into a
summary that is replayed as a system message. The conversation stays inside
the context window without simply forgetting its own beginning.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
llm
|
LLM | None
|
Model used to write summaries. Use a small, cheap one. |
None
|
window
|
int
|
How many recent messages to keep verbatim. |
10
|
summarize_every
|
int
|
Summarise once this many messages have overflowed, so the model is not called on every single turn. |
6
|
**config
|
Any
|
Forwarded to :class: |
{}
|
Raises:
| Type | Description |
|---|---|
ConfigurationError
|
When no model is supplied. |
Performance
One model call per summarize_every overflowed messages, made inline
during :meth:aget. Summarisation failures are logged and skipped —
memory degrades to a plain window rather than breaking the conversation.
Example
from windlass.core.types import Message from windlass.providers.llm.fake import FakeLLM m = SummaryMemory( ... llm=FakeLLM(responses=["User greeted twice."]), ... window=2, ... summarize_every=2, ... ) m.add([Message.user(f"msg {i}") for i in range(6)]) recalled = m.get() recalled[0].role.value 'system'
Source code in src\windlass\providers\memory\conversation.py
aget
async
¶
aget(
*,
thread_id: str = DEFAULT_THREAD,
query: str | None = None,
limit: int | None = None
) -> list[Message]
Return a summary message followed by the recent window.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
thread_id
|
str
|
Conversation to recall. |
DEFAULT_THREAD
|
query
|
str | None
|
Ignored — recall is recency-based. |
None
|
limit
|
int | None
|
Override for the window size. |
None
|
Returns:
| Type | Description |
|---|---|
list[Message]
|
The recalled messages, with any summary prepended as a system |
list[Message]
|
message. |
Source code in src\windlass\providers\memory\conversation.py
summary
¶
aclear
async
¶
Forget messages and summaries.