hanzo-mcp 0.8.2__py3-none-any.whl → 0.8.3__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 hanzo-mcp might be problematic. Click here for more details.
- hanzo_mcp/__init__.py +14 -1
- hanzo_mcp/bridge.py +133 -127
- hanzo_mcp/compute_nodes.py +68 -55
- hanzo_mcp/config/settings.py +11 -0
- hanzo_mcp/core/base_agent.py +521 -0
- hanzo_mcp/core/model_registry.py +436 -0
- hanzo_mcp/dev_server.py +3 -2
- hanzo_mcp/server.py +4 -1
- hanzo_mcp/tools/__init__.py +61 -46
- hanzo_mcp/tools/agent/__init__.py +19 -35
- hanzo_mcp/tools/agent/cli_tools.py +544 -0
- hanzo_mcp/tools/agent/unified_cli_tools.py +259 -0
- hanzo_mcp/tools/common/batch_tool.py +2 -0
- hanzo_mcp/tools/common/context.py +3 -1
- hanzo_mcp/tools/config/config_tool.py +121 -9
- hanzo_mcp/tools/filesystem/__init__.py +18 -0
- hanzo_mcp/tools/llm/__init__.py +44 -16
- hanzo_mcp/tools/llm/llm_tool.py +13 -0
- hanzo_mcp/tools/llm/llm_unified.py +911 -0
- hanzo_mcp/tools/shell/auto_background.py +24 -0
- {hanzo_mcp-0.8.2.dist-info → hanzo_mcp-0.8.3.dist-info}/METADATA +1 -1
- {hanzo_mcp-0.8.2.dist-info → hanzo_mcp-0.8.3.dist-info}/RECORD +25 -20
- {hanzo_mcp-0.8.2.dist-info → hanzo_mcp-0.8.3.dist-info}/WHEEL +0 -0
- {hanzo_mcp-0.8.2.dist-info → hanzo_mcp-0.8.3.dist-info}/entry_points.txt +0 -0
- {hanzo_mcp-0.8.2.dist-info → hanzo_mcp-0.8.3.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,521 @@
|
|
|
1
|
+
"""Base Agent - Unified foundation for all AI agent implementations.
|
|
2
|
+
|
|
3
|
+
This module provides the single base class for all agent operations,
|
|
4
|
+
following DRY principles and ensuring consistent behavior across all agents.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import asyncio
|
|
11
|
+
import logging
|
|
12
|
+
from abc import ABC, abstractmethod
|
|
13
|
+
from typing import Any, Dict, List, Optional, Protocol, TypeVar, Generic, runtime_checkable
|
|
14
|
+
from dataclasses import dataclass, field
|
|
15
|
+
from datetime import datetime
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
from .model_registry import registry, ModelConfig
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# Type variables for generic context
|
|
25
|
+
TContext = TypeVar("TContext")
|
|
26
|
+
TResult = TypeVar("TResult")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@runtime_checkable
|
|
30
|
+
class AgentContext(Protocol):
|
|
31
|
+
"""Protocol for agent execution context."""
|
|
32
|
+
|
|
33
|
+
async def log(self, message: str, level: str = "info") -> None:
|
|
34
|
+
"""Log a message."""
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
async def progress(self, message: str, percentage: Optional[float] = None) -> None:
|
|
38
|
+
"""Report progress."""
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass
|
|
43
|
+
class AgentConfig:
|
|
44
|
+
"""Configuration for agent execution."""
|
|
45
|
+
|
|
46
|
+
model: str = "claude-3-5-sonnet-20241022"
|
|
47
|
+
timeout: int = 300
|
|
48
|
+
max_retries: int = 3
|
|
49
|
+
working_dir: Optional[Path] = None
|
|
50
|
+
environment: Dict[str, str] = field(default_factory=dict)
|
|
51
|
+
stream_output: bool = False
|
|
52
|
+
use_worktree: bool = False
|
|
53
|
+
|
|
54
|
+
def __post_init__(self) -> None:
|
|
55
|
+
"""Resolve model name and validate configuration."""
|
|
56
|
+
self.model = registry.resolve(self.model)
|
|
57
|
+
if self.working_dir and not isinstance(self.working_dir, Path):
|
|
58
|
+
self.working_dir = Path(self.working_dir)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@dataclass
|
|
62
|
+
class AgentResult:
|
|
63
|
+
"""Result from agent execution."""
|
|
64
|
+
|
|
65
|
+
success: bool
|
|
66
|
+
output: Optional[str] = None
|
|
67
|
+
error: Optional[str] = None
|
|
68
|
+
duration: Optional[float] = None
|
|
69
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def content(self) -> str:
|
|
73
|
+
"""Get the primary content (output or error)."""
|
|
74
|
+
return self.output if self.success else (self.error or "Unknown error")
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class BaseAgent(ABC, Generic[TContext]):
|
|
78
|
+
"""Base class for all AI agents.
|
|
79
|
+
|
|
80
|
+
This is the single foundation for all agent implementations,
|
|
81
|
+
ensuring consistent behavior and eliminating code duplication.
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
def __init__(self, config: Optional[AgentConfig] = None) -> None:
|
|
85
|
+
"""Initialize agent with configuration.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
config: Agent configuration
|
|
89
|
+
"""
|
|
90
|
+
self.config = config or AgentConfig()
|
|
91
|
+
self._start_time: Optional[datetime] = None
|
|
92
|
+
self._end_time: Optional[datetime] = None
|
|
93
|
+
|
|
94
|
+
@property
|
|
95
|
+
@abstractmethod
|
|
96
|
+
def name(self) -> str:
|
|
97
|
+
"""Agent name."""
|
|
98
|
+
pass
|
|
99
|
+
|
|
100
|
+
@property
|
|
101
|
+
@abstractmethod
|
|
102
|
+
def description(self) -> str:
|
|
103
|
+
"""Agent description."""
|
|
104
|
+
pass
|
|
105
|
+
|
|
106
|
+
async def execute(
|
|
107
|
+
self,
|
|
108
|
+
prompt: str,
|
|
109
|
+
context: Optional[TContext] = None,
|
|
110
|
+
**kwargs: Any,
|
|
111
|
+
) -> AgentResult:
|
|
112
|
+
"""Execute agent with prompt.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
prompt: The prompt or task
|
|
116
|
+
context: Execution context
|
|
117
|
+
**kwargs: Additional parameters
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
Agent execution result
|
|
121
|
+
"""
|
|
122
|
+
self._start_time = datetime.now()
|
|
123
|
+
|
|
124
|
+
try:
|
|
125
|
+
# Setup environment
|
|
126
|
+
env = self._prepare_environment()
|
|
127
|
+
|
|
128
|
+
# Log start
|
|
129
|
+
if context and isinstance(context, AgentContext):
|
|
130
|
+
await context.log(f"Starting {self.name} with model {self.config.model}")
|
|
131
|
+
|
|
132
|
+
# Execute with retries
|
|
133
|
+
result = await self._execute_with_retries(prompt, context, env, **kwargs)
|
|
134
|
+
|
|
135
|
+
# Calculate duration
|
|
136
|
+
self._end_time = datetime.now()
|
|
137
|
+
duration = (self._end_time - self._start_time).total_seconds()
|
|
138
|
+
|
|
139
|
+
return AgentResult(
|
|
140
|
+
success=True,
|
|
141
|
+
output=result,
|
|
142
|
+
duration=duration,
|
|
143
|
+
metadata={"model": self.config.model, "agent": self.name},
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
except Exception as e:
|
|
147
|
+
self._end_time = datetime.now()
|
|
148
|
+
duration = (self._end_time - self._start_time).total_seconds() if self._start_time else None
|
|
149
|
+
|
|
150
|
+
logger.error(f"Agent {self.name} failed: {e}")
|
|
151
|
+
|
|
152
|
+
return AgentResult(
|
|
153
|
+
success=False,
|
|
154
|
+
error=str(e),
|
|
155
|
+
duration=duration,
|
|
156
|
+
metadata={"model": self.config.model, "agent": self.name},
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
def _prepare_environment(self) -> Dict[str, str]:
|
|
160
|
+
"""Prepare environment variables for execution.
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
Environment variables dictionary
|
|
164
|
+
"""
|
|
165
|
+
env = os.environ.copy()
|
|
166
|
+
|
|
167
|
+
# Add model-specific API key
|
|
168
|
+
model_config = registry.get(self.config.model)
|
|
169
|
+
if model_config and model_config.api_key_env:
|
|
170
|
+
key_var = model_config.api_key_env
|
|
171
|
+
if key_var in os.environ:
|
|
172
|
+
env[key_var] = os.environ[key_var]
|
|
173
|
+
|
|
174
|
+
# Add Hanzo unified auth
|
|
175
|
+
if "HANZO_API_KEY" in os.environ:
|
|
176
|
+
env["HANZO_API_KEY"] = os.environ["HANZO_API_KEY"]
|
|
177
|
+
|
|
178
|
+
# Add custom environment
|
|
179
|
+
env.update(self.config.environment)
|
|
180
|
+
|
|
181
|
+
return env
|
|
182
|
+
|
|
183
|
+
async def _execute_with_retries(
|
|
184
|
+
self,
|
|
185
|
+
prompt: str,
|
|
186
|
+
context: Optional[TContext],
|
|
187
|
+
env: Dict[str, str],
|
|
188
|
+
**kwargs: Any,
|
|
189
|
+
) -> str:
|
|
190
|
+
"""Execute with retry logic.
|
|
191
|
+
|
|
192
|
+
Args:
|
|
193
|
+
prompt: The prompt
|
|
194
|
+
context: Execution context
|
|
195
|
+
env: Environment variables
|
|
196
|
+
**kwargs: Additional parameters
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
Execution output
|
|
200
|
+
|
|
201
|
+
Raises:
|
|
202
|
+
Exception: If all retries fail
|
|
203
|
+
"""
|
|
204
|
+
last_error = None
|
|
205
|
+
|
|
206
|
+
for attempt in range(self.config.max_retries):
|
|
207
|
+
try:
|
|
208
|
+
# Call the implementation
|
|
209
|
+
result = await self._execute_impl(prompt, context, env, **kwargs)
|
|
210
|
+
return result
|
|
211
|
+
|
|
212
|
+
except asyncio.TimeoutError:
|
|
213
|
+
last_error = f"Timeout after {self.config.timeout} seconds"
|
|
214
|
+
if context and isinstance(context, AgentContext):
|
|
215
|
+
await context.log(f"Attempt {attempt + 1} timed out", "warning")
|
|
216
|
+
|
|
217
|
+
except Exception as e:
|
|
218
|
+
last_error = str(e)
|
|
219
|
+
if context and isinstance(context, AgentContext):
|
|
220
|
+
await context.log(f"Attempt {attempt + 1} failed: {e}", "warning")
|
|
221
|
+
|
|
222
|
+
# Don't retry on certain errors
|
|
223
|
+
if "unauthorized" in str(e).lower() or "forbidden" in str(e).lower():
|
|
224
|
+
raise
|
|
225
|
+
|
|
226
|
+
# Wait before retry (exponential backoff)
|
|
227
|
+
if attempt < self.config.max_retries - 1:
|
|
228
|
+
await asyncio.sleep(2 ** attempt)
|
|
229
|
+
|
|
230
|
+
raise Exception(f"All {self.config.max_retries} attempts failed. Last error: {last_error}")
|
|
231
|
+
|
|
232
|
+
@abstractmethod
|
|
233
|
+
async def _execute_impl(
|
|
234
|
+
self,
|
|
235
|
+
prompt: str,
|
|
236
|
+
context: Optional[TContext],
|
|
237
|
+
env: Dict[str, str],
|
|
238
|
+
**kwargs: Any,
|
|
239
|
+
) -> str:
|
|
240
|
+
"""Implementation-specific execution.
|
|
241
|
+
|
|
242
|
+
Args:
|
|
243
|
+
prompt: The prompt
|
|
244
|
+
context: Execution context
|
|
245
|
+
env: Environment variables
|
|
246
|
+
**kwargs: Additional parameters
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
Execution output
|
|
250
|
+
"""
|
|
251
|
+
pass
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
class CLIAgent(BaseAgent[TContext]):
|
|
255
|
+
"""Base class for CLI-based agents."""
|
|
256
|
+
|
|
257
|
+
@property
|
|
258
|
+
@abstractmethod
|
|
259
|
+
def cli_command(self) -> str:
|
|
260
|
+
"""CLI command to execute."""
|
|
261
|
+
pass
|
|
262
|
+
|
|
263
|
+
def build_command(self, prompt: str, **kwargs: Any) -> List[str]:
|
|
264
|
+
"""Build the CLI command.
|
|
265
|
+
|
|
266
|
+
Args:
|
|
267
|
+
prompt: The prompt
|
|
268
|
+
**kwargs: Additional parameters
|
|
269
|
+
|
|
270
|
+
Returns:
|
|
271
|
+
Command arguments list
|
|
272
|
+
"""
|
|
273
|
+
command = [self.cli_command]
|
|
274
|
+
|
|
275
|
+
# Add model if specified
|
|
276
|
+
model_config = registry.get(self.config.model)
|
|
277
|
+
if model_config:
|
|
278
|
+
command.extend(["--model", model_config.full_name])
|
|
279
|
+
|
|
280
|
+
# Add prompt
|
|
281
|
+
command.append(prompt)
|
|
282
|
+
|
|
283
|
+
return command
|
|
284
|
+
|
|
285
|
+
async def _execute_impl(
|
|
286
|
+
self,
|
|
287
|
+
prompt: str,
|
|
288
|
+
context: Optional[TContext],
|
|
289
|
+
env: Dict[str, str],
|
|
290
|
+
**kwargs: Any,
|
|
291
|
+
) -> str:
|
|
292
|
+
"""Execute CLI command.
|
|
293
|
+
|
|
294
|
+
Args:
|
|
295
|
+
prompt: The prompt
|
|
296
|
+
context: Execution context
|
|
297
|
+
env: Environment variables
|
|
298
|
+
**kwargs: Additional parameters
|
|
299
|
+
|
|
300
|
+
Returns:
|
|
301
|
+
Command output
|
|
302
|
+
"""
|
|
303
|
+
command = self.build_command(prompt, **kwargs)
|
|
304
|
+
|
|
305
|
+
# Determine if we need stdin
|
|
306
|
+
needs_stdin = self.cli_command in ["claude", "cline"]
|
|
307
|
+
|
|
308
|
+
# Execute command
|
|
309
|
+
process = await asyncio.create_subprocess_exec(
|
|
310
|
+
*command,
|
|
311
|
+
stdin=asyncio.subprocess.PIPE if needs_stdin else None,
|
|
312
|
+
stdout=asyncio.subprocess.PIPE,
|
|
313
|
+
stderr=asyncio.subprocess.PIPE,
|
|
314
|
+
cwd=str(self.config.working_dir) if self.config.working_dir else None,
|
|
315
|
+
env=env,
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
# Handle timeout
|
|
319
|
+
try:
|
|
320
|
+
stdout, stderr = await asyncio.wait_for(
|
|
321
|
+
process.communicate(prompt.encode() if needs_stdin else None),
|
|
322
|
+
timeout=self.config.timeout,
|
|
323
|
+
)
|
|
324
|
+
except asyncio.TimeoutError:
|
|
325
|
+
process.kill()
|
|
326
|
+
raise asyncio.TimeoutError(f"Command timed out after {self.config.timeout} seconds")
|
|
327
|
+
|
|
328
|
+
# Check for errors
|
|
329
|
+
if process.returncode != 0:
|
|
330
|
+
error_msg = stderr.decode() if stderr else "Command failed"
|
|
331
|
+
raise Exception(error_msg)
|
|
332
|
+
|
|
333
|
+
return stdout.decode()
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
class APIAgent(BaseAgent[TContext]):
|
|
337
|
+
"""Base class for API-based agents."""
|
|
338
|
+
|
|
339
|
+
async def _execute_impl(
|
|
340
|
+
self,
|
|
341
|
+
prompt: str,
|
|
342
|
+
context: Optional[TContext],
|
|
343
|
+
env: Dict[str, str],
|
|
344
|
+
**kwargs: Any,
|
|
345
|
+
) -> str:
|
|
346
|
+
"""Execute via API.
|
|
347
|
+
|
|
348
|
+
Args:
|
|
349
|
+
prompt: The prompt
|
|
350
|
+
context: Execution context
|
|
351
|
+
env: Environment variables
|
|
352
|
+
**kwargs: Additional parameters
|
|
353
|
+
|
|
354
|
+
Returns:
|
|
355
|
+
API response
|
|
356
|
+
"""
|
|
357
|
+
# This would be implemented by specific API agents
|
|
358
|
+
# using the appropriate client library
|
|
359
|
+
raise NotImplementedError("API agents must implement _execute_impl")
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
class AgentOrchestrator:
|
|
363
|
+
"""Orchestrator for managing multiple agents."""
|
|
364
|
+
|
|
365
|
+
def __init__(self, default_config: Optional[AgentConfig] = None) -> None:
|
|
366
|
+
"""Initialize orchestrator.
|
|
367
|
+
|
|
368
|
+
Args:
|
|
369
|
+
default_config: Default configuration for agents
|
|
370
|
+
"""
|
|
371
|
+
self.default_config = default_config or AgentConfig()
|
|
372
|
+
self._agents: Dict[str, BaseAgent] = {}
|
|
373
|
+
self._semaphore: Optional[asyncio.Semaphore] = None
|
|
374
|
+
|
|
375
|
+
def register(self, agent: BaseAgent) -> None:
|
|
376
|
+
"""Register an agent.
|
|
377
|
+
|
|
378
|
+
Args:
|
|
379
|
+
agent: Agent to register
|
|
380
|
+
"""
|
|
381
|
+
self._agents[agent.name] = agent
|
|
382
|
+
|
|
383
|
+
def get_agent(self, name: str) -> Optional[BaseAgent]:
|
|
384
|
+
"""Get agent by name.
|
|
385
|
+
|
|
386
|
+
Args:
|
|
387
|
+
name: Agent name
|
|
388
|
+
|
|
389
|
+
Returns:
|
|
390
|
+
Agent instance or None
|
|
391
|
+
"""
|
|
392
|
+
return self._agents.get(name)
|
|
393
|
+
|
|
394
|
+
async def execute_single(
|
|
395
|
+
self,
|
|
396
|
+
agent_name: str,
|
|
397
|
+
prompt: str,
|
|
398
|
+
context: Optional[Any] = None,
|
|
399
|
+
**kwargs: Any,
|
|
400
|
+
) -> AgentResult:
|
|
401
|
+
"""Execute single agent.
|
|
402
|
+
|
|
403
|
+
Args:
|
|
404
|
+
agent_name: Name of agent to use
|
|
405
|
+
prompt: The prompt
|
|
406
|
+
context: Execution context
|
|
407
|
+
**kwargs: Additional parameters
|
|
408
|
+
|
|
409
|
+
Returns:
|
|
410
|
+
Execution result
|
|
411
|
+
"""
|
|
412
|
+
agent = self.get_agent(agent_name)
|
|
413
|
+
if not agent:
|
|
414
|
+
return AgentResult(
|
|
415
|
+
success=False,
|
|
416
|
+
error=f"Agent '{agent_name}' not found",
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
return await agent.execute(prompt, context, **kwargs)
|
|
420
|
+
|
|
421
|
+
async def execute_parallel(
|
|
422
|
+
self,
|
|
423
|
+
tasks: List[Dict[str, Any]],
|
|
424
|
+
max_concurrent: int = 5,
|
|
425
|
+
) -> List[AgentResult]:
|
|
426
|
+
"""Execute multiple agents in parallel.
|
|
427
|
+
|
|
428
|
+
Args:
|
|
429
|
+
tasks: List of task definitions
|
|
430
|
+
max_concurrent: Maximum concurrent executions
|
|
431
|
+
|
|
432
|
+
Returns:
|
|
433
|
+
List of results
|
|
434
|
+
"""
|
|
435
|
+
self._semaphore = asyncio.Semaphore(max_concurrent)
|
|
436
|
+
|
|
437
|
+
async def run_with_semaphore(task: Dict[str, Any]) -> AgentResult:
|
|
438
|
+
async with self._semaphore:
|
|
439
|
+
return await self.execute_single(
|
|
440
|
+
task["agent"],
|
|
441
|
+
task["prompt"],
|
|
442
|
+
task.get("context"),
|
|
443
|
+
**task.get("kwargs", {}),
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
return await asyncio.gather(
|
|
447
|
+
*[run_with_semaphore(task) for task in tasks],
|
|
448
|
+
return_exceptions=False,
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
async def execute_consensus(
|
|
452
|
+
self,
|
|
453
|
+
prompt: str,
|
|
454
|
+
agents: List[str],
|
|
455
|
+
threshold: float = 0.66,
|
|
456
|
+
) -> Dict[str, Any]:
|
|
457
|
+
"""Execute consensus operation with multiple agents.
|
|
458
|
+
|
|
459
|
+
Args:
|
|
460
|
+
prompt: The prompt
|
|
461
|
+
agents: List of agent names
|
|
462
|
+
threshold: Agreement threshold
|
|
463
|
+
|
|
464
|
+
Returns:
|
|
465
|
+
Consensus results
|
|
466
|
+
"""
|
|
467
|
+
# Execute all agents in parallel
|
|
468
|
+
tasks = [{"agent": agent, "prompt": prompt} for agent in agents]
|
|
469
|
+
results = await self.execute_parallel(tasks)
|
|
470
|
+
|
|
471
|
+
# Analyze consensus
|
|
472
|
+
successful = [r for r in results if r.success]
|
|
473
|
+
agreement = len(successful) / len(results) if results else 0
|
|
474
|
+
|
|
475
|
+
return {
|
|
476
|
+
"consensus_reached": agreement >= threshold,
|
|
477
|
+
"agreement_score": agreement,
|
|
478
|
+
"individual_results": results,
|
|
479
|
+
"agents_used": agents,
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
async def execute_chain(
|
|
483
|
+
self,
|
|
484
|
+
initial_prompt: str,
|
|
485
|
+
agents: List[str],
|
|
486
|
+
) -> List[AgentResult]:
|
|
487
|
+
"""Execute agents in a chain, passing output forward.
|
|
488
|
+
|
|
489
|
+
Args:
|
|
490
|
+
initial_prompt: Initial prompt
|
|
491
|
+
agents: List of agent names
|
|
492
|
+
|
|
493
|
+
Returns:
|
|
494
|
+
List of results from each step
|
|
495
|
+
"""
|
|
496
|
+
results = []
|
|
497
|
+
current_prompt = initial_prompt
|
|
498
|
+
|
|
499
|
+
for agent_name in agents:
|
|
500
|
+
result = await self.execute_single(agent_name, current_prompt)
|
|
501
|
+
results.append(result)
|
|
502
|
+
|
|
503
|
+
if result.success and result.output:
|
|
504
|
+
# Use output as input for next agent
|
|
505
|
+
current_prompt = f"Review and improve:\n{result.output}"
|
|
506
|
+
else:
|
|
507
|
+
# Chain broken
|
|
508
|
+
break
|
|
509
|
+
|
|
510
|
+
return results
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
__all__ = [
|
|
514
|
+
"AgentContext",
|
|
515
|
+
"AgentConfig",
|
|
516
|
+
"AgentResult",
|
|
517
|
+
"BaseAgent",
|
|
518
|
+
"CLIAgent",
|
|
519
|
+
"APIAgent",
|
|
520
|
+
"AgentOrchestrator",
|
|
521
|
+
]
|