@@ -312,6 +312,49 @@ async def test_list_directives_by_tags(self, memory: MemoryEngine, request_conte
312312 # Cleanup
313313 await memory .delete_bank (bank_id , request_context = request_context )
314314
315+ async def test_list_all_directives_without_filter (self , memory : MemoryEngine , request_context ):
316+ """Test that listing directives without tags returns ALL directives (both tagged and untagged)."""
317+ bank_id = f"test-directive-list-all-{ uuid .uuid4 ().hex [:8 ]} "
318+
319+ # Ensure bank exists
320+ await memory .get_bank_profile (bank_id , request_context = request_context )
321+
322+ # Create untagged directive
323+ await memory .create_directive (
324+ bank_id = bank_id ,
325+ name = "Untagged Directive" ,
326+ content = "This has no tags" ,
327+ request_context = request_context ,
328+ )
329+
330+ # Create tagged directive
331+ await memory .create_directive (
332+ bank_id = bank_id ,
333+ name = "Tagged Directive" ,
334+ content = "This has tags" ,
335+ tags = ["project-x" ],
336+ request_context = request_context ,
337+ )
338+
339+ # List ALL directives (no tag filter, isolation_mode defaults to False)
340+ all_directives = await memory .list_directives (
341+ bank_id = bank_id ,
342+ request_context = request_context ,
343+ )
344+
345+ # Should return BOTH tagged and untagged directives
346+ assert len (all_directives ) == 2
347+ directive_names = {d ["name" ] for d in all_directives }
348+ assert "Untagged Directive" in directive_names
349+ assert "Tagged Directive" in directive_names
350+
351+ # Verify the tagged directive has its tags
352+ tagged = next (d for d in all_directives if d ["name" ] == "Tagged Directive" )
353+ assert tagged ["tags" ] == ["project-x" ]
354+
355+ # Cleanup
356+ await memory .delete_bank (bank_id , request_context = request_context )
357+
315358
316359class TestReflect :
317360 """Test reflect endpoint."""
@@ -399,6 +442,161 @@ async def test_reflect_follows_language_directive(self, memory: MemoryEngine, re
399442 # Cleanup
400443 await memory .delete_bank (bank_id , request_context = request_context )
401444
445+ async def test_tagged_directive_not_applied_without_tags (self , memory : MemoryEngine , request_context ):
446+ """Test that directives with tags are NOT applied to untagged reflect operations."""
447+ bank_id = f"test-directive-isolation-{ uuid .uuid4 ().hex [:8 ]} "
448+
449+ # Ensure bank exists
450+ await memory .get_bank_profile (bank_id , request_context = request_context )
451+
452+ # Add some untagged content
453+ await memory .retain_batch_async (
454+ bank_id = bank_id ,
455+ contents = [
456+ {"content" : "The sky is blue." },
457+ {"content" : "Water is wet." },
458+ ],
459+ request_context = request_context ,
460+ )
461+
462+ # Add some tagged content for the project-x context
463+ await memory .retain_batch_async (
464+ bank_id = bank_id ,
465+ contents = [
466+ {"content" : "The sky is blue according to project X standards." , "tags" : ["project-x" ]},
467+ {"content" : "Project X color guidelines specify sky is blue." , "tags" : ["project-x" ]},
468+ ],
469+ request_context = request_context ,
470+ )
471+ await memory .wait_for_background_tasks ()
472+
473+ # Create an untagged directive (should be applied)
474+ await memory .create_directive (
475+ bank_id = bank_id ,
476+ name = "General Policy" ,
477+ content = "Always be polite and start responses with 'Hello!'" ,
478+ request_context = request_context ,
479+ )
480+
481+ # Create a tagged directive (should NOT be applied to untagged reflect)
482+ await memory .create_directive (
483+ bank_id = bank_id ,
484+ name = "Tagged Policy" ,
485+ content = "ALWAYS respond in ALL CAPS and end with 'PROJECT-X ONLY'" ,
486+ tags = ["project-x" ],
487+ request_context = request_context ,
488+ )
489+
490+ # Run reflect without tags - should only apply the untagged directive
491+ result = await memory .reflect_async (
492+ bank_id = bank_id ,
493+ query = "What color is the sky?" ,
494+ request_context = request_context ,
495+ )
496+
497+ response_lower = result .text .lower ()
498+
499+ # Should follow the untagged directive (polite greeting)
500+ assert "hello" in response_lower , f"Expected 'Hello' from untagged directive, but got: { result .text } "
501+
502+ # Should NOT follow the tagged directive (all caps and PROJECT-X)
503+ # If it did follow, the entire response would be in caps
504+ all_caps = result .text .replace (" " , "" ).replace ("!" , "" ).replace ("." , "" ).isupper ()
505+ assert not all_caps , f"Tagged directive was incorrectly applied to untagged operation: { result .text } "
506+ assert "project-x only" not in response_lower , f"Tagged directive was incorrectly applied: { result .text } "
507+
508+ # Now run reflect WITH the tag - should apply BOTH directives
509+ result_tagged = await memory .reflect_async (
510+ bank_id = bank_id ,
511+ query = "What color is the sky?" ,
512+ tags = ["project-x" ],
513+ tags_match = "all_strict" ,
514+ request_context = request_context ,
515+ )
516+
517+ response_tagged_lower = result_tagged .text .lower ()
518+
519+ # With strict matching and tags, should apply the tagged directive
520+ assert "project-x only" in response_tagged_lower , f"Tagged directive should be applied with tags: { result_tagged .text } "
521+
522+ # Cleanup
523+ await memory .delete_bank (bank_id , request_context = request_context )
524+
525+ async def test_reflect_based_on_structure (self , memory : MemoryEngine , request_context ):
526+ """Test that reflect returns correct based_on structure with directives and memories separated."""
527+ bank_id = f"test-reflect-based-on-{ uuid .uuid4 ().hex [:8 ]} "
528+
529+ # Ensure bank exists
530+ await memory .get_bank_profile (bank_id , request_context = request_context )
531+
532+ # Add some memories
533+ await memory .retain_batch_async (
534+ bank_id = bank_id ,
535+ contents = [
536+ {"content" : "Alice works at Google as a software engineer." },
537+ {"content" : "Bob is a product manager at Microsoft." },
538+ {"content" : "The team meets every Monday at 9am." },
539+ ],
540+ request_context = request_context ,
541+ )
542+ await memory .wait_for_background_tasks ()
543+
544+ # Create a directive
545+ directive = await memory .create_directive (
546+ bank_id = bank_id ,
547+ name = "Professional Tone" ,
548+ content = "Always maintain a professional and formal tone in responses." ,
549+ request_context = request_context ,
550+ )
551+ directive_id = directive ["id" ]
552+
553+ # Run reflect which returns the core result
554+ result = await memory .reflect_async (
555+ bank_id = bank_id ,
556+ query = "Who works at Google?" ,
557+ request_context = request_context ,
558+ )
559+
560+ # Verify based_on structure exists
561+ assert result .based_on is not None
562+
563+ # Verify directives key exists and contains our directive
564+ assert "directives" in result .based_on
565+ directives_list = result .based_on .get ("directives" , [])
566+
567+ # Verify directives are dicts with id, name, content (not MemoryFact objects)
568+ assert len (directives_list ) > 0 , "Should have at least one directive"
569+ directive_found = False
570+ for d in directives_list :
571+ assert isinstance (d , dict ), f"Directive should be dict, got { type (d )} "
572+ assert "id" in d , "Directive dict should have 'id'"
573+ assert "name" in d , "Directive dict should have 'name'"
574+ assert "content" in d , "Directive dict should have 'content'"
575+ # Check if this is our directive
576+ if d ["id" ] == directive_id :
577+ directive_found = True
578+ assert d ["name" ] == "Professional Tone"
579+ assert "professional" in d ["content" ].lower ()
580+
581+ assert directive_found , f"Our directive { directive_id } should be in based_on.directives"
582+
583+ # Verify memories (world/experience) are separate from directives
584+ has_memories = "world" in result .based_on or "experience" in result .based_on
585+ assert has_memories , "Should have world or experience memories"
586+
587+ # Verify that if mental-models key exists, it's separate from directives
588+ if "mental-models" in result .based_on :
589+ mental_models = result .based_on .get ("mental-models" , [])
590+ # Verify mental models are MemoryFact objects, not dicts like directives
591+ for mm in mental_models :
592+ assert hasattr (mm , "fact_type" ), "Mental model should be MemoryFact with fact_type"
593+ assert mm .fact_type == "mental-models"
594+ assert hasattr (mm , "context" )
595+ assert "mental model" in mm .context .lower ()
596+
597+ # Cleanup
598+ await memory .delete_bank (bank_id , request_context = request_context )
599+
402600
403601class TestDirectivesPromptInjection :
404602 """Test that directives are properly injected into the system prompt."""
0 commit comments