7
7
from agent_memory_server .auth import UserInfo , get_current_user
8
8
from agent_memory_server .config import settings
9
9
from agent_memory_server .dependencies import get_background_tasks
10
+ from agent_memory_server .filters import SessionId , UserId
10
11
from agent_memory_server .llms import get_model_client , get_model_config
11
12
from agent_memory_server .logging import get_logger
12
13
from agent_memory_server .models import (
@@ -234,7 +235,6 @@ async def get_working_memory(
234
235
"""
235
236
redis = await get_redis_conn ()
236
237
237
- # Get unified working memory
238
238
working_mem = await working_memory .get_working_memory (
239
239
session_id = session_id ,
240
240
namespace = namespace ,
@@ -243,7 +243,7 @@ async def get_working_memory(
243
243
)
244
244
245
245
if not working_mem :
246
- # Return empty working memory if none exists
246
+ # Create empty working memory if none exists
247
247
working_mem = WorkingMemory (
248
248
messages = [],
249
249
memories = [],
@@ -267,6 +267,8 @@ async def get_working_memory(
267
267
break
268
268
working_mem .messages = truncated_messages
269
269
270
+ logger .debug (f"Working mem: { working_mem } " )
271
+
270
272
return working_mem
271
273
272
274
@@ -314,6 +316,14 @@ async def put_working_memory(
314
316
detail = "All memory records in working memory must have an ID" ,
315
317
)
316
318
319
+ # Validate that all messages have non-empty content
320
+ for msg in memory .messages :
321
+ if not msg .content or not msg .content .strip ():
322
+ raise HTTPException (
323
+ status_code = 400 ,
324
+ detail = f"Message content cannot be empty (message ID: { msg .id } )" ,
325
+ )
326
+
317
327
# Handle summarization if needed (before storing) - now token-based
318
328
updated_memory = memory
319
329
if memory .messages :
@@ -333,8 +343,9 @@ async def put_working_memory(
333
343
# Promote structured memories from working memory to long-term storage
334
344
await background_tasks .add_task (
335
345
long_term_memory .promote_working_memory_to_long_term ,
336
- session_id ,
337
- updated_memory .namespace ,
346
+ session_id = session_id ,
347
+ user_id = updated_memory .user_id ,
348
+ namespace = updated_memory .namespace ,
338
349
)
339
350
340
351
return updated_memory
@@ -431,7 +442,7 @@ async def search_long_term_memory(
431
442
# Extract filter objects from the payload
432
443
filters = payload .get_filters ()
433
444
434
- print ( "Long-term search filters: " , filters )
445
+ logger . debug ( f "Long-term search filters: { filters } " )
435
446
436
447
kwargs = {
437
448
"distance_threshold" : payload .distance_threshold ,
@@ -440,10 +451,10 @@ async def search_long_term_memory(
440
451
** filters ,
441
452
}
442
453
443
- print ("Kwargs: " , kwargs )
444
-
445
454
kwargs ["text" ] = payload .text or ""
446
455
456
+ logger .debug (f"Long-term search kwargs: { kwargs } " )
457
+
447
458
# Pass text and filter objects to the search function (no redis needed for vectorstore adapter)
448
459
return await long_term_memory .search_long_term_memories (** kwargs )
449
460
@@ -466,53 +477,6 @@ async def delete_long_term_memory(
466
477
return AckResponse (status = f"ok, deleted { count } memories" )
467
478
468
479
469
- @router .post ("/v1/memory/search" , response_model = MemoryRecordResultsResponse )
470
- async def search_memory (
471
- payload : SearchRequest ,
472
- current_user : UserInfo = Depends (get_current_user ),
473
- ):
474
- """
475
- Run a search across all memory types (working memory and long-term memory).
476
-
477
- This endpoint searches both working memory (ephemeral, session-scoped) and
478
- long-term memory (persistent, indexed) to provide comprehensive results.
479
-
480
- For working memory:
481
- - Uses simple text matching
482
- - Searches across all sessions (unless session_id filter is provided)
483
- - Returns memories that haven't been promoted to long-term storage
484
-
485
- For long-term memory:
486
- - Uses semantic vector search
487
- - Includes promoted memories from working memory
488
- - Supports advanced filtering by topics, entities, etc.
489
-
490
- Args:
491
- payload: Search payload with filter objects for precise queries
492
-
493
- Returns:
494
- Search results from both memory types, sorted by relevance
495
- """
496
- redis = await get_redis_conn ()
497
-
498
- # Extract filter objects from the payload
499
- filters = payload .get_filters ()
500
-
501
- kwargs = {
502
- "redis" : redis ,
503
- "distance_threshold" : payload .distance_threshold ,
504
- "limit" : payload .limit ,
505
- "offset" : payload .offset ,
506
- ** filters ,
507
- }
508
-
509
- if payload .text :
510
- kwargs ["text" ] = payload .text
511
-
512
- # Use the search function
513
- return await long_term_memory .search_memories (** kwargs )
514
-
515
-
516
480
@router .post ("/v1/memory/prompt" , response_model = MemoryPromptResponse )
517
481
async def memory_prompt (
518
482
params : MemoryPromptRequest ,
@@ -546,7 +510,7 @@ async def memory_prompt(
546
510
redis = await get_redis_conn ()
547
511
_messages = []
548
512
549
- print ( "Received params: " , params )
513
+ logger . debug ( f"Memory prompt params: { params } " )
550
514
551
515
if params .session :
552
516
# Use token limit for memory prompt, fallback to message count for backward compatibility
@@ -569,6 +533,8 @@ async def memory_prompt(
569
533
redis_client = redis ,
570
534
)
571
535
536
+ logger .debug (f"Found working memory: { working_mem } " )
537
+
572
538
if working_mem :
573
539
if working_mem .context :
574
540
# TODO: Weird to use MCP types here?
@@ -580,7 +546,7 @@ async def memory_prompt(
580
546
),
581
547
)
582
548
)
583
- # Apply token-based or message-based truncation
549
+ # Apply token-based truncation if model info is provided
584
550
if params .session .model_name or params .session .context_window_max :
585
551
# Token-based truncation
586
552
if (
@@ -598,39 +564,55 @@ async def memory_prompt(
598
564
break
599
565
else :
600
566
recent_messages = working_mem .messages
567
+
568
+ for msg in recent_messages :
569
+ if msg .role == "user" :
570
+ msg_class = base .UserMessage
571
+ else :
572
+ msg_class = base .AssistantMessage
573
+ _messages .append (
574
+ msg_class (
575
+ content = TextContent (type = "text" , text = msg .content ),
576
+ )
577
+ )
601
578
else :
602
- # Message-based truncation (backward compatibility)
603
- recent_messages = (
604
- working_mem .messages [- effective_window_size :]
605
- if len (working_mem .messages ) > effective_window_size
606
- else working_mem .messages
607
- )
608
- for msg in recent_messages :
609
- if msg .role == "user" :
610
- msg_class = base .UserMessage
611
- else :
612
- msg_class = base .AssistantMessage
613
- _messages .append (
614
- msg_class (
615
- content = TextContent (type = "text" , text = msg .content ),
579
+ # No token-based truncation - use all messages
580
+ for msg in working_mem .messages :
581
+ if msg .role == "user" :
582
+ msg_class = base .UserMessage
583
+ else :
584
+ msg_class = base .AssistantMessage
585
+ _messages .append (
586
+ msg_class (
587
+ content = TextContent (type = "text" , text = msg .content ),
588
+ )
616
589
)
617
- )
618
590
619
591
if params .long_term_search :
620
- # TODO: Exclude session messages if we already included them from session memory
621
-
622
- # If no text is provided in long_term_search, use the user's query
623
- if not params .long_term_search .text :
624
- # Create a new SearchRequest with the query as text
625
- search_payload = params .long_term_search .model_copy ()
626
- search_payload .text = params .query
592
+ logger .debug (
593
+ f"[memory_prompt] Long-term search args: { params .long_term_search } "
594
+ )
595
+ if isinstance (params .long_term_search , bool ):
596
+ search_kwargs = {}
597
+ if params .session :
598
+ # Exclude memories from the current session because we already included them
599
+ search_kwargs ["session_id" ] = SessionId (ne = params .session .session_id )
600
+ if params .session and params .session .user_id :
601
+ search_kwargs ["user_id" ] = UserId (eq = params .session .user_id )
602
+ search_payload = SearchRequest (** search_kwargs , limit = 20 , offset = 0 )
627
603
else :
628
- search_payload = params .long_term_search
604
+ search_payload = params .long_term_search .model_copy ()
605
+ # Merge session user_id into the search request if not already specified
606
+ if params .session and params .session .user_id and not search_payload .user_id :
607
+ search_payload .user_id = UserId (eq = params .session .user_id )
629
608
609
+ logger .debug (f"[memory_prompt] Search payload: { search_payload } " )
630
610
long_term_memories = await search_long_term_memory (
631
611
search_payload ,
632
612
)
633
613
614
+ logger .debug (f"[memory_prompt] Long-term memories: { long_term_memories } " )
615
+
634
616
if long_term_memories .total > 0 :
635
617
long_term_memories_text = "\n " .join (
636
618
[f"- { m .text } " for m in long_term_memories .memories ]
0 commit comments