autobyteus 1.1.2__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 (171) hide show
  1. autobyteus/agent/agent.py +1 -1
  2. autobyteus/agent/bootstrap_steps/__init__.py +2 -0
  3. autobyteus/agent/bootstrap_steps/agent_bootstrapper.py +2 -0
  4. autobyteus/agent/bootstrap_steps/mcp_server_prewarming_step.py +71 -0
  5. autobyteus/agent/bootstrap_steps/system_prompt_processing_step.py +4 -2
  6. autobyteus/agent/context/agent_config.py +36 -5
  7. autobyteus/agent/events/worker_event_dispatcher.py +1 -2
  8. autobyteus/agent/handlers/inter_agent_message_event_handler.py +1 -1
  9. autobyteus/agent/handlers/llm_user_message_ready_event_handler.py +2 -2
  10. autobyteus/agent/handlers/tool_result_event_handler.py +48 -20
  11. autobyteus/agent/handlers/user_input_message_event_handler.py +1 -1
  12. autobyteus/agent/input_processor/__init__.py +1 -7
  13. autobyteus/agent/llm_response_processor/provider_aware_tool_usage_processor.py +41 -12
  14. autobyteus/agent/message/context_file_type.py +6 -0
  15. autobyteus/agent/message/send_message_to.py +68 -99
  16. autobyteus/agent/phases/discover.py +2 -1
  17. autobyteus/agent/runtime/agent_worker.py +25 -34
  18. autobyteus/agent/shutdown_steps/__init__.py +17 -0
  19. autobyteus/agent/shutdown_steps/agent_shutdown_orchestrator.py +63 -0
  20. autobyteus/agent/shutdown_steps/base_shutdown_step.py +33 -0
  21. autobyteus/agent/shutdown_steps/llm_instance_cleanup_step.py +45 -0
  22. autobyteus/agent/shutdown_steps/mcp_server_cleanup_step.py +32 -0
  23. autobyteus/agent/tool_execution_result_processor/__init__.py +9 -0
  24. autobyteus/agent/tool_execution_result_processor/base_processor.py +46 -0
  25. autobyteus/agent/tool_execution_result_processor/processor_definition.py +36 -0
  26. autobyteus/agent/tool_execution_result_processor/processor_meta.py +36 -0
  27. autobyteus/agent/tool_execution_result_processor/processor_registry.py +70 -0
  28. autobyteus/agent/workspace/base_workspace.py +17 -2
  29. autobyteus/cli/__init__.py +1 -1
  30. autobyteus/cli/cli_display.py +1 -1
  31. autobyteus/cli/workflow_tui/__init__.py +4 -0
  32. autobyteus/cli/workflow_tui/app.py +210 -0
  33. autobyteus/cli/workflow_tui/state.py +189 -0
  34. autobyteus/cli/workflow_tui/widgets/__init__.py +6 -0
  35. autobyteus/cli/workflow_tui/widgets/agent_list_sidebar.py +149 -0
  36. autobyteus/cli/workflow_tui/widgets/focus_pane.py +335 -0
  37. autobyteus/cli/workflow_tui/widgets/logo.py +27 -0
  38. autobyteus/cli/workflow_tui/widgets/renderables.py +70 -0
  39. autobyteus/cli/workflow_tui/widgets/shared.py +51 -0
  40. autobyteus/cli/workflow_tui/widgets/status_bar.py +14 -0
  41. autobyteus/events/event_types.py +3 -0
  42. autobyteus/llm/api/lmstudio_llm.py +37 -0
  43. autobyteus/llm/api/openai_compatible_llm.py +20 -3
  44. autobyteus/llm/llm_factory.py +2 -0
  45. autobyteus/llm/lmstudio_provider.py +89 -0
  46. autobyteus/llm/providers.py +1 -0
  47. autobyteus/llm/token_counter/token_counter_factory.py +2 -0
  48. autobyteus/tools/__init__.py +2 -0
  49. autobyteus/tools/ask_user_input.py +2 -1
  50. autobyteus/tools/base_tool.py +2 -0
  51. autobyteus/tools/bash/bash_executor.py +2 -1
  52. autobyteus/tools/browser/session_aware/browser_session_aware_navigate_to.py +2 -0
  53. autobyteus/tools/browser/session_aware/browser_session_aware_web_element_trigger.py +3 -0
  54. autobyteus/tools/browser/session_aware/browser_session_aware_webpage_reader.py +3 -0
  55. autobyteus/tools/browser/session_aware/browser_session_aware_webpage_screenshot_taker.py +3 -0
  56. autobyteus/tools/browser/standalone/google_search_ui.py +2 -0
  57. autobyteus/tools/browser/standalone/navigate_to.py +2 -0
  58. autobyteus/tools/browser/standalone/web_page_pdf_generator.py +3 -0
  59. autobyteus/tools/browser/standalone/webpage_image_downloader.py +3 -0
  60. autobyteus/tools/browser/standalone/webpage_reader.py +2 -0
  61. autobyteus/tools/browser/standalone/webpage_screenshot_taker.py +3 -0
  62. autobyteus/tools/file/file_reader.py +36 -9
  63. autobyteus/tools/file/file_writer.py +37 -9
  64. autobyteus/tools/functional_tool.py +5 -4
  65. autobyteus/tools/image_downloader.py +2 -0
  66. autobyteus/tools/mcp/__init__.py +10 -7
  67. autobyteus/tools/mcp/call_handlers/__init__.py +0 -2
  68. autobyteus/tools/mcp/config_service.py +1 -6
  69. autobyteus/tools/mcp/factory.py +12 -26
  70. autobyteus/tools/mcp/server/__init__.py +16 -0
  71. autobyteus/tools/mcp/server/base_managed_mcp_server.py +139 -0
  72. autobyteus/tools/mcp/server/http_managed_mcp_server.py +29 -0
  73. autobyteus/tools/mcp/server/proxy.py +36 -0
  74. autobyteus/tools/mcp/server/stdio_managed_mcp_server.py +33 -0
  75. autobyteus/tools/mcp/server_instance_manager.py +93 -0
  76. autobyteus/tools/mcp/tool.py +28 -46
  77. autobyteus/tools/mcp/tool_registrar.py +179 -0
  78. autobyteus/tools/mcp/types.py +10 -21
  79. autobyteus/tools/pdf_downloader.py +2 -1
  80. autobyteus/tools/registry/tool_definition.py +20 -7
  81. autobyteus/tools/registry/tool_registry.py +75 -28
  82. autobyteus/tools/timer.py +2 -0
  83. autobyteus/tools/tool_category.py +14 -4
  84. autobyteus/tools/tool_meta.py +6 -1
  85. autobyteus/tools/tool_origin.py +10 -0
  86. autobyteus/workflow/agentic_workflow.py +93 -0
  87. autobyteus/{agent/workflow → workflow}/base_agentic_workflow.py +19 -27
  88. autobyteus/workflow/bootstrap_steps/__init__.py +20 -0
  89. autobyteus/workflow/bootstrap_steps/agent_tool_injection_step.py +34 -0
  90. autobyteus/workflow/bootstrap_steps/base_workflow_bootstrap_step.py +23 -0
  91. autobyteus/workflow/bootstrap_steps/coordinator_initialization_step.py +41 -0
  92. autobyteus/workflow/bootstrap_steps/coordinator_prompt_preparation_step.py +108 -0
  93. autobyteus/workflow/bootstrap_steps/workflow_bootstrapper.py +50 -0
  94. autobyteus/workflow/bootstrap_steps/workflow_runtime_queue_initialization_step.py +25 -0
  95. autobyteus/workflow/context/__init__.py +17 -0
  96. autobyteus/workflow/context/team_manager.py +147 -0
  97. autobyteus/workflow/context/workflow_config.py +30 -0
  98. autobyteus/workflow/context/workflow_context.py +61 -0
  99. autobyteus/workflow/context/workflow_node_config.py +76 -0
  100. autobyteus/workflow/context/workflow_runtime_state.py +53 -0
  101. autobyteus/workflow/events/__init__.py +29 -0
  102. autobyteus/workflow/events/workflow_event_dispatcher.py +39 -0
  103. autobyteus/workflow/events/workflow_events.py +53 -0
  104. autobyteus/workflow/events/workflow_input_event_queue_manager.py +21 -0
  105. autobyteus/workflow/exceptions.py +8 -0
  106. autobyteus/workflow/factory/__init__.py +9 -0
  107. autobyteus/workflow/factory/workflow_factory.py +99 -0
  108. autobyteus/workflow/handlers/__init__.py +19 -0
  109. autobyteus/workflow/handlers/base_workflow_event_handler.py +16 -0
  110. autobyteus/workflow/handlers/inter_agent_message_request_event_handler.py +61 -0
  111. autobyteus/workflow/handlers/lifecycle_workflow_event_handler.py +27 -0
  112. autobyteus/workflow/handlers/process_user_message_event_handler.py +46 -0
  113. autobyteus/workflow/handlers/tool_approval_workflow_event_handler.py +39 -0
  114. autobyteus/workflow/handlers/workflow_event_handler_registry.py +23 -0
  115. autobyteus/workflow/phases/__init__.py +11 -0
  116. autobyteus/workflow/phases/workflow_operational_phase.py +19 -0
  117. autobyteus/workflow/phases/workflow_phase_manager.py +48 -0
  118. autobyteus/workflow/runtime/__init__.py +13 -0
  119. autobyteus/workflow/runtime/workflow_runtime.py +82 -0
  120. autobyteus/workflow/runtime/workflow_worker.py +117 -0
  121. autobyteus/workflow/shutdown_steps/__init__.py +17 -0
  122. autobyteus/workflow/shutdown_steps/agent_team_shutdown_step.py +42 -0
  123. autobyteus/workflow/shutdown_steps/base_workflow_shutdown_step.py +16 -0
  124. autobyteus/workflow/shutdown_steps/bridge_cleanup_step.py +28 -0
  125. autobyteus/workflow/shutdown_steps/sub_workflow_shutdown_step.py +41 -0
  126. autobyteus/workflow/shutdown_steps/workflow_shutdown_orchestrator.py +35 -0
  127. autobyteus/workflow/streaming/__init__.py +26 -0
  128. autobyteus/workflow/streaming/agent_event_bridge.py +48 -0
  129. autobyteus/workflow/streaming/agent_event_multiplexer.py +70 -0
  130. autobyteus/workflow/streaming/workflow_event_bridge.py +50 -0
  131. autobyteus/workflow/streaming/workflow_event_notifier.py +83 -0
  132. autobyteus/workflow/streaming/workflow_event_stream.py +33 -0
  133. autobyteus/workflow/streaming/workflow_stream_event_payloads.py +28 -0
  134. autobyteus/workflow/streaming/workflow_stream_events.py +45 -0
  135. autobyteus/workflow/utils/__init__.py +9 -0
  136. autobyteus/workflow/utils/wait_for_idle.py +46 -0
  137. autobyteus/workflow/workflow_builder.py +151 -0
  138. {autobyteus-1.1.2.dist-info → autobyteus-1.1.4.dist-info}/METADATA +16 -13
  139. {autobyteus-1.1.2.dist-info → autobyteus-1.1.4.dist-info}/RECORD +156 -75
  140. {autobyteus-1.1.2.dist-info → autobyteus-1.1.4.dist-info}/top_level.txt +1 -0
  141. examples/__init__.py +1 -0
  142. examples/discover_phase_transitions.py +104 -0
  143. examples/run_browser_agent.py +260 -0
  144. examples/run_google_slides_agent.py +286 -0
  145. examples/run_mcp_browser_client.py +174 -0
  146. examples/run_mcp_google_slides_client.py +270 -0
  147. examples/run_mcp_list_tools.py +189 -0
  148. examples/run_poem_writer.py +274 -0
  149. examples/run_sqlite_agent.py +293 -0
  150. examples/workflow/__init__.py +1 -0
  151. examples/workflow/run_basic_research_workflow.py +189 -0
  152. examples/workflow/run_code_review_workflow.py +269 -0
  153. examples/workflow/run_debate_workflow.py +212 -0
  154. examples/workflow/run_workflow_with_tui.py +153 -0
  155. autobyteus/agent/context/agent_phase_manager.py +0 -264
  156. autobyteus/agent/context/phases.py +0 -49
  157. autobyteus/agent/group/__init__.py +0 -0
  158. autobyteus/agent/group/agent_group.py +0 -164
  159. autobyteus/agent/group/agent_group_context.py +0 -81
  160. autobyteus/agent/input_processor/content_prefixing_input_processor.py +0 -41
  161. autobyteus/agent/input_processor/metadata_appending_input_processor.py +0 -34
  162. autobyteus/agent/input_processor/passthrough_input_processor.py +0 -33
  163. autobyteus/agent/workflow/__init__.py +0 -11
  164. autobyteus/agent/workflow/agentic_workflow.py +0 -89
  165. autobyteus/tools/mcp/call_handlers/sse_handler.py +0 -22
  166. autobyteus/tools/mcp/registrar.py +0 -323
  167. autobyteus/workflow/simple_task.py +0 -98
  168. autobyteus/workflow/task.py +0 -147
  169. autobyteus/workflow/workflow.py +0 -49
  170. {autobyteus-1.1.2.dist-info → autobyteus-1.1.4.dist-info}/WHEEL +0 -0
  171. {autobyteus-1.1.2.dist-info → autobyteus-1.1.4.dist-info}/licenses/LICENSE +0 -0
@@ -2,149 +2,118 @@
2
2
  import logging
3
3
  from typing import TYPE_CHECKING, Any, Optional
4
4
 
5
- from autobyteus.agent.message.inter_agent_message import InterAgentMessage
6
5
  from autobyteus.tools.base_tool import BaseTool
7
- from autobyteus.agent.group.agent_group_context import AgentGroupContext
8
- # Updated imports for schema
6
+ from autobyteus.tools.tool_category import ToolCategory
9
7
  from autobyteus.tools.parameter_schema import ParameterSchema, ParameterDefinition, ParameterType
10
-
8
+ from autobyteus.tools.tool_config import ToolConfig
9
+ # This import is for type hinting only and avoids circular dependencies at runtime
11
10
  if TYPE_CHECKING:
12
11
  from autobyteus.agent.context import AgentContext
13
- from autobyteus.agent.agent import Agent
12
+ from autobyteus.workflow.context.team_manager import TeamManager
13
+ from autobyteus.workflow.events.workflow_events import InterAgentMessageRequestEvent
14
14
 
15
15
  logger = logging.getLogger(__name__)
16
16
 
17
17
  class SendMessageTo(BaseTool):
18
18
  """
19
- A tool for sending messages to other agents within the same AgentGroup.
20
- It utilizes AgentGroupContext injected into the calling agent's AgentContext
21
- to resolve recipient agents.
19
+ A tool for sending messages to other agents within the same workflow team.
20
+ This tool requires a TeamManager to be injected at runtime by the
21
+ workflow framework to enable communication with the parent orchestrator.
22
22
  """
23
- TOOL_NAME = "SendMessageTo" # Class attribute for the name
23
+ TOOL_NAME = "SendMessageTo"
24
+ CATEGORY = ToolCategory.AGENT_COMMUNICATION
25
+
26
+ def __init__(self, config: Optional[ToolConfig] = None):
27
+ """
28
+ Initializes the SendMessageTo tool. The TeamManager is injected separately
29
+ after instantiation.
30
+ """
31
+ super().__init__(config=config)
32
+ self._team_manager: Optional['TeamManager'] = None
33
+ logger.debug("SendMessageTo tool initialized. TeamManager is not yet injected.")
24
34
 
25
- def __init__(self):
26
- super().__init__()
27
- logger.debug(f"{self.get_name()} tool initialized.") # Use get_name()
35
+ def set_team_manager(self, team_manager: 'TeamManager'):
36
+ """Sets the TeamManager instance after the tool has been created."""
37
+ self._team_manager = team_manager
38
+ logger.debug(f"TeamManager was set on SendMessageTo instance post-creation.")
28
39
 
29
40
  @classmethod
30
- def get_name(cls) -> str: # Implemented as per BaseTool requirement
41
+ def get_name(cls) -> str:
31
42
  return cls.TOOL_NAME
32
43
 
33
44
  @classmethod
34
45
  def get_description(cls) -> str:
35
- return ("Sends a message to another agent within the same group. "
36
- "Can target by role or specific agent ID.")
46
+ return ("Sends a message to another agent within the same team, starting them if necessary. "
47
+ "You must specify the recipient by their unique name as provided in your team manifest.")
37
48
 
38
49
  @classmethod
39
50
  def get_argument_schema(cls) -> Optional[ParameterSchema]:
40
51
  schema = ParameterSchema()
41
52
  schema.add_parameter(ParameterDefinition(
42
- name="recipient_role_name",
53
+ name="recipient_name",
43
54
  param_type=ParameterType.STRING,
44
- description="The general role name of the recipient agent (e.g., 'worker', 'reviewer').",
55
+ description='The unique name of the recipient agent (e.g., "Researcher", "Writer_1"). This MUST match a name from your team manifest.',
45
56
  required=True
46
57
  ))
47
58
  schema.add_parameter(ParameterDefinition(
48
59
  name="content",
49
60
  param_type=ParameterType.STRING,
50
- description="The actual message text.",
61
+ description="The actual message content or task instruction.",
51
62
  required=True
52
63
  ))
53
64
  schema.add_parameter(ParameterDefinition(
54
65
  name="message_type",
55
- param_type=ParameterType.STRING, # Or ENUM if InterAgentMessageType values are known and fixed for schema
66
+ param_type=ParameterType.STRING,
56
67
  description="Type of the message (e.g., TASK_ASSIGNMENT, CLARIFICATION). Custom types allowed.",
57
68
  required=True
58
69
  ))
59
- schema.add_parameter(ParameterDefinition(
60
- name="recipient_agent_id",
61
- param_type=ParameterType.STRING,
62
- description='Optional. Specific ID of the recipient agent. If "unknown" or omitted, resolves by role.',
63
- required=False,
64
- default_value=None # Explicitly no default, truly optional
65
- ))
66
70
  return schema
67
71
 
68
- # tool_usage_xml() and tool_usage_json() are inherited from BaseTool
69
- # get_config_schema() for instantiation defaults to None
70
-
71
72
  async def _execute(self,
72
73
  context: 'AgentContext',
73
- recipient_role_name: str,
74
+ recipient_name: str,
74
75
  content: str,
75
- message_type: str,
76
- recipient_agent_id: Optional[str] = None) -> str: # Named parameters
76
+ message_type: str) -> str:
77
77
  """
78
- Sends a message to another agent in the group.
79
- Arguments are validated by BaseTool.execute().
78
+ Creates and dispatches a InterAgentMessageRequestEvent to the parent workflow
79
+ using the injected team_manager.
80
80
  """
81
- sender_agent_id = context.agent_id
82
- logger.info(f"Tool '{self.get_name()}': Sender '{sender_agent_id}' attempting to send message. "
83
- f"Recipient Role: '{recipient_role_name}', Recipient ID: '{recipient_agent_id}', Type: '{message_type}'.")
81
+ # Local import to break circular dependency at module load time.
82
+ from autobyteus.workflow.events.workflow_events import InterAgentMessageRequestEvent
84
83
 
85
- group_context_any = context.custom_data.get('agent_group_context')
86
-
87
- if not isinstance(group_context_any, AgentGroupContext):
88
- error_msg = f"Tool '{self.get_name()}' critical error: AgentGroupContext not found or invalid in AgentContext.custom_data for agent '{sender_agent_id}'. Cannot send message."
89
- logger.error(error_msg)
84
+ if self._team_manager is None:
85
+ error_msg = "Critical error: SendMessageTo tool is not configured for workflow communication. It can only be used within a managed AgenticWorkflow."
86
+ logger.error(f"Agent '{context.agent_id}': {error_msg}")
90
87
  return f"Error: {error_msg}"
91
-
92
- group_context: AgentGroupContext = group_context_any # Type cast after check
93
88
 
94
- target_agent: Optional['Agent'] = None
95
-
96
- # Use recipient_agent_id if provided and not explicitly "unknown" (case-insensitive)
97
- if recipient_agent_id and recipient_agent_id.lower() != "unknown":
98
- target_agent = group_context.get_agent(recipient_agent_id)
99
- if not target_agent:
100
- logger.warning(f"Tool '{self.get_name()}': Agent with ID '{recipient_agent_id}' not found in group '{group_context.group_id}'. "
101
- f"Attempting to find by role '{recipient_role_name}'.")
102
-
103
- if not target_agent:
104
- agents_with_role = group_context.get_agents_by_role(recipient_role_name)
105
- if not agents_with_role:
106
- error_msg = f"No agent found with role '{recipient_role_name}' (and specific ID '{recipient_agent_id}' if provided was not found) in group '{group_context.group_id}'."
107
- logger.error(f"Tool '{self.get_name()}': {error_msg}")
108
- return f"Error: {error_msg}"
89
+ # --- Input Validation ---
90
+ if not isinstance(recipient_name, str) or not recipient_name.strip():
91
+ error_msg = "Error: `recipient_name` must be a non-empty string."
92
+ logger.error(f"Tool '{self.get_name()}' validation failed: {error_msg}")
93
+ return error_msg
94
+ if not isinstance(content, str) or not content.strip():
95
+ error_msg = "Error: `content` must be a non-empty string."
96
+ logger.error(f"Tool '{self.get_name()}' validation failed: {error_msg}")
97
+ return error_msg
98
+ if not isinstance(message_type, str) or not message_type.strip():
99
+ error_msg = "Error: `message_type` must be a non-empty string."
100
+ logger.error(f"Tool '{self.get_name()}' validation failed: {error_msg}")
101
+ return error_msg
109
102
 
110
- if len(agents_with_role) > 1:
111
- logger.warning(f"Tool '{self.get_name()}': Multiple agents ({len(agents_with_role)}) found for role '{recipient_role_name}'. "
112
- f"Sending to the first one: {agents_with_role[0].agent_id}. "
113
- "Consider using specific recipient_agent_id for clarity.")
114
- target_agent = agents_with_role[0]
115
- # Update recipient_agent_id to the one resolved by role if it was initially None or "unknown"
116
- recipient_agent_id = target_agent.agent_id
117
-
118
-
119
- if not target_agent:
120
- error_msg = f"Could not resolve recipient agent with role '{recipient_role_name}' or ID '{recipient_agent_id}'." # recipient_agent_id would be updated here
121
- logger.error(f"Tool '{self.get_name()}': {error_msg}")
122
- return f"Error: {error_msg}"
123
-
124
- try:
125
- message_to_send = InterAgentMessage.create_with_dynamic_message_type(
126
- recipient_role_name=target_agent.context.config.role,
127
- recipient_agent_id=target_agent.agent_id, # Use the definitively resolved agent ID
128
- content=content,
129
- message_type=message_type,
130
- sender_agent_id=sender_agent_id
131
- )
132
-
133
- await target_agent.post_inter_agent_message(message_to_send)
134
- success_msg = (f"Message successfully sent from '{sender_agent_id}' to agent "
135
- f"'{target_agent.agent_id}' (Role: '{target_agent.context.config.role}').")
136
- logger.info(f"Tool '{self.get_name()}': {success_msg}")
137
- return success_msg
138
- except ValueError as ve:
139
- error_msg = f"Error creating message: {str(ve)}"
140
- logger.error(f"Tool '{self.get_name()}': {error_msg}", exc_info=True)
141
- return f"Error: {error_msg}"
142
- except Exception as e:
143
- error_msg = f"An unexpected error occurred while sending message: {str(e)}"
144
- logger.error(f"Tool '{self.get_name()}': {error_msg}", exc_info=True)
145
- return f"Error: {error_msg}"
103
+ sender_agent_id = context.agent_id
104
+ logger.info(f"Tool '{self.get_name()}': Agent '{sender_agent_id}' requesting to send message to '{recipient_name}'.")
105
+
106
+ # Create the event for the workflow to handle
107
+ event = InterAgentMessageRequestEvent(
108
+ sender_agent_id=sender_agent_id,
109
+ recipient_name=recipient_name,
110
+ content=content,
111
+ message_type=message_type
112
+ )
113
+
114
+ # Dispatch the event "up" to the workflow's event loop via the team manager
115
+ await self._team_manager.dispatch_inter_agent_message_request(event)
146
116
 
147
- # tool_usage_xml was defined directly here, this is now inherited from BaseTool
148
- # BaseTool.tool_usage_xml() will use get_argument_schema() to generate it.
149
- # If a custom XML format was desired that differs from the auto-generated one,
150
- # then this method could be overridden. For now, assume auto-generated is fine.
117
+ success_msg = f"Message dispatch for recipient '{recipient_name}' has been successfully requested."
118
+ logger.info(f"Tool '{self.get_name()}': {success_msg}")
119
+ return success_msg
@@ -3,7 +3,8 @@ import inspect
3
3
  import logging
4
4
  from typing import List, Optional
5
5
 
6
- from autobyteus.agent.context.agent_phase_manager import AgentPhaseManager
6
+ from autobyteus.agent.phases.manager import AgentPhaseManager
7
+
7
8
  from .transition_info import PhaseTransitionInfo
8
9
 
9
10
  logger = logging.getLogger(__name__)
@@ -15,6 +15,7 @@ from autobyteus.agent.events import (
15
15
  from autobyteus.agent.events import WorkerEventDispatcher
16
16
  from autobyteus.agent.runtime.agent_thread_pool_manager import AgentThreadPoolManager
17
17
  from autobyteus.agent.bootstrap_steps.agent_bootstrapper import AgentBootstrapper
18
+ from autobyteus.agent.shutdown_steps import AgentShutdownOrchestrator
18
19
 
19
20
  if TYPE_CHECKING:
20
21
  from autobyteus.agent.context import AgentContext
@@ -176,41 +177,22 @@ class AgentWorker:
176
177
  logger.error(f"Fatal error in AgentWorker '{agent_id}' async_run() loop: {e}", exc_info=True)
177
178
  finally:
178
179
  logger.info(f"AgentWorker '{agent_id}' async_run() loop has finished.")
180
+ # --- Shutdown sequence moved here, inside the original task's finally block ---
181
+ logger.info(f"AgentWorker '{agent_id}': Running shutdown sequence on worker loop.")
182
+ orchestrator = AgentShutdownOrchestrator()
183
+ cleanup_successful = await orchestrator.run(self.context)
179
184
 
180
- async def _signal_internal_stop(self):
181
- if self._async_stop_event and not self._async_stop_event.is_set():
182
- self._async_stop_event.set()
183
- if self.context.state.input_event_queues:
184
- await self.context.state.input_event_queues.enqueue_internal_system_event(AgentStoppedEvent())
185
+ if not cleanup_successful:
186
+ logger.critical(f"AgentWorker '{agent_id}': Shutdown resource cleanup failed. The agent may not have shut down cleanly.")
187
+ else:
188
+ logger.info(f"AgentWorker '{agent_id}': Shutdown resource cleanup completed successfully.")
189
+ logger.info(f"AgentWorker '{agent_id}': Shutdown sequence completed.")
185
190
 
186
- async def _shutdown_sequence(self):
187
- """
188
- The explicit, ordered shutdown sequence for the worker, executed on its own event loop.
189
- """
190
- agent_id = self.context.agent_id
191
- logger.info(f"AgentWorker '{agent_id}': Running shutdown sequence on worker loop.")
192
-
193
- # 1. Clean up resources like the LLM instance.
194
- if self.context.llm_instance and hasattr(self.context.llm_instance, 'cleanup'):
195
- logger.info(f"AgentWorker '{agent_id}': Running LLM instance cleanup.")
196
- try:
197
- cleanup_func = self.context.llm_instance.cleanup
198
- if asyncio.iscoroutinefunction(cleanup_func):
199
- await cleanup_func()
200
- else:
201
- cleanup_func()
202
- logger.info(f"AgentWorker '{agent_id}': LLM instance cleanup completed.")
203
- except Exception as e:
204
- logger.error(f"AgentWorker '{agent_id}': Error during LLM instance cleanup: {e}", exc_info=True)
205
-
206
- # 2. Signal the main event loop to stop.
207
- await self._signal_internal_stop()
208
- logger.info(f"AgentWorker '{agent_id}': Shutdown sequence completed.")
209
191
 
210
192
  async def stop(self, timeout: float = 10.0) -> None:
211
193
  """
212
- Gracefully stops the worker by scheduling a final shutdown sequence on its
213
- event loop, then waiting for the thread to terminate.
194
+ Gracefully stops the worker by signaling its event loop to terminate,
195
+ then waiting for the thread to complete its cleanup and exit.
214
196
  """
215
197
  if not self._is_active or self._stop_initiated:
216
198
  return
@@ -219,18 +201,27 @@ class AgentWorker:
219
201
  logger.info(f"AgentWorker '{agent_id}': Stop requested.")
220
202
  self._stop_initiated = True
221
203
 
222
- # Schedule the explicit shutdown sequence on the worker's loop.
204
+ # Schedule a coroutine on the worker's loop to set the stop event.
223
205
  if self.get_worker_loop():
224
- future = self.schedule_coroutine_on_worker_loop(self._shutdown_sequence)
206
+ def _coro_factory():
207
+ async def _signal_coro():
208
+ if self._async_stop_event and not self._async_stop_event.is_set():
209
+ self._async_stop_event.set()
210
+ if self.context.state.input_event_queues:
211
+ await self.context.state.input_event_queues.enqueue_internal_system_event(AgentStoppedEvent())
212
+ return _signal_coro()
213
+
214
+ future = self.schedule_coroutine_on_worker_loop(_coro_factory)
225
215
  try:
226
- # Wait for the cleanup and stop signal to be processed.
216
+ # Wait for the signal to be processed.
227
217
  future.result(timeout=max(1.0, timeout-1))
228
218
  except Exception as e:
229
- logger.error(f"AgentWorker '{agent_id}': Error during scheduled shutdown sequence: {e}", exc_info=True)
219
+ logger.error(f"AgentWorker '{agent_id}': Error signaling stop event: {e}", exc_info=True)
230
220
 
231
221
  # Wait for the main thread future to complete.
232
222
  if self._thread_future:
233
223
  try:
224
+ # FIX: Use asyncio.wait_for() to handle the timeout correctly.
234
225
  await asyncio.wait_for(asyncio.wrap_future(self._thread_future), timeout=timeout)
235
226
  logger.info(f"AgentWorker '{agent_id}': Worker thread has terminated.")
236
227
  except asyncio.TimeoutError:
@@ -0,0 +1,17 @@
1
+ # file: autobyteus/autobyteus/agent/shutdown_steps/__init__.py
2
+ """
3
+ Defines individual, self-contained steps for the agent shutdown process.
4
+ These steps are orchestrated by the AgentShutdownOrchestrator.
5
+ """
6
+
7
+ from .base_shutdown_step import BaseShutdownStep
8
+ from .llm_instance_cleanup_step import LLMInstanceCleanupStep
9
+ from .mcp_server_cleanup_step import McpServerCleanupStep
10
+ from .agent_shutdown_orchestrator import AgentShutdownOrchestrator
11
+
12
+ __all__ = [
13
+ "BaseShutdownStep",
14
+ "LLMInstanceCleanupStep",
15
+ "McpServerCleanupStep",
16
+ "AgentShutdownOrchestrator",
17
+ ]
@@ -0,0 +1,63 @@
1
+ # file: autobyteus/autobyteus/agent/shutdown_steps/agent_shutdown_orchestrator.py
2
+ import logging
3
+ from typing import TYPE_CHECKING, List, Optional
4
+
5
+ from .base_shutdown_step import BaseShutdownStep
6
+ from .llm_instance_cleanup_step import LLMInstanceCleanupStep
7
+ from .mcp_server_cleanup_step import McpServerCleanupStep
8
+
9
+ if TYPE_CHECKING:
10
+ from autobyteus.agent.context import AgentContext
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+ class AgentShutdownOrchestrator:
15
+ """
16
+ Orchestrates the agent's shutdown process by executing a sequence of
17
+ self-contained cleanup steps.
18
+ """
19
+ def __init__(self, steps: Optional[List[BaseShutdownStep]] = None):
20
+ """
21
+ Initializes the AgentShutdownOrchestrator.
22
+
23
+ Args:
24
+ steps: An optional list of shutdown steps to execute. If not provided,
25
+ a default sequence will be used.
26
+ """
27
+ if steps is None:
28
+ self.shutdown_steps: List[BaseShutdownStep] = [
29
+ LLMInstanceCleanupStep(),
30
+ McpServerCleanupStep(),
31
+ ]
32
+ logger.debug("AgentShutdownOrchestrator initialized with default steps.")
33
+ else:
34
+ self.shutdown_steps = steps
35
+ logger.debug(f"AgentShutdownOrchestrator initialized with {len(steps)} custom steps.")
36
+
37
+ async def run(self, context: 'AgentContext') -> bool:
38
+ """
39
+ Executes the configured sequence of shutdown steps.
40
+
41
+ Args:
42
+ context: The agent's context.
43
+
44
+ Returns:
45
+ True if all steps completed successfully, False otherwise.
46
+ """
47
+ agent_id = context.agent_id
48
+ logger.info(f"Agent '{agent_id}': AgentShutdownOrchestrator starting execution.")
49
+
50
+ for step_index, step_instance in enumerate(self.shutdown_steps):
51
+ step_name = step_instance.__class__.__name__
52
+ logger.debug(f"Agent '{agent_id}': Executing shutdown step {step_index + 1}/{len(self.shutdown_steps)}: {step_name}")
53
+
54
+ success = await step_instance.execute(context)
55
+
56
+ if not success:
57
+ error_message = f"Shutdown step {step_name} failed."
58
+ logger.error(f"Agent '{agent_id}': {error_message} Halting shutdown orchestration.")
59
+ # The step itself is responsible for detailed error logging.
60
+ return False
61
+
62
+ logger.info(f"Agent '{agent_id}': All shutdown steps completed successfully.")
63
+ return True
@@ -0,0 +1,33 @@
1
+ # file: autobyteus/autobyteus/agent/shutdown_steps/base_shutdown_step.py
2
+ import logging
3
+ from abc import ABC, abstractmethod
4
+ from typing import TYPE_CHECKING
5
+
6
+ if TYPE_CHECKING:
7
+ from autobyteus.agent.context import AgentContext
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+ class BaseShutdownStep(ABC):
12
+ """
13
+ Abstract base class for individual steps in the agent shutdown process.
14
+ Each step is responsible for a specific part of the cleanup and
15
+ for reporting its success or failure.
16
+ """
17
+
18
+ @abstractmethod
19
+ async def execute(self, context: 'AgentContext') -> bool:
20
+ """
21
+ Executes the shutdown step.
22
+
23
+ Args:
24
+ context: The agent's context, providing access to state and resources.
25
+
26
+ Returns:
27
+ True if the step completed successfully, False otherwise.
28
+ If False, the step is expected to have handled its own detailed logging.
29
+ """
30
+ raise NotImplementedError("Subclasses must implement the 'execute' method.")
31
+
32
+ def __repr__(self) -> str:
33
+ return f"<{self.__class__.__name__}>"
@@ -0,0 +1,45 @@
1
+ # file: autobyteus/autobyteus/agent/shutdown_steps/llm_instance_cleanup_step.py
2
+ import asyncio
3
+ import logging
4
+ from typing import TYPE_CHECKING
5
+
6
+ from .base_shutdown_step import BaseShutdownStep
7
+
8
+ if TYPE_CHECKING:
9
+ from autobyteus.agent.context import AgentContext
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+ class LLMInstanceCleanupStep(BaseShutdownStep):
14
+ """
15
+ Shutdown step for cleaning up the agent's LLM instance.
16
+ """
17
+ def __init__(self):
18
+ logger.debug("LLMInstanceCleanupStep initialized.")
19
+
20
+ async def execute(self, context: 'AgentContext') -> bool:
21
+ agent_id = context.agent_id
22
+ logger.info(f"Agent '{agent_id}': Executing LLMInstanceCleanupStep.")
23
+
24
+ llm_instance = context.llm_instance
25
+ if not llm_instance:
26
+ logger.debug(f"Agent '{agent_id}': No LLM instance found in context. Skipping cleanup.")
27
+ return True
28
+
29
+ if hasattr(llm_instance, 'cleanup') and callable(getattr(llm_instance, 'cleanup')):
30
+ try:
31
+ logger.info(f"Agent '{agent_id}': Running LLM instance cleanup for '{llm_instance.__class__.__name__}'.")
32
+ cleanup_func = llm_instance.cleanup
33
+ if asyncio.iscoroutinefunction(cleanup_func):
34
+ await cleanup_func()
35
+ else:
36
+ cleanup_func()
37
+ logger.info(f"Agent '{agent_id}': LLM instance cleanup completed successfully.")
38
+ return True
39
+ except Exception as e:
40
+ error_message = f"Agent '{agent_id}': Error during LLM instance cleanup: {e}"
41
+ logger.error(error_message, exc_info=True)
42
+ return False
43
+ else:
44
+ logger.debug(f"Agent '{agent_id}': LLM instance of type '{llm_instance.__class__.__name__}' does not have a 'cleanup' method. Skipping.")
45
+ return True
@@ -0,0 +1,32 @@
1
+ # file: autobyteus/autobyteus/agent/shutdown_steps/mcp_server_cleanup_step.py
2
+ import logging
3
+ from typing import TYPE_CHECKING
4
+
5
+ from .base_shutdown_step import BaseShutdownStep
6
+ from autobyteus.tools.mcp.server_instance_manager import McpServerInstanceManager
7
+
8
+ if TYPE_CHECKING:
9
+ from autobyteus.agent.context import AgentContext
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+ class McpServerCleanupStep(BaseShutdownStep):
14
+ """
15
+ Shutdown step for cleaning up all MCP server instances associated with an agent.
16
+ """
17
+ def __init__(self):
18
+ self._instance_manager = McpServerInstanceManager()
19
+ logger.debug("McpServerCleanupStep initialized.")
20
+
21
+ async def execute(self, context: 'AgentContext') -> bool:
22
+ agent_id = context.agent_id
23
+ logger.info(f"Agent '{agent_id}': Executing McpServerCleanupStep.")
24
+
25
+ try:
26
+ await self._instance_manager.cleanup_mcp_server_instances_for_agent(agent_id)
27
+ logger.info(f"Agent '{agent_id}': MCP server instance cleanup completed successfully.")
28
+ return True
29
+ except Exception as e:
30
+ error_message = f"Agent '{agent_id}': Critical failure during McpServerCleanupStep: {e}"
31
+ logger.error(error_message, exc_info=True)
32
+ return False
@@ -0,0 +1,9 @@
1
+ # file: autobyteus/autobyteus/agent/tool_execution_result_processor/__init__.py
2
+ """
3
+ Components for processing tool execution results before they are sent to the LLM.
4
+ """
5
+ from .base_processor import BaseToolExecutionResultProcessor
6
+
7
+ __all__ = [
8
+ "BaseToolExecutionResultProcessor",
9
+ ]
@@ -0,0 +1,46 @@
1
+ # file: autobyteus/autobyteus/agent/tool_execution_result_processor/base_processor.py
2
+ import logging
3
+ from abc import ABC, abstractmethod
4
+ from typing import TYPE_CHECKING
5
+
6
+ from .processor_meta import ToolExecutionResultProcessorMeta
7
+
8
+ if TYPE_CHECKING:
9
+ from autobyteus.agent.context import AgentContext
10
+ from autobyteus.agent.events import ToolResultEvent
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+ class BaseToolExecutionResultProcessor(ABC, metaclass=ToolExecutionResultProcessorMeta):
15
+ """
16
+ Abstract base class for processors that can modify a tool's execution result.
17
+ These processors are applied after a tool runs but before its result is formatted
18
+ for the LLM.
19
+ """
20
+
21
+ @classmethod
22
+ def get_name(cls) -> str:
23
+ """
24
+ Returns the unique registration name for this processor.
25
+ Defaults to the class name.
26
+ """
27
+ return cls.__name__
28
+
29
+ @abstractmethod
30
+ async def process(self,
31
+ event: 'ToolResultEvent',
32
+ context: 'AgentContext') -> 'ToolResultEvent':
33
+ """
34
+ Processes the given ToolResultEvent.
35
+
36
+ Args:
37
+ event: The ToolResultEvent containing the tool's output or error.
38
+ context: The agent's context, providing access to config and state.
39
+
40
+ Returns:
41
+ The processed (potentially modified) ToolResultEvent.
42
+ """
43
+ raise NotImplementedError("Subclasses must implement the 'process' method.")
44
+
45
+ def __repr__(self) -> str:
46
+ return f"<{self.__class__.__name__}>"
@@ -0,0 +1,36 @@
1
+ # file: autobyteus/autobyteus/agent/tool_execution_result_processor/processor_definition.py
2
+ import logging
3
+ from typing import Type, TYPE_CHECKING
4
+
5
+ if TYPE_CHECKING:
6
+ from .base_processor import BaseToolExecutionResultProcessor
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+ class ToolExecutionResultProcessorDefinition:
11
+ """
12
+ Represents the definition of a tool execution result processor.
13
+ Contains its registered name and the class itself.
14
+ """
15
+ def __init__(self, name: str, processor_class: Type['BaseToolExecutionResultProcessor']):
16
+ """
17
+ Initializes the ToolExecutionResultProcessorDefinition.
18
+
19
+ Args:
20
+ name: The unique registered name of the processor.
21
+ processor_class: The class of the tool execution result processor.
22
+
23
+ Raises:
24
+ ValueError: If name is empty or processor_class is not a type.
25
+ """
26
+ if not name or not isinstance(name, str):
27
+ raise ValueError("Tool Execution Result Processor name must be a non-empty string.")
28
+ if not isinstance(processor_class, type):
29
+ raise ValueError("processor_class must be a class type.")
30
+
31
+ self.name: str = name
32
+ self.processor_class: Type['BaseToolExecutionResultProcessor'] = processor_class
33
+ logger.debug(f"ToolExecutionResultProcessorDefinition created: name='{name}', class='{processor_class.__name__}'.")
34
+
35
+ def __repr__(self) -> str:
36
+ return f"<ToolExecutionResultProcessorDefinition name='{self.name}', class='{self.processor_class.__name__}'>"
@@ -0,0 +1,36 @@
1
+ # file: autobyteus/autobyteus/agent/tool_execution_result_processor/processor_meta.py
2
+ import logging
3
+ from abc import ABCMeta
4
+
5
+ from .processor_registry import default_tool_execution_result_processor_registry
6
+ from .processor_definition import ToolExecutionResultProcessorDefinition
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+ class ToolExecutionResultProcessorMeta(ABCMeta):
11
+ """
12
+ Metaclass for BaseToolExecutionResultProcessor that automatically registers
13
+ concrete processor subclasses with the default registry.
14
+ """
15
+ def __init__(cls, name, bases, dct):
16
+ super().__init__(name, bases, dct)
17
+
18
+ if name == 'BaseToolExecutionResultProcessor' or getattr(cls, "__abstractmethods__", None):
19
+ logger.debug(f"Skipping registration for abstract tool execution result processor class: {name}")
20
+ return
21
+
22
+ try:
23
+ processor_name = cls.get_name()
24
+
25
+ if not processor_name or not isinstance(processor_name, str):
26
+ logger.error(f"Tool execution result processor class {name} must return a valid string from get_name(). Skipping.")
27
+ return
28
+
29
+ definition = ToolExecutionResultProcessorDefinition(name=processor_name, processor_class=cls)
30
+ default_tool_execution_result_processor_registry.register_processor(definition)
31
+ logger.info(f"Auto-registered tool execution result processor: '{processor_name}' from class {name}.")
32
+
33
+ except AttributeError as e:
34
+ logger.error(f"Tool execution result processor class {name} is missing 'get_name' method ({e}). Skipping registration.")
35
+ except Exception as e:
36
+ logger.error(f"Failed to auto-register tool execution result processor class {name}: {e}", exc_info=True)