autobyteus 1.1.3__py3-none-any.whl → 1.1.4__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.
Files changed (148) hide show
  1. autobyteus/agent/agent.py +1 -1
  2. autobyteus/agent/bootstrap_steps/system_prompt_processing_step.py +4 -2
  3. autobyteus/agent/context/agent_config.py +36 -5
  4. autobyteus/agent/events/worker_event_dispatcher.py +1 -2
  5. autobyteus/agent/handlers/inter_agent_message_event_handler.py +1 -1
  6. autobyteus/agent/handlers/llm_user_message_ready_event_handler.py +2 -2
  7. autobyteus/agent/handlers/tool_result_event_handler.py +48 -20
  8. autobyteus/agent/handlers/user_input_message_event_handler.py +1 -1
  9. autobyteus/agent/input_processor/__init__.py +1 -7
  10. autobyteus/agent/message/context_file_type.py +6 -0
  11. autobyteus/agent/message/send_message_to.py +68 -99
  12. autobyteus/agent/phases/discover.py +2 -1
  13. autobyteus/agent/runtime/agent_worker.py +1 -0
  14. autobyteus/agent/tool_execution_result_processor/__init__.py +9 -0
  15. autobyteus/agent/tool_execution_result_processor/base_processor.py +46 -0
  16. autobyteus/agent/tool_execution_result_processor/processor_definition.py +36 -0
  17. autobyteus/agent/tool_execution_result_processor/processor_meta.py +36 -0
  18. autobyteus/agent/tool_execution_result_processor/processor_registry.py +70 -0
  19. autobyteus/agent/workspace/base_workspace.py +17 -2
  20. autobyteus/cli/__init__.py +1 -1
  21. autobyteus/cli/cli_display.py +1 -1
  22. autobyteus/cli/workflow_tui/__init__.py +4 -0
  23. autobyteus/cli/workflow_tui/app.py +210 -0
  24. autobyteus/cli/workflow_tui/state.py +189 -0
  25. autobyteus/cli/workflow_tui/widgets/__init__.py +6 -0
  26. autobyteus/cli/workflow_tui/widgets/agent_list_sidebar.py +149 -0
  27. autobyteus/cli/workflow_tui/widgets/focus_pane.py +335 -0
  28. autobyteus/cli/workflow_tui/widgets/logo.py +27 -0
  29. autobyteus/cli/workflow_tui/widgets/renderables.py +70 -0
  30. autobyteus/cli/workflow_tui/widgets/shared.py +51 -0
  31. autobyteus/cli/workflow_tui/widgets/status_bar.py +14 -0
  32. autobyteus/events/event_types.py +3 -0
  33. autobyteus/llm/api/lmstudio_llm.py +37 -0
  34. autobyteus/llm/api/openai_compatible_llm.py +20 -3
  35. autobyteus/llm/llm_factory.py +2 -0
  36. autobyteus/llm/lmstudio_provider.py +89 -0
  37. autobyteus/llm/providers.py +1 -0
  38. autobyteus/llm/token_counter/token_counter_factory.py +2 -0
  39. autobyteus/tools/__init__.py +2 -0
  40. autobyteus/tools/ask_user_input.py +2 -1
  41. autobyteus/tools/bash/bash_executor.py +2 -1
  42. autobyteus/tools/browser/session_aware/browser_session_aware_navigate_to.py +2 -0
  43. autobyteus/tools/browser/session_aware/browser_session_aware_web_element_trigger.py +3 -0
  44. autobyteus/tools/browser/session_aware/browser_session_aware_webpage_reader.py +3 -0
  45. autobyteus/tools/browser/session_aware/browser_session_aware_webpage_screenshot_taker.py +3 -0
  46. autobyteus/tools/browser/standalone/google_search_ui.py +2 -0
  47. autobyteus/tools/browser/standalone/navigate_to.py +2 -0
  48. autobyteus/tools/browser/standalone/web_page_pdf_generator.py +3 -0
  49. autobyteus/tools/browser/standalone/webpage_image_downloader.py +3 -0
  50. autobyteus/tools/browser/standalone/webpage_reader.py +2 -0
  51. autobyteus/tools/browser/standalone/webpage_screenshot_taker.py +3 -0
  52. autobyteus/tools/file/file_reader.py +36 -9
  53. autobyteus/tools/file/file_writer.py +37 -9
  54. autobyteus/tools/functional_tool.py +5 -4
  55. autobyteus/tools/image_downloader.py +2 -0
  56. autobyteus/tools/mcp/tool_registrar.py +3 -1
  57. autobyteus/tools/pdf_downloader.py +2 -1
  58. autobyteus/tools/registry/tool_definition.py +12 -8
  59. autobyteus/tools/registry/tool_registry.py +50 -2
  60. autobyteus/tools/timer.py +2 -0
  61. autobyteus/tools/tool_category.py +14 -4
  62. autobyteus/tools/tool_meta.py +6 -1
  63. autobyteus/tools/tool_origin.py +10 -0
  64. autobyteus/workflow/agentic_workflow.py +93 -0
  65. autobyteus/{agent/workflow → workflow}/base_agentic_workflow.py +19 -27
  66. autobyteus/workflow/bootstrap_steps/__init__.py +20 -0
  67. autobyteus/workflow/bootstrap_steps/agent_tool_injection_step.py +34 -0
  68. autobyteus/workflow/bootstrap_steps/base_workflow_bootstrap_step.py +23 -0
  69. autobyteus/workflow/bootstrap_steps/coordinator_initialization_step.py +41 -0
  70. autobyteus/workflow/bootstrap_steps/coordinator_prompt_preparation_step.py +108 -0
  71. autobyteus/workflow/bootstrap_steps/workflow_bootstrapper.py +50 -0
  72. autobyteus/workflow/bootstrap_steps/workflow_runtime_queue_initialization_step.py +25 -0
  73. autobyteus/workflow/context/__init__.py +17 -0
  74. autobyteus/workflow/context/team_manager.py +147 -0
  75. autobyteus/workflow/context/workflow_config.py +30 -0
  76. autobyteus/workflow/context/workflow_context.py +61 -0
  77. autobyteus/workflow/context/workflow_node_config.py +76 -0
  78. autobyteus/workflow/context/workflow_runtime_state.py +53 -0
  79. autobyteus/workflow/events/__init__.py +29 -0
  80. autobyteus/workflow/events/workflow_event_dispatcher.py +39 -0
  81. autobyteus/workflow/events/workflow_events.py +53 -0
  82. autobyteus/workflow/events/workflow_input_event_queue_manager.py +21 -0
  83. autobyteus/workflow/exceptions.py +8 -0
  84. autobyteus/workflow/factory/__init__.py +9 -0
  85. autobyteus/workflow/factory/workflow_factory.py +99 -0
  86. autobyteus/workflow/handlers/__init__.py +19 -0
  87. autobyteus/workflow/handlers/base_workflow_event_handler.py +16 -0
  88. autobyteus/workflow/handlers/inter_agent_message_request_event_handler.py +61 -0
  89. autobyteus/workflow/handlers/lifecycle_workflow_event_handler.py +27 -0
  90. autobyteus/workflow/handlers/process_user_message_event_handler.py +46 -0
  91. autobyteus/workflow/handlers/tool_approval_workflow_event_handler.py +39 -0
  92. autobyteus/workflow/handlers/workflow_event_handler_registry.py +23 -0
  93. autobyteus/workflow/phases/__init__.py +11 -0
  94. autobyteus/workflow/phases/workflow_operational_phase.py +19 -0
  95. autobyteus/workflow/phases/workflow_phase_manager.py +48 -0
  96. autobyteus/workflow/runtime/__init__.py +13 -0
  97. autobyteus/workflow/runtime/workflow_runtime.py +82 -0
  98. autobyteus/workflow/runtime/workflow_worker.py +117 -0
  99. autobyteus/workflow/shutdown_steps/__init__.py +17 -0
  100. autobyteus/workflow/shutdown_steps/agent_team_shutdown_step.py +42 -0
  101. autobyteus/workflow/shutdown_steps/base_workflow_shutdown_step.py +16 -0
  102. autobyteus/workflow/shutdown_steps/bridge_cleanup_step.py +28 -0
  103. autobyteus/workflow/shutdown_steps/sub_workflow_shutdown_step.py +41 -0
  104. autobyteus/workflow/shutdown_steps/workflow_shutdown_orchestrator.py +35 -0
  105. autobyteus/workflow/streaming/__init__.py +26 -0
  106. autobyteus/workflow/streaming/agent_event_bridge.py +48 -0
  107. autobyteus/workflow/streaming/agent_event_multiplexer.py +70 -0
  108. autobyteus/workflow/streaming/workflow_event_bridge.py +50 -0
  109. autobyteus/workflow/streaming/workflow_event_notifier.py +83 -0
  110. autobyteus/workflow/streaming/workflow_event_stream.py +33 -0
  111. autobyteus/workflow/streaming/workflow_stream_event_payloads.py +28 -0
  112. autobyteus/workflow/streaming/workflow_stream_events.py +45 -0
  113. autobyteus/workflow/utils/__init__.py +9 -0
  114. autobyteus/workflow/utils/wait_for_idle.py +46 -0
  115. autobyteus/workflow/workflow_builder.py +151 -0
  116. {autobyteus-1.1.3.dist-info → autobyteus-1.1.4.dist-info}/METADATA +16 -14
  117. {autobyteus-1.1.3.dist-info → autobyteus-1.1.4.dist-info}/RECORD +134 -65
  118. {autobyteus-1.1.3.dist-info → autobyteus-1.1.4.dist-info}/top_level.txt +1 -0
  119. examples/__init__.py +1 -0
  120. examples/discover_phase_transitions.py +104 -0
  121. examples/run_browser_agent.py +260 -0
  122. examples/run_google_slides_agent.py +286 -0
  123. examples/run_mcp_browser_client.py +174 -0
  124. examples/run_mcp_google_slides_client.py +270 -0
  125. examples/run_mcp_list_tools.py +189 -0
  126. examples/run_poem_writer.py +274 -0
  127. examples/run_sqlite_agent.py +293 -0
  128. examples/workflow/__init__.py +1 -0
  129. examples/workflow/run_basic_research_workflow.py +189 -0
  130. examples/workflow/run_code_review_workflow.py +269 -0
  131. examples/workflow/run_debate_workflow.py +212 -0
  132. examples/workflow/run_workflow_with_tui.py +153 -0
  133. autobyteus/agent/context/agent_phase_manager.py +0 -264
  134. autobyteus/agent/context/phases.py +0 -49
  135. autobyteus/agent/group/__init__.py +0 -0
  136. autobyteus/agent/group/agent_group.py +0 -164
  137. autobyteus/agent/group/agent_group_context.py +0 -81
  138. autobyteus/agent/input_processor/content_prefixing_input_processor.py +0 -41
  139. autobyteus/agent/input_processor/metadata_appending_input_processor.py +0 -34
  140. autobyteus/agent/input_processor/passthrough_input_processor.py +0 -33
  141. autobyteus/agent/workflow/__init__.py +0 -11
  142. autobyteus/agent/workflow/agentic_workflow.py +0 -89
  143. autobyteus/tools/mcp/registrar.py +0 -202
  144. autobyteus/workflow/simple_task.py +0 -98
  145. autobyteus/workflow/task.py +0 -147
  146. autobyteus/workflow/workflow.py +0 -49
  147. {autobyteus-1.1.3.dist-info → autobyteus-1.1.4.dist-info}/WHEEL +0 -0
  148. {autobyteus-1.1.3.dist-info → autobyteus-1.1.4.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,99 @@
1
+ # file: autobyteus/autobyteus/workflow/factory/workflow_factory.py
2
+ import logging
3
+ import uuid
4
+ from typing import Optional, Dict, List
5
+
6
+ from autobyteus.utils.singleton import SingletonMeta
7
+ from autobyteus.workflow.agentic_workflow import AgenticWorkflow
8
+ from autobyteus.workflow.context.workflow_config import WorkflowConfig
9
+ from autobyteus.workflow.context.workflow_context import WorkflowContext
10
+ from autobyteus.workflow.context.workflow_runtime_state import WorkflowRuntimeState
11
+ from autobyteus.workflow.context.team_manager import TeamManager
12
+ from autobyteus.workflow.runtime.workflow_runtime import WorkflowRuntime
13
+ from autobyteus.workflow.handlers.workflow_event_handler_registry import WorkflowEventHandlerRegistry
14
+ from autobyteus.workflow.handlers.process_user_message_event_handler import ProcessUserMessageEventHandler
15
+ from autobyteus.workflow.handlers.lifecycle_workflow_event_handler import LifecycleWorkflowEventHandler
16
+ from autobyteus.workflow.handlers.inter_agent_message_request_event_handler import InterAgentMessageRequestEventHandler
17
+ from autobyteus.workflow.handlers.tool_approval_workflow_event_handler import ToolApprovalWorkflowEventHandler
18
+ from autobyteus.workflow.events.workflow_events import (
19
+ ProcessUserMessageEvent,
20
+ WorkflowReadyEvent,
21
+ WorkflowErrorEvent,
22
+ InterAgentMessageRequestEvent,
23
+ ToolApprovalWorkflowEvent
24
+ )
25
+
26
+ logger = logging.getLogger(__name__)
27
+
28
+ class WorkflowFactory(metaclass=SingletonMeta):
29
+ """
30
+ A singleton factory for creating and managing AgenticWorkflow instances.
31
+ It orchestrates the assembly of all core workflow components.
32
+ """
33
+ def __init__(self):
34
+ self._active_workflows: Dict[str, AgenticWorkflow] = {}
35
+ logger.info("WorkflowFactory (Singleton) initialized.")
36
+
37
+ def _get_default_event_handler_registry(self) -> WorkflowEventHandlerRegistry:
38
+ """Returns a registry with default handlers for a new workflow."""
39
+ registry = WorkflowEventHandlerRegistry()
40
+ registry.register(ProcessUserMessageEvent, ProcessUserMessageEventHandler())
41
+ registry.register(InterAgentMessageRequestEvent, InterAgentMessageRequestEventHandler())
42
+ registry.register(ToolApprovalWorkflowEvent, ToolApprovalWorkflowEventHandler())
43
+ lifecycle_handler = LifecycleWorkflowEventHandler()
44
+ registry.register(WorkflowReadyEvent, lifecycle_handler)
45
+ registry.register(WorkflowErrorEvent, lifecycle_handler)
46
+ return registry
47
+
48
+ def create_workflow(self, config: WorkflowConfig) -> AgenticWorkflow:
49
+ """
50
+ Creates a new workflow based on the provided WorkflowConfig, stores it,
51
+ and returns its facade (AgenticWorkflow).
52
+ """
53
+ if not isinstance(config, WorkflowConfig):
54
+ raise TypeError(f"Expected WorkflowConfig instance, got {type(config).__name__}.")
55
+
56
+ workflow_id = f"workflow_{uuid.uuid4().hex[:8]}"
57
+ while workflow_id in self._active_workflows:
58
+ workflow_id = f"workflow_{uuid.uuid4().hex[:8]}"
59
+
60
+ # --- Component Assembly as per new architecture ---
61
+ state = WorkflowRuntimeState(workflow_id=workflow_id)
62
+ context = WorkflowContext(workflow_id=workflow_id, config=config, state=state)
63
+
64
+ handler_registry = self._get_default_event_handler_registry()
65
+ runtime = WorkflowRuntime(context=context, event_handler_registry=handler_registry)
66
+
67
+ team_manager = TeamManager(
68
+ workflow_id=workflow_id,
69
+ runtime=runtime,
70
+ multiplexer=runtime.multiplexer # Pass multiplexer created in runtime
71
+ )
72
+
73
+ context.state.team_manager = team_manager
74
+
75
+ workflow = AgenticWorkflow(runtime=runtime)
76
+
77
+ self._active_workflows[workflow_id] = workflow
78
+ logger.info(f"Workflow '{workflow_id}' created and stored successfully.")
79
+ return workflow
80
+
81
+ def get_workflow(self, workflow_id: str) -> Optional[AgenticWorkflow]:
82
+ """Retrieves an active workflow instance by its ID."""
83
+ return self._active_workflows.get(workflow_id)
84
+
85
+ async def remove_workflow(self, workflow_id: str, shutdown_timeout: float = 10.0) -> bool:
86
+ """
87
+ Removes a workflow from the factory's management and gracefully stops it.
88
+ """
89
+ workflow = self._active_workflows.pop(workflow_id, None)
90
+ if workflow:
91
+ logger.info(f"Removing workflow '{workflow_id}'. Attempting graceful shutdown.")
92
+ await workflow.stop(timeout=shutdown_timeout)
93
+ return True
94
+ logger.warning(f"Workflow with ID '{workflow_id}' not found for removal.")
95
+ return False
96
+
97
+ def list_active_workflow_ids(self) -> List[str]:
98
+ """Returns a list of IDs of all active workflows managed by this factory."""
99
+ return list(self._active_workflows.keys())
@@ -0,0 +1,19 @@
1
+ # file: autobyteus/autobyteus/workflow/handlers/__init__.py
2
+ """
3
+ Event handlers for the workflow runtime.
4
+ """
5
+ from autobyteus.workflow.handlers.base_workflow_event_handler import BaseWorkflowEventHandler
6
+ from autobyteus.workflow.handlers.lifecycle_workflow_event_handler import LifecycleWorkflowEventHandler
7
+ from autobyteus.workflow.handlers.inter_agent_message_request_event_handler import InterAgentMessageRequestEventHandler
8
+ from autobyteus.workflow.handlers.process_user_message_event_handler import ProcessUserMessageEventHandler
9
+ from autobyteus.workflow.handlers.tool_approval_workflow_event_handler import ToolApprovalWorkflowEventHandler
10
+ from autobyteus.workflow.handlers.workflow_event_handler_registry import WorkflowEventHandlerRegistry
11
+
12
+ __all__ = [
13
+ "BaseWorkflowEventHandler",
14
+ "LifecycleWorkflowEventHandler",
15
+ "InterAgentMessageRequestEventHandler",
16
+ "ProcessUserMessageEventHandler",
17
+ "ToolApprovalWorkflowEventHandler",
18
+ "WorkflowEventHandlerRegistry",
19
+ ]
@@ -0,0 +1,16 @@
1
+ # file: autobyteus/autobyteus/workflow/handlers/base_workflow_event_handler.py
2
+ import logging
3
+ from abc import ABC, abstractmethod
4
+ from typing import Any, TYPE_CHECKING
5
+
6
+ if TYPE_CHECKING:
7
+ from autobyteus.workflow.context.workflow_context import WorkflowContext
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+ class BaseWorkflowEventHandler(ABC):
12
+ """Abstract base class for workflow event handlers."""
13
+
14
+ @abstractmethod
15
+ async def handle(self, event: Any, context: 'WorkflowContext') -> None:
16
+ raise NotImplementedError("Subclasses must implement the 'handle' method.")
@@ -0,0 +1,61 @@
1
+ # file: autobyteus/autobyteus/workflow/handlers/inter_agent_message_request_event_handler.py
2
+ import logging
3
+ from typing import TYPE_CHECKING
4
+
5
+ from autobyteus.workflow.handlers.base_workflow_event_handler import BaseWorkflowEventHandler
6
+ from autobyteus.workflow.events.workflow_events import InterAgentMessageRequestEvent
7
+ from autobyteus.agent.message.inter_agent_message import InterAgentMessage
8
+ from autobyteus.agent.message.agent_input_user_message import AgentInputUserMessage
9
+ from autobyteus.workflow.agentic_workflow import AgenticWorkflow
10
+ from autobyteus.agent.agent import Agent
11
+
12
+ if TYPE_CHECKING:
13
+ from autobyteus.workflow.context.workflow_context import WorkflowContext
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+ class InterAgentMessageRequestEventHandler(BaseWorkflowEventHandler):
18
+ """
19
+ Handles requests to send messages between nodes (agents or sub-workflows).
20
+ It relies on the TeamManager to handle on-demand startup of the recipient.
21
+ """
22
+ async def handle(self, event: InterAgentMessageRequestEvent, context: 'WorkflowContext') -> None:
23
+ workflow_id = context.workflow_id
24
+ team_manager = context.team_manager
25
+
26
+ if not team_manager:
27
+ logger.error(f"Workflow '{workflow_id}': TeamManager not found. Cannot route message from '{event.sender_agent_id}' to '{event.recipient_name}'.")
28
+ return
29
+
30
+ try:
31
+ target_node = await team_manager.ensure_node_is_ready(event.recipient_name)
32
+ except Exception as e:
33
+ msg = f"Recipient node '{event.recipient_name}' not found or failed to start for message from '{event.sender_agent_id}'. Error: {e}"
34
+ logger.error(f"Workflow '{workflow_id}': {msg}", exc_info=True)
35
+ return
36
+
37
+ try:
38
+ if isinstance(target_node, AgenticWorkflow):
39
+ # If target is a sub-workflow, post a user message to it.
40
+ # The sub-workflow will route it to its own coordinator.
41
+ message_for_workflow = AgentInputUserMessage(content=event.content)
42
+ await target_node.post_message(message_for_workflow)
43
+ logger.info(f"Workflow '{workflow_id}': Successfully posted message from '{event.sender_agent_id}' to sub-workflow '{event.recipient_name}'.")
44
+
45
+ elif isinstance(target_node, Agent):
46
+ # If target is a regular agent, create and post an InterAgentMessage.
47
+ message_for_agent = InterAgentMessage.create_with_dynamic_message_type(
48
+ recipient_role_name=target_node.context.config.role,
49
+ recipient_agent_id=target_node.agent_id,
50
+ content=event.content,
51
+ message_type=event.message_type,
52
+ sender_agent_id=event.sender_agent_id
53
+ )
54
+ await target_node.post_inter_agent_message(message_for_agent)
55
+ logger.info(f"Workflow '{workflow_id}': Successfully posted message from '{event.sender_agent_id}' to agent '{event.recipient_name}'.")
56
+ else:
57
+ raise TypeError(f"Target node '{event.recipient_name}' is of an unsupported type: {type(target_node).__name__}")
58
+
59
+ except Exception as e:
60
+ msg = f"Error posting message to node '{event.recipient_name}': {e}"
61
+ logger.error(f"Workflow '{workflow_id}': {msg}", exc_info=True)
@@ -0,0 +1,27 @@
1
+ # file: autobyteus/autobyteus/workflow/handlers/lifecycle_workflow_event_handler.py
2
+ import logging
3
+ from typing import TYPE_CHECKING
4
+
5
+ from autobyteus.workflow.handlers.base_workflow_event_handler import BaseWorkflowEventHandler
6
+ from autobyteus.workflow.events.workflow_events import BaseWorkflowEvent, WorkflowReadyEvent, WorkflowErrorEvent
7
+
8
+ if TYPE_CHECKING:
9
+ from autobyteus.workflow.context.workflow_context import WorkflowContext
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+ class LifecycleWorkflowEventHandler(BaseWorkflowEventHandler):
14
+ """Logs various lifecycle events for a workflow."""
15
+ async def handle(self, event: BaseWorkflowEvent, context: 'WorkflowContext') -> None:
16
+ workflow_id = context.workflow_id
17
+ current_phase = context.state.current_phase.value
18
+
19
+ if isinstance(event, WorkflowReadyEvent):
20
+ logger.info(f"Workflow '{workflow_id}' Logged WorkflowReadyEvent. Current phase: {current_phase}")
21
+ elif isinstance(event, WorkflowErrorEvent):
22
+ logger.error(
23
+ f"Workflow '{workflow_id}' Logged WorkflowErrorEvent: {event.error_message}. "
24
+ f"Details: {event.exception_details}. Current phase: {current_phase}"
25
+ )
26
+ else:
27
+ logger.warning(f"LifecycleWorkflowEventHandler received unhandled event type: {type(event).__name__}")
@@ -0,0 +1,46 @@
1
+ # file: autobyteus/autobyteus/workflow/handlers/process_user_message_event_handler.py
2
+ import logging
3
+ from typing import TYPE_CHECKING
4
+ from autobyteus.workflow.handlers.base_workflow_event_handler import BaseWorkflowEventHandler
5
+ from autobyteus.workflow.events.workflow_events import ProcessUserMessageEvent
6
+ from autobyteus.agent.agent import Agent
7
+ from autobyteus.workflow.agentic_workflow import AgenticWorkflow
8
+ from autobyteus.agent.message.agent_input_user_message import AgentInputUserMessage
9
+
10
+ if TYPE_CHECKING:
11
+ from autobyteus.workflow.context.workflow_context import WorkflowContext
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+ class ProcessUserMessageEventHandler(BaseWorkflowEventHandler):
16
+ """Handles user messages by routing them to the specified target agent or sub-workflow."""
17
+ async def handle(self, event: ProcessUserMessageEvent, context: 'WorkflowContext') -> None:
18
+ await context.phase_manager.notify_processing_started()
19
+
20
+ team_manager = context.team_manager
21
+ if not team_manager:
22
+ msg = f"Workflow '{context.workflow_id}': TeamManager not found. Cannot route message."
23
+ logger.error(msg)
24
+ await context.phase_manager.notify_error_occurred(msg, "TeamManager is not initialized.")
25
+ return
26
+
27
+ try:
28
+ target_node = await team_manager.ensure_node_is_ready(event.target_agent_name)
29
+ except Exception as e:
30
+ msg = f"Workflow '{context.workflow_id}': Node '{event.target_agent_name}' not found or failed to start. Cannot route message. Error: {e}"
31
+ logger.error(msg, exc_info=True)
32
+ await context.phase_manager.notify_error_occurred(msg, f"Node '{event.target_agent_name}' not found or failed to start.")
33
+ return
34
+
35
+ if isinstance(target_node, Agent):
36
+ await target_node.post_user_message(event.user_message)
37
+ logger.info(f"Workflow '{context.workflow_id}': Routed user message to agent node '{event.target_agent_name}'.")
38
+ elif isinstance(target_node, AgenticWorkflow):
39
+ await target_node.post_message(event.user_message)
40
+ logger.info(f"Workflow '{context.workflow_id}': Routed user message to sub-workflow node '{event.target_agent_name}'.")
41
+ else:
42
+ msg = f"Target node '{event.target_agent_name}' is of an unsupported type: {type(target_node).__name__}"
43
+ logger.error(f"Workflow '{context.workflow_id}': {msg}")
44
+ await context.phase_manager.notify_error_occurred(msg, "")
45
+
46
+ await context.phase_manager.notify_processing_complete_and_idle()
@@ -0,0 +1,39 @@
1
+ # file: autobyteus/autobyteus/workflow/handlers/tool_approval_workflow_event_handler.py
2
+ import logging
3
+ from typing import TYPE_CHECKING
4
+
5
+ from autobyteus.workflow.handlers.base_workflow_event_handler import BaseWorkflowEventHandler
6
+ from autobyteus.workflow.events.workflow_events import ToolApprovalWorkflowEvent
7
+
8
+ if TYPE_CHECKING:
9
+ from autobyteus.workflow.context.workflow_context import WorkflowContext
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+ class ToolApprovalWorkflowEventHandler(BaseWorkflowEventHandler):
14
+ """
15
+ Handles tool approval events by routing them to the correct agent.
16
+ """
17
+ async def handle(self, event: ToolApprovalWorkflowEvent, context: 'WorkflowContext') -> None:
18
+ workflow_id = context.workflow_id
19
+ team_manager = context.team_manager
20
+
21
+ if not team_manager:
22
+ msg = f"Workflow '{workflow_id}': TeamManager not found. Cannot route approval for agent '{event.agent_name}'."
23
+ logger.error(msg)
24
+ await context.phase_manager.notify_error_occurred(msg, "TeamManager is not initialized.")
25
+ return
26
+
27
+ target_agent = await team_manager.ensure_agent_is_ready(event.agent_name)
28
+ if not target_agent:
29
+ msg = f"Workflow '{workflow_id}': Target agent '{event.agent_name}' for approval not found or failed to start."
30
+ logger.error(msg)
31
+ await context.phase_manager.notify_error_occurred(msg, f"Agent '{event.agent_name}' not found or failed to start.")
32
+ return
33
+
34
+ logger.info(f"Workflow '{workflow_id}': Posting tool approval (Approved: {event.is_approved}) to agent '{event.agent_name}' for invocation '{event.tool_invocation_id}'.")
35
+ await target_agent.post_tool_execution_approval(
36
+ tool_invocation_id=event.tool_invocation_id,
37
+ is_approved=event.is_approved,
38
+ reason=event.reason
39
+ )
@@ -0,0 +1,23 @@
1
+ # file: autobyteus/autobyteus/workflow/handlers/workflow_event_handler_registry.py
2
+ import logging
3
+ from typing import Dict, Type, Optional
4
+
5
+ from autobyteus.workflow.handlers.base_workflow_event_handler import BaseWorkflowEventHandler
6
+ from autobyteus.workflow.events.workflow_events import BaseWorkflowEvent
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+ class WorkflowEventHandlerRegistry:
11
+ """Manages registration and retrieval of workflow event handlers."""
12
+ def __init__(self):
13
+ self._handlers: Dict[Type[BaseWorkflowEvent], BaseWorkflowEventHandler] = {}
14
+ logger.info("WorkflowEventHandlerRegistry initialized.")
15
+
16
+ def register(self, event_class: Type[BaseWorkflowEvent], handler_instance: BaseWorkflowEventHandler):
17
+ if not issubclass(event_class, BaseWorkflowEvent):
18
+ raise TypeError("Can only register handlers for BaseWorkflowEvent subclasses.")
19
+ self._handlers[event_class] = handler_instance
20
+ logger.info(f"Handler '{type(handler_instance).__name__}' registered for event '{event_class.__name__}'.")
21
+
22
+ def get_handler(self, event_class: Type[BaseWorkflowEvent]) -> Optional[BaseWorkflowEventHandler]:
23
+ return self._handlers.get(event_class)
@@ -0,0 +1,11 @@
1
+ # file: autobyteus/autobyteus/workflow/phases/__init__.py
2
+ """
3
+ This package contains components for defining and managing workflow operational phases.
4
+ """
5
+ from autobyteus.workflow.phases.workflow_operational_phase import WorkflowOperationalPhase
6
+ from autobyteus.workflow.phases.workflow_phase_manager import WorkflowPhaseManager
7
+
8
+ __all__ = [
9
+ "WorkflowOperationalPhase",
10
+ "WorkflowPhaseManager",
11
+ ]
@@ -0,0 +1,19 @@
1
+ # file: autobyteus/autobyteus/workflow/phases/workflow_operational_phase.py
2
+ from enum import Enum
3
+
4
+ class WorkflowOperationalPhase(str, Enum):
5
+ """Defines the operational phases of an AgenticWorkflow."""
6
+ UNINITIALIZED = "uninitialized"
7
+ BOOTSTRAPPING = "bootstrapping"
8
+ IDLE = "idle"
9
+ PROCESSING = "processing"
10
+ SHUTTING_DOWN = "shutting_down"
11
+ SHUTDOWN_COMPLETE = "shutdown_complete"
12
+ ERROR = "error"
13
+
14
+ def is_terminal(self) -> bool:
15
+ """Checks if the phase is a terminal state."""
16
+ return self in [WorkflowOperationalPhase.SHUTDOWN_COMPLETE, WorkflowOperationalPhase.ERROR]
17
+
18
+ def __str__(self) -> str:
19
+ return self.value
@@ -0,0 +1,48 @@
1
+ # file: autobyteus/autobyteus/workflow/phases/workflow_phase_manager.py
2
+ import logging
3
+ from typing import TYPE_CHECKING, Optional
4
+
5
+ from autobyteus.workflow.phases.workflow_operational_phase import WorkflowOperationalPhase
6
+
7
+ if TYPE_CHECKING:
8
+ from autobyteus.workflow.context.workflow_context import WorkflowContext
9
+ from autobyteus.workflow.streaming.workflow_event_notifier import WorkflowExternalEventNotifier
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+ class WorkflowPhaseManager:
14
+ """Manages the operational phase of a workflow."""
15
+ def __init__(self, context: 'WorkflowContext', notifier: 'WorkflowExternalEventNotifier'):
16
+ self.context = context
17
+ self.notifier = notifier
18
+ self.context.state.current_phase = WorkflowOperationalPhase.UNINITIALIZED
19
+ logger.debug(f"WorkflowPhaseManager initialized for workflow '{context.workflow_id}'.")
20
+
21
+ async def _transition_phase(self, new_phase: WorkflowOperationalPhase, extra_data: Optional[dict] = None):
22
+ old_phase = self.context.state.current_phase
23
+ if old_phase == new_phase:
24
+ return
25
+ logger.info(f"Workflow '{self.context.workflow_id}' transitioning from {old_phase.value} to {new_phase.value}.")
26
+ self.context.state.current_phase = new_phase
27
+ self.notifier.notify_phase_change(new_phase, old_phase, extra_data)
28
+
29
+ async def notify_bootstrapping_started(self):
30
+ await self._transition_phase(WorkflowOperationalPhase.BOOTSTRAPPING)
31
+
32
+ async def notify_initialization_complete(self):
33
+ await self._transition_phase(WorkflowOperationalPhase.IDLE)
34
+
35
+ async def notify_processing_started(self):
36
+ await self._transition_phase(WorkflowOperationalPhase.PROCESSING)
37
+
38
+ async def notify_processing_complete_and_idle(self):
39
+ await self._transition_phase(WorkflowOperationalPhase.IDLE)
40
+
41
+ async def notify_error_occurred(self, error_message: str, error_details: Optional[str] = None):
42
+ await self._transition_phase(WorkflowOperationalPhase.ERROR, {"error_message": error_message, "error_details": error_details})
43
+
44
+ async def notify_shutdown_initiated(self):
45
+ await self._transition_phase(WorkflowOperationalPhase.SHUTTING_DOWN)
46
+
47
+ async def notify_final_shutdown_complete(self):
48
+ await self._transition_phase(WorkflowOperationalPhase.SHUTDOWN_COMPLETE)
@@ -0,0 +1,13 @@
1
+ # file: autobyteus/autobyteus/workflow/runtime/__init__.py
2
+ """
3
+ The workflow runtime contains the active execution components for a workflow,
4
+ including the main WorkflowRuntime controller and the WorkflowWorker that runs
5
+ in a dedicated thread.
6
+ """
7
+ from autobyteus.workflow.runtime.workflow_runtime import WorkflowRuntime
8
+ from autobyteus.workflow.runtime.workflow_worker import WorkflowWorker
9
+
10
+ __all__ = [
11
+ "WorkflowRuntime",
12
+ "WorkflowWorker",
13
+ ]
@@ -0,0 +1,82 @@
1
+ # file: autobyteus/autobyteus/workflow/runtime/workflow_runtime.py
2
+ import asyncio
3
+ import logging
4
+ from typing import TYPE_CHECKING, Callable, Optional
5
+
6
+ from autobyteus.workflow.context.workflow_context import WorkflowContext
7
+ from autobyteus.workflow.phases.workflow_phase_manager import WorkflowPhaseManager
8
+ from autobyteus.workflow.runtime.workflow_worker import WorkflowWorker
9
+ from autobyteus.workflow.events.workflow_events import BaseWorkflowEvent
10
+ from autobyteus.workflow.streaming.workflow_event_notifier import WorkflowExternalEventNotifier
11
+ from autobyteus.workflow.streaming.agent_event_multiplexer import AgentEventMultiplexer
12
+
13
+ if TYPE_CHECKING:
14
+ from autobyteus.workflow.handlers.workflow_event_handler_registry import WorkflowEventHandlerRegistry
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+ class WorkflowRuntime:
19
+ """The active execution engine for a workflow, managing the worker."""
20
+ def __init__(self, context: WorkflowContext, event_handler_registry: 'WorkflowEventHandlerRegistry'):
21
+ self.context = context
22
+ self.notifier = WorkflowExternalEventNotifier(workflow_id=self.context.workflow_id, runtime_ref=self)
23
+ self.phase_manager = WorkflowPhaseManager(context=self.context, notifier=self.notifier)
24
+
25
+ # --- FIX: Set the phase_manager_ref on the context's state BEFORE creating the worker ---
26
+ self.context.state.phase_manager_ref = self.phase_manager
27
+
28
+ self._worker = WorkflowWorker(self.context, event_handler_registry)
29
+
30
+ self.multiplexer = AgentEventMultiplexer(
31
+ workflow_id=self.context.workflow_id,
32
+ notifier=self.notifier,
33
+ worker_ref=self._worker
34
+ )
35
+
36
+ # Set other references on the context's state object for access by other components
37
+ self.context.state.multiplexer_ref = self.multiplexer
38
+
39
+ self._worker.add_done_callback(self._handle_worker_completion)
40
+ logger.info(f"WorkflowRuntime initialized for workflow '{self.context.workflow_id}'.")
41
+
42
+ def get_worker_loop(self) -> Optional[asyncio.AbstractEventLoop]:
43
+ """Returns the worker's event loop if it's running."""
44
+ return self._worker.get_worker_loop()
45
+
46
+ def _handle_worker_completion(self, future: asyncio.Future):
47
+ workflow_id = self.context.workflow_id
48
+ try:
49
+ future.result()
50
+ logger.info(f"WorkflowRuntime '{workflow_id}': Worker thread completed.")
51
+ except Exception as e:
52
+ logger.error(f"WorkflowRuntime '{workflow_id}': Worker thread terminated with exception: {e}", exc_info=True)
53
+ if not self.context.state.current_phase.is_terminal():
54
+ asyncio.run(self.phase_manager.notify_final_shutdown_complete())
55
+
56
+ def start(self):
57
+ if self._worker.is_alive:
58
+ return
59
+ self._worker.start()
60
+
61
+ async def stop(self, timeout: float = 10.0):
62
+ await self.phase_manager.notify_shutdown_initiated()
63
+ await self._worker.stop(timeout=timeout)
64
+ await self.phase_manager.notify_final_shutdown_complete()
65
+
66
+ async def submit_event(self, event: BaseWorkflowEvent):
67
+ if not self._worker.is_alive:
68
+ raise RuntimeError("Workflow worker is not active.")
69
+ def _coro_factory():
70
+ async def _enqueue():
71
+ from autobyteus.workflow.events.workflow_events import ProcessUserMessageEvent
72
+ if isinstance(event, ProcessUserMessageEvent):
73
+ await self.context.state.input_event_queues.enqueue_user_message(event)
74
+ else:
75
+ await self.context.state.input_event_queues.enqueue_internal_system_event(event)
76
+ return _enqueue()
77
+ future = self._worker.schedule_coroutine(_coro_factory)
78
+ await asyncio.wrap_future(future)
79
+
80
+ @property
81
+ def is_running(self) -> bool:
82
+ return self._worker.is_alive
@@ -0,0 +1,117 @@
1
+ # file: autobyteus/autobyteus/workflow/runtime/workflow_worker.py
2
+ import asyncio
3
+ import logging
4
+ import concurrent.futures
5
+ from typing import TYPE_CHECKING, Optional, Callable, Awaitable, Any
6
+
7
+ from autobyteus.workflow.events.workflow_event_dispatcher import WorkflowEventDispatcher
8
+ from autobyteus.workflow.bootstrap_steps.workflow_bootstrapper import WorkflowBootstrapper
9
+ from autobyteus.workflow.shutdown_steps.workflow_shutdown_orchestrator import WorkflowShutdownOrchestrator
10
+ from autobyteus.agent.runtime.agent_thread_pool_manager import AgentThreadPoolManager
11
+
12
+ if TYPE_CHECKING:
13
+ from autobyteus.workflow.context.workflow_context import WorkflowContext
14
+ from autobyteus.workflow.handlers.workflow_event_handler_registry import WorkflowEventHandlerRegistry
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+ class WorkflowWorker:
19
+ """Encapsulates the core event processing loop for a workflow."""
20
+ def __init__(self, context: 'WorkflowContext', event_handler_registry: 'WorkflowEventHandlerRegistry'):
21
+ self.context = context
22
+ self.phase_manager = self.context.phase_manager
23
+ self.event_dispatcher = WorkflowEventDispatcher(event_handler_registry, self.phase_manager)
24
+
25
+ self._thread_pool_manager = AgentThreadPoolManager()
26
+ self._thread_future: Optional[concurrent.futures.Future] = None
27
+ self._worker_loop: Optional[asyncio.AbstractEventLoop] = None
28
+ self._async_stop_event: Optional[asyncio.Event] = None
29
+ self._is_active: bool = False
30
+ self._stop_initiated: bool = False
31
+ self._done_callbacks: list[Callable[[concurrent.futures.Future], None]] = []
32
+ logger.info(f"WorkflowWorker initialized for workflow '{self.context.workflow_id}'.")
33
+
34
+ def get_worker_loop(self) -> Optional[asyncio.AbstractEventLoop]:
35
+ """Returns the worker's event loop if it's running."""
36
+ return self._worker_loop if self._worker_loop and self._worker_loop.is_running() else None
37
+
38
+ def add_done_callback(self, callback: Callable):
39
+ if self._thread_future:
40
+ self._thread_future.add_done_callback(callback)
41
+ else:
42
+ self._done_callbacks.append(callback)
43
+
44
+ def schedule_coroutine(self, coro_factory: Callable[[], Awaitable[Any]]) -> concurrent.futures.Future:
45
+ if not self._worker_loop:
46
+ raise RuntimeError("WorkflowWorker loop is not available.")
47
+ return asyncio.run_coroutine_threadsafe(coro_factory(), self._worker_loop)
48
+
49
+ def start(self):
50
+ if self._is_active:
51
+ return
52
+ self._is_active = True
53
+ self._thread_future = self._thread_pool_manager.submit_task(self._run_managed_loop)
54
+ for cb in self._done_callbacks:
55
+ self._thread_future.add_done_callback(cb)
56
+ self._done_callbacks.clear()
57
+
58
+ def _run_managed_loop(self):
59
+ try:
60
+ self._worker_loop = asyncio.new_event_loop()
61
+ asyncio.set_event_loop(self._worker_loop)
62
+ self._async_stop_event = asyncio.Event()
63
+ self._worker_loop.run_until_complete(self.async_run())
64
+ except Exception as e:
65
+ logger.error(f"WorkflowWorker '{self.context.workflow_id}' event loop crashed: {e}", exc_info=True)
66
+ finally:
67
+ if self._worker_loop:
68
+ self._worker_loop.close()
69
+ self._is_active = False
70
+
71
+ async def async_run(self):
72
+ bootstrapper = WorkflowBootstrapper()
73
+ if not await bootstrapper.run(self.context, self.phase_manager):
74
+ logger.critical(f"Workflow '{self.context.workflow_id}' failed to initialize. Shutting down.")
75
+ return
76
+
77
+ logger.info(f"Workflow '{self.context.workflow_id}' entering main event loop.")
78
+ while not self._async_stop_event.is_set():
79
+ try:
80
+ # Combine queues for a single wait point
81
+ user_message_task = asyncio.create_task(self.context.state.input_event_queues.user_message_queue.get())
82
+ system_task = asyncio.create_task(self.context.state.input_event_queues.internal_system_event_queue.get())
83
+ done, pending = await asyncio.wait([user_message_task, system_task], return_when=asyncio.FIRST_COMPLETED, timeout=0.2)
84
+
85
+ for task in pending:
86
+ task.cancel()
87
+
88
+ if not done:
89
+ continue
90
+
91
+ event = done.pop().result()
92
+ await self.event_dispatcher.dispatch(event, self.context)
93
+
94
+ except asyncio.TimeoutError:
95
+ continue
96
+
97
+ logger.info(f"Workflow '{self.context.workflow_id}' shutdown signal received. Cleaning up.")
98
+ shutdown_orchestrator = WorkflowShutdownOrchestrator()
99
+ await shutdown_orchestrator.run(self.context)
100
+
101
+ async def stop(self, timeout: float = 10.0):
102
+ if not self._is_active or self._stop_initiated:
103
+ return
104
+ self._stop_initiated = True
105
+ if self._worker_loop:
106
+ self._worker_loop.call_soon_threadsafe(self._async_stop_event.set)
107
+ if self._thread_future:
108
+ try:
109
+ # FIX: Use asyncio.wait_for() to handle the timeout correctly.
110
+ await asyncio.wait_for(asyncio.wrap_future(self._thread_future), timeout=timeout)
111
+ except asyncio.TimeoutError:
112
+ logger.warning(f"Timeout waiting for workflow worker '{self.context.workflow_id}' to terminate.")
113
+ self._is_active = False
114
+
115
+ @property
116
+ def is_alive(self) -> bool:
117
+ return self._thread_future is not None and not self._thread_future.done()
@@ -0,0 +1,17 @@
1
+ # file: autobyteus/autobyteus/workflow/shutdown_steps/__init__.py
2
+ """
3
+ Defines individual, self-contained steps for the workflow shutdown process.
4
+ """
5
+ from autobyteus.workflow.shutdown_steps.base_workflow_shutdown_step import BaseWorkflowShutdownStep
6
+ from autobyteus.workflow.shutdown_steps.agent_team_shutdown_step import AgentTeamShutdownStep
7
+ from autobyteus.workflow.shutdown_steps.sub_workflow_shutdown_step import SubWorkflowShutdownStep
8
+ from autobyteus.workflow.shutdown_steps.bridge_cleanup_step import BridgeCleanupStep
9
+ from autobyteus.workflow.shutdown_steps.workflow_shutdown_orchestrator import WorkflowShutdownOrchestrator
10
+
11
+ __all__ = [
12
+ "BaseWorkflowShutdownStep",
13
+ "AgentTeamShutdownStep",
14
+ "SubWorkflowShutdownStep",
15
+ "BridgeCleanupStep",
16
+ "WorkflowShutdownOrchestrator",
17
+ ]