Skip to content

Commit 7752778

Browse files
committed
Merge branch 'main' into long-term-memory-interface
2 parents 5849f71 + d1bffa0 commit 7752778

23 files changed

+266
-204
lines changed

CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ uv run ruff check # Run linting
1616
uv run ruff format # Format code
1717
uv run pytest # Run tests
1818
uv run pytest tests/ # Run specific test directory
19+
uv run pytest --run-api-tests # Run all tests, including API tests
1920
uv add <dependency> # Add a dependency to pyproject.toml and update lock file
2021
uv remove <dependency> # Remove a dependency from pyproject.toml and update lock file
2122

agent-memory-client/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,8 +206,8 @@ await client.update_working_memory_data(
206206

207207
# Append messages
208208
new_messages = [
209-
MemoryMessage(role="user", content="What's the weather?"),
210-
MemoryMessage(role="assistant", content="It's sunny today!")
209+
{"role": "user", "content": "What's the weather?"},
210+
{"role": "assistant", "content": "It's sunny today!"}
211211
]
212212

213213
await client.append_messages_to_working_memory(

agent-memory-client/agent_memory_client/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
memory management capabilities for AI agents and applications.
66
"""
77

8-
__version__ = "0.9.0b3"
8+
__version__ = "0.9.0b4"
99

1010
from .client import MemoryAPIClient, MemoryClientConfig, create_memory_client
1111
from .exceptions import (

agent-memory-client/agent_memory_client/client.py

Lines changed: 76 additions & 68 deletions
Large diffs are not rendered by default.

agent-memory-client/tests/test_client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -527,8 +527,8 @@ async def test_append_messages_to_working_memory(self, enhanced_test_client):
527527
)
528528

529529
new_messages = [
530-
MemoryMessage(role="assistant", content="Second message"),
531-
MemoryMessage(role="user", content="Third message"),
530+
{"role": "assistant", "content": "Second message"},
531+
{"role": "user", "content": "Third message"},
532532
]
533533

534534
with (

agent_memory_server/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
"""Redis Agent Memory Server - A memory system for conversational AI."""
22

3-
__version__ = "0.9.0b3"
3+
__version__ = "0.9.0b4"

agent_memory_server/api.py

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ async def list_sessions(
188188
Get a list of session IDs, with optional pagination.
189189
190190
Args:
191-
options: Query parameters (page, size, namespace)
191+
options: Query parameters (limit, offset, namespace, user_id)
192192
193193
Returns:
194194
List of session IDs
@@ -200,6 +200,7 @@ async def list_sessions(
200200
limit=options.limit,
201201
offset=options.offset,
202202
namespace=options.namespace,
203+
user_id=options.user_id,
203204
)
204205

205206
return SessionListResponse(
@@ -211,8 +212,8 @@ async def list_sessions(
211212
@router.get("/v1/working-memory/{session_id}", response_model=WorkingMemoryResponse)
212213
async def get_working_memory(
213214
session_id: str,
215+
user_id: str | None = None,
214216
namespace: str | None = None,
215-
window_size: int = settings.window_size, # Deprecated: kept for backward compatibility
216217
model_name: ModelNameLiteral | None = None,
217218
context_window_max: int | None = None,
218219
current_user: UserInfo = Depends(get_current_user),
@@ -225,8 +226,8 @@ async def get_working_memory(
225226
226227
Args:
227228
session_id: The session ID
229+
user_id: The user ID to retrieve working memory for
228230
namespace: The namespace to use for the session
229-
window_size: DEPRECATED - The number of messages to include (kept for backward compatibility)
230231
model_name: The client's LLM model name (will determine context window size if provided)
231232
context_window_max: Direct specification of the context window max tokens (overrides model_name)
232233
@@ -240,6 +241,7 @@ async def get_working_memory(
240241
session_id=session_id,
241242
namespace=namespace,
242243
redis_client=redis,
244+
user_id=user_id,
243245
)
244246

245247
if not working_mem:
@@ -249,6 +251,7 @@ async def get_working_memory(
249251
memories=[],
250252
session_id=session_id,
251253
namespace=namespace,
254+
user_id=user_id,
252255
)
253256

254257
# Apply token-based truncation if we have messages and model info
@@ -266,17 +269,14 @@ async def get_working_memory(
266269
break
267270
working_mem.messages = truncated_messages
268271

269-
# Fallback to message-count truncation for backward compatibility
270-
elif len(working_mem.messages) > window_size:
271-
working_mem.messages = working_mem.messages[-window_size:]
272-
273272
return working_mem
274273

275274

276275
@router.put("/v1/working-memory/{session_id}", response_model=WorkingMemoryResponse)
277276
async def put_working_memory(
278277
session_id: str,
279278
memory: WorkingMemory,
279+
user_id: str | None = None,
280280
model_name: ModelNameLiteral | None = None,
281281
context_window_max: int | None = None,
282282
background_tasks=Depends(get_background_tasks),
@@ -291,6 +291,7 @@ async def put_working_memory(
291291
Args:
292292
session_id: The session ID
293293
memory: Working memory to save
294+
user_id: Optional user ID for the session (overrides user_id in memory object)
294295
model_name: The client's LLM model name for context window determination
295296
context_window_max: Direct specification of context window max tokens
296297
background_tasks: DocketBackgroundTasks instance (injected automatically)
@@ -303,6 +304,10 @@ async def put_working_memory(
303304
# Ensure session_id matches
304305
memory.session_id = session_id
305306

307+
# Override user_id if provided as query parameter
308+
if user_id is not None:
309+
memory.user_id = user_id
310+
306311
# Validate that all structured memories have id (if any)
307312
for mem in memory.memories:
308313
if not mem.id:
@@ -359,6 +364,7 @@ async def put_working_memory(
359364
@router.delete("/v1/working-memory/{session_id}", response_model=AckResponse)
360365
async def delete_working_memory(
361366
session_id: str,
367+
user_id: str | None = None,
362368
namespace: str | None = None,
363369
current_user: UserInfo = Depends(get_current_user),
364370
):
@@ -369,6 +375,7 @@ async def delete_working_memory(
369375
370376
Args:
371377
session_id: The session ID
378+
user_id: Optional user ID for the session
372379
namespace: Optional namespace for the session
373380
374381
Returns:
@@ -379,6 +386,7 @@ async def delete_working_memory(
379386
# Delete unified working memory
380387
await working_memory.delete_working_memory(
381388
session_id=session_id,
389+
user_id=user_id,
382390
namespace=namespace,
383391
redis_client=redis,
384392
)
@@ -557,6 +565,7 @@ async def memory_prompt(
557565
working_mem = await working_memory.get_working_memory(
558566
session_id=params.session.session_id,
559567
namespace=params.session.namespace,
568+
user_id=params.session.user_id,
560569
redis_client=redis,
561570
)
562571

agent_memory_server/config.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@
5050
class Settings(BaseSettings):
5151
redis_url: str = "redis://localhost:6379"
5252
long_term_memory: bool = True
53-
window_size: int = 20
5453
openai_api_key: str | None = None
5554
anthropic_api_key: str | None = None
5655
generation_model: str = "gpt-4o-mini"
@@ -109,6 +108,9 @@ class Settings(BaseSettings):
109108
auth0_client_id: str | None = None
110109
auth0_client_secret: str | None = None
111110

111+
# Working memory settings
112+
window_size: int = 20 # Default number of recent messages to return
113+
112114
# Other Application settings
113115
log_level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] = "INFO"
114116

agent_memory_server/dependencies.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,19 @@ async def add_task(
2727
logger.info("Scheduling task through Docket")
2828
# Get the Redis connection that's already configured (will use testcontainer in tests)
2929
redis_conn = await get_redis_conn()
30-
# Use the connection's URL instead of settings.redis_url directly
31-
redis_url = redis_conn.connection_pool.connection_kwargs.get(
32-
"url", settings.redis_url
33-
)
30+
31+
# Extract Redis URL from the connection pool
32+
connection_kwargs = redis_conn.connection_pool.connection_kwargs
33+
if "host" in connection_kwargs and "port" in connection_kwargs:
34+
redis_url = (
35+
f"redis://{connection_kwargs['host']}:{connection_kwargs['port']}"
36+
)
37+
if "db" in connection_kwargs:
38+
redis_url += f"/{connection_kwargs['db']}"
39+
else:
40+
# Fallback to settings if we can't extract from connection
41+
redis_url = settings.redis_url
42+
3443
logger.info("redis_url: %s", redis_url)
3544
logger.info("docket_name: %s", settings.docket_name)
3645
async with Docket(

agent_memory_server/long_term_memory.py

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,6 @@
4242
from agent_memory_server.vectorstore_factory import get_vectorstore_adapter
4343

4444

45-
DEFAULT_MEMORY_LIMIT = 1000
46-
MEMORY_INDEX = "memory_idx"
47-
4845
# Prompt for extracting memories from messages in working memory context
4946
WORKING_MEMORY_EXTRACTION_PROMPT = """
5047
You are a memory extraction assistant. Your job is to analyze conversation
@@ -350,15 +347,27 @@ async def compact_long_term_memories(
350347
# Find all memories with this hash
351348
# Use FT.SEARCH to find the actual memories with this hash
352349
# TODO: Use RedisVL index
353-
search_query = (
354-
f"FT.SEARCH {index_name} "
355-
f"(@memory_hash:{{{memory_hash}}}) {' '.join(filters)} "
356-
"RETURN 6 id_ text last_accessed created_at user_id session_id "
357-
"SORTBY last_accessed ASC" # Oldest first
358-
)
350+
if filters:
351+
# Combine hash query with filters using boolean AND
352+
query_expr = f"(@memory_hash:{{{memory_hash}}}) ({' '.join(filters)})"
353+
else:
354+
query_expr = f"@memory_hash:{{{memory_hash}}}"
359355

360356
search_results = await redis_client.execute_command(
361-
search_query
357+
"FT.SEARCH",
358+
index_name,
359+
f"'{query_expr}'",
360+
"RETURN",
361+
"6",
362+
"id_",
363+
"text",
364+
"last_accessed",
365+
"created_at",
366+
"user_id",
367+
"session_id",
368+
"SORTBY",
369+
"last_accessed",
370+
"ASC",
362371
)
363372

364373
if search_results and search_results[0] > 1:
@@ -1066,15 +1075,25 @@ async def deduplicate_by_id(
10661075

10671076
# Use FT.SEARCH to find memories with this id
10681077
# TODO: Use RedisVL
1069-
search_query = (
1070-
f"FT.SEARCH {index_name} "
1071-
f"(@id:{{{memory.id}}}) {filter_str} "
1072-
"RETURN 2 id_ persisted_at "
1073-
"SORTBY last_accessed DESC" # Newest first
1078+
if filter_str:
1079+
# Combine the id query with filters - Redis FT.SEARCH uses implicit AND between terms
1080+
query_expr = f"@id:{{{memory.id}}} {filter_str}"
1081+
else:
1082+
query_expr = f"@id:{{{memory.id}}}"
1083+
1084+
search_results = await redis_client.execute_command(
1085+
"FT.SEARCH",
1086+
index_name,
1087+
f"'{query_expr}'",
1088+
"RETURN",
1089+
"2",
1090+
"id_",
1091+
"persisted_at",
1092+
"SORTBY",
1093+
"last_accessed",
1094+
"DESC",
10741095
)
10751096

1076-
search_results = await redis_client.execute_command(search_query)
1077-
10781097
if search_results and search_results[0] > 0:
10791098
# Found existing memory with the same id
10801099
logger.info(f"Found existing memory with id {memory.id}, will overwrite")

0 commit comments

Comments
 (0)