autobyteus 1.1.0__py3-none-any.whl → 1.1.2__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 (103) 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 +5 -10
  45. autobyteus/agent/runtime/agent_worker.py +62 -19
  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 +11 -173
  67. autobyteus/llm/api/grok_llm.py +11 -172
  68. autobyteus/llm/api/kimi_llm.py +24 -0
  69. autobyteus/llm/api/mistral_llm.py +4 -4
  70. autobyteus/llm/api/ollama_llm.py +2 -2
  71. autobyteus/llm/api/openai_compatible_llm.py +193 -0
  72. autobyteus/llm/api/openai_llm.py +11 -139
  73. autobyteus/llm/extensions/token_usage_tracking_extension.py +11 -1
  74. autobyteus/llm/llm_factory.py +168 -42
  75. autobyteus/llm/models.py +25 -29
  76. autobyteus/llm/ollama_provider.py +6 -2
  77. autobyteus/llm/ollama_provider_resolver.py +44 -0
  78. autobyteus/llm/providers.py +1 -0
  79. autobyteus/llm/token_counter/kimi_token_counter.py +24 -0
  80. autobyteus/llm/token_counter/token_counter_factory.py +3 -0
  81. autobyteus/llm/utils/messages.py +3 -3
  82. autobyteus/tools/__init__.py +2 -0
  83. autobyteus/tools/base_tool.py +7 -1
  84. autobyteus/tools/functional_tool.py +20 -5
  85. autobyteus/tools/mcp/call_handlers/stdio_handler.py +15 -1
  86. autobyteus/tools/mcp/config_service.py +106 -127
  87. autobyteus/tools/mcp/registrar.py +247 -59
  88. autobyteus/tools/mcp/types.py +5 -3
  89. autobyteus/tools/registry/tool_definition.py +8 -1
  90. autobyteus/tools/registry/tool_registry.py +18 -0
  91. autobyteus/tools/tool_category.py +11 -0
  92. autobyteus/tools/tool_meta.py +3 -1
  93. autobyteus/tools/tool_state.py +20 -0
  94. autobyteus/tools/usage/parsers/_json_extractor.py +99 -0
  95. autobyteus/tools/usage/parsers/default_json_tool_usage_parser.py +46 -77
  96. autobyteus/tools/usage/parsers/default_xml_tool_usage_parser.py +87 -96
  97. autobyteus/tools/usage/parsers/gemini_json_tool_usage_parser.py +37 -47
  98. autobyteus/tools/usage/parsers/openai_json_tool_usage_parser.py +112 -113
  99. {autobyteus-1.1.0.dist-info → autobyteus-1.1.2.dist-info}/METADATA +13 -12
  100. {autobyteus-1.1.0.dist-info → autobyteus-1.1.2.dist-info}/RECORD +103 -82
  101. {autobyteus-1.1.0.dist-info → autobyteus-1.1.2.dist-info}/WHEEL +0 -0
  102. {autobyteus-1.1.0.dist-info → autobyteus-1.1.2.dist-info}/licenses/LICENSE +0 -0
  103. {autobyteus-1.1.0.dist-info → autobyteus-1.1.2.dist-info}/top_level.txt +0 -0
@@ -1,12 +1,12 @@
1
1
  # file: autobyteus/autobyteus/tools/usage/parsers/openai_json_tool_usage_parser.py
2
2
  import json
3
3
  import logging
4
- import re
5
- import uuid
6
4
  from typing import TYPE_CHECKING, List, Optional, Any, Dict
7
5
 
8
6
  from autobyteus.agent.tool_invocation import ToolInvocation
9
7
  from .base_parser import BaseToolUsageParser
8
+ from .exceptions import ToolUsageParseException
9
+ from ._json_extractor import _find_json_blobs
10
10
 
11
11
  if TYPE_CHECKING:
12
12
  from autobyteus.llm.utils.response_types import CompleteResponse
@@ -15,133 +15,132 @@ logger = logging.getLogger(__name__)
15
15
 
16
16
  class OpenAiJsonToolUsageParser(BaseToolUsageParser):
17
17
  """
18
- Parses LLM responses for tool usage commands formatted in the OpenAI style.
19
- This parser is flexible and can handle multiple formats:
20
- 1. The official OpenAI API format with a 'tool_calls' list.
21
- 2. A raw list of tool call objects.
22
- 3. Simplified tool calls from fine-tuned models (e.g., {"name": "...", "arguments": {...}}).
23
- 4. A single tool call object not wrapped in a list.
24
- 5. Tool calls wrapped in a `{"tool": ...}` structure for prompt consistency.
18
+ Parses LLM responses for tool usage commands formatted in various JSON
19
+ styles commonly produced by OpenAI and similar models.
20
+
21
+ This parser is highly flexible and robustly handles multiple formats:
22
+ - A root object with a "tool_calls" or "tools" key containing a list of tool calls.
23
+ - A root object with a single "tool" key.
24
+ - A root object that is itself a single tool call.
25
+ - A raw list of tool calls.
26
+ - Tool call arguments can be either a JSON object or a stringified JSON.
25
27
  """
26
28
  def get_name(self) -> str:
27
29
  return "openai_json_tool_usage_parser"
28
30
 
29
- def _extract_json_from_response(self, text: str) -> Optional[str]:
30
- match = re.search(r"```(?:json)?\s*([\s\S]+?)\s*```", text)
31
- if match:
32
- return match.group(1).strip()
33
-
34
- # Try to find a JSON object or array
35
- first_bracket = text.find('[')
36
- first_brace = text.find('{')
37
-
38
- if first_brace == -1 and first_bracket == -1:
39
- return None
40
-
41
- start_index = -1
42
- if first_bracket != -1 and first_brace != -1:
43
- start_index = min(first_bracket, first_brace)
44
- elif first_bracket != -1:
45
- start_index = first_bracket
46
- else: # first_brace != -1
47
- start_index = first_brace
48
-
49
- json_substring = text[start_index:]
50
- try:
51
- # Check if the substring is valid JSON
52
- json.loads(json_substring)
53
- return json_substring
54
- except json.JSONDecodeError:
55
- logger.debug(f"Found potential start of JSON, but substring was not valid: {json_substring[:100]}")
56
- return None
57
-
58
31
  def parse(self, response: 'CompleteResponse') -> List[ToolInvocation]:
32
+ response_text = response.content
59
33
  invocations: List[ToolInvocation] = []
60
- response_text = self._extract_json_from_response(response.content)
61
- if not response_text:
34
+
35
+ # Use a robust method to find all potential JSON blobs in the text
36
+ json_blobs = _find_json_blobs(response_text)
37
+ if not json_blobs:
62
38
  logger.debug("No valid JSON object could be extracted from the response content.")
63
39
  return invocations
64
40
 
65
- try:
66
- data = json.loads(response_text)
67
- except json.JSONDecodeError:
68
- logger.debug(f"Could not parse extracted text as JSON. Text: {response_text[:200]}")
69
- return invocations
41
+ for blob in json_blobs:
42
+ try:
43
+ data = json.loads(blob)
44
+
45
+ # Determine the structure of the JSON data
46
+ tool_calls_data: List[Dict[str, Any]] = []
47
+ if isinstance(data, list):
48
+ # Format: [{"function":...}, {"function":...}]
49
+ tool_calls_data = data
50
+ elif isinstance(data, dict):
51
+ # Format: {"tool_calls": [...]} or {"tools": [...]}
52
+ if "tool_calls" in data and isinstance(data["tool_calls"], list):
53
+ tool_calls_data = data["tool_calls"]
54
+ elif "tools" in data and isinstance(data["tools"], list):
55
+ tool_calls_data = data["tools"]
56
+ # Format: {"tool": {...}}
57
+ elif "tool" in data and isinstance(data["tool"], dict):
58
+ tool_calls_data = [data["tool"]]
59
+ # Format: {"function": ...}
60
+ else:
61
+ tool_calls_data = [data]
62
+
63
+ if not tool_calls_data:
64
+ logger.debug(f"JSON response does not match any expected tool call format. Content: {blob[:200]}")
65
+ continue
70
66
 
71
- tool_calls: Optional[List[Any]] = None
72
- if isinstance(data, dict):
73
- # Standard OpenAI format: check for 'tool_calls', fallback to 'tools'
74
- tool_calls = data.get("tool_calls")
75
- if not isinstance(tool_calls, list):
76
- tool_calls = data.get("tools")
77
- elif isinstance(data, list):
78
- # The entire response is a list of tool calls
79
- tool_calls = data
80
-
81
- if not isinstance(tool_calls, list):
82
- # Handle the case where a single tool call is returned as a dictionary, not in a list.
83
- if isinstance(data, dict):
84
- # Check for a 'tool' wrapper. If present, the content is the call.
85
- if "tool" in data and isinstance(data.get("tool"), dict):
86
- tool_calls = [data] # The list contains the wrapped object
87
- # Otherwise, check for standard function/simplified formats.
88
- elif ('name' in data and 'arguments' in data) or 'function' in data:
89
- tool_calls = [data]
90
-
91
- if not isinstance(tool_calls, list):
92
- logger.warning(f"Expected a list of tool calls, but couldn't find one in the response. Data type: {type(data)}. Skipping.")
93
- return invocations
67
+ for call_data in tool_calls_data:
68
+ invocation = self._parse_tool_call_object(call_data)
69
+ if invocation:
70
+ invocations.append(invocation)
94
71
 
95
- for call_data in tool_calls:
96
- if not isinstance(call_data, dict):
97
- logger.debug(f"Skipping non-dict item in tool_calls: {call_data}")
72
+ except json.JSONDecodeError:
73
+ # This can happen if a blob is not a tool call (e.g., just example JSON).
74
+ # We can safely ignore these.
75
+ logger.debug(f"Could not parse extracted text as JSON in {self.get_name()}. Blob: {blob[:200]}")
98
76
  continue
77
+ except Exception as e:
78
+ # If we're here, it's likely a valid JSON but with unexpected structure.
79
+ # It's safer to raise this for upstream handling.
80
+ error_msg = f"Unexpected error while parsing JSON blob: {e}. Blob: {blob[:200]}"
81
+ logger.error(error_msg, exc_info=True)
82
+ raise ToolUsageParseException(error_msg, original_exception=e)
83
+
84
+ return invocations
99
85
 
100
- # Handle if the call is wrapped in a 'tool' key.
101
- # This makes the parser compatible with the new example format.
102
- if "tool" in call_data and isinstance(call_data.get("tool"), dict):
103
- call_data = call_data["tool"]
86
+ def _parse_tool_call_object(self, call_data: Dict[str, Any]) -> Optional[ToolInvocation]:
87
+ """
88
+ Parses a single tool call object, which can have various structures.
89
+ - {"function": {"name": str, "arguments": str_json_or_dict}}
90
+ - {"name": str, "arguments": dict}
91
+ """
92
+ if not isinstance(call_data, dict):
93
+ logger.debug(f"Skipping non-dictionary item in tool call list: {call_data}")
94
+ return None
104
95
 
105
- # A tool call ID is required for tracking, but the model may not provide one.
106
- # If it's missing, we generate one.
107
- tool_id = call_data.get("id") or f"call_{uuid.uuid4().hex}"
108
-
109
- # The tool call can be in the full format `{"function": ...}` or a simplified `{"name": ...}`.
110
- function_data: Optional[Dict] = call_data.get("function")
111
- if not isinstance(function_data, dict):
112
- # If 'function' key is missing, assume simplified format where call_data is the function data.
113
- function_data = call_data
114
-
96
+ function_data: Optional[Dict] = call_data.get("function")
97
+ if isinstance(function_data, dict):
98
+ # Standard OpenAI format: {"function": {"name": ..., "arguments": ...}}
115
99
  tool_name = function_data.get("name")
116
100
  arguments_raw = function_data.get("arguments")
101
+ else:
102
+ # Handle flattened format: {"name": ..., "arguments": ...}
103
+ tool_name = call_data.get("name")
104
+ arguments_raw = call_data.get("arguments")
117
105
 
118
- if not tool_name:
119
- logger.debug(f"Skipping malformed function data (missing 'name'): {function_data}")
120
- continue
106
+ if not tool_name or not isinstance(tool_name, str):
107
+ logger.debug(f"Skipping malformed tool call (missing or invalid 'name'): {call_data}")
108
+ return None
121
109
 
122
- arguments: Optional[Dict] = None
123
- if isinstance(arguments_raw, str):
124
- try:
125
- arguments = json.loads(arguments_raw)
126
- except json.JSONDecodeError:
127
- logger.error(f"Failed to parse 'arguments' string for tool '{tool_name}': {arguments_raw}")
128
- continue
129
- elif isinstance(arguments_raw, dict):
130
- arguments = arguments_raw
131
- elif arguments_raw is None:
132
- arguments = {} # Treat missing arguments as an empty dictionary
110
+ arguments: Dict[str, Any]
111
+ if arguments_raw is None:
112
+ arguments = {}
113
+ elif isinstance(arguments_raw, dict):
114
+ # Arguments are already a dictionary
115
+ arguments = arguments_raw
116
+ elif isinstance(arguments_raw, str):
117
+ # Arguments are a stringified JSON
118
+ arg_string = arguments_raw.strip()
119
+ if not arg_string:
120
+ arguments = {}
133
121
  else:
134
- logger.debug(f"Skipping function data with invalid 'arguments' type ({type(arguments_raw)}): {function_data}")
135
- continue
136
-
137
- if not isinstance(arguments, dict):
138
- logger.error(f"Parsed arguments for tool '{tool_name}' is not a dictionary. Got: {type(arguments)}")
139
- continue
140
-
141
- try:
142
- tool_invocation = ToolInvocation(name=tool_name, arguments=arguments, id=tool_id)
143
- invocations.append(tool_invocation)
144
- except Exception as e:
145
- logger.error(f"Unexpected error creating ToolInvocation for tool '{tool_name}' (ID: {tool_id}): {e}", exc_info=True)
146
-
147
- return invocations
122
+ try:
123
+ parsed_args = json.loads(arg_string)
124
+ if not isinstance(parsed_args, dict):
125
+ logger.error(f"Parsed 'arguments' string for tool '{tool_name}' must be a dictionary, but got {type(parsed_args)}.")
126
+ return None
127
+ arguments = parsed_args
128
+ except json.JSONDecodeError as e:
129
+ # If it's a string but not valid JSON, it's a hard error.
130
+ raise ToolUsageParseException(
131
+ f"Failed to parse 'arguments' string for tool '{tool_name}': {arguments_raw}",
132
+ original_exception=e
133
+ )
134
+ else:
135
+ # Any other type for arguments is invalid
136
+ logger.error(f"Skipping tool call with invalid 'arguments' type. Expected dict or string, got {type(arguments_raw)}: {call_data}")
137
+ return None
138
+
139
+ try:
140
+ # The ToolInvocation constructor will generate a deterministic ID if 'id' is None.
141
+ tool_invocation = ToolInvocation(name=tool_name, arguments=arguments, id=None)
142
+ logger.info(f"Successfully parsed OpenAI-style JSON tool invocation for '{tool_name}'.")
143
+ return tool_invocation
144
+ except Exception as e:
145
+ logger.error(f"Unexpected error creating ToolInvocation for tool '{tool_name}': {e}", exc_info=True)
146
+ return None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: autobyteus
3
- Version: 1.1.0
3
+ Version: 1.1.2
4
4
  Summary: Multi-Agent framework
5
5
  Home-page: https://github.com/AutoByteus/autobyteus
6
6
  Author: Ryan Zheng
@@ -25,12 +25,13 @@ Requires-Dist: boto3
25
25
  Requires-Dist: botocore
26
26
  Requires-Dist: anthropic==0.37.1
27
27
  Requires-Dist: Jinja2
28
- Requires-Dist: ollama==0.4.5
29
- Requires-Dist: mistral_common
28
+ Requires-Dist: ollama
29
+ Requires-Dist: mistral_common==1.6.3
30
+ Requires-Dist: mistralai==1.5.2
30
31
  Requires-Dist: certifi==2025.4.26
31
32
  Requires-Dist: numpy==2.2.5
32
33
  Requires-Dist: aiohttp
33
- Requires-Dist: autobyteus-llm-client==1.1.0
34
+ Requires-Dist: autobyteus-llm-client==1.1.1
34
35
  Requires-Dist: brui-core==1.0.8
35
36
  Provides-Extra: dev
36
37
  Requires-Dist: coverage; extra == "dev"
@@ -66,19 +67,19 @@ Dynamic: summary
66
67
 
67
68
  # Autobyteus
68
69
 
69
- Autobyteus is an open-source coding assistance tool designed to enhance the software development workflow by making it context-aware. Each step in the workflow is interactive through a user-friendly interface, incorporating the entire software development lifecycle into each stage.
70
+ Autobyteus is an open-source, application-first agentic framework for Python. It is designed to help developers build, test, and deploy complex, stateful, and extensible AI agents by providing a robust architecture and a powerful set of tools.
70
71
 
71
72
  ## Architecture
72
73
 
73
74
  Autobyteus is built with a modular, event-driven architecture designed for extensibility and clear separation of concerns. The key components are:
74
75
 
75
- - **Agent Core**: The heart of the system, comprising an `Agent` facade that provides the primary user-facing API. This facade communicates with an `AgentRuntime`, which manages an `AgentWorker` running in a dedicated thread. This design ensures non-blocking agent operations.
76
- - **Context & Configuration**: Agent behavior is defined through a clear configuration (`AgentConfig`) and its dynamic state is managed in `AgentRuntimeState`. These are bundled into a comprehensive `AgentContext` that is available to all components during runtime, providing a single source of truth for an agent's status and capabilities.
77
- - **Event-Driven System**: Agents operate on an internal event loop. Actions (like receiving user messages or tool results) are translated into events and placed on internal queues. `EventHandlers` process these events, containing the core logic for how an agent thinks and acts. This decouples logic and makes the system easy to extend.
78
- - **Pluggable Processors**: The framework uses a pipeline of processors to modify data at key stages. `SystemPromptProcessors` dynamically build the final system prompt (e.g., by injecting tool definitions), `InputProcessors` preprocess user messages, and `LLMResponseProcessors` parse outputs from the language model (e.g., to detect tool usage).
79
- - **Tooling**: Agents can be extended with `Tools`, which are self-contained capabilities that can be called by the LLM. The system includes built-in tools (like `SendMessageTo` for inter-agent communication) and supports custom tools.
80
- - **Multi-Agent Systems**: The `AgentGroup` and `AgenticWorkflow` abstractions provide powerful, high-level APIs for orchestrating multiple agents to collaborate on complex tasks.
81
- - **Remote Agents**: Built-in RPC capabilities allow for communication with agents running in separate processes or on different machines, enabling distributed agent systems.
76
+ - **Agent Core**: The heart of the system. Each agent is a stateful, autonomous entity that runs as a background process in its own thread, managed by a dedicated `AgentWorker`. This design makes every agent a truly independent entity capable of handling long-running tasks.
77
+ - **Context & Configuration**: Agent behavior is defined through a static configuration (`AgentConfig`) and its dynamic state is managed in `AgentRuntimeState`. These are bundled into a comprehensive `AgentContext` that is passed to all components, providing a single source of truth.
78
+ - **Event-Driven System**: Agents operate on an internal `asyncio` event loop. User messages, tool results, and internal signals are handled as events, which are processed by dedicated `EventHandlers`. This decouples logic and makes the system highly extensible.
79
+ - **Pluggable Processors & Hooks**: The framework provides extension points to inject custom logic. `InputProcessors` and `LLMResponseProcessors` modify data in the main processing pipeline, while `PhaseHooks` allow custom code to run on specific agent lifecycle transitions (e.g., from `BOOTSTRAPPING` to `IDLE`).
80
+ - **Context-Aware Tooling**: Tools are first-class citizens that receive the agent's full `AgentContext` during execution. This allows tools to be deeply integrated with the agent's state, configuration, and workspace, enabling more intelligent and powerful actions.
81
+ - **Tool Approval Flow**: The framework has native support for human-in-the-loop workflows. By setting `auto_execute_tools=False` in the agent's configuration, the agent will pause before executing a tool, emit an event requesting permission, and wait for external approval before proceeding.
82
+ - **MCP Integration**: The framework has native support for the Model Context Protocol (MCP). This allows agents to discover and use tools from external, language-agnostic tool servers, making the ecosystem extremely flexible and ready for enterprise integration.
82
83
 
83
84
  ## Features
84
85