TradingAgents/.claude/lib/agent_invoker.py

247 lines
9.1 KiB
Python

"""
Unified agent invocation factory pattern.
Eliminates 1,200+ lines of duplication across orchestrator.py by providing
a single factory for invoking all agents with consistent patterns.
See error-handling-patterns skill for exception hierarchy and error handling best practices.
Design Patterns:
See library-design-patterns skill for standardized design patterns.
See state-management-patterns skill for standardized design patterns.
"""
from typing import Dict, Any
# Use absolute imports for better test compatibility
# (relative imports fail when module is imported from test files)
try:
from .artifacts import ArtifactManager
from .logging_utils import WorkflowLogger, WorkflowProgressTracker
except ImportError:
# Fallback to absolute imports (for tests)
from artifacts import ArtifactManager
from logging_utils import WorkflowLogger, WorkflowProgressTracker
class AgentInvoker:
"""Factory for invoking agents with consistent patterns."""
# Agent configuration mapping
AGENT_CONFIGS = {
'alignment-validator': {
'progress_pct': 5,
'artifacts_required': [], # No artifacts needed, just PROJECT.md
'description_template': 'Validate PROJECT.md alignment for: {request}',
'mission': 'Validate if request aligns with PROJECT.md GOALS, SCOPE, and CONSTRAINTS'
},
'researcher': {
'progress_pct': 20,
'artifacts_required': ['manifest'],
'description_template': 'Research patterns and best practices for: {request}',
'mission': 'Research the requested feature to inform implementation'
},
'planner': {
'progress_pct': 35,
'artifacts_required': ['manifest', 'research'],
'description_template': 'Design architecture for: {request}',
'mission': 'Design a comprehensive architecture plan'
},
'test-master': {
'progress_pct': 50,
'artifacts_required': ['manifest', 'architecture'],
'description_template': 'Write TDD tests for: {request}',
'mission': 'Write failing tests that define expected behavior (TDD red phase)'
},
'implementer': {
'progress_pct': 70,
'artifacts_required': ['manifest', 'architecture', 'tests'],
'description_template': 'Implement: {request}',
'mission': 'Write clean, tested implementation that makes all tests pass (TDD green phase)'
},
'reviewer': {
'progress_pct': 80,
'artifacts_required': ['manifest', 'architecture', 'tests', 'implementation'],
'description_template': 'Review implementation for: {request}',
'mission': 'Validate code quality and test coverage'
},
'security-auditor': {
'progress_pct': 90,
'artifacts_required': ['manifest', 'architecture', 'implementation'],
'description_template': 'Security audit for: {request}',
'mission': 'Perform comprehensive security audit'
},
'doc-master': {
'progress_pct': 95,
'artifacts_required': ['manifest', 'architecture', 'implementation'],
'description_template': 'Document: {request}',
'mission': 'Synchronize documentation with implementation'
},
'commit-message-generator': {
'progress_pct': 90,
'artifacts_required': ['manifest', 'architecture', 'implementation'],
'description_template': 'Generate commit message for: {request}',
'mission': 'Generate descriptive commit message following conventional commits format'
},
'pr-description-generator': {
'progress_pct': 96,
'artifacts_required': ['manifest', 'architecture', 'implementation', 'tests', 'security', 'review', 'documentation'],
'description_template': 'Generate PR description for: {request}',
'mission': 'Generate comprehensive pull request description from implementation artifacts'
},
'project-progress-tracker': {
'progress_pct': 98,
'artifacts_required': ['manifest', 'implementation'],
'description_template': 'Track PROJECT.md progress for: {request}',
'mission': 'Track and update PROJECT.md goal completion progress'
}
}
def __init__(self, artifact_manager: ArtifactManager):
"""
Initialize agent invoker.
Args:
artifact_manager: ArtifactManager instance for reading/writing artifacts
"""
self.artifact_manager = artifact_manager
def invoke(
self,
agent_name: str,
workflow_id: str,
**context
) -> Dict[str, Any]:
"""
Generic agent invocation with consistent logging and progress tracking.
Args:
agent_name: Name of agent to invoke (e.g., 'researcher', 'planner')
workflow_id: Unique workflow identifier
**context: Additional context to pass to agent (e.g., request, user_prompt)
Returns:
Dict with subagent invocation details:
- subagent_type: Agent name
- description: Human-readable description
- prompt: Formatted prompt for the agent
Raises:
ValueError: If agent_name is not recognized
"""
if agent_name not in self.AGENT_CONFIGS:
raise ValueError(
f"Unknown agent: {agent_name}. "
f"Valid agents: {list(self.AGENT_CONFIGS.keys())}"
)
config = self.AGENT_CONFIGS[agent_name]
# Initialize logging
logger = WorkflowLogger(workflow_id, 'orchestrator')
logger.log_event(f'invoke_{agent_name}', f'Invoking {agent_name}')
# Update progress
progress_tracker = WorkflowProgressTracker(workflow_id)
progress_tracker.update_progress(
current_agent=agent_name,
status='in_progress',
progress_percentage=config['progress_pct'],
message=f'{agent_name}: Starting...'
)
# Read required artifacts
artifacts = {}
for artifact_name in config['artifacts_required']:
try:
artifacts[artifact_name] = self.artifact_manager.read_artifact(
workflow_id,
artifact_type=artifact_name,
validate=True
)
except FileNotFoundError:
# Some artifacts may not exist yet (acceptable for early agents)
logger.log_event(
'artifact_missing',
f'Artifact {artifact_name} not found (may be expected)'
)
# Build invocation response
return {
'subagent_type': agent_name,
'description': config['description_template'].format(**context),
'prompt': self._build_prompt(agent_name, workflow_id, artifacts, context)
}
def _build_prompt(
self,
agent_name: str,
workflow_id: str,
artifacts: Dict[str, Any],
context: Dict[str, Any]
) -> str:
"""
Build agent prompt from artifacts and context.
Trust the model - provide essential context, let agent figure out details.
Args:
agent_name: Name of agent
workflow_id: Workflow identifier
artifacts: Available artifacts
context: Additional context
Returns:
Formatted prompt string
"""
config = self.AGENT_CONFIGS[agent_name]
# Extract request from manifest or context
manifest = artifacts.get('manifest', {})
request = manifest.get('request', context.get('request', 'No request specified'))
# Build concise prompt
prompt_parts = [
f"You are the {agent_name} agent.",
f"",
f"Mission: {config['mission']}",
f"",
f"Request: {request}",
f"",
f"Workflow ID: {workflow_id}",
f"",
f"Available artifacts: {list(artifacts.keys())}",
f"",
f"See your agent definition ({agent_name}.md) for detailed responsibilities.",
f"",
f"Execute your mission effectively. Trust your training."
]
return "\n".join(prompt_parts)
def invoke_with_task_tool(
self,
agent_name: str,
workflow_id: str,
**context
) -> Dict[str, Any]:
"""
Invoke agent with Task tool enabled for complex workflows.
Same as invoke() but signals that agent should use Task tool
for multi-step research/analysis.
Args:
agent_name: Name of agent to invoke
workflow_id: Workflow identifier
**context: Additional context
Returns:
Dict with subagent invocation details (includes task_tool_enabled flag)
"""
result = self.invoke(agent_name, workflow_id, **context)
result['task_tool_enabled'] = True
result['prompt'] += "\n\nTask tool is enabled for complex multi-step work."
return result