windlass.providers.memory.longterm¶
longterm
¶
Long-term memory: durable facts recalled by relevance.
Conversation memory answers "what did we just say?". Long-term memory answers "what do I know about this user?" — and the right recall mechanism for that is similarity, not recency. A preference stated three months ago should surface when it becomes relevant again, not scroll out of a window.
:class:VectorMemory embeds each remembered item and retrieves the closest ones
to the current input. It works with any Windlass vector store, so the same code
runs against an in-memory index in tests and Pinecone in production.
Example
from windlass.providers.embeddings.hash import HashEmbedder from windlass.core.types import Message memory = VectorMemory( ... embedder=HashEmbedder(dimensions=128), top_k=1, score_threshold=None ... ) memory.add(Message.user("I am allergic to peanuts")) memory.add(Message.user("My favourite colour is blue")) "peanuts" in memory.get(query="what foods should I avoid")[0].content True
VectorMemory
¶
VectorMemory(
*,
embedder: Embedder | None = None,
vectorstore: VectorStore | None = None,
top_k: int = 5,
score_threshold: float | None = 0.2,
store_roles: tuple[str, ...] = ("user", "assistant"),
collection: str = "windlass-memory",
**config: Any
)
Bases: Memory
Long-term memory backed by a vector store.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
embedder
|
Embedder | None
|
Model used to embed remembered items and the recall query. Required. |
None
|
vectorstore
|
VectorStore | None
|
Where memories live. Defaults to a fresh in-memory store, which is fine for a single process but does not survive a restart — pass a persistent store for real deployments. |
None
|
top_k
|
int
|
How many memories to recall per query. |
5
|
score_threshold
|
float | None
|
Drop recalled memories scoring below this. Prevents irrelevant memories being injected into every prompt just because they were the closest of a bad set. |
0.2
|
store_roles
|
tuple[str, ...]
|
Which message roles are worth remembering. Assistant chatter usually is not. |
('user', 'assistant')
|
collection
|
str
|
Collection name used when creating the default store. |
'windlass-memory'
|
**config
|
Any
|
Forwarded to :class: |
{}
|
Raises:
| Type | Description |
|---|---|
ConfigurationError
|
When no embedder is supplied. |
Performance
One embedding call per :meth:aadd batch and one per :meth:aget.
Recall cost is whatever the underlying store charges.
Source code in src\windlass\providers\memory\longterm.py
native
¶
aadd
async
¶
Embed and store messages worth remembering.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
messages
|
Message | Sequence[Message]
|
A message or a sequence of them. |
required |
thread_id
|
str
|
Conversation this belongs to; recorded in metadata so recall can be scoped per user. |
DEFAULT_THREAD
|
Source code in src\windlass\providers\memory\longterm.py
aget
async
¶
aget(
*,
thread_id: str = DEFAULT_THREAD,
query: str | None = None,
limit: int | None = None
) -> list[Message]
Recall the memories most relevant to query.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
thread_id
|
str
|
Scope recall to one conversation. |
DEFAULT_THREAD
|
query
|
str | None
|
The current user input. Without it, nothing is recalled — relevance-based memory has no notion of "the recent ones". |
None
|
limit
|
int | None
|
Override for :attr: |
None
|
Returns:
| Type | Description |
|---|---|
list[Message]
|
Recalled messages, oldest first so they read as history. |
Source code in src\windlass\providers\memory\longterm.py
aclear
async
¶
Forget one thread's memories, or all of them.
Source code in src\windlass\providers\memory\longterm.py
aremember
async
¶
Store a bare fact, without wrapping it in a conversation turn.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fact
|
str
|
The text to remember. |
required |
thread_id
|
str
|
Scope for the memory. |
DEFAULT_THREAD
|
**metadata
|
Any
|
Extra fields stored alongside it. |
{}
|
Example
import asyncio from windlass.providers.embeddings.hash import HashEmbedder m = VectorMemory(embedder=HashEmbedder(dimensions=64)) asyncio.run(m.aremember("prefers dark mode", source="settings"))
Source code in src\windlass\providers\memory\longterm.py
remember
¶
Blocking :meth:aremember.
Source code in src\windlass\providers\memory\longterm.py
CompositeMemory
¶
Bases: Memory
Runs several memories together.
The usual configuration is a window for the recent transcript plus vector memory for durable facts, which is how a production assistant both follows the current thread and remembers what you told it last month.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
memories
|
Sequence[Memory] | None
|
The memories to combine, in the order their recall should be concatenated. Long-term memories should come first so the recent transcript stays closest to the model's most recent attention. |
None
|
**config
|
Any
|
Forwarded to :class: |
{}
|
Raises:
| Type | Description |
|---|---|
ConfigurationError
|
When fewer than one memory is supplied. |
Example
from windlass.providers.memory.conversation import WindowMemory from windlass.core.types import Message combined = CompositeMemory(memories=[WindowMemory(window=2)]) combined.add(Message.user("hello")) len(combined.get()) 1
Source code in src\windlass\providers\memory\longterm.py
aadd
async
¶
Write to every constituent memory.
Source code in src\windlass\providers\memory\longterm.py
aget
async
¶
aget(
*,
thread_id: str = DEFAULT_THREAD,
query: str | None = None,
limit: int | None = None
) -> list[Message]
Concatenate recall from every constituent memory, de-duplicated.