dacp 0.3.1__py3-none-any.whl → 0.3.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.
- dacp/__init__.py +44 -5
- dacp/intelligence.py +230 -305
- dacp/json_parser.py +232 -0
- dacp/llm.py +13 -6
- dacp/logging_config.py +17 -16
- dacp/main.py +7 -13
- dacp/orchestrator.py +230 -182
- dacp/tools.py +64 -45
- dacp/workflow.py +409 -0
- dacp/workflow_runtime.py +508 -0
- {dacp-0.3.1.dist-info → dacp-0.3.3.dist-info}/METADATA +342 -1
- dacp-0.3.3.dist-info/RECORD +18 -0
- dacp-0.3.1.dist-info/RECORD +0 -15
- {dacp-0.3.1.dist-info → dacp-0.3.3.dist-info}/WHEEL +0 -0
- {dacp-0.3.1.dist-info → dacp-0.3.3.dist-info}/licenses/LICENSE +0 -0
- {dacp-0.3.1.dist-info → dacp-0.3.3.dist-info}/top_level.txt +0 -0
dacp/workflow_runtime.py
ADDED
@@ -0,0 +1,508 @@
|
|
1
|
+
"""
|
2
|
+
DACP Workflow Runtime - Declarative workflow execution from workflow.yaml
|
3
|
+
|
4
|
+
This module provides a runtime system that reads workflow.yaml files and
|
5
|
+
orchestrates agent collaboration through agent and task registries.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import logging
|
9
|
+
import time
|
10
|
+
import uuid
|
11
|
+
import yaml
|
12
|
+
import json
|
13
|
+
from typing import Dict, Any, List, Optional, Union
|
14
|
+
from pathlib import Path
|
15
|
+
from dataclasses import dataclass, field
|
16
|
+
from enum import Enum
|
17
|
+
|
18
|
+
logger = logging.getLogger("dacp.workflow_runtime")
|
19
|
+
|
20
|
+
|
21
|
+
class TaskStatus(Enum):
|
22
|
+
"""Task execution status."""
|
23
|
+
PENDING = "pending"
|
24
|
+
RUNNING = "running"
|
25
|
+
COMPLETED = "completed"
|
26
|
+
FAILED = "failed"
|
27
|
+
CANCELLED = "cancelled"
|
28
|
+
|
29
|
+
|
30
|
+
@dataclass
|
31
|
+
class TaskExecution:
|
32
|
+
"""Represents a task execution instance."""
|
33
|
+
id: str
|
34
|
+
workflow_id: str
|
35
|
+
step_id: str
|
36
|
+
agent_id: str
|
37
|
+
task_name: str
|
38
|
+
input_data: Dict[str, Any]
|
39
|
+
status: TaskStatus = TaskStatus.PENDING
|
40
|
+
output_data: Optional[Dict[str, Any]] = None
|
41
|
+
error: Optional[str] = None
|
42
|
+
created_at: float = field(default_factory=time.time)
|
43
|
+
started_at: Optional[float] = None
|
44
|
+
completed_at: Optional[float] = None
|
45
|
+
duration: Optional[float] = None
|
46
|
+
|
47
|
+
def to_dict(self) -> Dict[str, Any]:
|
48
|
+
"""Convert to dictionary representation."""
|
49
|
+
return {
|
50
|
+
"id": self.id,
|
51
|
+
"workflow_id": self.workflow_id,
|
52
|
+
"step_id": self.step_id,
|
53
|
+
"agent_id": self.agent_id,
|
54
|
+
"task_name": self.task_name,
|
55
|
+
"input_data": self.input_data,
|
56
|
+
"status": self.status.value,
|
57
|
+
"output_data": self.output_data,
|
58
|
+
"error": self.error,
|
59
|
+
"created_at": self.created_at,
|
60
|
+
"started_at": self.started_at,
|
61
|
+
"completed_at": self.completed_at,
|
62
|
+
"duration": self.duration,
|
63
|
+
}
|
64
|
+
|
65
|
+
|
66
|
+
@dataclass
|
67
|
+
class RegisteredAgent:
|
68
|
+
"""Represents a registered agent in the registry."""
|
69
|
+
id: str
|
70
|
+
agent_instance: Any
|
71
|
+
spec_file: Optional[str] = None
|
72
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
73
|
+
registered_at: float = field(default_factory=time.time)
|
74
|
+
last_activity: Optional[float] = None
|
75
|
+
|
76
|
+
def to_dict(self) -> Dict[str, Any]:
|
77
|
+
"""Convert to dictionary representation."""
|
78
|
+
return {
|
79
|
+
"id": self.id,
|
80
|
+
"agent_type": type(self.agent_instance).__name__,
|
81
|
+
"spec_file": self.spec_file,
|
82
|
+
"metadata": self.metadata,
|
83
|
+
"registered_at": self.registered_at,
|
84
|
+
"last_activity": self.last_activity,
|
85
|
+
}
|
86
|
+
|
87
|
+
|
88
|
+
class AgentRegistry:
|
89
|
+
"""Registry for managing agent instances."""
|
90
|
+
|
91
|
+
def __init__(self):
|
92
|
+
self.agents: Dict[str, RegisteredAgent] = {}
|
93
|
+
|
94
|
+
def register_agent(
|
95
|
+
self,
|
96
|
+
agent_id: str,
|
97
|
+
agent_instance: Any,
|
98
|
+
spec_file: Optional[str] = None,
|
99
|
+
metadata: Optional[Dict[str, Any]] = None
|
100
|
+
) -> None:
|
101
|
+
"""Register an agent instance."""
|
102
|
+
registered_agent = RegisteredAgent(
|
103
|
+
id=agent_id,
|
104
|
+
agent_instance=agent_instance,
|
105
|
+
spec_file=spec_file,
|
106
|
+
metadata=metadata or {}
|
107
|
+
)
|
108
|
+
|
109
|
+
self.agents[agent_id] = registered_agent
|
110
|
+
logger.info(f"🤖 Agent '{agent_id}' registered in registry")
|
111
|
+
|
112
|
+
def get_agent(self, agent_id: str) -> Optional[Any]:
|
113
|
+
"""Get an agent instance by ID."""
|
114
|
+
if agent_id in self.agents:
|
115
|
+
self.agents[agent_id].last_activity = time.time()
|
116
|
+
return self.agents[agent_id].agent_instance
|
117
|
+
return None
|
118
|
+
|
119
|
+
def list_agents(self) -> List[str]:
|
120
|
+
"""List all registered agent IDs."""
|
121
|
+
return list(self.agents.keys())
|
122
|
+
|
123
|
+
def get_agent_info(self, agent_id: str) -> Optional[Dict[str, Any]]:
|
124
|
+
"""Get agent registration information."""
|
125
|
+
if agent_id in self.agents:
|
126
|
+
return self.agents[agent_id].to_dict()
|
127
|
+
return None
|
128
|
+
|
129
|
+
def unregister_agent(self, agent_id: str) -> bool:
|
130
|
+
"""Unregister an agent."""
|
131
|
+
if agent_id in self.agents:
|
132
|
+
del self.agents[agent_id]
|
133
|
+
logger.info(f"🗑️ Agent '{agent_id}' unregistered from registry")
|
134
|
+
return True
|
135
|
+
return False
|
136
|
+
|
137
|
+
|
138
|
+
class TaskRegistry:
|
139
|
+
"""Registry for managing task executions."""
|
140
|
+
|
141
|
+
def __init__(self):
|
142
|
+
self.tasks: Dict[str, TaskExecution] = {}
|
143
|
+
self.workflow_tasks: Dict[str, List[str]] = {} # workflow_id -> task_ids
|
144
|
+
|
145
|
+
def create_task(
|
146
|
+
self,
|
147
|
+
workflow_id: str,
|
148
|
+
step_id: str,
|
149
|
+
agent_id: str,
|
150
|
+
task_name: str,
|
151
|
+
input_data: Dict[str, Any]
|
152
|
+
) -> str:
|
153
|
+
"""Create a new task execution."""
|
154
|
+
task_id = str(uuid.uuid4())
|
155
|
+
|
156
|
+
task = TaskExecution(
|
157
|
+
id=task_id,
|
158
|
+
workflow_id=workflow_id,
|
159
|
+
step_id=step_id,
|
160
|
+
agent_id=agent_id,
|
161
|
+
task_name=task_name,
|
162
|
+
input_data=input_data
|
163
|
+
)
|
164
|
+
|
165
|
+
self.tasks[task_id] = task
|
166
|
+
|
167
|
+
# Add to workflow tasks
|
168
|
+
if workflow_id not in self.workflow_tasks:
|
169
|
+
self.workflow_tasks[workflow_id] = []
|
170
|
+
self.workflow_tasks[workflow_id].append(task_id)
|
171
|
+
|
172
|
+
logger.info(f"📋 Task '{task_id}' created for agent '{agent_id}' in workflow '{workflow_id}'")
|
173
|
+
return task_id
|
174
|
+
|
175
|
+
def get_task(self, task_id: str) -> Optional[TaskExecution]:
|
176
|
+
"""Get a task by ID."""
|
177
|
+
return self.tasks.get(task_id)
|
178
|
+
|
179
|
+
def update_task_status(self, task_id: str, status: TaskStatus, **kwargs) -> bool:
|
180
|
+
"""Update task status and optional fields."""
|
181
|
+
if task_id not in self.tasks:
|
182
|
+
return False
|
183
|
+
|
184
|
+
task = self.tasks[task_id]
|
185
|
+
task.status = status
|
186
|
+
|
187
|
+
# Update optional fields
|
188
|
+
for key, value in kwargs.items():
|
189
|
+
if hasattr(task, key):
|
190
|
+
setattr(task, key, value)
|
191
|
+
|
192
|
+
# Calculate duration if completed
|
193
|
+
if status in [TaskStatus.COMPLETED, TaskStatus.FAILED] and task.started_at:
|
194
|
+
task.completed_at = time.time()
|
195
|
+
task.duration = task.completed_at - task.started_at
|
196
|
+
|
197
|
+
logger.info(f"📊 Task '{task_id}' status updated to {status.value}")
|
198
|
+
return True
|
199
|
+
|
200
|
+
def get_workflow_tasks(self, workflow_id: str) -> List[TaskExecution]:
|
201
|
+
"""Get all tasks for a workflow."""
|
202
|
+
task_ids = self.workflow_tasks.get(workflow_id, [])
|
203
|
+
return [self.tasks[tid] for tid in task_ids if tid in self.tasks]
|
204
|
+
|
205
|
+
def get_task_summary(self) -> Dict[str, Any]:
|
206
|
+
"""Get summary of all tasks."""
|
207
|
+
status_counts = {}
|
208
|
+
for task in self.tasks.values():
|
209
|
+
status = task.status.value
|
210
|
+
status_counts[status] = status_counts.get(status, 0) + 1
|
211
|
+
|
212
|
+
return {
|
213
|
+
"total_tasks": len(self.tasks),
|
214
|
+
"status_counts": status_counts,
|
215
|
+
"workflows": len(self.workflow_tasks)
|
216
|
+
}
|
217
|
+
|
218
|
+
|
219
|
+
class WorkflowRuntime:
|
220
|
+
"""DACP Workflow Runtime - Executes workflows from workflow.yaml"""
|
221
|
+
|
222
|
+
def __init__(self, orchestrator=None):
|
223
|
+
self.orchestrator = orchestrator
|
224
|
+
self.agent_registry = AgentRegistry()
|
225
|
+
self.task_registry = TaskRegistry()
|
226
|
+
self.workflow_config = {}
|
227
|
+
self.active_workflows: Dict[str, Dict[str, Any]] = {}
|
228
|
+
|
229
|
+
def load_workflow_config(self, config_path: str) -> None:
|
230
|
+
"""Load workflow configuration from YAML file."""
|
231
|
+
config_file = Path(config_path)
|
232
|
+
if not config_file.exists():
|
233
|
+
raise FileNotFoundError(f"Workflow config file not found: {config_path}")
|
234
|
+
|
235
|
+
with open(config_file, 'r') as f:
|
236
|
+
self.workflow_config = yaml.safe_load(f)
|
237
|
+
|
238
|
+
logger.info(f"📁 Loaded workflow config from {config_path}")
|
239
|
+
logger.info(f"📋 Found {len(self.workflow_config.get('workflows', {}))} workflows")
|
240
|
+
|
241
|
+
def register_agent_from_config(self, agent_id: str, agent_instance: Any) -> None:
|
242
|
+
"""Register an agent instance based on workflow config."""
|
243
|
+
# Find agent spec in config
|
244
|
+
agent_spec = None
|
245
|
+
for agent_config in self.workflow_config.get('agents', []):
|
246
|
+
if agent_config['id'] == agent_id:
|
247
|
+
agent_spec = agent_config.get('spec')
|
248
|
+
break
|
249
|
+
|
250
|
+
self.agent_registry.register_agent(
|
251
|
+
agent_id=agent_id,
|
252
|
+
agent_instance=agent_instance,
|
253
|
+
spec_file=agent_spec,
|
254
|
+
metadata={"config_based": True}
|
255
|
+
)
|
256
|
+
|
257
|
+
def execute_workflow(self, workflow_name: str, initial_input: Dict[str, Any] = None) -> str:
|
258
|
+
"""Execute a workflow by name."""
|
259
|
+
if workflow_name not in self.workflow_config.get('workflows', {}):
|
260
|
+
raise ValueError(f"Workflow '{workflow_name}' not found in config")
|
261
|
+
|
262
|
+
workflow_def = self.workflow_config['workflows'][workflow_name]
|
263
|
+
workflow_id = str(uuid.uuid4())
|
264
|
+
|
265
|
+
logger.info(f"🚀 Starting workflow '{workflow_name}' with ID '{workflow_id}'")
|
266
|
+
|
267
|
+
# Initialize workflow state
|
268
|
+
self.active_workflows[workflow_id] = {
|
269
|
+
"name": workflow_name,
|
270
|
+
"definition": workflow_def,
|
271
|
+
"current_step": 0,
|
272
|
+
"context": initial_input or {},
|
273
|
+
"started_at": time.time()
|
274
|
+
}
|
275
|
+
|
276
|
+
# Execute first step
|
277
|
+
self._execute_workflow_step(workflow_id, 0)
|
278
|
+
|
279
|
+
return workflow_id
|
280
|
+
|
281
|
+
def _execute_workflow_step(self, workflow_id: str, step_index: int) -> None:
|
282
|
+
"""Execute a specific workflow step."""
|
283
|
+
if workflow_id not in self.active_workflows:
|
284
|
+
logger.error(f"❌ Workflow '{workflow_id}' not found")
|
285
|
+
return
|
286
|
+
|
287
|
+
workflow_state = self.active_workflows[workflow_id]
|
288
|
+
workflow_def = workflow_state["definition"]
|
289
|
+
steps = workflow_def.get("steps", [])
|
290
|
+
|
291
|
+
if step_index >= len(steps):
|
292
|
+
logger.info(f"🏁 Workflow '{workflow_id}' completed")
|
293
|
+
return
|
294
|
+
|
295
|
+
step = steps[step_index]
|
296
|
+
step_id = f"step_{step_index}"
|
297
|
+
|
298
|
+
# Extract step configuration
|
299
|
+
agent_id = step.get("agent")
|
300
|
+
task_name = step.get("task")
|
301
|
+
step_input = step.get("input", {})
|
302
|
+
|
303
|
+
# Resolve input data with context
|
304
|
+
resolved_input = self._resolve_input_data(step_input, workflow_state["context"])
|
305
|
+
|
306
|
+
logger.info(f"📋 Executing step {step_index}: {agent_id}.{task_name}")
|
307
|
+
|
308
|
+
# Create task
|
309
|
+
task_id = self.task_registry.create_task(
|
310
|
+
workflow_id=workflow_id,
|
311
|
+
step_id=step_id,
|
312
|
+
agent_id=agent_id,
|
313
|
+
task_name=task_name,
|
314
|
+
input_data=resolved_input
|
315
|
+
)
|
316
|
+
|
317
|
+
# Execute task
|
318
|
+
self._execute_task(task_id, workflow_id, step_index)
|
319
|
+
|
320
|
+
def _execute_task(self, task_id: str, workflow_id: str, step_index: int) -> None:
|
321
|
+
"""Execute a single task."""
|
322
|
+
task = self.task_registry.get_task(task_id)
|
323
|
+
if not task:
|
324
|
+
logger.error(f"❌ Task '{task_id}' not found")
|
325
|
+
return
|
326
|
+
|
327
|
+
# Get agent instance
|
328
|
+
agent = self.agent_registry.get_agent(task.agent_id)
|
329
|
+
if not agent:
|
330
|
+
self.task_registry.update_task_status(
|
331
|
+
task_id, TaskStatus.FAILED,
|
332
|
+
error=f"Agent '{task.agent_id}' not found"
|
333
|
+
)
|
334
|
+
return
|
335
|
+
|
336
|
+
# Update task status
|
337
|
+
self.task_registry.update_task_status(
|
338
|
+
task_id, TaskStatus.RUNNING,
|
339
|
+
started_at=time.time()
|
340
|
+
)
|
341
|
+
|
342
|
+
try:
|
343
|
+
# Prepare message for agent
|
344
|
+
message = {
|
345
|
+
"task": task.task_name,
|
346
|
+
**task.input_data
|
347
|
+
}
|
348
|
+
|
349
|
+
logger.info(f"📨 Sending task '{task.task_name}' to agent '{task.agent_id}'")
|
350
|
+
|
351
|
+
# Execute via orchestrator or direct call
|
352
|
+
if self.orchestrator:
|
353
|
+
result = self.orchestrator.send_message(task.agent_id, message)
|
354
|
+
else:
|
355
|
+
result = agent.handle_message(message)
|
356
|
+
|
357
|
+
# Check for errors
|
358
|
+
if isinstance(result, dict) and "error" in result:
|
359
|
+
self.task_registry.update_task_status(
|
360
|
+
task_id, TaskStatus.FAILED,
|
361
|
+
error=result["error"]
|
362
|
+
)
|
363
|
+
logger.error(f"❌ Task '{task_id}' failed: {result['error']}")
|
364
|
+
return
|
365
|
+
|
366
|
+
# Task completed successfully
|
367
|
+
self.task_registry.update_task_status(
|
368
|
+
task_id, TaskStatus.COMPLETED,
|
369
|
+
output_data=result
|
370
|
+
)
|
371
|
+
|
372
|
+
logger.info(f"✅ Task '{task_id}' completed successfully")
|
373
|
+
|
374
|
+
# Continue workflow
|
375
|
+
self._handle_task_completion(task_id, workflow_id, step_index, result)
|
376
|
+
|
377
|
+
except Exception as e:
|
378
|
+
self.task_registry.update_task_status(
|
379
|
+
task_id, TaskStatus.FAILED,
|
380
|
+
error=str(e)
|
381
|
+
)
|
382
|
+
logger.error(f"❌ Task '{task_id}' failed with exception: {e}")
|
383
|
+
|
384
|
+
def _handle_task_completion(self, task_id: str, workflow_id: str, step_index: int, result: Dict[str, Any]) -> None:
|
385
|
+
"""Handle task completion and route to next step."""
|
386
|
+
workflow_state = self.active_workflows[workflow_id]
|
387
|
+
workflow_def = workflow_state["definition"]
|
388
|
+
steps = workflow_def.get("steps", [])
|
389
|
+
|
390
|
+
if step_index >= len(steps):
|
391
|
+
return
|
392
|
+
|
393
|
+
current_step = steps[step_index]
|
394
|
+
|
395
|
+
# Convert result to dictionary if it's a Pydantic model
|
396
|
+
if hasattr(result, 'model_dump'):
|
397
|
+
result_dict = result.model_dump()
|
398
|
+
logger.debug(f"🔧 Converted Pydantic model to dict: {result_dict}")
|
399
|
+
elif hasattr(result, 'dict'):
|
400
|
+
result_dict = result.dict()
|
401
|
+
logger.debug(f"🔧 Converted Pydantic model to dict (legacy): {result_dict}")
|
402
|
+
else:
|
403
|
+
result_dict = result
|
404
|
+
|
405
|
+
# Update workflow context with result
|
406
|
+
workflow_state["context"].update({"output": result_dict})
|
407
|
+
|
408
|
+
# Check for routing
|
409
|
+
route_config = current_step.get("route_output_to")
|
410
|
+
if route_config:
|
411
|
+
# Route to next agent
|
412
|
+
next_agent_id = route_config.get("agent")
|
413
|
+
next_task_name = route_config.get("task")
|
414
|
+
input_mapping = route_config.get("input_mapping", {})
|
415
|
+
|
416
|
+
logger.debug(f"🔍 Input mapping: {input_mapping}")
|
417
|
+
logger.debug(f"🔍 Available output data: {result_dict}")
|
418
|
+
|
419
|
+
# Resolve input mapping
|
420
|
+
next_input = self._resolve_input_mapping(input_mapping, result_dict, workflow_state["context"])
|
421
|
+
|
422
|
+
logger.info(f"🔄 Routing output to {next_agent_id}.{next_task_name}")
|
423
|
+
logger.debug(f"🔍 Resolved input for next task: {next_input}")
|
424
|
+
|
425
|
+
# Create and execute next task
|
426
|
+
next_task_id = self.task_registry.create_task(
|
427
|
+
workflow_id=workflow_id,
|
428
|
+
step_id=f"routed_step_{step_index}",
|
429
|
+
agent_id=next_agent_id,
|
430
|
+
task_name=next_task_name,
|
431
|
+
input_data=next_input
|
432
|
+
)
|
433
|
+
|
434
|
+
self._execute_task(next_task_id, workflow_id, step_index + 1)
|
435
|
+
else:
|
436
|
+
# Continue to next step
|
437
|
+
self._execute_workflow_step(workflow_id, step_index + 1)
|
438
|
+
|
439
|
+
def _resolve_input_data(self, input_config: Dict[str, Any], context: Dict[str, Any]) -> Dict[str, Any]:
|
440
|
+
"""Resolve input data with context variables."""
|
441
|
+
resolved = {}
|
442
|
+
for key, value in input_config.items():
|
443
|
+
if isinstance(value, str) and value.startswith("{{") and value.endswith("}}"):
|
444
|
+
# Template variable
|
445
|
+
var_path = value[2:-2].strip()
|
446
|
+
resolved[key] = self._get_nested_value(context, var_path)
|
447
|
+
else:
|
448
|
+
resolved[key] = value
|
449
|
+
return resolved
|
450
|
+
|
451
|
+
def _resolve_input_mapping(self, mapping: Dict[str, str], output: Dict[str, Any], context: Dict[str, Any]) -> Dict[str, Any]:
|
452
|
+
"""Resolve input mapping with output and context."""
|
453
|
+
resolved = {}
|
454
|
+
for target_key, source_template in mapping.items():
|
455
|
+
if isinstance(source_template, str) and source_template.startswith("{{") and source_template.endswith("}}"):
|
456
|
+
var_path = source_template[2:-2].strip()
|
457
|
+
if var_path.startswith("output."):
|
458
|
+
# From current output
|
459
|
+
field_name = var_path[7:] # Remove "output."
|
460
|
+
resolved[target_key] = output.get(field_name, "")
|
461
|
+
else:
|
462
|
+
# From context
|
463
|
+
resolved[target_key] = self._get_nested_value(context, var_path)
|
464
|
+
else:
|
465
|
+
resolved[target_key] = source_template
|
466
|
+
return resolved
|
467
|
+
|
468
|
+
def _get_nested_value(self, data: Dict[str, Any], path: str) -> Any:
|
469
|
+
"""Get nested value from dictionary using dot notation."""
|
470
|
+
keys = path.split('.')
|
471
|
+
current = data
|
472
|
+
for key in keys:
|
473
|
+
if isinstance(current, dict) and key in current:
|
474
|
+
current = current[key]
|
475
|
+
else:
|
476
|
+
return None
|
477
|
+
return current
|
478
|
+
|
479
|
+
def get_workflow_status(self, workflow_id: str) -> Optional[Dict[str, Any]]:
|
480
|
+
"""Get workflow execution status."""
|
481
|
+
if workflow_id not in self.active_workflows:
|
482
|
+
return None
|
483
|
+
|
484
|
+
workflow_state = self.active_workflows[workflow_id]
|
485
|
+
tasks = self.task_registry.get_workflow_tasks(workflow_id)
|
486
|
+
|
487
|
+
return {
|
488
|
+
"workflow_id": workflow_id,
|
489
|
+
"name": workflow_state["name"],
|
490
|
+
"current_step": workflow_state["current_step"],
|
491
|
+
"started_at": workflow_state["started_at"],
|
492
|
+
"context": workflow_state["context"],
|
493
|
+
"tasks": [task.to_dict() for task in tasks]
|
494
|
+
}
|
495
|
+
|
496
|
+
def get_runtime_status(self) -> Dict[str, Any]:
|
497
|
+
"""Get overall runtime status."""
|
498
|
+
return {
|
499
|
+
"agents": {
|
500
|
+
"registered": len(self.agent_registry.agents),
|
501
|
+
"agents": [agent.to_dict() for agent in self.agent_registry.agents.values()]
|
502
|
+
},
|
503
|
+
"tasks": self.task_registry.get_task_summary(),
|
504
|
+
"workflows": {
|
505
|
+
"active": len(self.active_workflows),
|
506
|
+
"configured": len(self.workflow_config.get('workflows', {}))
|
507
|
+
}
|
508
|
+
}
|