strands-swarms 0.1.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.
- strands_swarms/__init__.py +88 -0
- strands_swarms/events.py +564 -0
- strands_swarms/orchestrator.py +265 -0
- strands_swarms/py.typed +0 -0
- strands_swarms/swarm.py +738 -0
- strands_swarms-0.1.0.dist-info/METADATA +386 -0
- strands_swarms-0.1.0.dist-info/RECORD +9 -0
- strands_swarms-0.1.0.dist-info/WHEEL +4 -0
- strands_swarms-0.1.0.dist-info/licenses/LICENSE +176 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""Dynamic multi-agent orchestration for Strands Agents.
|
|
2
|
+
|
|
3
|
+
This package provides DynamicSwarm - an orchestrator-driven approach to multi-agent
|
|
4
|
+
workflows where an LLM automatically designs and executes agent pipelines
|
|
5
|
+
based on user queries.
|
|
6
|
+
|
|
7
|
+
The orchestrator agent handles three responsibilities in a single conversation:
|
|
8
|
+
1. Planning and creating subagents - Analyze the task and spawn specialized agents
|
|
9
|
+
2. Assigning tasks - Create and assign tasks to the spawned agents
|
|
10
|
+
3. Generating final response - Synthesize results into a cohesive response
|
|
11
|
+
|
|
12
|
+
Using a single orchestrator agent for all three phases (rather than separate agents)
|
|
13
|
+
provides better context - the orchestrator knows exactly what it planned and why,
|
|
14
|
+
leading to more coherent final responses.
|
|
15
|
+
|
|
16
|
+
For static multi-agent workflows, use the Strands SDK directly:
|
|
17
|
+
- strands.multiagent.swarm.Swarm - dynamic handoffs between agents
|
|
18
|
+
- strands.multiagent.graph.Graph - dependency-based execution
|
|
19
|
+
|
|
20
|
+
Example:
|
|
21
|
+
from strands import tool
|
|
22
|
+
from strands_swarms import DynamicSwarm
|
|
23
|
+
|
|
24
|
+
@tool
|
|
25
|
+
def search_web(query: str) -> str:
|
|
26
|
+
'''Search the web.'''
|
|
27
|
+
return f"Results for: {query}"
|
|
28
|
+
|
|
29
|
+
swarm = DynamicSwarm(
|
|
30
|
+
available_tools={"search_web": search_web},
|
|
31
|
+
verbose=True,
|
|
32
|
+
)
|
|
33
|
+
result = swarm.execute("Research AI trends and summarize")
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
from .swarm import DynamicSwarm, DynamicSwarmResult
|
|
37
|
+
from .orchestrator import create_orchestrator_agent
|
|
38
|
+
from .events import (
|
|
39
|
+
# Planning/Orchestration events
|
|
40
|
+
SwarmStartedEvent,
|
|
41
|
+
PlanningStartedEvent,
|
|
42
|
+
AgentSpawnedEvent,
|
|
43
|
+
TaskCreatedEvent,
|
|
44
|
+
PlanningCompletedEvent,
|
|
45
|
+
# Execution events
|
|
46
|
+
ExecutionStartedEvent,
|
|
47
|
+
TaskStartedEvent,
|
|
48
|
+
TaskCompletedEvent,
|
|
49
|
+
TaskFailedEvent,
|
|
50
|
+
ExecutionCompletedEvent,
|
|
51
|
+
SwarmCompletedEvent,
|
|
52
|
+
SwarmFailedEvent,
|
|
53
|
+
# Hook provider
|
|
54
|
+
PrintingHookProvider,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
# Re-export strands types for convenience
|
|
58
|
+
from strands.hooks import HookProvider, HookRegistry
|
|
59
|
+
from strands.multiagent.base import Status
|
|
60
|
+
|
|
61
|
+
__version__ = "0.1.0"
|
|
62
|
+
|
|
63
|
+
__all__ = [
|
|
64
|
+
# Main API
|
|
65
|
+
"DynamicSwarm",
|
|
66
|
+
"DynamicSwarmResult",
|
|
67
|
+
# Orchestrator (handles both planning AND completion in same conversation)
|
|
68
|
+
"create_orchestrator_agent",
|
|
69
|
+
# Status enum
|
|
70
|
+
"Status",
|
|
71
|
+
# Events (for custom hooks)
|
|
72
|
+
"SwarmStartedEvent",
|
|
73
|
+
"PlanningStartedEvent",
|
|
74
|
+
"AgentSpawnedEvent",
|
|
75
|
+
"TaskCreatedEvent",
|
|
76
|
+
"PlanningCompletedEvent",
|
|
77
|
+
"ExecutionStartedEvent",
|
|
78
|
+
"TaskStartedEvent",
|
|
79
|
+
"TaskCompletedEvent",
|
|
80
|
+
"TaskFailedEvent",
|
|
81
|
+
"ExecutionCompletedEvent",
|
|
82
|
+
"SwarmCompletedEvent",
|
|
83
|
+
"SwarmFailedEvent",
|
|
84
|
+
# Hook system
|
|
85
|
+
"PrintingHookProvider",
|
|
86
|
+
"HookProvider",
|
|
87
|
+
"HookRegistry",
|
|
88
|
+
]
|
strands_swarms/events.py
ADDED
|
@@ -0,0 +1,564 @@
|
|
|
1
|
+
"""Event types for DynamicSwarm execution.
|
|
2
|
+
|
|
3
|
+
This module extends strands' hook system with events specific to dynamic
|
|
4
|
+
swarm planning and execution. Events follow the same patterns as
|
|
5
|
+
strands.hooks.events for consistency.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import re
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from strands.hooks.registry import BaseHookEvent, HookProvider, HookRegistry
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# =============================================================================
|
|
18
|
+
# Planning Events
|
|
19
|
+
# =============================================================================
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class SwarmStartedEvent(BaseHookEvent):
|
|
24
|
+
"""Event triggered when a dynamic swarm begins execution.
|
|
25
|
+
|
|
26
|
+
Attributes:
|
|
27
|
+
query: The user's input query.
|
|
28
|
+
available_tools: List of tool names available to spawned agents.
|
|
29
|
+
available_models: List of model names available to spawned agents.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
query: str
|
|
33
|
+
available_tools: list[str] = field(default_factory=list)
|
|
34
|
+
available_models: list[str] = field(default_factory=list)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass
|
|
38
|
+
class PlanningStartedEvent(BaseHookEvent):
|
|
39
|
+
"""Event triggered when the planning phase begins."""
|
|
40
|
+
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass
|
|
45
|
+
class AgentSpawnedEvent(BaseHookEvent):
|
|
46
|
+
"""Event triggered when a new agent is dynamically spawned.
|
|
47
|
+
|
|
48
|
+
Attributes:
|
|
49
|
+
name: The agent's unique identifier.
|
|
50
|
+
role: The agent's role description.
|
|
51
|
+
instructions: Additional instructions for the agent.
|
|
52
|
+
tools: List of tool names assigned to this agent.
|
|
53
|
+
model: Model name assigned to this agent.
|
|
54
|
+
color: ANSI color code assigned to this agent.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
name: str
|
|
58
|
+
role: str
|
|
59
|
+
instructions: str | None = None
|
|
60
|
+
tools: list[str] = field(default_factory=list)
|
|
61
|
+
model: str | None = None
|
|
62
|
+
color: str | None = None # ANSI color code for consistent display
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dataclass
|
|
66
|
+
class TaskCreatedEvent(BaseHookEvent):
|
|
67
|
+
"""Event triggered when a new task is created.
|
|
68
|
+
|
|
69
|
+
Attributes:
|
|
70
|
+
name: The task's unique identifier.
|
|
71
|
+
agent: Name of the agent assigned to this task.
|
|
72
|
+
description: Description of what the task does.
|
|
73
|
+
depends_on: List of task names this task depends on.
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
name: str
|
|
77
|
+
agent: str
|
|
78
|
+
description: str | None = None
|
|
79
|
+
depends_on: list[str] = field(default_factory=list)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@dataclass
|
|
83
|
+
class AgentInfo:
|
|
84
|
+
"""Info about a spawned agent for summary display."""
|
|
85
|
+
name: str
|
|
86
|
+
role: str
|
|
87
|
+
tools: list[str]
|
|
88
|
+
model: str | None
|
|
89
|
+
color: str | None = None
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@dataclass
|
|
93
|
+
class TaskInfo:
|
|
94
|
+
"""Info about a created task for summary display."""
|
|
95
|
+
name: str
|
|
96
|
+
agent: str
|
|
97
|
+
description: str | None
|
|
98
|
+
depends_on: list[str]
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@dataclass
|
|
102
|
+
class PlanningCompletedEvent(BaseHookEvent):
|
|
103
|
+
"""Event triggered when planning phase completes.
|
|
104
|
+
|
|
105
|
+
Attributes:
|
|
106
|
+
entry_task: The entry point task, if specified.
|
|
107
|
+
agents: List of agent info for summary display.
|
|
108
|
+
tasks: List of task info for summary display.
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
entry_task: str | None = None
|
|
112
|
+
agents: list[AgentInfo] = field(default_factory=list)
|
|
113
|
+
tasks: list[TaskInfo] = field(default_factory=list)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
# =============================================================================
|
|
117
|
+
# Execution Events
|
|
118
|
+
# =============================================================================
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@dataclass
|
|
122
|
+
class ExecutionStartedEvent(BaseHookEvent):
|
|
123
|
+
"""Event triggered when the execution phase begins.
|
|
124
|
+
|
|
125
|
+
Attributes:
|
|
126
|
+
tasks: List of task names to execute.
|
|
127
|
+
"""
|
|
128
|
+
|
|
129
|
+
tasks: list[str] = field(default_factory=list)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@dataclass
|
|
133
|
+
class TaskStartedEvent(BaseHookEvent):
|
|
134
|
+
"""Event triggered when a task begins executing.
|
|
135
|
+
|
|
136
|
+
Attributes:
|
|
137
|
+
name: The task name.
|
|
138
|
+
agent_role: Role of the agent executing this task.
|
|
139
|
+
"""
|
|
140
|
+
|
|
141
|
+
name: str
|
|
142
|
+
agent_role: str | None = None
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
@dataclass
|
|
146
|
+
class TaskCompletedEvent(BaseHookEvent):
|
|
147
|
+
"""Event triggered when a task completes successfully.
|
|
148
|
+
|
|
149
|
+
Attributes:
|
|
150
|
+
name: The task name.
|
|
151
|
+
"""
|
|
152
|
+
|
|
153
|
+
name: str
|
|
154
|
+
|
|
155
|
+
@property
|
|
156
|
+
def should_reverse_callbacks(self) -> bool:
|
|
157
|
+
"""Cleanup events should reverse callback order."""
|
|
158
|
+
return True
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
@dataclass
|
|
162
|
+
class TaskFailedEvent(BaseHookEvent):
|
|
163
|
+
"""Event triggered when a task fails.
|
|
164
|
+
|
|
165
|
+
Attributes:
|
|
166
|
+
name: The task name.
|
|
167
|
+
error: Error message or description.
|
|
168
|
+
"""
|
|
169
|
+
|
|
170
|
+
name: str
|
|
171
|
+
error: str | None = None
|
|
172
|
+
|
|
173
|
+
@property
|
|
174
|
+
def should_reverse_callbacks(self) -> bool:
|
|
175
|
+
"""Cleanup events should reverse callback order."""
|
|
176
|
+
return True
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
@dataclass
|
|
180
|
+
class ExecutionCompletedEvent(BaseHookEvent):
|
|
181
|
+
"""Event triggered when execution phase completes.
|
|
182
|
+
|
|
183
|
+
Attributes:
|
|
184
|
+
status: Final execution status.
|
|
185
|
+
agent_count: Number of agents used.
|
|
186
|
+
task_count: Number of tasks completed.
|
|
187
|
+
"""
|
|
188
|
+
|
|
189
|
+
status: str
|
|
190
|
+
agent_count: int = 0
|
|
191
|
+
task_count: int = 0
|
|
192
|
+
|
|
193
|
+
@property
|
|
194
|
+
def should_reverse_callbacks(self) -> bool:
|
|
195
|
+
"""Cleanup events should reverse callback order."""
|
|
196
|
+
return True
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
@dataclass
|
|
200
|
+
class SwarmCompletedEvent(BaseHookEvent):
|
|
201
|
+
"""Event triggered when swarm completes successfully."""
|
|
202
|
+
|
|
203
|
+
pass
|
|
204
|
+
|
|
205
|
+
@property
|
|
206
|
+
def should_reverse_callbacks(self) -> bool:
|
|
207
|
+
"""Cleanup events should reverse callback order."""
|
|
208
|
+
return True
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
@dataclass
|
|
212
|
+
class SwarmFailedEvent(BaseHookEvent):
|
|
213
|
+
"""Event triggered when swarm fails.
|
|
214
|
+
|
|
215
|
+
Attributes:
|
|
216
|
+
error: Error message or description.
|
|
217
|
+
"""
|
|
218
|
+
|
|
219
|
+
error: str
|
|
220
|
+
|
|
221
|
+
@property
|
|
222
|
+
def should_reverse_callbacks(self) -> bool:
|
|
223
|
+
"""Cleanup events should reverse callback order."""
|
|
224
|
+
return True
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
# =============================================================================
|
|
228
|
+
# Default Hook Provider
|
|
229
|
+
# =============================================================================
|
|
230
|
+
|
|
231
|
+
# ANSI Color Constants (for consistent agent output coloring)
|
|
232
|
+
AGENT_COLORS = [
|
|
233
|
+
"\033[94m", # Blue
|
|
234
|
+
"\033[92m", # Green
|
|
235
|
+
"\033[93m", # Yellow
|
|
236
|
+
"\033[95m", # Magenta
|
|
237
|
+
"\033[96m", # Cyan
|
|
238
|
+
"\033[91m", # Red
|
|
239
|
+
]
|
|
240
|
+
RESET = "\033[0m"
|
|
241
|
+
BOLD = "\033[1m"
|
|
242
|
+
DIM = "\033[2m"
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
# =============================================================================
|
|
246
|
+
# Colored Callback Handler
|
|
247
|
+
# =============================================================================
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
# Patterns for internal LLM reasoning tags that should be filtered from output
|
|
251
|
+
_INTERNAL_TAG_PATTERN = re.compile(
|
|
252
|
+
r'<(/?)(?:thinking|result|task_quality_reflection|task_quality_score|'
|
|
253
|
+
r'search_quality_reflection|search_quality_score|reflection|score)>',
|
|
254
|
+
re.IGNORECASE
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def _filter_internal_tags(text: str) -> str:
|
|
259
|
+
"""Filter out internal LLM reasoning tags from streamed text.
|
|
260
|
+
|
|
261
|
+
These tags are generated by the LLM for internal reasoning but shouldn't
|
|
262
|
+
be displayed to users in verbose output.
|
|
263
|
+
"""
|
|
264
|
+
return _INTERNAL_TAG_PATTERN.sub('', text)
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def create_colored_callback_handler(color: str, agent_name: str):
|
|
268
|
+
"""Create a callback handler that prints agent output with a specific color.
|
|
269
|
+
|
|
270
|
+
This ensures all output from a specific agent (text, reasoning, tool calls)
|
|
271
|
+
is displayed with the same color for easy visual tracking.
|
|
272
|
+
|
|
273
|
+
Internal reasoning tags (like <thinking>, <result>, etc.) are automatically
|
|
274
|
+
filtered out from the streamed output.
|
|
275
|
+
|
|
276
|
+
Args:
|
|
277
|
+
color: ANSI color code to use for this agent's output.
|
|
278
|
+
agent_name: Name of the agent (for tool call headers).
|
|
279
|
+
|
|
280
|
+
Returns:
|
|
281
|
+
A callback handler function for the strands Agent.
|
|
282
|
+
"""
|
|
283
|
+
# Track which tool calls we've already printed to avoid duplicates
|
|
284
|
+
# (strands fires current_tool_use multiple times as input streams in)
|
|
285
|
+
printed_tool_ids: set[str] = set()
|
|
286
|
+
|
|
287
|
+
def handler(**kwargs: Any) -> None:
|
|
288
|
+
nonlocal printed_tool_ids
|
|
289
|
+
|
|
290
|
+
# Text generation events
|
|
291
|
+
if "data" in kwargs:
|
|
292
|
+
# Filter internal reasoning tags and stream text chunk with color
|
|
293
|
+
filtered_text = _filter_internal_tags(kwargs['data'])
|
|
294
|
+
if filtered_text:
|
|
295
|
+
print(f"{color}{filtered_text}{RESET}", end="", flush=True)
|
|
296
|
+
|
|
297
|
+
# Tool usage events - only print once per tool call
|
|
298
|
+
elif "current_tool_use" in kwargs:
|
|
299
|
+
tool = kwargs["current_tool_use"]
|
|
300
|
+
tool_id = tool.get("toolUseId", "")
|
|
301
|
+
|
|
302
|
+
# Skip if we've already printed this tool call
|
|
303
|
+
if tool_id in printed_tool_ids:
|
|
304
|
+
return
|
|
305
|
+
printed_tool_ids.add(tool_id)
|
|
306
|
+
|
|
307
|
+
tool_name = tool.get("name", "unknown")
|
|
308
|
+
# Print tool header with color
|
|
309
|
+
print(f"\n{color}{BOLD}Tool: {tool_name}{RESET}")
|
|
310
|
+
|
|
311
|
+
# Tool result events - print the input when tool completes
|
|
312
|
+
elif "tool_result" in kwargs:
|
|
313
|
+
result = kwargs["tool_result"]
|
|
314
|
+
tool_id = result.get("toolUseId", "")
|
|
315
|
+
tool_input = result.get("input")
|
|
316
|
+
|
|
317
|
+
if tool_input:
|
|
318
|
+
if isinstance(tool_input, dict):
|
|
319
|
+
for key, value in tool_input.items():
|
|
320
|
+
# Truncate long values for readability
|
|
321
|
+
str_value = str(value)
|
|
322
|
+
if len(str_value) > 100:
|
|
323
|
+
str_value = str_value[:100] + "..."
|
|
324
|
+
print(f"{color} {key}: {str_value}{RESET}")
|
|
325
|
+
else:
|
|
326
|
+
str_input = str(tool_input)
|
|
327
|
+
if len(str_input) > 200:
|
|
328
|
+
str_input = str_input[:200] + "..."
|
|
329
|
+
print(f"{color} Input: {str_input}{RESET}")
|
|
330
|
+
|
|
331
|
+
# Reasoning/thinking events - also filter internal tags
|
|
332
|
+
elif "reasoningText" in kwargs:
|
|
333
|
+
filtered_text = _filter_internal_tags(kwargs['reasoningText'])
|
|
334
|
+
if filtered_text:
|
|
335
|
+
print(f"{color}{filtered_text}{RESET}", end="", flush=True)
|
|
336
|
+
elif "reasoning" in kwargs:
|
|
337
|
+
# Start of reasoning block
|
|
338
|
+
pass # The reasoningText will contain the actual content
|
|
339
|
+
|
|
340
|
+
return handler
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
class PrintingHookProvider(HookProvider):
|
|
344
|
+
"""Hook provider that prints formatted output to console.
|
|
345
|
+
|
|
346
|
+
This provides a nice CLI experience with emoji indicators and colors.
|
|
347
|
+
Use with DynamicSwarm for live status updates.
|
|
348
|
+
|
|
349
|
+
Example:
|
|
350
|
+
swarm = DynamicSwarm(
|
|
351
|
+
...,
|
|
352
|
+
hooks=[PrintingHookProvider()],
|
|
353
|
+
)
|
|
354
|
+
"""
|
|
355
|
+
|
|
356
|
+
# Use same colors as agent output for consistency
|
|
357
|
+
COLORS = AGENT_COLORS
|
|
358
|
+
|
|
359
|
+
def __init__(self, use_colors: bool = True) -> None:
|
|
360
|
+
"""Initialize the hook provider with state tracking.
|
|
361
|
+
|
|
362
|
+
Args:
|
|
363
|
+
use_colors: Whether to use ANSI colors for agent differentiation.
|
|
364
|
+
"""
|
|
365
|
+
self._agents_header_printed = False
|
|
366
|
+
self._tasks_header_printed = False
|
|
367
|
+
self._use_colors = use_colors
|
|
368
|
+
self._agent_colors: dict[str, str] = {} # Populated from AgentSpawnedEvent
|
|
369
|
+
self._task_agents: dict[str, str] = {} # task_name -> agent_name
|
|
370
|
+
|
|
371
|
+
def _get_agent_color(self, agent_name: str | None) -> str:
|
|
372
|
+
"""Get a consistent color for an agent."""
|
|
373
|
+
if not self._use_colors or not agent_name:
|
|
374
|
+
return ""
|
|
375
|
+
return self._agent_colors.get(agent_name, "")
|
|
376
|
+
|
|
377
|
+
def _colored(self, text: str, agent_name: str | None = None, bold: bool = False) -> str:
|
|
378
|
+
"""Apply color to text."""
|
|
379
|
+
if not self._use_colors:
|
|
380
|
+
return text
|
|
381
|
+
color = self._get_agent_color(agent_name) if agent_name else ""
|
|
382
|
+
bold_code = BOLD if bold else ""
|
|
383
|
+
return f"{bold_code}{color}{text}{RESET}"
|
|
384
|
+
|
|
385
|
+
def _get_task_agent(self, task_name: str) -> str | None:
|
|
386
|
+
"""Get the agent assigned to a task."""
|
|
387
|
+
return self._task_agents.get(task_name)
|
|
388
|
+
|
|
389
|
+
def register_hooks(self, registry: HookRegistry, **kwargs: Any) -> None:
|
|
390
|
+
"""Register all printing callbacks."""
|
|
391
|
+
# Planning events
|
|
392
|
+
registry.add_callback(SwarmStartedEvent, self._on_swarm_started)
|
|
393
|
+
registry.add_callback(PlanningStartedEvent, self._on_planning_started)
|
|
394
|
+
registry.add_callback(AgentSpawnedEvent, self._on_agent_spawned)
|
|
395
|
+
registry.add_callback(TaskCreatedEvent, self._on_task_created)
|
|
396
|
+
registry.add_callback(PlanningCompletedEvent, self._on_planning_completed)
|
|
397
|
+
|
|
398
|
+
# Execution events
|
|
399
|
+
registry.add_callback(ExecutionStartedEvent, self._on_execution_started)
|
|
400
|
+
registry.add_callback(TaskStartedEvent, self._on_task_started)
|
|
401
|
+
registry.add_callback(TaskCompletedEvent, self._on_task_completed)
|
|
402
|
+
registry.add_callback(TaskFailedEvent, self._on_task_failed)
|
|
403
|
+
registry.add_callback(ExecutionCompletedEvent, self._on_execution_completed)
|
|
404
|
+
|
|
405
|
+
# Completion events
|
|
406
|
+
registry.add_callback(SwarmCompletedEvent, self._on_swarm_completed)
|
|
407
|
+
registry.add_callback(SwarmFailedEvent, self._on_swarm_failed)
|
|
408
|
+
|
|
409
|
+
def _on_swarm_started(self, event: SwarmStartedEvent) -> None:
|
|
410
|
+
# Reset state for new swarm execution
|
|
411
|
+
self._agents_header_printed = False
|
|
412
|
+
self._tasks_header_printed = False
|
|
413
|
+
self._agent_colors.clear()
|
|
414
|
+
self._task_agents.clear()
|
|
415
|
+
|
|
416
|
+
print("\n" + "=" * 60)
|
|
417
|
+
print("๐ DYNAMIC SWARM STARTING")
|
|
418
|
+
print("=" * 60)
|
|
419
|
+
query = event.query
|
|
420
|
+
print(f"\n๐ Query: {query[:200]}{'...' if len(query) > 200 else ''}")
|
|
421
|
+
print(f"๐ฆ Available tools: {event.available_tools or ['none']}")
|
|
422
|
+
print(f"๐ง Available models: {event.available_models or ['default']}")
|
|
423
|
+
|
|
424
|
+
def _on_planning_started(self, event: PlanningStartedEvent) -> None:
|
|
425
|
+
print("\n" + "-" * 40)
|
|
426
|
+
print("๐ PHASE 1: PLANNING")
|
|
427
|
+
print("-" * 40)
|
|
428
|
+
|
|
429
|
+
def _on_agent_spawned(self, event: AgentSpawnedEvent) -> None:
|
|
430
|
+
# Store the color from the event (assigned by registry) for later use
|
|
431
|
+
# Don't print here - we'll print a clean summary after planning completes
|
|
432
|
+
if event.color:
|
|
433
|
+
self._agent_colors[event.name] = event.color
|
|
434
|
+
|
|
435
|
+
def _on_task_created(self, event: TaskCreatedEvent) -> None:
|
|
436
|
+
# Track task-agent mapping for coloring during execution
|
|
437
|
+
# Don't print here - we'll print a clean summary after planning completes
|
|
438
|
+
self._task_agents[event.name] = event.agent
|
|
439
|
+
|
|
440
|
+
def _on_planning_completed(self, event: PlanningCompletedEvent) -> None:
|
|
441
|
+
# Print agents summary
|
|
442
|
+
if event.agents:
|
|
443
|
+
print("\n" + "ยท" * 40)
|
|
444
|
+
print("๐ค AGENTS")
|
|
445
|
+
print("ยท" * 40)
|
|
446
|
+
for agent in event.agents:
|
|
447
|
+
# Store color for later use
|
|
448
|
+
if agent.color:
|
|
449
|
+
self._agent_colors[agent.name] = agent.color
|
|
450
|
+
|
|
451
|
+
# Color the entire agent block
|
|
452
|
+
c = self._colored
|
|
453
|
+
print(f"\n {c(f'[{agent.name}]', agent.name, bold=True)}")
|
|
454
|
+
print(f" {c('Role:', agent.name, bold=True)} {c(agent.role, agent.name)}")
|
|
455
|
+
print(f" {c('Tools:', agent.name, bold=True)} {c(str(agent.tools or ['none']), agent.name)}")
|
|
456
|
+
print(f" {c('Model:', agent.name, bold=True)} {c(agent.model or 'default', agent.name)}")
|
|
457
|
+
|
|
458
|
+
# Print tasks summary with dependency visualization
|
|
459
|
+
if event.tasks:
|
|
460
|
+
print("\n" + "ยท" * 40)
|
|
461
|
+
print("๐ TASKS & DEPENDENCIES")
|
|
462
|
+
print("ยท" * 40)
|
|
463
|
+
for task in event.tasks:
|
|
464
|
+
# Track task-agent mapping
|
|
465
|
+
self._task_agents[task.name] = task.agent
|
|
466
|
+
|
|
467
|
+
# Color the entire task block with the agent's color
|
|
468
|
+
c = self._colored
|
|
469
|
+
agent_name = task.agent
|
|
470
|
+
|
|
471
|
+
print(f"\n {c(f'[{task.name}]', agent_name, bold=True)}")
|
|
472
|
+
print(f" {c('Agent:', agent_name, bold=True)} {c(agent_name, agent_name)}")
|
|
473
|
+
if task.description:
|
|
474
|
+
print(f" {c('Description:', agent_name, bold=True)} {c(task.description, agent_name)}")
|
|
475
|
+
if task.depends_on:
|
|
476
|
+
deps_colored = [self._colored(d, self._task_agents.get(d), bold=True) for d in task.depends_on]
|
|
477
|
+
print(f" {c('โณ Waits for:', agent_name, bold=True)} {', '.join(deps_colored)}")
|
|
478
|
+
else:
|
|
479
|
+
print(f" {c('โก Can start immediately', agent_name)}")
|
|
480
|
+
|
|
481
|
+
# Print execution summary
|
|
482
|
+
print("\n" + "ยท" * 40)
|
|
483
|
+
print("โ
PLAN READY")
|
|
484
|
+
print("ยท" * 40)
|
|
485
|
+
print(f" Entry: {event.entry_task or 'auto'}")
|
|
486
|
+
print(f" Total: {len(event.agents)} agents, {len(event.tasks)} tasks")
|
|
487
|
+
|
|
488
|
+
def _on_execution_started(self, event: ExecutionStartedEvent) -> None:
|
|
489
|
+
print("\n" + "-" * 40)
|
|
490
|
+
print("โก PHASE 2: EXECUTION")
|
|
491
|
+
print("-" * 40)
|
|
492
|
+
# Color each task name by its agent
|
|
493
|
+
tasks_colored = [self._colored(t, self._get_task_agent(t), bold=True) for t in event.tasks]
|
|
494
|
+
print(f"๐ Tasks to execute: [{', '.join(tasks_colored)}]")
|
|
495
|
+
|
|
496
|
+
def _on_task_started(self, event: TaskStartedEvent) -> None:
|
|
497
|
+
agent = self._get_task_agent(event.name)
|
|
498
|
+
task_str = self._colored(event.name, agent, bold=True)
|
|
499
|
+
print(f"\nโถ๏ธ Executing task: {task_str}")
|
|
500
|
+
if event.agent_role:
|
|
501
|
+
role_str = self._colored(event.agent_role, agent)
|
|
502
|
+
print(f" Agent role: {role_str}")
|
|
503
|
+
|
|
504
|
+
def _on_task_completed(self, event: TaskCompletedEvent) -> None:
|
|
505
|
+
agent = self._get_task_agent(event.name)
|
|
506
|
+
task_str = self._colored(event.name, agent)
|
|
507
|
+
print(f" โ Completed: {task_str}")
|
|
508
|
+
|
|
509
|
+
def _on_task_failed(self, event: TaskFailedEvent) -> None:
|
|
510
|
+
task_str = self._colored(event.name, self._get_task_agent(event.name))
|
|
511
|
+
print(f" โ Failed: {task_str}")
|
|
512
|
+
if event.error:
|
|
513
|
+
print(f" Error: {event.error}")
|
|
514
|
+
|
|
515
|
+
def _on_execution_completed(self, event: ExecutionCompletedEvent) -> None:
|
|
516
|
+
print("\n" + "-" * 40)
|
|
517
|
+
print("๐ EXECUTION COMPLETE")
|
|
518
|
+
print("-" * 40)
|
|
519
|
+
print(f" Status: {event.status}")
|
|
520
|
+
print(f" Agents used: {event.agent_count}")
|
|
521
|
+
print(f" Tasks completed: {event.task_count}")
|
|
522
|
+
|
|
523
|
+
def _on_swarm_completed(self, event: SwarmCompletedEvent) -> None:
|
|
524
|
+
print("\n" + "=" * 60)
|
|
525
|
+
print("โ
SWARM COMPLETED SUCCESSFULLY")
|
|
526
|
+
print("=" * 60)
|
|
527
|
+
|
|
528
|
+
def _on_swarm_failed(self, event: SwarmFailedEvent) -> None:
|
|
529
|
+
print("\n" + "=" * 60)
|
|
530
|
+
print(f"โ SWARM FAILED: {event.error}")
|
|
531
|
+
print("=" * 60)
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
# Re-export strands types for convenience
|
|
535
|
+
__all__ = [
|
|
536
|
+
# Events
|
|
537
|
+
"SwarmStartedEvent",
|
|
538
|
+
"PlanningStartedEvent",
|
|
539
|
+
"AgentSpawnedEvent",
|
|
540
|
+
"TaskCreatedEvent",
|
|
541
|
+
"PlanningCompletedEvent",
|
|
542
|
+
"ExecutionStartedEvent",
|
|
543
|
+
"TaskStartedEvent",
|
|
544
|
+
"TaskCompletedEvent",
|
|
545
|
+
"TaskFailedEvent",
|
|
546
|
+
"ExecutionCompletedEvent",
|
|
547
|
+
"SwarmCompletedEvent",
|
|
548
|
+
"SwarmFailedEvent",
|
|
549
|
+
# Data types
|
|
550
|
+
"AgentInfo",
|
|
551
|
+
"TaskInfo",
|
|
552
|
+
# Hook provider
|
|
553
|
+
"PrintingHookProvider",
|
|
554
|
+
# Callback handler factory
|
|
555
|
+
"create_colored_callback_handler",
|
|
556
|
+
# Color constants
|
|
557
|
+
"AGENT_COLORS",
|
|
558
|
+
"RESET",
|
|
559
|
+
"BOLD",
|
|
560
|
+
"DIM",
|
|
561
|
+
# Re-exports from strands
|
|
562
|
+
"HookProvider",
|
|
563
|
+
"HookRegistry",
|
|
564
|
+
]
|