autobyteus 1.1.0__py3-none-any.whl → 1.1.1__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 (95) hide show
  1. autobyteus/agent/bootstrap_steps/agent_bootstrapper.py +1 -1
  2. autobyteus/agent/bootstrap_steps/agent_runtime_queue_initialization_step.py +1 -1
  3. autobyteus/agent/bootstrap_steps/base_bootstrap_step.py +1 -1
  4. autobyteus/agent/bootstrap_steps/system_prompt_processing_step.py +1 -1
  5. autobyteus/agent/bootstrap_steps/workspace_context_initialization_step.py +1 -1
  6. autobyteus/agent/context/__init__.py +0 -5
  7. autobyteus/agent/context/agent_config.py +6 -2
  8. autobyteus/agent/context/agent_context.py +2 -5
  9. autobyteus/agent/context/agent_phase_manager.py +105 -5
  10. autobyteus/agent/context/agent_runtime_state.py +2 -2
  11. autobyteus/agent/context/phases.py +2 -0
  12. autobyteus/agent/events/__init__.py +0 -11
  13. autobyteus/agent/events/agent_events.py +0 -37
  14. autobyteus/agent/events/notifiers.py +25 -7
  15. autobyteus/agent/events/worker_event_dispatcher.py +1 -1
  16. autobyteus/agent/factory/agent_factory.py +6 -2
  17. autobyteus/agent/group/agent_group.py +16 -7
  18. autobyteus/agent/handlers/approved_tool_invocation_event_handler.py +28 -14
  19. autobyteus/agent/handlers/lifecycle_event_logger.py +1 -1
  20. autobyteus/agent/handlers/llm_complete_response_received_event_handler.py +4 -2
  21. autobyteus/agent/handlers/tool_invocation_request_event_handler.py +40 -15
  22. autobyteus/agent/handlers/tool_result_event_handler.py +12 -7
  23. autobyteus/agent/hooks/__init__.py +7 -0
  24. autobyteus/agent/hooks/base_phase_hook.py +11 -2
  25. autobyteus/agent/hooks/hook_definition.py +36 -0
  26. autobyteus/agent/hooks/hook_meta.py +37 -0
  27. autobyteus/agent/hooks/hook_registry.py +118 -0
  28. autobyteus/agent/input_processor/base_user_input_processor.py +6 -3
  29. autobyteus/agent/input_processor/passthrough_input_processor.py +2 -1
  30. autobyteus/agent/input_processor/processor_meta.py +1 -1
  31. autobyteus/agent/input_processor/processor_registry.py +19 -0
  32. autobyteus/agent/llm_response_processor/base_processor.py +6 -3
  33. autobyteus/agent/llm_response_processor/processor_meta.py +1 -1
  34. autobyteus/agent/llm_response_processor/processor_registry.py +19 -0
  35. autobyteus/agent/llm_response_processor/provider_aware_tool_usage_processor.py +2 -1
  36. autobyteus/agent/message/context_file_type.py +2 -3
  37. autobyteus/agent/phases/__init__.py +18 -0
  38. autobyteus/agent/phases/discover.py +52 -0
  39. autobyteus/agent/phases/manager.py +265 -0
  40. autobyteus/agent/phases/phase_enum.py +49 -0
  41. autobyteus/agent/phases/transition_decorator.py +40 -0
  42. autobyteus/agent/phases/transition_info.py +33 -0
  43. autobyteus/agent/remote_agent.py +1 -1
  44. autobyteus/agent/runtime/agent_runtime.py +4 -6
  45. autobyteus/agent/runtime/agent_worker.py +1 -1
  46. autobyteus/agent/streaming/agent_event_stream.py +58 -5
  47. autobyteus/agent/streaming/stream_event_payloads.py +24 -13
  48. autobyteus/agent/streaming/stream_events.py +14 -11
  49. autobyteus/agent/system_prompt_processor/base_processor.py +6 -3
  50. autobyteus/agent/system_prompt_processor/processor_meta.py +1 -1
  51. autobyteus/agent/system_prompt_processor/tool_manifest_injector_processor.py +45 -31
  52. autobyteus/agent/tool_invocation.py +29 -3
  53. autobyteus/agent/utils/wait_for_idle.py +1 -1
  54. autobyteus/agent/workspace/__init__.py +2 -0
  55. autobyteus/agent/workspace/base_workspace.py +33 -11
  56. autobyteus/agent/workspace/workspace_config.py +160 -0
  57. autobyteus/agent/workspace/workspace_definition.py +36 -0
  58. autobyteus/agent/workspace/workspace_meta.py +37 -0
  59. autobyteus/agent/workspace/workspace_registry.py +72 -0
  60. autobyteus/cli/__init__.py +4 -3
  61. autobyteus/cli/agent_cli.py +25 -207
  62. autobyteus/cli/cli_display.py +205 -0
  63. autobyteus/events/event_manager.py +2 -1
  64. autobyteus/events/event_types.py +3 -1
  65. autobyteus/llm/api/autobyteus_llm.py +2 -12
  66. autobyteus/llm/api/deepseek_llm.py +5 -5
  67. autobyteus/llm/api/grok_llm.py +5 -5
  68. autobyteus/llm/api/mistral_llm.py +4 -4
  69. autobyteus/llm/api/ollama_llm.py +2 -2
  70. autobyteus/llm/extensions/token_usage_tracking_extension.py +11 -1
  71. autobyteus/llm/llm_factory.py +106 -42
  72. autobyteus/llm/models.py +25 -29
  73. autobyteus/llm/ollama_provider.py +6 -2
  74. autobyteus/llm/ollama_provider_resolver.py +44 -0
  75. autobyteus/tools/__init__.py +2 -0
  76. autobyteus/tools/base_tool.py +7 -1
  77. autobyteus/tools/functional_tool.py +20 -5
  78. autobyteus/tools/mcp/call_handlers/stdio_handler.py +15 -1
  79. autobyteus/tools/mcp/config_service.py +106 -127
  80. autobyteus/tools/mcp/registrar.py +247 -59
  81. autobyteus/tools/mcp/types.py +5 -3
  82. autobyteus/tools/registry/tool_definition.py +8 -1
  83. autobyteus/tools/registry/tool_registry.py +18 -0
  84. autobyteus/tools/tool_category.py +11 -0
  85. autobyteus/tools/tool_meta.py +3 -1
  86. autobyteus/tools/tool_state.py +20 -0
  87. autobyteus/tools/usage/parsers/default_json_tool_usage_parser.py +3 -3
  88. autobyteus/tools/usage/parsers/default_xml_tool_usage_parser.py +2 -1
  89. autobyteus/tools/usage/parsers/gemini_json_tool_usage_parser.py +17 -19
  90. autobyteus/tools/usage/parsers/openai_json_tool_usage_parser.py +126 -77
  91. {autobyteus-1.1.0.dist-info → autobyteus-1.1.1.dist-info}/METADATA +11 -11
  92. {autobyteus-1.1.0.dist-info → autobyteus-1.1.1.dist-info}/RECORD +95 -78
  93. {autobyteus-1.1.0.dist-info → autobyteus-1.1.1.dist-info}/WHEEL +0 -0
  94. {autobyteus-1.1.0.dist-info → autobyteus-1.1.1.dist-info}/licenses/LICENSE +0 -0
  95. {autobyteus-1.1.0.dist-info → autobyteus-1.1.1.dist-info}/top_level.txt +0 -0
@@ -10,7 +10,7 @@ from autobyteus.agent.events import AgentReadyEvent
10
10
 
11
11
  if TYPE_CHECKING:
12
12
  from autobyteus.agent.context import AgentContext
13
- from autobyteus.agent.context.agent_phase_manager import AgentPhaseManager
13
+ from autobyteus.agent.phases import AgentPhaseManager
14
14
 
15
15
  logger = logging.getLogger(__name__)
16
16
 
@@ -8,7 +8,7 @@ from autobyteus.agent.events import AgentErrorEvent, AgentInputEventQueueManager
8
8
 
9
9
  if TYPE_CHECKING:
10
10
  from autobyteus.agent.context import AgentContext
11
- from autobyteus.agent.context.agent_phase_manager import AgentPhaseManager
11
+ from autobyteus.agent.phases import AgentPhaseManager
12
12
 
13
13
  logger = logging.getLogger(__name__)
14
14
 
@@ -5,7 +5,7 @@ from typing import TYPE_CHECKING
5
5
 
6
6
  if TYPE_CHECKING:
7
7
  from autobyteus.agent.context import AgentContext
8
- from autobyteus.agent.context.agent_phase_manager import AgentPhaseManager
8
+ from autobyteus.agent.phases import AgentPhaseManager
9
9
 
10
10
  logger = logging.getLogger(__name__)
11
11
 
@@ -8,7 +8,7 @@ from autobyteus.agent.system_prompt_processor.base_processor import BaseSystemPr
8
8
 
9
9
  if TYPE_CHECKING:
10
10
  from autobyteus.agent.context import AgentContext
11
- from autobyteus.agent.context.agent_phase_manager import AgentPhaseManager
11
+ from autobyteus.agent.phases import AgentPhaseManager
12
12
 
13
13
  logger = logging.getLogger(__name__)
14
14
 
@@ -6,7 +6,7 @@ from .base_bootstrap_step import BaseBootstrapStep
6
6
 
7
7
  if TYPE_CHECKING:
8
8
  from autobyteus.agent.context import AgentContext
9
- from autobyteus.agent.context.agent_phase_manager import AgentPhaseManager
9
+ from autobyteus.agent.phases import AgentPhaseManager
10
10
 
11
11
  logger = logging.getLogger(__name__)
12
12
 
@@ -5,14 +5,9 @@ Components related to the agent's runtime context, state, config, and status man
5
5
  from .agent_config import AgentConfig
6
6
  from .agent_runtime_state import AgentRuntimeState
7
7
  from .agent_context import AgentContext
8
- from .agent_phase_manager import AgentPhaseManager
9
- from .phases import AgentOperationalPhase
10
-
11
8
 
12
9
  __all__ = [
13
10
  "AgentContext",
14
11
  "AgentConfig",
15
12
  "AgentRuntimeState",
16
- "AgentPhaseManager",
17
- "AgentOperationalPhase",
18
13
  ]
@@ -1,6 +1,6 @@
1
1
  # file: autobyteus/autobyteus/agent/context/agent_config.py
2
2
  import logging
3
- from typing import List, Optional, Union, Tuple, TYPE_CHECKING
3
+ from typing import List, Optional, Union, Tuple, TYPE_CHECKING, Dict, Any
4
4
 
5
5
  # Correctly import the new master processor and the base class
6
6
  from autobyteus.agent.system_prompt_processor import ToolManifestInjectorProcessor, BaseSystemPromptProcessor
@@ -40,7 +40,8 @@ class AgentConfig:
40
40
  llm_response_processors: Optional[List['BaseLLMResponseProcessor']] = None,
41
41
  system_prompt_processors: Optional[List['BaseSystemPromptProcessor']] = None,
42
42
  workspace: Optional['BaseAgentWorkspace'] = None,
43
- phase_hooks: Optional[List['BasePhaseHook']] = None):
43
+ phase_hooks: Optional[List['BasePhaseHook']] = None,
44
+ initial_custom_data: Optional[Dict[str, Any]] = None):
44
45
  """
45
46
  Initializes the AgentConfig.
46
47
 
@@ -59,6 +60,8 @@ class AgentConfig:
59
60
  system_prompt_processors: A list of system prompt processor instances.
60
61
  workspace: An optional pre-initialized workspace instance for the agent.
61
62
  phase_hooks: An optional list of phase transition hook instances.
63
+ initial_custom_data: An optional dictionary of data to pre-populate
64
+ the agent's runtime state `custom_data`.
62
65
  """
63
66
  self.name = name
64
67
  self.role = role
@@ -73,6 +76,7 @@ class AgentConfig:
73
76
  self.llm_response_processors = llm_response_processors if llm_response_processors is not None else list(self.DEFAULT_LLM_RESPONSE_PROCESSORS)
74
77
  self.system_prompt_processors = system_prompt_processors if system_prompt_processors is not None else list(self.DEFAULT_SYSTEM_PROMPT_PROCESSORS)
75
78
  self.phase_hooks = phase_hooks or []
79
+ self.initial_custom_data = initial_custom_data
76
80
 
77
81
  logger.debug(f"AgentConfig created for name '{self.name}', role '{self.role}'.")
78
82
 
@@ -2,7 +2,7 @@
2
2
  import logging
3
3
  from typing import TYPE_CHECKING, List, Dict, Any, Optional
4
4
 
5
- from .phases import AgentOperationalPhase
5
+ from autobyteus.agent.phases import AgentOperationalPhase
6
6
 
7
7
  if TYPE_CHECKING:
8
8
  from .agent_config import AgentConfig
@@ -13,7 +13,7 @@ if TYPE_CHECKING:
13
13
  from autobyteus.agent.tool_invocation import ToolInvocation
14
14
  # LLMConfig no longer needed here
15
15
  from autobyteus.agent.workspace.base_workspace import BaseAgentWorkspace
16
- from autobyteus.agent.context.agent_phase_manager import AgentPhaseManager
16
+ from autobyteus.agent.phases import AgentPhaseManager
17
17
 
18
18
  logger = logging.getLogger(__name__)
19
19
 
@@ -48,9 +48,6 @@ class AgentContext:
48
48
  @property
49
49
  def auto_execute_tools(self) -> bool:
50
50
  return self.config.auto_execute_tools
51
-
52
- # llm_model_name property removed
53
- # custom_llm_config property removed
54
51
 
55
52
  @property
56
53
  def llm_instance(self) -> Optional['BaseLLM']:
@@ -3,7 +3,7 @@ import asyncio
3
3
  import logging
4
4
  from typing import TYPE_CHECKING, Optional, Dict, Any
5
5
 
6
- from .phases import AgentOperationalPhase
6
+ from autobyteus.agent.phases import AgentOperationalPhase, phase_transition
7
7
 
8
8
  if TYPE_CHECKING:
9
9
  from autobyteus.agent.context.agent_context import AgentContext
@@ -84,6 +84,11 @@ class AgentPhaseManager:
84
84
  else:
85
85
  logger.error(f"AgentPhaseManager for '{self.context.agent_id}': Notifier method '{notify_method_name}' not found or not callable on {type(self.notifier).__name__}.")
86
86
 
87
+ @phase_transition(
88
+ source_phases=[AgentOperationalPhase.SHUTDOWN_COMPLETE, AgentOperationalPhase.ERROR],
89
+ target_phase=AgentOperationalPhase.UNINITIALIZED,
90
+ description="Triggered when the agent runtime is started or restarted after being in a terminal state."
91
+ )
87
92
  async def notify_runtime_starting_and_uninitialized(self) -> None:
88
93
  if self.context.current_phase == AgentOperationalPhase.UNINITIALIZED:
89
94
  await self._transition_phase(AgentOperationalPhase.UNINITIALIZED, "notify_phase_uninitialized_entered")
@@ -92,9 +97,19 @@ class AgentPhaseManager:
92
97
  else:
93
98
  logger.warning(f"Agent '{self.context.agent_id}' notify_runtime_starting_and_uninitialized called in unexpected phase: {self.context.current_phase.value}")
94
99
 
100
+ @phase_transition(
101
+ source_phases=[AgentOperationalPhase.UNINITIALIZED],
102
+ target_phase=AgentOperationalPhase.BOOTSTRAPPING,
103
+ description="Occurs when the agent's internal bootstrapping process begins."
104
+ )
95
105
  async def notify_bootstrapping_started(self) -> None:
96
106
  await self._transition_phase(AgentOperationalPhase.BOOTSTRAPPING, "notify_phase_bootstrapping_started")
97
107
 
108
+ @phase_transition(
109
+ source_phases=[AgentOperationalPhase.BOOTSTRAPPING],
110
+ target_phase=AgentOperationalPhase.IDLE,
111
+ description="Occurs when the agent successfully completes bootstrapping and is ready for input."
112
+ )
98
113
  async def notify_initialization_complete(self) -> None:
99
114
  if self.context.current_phase.is_initializing() or self.context.current_phase == AgentOperationalPhase.UNINITIALIZED:
100
115
  # This will now be a BOOTSTRAPPING -> IDLE transition
@@ -102,8 +117,17 @@ class AgentPhaseManager:
102
117
  else:
103
118
  logger.warning(f"Agent '{self.context.agent_id}' notify_initialization_complete called in unexpected phase: {self.context.current_phase.value}")
104
119
 
120
+ @phase_transition(
121
+ source_phases=[
122
+ AgentOperationalPhase.IDLE, AgentOperationalPhase.ANALYZING_LLM_RESPONSE,
123
+ AgentOperationalPhase.PROCESSING_TOOL_RESULT, AgentOperationalPhase.EXECUTING_TOOL,
124
+ AgentOperationalPhase.TOOL_DENIED
125
+ ],
126
+ target_phase=AgentOperationalPhase.PROCESSING_USER_INPUT,
127
+ description="Fires when the agent begins processing a new user message or inter-agent message."
128
+ )
105
129
  async def notify_processing_input_started(self, trigger_info: Optional[str] = None) -> None:
106
- if self.context.current_phase in [AgentOperationalPhase.IDLE, AgentOperationalPhase.ANALYZING_LLM_RESPONSE, AgentOperationalPhase.PROCESSING_TOOL_RESULT, AgentOperationalPhase.EXECUTING_TOOL]:
130
+ if self.context.current_phase in [AgentOperationalPhase.IDLE, AgentOperationalPhase.ANALYZING_LLM_RESPONSE, AgentOperationalPhase.PROCESSING_TOOL_RESULT, AgentOperationalPhase.EXECUTING_TOOL, AgentOperationalPhase.TOOL_DENIED]:
107
131
  data = {"trigger_info": trigger_info} if trigger_info else {}
108
132
  await self._transition_phase(AgentOperationalPhase.PROCESSING_USER_INPUT, "notify_phase_processing_user_input_started", additional_data=data)
109
133
  elif self.context.current_phase == AgentOperationalPhase.PROCESSING_USER_INPUT:
@@ -111,30 +135,79 @@ class AgentPhaseManager:
111
135
  else:
112
136
  logger.warning(f"Agent '{self.context.agent_id}' notify_processing_input_started called in unexpected phase: {self.context.current_phase.value}")
113
137
 
138
+ @phase_transition(
139
+ source_phases=[AgentOperationalPhase.PROCESSING_USER_INPUT, AgentOperationalPhase.PROCESSING_TOOL_RESULT],
140
+ target_phase=AgentOperationalPhase.AWAITING_LLM_RESPONSE,
141
+ description="Occurs just before the agent makes a call to the LLM."
142
+ )
114
143
  async def notify_awaiting_llm_response(self) -> None:
115
144
  await self._transition_phase(AgentOperationalPhase.AWAITING_LLM_RESPONSE, "notify_phase_awaiting_llm_response_started")
116
145
 
146
+ @phase_transition(
147
+ source_phases=[AgentOperationalPhase.AWAITING_LLM_RESPONSE],
148
+ target_phase=AgentOperationalPhase.ANALYZING_LLM_RESPONSE,
149
+ description="Occurs after the agent has received a complete response from the LLM and begins to analyze it."
150
+ )
117
151
  async def notify_analyzing_llm_response(self) -> None:
118
152
  await self._transition_phase(AgentOperationalPhase.ANALYZING_LLM_RESPONSE, "notify_phase_analyzing_llm_response_started")
119
153
 
154
+ @phase_transition(
155
+ source_phases=[AgentOperationalPhase.ANALYZING_LLM_RESPONSE],
156
+ target_phase=AgentOperationalPhase.AWAITING_TOOL_APPROVAL,
157
+ description="Occurs if the agent proposes a tool use that requires manual user approval."
158
+ )
120
159
  async def notify_tool_execution_pending_approval(self, tool_invocation: 'ToolInvocation') -> None:
121
- # The notifier's notify_phase_awaiting_tool_approval_started method no longer takes tool_details.
122
- # The phase event itself is the signal. Tool data comes via queue.
123
160
  await self._transition_phase(AgentOperationalPhase.AWAITING_TOOL_APPROVAL, "notify_phase_awaiting_tool_approval_started")
124
161
 
162
+ @phase_transition(
163
+ source_phases=[AgentOperationalPhase.AWAITING_TOOL_APPROVAL],
164
+ target_phase=AgentOperationalPhase.EXECUTING_TOOL,
165
+ description="Occurs after a pending tool use has been approved and is about to be executed."
166
+ )
125
167
  async def notify_tool_execution_resumed_after_approval(self, approved: bool, tool_name: Optional[str]) -> None:
126
168
  if approved and tool_name:
127
169
  await self._transition_phase(AgentOperationalPhase.EXECUTING_TOOL, "notify_phase_executing_tool_started", additional_data={"tool_name": tool_name})
128
170
  else:
129
171
  logger.info(f"Agent '{self.context.agent_id}' tool execution denied for '{tool_name}'. Transitioning to allow LLM to process denial.")
130
- await self._transition_phase(AgentOperationalPhase.ANALYZING_LLM_RESPONSE, "notify_phase_analyzing_llm_response_started", additional_data={"denial_for_tool": tool_name})
172
+ await self.notify_tool_denied(tool_name)
131
173
 
174
+ @phase_transition(
175
+ source_phases=[AgentOperationalPhase.AWAITING_TOOL_APPROVAL],
176
+ target_phase=AgentOperationalPhase.TOOL_DENIED,
177
+ description="Occurs after a pending tool use has been denied by the user."
178
+ )
179
+ async def notify_tool_denied(self, tool_name: Optional[str]) -> None:
180
+ """Notifies that a tool execution has been denied."""
181
+ await self._transition_phase(
182
+ AgentOperationalPhase.TOOL_DENIED,
183
+ "notify_phase_tool_denied_started",
184
+ additional_data={"tool_name": tool_name, "denial_for_tool": tool_name}
185
+ )
186
+
187
+ @phase_transition(
188
+ source_phases=[AgentOperationalPhase.ANALYZING_LLM_RESPONSE],
189
+ target_phase=AgentOperationalPhase.EXECUTING_TOOL,
190
+ description="Occurs when an agent with auto-approval executes a tool."
191
+ )
132
192
  async def notify_tool_execution_started(self, tool_name: str) -> None:
133
193
  await self._transition_phase(AgentOperationalPhase.EXECUTING_TOOL, "notify_phase_executing_tool_started", additional_data={"tool_name": tool_name})
134
194
 
195
+ @phase_transition(
196
+ source_phases=[AgentOperationalPhase.EXECUTING_TOOL],
197
+ target_phase=AgentOperationalPhase.PROCESSING_TOOL_RESULT,
198
+ description="Fires after a tool has finished executing and the agent begins processing its result."
199
+ )
135
200
  async def notify_processing_tool_result(self, tool_name: str) -> None:
136
201
  await self._transition_phase(AgentOperationalPhase.PROCESSING_TOOL_RESULT, "notify_phase_processing_tool_result_started", additional_data={"tool_name": tool_name})
137
202
 
203
+ @phase_transition(
204
+ source_phases=[
205
+ AgentOperationalPhase.PROCESSING_USER_INPUT, AgentOperationalPhase.ANALYZING_LLM_RESPONSE,
206
+ AgentOperationalPhase.PROCESSING_TOOL_RESULT
207
+ ],
208
+ target_phase=AgentOperationalPhase.IDLE,
209
+ description="Occurs when an agent completes a processing cycle and is waiting for new input."
210
+ )
138
211
  async def notify_processing_complete_and_idle(self) -> None:
139
212
  if not self.context.current_phase.is_terminal() and self.context.current_phase != AgentOperationalPhase.IDLE:
140
213
  await self._transition_phase(AgentOperationalPhase.IDLE, "notify_phase_idle_entered")
@@ -143,6 +216,17 @@ class AgentPhaseManager:
143
216
  else:
144
217
  logger.warning(f"Agent '{self.context.agent_id}' notify_processing_complete_and_idle called in unexpected phase: {self.context.current_phase.value}")
145
218
 
219
+ @phase_transition(
220
+ source_phases=[
221
+ AgentOperationalPhase.UNINITIALIZED, AgentOperationalPhase.BOOTSTRAPPING, AgentOperationalPhase.IDLE,
222
+ AgentOperationalPhase.PROCESSING_USER_INPUT, AgentOperationalPhase.AWAITING_LLM_RESPONSE,
223
+ AgentOperationalPhase.ANALYZING_LLM_RESPONSE, AgentOperationalPhase.AWAITING_TOOL_APPROVAL,
224
+ AgentOperationalPhase.TOOL_DENIED, AgentOperationalPhase.EXECUTING_TOOL,
225
+ AgentOperationalPhase.PROCESSING_TOOL_RESULT, AgentOperationalPhase.SHUTTING_DOWN
226
+ ],
227
+ target_phase=AgentOperationalPhase.ERROR,
228
+ description="A catch-all transition that can occur from any non-terminal state if an unrecoverable error happens."
229
+ )
146
230
  async def notify_error_occurred(self, error_message: str, error_details: Optional[str] = None) -> None:
147
231
  if self.context.current_phase != AgentOperationalPhase.ERROR:
148
232
  data = {"error_message": error_message, "error_details": error_details}
@@ -150,12 +234,28 @@ class AgentPhaseManager:
150
234
  else:
151
235
  logger.debug(f"Agent '{self.context.agent_id}' already in ERROR phase when another error notified: {error_message}")
152
236
 
237
+ @phase_transition(
238
+ source_phases=[
239
+ AgentOperationalPhase.UNINITIALIZED, AgentOperationalPhase.BOOTSTRAPPING, AgentOperationalPhase.IDLE,
240
+ AgentOperationalPhase.PROCESSING_USER_INPUT, AgentOperationalPhase.AWAITING_LLM_RESPONSE,
241
+ AgentOperationalPhase.ANALYZING_LLM_RESPONSE, AgentOperationalPhase.AWAITING_TOOL_APPROVAL,
242
+ AgentOperationalPhase.TOOL_DENIED, AgentOperationalPhase.EXECUTING_TOOL,
243
+ AgentOperationalPhase.PROCESSING_TOOL_RESULT
244
+ ],
245
+ target_phase=AgentOperationalPhase.SHUTTING_DOWN,
246
+ description="Fires when the agent begins its graceful shutdown sequence."
247
+ )
153
248
  async def notify_shutdown_initiated(self) -> None:
154
249
  if not self.context.current_phase.is_terminal():
155
250
  await self._transition_phase(AgentOperationalPhase.SHUTTING_DOWN, "notify_phase_shutting_down_started")
156
251
  else:
157
252
  logger.debug(f"Agent '{self.context.agent_id}' shutdown initiated but already in a terminal phase: {self.context.current_phase.value}")
158
253
 
254
+ @phase_transition(
255
+ source_phases=[AgentOperationalPhase.SHUTTING_DOWN],
256
+ target_phase=AgentOperationalPhase.SHUTDOWN_COMPLETE,
257
+ description="The final transition when the agent has successfully shut down and released its resources."
258
+ )
159
259
  async def notify_final_shutdown_complete(self) -> None:
160
260
  final_phase = AgentOperationalPhase.ERROR if self.context.current_phase == AgentOperationalPhase.ERROR else AgentOperationalPhase.SHUTDOWN_COMPLETE
161
261
  if final_phase == AgentOperationalPhase.ERROR:
@@ -7,14 +7,14 @@ from autobyteus.agent.events.agent_input_event_queue_manager import AgentInputEv
7
7
  # from autobyteus.agent.events.agent_output_data_manager import AgentOutputDataManager
8
8
 
9
9
  from autobyteus.llm.base_llm import BaseLLM
10
- from .phases import AgentOperationalPhase
10
+ from autobyteus.agent.phases import AgentOperationalPhase
11
11
  from autobyteus.agent.workspace.base_workspace import BaseAgentWorkspace
12
12
  from autobyteus.agent.tool_invocation import ToolInvocation
13
13
  # LLMConfig is no longer needed here
14
14
  # from autobyteus.llm.utils.llm_config import LLMConfig
15
15
 
16
16
  if TYPE_CHECKING:
17
- from autobyteus.agent.context.agent_phase_manager import AgentPhaseManager
17
+ from autobyteus.agent.phases import AgentPhaseManager
18
18
  from autobyteus.tools.base_tool import BaseTool
19
19
 
20
20
  logger = logging.getLogger(__name__)
@@ -15,6 +15,7 @@ class AgentOperationalPhase(str, Enum):
15
15
  ANALYZING_LLM_RESPONSE = "analyzing_llm_response" # Received LLM response, analyzing it for next actions (e.g., tool use, direct reply).
16
16
 
17
17
  AWAITING_TOOL_APPROVAL = "awaiting_tool_approval" # Paused, needs external (user) approval for a tool invocation.
18
+ TOOL_DENIED = "tool_denied" # A proposed tool execution was denied by the user. Agent is processing the denial.
18
19
  EXECUTING_TOOL = "executing_tool" # Tool has been approved (or auto-approved) and is currently running.
19
20
  PROCESSING_TOOL_RESULT = "processing_tool_result" # Received a tool's result, actively processing it (often leading to another LLM call).
20
21
 
@@ -38,6 +39,7 @@ class AgentOperationalPhase(str, Enum):
38
39
  AgentOperationalPhase.AWAITING_LLM_RESPONSE,
39
40
  AgentOperationalPhase.ANALYZING_LLM_RESPONSE,
40
41
  AgentOperationalPhase.AWAITING_TOOL_APPROVAL,
42
+ AgentOperationalPhase.TOOL_DENIED,
41
43
  AgentOperationalPhase.EXECUTING_TOOL,
42
44
  AgentOperationalPhase.PROCESSING_TOOL_RESULT,
43
45
  ]
@@ -12,17 +12,11 @@ from .agent_events import (
12
12
  LifecycleEvent,
13
13
  AgentProcessingEvent,
14
14
  # Agent Phase-Specific Base Events
15
- AgentPreparationEvent,
16
15
  AgentOperationalEvent,
17
16
  # Specific Lifecycle Events
18
17
  AgentReadyEvent,
19
18
  AgentStoppedEvent,
20
19
  AgentErrorEvent,
21
- # DEPRECATED Initialization Events
22
- CreateToolInstancesEvent,
23
- ProcessSystemPromptEvent,
24
- FinalizeLLMConfigEvent,
25
- CreateLLMInstanceEvent,
26
20
  # Regular Agent Processing Events
27
21
  UserMessageReceivedEvent,
28
22
  InterAgentMessageReceivedEvent,
@@ -42,15 +36,10 @@ __all__ = [
42
36
  "BaseEvent",
43
37
  "LifecycleEvent",
44
38
  "AgentProcessingEvent",
45
- "AgentPreparationEvent",
46
39
  "AgentOperationalEvent",
47
40
  "AgentReadyEvent",
48
41
  "AgentStoppedEvent",
49
42
  "AgentErrorEvent",
50
- "CreateToolInstancesEvent",
51
- "ProcessSystemPromptEvent",
52
- "FinalizeLLMConfigEvent",
53
- "CreateLLMInstanceEvent",
54
43
  "UserMessageReceivedEvent",
55
44
  "InterAgentMessageReceivedEvent",
56
45
  "LLMUserMessageReadyEvent",
@@ -25,43 +25,6 @@ class AgentProcessingEvent(BaseEvent):
25
25
  """Base class for events related to the agent's internal data processing and task execution logic."""
26
26
 
27
27
 
28
- # --- Agent Initialization Sequence Events ---
29
- @dataclass
30
- class AgentPreparationEvent(AgentProcessingEvent):
31
- """Base class for events that are part of the agent's preparation/initialization sequence."""
32
- pass
33
-
34
- # BootstrapAgentEvent REMOVED
35
-
36
- @dataclass
37
- class CreateToolInstancesEvent(AgentPreparationEvent):
38
- """
39
- DEPRECATED. Initialization is now handled directly by the agent worker.
40
- """
41
- pass
42
-
43
- @dataclass
44
- class ProcessSystemPromptEvent(AgentPreparationEvent):
45
- """
46
- DEPRECATED. Initialization is now handled directly by the agent worker.
47
- """
48
- pass
49
-
50
- @dataclass
51
- class FinalizeLLMConfigEvent(AgentPreparationEvent):
52
- """
53
- DEPRECATED. Initialization is now handled directly by the agent worker.
54
- """
55
- pass
56
-
57
- @dataclass
58
- class CreateLLMInstanceEvent(AgentPreparationEvent):
59
- """
60
- DEPRECATED. Initialization is now handled directly by the agent worker.
61
- """
62
- pass
63
-
64
-
65
28
  # --- Agent Operational Phase Events ---
66
29
  @dataclass
67
30
  class AgentOperationalEvent(AgentProcessingEvent):
@@ -4,7 +4,7 @@ from typing import Optional, Dict, Any, TYPE_CHECKING
4
4
 
5
5
  from autobyteus.events.event_emitter import EventEmitter
6
6
  from autobyteus.events.event_types import EventType
7
- from autobyteus.agent.context.phases import AgentOperationalPhase
7
+ from autobyteus.agent.phases import AgentOperationalPhase
8
8
 
9
9
  if TYPE_CHECKING:
10
10
  from autobyteus.llm.utils.response_types import ChunkResponse, CompleteResponse
@@ -27,7 +27,15 @@ class AgentExternalEventNotifier(EventEmitter):
27
27
  emit_kwargs["payload"] = payload_content
28
28
 
29
29
  self.emit(event_type, **emit_kwargs)
30
- logger.info(f"AgentExternalEventNotifier (NotifierID: {self.object_id}, AgentID: {self.agent_id}) emitted {event_type.name}. Kwarg keys for emit: {list(emit_kwargs.keys())}")
30
+ log_message = (
31
+ f"AgentExternalEventNotifier (NotifierID: {self.object_id}, AgentID: {self.agent_id}) "
32
+ f"emitted {event_type.name}. Kwarg keys for emit: {list(emit_kwargs.keys())}"
33
+ )
34
+ # Reduce log level for high-frequency events like streaming chunks
35
+ if event_type == EventType.AGENT_DATA_ASSISTANT_CHUNK:
36
+ logger.debug(log_message)
37
+ else:
38
+ logger.info(log_message)
31
39
 
32
40
 
33
41
  def _emit_phase_change(self,
@@ -57,10 +65,18 @@ class AgentExternalEventNotifier(EventEmitter):
57
65
  self._emit_phase_change(EventType.AGENT_PHASE_PROCESSING_USER_INPUT_STARTED, AgentOperationalPhase.PROCESSING_USER_INPUT, old_phase, additional_data=data)
58
66
  def notify_phase_awaiting_llm_response_started(self, old_phase: Optional[AgentOperationalPhase]):
59
67
  self._emit_phase_change(EventType.AGENT_PHASE_AWAITING_LLM_RESPONSE_STARTED, AgentOperationalPhase.AWAITING_LLM_RESPONSE, old_phase)
68
+
60
69
  def notify_phase_analyzing_llm_response_started(self, old_phase: Optional[AgentOperationalPhase]):
61
70
  self._emit_phase_change(EventType.AGENT_PHASE_ANALYZING_LLM_RESPONSE_STARTED, AgentOperationalPhase.ANALYZING_LLM_RESPONSE, old_phase)
71
+
62
72
  def notify_phase_awaiting_tool_approval_started(self, old_phase: Optional[AgentOperationalPhase]):
63
73
  self._emit_phase_change(EventType.AGENT_PHASE_AWAITING_TOOL_APPROVAL_STARTED, AgentOperationalPhase.AWAITING_TOOL_APPROVAL, old_phase)
74
+
75
+ def notify_phase_tool_denied_started(self, old_phase: Optional[AgentOperationalPhase], tool_name: Optional[str], denial_for_tool: Optional[str]):
76
+ data = {"tool_name": tool_name, "denial_for_tool": denial_for_tool}
77
+ # Assuming EventType.AGENT_PHASE_TOOL_DENIED_STARTED exists in the main EventType enum
78
+ self._emit_phase_change(EventType.AGENT_PHASE_TOOL_DENIED_STARTED, AgentOperationalPhase.TOOL_DENIED, old_phase, additional_data=data)
79
+
64
80
  def notify_phase_executing_tool_started(self, old_phase: Optional[AgentOperationalPhase], tool_name: str):
65
81
  data = {"tool_name": tool_name}
66
82
  self._emit_phase_change(EventType.AGENT_PHASE_EXECUTING_TOOL_STARTED, AgentOperationalPhase.EXECUTING_TOOL, old_phase, additional_data=data)
@@ -81,13 +97,11 @@ class AgentExternalEventNotifier(EventEmitter):
81
97
  def notify_agent_data_assistant_chunk_stream_end(self):
82
98
  self._emit_event(EventType.AGENT_DATA_ASSISTANT_CHUNK_STREAM_END)
83
99
 
84
- # RENAMED METHOD
85
- def notify_agent_data_assistant_complete_response(self, complete_response: 'CompleteResponse'): # RENAMED from notify_agent_data_assistant_final_message
86
- # Use RENAMED EventType
100
+ def notify_agent_data_assistant_complete_response(self, complete_response: 'CompleteResponse'):
87
101
  self._emit_event(EventType.AGENT_DATA_ASSISTANT_COMPLETE_RESPONSE, payload_content=complete_response)
88
102
 
89
- def notify_agent_data_tool_log(self, log_entry: str):
90
- self._emit_event(EventType.AGENT_DATA_TOOL_LOG, payload_content=log_entry)
103
+ def notify_agent_data_tool_log(self, log_data: Dict[str, Any]):
104
+ self._emit_event(EventType.AGENT_DATA_TOOL_LOG, payload_content=log_data)
91
105
 
92
106
  def notify_agent_data_tool_log_stream_end(self):
93
107
  self._emit_event(EventType.AGENT_DATA_TOOL_LOG_STREAM_END)
@@ -95,6 +109,10 @@ class AgentExternalEventNotifier(EventEmitter):
95
109
  def notify_agent_request_tool_invocation_approval(self, approval_data: Dict[str, Any]):
96
110
  self._emit_event(EventType.AGENT_REQUEST_TOOL_INVOCATION_APPROVAL, payload_content=approval_data)
97
111
 
112
+ def notify_agent_tool_invocation_auto_executing(self, auto_exec_data: Dict[str, Any]):
113
+ """Notifies that a tool is being automatically executed."""
114
+ self._emit_event(EventType.AGENT_TOOL_INVOCATION_AUTO_EXECUTING, payload_content=auto_exec_data)
115
+
98
116
  def notify_agent_error_output_generation(self, error_source: str, error_message: str, error_details: Optional[str] = None):
99
117
  payload_dict = {
100
118
  "source": error_source,
@@ -4,7 +4,7 @@ import logging
4
4
  import traceback
5
5
  from typing import TYPE_CHECKING, Optional
6
6
 
7
- from autobyteus.agent.context.phases import AgentOperationalPhase
7
+ from autobyteus.agent.phases import AgentOperationalPhase
8
8
  from autobyteus.agent.events.agent_events import ( # Updated relative import path if needed, but BaseEvent is fine
9
9
  BaseEvent,
10
10
  AgentReadyEvent,
@@ -75,8 +75,12 @@ class AgentFactory(metaclass=SingletonMeta):
75
75
  ) -> 'AgentRuntime':
76
76
  from autobyteus.agent.runtime.agent_runtime import AgentRuntime
77
77
 
78
- # The workspace is now passed directly from the config
79
- runtime_state = AgentRuntimeState(agent_id=agent_id, workspace=config.workspace)
78
+ # The workspace and initial custom data are now passed directly from the config to the state.
79
+ runtime_state = AgentRuntimeState(
80
+ agent_id=agent_id,
81
+ workspace=config.workspace,
82
+ custom_data=config.initial_custom_data
83
+ )
80
84
 
81
85
  # --- Set pre-initialized instances on the state ---
82
86
  runtime_state.llm_instance = config.llm_instance
@@ -58,10 +58,13 @@ class AgentGroup:
58
58
  if not is_send_message_present:
59
59
  modified_tools.append(SendMessageTo())
60
60
 
61
- # This logic correctly re-uses the user-provided LLM instance when creating the effective config.
61
+ # This logic correctly re-uses the user-provided LLM instance and other properties
62
+ # when creating the effective config for the agent factory.
62
63
  effective_config = AgentConfig(
63
- name=original_config.name, role=original_config.role, description=original_config.description,
64
- llm_instance=original_config.llm_instance, # Pass the instance through
64
+ name=original_config.name,
65
+ role=original_config.role,
66
+ description=original_config.description,
67
+ llm_instance=original_config.llm_instance,
65
68
  system_prompt=original_config.system_prompt,
66
69
  tools=modified_tools,
67
70
  auto_execute_tools=original_config.auto_execute_tools,
@@ -69,7 +72,9 @@ class AgentGroup:
69
72
  input_processors=original_config.input_processors,
70
73
  llm_response_processors=original_config.llm_response_processors,
71
74
  system_prompt_processors=original_config.system_prompt_processors,
72
- workspace=original_config.workspace # Pass the workspace through
75
+ workspace=original_config.workspace,
76
+ phase_hooks=original_config.phase_hooks,
77
+ initial_custom_data=original_config.initial_custom_data
73
78
  )
74
79
 
75
80
  try:
@@ -130,14 +135,18 @@ class AgentGroup:
130
135
  async def listen_for_final_output():
131
136
  nonlocal final_response_aggregator
132
137
  try:
133
- async for complete_response in streamer.stream_assistant_final_messages():
134
- final_response_aggregator += complete_response.content
138
+ async for complete_response_data in streamer.stream_assistant_final_response():
139
+ final_response_aggregator += complete_response_data.content
135
140
  except Exception as e_stream:
136
141
  logger.error(f"Error streaming final output from coordinator: {e_stream}", exc_info=True)
137
142
  output_stream_listener_task = asyncio.create_task(listen_for_final_output())
138
143
  input_message = AgentInputUserMessage(content=initial_input_content, metadata={"user_id": user_id} if user_id else {})
139
144
  await self.coordinator_agent.post_user_message(input_message)
140
- if output_stream_listener_task: await output_stream_listener_task
145
+
146
+ # Wait for the listener to finish, which happens after the agent is done and the stream closes.
147
+ if output_stream_listener_task:
148
+ await output_stream_listener_task
149
+
141
150
  return final_response_aggregator
142
151
  finally:
143
152
  if output_stream_listener_task and not output_stream_listener_task.done():