Skip to content

Commit 8542276

Browse files
committed
Make client-side IDs required in working memory
1 parent f1944d2 commit 8542276

17 files changed

+2119
-1718
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ A Redis-powered memory server built for AI agents and applications. It manages b
2828
- Background task processing for memory indexing and promotion
2929
- Unified search across working memory and long-term memory
3030

31+
For detailed information about memory types, their differences, and when to use each, see the [Memory Types Guide](docs/memory-types.md).
32+
3133
## Authentication
3234

3335
The Redis Agent Memory Server supports OAuth2/JWT Bearer token authentication for secure API access. It's compatible with Auth0, AWS Cognito, Okta, Azure AD, and other standard OAuth2 providers.

agent_memory_server/api.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from fastapi import APIRouter, Depends, HTTPException
33
from mcp.server.fastmcp.prompts import base
44
from mcp.types import TextContent
5+
from ulid import ULID
56

67
from agent_memory_server import long_term_memory, working_memory
78
from agent_memory_server.auth import UserInfo, get_current_user
@@ -278,6 +279,7 @@ async def put_session_memory(
278279

279280
memories = [
280281
MemoryRecord(
282+
id=str(ULID()),
281283
session_id=session_id,
282284
text=f"{msg.role}: {msg.content}",
283285
namespace=updated_memory.namespace,

agent_memory_server/client/api.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
)
2424
from agent_memory_server.models import (
2525
AckResponse,
26+
ClientMemoryRecord,
2627
CreateMemoryRecordRequest,
2728
HealthCheckResponse,
2829
MemoryPromptRequest,
@@ -300,7 +301,7 @@ async def set_working_memory_data(
300301
async def add_memories_to_working_memory(
301302
self,
302303
session_id: str,
303-
memories: list[MemoryRecord],
304+
memories: list[ClientMemoryRecord | MemoryRecord],
304305
namespace: str | None = None,
305306
replace: bool = False,
306307
) -> WorkingMemoryResponse:
@@ -368,7 +369,7 @@ async def add_memories_to_working_memory(
368369
return await self.put_session_memory(session_id, working_memory)
369370

370371
async def create_long_term_memory(
371-
self, memories: list[MemoryRecord]
372+
self, memories: list[ClientMemoryRecord | MemoryRecord]
372373
) -> AckResponse:
373374
"""
374375
Create long-term memories for later retrieval.

agent_memory_server/long_term_memory.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -494,7 +494,7 @@ async def compact_long_term_memories(
494494
discrete_memory_extracted_value = "t"
495495

496496
memory_obj = MemoryRecord(
497-
id_=memory_id,
497+
id=memory_id,
498498
text=str(memory_data.get("text", "")),
499499
user_id=str(memory_data.get("user_id"))
500500
if memory_data.get("user_id")
@@ -664,7 +664,7 @@ async def index_long_term_memories(
664664
async with redis.pipeline(transaction=False) as pipe:
665665
for idx, vector in enumerate(embeddings):
666666
memory = processed_memories[idx]
667-
id_ = memory.id_ if memory.id_ else str(ULID())
667+
id_ = memory.id if memory.id else str(ULID())
668668
key = Keys.memory_key(id_, memory.namespace)
669669

670670
# Generate memory hash for the memory
@@ -861,7 +861,8 @@ async def search_long_term_memories(
861861

862862
results.append(
863863
MemoryRecordResult(
864-
id_=safe_get(doc, "id_"),
864+
id=safe_get(doc, "id_")
865+
or safe_get(doc, "id", ""), # Use id_ or fallback to id
865866
text=safe_get(doc, "text", ""),
866867
dist=float(safe_get(doc, "vector_distance", 0)),
867868
created_at=datetime.fromtimestamp(int(safe_get(doc, "created_at", 0))),
@@ -876,7 +877,6 @@ async def search_long_term_memories(
876877
entities=doc_entities,
877878
memory_hash=safe_get(doc, "memory_hash"),
878879
memory_type=safe_get(doc, "memory_type", "message"),
879-
id=safe_get(doc, "id"),
880880
persisted_at=datetime.fromtimestamp(
881881
int(safe_get(doc, "persisted_at", 0))
882882
)
@@ -1049,7 +1049,7 @@ async def search_memories(
10491049
if text.lower() in memory.text.lower():
10501050
working_memory_results.append(
10511051
MemoryRecordResult(
1052-
id_=memory.id_ or "",
1052+
id=memory.id or "", # Use id instead of id_
10531053
text=memory.text,
10541054
dist=0.0, # No vector distance for working memory
10551055
created_at=memory.created_at or 0,
@@ -1062,7 +1062,6 @@ async def search_memories(
10621062
entities=memory.entities or [],
10631063
memory_hash="", # Working memory doesn't have hash
10641064
memory_type=memory.memory_type,
1065-
id=memory.id,
10661065
persisted_at=memory.persisted_at,
10671066
event_date=memory.event_date,
10681067
)
@@ -1427,7 +1426,7 @@ async def deduplicate_by_semantic_search(
14271426

14281427
# Convert back to LongTermMemory
14291428
merged_memory_obj = MemoryRecord(
1430-
id_=memory.id_ or str(ULID()),
1429+
id=memory.id or str(ULID()),
14311430
text=merged_memory["text"],
14321431
user_id=merged_memory["user_id"],
14331432
session_id=merged_memory["session_id"],

agent_memory_server/models.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
from mcp.server.fastmcp.prompts import base
77
from pydantic import BaseModel, Field
8+
from ulid import ULID
89

910
from agent_memory_server.config import settings
1011
from agent_memory_server.filters import (
@@ -78,11 +79,8 @@ class SessionListResponse(BaseModel):
7879
class MemoryRecord(BaseModel):
7980
"""A memory record"""
8081

82+
id: str = Field(description="Client-provided ID for deduplication and overwrites")
8183
text: str
82-
id_: str | None = Field(
83-
default=None,
84-
description="Optional ID for the memory record",
85-
)
8684
session_id: str | None = Field(
8785
default=None,
8886
description="Optional session ID for the memory record",
@@ -127,10 +125,6 @@ class MemoryRecord(BaseModel):
127125
default=MemoryTypeEnum.MESSAGE,
128126
description="Type of memory",
129127
)
130-
id: str | None = Field(
131-
default=None,
132-
description="Client-provided ID for deduplication and overwrites",
133-
)
134128
persisted_at: datetime | None = Field(
135129
default=None,
136130
description="Server-assigned timestamp when memory was persisted to long-term storage",
@@ -145,6 +139,15 @@ class MemoryRecord(BaseModel):
145139
)
146140

147141

142+
class ClientMemoryRecord(MemoryRecord):
143+
"""A memory record with a client-provided ID"""
144+
145+
id: str = Field(
146+
default=str(ULID()),
147+
description="Client-provided ID for deduplication and overwrites",
148+
)
149+
150+
148151
class WorkingMemory(BaseModel):
149152
"""Working memory for a session - contains both messages and structured memory records"""
150153

@@ -153,7 +156,7 @@ class WorkingMemory(BaseModel):
153156
default_factory=list,
154157
description="Conversation messages (role/content pairs)",
155158
)
156-
memories: list[MemoryRecord] = Field(
159+
memories: list[MemoryRecord | ClientMemoryRecord] = Field(
157160
default_factory=list,
158161
description="Structured memory records for promotion to long-term storage",
159162
)

agent_memory_server/utils/redis.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ def get_search_index(
7171
"type": "vector",
7272
"attrs": {
7373
"algorithm": "HNSW",
74-
"dims": vector_dimensions,
74+
"dims": int(vector_dimensions),
7575
"distance_metric": distance_metric,
7676
"datatype": "float32",
7777
},

0 commit comments

Comments
 (0)