diff --git a/backend/services/intelligence/agents/agent_orchestrator.py b/backend/services/intelligence/agents/agent_orchestrator.py index fe04b58d..b00c9bce 100644 --- a/backend/services/intelligence/agents/agent_orchestrator.py +++ b/backend/services/intelligence/agents/agent_orchestrator.py @@ -145,6 +145,13 @@ class ALwrityAgentOrchestrator: if enabled_by_key.get("content_strategist", True): self.content_agent = ContentStrategyAgent(self.user_id, self.config.shared_llm, llm=self.llm) self.agents['content'] = self.content_agent + + # Strategy Architect Agent + if enabled_by_key.get("strategy_architect", True): + from services.intelligence.txtai_service import TxtaiIntelligenceService + intel_service = TxtaiIntelligenceService(self.user_id) + self.strategy_agent = StrategyArchitectAgent(intel_service, self.user_id) + self.agents['strategy'] = self.strategy_agent # Competitor Response Agent if enabled_by_key.get("competitor_analyst", True): diff --git a/backend/services/today_workflow_service.py b/backend/services/today_workflow_service.py index 48667516..e027bc92 100644 --- a/backend/services/today_workflow_service.py +++ b/backend/services/today_workflow_service.py @@ -292,10 +292,10 @@ async def generate_agent_enhanced_plan(db: Session, user_id: str, date: str) -> # Define agents to poll agents_to_poll = [ orchestrator.agents.get('content'), # ContentStrategyAgent + orchestrator.agents.get('strategy'), # StrategyArchitectAgent orchestrator.agents.get('seo'), # SEOOptimizationAgent orchestrator.agents.get('social'), # SocialAmplificationAgent orchestrator.agents.get('competitor'), # CompetitorResponseAgent - orchestrator.agents.get('strategy'), # StrategyArchitectAgent ] # Filter out None agents (disabled/failed init) diff --git a/backend/tests/test_today_workflow_pillar_coverage.py b/backend/tests/test_today_workflow_pillar_coverage.py index e8fe3838..9bb9a821 100644 --- a/backend/tests/test_today_workflow_pillar_coverage.py +++ b/backend/tests/test_today_workflow_pillar_coverage.py @@ -20,6 +20,14 @@ class DummyActivity: return None +class DummyMemoryService: + def __init__(self, user_id, db): + pass + + async def filter_redundant_proposals(self, proposals): + return proposals + + class DummyAgent: def __init__(self, proposals): self._proposals = proposals @@ -28,10 +36,11 @@ class DummyAgent: return self._proposals -def _mock_orchestrator_with_agents(proposals): +def _mock_orchestrator_with_agents(content_proposals=None, strategy_proposals=None): return SimpleNamespace( agents={ - "content": DummyAgent(proposals), + "content": DummyAgent(content_proposals or []), + "strategy": DummyAgent(strategy_proposals or []), "seo": None, "social": None, "competitor": None, @@ -55,7 +64,7 @@ async def test_generate_agent_enhanced_plan_preserves_full_committee_coverage(mo ] async def _get_orchestrator(user_id): - return _mock_orchestrator_with_agents(proposals) + return _mock_orchestrator_with_agents(content_proposals=proposals) monkeypatch.setattr(svc, "build_grounding_context", lambda db, user_id, date: {}) monkeypatch.setattr(svc.orchestration_service, "get_or_create_orchestrator", _get_orchestrator) @@ -74,7 +83,7 @@ async def test_generate_agent_enhanced_plan_backfills_missing_committee_pillars( ] async def _get_orchestrator(user_id): - return _mock_orchestrator_with_agents(proposals) + return _mock_orchestrator_with_agents(content_proposals=proposals) monkeypatch.setattr(svc, "build_grounding_context", lambda db, user_id, date: {}) monkeypatch.setattr(svc.orchestration_service, "get_or_create_orchestrator", _get_orchestrator) @@ -88,7 +97,7 @@ async def test_generate_agent_enhanced_plan_backfills_missing_committee_pillars( @pytest.mark.asyncio async def test_generate_agent_enhanced_plan_full_fallback_path_still_covers_all_pillars(monkeypatch): async def _get_orchestrator(user_id): - return _mock_orchestrator_with_agents([]) + return _mock_orchestrator_with_agents() monkeypatch.setattr(svc, "build_grounding_context", lambda db, user_id, date: {}) monkeypatch.setattr(svc.orchestration_service, "get_or_create_orchestrator", _get_orchestrator) @@ -103,3 +112,55 @@ async def test_generate_agent_enhanced_plan_full_fallback_path_still_covers_all_ assert _covered_pillars(result) == set(svc.PILLAR_IDS) assert len(result["tasks"]) >= len(svc.PILLAR_IDS) + + +@pytest.mark.asyncio +async def test_generate_agent_enhanced_plan_strategy_plan_task_survives_dedupe_and_coverage(monkeypatch): + content_proposals = [ + TaskProposal( + "Review Strategic Goals", + "desc", + "plan", + "medium", + 10, + "ContentStrategyAgent", + "why", + {}, + "navigate", + "/content-planning-dashboard", + ), + ] + strategy_proposals = [ + TaskProposal( + "Review Strategic Goals", + "desc", + "plan", + "high", + 10, + "StrategyArchitectAgent", + "why", + {}, + "navigate", + "/content-planning-dashboard", + ), + ] + + async def _get_orchestrator(user_id): + return _mock_orchestrator_with_agents( + content_proposals=content_proposals, + strategy_proposals=strategy_proposals, + ) + + monkeypatch.setattr(svc, "build_grounding_context", lambda db, user_id, date: {}) + monkeypatch.setattr(svc.orchestration_service, "get_or_create_orchestrator", _get_orchestrator) + monkeypatch.setattr(svc, "TaskMemoryService", DummyMemoryService) + + result = await svc.generate_agent_enhanced_plan(db=None, user_id="u1", date="2026-01-01") + + assert _covered_pillars(result) == set(svc.PILLAR_IDS) + plan_tasks = [task for task in result["tasks"] if task["pillarId"] == "plan"] + assert any( + task["title"] == "Review Strategic Goals" + and task["metadata"].get("source_agent") == "StrategyArchitectAgent" + for task in plan_tasks + )