From 33e9f386339f6ee4ff5a9a1670af20bd9dc39f80 Mon Sep 17 00:00:00 2001 From: Auto Date: Thu, 22 Jan 2026 08:23:47 +0200 Subject: [PATCH] fix: prevent testing agents from running indefinitely This fix addresses two root causes that caused testing agents to accumulate (10-12 agents) instead of maintaining a 1:1 ratio with coding agents: 1. Testing agents now exit after one session (agent.py) - Added `or agent_type == "testing"` to the exit condition - Previously, testing agents never hit the exit condition since they're spawned with feature_id=None 2. Testing agents now spawn when coding agents START, not complete - Moved spawn logic from _on_agent_complete() to start_feature() - Removed the old spawn logic from _on_agent_complete() - This ensures proper 1:1 ratio and prevents accumulation Expected behavior after fix: - First coding agent: no testing agent (no passing features yet) - Subsequent coding agents: one testing agent spawns per start - Each testing agent tests ONE feature then terminates immediately Co-Authored-By: Claude Opus 4.5 --- agent.py | 9 ++++++--- parallel_orchestrator.py | 32 ++++++++++++-------------------- 2 files changed, 18 insertions(+), 23 deletions(-) diff --git a/agent.py b/agent.py index 09c1a04..10253b5 100644 --- a/agent.py +++ b/agent.py @@ -306,9 +306,12 @@ async def run_autonomous_agent( print("The autonomous agent has finished its work.") break - # Single-feature mode: exit after one session (orchestrator manages agents) - if feature_id is not None: - print(f"\nSingle-feature mode: Feature #{feature_id} session complete.") + # Single-feature mode OR testing agent: exit after one session + if feature_id is not None or agent_type == "testing": + if agent_type == "testing": + print("\nTesting agent complete. Terminating session.") + else: + print(f"\nSingle-feature mode: Feature #{feature_id} session complete.") break await asyncio.sleep(delay_seconds) diff --git a/parallel_orchestrator.py b/parallel_orchestrator.py index 09f4e22..5bbcba9 100644 --- a/parallel_orchestrator.py +++ b/parallel_orchestrator.py @@ -412,9 +412,16 @@ class ParallelOrchestrator: if not success: return False, message - # NOTE: Testing agents are spawned in _on_agent_complete() after a coding agent - # succeeds, not here. This ensures we only spawn testing agents when there are - # actually passing features to test. + # Spawn ONE testing agent when coding agent STARTS (if not YOLO mode and passing features exist) + # Testing agents exit after one test, so we spawn fresh ones with each coding agent start + if not self.yolo_mode and self.testing_agent_ratio > 0: + passing_count = self.get_passing_count() + if passing_count > 0: + print(f"[DEBUG] Coding agent started, spawning testing agent (passing_count={passing_count})", flush=True) + debug_log.log("TESTING", "Spawning testing agent on coding agent start", + feature_id=feature_id, + passing_count=passing_count) + self._spawn_testing_agent() return True, f"Started feature {feature_id}" @@ -724,23 +731,8 @@ class ParallelOrchestrator: # CRITICAL: This print triggers the WebSocket to emit agent_update with state='error' or 'success' print(f"Feature #{feature_id} {status}", flush=True) - # Spawn testing agents after successful coding agent completion - # This is the correct place to spawn testing agents - after we know there are - # passing features (the one this agent just completed, plus any previous ones) - if return_code == 0 and not self.yolo_mode and self.testing_agent_ratio > 0: - passing_count = self.get_passing_count() - print(f"[DEBUG] Coding agent completed successfully, passing_count={passing_count}", flush=True) - debug_log.log("TESTING", "Checking if testing agents should spawn", - yolo_mode=self.yolo_mode, - testing_agent_ratio=self.testing_agent_ratio, - passing_count=passing_count) - if passing_count > 0: - print(f"[DEBUG] Spawning testing agents (ratio={self.testing_agent_ratio})", flush=True) - debug_log.log("TESTING", f"Spawning {self.testing_agent_ratio} testing agent(s)") - self._spawn_testing_agents() - elif return_code == 0: - debug_log.log("TESTING", "Skipping testing agents", - reason="yolo_mode" if self.yolo_mode else f"ratio={self.testing_agent_ratio}") + # NOTE: Testing agents are now spawned in start_feature() when coding agents START, + # not here when they complete. This ensures 1:1 ratio and proper termination. def stop_feature(self, feature_id: int) -> tuple[bool, str]: """Stop a running coding agent and all its child processes."""