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.

Files changed (159) hide show
  1. claude_mpm/__init__.py +17 -0
  2. claude_mpm/__main__.py +14 -0
  3. claude_mpm/_version.py +32 -0
  4. claude_mpm/agents/BASE_AGENT_TEMPLATE.md +88 -0
  5. claude_mpm/agents/INSTRUCTIONS.md +375 -0
  6. claude_mpm/agents/__init__.py +118 -0
  7. claude_mpm/agents/agent_loader.py +621 -0
  8. claude_mpm/agents/agent_loader_integration.py +229 -0
  9. claude_mpm/agents/agents_metadata.py +204 -0
  10. claude_mpm/agents/base_agent.json +27 -0
  11. claude_mpm/agents/base_agent_loader.py +519 -0
  12. claude_mpm/agents/schema/agent_schema.json +160 -0
  13. claude_mpm/agents/system_agent_config.py +587 -0
  14. claude_mpm/agents/templates/__init__.py +101 -0
  15. claude_mpm/agents/templates/data_engineer_agent.json +46 -0
  16. claude_mpm/agents/templates/documentation_agent.json +45 -0
  17. claude_mpm/agents/templates/engineer_agent.json +49 -0
  18. claude_mpm/agents/templates/ops_agent.json +46 -0
  19. claude_mpm/agents/templates/qa_agent.json +45 -0
  20. claude_mpm/agents/templates/research_agent.json +49 -0
  21. claude_mpm/agents/templates/security_agent.json +46 -0
  22. claude_mpm/agents/templates/update-optimized-specialized-agents.json +374 -0
  23. claude_mpm/agents/templates/version_control_agent.json +46 -0
  24. claude_mpm/agents/test_fix_deployment/.claude-pm/config/project.json +6 -0
  25. claude_mpm/cli.py +655 -0
  26. claude_mpm/cli_main.py +13 -0
  27. claude_mpm/cli_module/__init__.py +15 -0
  28. claude_mpm/cli_module/args.py +222 -0
  29. claude_mpm/cli_module/commands.py +203 -0
  30. claude_mpm/cli_module/migration_example.py +183 -0
  31. claude_mpm/cli_module/refactoring_guide.md +253 -0
  32. claude_mpm/cli_old/__init__.py +1 -0
  33. claude_mpm/cli_old/ticket_cli.py +102 -0
  34. claude_mpm/config/__init__.py +5 -0
  35. claude_mpm/config/hook_config.py +42 -0
  36. claude_mpm/constants.py +150 -0
  37. claude_mpm/core/__init__.py +45 -0
  38. claude_mpm/core/agent_name_normalizer.py +248 -0
  39. claude_mpm/core/agent_registry.py +627 -0
  40. claude_mpm/core/agent_registry.py.bak +312 -0
  41. claude_mpm/core/agent_session_manager.py +273 -0
  42. claude_mpm/core/base_service.py +747 -0
  43. claude_mpm/core/base_service.py.bak +406 -0
  44. claude_mpm/core/config.py +334 -0
  45. claude_mpm/core/config_aliases.py +292 -0
  46. claude_mpm/core/container.py +347 -0
  47. claude_mpm/core/factories.py +281 -0
  48. claude_mpm/core/framework_loader.py +472 -0
  49. claude_mpm/core/injectable_service.py +206 -0
  50. claude_mpm/core/interfaces.py +539 -0
  51. claude_mpm/core/logger.py +468 -0
  52. claude_mpm/core/minimal_framework_loader.py +107 -0
  53. claude_mpm/core/mixins.py +150 -0
  54. claude_mpm/core/service_registry.py +299 -0
  55. claude_mpm/core/session_manager.py +190 -0
  56. claude_mpm/core/simple_runner.py +511 -0
  57. claude_mpm/core/tool_access_control.py +173 -0
  58. claude_mpm/hooks/README.md +243 -0
  59. claude_mpm/hooks/__init__.py +5 -0
  60. claude_mpm/hooks/base_hook.py +154 -0
  61. claude_mpm/hooks/builtin/__init__.py +1 -0
  62. claude_mpm/hooks/builtin/logging_hook_example.py +165 -0
  63. claude_mpm/hooks/builtin/post_delegation_hook_example.py +124 -0
  64. claude_mpm/hooks/builtin/pre_delegation_hook_example.py +125 -0
  65. claude_mpm/hooks/builtin/submit_hook_example.py +100 -0
  66. claude_mpm/hooks/builtin/ticket_extraction_hook_example.py +237 -0
  67. claude_mpm/hooks/builtin/todo_agent_prefix_hook.py +239 -0
  68. claude_mpm/hooks/builtin/workflow_start_hook.py +181 -0
  69. claude_mpm/hooks/hook_client.py +264 -0
  70. claude_mpm/hooks/hook_runner.py +370 -0
  71. claude_mpm/hooks/json_rpc_executor.py +259 -0
  72. claude_mpm/hooks/json_rpc_hook_client.py +319 -0
  73. claude_mpm/hooks/tool_call_interceptor.py +204 -0
  74. claude_mpm/init.py +246 -0
  75. claude_mpm/orchestration/SUBPROCESS_DESIGN.md +66 -0
  76. claude_mpm/orchestration/__init__.py +6 -0
  77. claude_mpm/orchestration/archive/direct_orchestrator.py +195 -0
  78. claude_mpm/orchestration/archive/factory.py +215 -0
  79. claude_mpm/orchestration/archive/hook_enabled_orchestrator.py +188 -0
  80. claude_mpm/orchestration/archive/hook_integration_example.py +178 -0
  81. claude_mpm/orchestration/archive/interactive_subprocess_orchestrator.py +826 -0
  82. claude_mpm/orchestration/archive/orchestrator.py +501 -0
  83. claude_mpm/orchestration/archive/pexpect_orchestrator.py +252 -0
  84. claude_mpm/orchestration/archive/pty_orchestrator.py +270 -0
  85. claude_mpm/orchestration/archive/simple_orchestrator.py +82 -0
  86. claude_mpm/orchestration/archive/subprocess_orchestrator.py +801 -0
  87. claude_mpm/orchestration/archive/system_prompt_orchestrator.py +278 -0
  88. claude_mpm/orchestration/archive/wrapper_orchestrator.py +187 -0
  89. claude_mpm/scripts/__init__.py +1 -0
  90. claude_mpm/scripts/ticket.py +269 -0
  91. claude_mpm/services/__init__.py +10 -0
  92. claude_mpm/services/agent_deployment.py +955 -0
  93. claude_mpm/services/agent_lifecycle_manager.py +948 -0
  94. claude_mpm/services/agent_management_service.py +596 -0
  95. claude_mpm/services/agent_modification_tracker.py +841 -0
  96. claude_mpm/services/agent_profile_loader.py +606 -0
  97. claude_mpm/services/agent_registry.py +677 -0
  98. claude_mpm/services/base_agent_manager.py +380 -0
  99. claude_mpm/services/framework_agent_loader.py +337 -0
  100. claude_mpm/services/framework_claude_md_generator/README.md +92 -0
  101. claude_mpm/services/framework_claude_md_generator/__init__.py +206 -0
  102. claude_mpm/services/framework_claude_md_generator/content_assembler.py +151 -0
  103. claude_mpm/services/framework_claude_md_generator/content_validator.py +126 -0
  104. claude_mpm/services/framework_claude_md_generator/deployment_manager.py +137 -0
  105. claude_mpm/services/framework_claude_md_generator/section_generators/__init__.py +106 -0
  106. claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +582 -0
  107. claude_mpm/services/framework_claude_md_generator/section_generators/claude_pm_init.py +97 -0
  108. claude_mpm/services/framework_claude_md_generator/section_generators/core_responsibilities.py +27 -0
  109. claude_mpm/services/framework_claude_md_generator/section_generators/delegation_constraints.py +23 -0
  110. claude_mpm/services/framework_claude_md_generator/section_generators/environment_config.py +23 -0
  111. claude_mpm/services/framework_claude_md_generator/section_generators/footer.py +20 -0
  112. claude_mpm/services/framework_claude_md_generator/section_generators/header.py +26 -0
  113. claude_mpm/services/framework_claude_md_generator/section_generators/orchestration_principles.py +30 -0
  114. claude_mpm/services/framework_claude_md_generator/section_generators/role_designation.py +37 -0
  115. claude_mpm/services/framework_claude_md_generator/section_generators/subprocess_validation.py +111 -0
  116. claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +89 -0
  117. claude_mpm/services/framework_claude_md_generator/section_generators/troubleshooting.py +39 -0
  118. claude_mpm/services/framework_claude_md_generator/section_manager.py +106 -0
  119. claude_mpm/services/framework_claude_md_generator/version_manager.py +121 -0
  120. claude_mpm/services/framework_claude_md_generator.py +621 -0
  121. claude_mpm/services/hook_service.py +388 -0
  122. claude_mpm/services/hook_service_manager.py +223 -0
  123. claude_mpm/services/json_rpc_hook_manager.py +92 -0
  124. claude_mpm/services/parent_directory_manager/README.md +83 -0
  125. claude_mpm/services/parent_directory_manager/__init__.py +577 -0
  126. claude_mpm/services/parent_directory_manager/backup_manager.py +258 -0
  127. claude_mpm/services/parent_directory_manager/config_manager.py +210 -0
  128. claude_mpm/services/parent_directory_manager/deduplication_manager.py +279 -0
  129. claude_mpm/services/parent_directory_manager/framework_protector.py +143 -0
  130. claude_mpm/services/parent_directory_manager/operations.py +186 -0
  131. claude_mpm/services/parent_directory_manager/state_manager.py +624 -0
  132. claude_mpm/services/parent_directory_manager/template_deployer.py +579 -0
  133. claude_mpm/services/parent_directory_manager/validation_manager.py +378 -0
  134. claude_mpm/services/parent_directory_manager/version_control_helper.py +339 -0
  135. claude_mpm/services/parent_directory_manager/version_manager.py +222 -0
  136. claude_mpm/services/shared_prompt_cache.py +819 -0
  137. claude_mpm/services/ticket_manager.py +213 -0
  138. claude_mpm/services/ticket_manager_di.py +318 -0
  139. claude_mpm/services/ticketing_service_original.py +508 -0
  140. claude_mpm/services/version_control/VERSION +1 -0
  141. claude_mpm/services/version_control/__init__.py +70 -0
  142. claude_mpm/services/version_control/branch_strategy.py +670 -0
  143. claude_mpm/services/version_control/conflict_resolution.py +744 -0
  144. claude_mpm/services/version_control/git_operations.py +784 -0
  145. claude_mpm/services/version_control/semantic_versioning.py +703 -0
  146. claude_mpm/ui/__init__.py +1 -0
  147. claude_mpm/ui/rich_terminal_ui.py +295 -0
  148. claude_mpm/ui/terminal_ui.py +328 -0
  149. claude_mpm/utils/__init__.py +16 -0
  150. claude_mpm/utils/config_manager.py +468 -0
  151. claude_mpm/utils/import_migration_example.py +80 -0
  152. claude_mpm/utils/imports.py +182 -0
  153. claude_mpm/utils/path_operations.py +357 -0
  154. claude_mpm/utils/paths.py +289 -0
  155. claude_mpm-0.3.0.dist-info/METADATA +290 -0
  156. claude_mpm-0.3.0.dist-info/RECORD +159 -0
  157. claude_mpm-0.3.0.dist-info/WHEEL +5 -0
  158. claude_mpm-0.3.0.dist-info/entry_points.txt +4 -0
  159. 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
+ """