@@ -597,6 +597,216 @@ def test_successful_callable_returns_result():
597597 assert result is not None
598598
599599
600+ # ============================================================================
601+ # Repeated Agent Flow Tests
602+ # ============================================================================
603+
604+
605+ def create_repeated_flow_agents ():
606+ """Create agents for repeated flow testing."""
607+ return [
608+ Agent (
609+ agent_name = "Writer" ,
610+ agent_description = "Expert in writing content" ,
611+ system_prompt = "You are a writer. Write one concise sentence." ,
612+ model_name = "gpt-4o-mini" ,
613+ max_loops = 1 ,
614+ verbose = False ,
615+ ),
616+ Agent (
617+ agent_name = "Reviewer" ,
618+ agent_description = "Expert in reviewing content" ,
619+ system_prompt = "You are a reviewer. Give one critique." ,
620+ model_name = "gpt-4o-mini" ,
621+ max_loops = 1 ,
622+ verbose = False ,
623+ ),
624+ ]
625+
626+
627+ def test_repeated_agent_flow_valid ():
628+ """Test that flows with repeated agents pass validation."""
629+ agents = create_repeated_flow_agents ()
630+
631+ agent_rearrange = AgentRearrange (
632+ agents = agents ,
633+ flow = "Writer -> Reviewer -> Writer" ,
634+ )
635+
636+ assert agent_rearrange .validate_flow () is True
637+ print ("✓ test_repeated_agent_flow_valid passed" )
638+
639+
640+ def test_repeated_agent_awareness_position_0 ():
641+ """Test that the first occurrence of a repeated agent gets correct awareness."""
642+ agents = create_repeated_flow_agents ()
643+
644+ agent_rearrange = AgentRearrange (
645+ agents = agents ,
646+ flow = "Writer -> Reviewer -> Writer" ,
647+ )
648+
649+ tasks = agent_rearrange .flow .split ("->" )
650+ awareness = agent_rearrange ._get_sequential_awareness (
651+ "Writer" , tasks , task_idx = 0
652+ )
653+
654+ # Writer at position 0: no agent ahead, Reviewer behind
655+ assert "Agent behind" in awareness
656+ assert "Reviewer" in awareness
657+ assert "Agent ahead" not in awareness
658+ print ("✓ test_repeated_agent_awareness_position_0 passed" )
659+
660+
661+ def test_repeated_agent_awareness_position_2 ():
662+ """Test that the second occurrence of a repeated agent gets correct awareness."""
663+ agents = create_repeated_flow_agents ()
664+
665+ agent_rearrange = AgentRearrange (
666+ agents = agents ,
667+ flow = "Writer -> Reviewer -> Writer" ,
668+ )
669+
670+ tasks = agent_rearrange .flow .split ("->" )
671+ awareness = agent_rearrange ._get_sequential_awareness (
672+ "Writer" , tasks , task_idx = 2
673+ )
674+
675+ # Writer at position 2: Reviewer ahead, no agent behind
676+ assert "Agent ahead" in awareness
677+ assert "Reviewer" in awareness
678+ assert "Agent behind" not in awareness
679+ print ("✓ test_repeated_agent_awareness_position_2 passed" )
680+
681+
682+ def test_repeated_agent_awareness_differs_per_position ():
683+ """Test that each occurrence of a repeated agent gets different awareness."""
684+ agents = create_repeated_flow_agents ()
685+
686+ agent_rearrange = AgentRearrange (
687+ agents = agents ,
688+ flow = "Writer -> Reviewer -> Writer" ,
689+ )
690+
691+ tasks = agent_rearrange .flow .split ("->" )
692+ awareness_0 = agent_rearrange ._get_sequential_awareness (
693+ "Writer" , tasks , task_idx = 0
694+ )
695+ awareness_2 = agent_rearrange ._get_sequential_awareness (
696+ "Writer" , tasks , task_idx = 2
697+ )
698+
699+ assert awareness_0 != awareness_2
700+ print ("✓ test_repeated_agent_awareness_differs_per_position passed" )
701+
702+
703+ def test_repeated_agent_awareness_fallback_without_idx ():
704+ """Test that awareness still works when task_idx is not provided (backward compat)."""
705+ agents = create_repeated_flow_agents ()
706+
707+ agent_rearrange = AgentRearrange (
708+ agents = agents ,
709+ flow = "Writer -> Reviewer -> Writer" ,
710+ )
711+
712+ tasks = agent_rearrange .flow .split ("->" )
713+ # Without task_idx, falls back to finding first occurrence
714+ awareness = agent_rearrange ._get_sequential_awareness (
715+ "Writer" , tasks
716+ )
717+
718+ assert awareness is not None
719+ assert isinstance (awareness , str )
720+ assert "Sequential awareness" in awareness
721+ print ("✓ test_repeated_agent_awareness_fallback_without_idx passed" )
722+
723+
724+ def test_repeated_agent_three_occurrences ():
725+ """Test awareness correctness with three occurrences of the same agent."""
726+ agents = create_repeated_flow_agents ()
727+
728+ agent_rearrange = AgentRearrange (
729+ agents = agents ,
730+ flow = "Writer -> Reviewer -> Writer -> Reviewer -> Writer" ,
731+ )
732+
733+ tasks = agent_rearrange .flow .split ("->" )
734+
735+ # Writer at pos 0: no ahead, Reviewer behind
736+ a0 = agent_rearrange ._get_sequential_awareness ("Writer" , tasks , task_idx = 0 )
737+ assert "Agent behind" in a0
738+ assert "Agent ahead" not in a0
739+
740+ # Writer at pos 2: Reviewer ahead, Reviewer behind
741+ a2 = agent_rearrange ._get_sequential_awareness ("Writer" , tasks , task_idx = 2 )
742+ assert "Agent ahead" in a2
743+ assert "Agent behind" in a2
744+
745+ # Writer at pos 4: Reviewer ahead, no behind
746+ a4 = agent_rearrange ._get_sequential_awareness ("Writer" , tasks , task_idx = 4 )
747+ assert "Agent ahead" in a4
748+ assert "Agent behind" not in a4
749+
750+ print ("✓ test_repeated_agent_three_occurrences passed" )
751+
752+
753+ def test_repeated_agent_run ():
754+ """Test that a repeated agent flow runs end-to-end."""
755+ agents = create_repeated_flow_agents ()
756+
757+ agent_rearrange = AgentRearrange (
758+ name = "repeated-flow-test" ,
759+ agents = agents ,
760+ flow = "Writer -> Reviewer -> Writer" ,
761+ max_loops = 1 ,
762+ )
763+
764+ result = agent_rearrange .run ("Write about the moon." )
765+ assert result is not None
766+ assert len (str (result )) > 0
767+
768+ # Verify Writer appears twice in conversation
769+ messages = agent_rearrange .conversation .to_dict ()
770+ writer_msgs = [m for m in messages if m .get ("role" ) == "Writer" ]
771+ assert len (writer_msgs ) == 2 , (
772+ f"Expected 2 Writer messages, got { len (writer_msgs )} "
773+ )
774+
775+ print ("✓ test_repeated_agent_run passed" )
776+
777+
778+ def test_repeated_agent_awareness_in_conversation ():
779+ """Test that different awareness messages are injected for each occurrence."""
780+ agents = create_repeated_flow_agents ()
781+
782+ agent_rearrange = AgentRearrange (
783+ name = "awareness-conv-test" ,
784+ agents = agents ,
785+ flow = "Writer -> Reviewer -> Writer" ,
786+ max_loops = 1 ,
787+ )
788+
789+ agent_rearrange .run ("Write about rain." )
790+
791+ messages = agent_rearrange .conversation .to_dict ()
792+
793+ # Find awareness messages that precede Writer messages
794+ writer_awareness = []
795+ for idx , msg in enumerate (messages ):
796+ if "Sequential awareness" in str (msg .get ("content" , "" )):
797+ if idx + 1 < len (messages ) and messages [idx + 1 ].get ("role" ) == "Writer" :
798+ writer_awareness .append (msg .get ("content" , "" ))
799+
800+ assert len (writer_awareness ) == 2 , (
801+ f"Expected 2 awareness messages before Writer, got { len (writer_awareness )} "
802+ )
803+ assert writer_awareness [0 ] != writer_awareness [1 ], (
804+ "Both Writer invocations got identical awareness"
805+ )
806+
807+ print ("✓ test_repeated_agent_awareness_in_conversation passed" )
808+
809+
600810def main ():
601811 """Run all tests."""
602812 tests = [
@@ -629,6 +839,14 @@ def main():
629839 test_error_logged_once ,
630840 test_successful_run_returns_result ,
631841 test_successful_callable_returns_result ,
842+ test_repeated_agent_flow_valid ,
843+ test_repeated_agent_awareness_position_0 ,
844+ test_repeated_agent_awareness_position_2 ,
845+ test_repeated_agent_awareness_differs_per_position ,
846+ test_repeated_agent_awareness_fallback_without_idx ,
847+ test_repeated_agent_three_occurrences ,
848+ test_repeated_agent_run ,
849+ test_repeated_agent_awareness_in_conversation ,
632850 ]
633851
634852 print ("=" * 60 )
0 commit comments