claude-mpm 0.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of claude-mpm might be problematic. Click here for more details.
- claude_mpm/__init__.py +17 -0
- claude_mpm/__main__.py +14 -0
- claude_mpm/_version.py +32 -0
- claude_mpm/agents/BASE_AGENT_TEMPLATE.md +88 -0
- claude_mpm/agents/INSTRUCTIONS.md +375 -0
- claude_mpm/agents/__init__.py +118 -0
- claude_mpm/agents/agent_loader.py +621 -0
- claude_mpm/agents/agent_loader_integration.py +229 -0
- claude_mpm/agents/agents_metadata.py +204 -0
- claude_mpm/agents/base_agent.json +27 -0
- claude_mpm/agents/base_agent_loader.py +519 -0
- claude_mpm/agents/schema/agent_schema.json +160 -0
- claude_mpm/agents/system_agent_config.py +587 -0
- claude_mpm/agents/templates/__init__.py +101 -0
- claude_mpm/agents/templates/data_engineer_agent.json +46 -0
- claude_mpm/agents/templates/documentation_agent.json +45 -0
- claude_mpm/agents/templates/engineer_agent.json +49 -0
- claude_mpm/agents/templates/ops_agent.json +46 -0
- claude_mpm/agents/templates/qa_agent.json +45 -0
- claude_mpm/agents/templates/research_agent.json +49 -0
- claude_mpm/agents/templates/security_agent.json +46 -0
- claude_mpm/agents/templates/update-optimized-specialized-agents.json +374 -0
- claude_mpm/agents/templates/version_control_agent.json +46 -0
- claude_mpm/agents/test_fix_deployment/.claude-pm/config/project.json +6 -0
- claude_mpm/cli.py +655 -0
- claude_mpm/cli_main.py +13 -0
- claude_mpm/cli_module/__init__.py +15 -0
- claude_mpm/cli_module/args.py +222 -0
- claude_mpm/cli_module/commands.py +203 -0
- claude_mpm/cli_module/migration_example.py +183 -0
- claude_mpm/cli_module/refactoring_guide.md +253 -0
- claude_mpm/cli_old/__init__.py +1 -0
- claude_mpm/cli_old/ticket_cli.py +102 -0
- claude_mpm/config/__init__.py +5 -0
- claude_mpm/config/hook_config.py +42 -0
- claude_mpm/constants.py +150 -0
- claude_mpm/core/__init__.py +45 -0
- claude_mpm/core/agent_name_normalizer.py +248 -0
- claude_mpm/core/agent_registry.py +627 -0
- claude_mpm/core/agent_registry.py.bak +312 -0
- claude_mpm/core/agent_session_manager.py +273 -0
- claude_mpm/core/base_service.py +747 -0
- claude_mpm/core/base_service.py.bak +406 -0
- claude_mpm/core/config.py +334 -0
- claude_mpm/core/config_aliases.py +292 -0
- claude_mpm/core/container.py +347 -0
- claude_mpm/core/factories.py +281 -0
- claude_mpm/core/framework_loader.py +472 -0
- claude_mpm/core/injectable_service.py +206 -0
- claude_mpm/core/interfaces.py +539 -0
- claude_mpm/core/logger.py +468 -0
- claude_mpm/core/minimal_framework_loader.py +107 -0
- claude_mpm/core/mixins.py +150 -0
- claude_mpm/core/service_registry.py +299 -0
- claude_mpm/core/session_manager.py +190 -0
- claude_mpm/core/simple_runner.py +511 -0
- claude_mpm/core/tool_access_control.py +173 -0
- claude_mpm/hooks/README.md +243 -0
- claude_mpm/hooks/__init__.py +5 -0
- claude_mpm/hooks/base_hook.py +154 -0
- claude_mpm/hooks/builtin/__init__.py +1 -0
- claude_mpm/hooks/builtin/logging_hook_example.py +165 -0
- claude_mpm/hooks/builtin/post_delegation_hook_example.py +124 -0
- claude_mpm/hooks/builtin/pre_delegation_hook_example.py +125 -0
- claude_mpm/hooks/builtin/submit_hook_example.py +100 -0
- claude_mpm/hooks/builtin/ticket_extraction_hook_example.py +237 -0
- claude_mpm/hooks/builtin/todo_agent_prefix_hook.py +239 -0
- claude_mpm/hooks/builtin/workflow_start_hook.py +181 -0
- claude_mpm/hooks/hook_client.py +264 -0
- claude_mpm/hooks/hook_runner.py +370 -0
- claude_mpm/hooks/json_rpc_executor.py +259 -0
- claude_mpm/hooks/json_rpc_hook_client.py +319 -0
- claude_mpm/hooks/tool_call_interceptor.py +204 -0
- claude_mpm/init.py +246 -0
- claude_mpm/orchestration/SUBPROCESS_DESIGN.md +66 -0
- claude_mpm/orchestration/__init__.py +6 -0
- claude_mpm/orchestration/archive/direct_orchestrator.py +195 -0
- claude_mpm/orchestration/archive/factory.py +215 -0
- claude_mpm/orchestration/archive/hook_enabled_orchestrator.py +188 -0
- claude_mpm/orchestration/archive/hook_integration_example.py +178 -0
- claude_mpm/orchestration/archive/interactive_subprocess_orchestrator.py +826 -0
- claude_mpm/orchestration/archive/orchestrator.py +501 -0
- claude_mpm/orchestration/archive/pexpect_orchestrator.py +252 -0
- claude_mpm/orchestration/archive/pty_orchestrator.py +270 -0
- claude_mpm/orchestration/archive/simple_orchestrator.py +82 -0
- claude_mpm/orchestration/archive/subprocess_orchestrator.py +801 -0
- claude_mpm/orchestration/archive/system_prompt_orchestrator.py +278 -0
- claude_mpm/orchestration/archive/wrapper_orchestrator.py +187 -0
- claude_mpm/scripts/__init__.py +1 -0
- claude_mpm/scripts/ticket.py +269 -0
- claude_mpm/services/__init__.py +10 -0
- claude_mpm/services/agent_deployment.py +955 -0
- claude_mpm/services/agent_lifecycle_manager.py +948 -0
- claude_mpm/services/agent_management_service.py +596 -0
- claude_mpm/services/agent_modification_tracker.py +841 -0
- claude_mpm/services/agent_profile_loader.py +606 -0
- claude_mpm/services/agent_registry.py +677 -0
- claude_mpm/services/base_agent_manager.py +380 -0
- claude_mpm/services/framework_agent_loader.py +337 -0
- claude_mpm/services/framework_claude_md_generator/README.md +92 -0
- claude_mpm/services/framework_claude_md_generator/__init__.py +206 -0
- claude_mpm/services/framework_claude_md_generator/content_assembler.py +151 -0
- claude_mpm/services/framework_claude_md_generator/content_validator.py +126 -0
- claude_mpm/services/framework_claude_md_generator/deployment_manager.py +137 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/__init__.py +106 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +582 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/claude_pm_init.py +97 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/core_responsibilities.py +27 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/delegation_constraints.py +23 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/environment_config.py +23 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/footer.py +20 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/header.py +26 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/orchestration_principles.py +30 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/role_designation.py +37 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/subprocess_validation.py +111 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +89 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/troubleshooting.py +39 -0
- claude_mpm/services/framework_claude_md_generator/section_manager.py +106 -0
- claude_mpm/services/framework_claude_md_generator/version_manager.py +121 -0
- claude_mpm/services/framework_claude_md_generator.py +621 -0
- claude_mpm/services/hook_service.py +388 -0
- claude_mpm/services/hook_service_manager.py +223 -0
- claude_mpm/services/json_rpc_hook_manager.py +92 -0
- claude_mpm/services/parent_directory_manager/README.md +83 -0
- claude_mpm/services/parent_directory_manager/__init__.py +577 -0
- claude_mpm/services/parent_directory_manager/backup_manager.py +258 -0
- claude_mpm/services/parent_directory_manager/config_manager.py +210 -0
- claude_mpm/services/parent_directory_manager/deduplication_manager.py +279 -0
- claude_mpm/services/parent_directory_manager/framework_protector.py +143 -0
- claude_mpm/services/parent_directory_manager/operations.py +186 -0
- claude_mpm/services/parent_directory_manager/state_manager.py +624 -0
- claude_mpm/services/parent_directory_manager/template_deployer.py +579 -0
- claude_mpm/services/parent_directory_manager/validation_manager.py +378 -0
- claude_mpm/services/parent_directory_manager/version_control_helper.py +339 -0
- claude_mpm/services/parent_directory_manager/version_manager.py +222 -0
- claude_mpm/services/shared_prompt_cache.py +819 -0
- claude_mpm/services/ticket_manager.py +213 -0
- claude_mpm/services/ticket_manager_di.py +318 -0
- claude_mpm/services/ticketing_service_original.py +508 -0
- claude_mpm/services/version_control/VERSION +1 -0
- claude_mpm/services/version_control/__init__.py +70 -0
- claude_mpm/services/version_control/branch_strategy.py +670 -0
- claude_mpm/services/version_control/conflict_resolution.py +744 -0
- claude_mpm/services/version_control/git_operations.py +784 -0
- claude_mpm/services/version_control/semantic_versioning.py +703 -0
- claude_mpm/ui/__init__.py +1 -0
- claude_mpm/ui/rich_terminal_ui.py +295 -0
- claude_mpm/ui/terminal_ui.py +328 -0
- claude_mpm/utils/__init__.py +16 -0
- claude_mpm/utils/config_manager.py +468 -0
- claude_mpm/utils/import_migration_example.py +80 -0
- claude_mpm/utils/imports.py +182 -0
- claude_mpm/utils/path_operations.py +357 -0
- claude_mpm/utils/paths.py +289 -0
- claude_mpm-0.3.0.dist-info/METADATA +290 -0
- claude_mpm-0.3.0.dist-info/RECORD +159 -0
- claude_mpm-0.3.0.dist-info/WHEEL +5 -0
- claude_mpm-0.3.0.dist-info/entry_points.txt +4 -0
- claude_mpm-0.3.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
"""Agent registry integration for Claude MPM."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Optional, Dict, Any, List
|
|
7
|
+
import importlib.util
|
|
8
|
+
|
|
9
|
+
try:
|
|
10
|
+
from ..core.logger import get_logger
|
|
11
|
+
except ImportError:
|
|
12
|
+
from core.logger import get_logger
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SimpleAgentRegistry:
|
|
16
|
+
"""Simple agent registry implementation."""
|
|
17
|
+
|
|
18
|
+
def __init__(self, framework_path: Path):
|
|
19
|
+
self.framework_path = framework_path
|
|
20
|
+
self.agents = {}
|
|
21
|
+
self._discover_agents()
|
|
22
|
+
|
|
23
|
+
def _discover_agents(self):
|
|
24
|
+
"""Discover agents from the framework."""
|
|
25
|
+
agents_dir = self.framework_path / "src" / "claude_mpm" / "agents"
|
|
26
|
+
if agents_dir.exists():
|
|
27
|
+
for agent_file in agents_dir.glob("*.md"):
|
|
28
|
+
agent_id = agent_file.stem
|
|
29
|
+
self.agents[agent_id] = {
|
|
30
|
+
'type': agent_id,
|
|
31
|
+
'path': str(agent_file),
|
|
32
|
+
'last_modified': agent_file.stat().st_mtime
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
def listAgents(self, **kwargs):
|
|
36
|
+
"""List all agents."""
|
|
37
|
+
return self.agents
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class AgentRegistryAdapter:
|
|
41
|
+
"""
|
|
42
|
+
Adapter to integrate claude-multiagent-pm's agent registry.
|
|
43
|
+
|
|
44
|
+
This adapter:
|
|
45
|
+
1. Locates the claude-multiagent-pm installation
|
|
46
|
+
2. Dynamically imports the agent registry
|
|
47
|
+
3. Provides a clean interface for agent operations
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
def __init__(self, framework_path: Optional[Path] = None):
|
|
51
|
+
"""
|
|
52
|
+
Initialize the agent registry adapter.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
framework_path: Path to claude-multiagent-pm (auto-detected if None)
|
|
56
|
+
"""
|
|
57
|
+
self.logger = get_logger("agent_registry")
|
|
58
|
+
self.framework_path = framework_path or self._find_framework()
|
|
59
|
+
self.registry = None
|
|
60
|
+
self._initialize_registry()
|
|
61
|
+
|
|
62
|
+
def _find_framework(self) -> Optional[Path]:
|
|
63
|
+
"""Find claude-mpm installation.
|
|
64
|
+
|
|
65
|
+
Search order:
|
|
66
|
+
1. CLAUDE_MPM_PATH environment variable
|
|
67
|
+
2. Current working directory (if it's claude-mpm)
|
|
68
|
+
3. Walk up from current file location
|
|
69
|
+
4. Common development locations
|
|
70
|
+
"""
|
|
71
|
+
# Check environment variable first
|
|
72
|
+
env_path = os.environ.get("CLAUDE_MPM_PATH")
|
|
73
|
+
if env_path:
|
|
74
|
+
candidate = Path(env_path)
|
|
75
|
+
if self._is_valid_framework_path(candidate):
|
|
76
|
+
self.logger.info(f"Using claude-mpm from CLAUDE_MPM_PATH: {candidate}")
|
|
77
|
+
return candidate
|
|
78
|
+
else:
|
|
79
|
+
self.logger.warning(f"CLAUDE_MPM_PATH is set but invalid: {env_path}")
|
|
80
|
+
|
|
81
|
+
# Check current working directory
|
|
82
|
+
cwd = Path.cwd()
|
|
83
|
+
if self._is_valid_framework_path(cwd):
|
|
84
|
+
return cwd
|
|
85
|
+
|
|
86
|
+
# Check if we're running from within the installed package
|
|
87
|
+
current_file = Path(__file__).resolve()
|
|
88
|
+
for parent in current_file.parents:
|
|
89
|
+
if self._is_valid_framework_path(parent):
|
|
90
|
+
return parent
|
|
91
|
+
# Stop at site-packages or similar
|
|
92
|
+
if parent.name in ("site-packages", "dist-packages", "lib"):
|
|
93
|
+
break
|
|
94
|
+
|
|
95
|
+
# Check common development locations
|
|
96
|
+
candidates = [
|
|
97
|
+
Path.home() / "Projects" / "claude-mpm",
|
|
98
|
+
Path.home() / "claude-mpm",
|
|
99
|
+
]
|
|
100
|
+
|
|
101
|
+
for candidate in candidates:
|
|
102
|
+
if self._is_valid_framework_path(candidate):
|
|
103
|
+
self.logger.info(f"Found claude-mpm at: {candidate}")
|
|
104
|
+
return candidate
|
|
105
|
+
|
|
106
|
+
return None
|
|
107
|
+
|
|
108
|
+
def _is_valid_framework_path(self, path: Path) -> bool:
|
|
109
|
+
"""Check if a path is a valid claude-mpm installation."""
|
|
110
|
+
return (
|
|
111
|
+
path.exists() and
|
|
112
|
+
(path / "src" / "claude_mpm").exists() and
|
|
113
|
+
(path / "src" / "claude_mpm" / "agents").exists()
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
def _initialize_registry(self):
|
|
117
|
+
"""Initialize the agent registry."""
|
|
118
|
+
if not self.framework_path:
|
|
119
|
+
self.logger.warning("No framework path, registry unavailable")
|
|
120
|
+
return
|
|
121
|
+
|
|
122
|
+
try:
|
|
123
|
+
# For now, create a simple registry implementation
|
|
124
|
+
# This will be replaced with proper agent discovery later
|
|
125
|
+
self.registry = SimpleAgentRegistry(self.framework_path)
|
|
126
|
+
self.logger.info("Agent registry initialized successfully")
|
|
127
|
+
|
|
128
|
+
except Exception as e:
|
|
129
|
+
self.logger.error(f"Failed to initialize registry: {e}")
|
|
130
|
+
|
|
131
|
+
def list_agents(self, **kwargs) -> Dict[str, Any]:
|
|
132
|
+
"""
|
|
133
|
+
List available agents.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
**kwargs: Arguments to pass to AgentRegistry.listAgents()
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
Dictionary of agents with metadata
|
|
140
|
+
"""
|
|
141
|
+
if not self.registry:
|
|
142
|
+
return {}
|
|
143
|
+
|
|
144
|
+
try:
|
|
145
|
+
return self.registry.listAgents(**kwargs)
|
|
146
|
+
except Exception as e:
|
|
147
|
+
self.logger.error(f"Error listing agents: {e}")
|
|
148
|
+
return {}
|
|
149
|
+
|
|
150
|
+
def get_agent_definition(self, agent_name: str) -> Optional[str]:
|
|
151
|
+
"""
|
|
152
|
+
Get agent definition by name.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
agent_name: Name of the agent
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
Agent definition content or None
|
|
159
|
+
"""
|
|
160
|
+
if not self.registry:
|
|
161
|
+
return None
|
|
162
|
+
|
|
163
|
+
try:
|
|
164
|
+
# Try to load agent definition
|
|
165
|
+
agents = self.registry.listAgents()
|
|
166
|
+
for agent_id, metadata in agents.items():
|
|
167
|
+
if agent_name in agent_id or agent_name == metadata.get('type'):
|
|
168
|
+
# Load the agent file
|
|
169
|
+
agent_path = Path(metadata['path'])
|
|
170
|
+
if agent_path.exists():
|
|
171
|
+
return agent_path.read_text()
|
|
172
|
+
|
|
173
|
+
return None
|
|
174
|
+
|
|
175
|
+
except Exception as e:
|
|
176
|
+
self.logger.error(f"Error getting agent definition: {e}")
|
|
177
|
+
return None
|
|
178
|
+
|
|
179
|
+
def select_agent_for_task(self, task_description: str, required_specializations: Optional[List[str]] = None) -> Optional[Dict[str, Any]]:
|
|
180
|
+
"""
|
|
181
|
+
Select optimal agent for a task.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
task_description: Description of the task
|
|
185
|
+
required_specializations: Required agent specializations
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
Agent metadata or None
|
|
189
|
+
"""
|
|
190
|
+
if not self.registry:
|
|
191
|
+
return None
|
|
192
|
+
|
|
193
|
+
try:
|
|
194
|
+
# Get agents with required specializations
|
|
195
|
+
if required_specializations:
|
|
196
|
+
agents = self.registry.listAgents(specializations=required_specializations)
|
|
197
|
+
else:
|
|
198
|
+
agents = self.registry.listAgents()
|
|
199
|
+
|
|
200
|
+
if not agents:
|
|
201
|
+
return None
|
|
202
|
+
|
|
203
|
+
# For now, return the first matching agent
|
|
204
|
+
# In future, could implement more sophisticated selection
|
|
205
|
+
agent_id = next(iter(agents))
|
|
206
|
+
return {
|
|
207
|
+
'id': agent_id,
|
|
208
|
+
'metadata': agents[agent_id]
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
except Exception as e:
|
|
212
|
+
self.logger.error(f"Error selecting agent: {e}")
|
|
213
|
+
return None
|
|
214
|
+
|
|
215
|
+
def get_agent_hierarchy(self) -> Dict[str, List[str]]:
|
|
216
|
+
"""
|
|
217
|
+
Get agent hierarchy (project → user → system).
|
|
218
|
+
|
|
219
|
+
Returns:
|
|
220
|
+
Dictionary with hierarchy levels and agent names
|
|
221
|
+
"""
|
|
222
|
+
if not self.registry:
|
|
223
|
+
return {
|
|
224
|
+
'project': [],
|
|
225
|
+
'user': [],
|
|
226
|
+
'system': []
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
try:
|
|
230
|
+
# Get all agents
|
|
231
|
+
all_agents = self.registry.listAgents()
|
|
232
|
+
|
|
233
|
+
hierarchy = {
|
|
234
|
+
'project': [],
|
|
235
|
+
'user': [],
|
|
236
|
+
'system': []
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
# Categorize by path
|
|
240
|
+
for agent_id, metadata in all_agents.items():
|
|
241
|
+
agent_path = metadata.get('path', '')
|
|
242
|
+
|
|
243
|
+
if 'project-specific' in agent_path:
|
|
244
|
+
hierarchy['project'].append(agent_id)
|
|
245
|
+
elif 'user-agents' in agent_path or 'user-defined' in agent_path:
|
|
246
|
+
hierarchy['user'].append(agent_id)
|
|
247
|
+
else:
|
|
248
|
+
hierarchy['system'].append(agent_id)
|
|
249
|
+
|
|
250
|
+
return hierarchy
|
|
251
|
+
|
|
252
|
+
except Exception as e:
|
|
253
|
+
self.logger.error(f"Error getting hierarchy: {e}")
|
|
254
|
+
return {'project': [], 'user': [], 'system': []}
|
|
255
|
+
|
|
256
|
+
def get_core_agents(self) -> List[str]:
|
|
257
|
+
"""
|
|
258
|
+
Get list of core system agents.
|
|
259
|
+
|
|
260
|
+
Returns:
|
|
261
|
+
List of core agent names
|
|
262
|
+
"""
|
|
263
|
+
return [
|
|
264
|
+
'documentation',
|
|
265
|
+
'engineer',
|
|
266
|
+
'qa',
|
|
267
|
+
'research',
|
|
268
|
+
'ops',
|
|
269
|
+
'security',
|
|
270
|
+
'version-control',
|
|
271
|
+
'data-engineer'
|
|
272
|
+
]
|
|
273
|
+
|
|
274
|
+
def format_agent_for_task_tool(self, agent_name: str, task: str, context: str = "") -> str:
|
|
275
|
+
"""
|
|
276
|
+
Format agent delegation for Task Tool.
|
|
277
|
+
|
|
278
|
+
Args:
|
|
279
|
+
agent_name: Name of the agent
|
|
280
|
+
task: Task description
|
|
281
|
+
context: Additional context
|
|
282
|
+
|
|
283
|
+
Returns:
|
|
284
|
+
Formatted Task Tool prompt
|
|
285
|
+
"""
|
|
286
|
+
# Map agent names to nicknames
|
|
287
|
+
nicknames = {
|
|
288
|
+
'documentation': 'Documenter',
|
|
289
|
+
'engineer': 'Engineer',
|
|
290
|
+
'qa': 'QA',
|
|
291
|
+
'research': 'Researcher',
|
|
292
|
+
'ops': 'Ops',
|
|
293
|
+
'security': 'Security',
|
|
294
|
+
'version-control': 'Versioner',
|
|
295
|
+
'data-engineer': 'Data Engineer'
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
nickname = nicknames.get(agent_name, agent_name.title())
|
|
299
|
+
|
|
300
|
+
from datetime import datetime
|
|
301
|
+
today = datetime.now().strftime("%Y-%m-%d")
|
|
302
|
+
|
|
303
|
+
return f"""**{nickname}**: {task}
|
|
304
|
+
|
|
305
|
+
TEMPORAL CONTEXT: Today is {today}. Apply date awareness to task execution.
|
|
306
|
+
|
|
307
|
+
**Task**: {task}
|
|
308
|
+
|
|
309
|
+
**Context**: {context}
|
|
310
|
+
|
|
311
|
+
**Authority**: Agent has full authority for {agent_name} operations
|
|
312
|
+
**Expected Results**: Completed task with operational insights"""
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
"""Agent-specific session management for performance optimization."""
|
|
2
|
+
|
|
3
|
+
import uuid
|
|
4
|
+
from typing import Dict, Optional, Tuple
|
|
5
|
+
from datetime import datetime, timedelta
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
import json
|
|
8
|
+
from collections import defaultdict
|
|
9
|
+
|
|
10
|
+
from ..core.logger import get_logger
|
|
11
|
+
|
|
12
|
+
logger = get_logger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class AgentSessionManager:
|
|
16
|
+
"""Manages separate sessions for each agent type to avoid context pollution."""
|
|
17
|
+
|
|
18
|
+
def __init__(self, session_dir: Optional[Path] = None, max_sessions_per_agent: int = 3):
|
|
19
|
+
"""Initialize agent session manager.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
session_dir: Directory to store session metadata
|
|
23
|
+
max_sessions_per_agent: Maximum concurrent sessions per agent type
|
|
24
|
+
"""
|
|
25
|
+
self.session_dir = session_dir or Path.home() / ".claude-mpm" / "agent_sessions"
|
|
26
|
+
self.session_dir.mkdir(parents=True, exist_ok=True)
|
|
27
|
+
self.max_sessions_per_agent = max_sessions_per_agent
|
|
28
|
+
|
|
29
|
+
# Sessions organized by agent type
|
|
30
|
+
self.agent_sessions: Dict[str, Dict[str, Dict]] = defaultdict(dict)
|
|
31
|
+
self.session_locks: Dict[str, bool] = {} # Track which sessions are in use
|
|
32
|
+
|
|
33
|
+
self._load_sessions()
|
|
34
|
+
|
|
35
|
+
def get_agent_session(self, agent_type: str, create_if_missing: bool = True) -> Optional[str]:
|
|
36
|
+
"""Get or create a session for a specific agent type.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
agent_type: Type of agent (e.g., "engineer", "qa", "documentation")
|
|
40
|
+
create_if_missing: Whether to create a new session if none available
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
Session ID or None
|
|
44
|
+
"""
|
|
45
|
+
agent_type = agent_type.lower()
|
|
46
|
+
|
|
47
|
+
# Find an available session for this agent
|
|
48
|
+
for session_id, session_data in self.agent_sessions[agent_type].items():
|
|
49
|
+
if not self.session_locks.get(session_id, False):
|
|
50
|
+
# Check if session is still fresh (not too old)
|
|
51
|
+
created = datetime.fromisoformat(session_data["created_at"])
|
|
52
|
+
if datetime.now() - created < timedelta(hours=1):
|
|
53
|
+
# Use this session
|
|
54
|
+
self.session_locks[session_id] = True
|
|
55
|
+
session_data["last_used"] = datetime.now().isoformat()
|
|
56
|
+
session_data["use_count"] += 1
|
|
57
|
+
logger.info(f"Reusing session {session_id} for {agent_type} agent")
|
|
58
|
+
return session_id
|
|
59
|
+
|
|
60
|
+
# No available session, create new one if allowed
|
|
61
|
+
if create_if_missing and len(self.agent_sessions[agent_type]) < self.max_sessions_per_agent:
|
|
62
|
+
return self.create_agent_session(agent_type)
|
|
63
|
+
|
|
64
|
+
return None
|
|
65
|
+
|
|
66
|
+
def create_agent_session(self, agent_type: str) -> str:
|
|
67
|
+
"""Create a new session for a specific agent type.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
agent_type: Type of agent
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
New session ID
|
|
74
|
+
"""
|
|
75
|
+
session_id = str(uuid.uuid4())
|
|
76
|
+
agent_type = agent_type.lower()
|
|
77
|
+
|
|
78
|
+
session_data = {
|
|
79
|
+
"id": session_id,
|
|
80
|
+
"agent_type": agent_type,
|
|
81
|
+
"created_at": datetime.now().isoformat(),
|
|
82
|
+
"last_used": datetime.now().isoformat(),
|
|
83
|
+
"use_count": 0,
|
|
84
|
+
"tasks_completed": []
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
self.agent_sessions[agent_type][session_id] = session_data
|
|
88
|
+
self.session_locks[session_id] = True
|
|
89
|
+
self._save_sessions()
|
|
90
|
+
|
|
91
|
+
logger.info(f"Created new session {session_id} for {agent_type} agent")
|
|
92
|
+
return session_id
|
|
93
|
+
|
|
94
|
+
def release_session(self, session_id: str):
|
|
95
|
+
"""Release a session back to the pool.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
session_id: Session to release
|
|
99
|
+
"""
|
|
100
|
+
if session_id in self.session_locks:
|
|
101
|
+
self.session_locks[session_id] = False
|
|
102
|
+
logger.debug(f"Released session {session_id}")
|
|
103
|
+
|
|
104
|
+
def record_task(self, session_id: str, task: str, success: bool = True):
|
|
105
|
+
"""Record a task completion for a session.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
session_id: Session ID
|
|
109
|
+
task: Task description
|
|
110
|
+
success: Whether task completed successfully
|
|
111
|
+
"""
|
|
112
|
+
# Find which agent this session belongs to
|
|
113
|
+
for agent_type, sessions in self.agent_sessions.items():
|
|
114
|
+
if session_id in sessions:
|
|
115
|
+
sessions[session_id]["tasks_completed"].append({
|
|
116
|
+
"task": task[:100], # Truncate long tasks
|
|
117
|
+
"timestamp": datetime.now().isoformat(),
|
|
118
|
+
"success": success
|
|
119
|
+
})
|
|
120
|
+
self._save_sessions()
|
|
121
|
+
break
|
|
122
|
+
|
|
123
|
+
def cleanup_old_sessions(self, max_age_hours: int = 4):
|
|
124
|
+
"""Remove sessions older than max_age_hours.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
max_age_hours: Maximum age in hours
|
|
128
|
+
"""
|
|
129
|
+
now = datetime.now()
|
|
130
|
+
max_age = timedelta(hours=max_age_hours)
|
|
131
|
+
|
|
132
|
+
for agent_type in list(self.agent_sessions.keys()):
|
|
133
|
+
expired = []
|
|
134
|
+
for session_id, session_data in self.agent_sessions[agent_type].items():
|
|
135
|
+
created = datetime.fromisoformat(session_data["created_at"])
|
|
136
|
+
if now - created > max_age:
|
|
137
|
+
expired.append(session_id)
|
|
138
|
+
|
|
139
|
+
for session_id in expired:
|
|
140
|
+
del self.agent_sessions[agent_type][session_id]
|
|
141
|
+
if session_id in self.session_locks:
|
|
142
|
+
del self.session_locks[session_id]
|
|
143
|
+
logger.info(f"Cleaned up expired {agent_type} session: {session_id}")
|
|
144
|
+
|
|
145
|
+
if any(self.agent_sessions.values()):
|
|
146
|
+
self._save_sessions()
|
|
147
|
+
|
|
148
|
+
def get_session_stats(self) -> Dict[str, Dict]:
|
|
149
|
+
"""Get statistics about current sessions.
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
Stats by agent type
|
|
153
|
+
"""
|
|
154
|
+
stats = {}
|
|
155
|
+
for agent_type, sessions in self.agent_sessions.items():
|
|
156
|
+
active = sum(1 for sid in sessions if self.session_locks.get(sid, False))
|
|
157
|
+
total_tasks = sum(len(s["tasks_completed"]) for s in sessions.values())
|
|
158
|
+
|
|
159
|
+
stats[agent_type] = {
|
|
160
|
+
"total_sessions": len(sessions),
|
|
161
|
+
"active_sessions": active,
|
|
162
|
+
"available_sessions": len(sessions) - active,
|
|
163
|
+
"total_tasks_completed": total_tasks
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return stats
|
|
167
|
+
|
|
168
|
+
def initialize_agent_session(self, agent_type: str, session_id: str) -> str:
|
|
169
|
+
"""Initialize a session with agent-specific context.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
agent_type: Type of agent
|
|
173
|
+
session_id: Session ID to initialize
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
Initialization prompt for the agent
|
|
177
|
+
"""
|
|
178
|
+
# This prompt establishes the agent's identity for the session
|
|
179
|
+
initialization_prompts = {
|
|
180
|
+
"engineer": "You are the Engineer Agent. Your role is code implementation and development. Confirm with 'Engineer Agent ready.'",
|
|
181
|
+
"qa": "You are the QA Agent. Your role is testing and quality assurance. Confirm with 'QA Agent ready.'",
|
|
182
|
+
"documentation": "You are the Documentation Agent. Your role is creating and maintaining documentation. Confirm with 'Documentation Agent ready.'",
|
|
183
|
+
"research": "You are the Research Agent. Your role is investigation and analysis. Confirm with 'Research Agent ready.'",
|
|
184
|
+
"security": "You are the Security Agent. Your role is security analysis and protection. Confirm with 'Security Agent ready.'",
|
|
185
|
+
"ops": "You are the Ops Agent. Your role is deployment and operations. Confirm with 'Ops Agent ready.'",
|
|
186
|
+
"data_engineer": "You are the Data Engineer Agent. Your role is data management and processing. Confirm with 'Data Engineer Agent ready.'",
|
|
187
|
+
"version_control": "You are the Version Control Agent. Your role is Git operations and version management. Confirm with 'Version Control Agent ready.'"
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return initialization_prompts.get(
|
|
191
|
+
agent_type.lower(),
|
|
192
|
+
f"You are the {agent_type} Agent. Confirm with '{agent_type} Agent ready.'"
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
def _save_sessions(self):
|
|
196
|
+
"""Save sessions to disk."""
|
|
197
|
+
session_file = self.session_dir / "agent_sessions.json"
|
|
198
|
+
try:
|
|
199
|
+
data = {
|
|
200
|
+
"agent_sessions": dict(self.agent_sessions),
|
|
201
|
+
"updated_at": datetime.now().isoformat()
|
|
202
|
+
}
|
|
203
|
+
with open(session_file, 'w') as f:
|
|
204
|
+
json.dump(data, f, indent=2)
|
|
205
|
+
except Exception as e:
|
|
206
|
+
logger.error(f"Failed to save agent sessions: {e}")
|
|
207
|
+
|
|
208
|
+
def _load_sessions(self):
|
|
209
|
+
"""Load sessions from disk."""
|
|
210
|
+
session_file = self.session_dir / "agent_sessions.json"
|
|
211
|
+
if session_file.exists():
|
|
212
|
+
try:
|
|
213
|
+
with open(session_file, 'r') as f:
|
|
214
|
+
data = json.load(f)
|
|
215
|
+
self.agent_sessions = defaultdict(dict, data.get("agent_sessions", {}))
|
|
216
|
+
|
|
217
|
+
# Clean up old sessions on load
|
|
218
|
+
self.cleanup_old_sessions()
|
|
219
|
+
except Exception as e:
|
|
220
|
+
logger.error(f"Failed to load agent sessions: {e}")
|
|
221
|
+
self.agent_sessions = defaultdict(dict)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
# Example usage in SubprocessOrchestrator:
|
|
225
|
+
"""
|
|
226
|
+
class SubprocessOrchestrator:
|
|
227
|
+
def __init__(self, ...):
|
|
228
|
+
self.agent_session_manager = AgentSessionManager()
|
|
229
|
+
# Pre-warm common agent sessions on startup
|
|
230
|
+
self._prewarm_agent_sessions()
|
|
231
|
+
|
|
232
|
+
def _prewarm_agent_sessions(self):
|
|
233
|
+
'''Pre-warm sessions for common agents.'''
|
|
234
|
+
common_agents = ['engineer', 'qa', 'documentation']
|
|
235
|
+
for agent in common_agents:
|
|
236
|
+
session_id = self.agent_session_manager.create_agent_session(agent)
|
|
237
|
+
# Initialize the session with agent identity
|
|
238
|
+
init_prompt = self.agent_session_manager.initialize_agent_session(agent, session_id)
|
|
239
|
+
self.launcher.launch_oneshot(
|
|
240
|
+
message=init_prompt,
|
|
241
|
+
session_id=session_id,
|
|
242
|
+
timeout=10
|
|
243
|
+
)
|
|
244
|
+
self.agent_session_manager.release_session(session_id)
|
|
245
|
+
|
|
246
|
+
def run_subprocess(self, agent: str, task: str) -> Tuple[str, float, int]:
|
|
247
|
+
# Get a session for this agent type
|
|
248
|
+
session_id = self.agent_session_manager.get_agent_session(agent)
|
|
249
|
+
|
|
250
|
+
try:
|
|
251
|
+
# Create agent prompt (without role definition since session knows)
|
|
252
|
+
prompt = f'''
|
|
253
|
+
## Current Task
|
|
254
|
+
{task}
|
|
255
|
+
|
|
256
|
+
## Response Format
|
|
257
|
+
Provide a clear, structured response that completes the requested task.
|
|
258
|
+
'''
|
|
259
|
+
|
|
260
|
+
# Run with agent-specific session
|
|
261
|
+
stdout, stderr, returncode = self.launcher.launch_oneshot(
|
|
262
|
+
message=prompt,
|
|
263
|
+
session_id=session_id, # Reuses agent-specific context!
|
|
264
|
+
timeout=60
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
# Record task completion
|
|
268
|
+
self.agent_session_manager.record_task(session_id, task, returncode == 0)
|
|
269
|
+
|
|
270
|
+
finally:
|
|
271
|
+
# Release session back to pool
|
|
272
|
+
self.agent_session_manager.release_session(session_id)
|
|
273
|
+
"""
|