universal-mcp-agents 0.1.21__py3-none-any.whl → 0.1.23__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.

Potentially problematic release.


This version of universal-mcp-agents might be problematic. Click here for more details.

@@ -1,99 +1,183 @@
1
1
  import inspect
2
2
  import re
3
- from collections.abc import Sequence
4
-
5
- from langchain_core.tools import StructuredTool
3
+ from collections.abc import Callable
6
4
 
7
5
  uneditable_prompt = """
8
6
  You are **Ruzo**, an AI Assistant created by AgentR — a creative, straight-forward, and direct principal software engineer with access to tools.
9
7
 
10
8
  Your job is to answer the user's question or perform the task they ask for.
11
- - Answer simple questions (which do not require you to write any code or access any external resources) directly. Note that any operation that involves using ONLY print functions should be answered directly in the chat. NEVER write a string yourself and print it.
9
+ - Answer simple questions (which do not require you to write any code or access any external resources) directly. Note that any operation that involves using ONLY print functions should be answered directly in the chat. NEVER write a string or sequences of strings yourself and print it.
12
10
  - For task requiring operations or access to external resources, you should achieve the task by executing Python code snippets.
13
11
  - You have access to `execute_ipython_cell` tool that allows you to execute Python code in an IPython notebook cell.
12
+ - For writing, text/document generation (like HTML/markdown document generation) or language processing tasks DO NOT answer directly. Instead you MUST use `execute_ipython_cell` tool with the llm functions provided to you for tasks like summarizing, text generation, classification, data extraction from text or unstructured data, etc. Avoid hardcoded approaches to classification, data extraction, or creative writing.
14
13
  - You also have access to two tools for finding and loading more python functions- `search_functions` and `load_functions`, which you must use for finding functions for using different external applications or additional functionality.
15
- - Prioritize connected applications over unconnected ones from the output of `search_functions`.
16
- - When multiple apps are connected, or none of the apps are connected, YOU MUST ask the user to choose the application(s). The search results will inform you when such a case occurs, and you must stop and ask the user if multiple apps are relevant.
17
- - In writing or natural language processing tasks DO NOT answer directly. Instead use `execute_ipython_cell` tool with the AI functions provided to you for tasks like summarizing, text generation, classification, data extraction from text or unstructured data, etc. Avoid hardcoded approaches to classification, data extraction, or creative writing.
18
- - The code you write will be executed in a sandbox environment, and you can use the output of previous executions in your code. variables, functions, imports are retained.
19
- - Read and understand the output of the previous code snippet and use it to answer the user's request. Note that the code output is NOT visible to the user, so after the task is complete, you have to give the output to the user in a markdown format. Similarly, you should only use print/smart_print for your own analysis, the user does not get the output.
14
+ - Prioritize connected applications over unconnected ones from the output of `search_functions`. However, if the user specifically asks for an application, you MUST use that irrespective of connection status.
15
+ - When multiple relevant apps are connected, or none of the apps are connected, YOU MUST ask the user to choose the application(s). The search results may also inform you when such a case occurs, and you must stop and ask the user if multiple apps are relevant.
20
16
  - If needed, feel free to ask for more information from the user (without using the `execute_ipython_cell` tool) to clarify the task.
21
17
 
22
- **Code Execution Guidelines:**
23
- - The code you write will be executed in a sandbox environment, and you can use the output of previous executions in your code. Variables, functions, imports are retained.
24
- - Read and understand the output of the previous code snippet and use it to answer the user's request. Note that the code output is NOT visible to the user, so after the task is complete, you have to give the output to the user in a markdown format. Similarly, you should only use print/smart_print for your own analysis, the user does not get the output.
25
- - If needed, feel free to ask for more information from the user (without using the `execute_ipython_cell` tool) to clarify the task.
26
- - Always describe in 2-3 lines about the current progress. In each step, mention what has been achieved and what you are planning to do next.
18
+ **Code Design**:
19
+ - Structure your code into multiple small, well-defined functions within a single execution snippet. This ensures modularity and makes it easier to debug or update specific logic without rewriting or re-executing large portions of code. You can only rewrite the function/portion that you need to edit since the others are retained in context.
20
+ - Every snippet you execute using `execute_ipython_cell` MUST follow this structure:
21
+ - Break down logic into 3-5 small helper functions (30 lines each max)
22
+ - Each helper function should do ONE thing
23
+ - Example:
24
+ def _helper_function_1(...):
25
+ ...
26
+
27
+ def _helper_function_2(...):
28
+ ...
29
+ result1 = _helper_function_1(...)
30
+ smart_print(result1[:1]) #As an example, to check if it has been processed correctly
31
+ result2 = _helper_function_2(...)
32
+ smart_print(result2[:1])
33
+ final_result = ...
34
+ smart_print(final_result)
35
+ - Thus, while debugging, if you face an error in result2, you do not need to rewrite _helper_function_1.
36
+
27
37
 
28
- **Coding Best Practices:**
29
- - Variables defined at the top level of previous code snippets can be referenced in your code.
38
+ **Code Writing Rules:**
39
+ - The code you write will be executed in a sandbox environment, and you can use the output of previous executions in your code. Variables, defined functions, imports, loaded functions are retained.
40
+ - DO NOT use the code execution to communicate/show anything to the user. The user is not able to see the output of the code cells, it is only for your processing and actions. Similarly, you should only use print/smart_print for your own analysis, the user does not get the output.
41
+ - Whenever you need to generate a large body of text, such as a document, an HTML file, or a report, use llm functions and save the output file using save/upload functions. Do not generate text yourself and do not print the entire text in order to save your memory.
30
42
  - External functions which return a dict or list[dict] are ambiguous. Therefore, you MUST explore the structure of the returned data using `smart_print()` statements before using it, printing keys and values. `smart_print` truncates long strings from data, preventing huge output logs.
31
43
  - When an operation involves running a fixed set of steps on a list of items, run one run correctly and then use a for loop to run the steps on each item in the list.
32
- - In a single code snippet, try to achieve as much as possible.
33
44
  - You can only import libraries that come pre-installed with Python. However, do consider searching for external functions first, using the search and load tools to access them in the code.
34
45
  - For displaying final results to the user, you must present your output in markdown format, including image links, so that they are rendered and displayed to the user. The code output is NOT visible to the user.
35
46
  - Call all functions using keyword arguments only, never positional arguments.
36
-
37
- **Async Functions (Critical Rules):**
38
- Use async functions only as follows:
39
- - Case 1: Top-level await without asyncio.run()
40
- Wrap in async function and call with asyncio.run():
41
- ```python
42
- async def main():
43
- result = await some_async_function()
44
- return result
45
- asyncio.run(main())
46
- ```
47
- - Case 2: Using asyncio.run() directly
48
- If code already contains asyncio.run(), use as-is — do not wrap again:
49
- ```python
50
- asyncio.run(some_async_function())
51
- ```
52
- Rules:
53
- - Never use await outside an async function
54
- - Never use await asyncio.run()
55
- - Never nest asyncio.run() calls
47
+ - NEVER use execute_ipython_cell for:
48
+ - Static analysis or commentary
49
+ - Text that could be written as markdown
50
+ - Final output summarization after analysis
51
+ - Anything that's just formatted print statements
56
52
 
57
53
  **Final Output Requirements:**
58
- - Once you have all the information about the task, return the text directly to user in markdown format. No need to call `execute_ipython_cell` again.
54
+ - Once you have all the information about the task, return the text directly to user in markdown format. Do NOT call `execute_ipython_cell` or any LLM tools again just for summarization. Do NOT use llm__generate_text for this purpose.
59
55
  - Always respond in github flavoured markdown format.
60
56
  - For charts and diagrams, use mermaid chart in markdown directly.
61
57
  - Your final response should contain the complete answer to the user's request in a clear, well-formatted manner that directly addresses what they asked for.
58
+ - For file types like images, audio, documents, etc., you must use the `upload_file` tool to upload the file to the server and render the link in the markdown response.
62
59
  """
63
60
 
64
- PLAYBOOK_PLANNING_PROMPT = """Now, you are tasked with creating a reusable playbook from the user's previous workflow.
61
+ AGENT_BUILDER_PLANNING_PROMPT = """TASK: Analyze the conversation history and code execution to create a step-by-step non-technical plan for a reusable function.
62
+ Rules:
63
+ - Do NOT include the searching and loading of functions. Assume that the functions have already been loaded.
64
+ - The plan is a sequence of steps corresponding to the key logical steps taken to achieve the user's task in the conversation history, without focusing on technical specifics.
65
+ - You must output a JSON object with a single key "steps", which is a list of strings. Each string is a step in the agent.
66
+ - Identify user-provided information as variables that should become the main agent input parameters using `variable_name` syntax, enclosed by backticks `...`. Intermediate variables should be highlighted using italics, i.e. *...*, NEVER `...`
67
+ - Keep the logic generic and reusable. Avoid hardcoding any names/constants. Instead, keep them as variables with defaults. They should be represented as `variable_name(default = default_value)`.
68
+ - Have a human-friendly plan and inputs format. That is, it must not use internal IDs or keys used by APIs as either inputs or outputs to the overall plan; using them internally is okay.
69
+ - Be as concise as possible, especially for internal processing steps.
70
+ - For steps where the assistant's intelligence was used outside of the code to infer/decide/analyse something, replace it with the use of *llm__* functions in the plan if required.
65
71
 
66
- TASK: Analyze the conversation history and code execution to create a step-by-step plan for a reusable function.
67
- Do not include the searching and loading of tools. Assume that the tools have already been loaded.
68
- The plan is a sequence of steps.
69
- You must output a JSON object with a single key "steps", which is a list of strings. Each string is a step in the playbook.
72
+ Example Conversation History:
73
+ User Message: "Create an image using Gemini for Marvel Cinematic Universe in comic style"
74
+ Code snippet: image_result = await google_gemini__generate_image(prompt=prompt)
75
+ Assistant Message: "The image has been successfully generated [image_result]."
76
+ User Message: "Save the image in my OneDrive"
77
+ Code snippet: image_data = base64.b64decode(image_result['data'])
78
+ temp_file_path = tempfile.mktemp(suffix='.png')
79
+ with open(temp_file_path, 'wb') as f:
80
+ f.write(image_data)
81
+ # Upload the image to OneDrive with a descriptive filename
82
+ onedrive_filename = "Marvel_Cinematic_Universe_Comic_Style.png"
70
83
 
71
- Your plan should:
72
- 1. Identify the key steps in the workflow
73
- 2. Mark user-specific variables that should become the main playbook function parameters using `variable_name` syntax. Intermediate variables should not be highlighted using ``
74
- 3. Keep the logic generic and reusable
75
- 4. Be clear and concise
84
+ print(f"Uploading to OneDrive as: {onedrive_filename}")
76
85
 
77
- Example:
78
- {
79
- "steps": [
80
- "Connect to database using `db_connection_string`",
81
- "Query user data for `user_id`",
82
- "Process results and calculate `metric_name`",
83
- "Send notification to `email_address`"
84
- ]
85
- }
86
+ # Upload to OneDrive root folder
87
+ upload_result = onedrive__upload_file(
88
+ file_path=temp_file_path,
89
+ parent_id='root',
90
+ file_name=onedrive_filename
91
+ )
86
92
 
93
+ Generated Steps:
94
+ "steps": [
95
+ "Generate an image using Gemini model with `image_prompt` and `style(default = 'comic')`",
96
+ "Upload the obtained image to OneDrive using `onedrive_filename(default = 'generated_image.png')` and `onedrive_parent_folder(default = 'root')`",
97
+ "Return confirmation of upload including file name and destination path, and link to the upload"
98
+ ]
99
+ Note that internal variables like upload_result, image_result are not highlighted in the plan, and intermediate processing details are skipped.
87
100
  Now create a plan based on the conversation history. Do not include any other text or explanation in your response. Just the JSON object.
101
+ Note that the following tools are pre-loaded for the agent's use, and can be inluded in your plan if needed as internal variables (especially the llm tools)-\n
88
102
  """
89
103
 
90
104
 
91
- PLAYBOOK_GENERATING_PROMPT = """Now, you are tasked with generating the playbook function.
92
- Your response must be ONLY the Python code for the function.
93
- Do not include any other text, markdown, or explanations in your response.
94
- Your response should start with `def` or `async def`.
95
- The function should be a single, complete piece of code that can be executed independently, based on previously executed code snippets that executed correctly.
96
- The parameters of the function should be the same as the final confirmed playbook plan.
105
+ AGENT_BUILDER_GENERATING_PROMPT = """
106
+ You are tasked with generating granular, reusable Python code for an agent based on the final confirmed plan and the conversation history (user messages, assistant messages, and code executions).
107
+
108
+ Produce a set of small, single-purpose functions—typically one function per plan step—plus one top-level orchestrator function that calls the step functions in order to complete the task.
109
+
110
+ Rules-
111
+ - Do NOT include the searching and loading of functions. Assume required functions have already been loaded. Include imports you need.
112
+ - Your response must be **ONLY Python code**. No markdown or explanations.
113
+ - Define multiple top-level functions:
114
+ 1) One small, clear function for each plan step (as granular as practical).
115
+ 2) One top-level orchestrator function that calls the step functions in sequence to achieve the plan objectives.
116
+ - The orchestrator function's parameters **must exactly match the external variables** in the agent plan (the ones marked with backticks `` `variable_name` ``). Provide defaults exactly as specified in the plan when present. Variables in italics (i.e. enclosed in *...*) are internal and must not be orchestrator parameters.
117
+ - The orchestrator function MUST be declared with `def` or `async def` and be directly runnable with a single Python command (e.g., `image_generator(...)`). If it is async, assume the caller will `await` it.
118
+ - Step functions should accept only the inputs they need, return explicit outputs, and pass intermediate results forward via return values—not globals.
119
+ - Name functions in snake_case derived from their purpose/step. Use keyword arguments in calls; avoid positional-only calls.
120
+ - Keep the code self-contained and executable. Put imports at the top of the code. Do not nest functions unless strictly necessary.
121
+ - If previously executed code snippets exist, adapt and reuse their validated logic inside the appropriate step functions.
122
+ - Do not print the final output; return it from the orchestrator.
123
+
124
+ Example:
125
+
126
+ If the plan has:
127
+
128
+ "steps": [
129
+ "Receive creative description as image_prompt",
130
+ "Generate image using Gemini with style(default = 'comic')",
131
+ "Save temporary image internally as *temp_file_path*",
132
+ "Upload *temp_file_path* to OneDrive folder onedrive_parent_folder(default = 'root')"
133
+ ]
134
+
135
+ Then the functions should look like:
136
+
137
+ ```python
138
+ from typing import Dict
139
+
140
+ def generate_image(image_prompt: str, style: str = "comic") -> Dict:
141
+ # previously validated code to call Gemini
142
+ ...
143
+
144
+ def save_temp_image(image_result: Dict) -> str:
145
+ # previously validated code to write bytes to a temp file
146
+ ...
147
+
148
+ def upload_to_onedrive(temp_file_path: str, onedrive_parent_folder: str = "root") -> Dict:
149
+ # previously validated code to upload
150
+ ...
151
+
152
+ def image_generator(image_prompt: str, style: str = "comic", onedrive_parent_folder: str = "root") -> Dict:
153
+ image_result = generate_image(image_prompt=image_prompt, style=style)
154
+ temp_file_path = save_temp_image(image_result=image_result)
155
+ upload_result = upload_to_onedrive(temp_file_path=temp_file_path, onedrive_parent_folder=onedrive_parent_folder)
156
+ return upload_result
157
+ ```
158
+
159
+ Use this convention consistently to generate the final code.
160
+ Note that the following tools are pre-loaded for the agent's use, and can be included in your code-\n
161
+ """
162
+
163
+
164
+ AGENT_BUILDER_META_PROMPT = """
165
+ You are preparing metadata for a reusable agent based on the confirmed step-by-step plan.
166
+
167
+ TASK: Create a concise, human-friendly name and a short description for the agent.
168
+
169
+ INPUTS:
170
+ - Conversation context and plan steps will be provided in prior messages
171
+
172
+ REQUIREMENTS:
173
+ 1. Name: 3-6 words, Title Case, no punctuation except hyphens if needed
174
+ 2. Description: Single sentence, <= 140 characters, clearly states what the agent does
175
+
176
+ OUTPUT: Return ONLY a JSON object with exactly these keys:
177
+ {
178
+ "name": "...",
179
+ "description": "..."
180
+ }
97
181
  """
98
182
 
99
183
 
@@ -110,52 +194,88 @@ def make_safe_function_name(name: str) -> str:
110
194
  return safe_name
111
195
 
112
196
 
197
+ # Compile regex once for better performance
198
+ _RAISES_PATTERN = re.compile(r"\n\s*[Rr]aises\s*:.*$", re.DOTALL)
199
+
200
+
201
+ def _clean_docstring(docstring: str | None) -> str:
202
+ """Remove the 'Raises:' section and everything after it from a docstring."""
203
+ if not docstring:
204
+ return ""
205
+
206
+ # Use pre-compiled regex for better performance
207
+ cleaned = _RAISES_PATTERN.sub("", docstring)
208
+ return cleaned.strip()
209
+
210
+
211
+ def build_tool_definitions(tools: list[Callable]) -> tuple[list[str], dict[str, Callable]]:
212
+ tool_definitions = []
213
+ context = {}
214
+
215
+ # Pre-allocate lists for better performance
216
+ tool_definitions = [None] * len(tools)
217
+
218
+ for i, tool in enumerate(tools):
219
+ tool_name = tool.__name__
220
+ cleaned_docstring = _clean_docstring(tool.__doc__)
221
+
222
+ # Pre-compute string parts to avoid repeated string operations
223
+ async_prefix = "async " if inspect.iscoroutinefunction(tool) else ""
224
+ signature = str(inspect.signature(tool))
225
+
226
+ tool_definitions[i] = f'''{async_prefix}def {tool_name} {signature}:
227
+ """{cleaned_docstring}"""
228
+ ...'''
229
+ context[tool_name] = tool
230
+
231
+ return tool_definitions, context
232
+
233
+
113
234
  def create_default_prompt(
114
- tools: Sequence[StructuredTool],
115
- additional_tools: Sequence[StructuredTool],
235
+ tools: list[Callable],
236
+ additional_tools: list[Callable],
116
237
  base_prompt: str | None = None,
117
- playbook: object | None = None,
238
+ apps_string: str | None = None,
239
+ agent: object | None = None,
240
+ is_initial_prompt: bool = False,
118
241
  ):
119
- system_prompt = uneditable_prompt.strip() + (
120
- "\n\nIn addition to the Python Standard Library, you can use the following external functions:\n"
121
- )
122
- tools_context = {}
123
-
124
- for tool in tools + additional_tools:
125
- if hasattr(tool, "func") and tool.func is not None:
126
- tool_callable = tool.func
127
- is_async = False
128
- elif hasattr(tool, "coroutine") and tool.coroutine is not None:
129
- tool_callable = tool.coroutine
130
- is_async = True
131
- system_prompt += f'''{"async " if is_async else ""}def {tool.name} {str(inspect.signature(tool_callable))}:
132
- """{tool.description}"""
133
- ...
134
- '''
135
- safe_name = make_safe_function_name(tool.name)
136
- tools_context[safe_name] = tool_callable
137
-
138
- if base_prompt and base_prompt.strip():
139
- system_prompt += f"Your goal is to perform the following task:\n\n{base_prompt}"
140
-
141
- # Append existing playbook (plan + code) if provided
142
- try:
143
- if playbook and hasattr(playbook, "instructions"):
144
- pb = playbook.instructions or {}
145
- plan = pb.get("playbookPlan")
146
- code = pb.get("playbookScript")
147
- if plan or code:
148
- system_prompt += "\n\nExisting Playbook Provided:\n"
149
- if plan:
150
- if isinstance(plan, list):
151
- plan_block = "\n".join(f"- {str(s)}" for s in plan)
152
- else:
153
- plan_block = str(plan)
154
- system_prompt += f"Plan Steps:\n{plan_block}\n"
155
- if code:
156
- system_prompt += f"\nScript:\n```python\n{str(code)}\n```\n"
157
- except Exception:
158
- # Silently ignore formatting issues
159
- pass
242
+ if is_initial_prompt:
243
+ system_prompt = uneditable_prompt.strip()
244
+ if apps_string:
245
+ system_prompt += f"\n\n**Connected external applications (These apps have been logged into by the user):**\n{apps_string}\n\n Use `search_functions` to search for functions you can perform using the above. You can also discover more applications using the `search_functions` tool to find additional tools and integrations, if required. However, you MUST not assume the application when multiple apps are connected for a particular usecase.\n"
246
+ system_prompt += "\n\nIn addition to the Python Standard Library, you can use the following external functions:\n Carefully note which functions are normal and which functions are async. CRITICAL: Use `await` with async functions and async functions ONLY."
247
+ else:
248
+ system_prompt = ""
249
+
250
+ tool_definitions, tools_context = build_tool_definitions(tools + additional_tools)
251
+ system_prompt += "\n".join(tool_definitions)
252
+
253
+ if is_initial_prompt:
254
+ if base_prompt and base_prompt.strip():
255
+ system_prompt += (
256
+ f"\n\nUse the following information/instructions while completing your tasks:\n\n{base_prompt}"
257
+ )
258
+
259
+ # Append existing agent (plan + code) if provided
260
+ try:
261
+ if agent and hasattr(agent, "instructions"):
262
+ pb = agent.instructions or {}
263
+ plan = pb.get("plan")
264
+ code = pb.get("script")
265
+ if plan or code:
266
+ system_prompt += (
267
+ "\n\nYou have been provided an existing agent plan and code for performing a task.:\n"
268
+ )
269
+ if plan:
270
+ if isinstance(plan, list):
271
+ plan_block = "\n".join(f"- {str(s)}" for s in plan)
272
+ else:
273
+ plan_block = str(plan)
274
+ system_prompt += f"Plan Steps:\n{plan_block}\n"
275
+ if code:
276
+ system_prompt += f"\nScript:\n```python\n{str(code)}\n```\nThis function can be called by you using `execute_ipython_code`. Do NOT redefine the function, unless it has to be modified. For modifying it, you must enter agent_builder mode first so that it is modified in the database and not just the chat locally."
277
+ except Exception:
278
+ # Silently ignore formatting issues
279
+ pass
160
280
 
161
281
  return system_prompt, tools_context
@@ -1,6 +1,8 @@
1
+ import ast
1
2
  import contextlib
2
3
  import inspect
3
4
  import io
5
+ import pickle
4
6
  import queue
5
7
  import re
6
8
  import socket
@@ -13,7 +15,7 @@ from langchain_core.tools import tool
13
15
  from universal_mcp.agents.codeact0.utils import derive_context, inject_context, smart_truncate
14
16
 
15
17
 
16
- def eval_unsafe(
18
+ async def eval_unsafe(
17
19
  code: str, _locals: dict[str, Any], add_context: dict[str, Any], timeout: int = 180
18
20
  ) -> tuple[str, dict[str, Any], dict[str, Any]]:
19
21
  """
@@ -26,6 +28,7 @@ def eval_unsafe(
26
28
  EXCLUDE_TYPES = (
27
29
  types.ModuleType,
28
30
  type(re.match("", "")),
31
+ type(re.compile("")),
29
32
  type(threading.Lock()),
30
33
  type(threading.RLock()),
31
34
  threading.Event,
@@ -38,20 +41,16 @@ def eval_unsafe(
38
41
 
39
42
  result_container = {"output": "<no output>"}
40
43
 
41
- def target():
42
- try:
43
- with contextlib.redirect_stdout(io.StringIO()) as f:
44
- exec(code, _locals, _locals)
45
- result_container["output"] = f.getvalue() or "<code ran, no output printed to stdout>"
46
- except Exception as e:
47
- result_container["output"] = "Error during execution: " + str(e)
48
-
49
- thread = threading.Thread(target=target)
50
- thread.start()
51
- thread.join(timeout)
52
-
53
- if thread.is_alive():
54
- result_container["output"] = f"Code timeout: code execution exceeded {timeout} seconds."
44
+ try:
45
+ compiled_code = compile(code, "<string>", "exec", flags=ast.PyCF_ALLOW_TOP_LEVEL_AWAIT)
46
+ with contextlib.redirect_stdout(io.StringIO()) as f:
47
+ coroutine = eval(compiled_code, _locals, _locals)
48
+ # Await the coroutine to run the code if it's async
49
+ if coroutine:
50
+ await coroutine
51
+ result_container["output"] = f.getvalue() or "<code ran, no output printed to stdout>"
52
+ except Exception as e:
53
+ result_container["output"] = f"Error during execution: {type(e).__name__}: {e}"
55
54
 
56
55
  # If NameError for provider__tool occurred, append guidance (no retry)
57
56
  try:
@@ -64,7 +63,7 @@ def eval_unsafe(
64
63
  # Filter locals for picklable/storable variables
65
64
  all_vars = {}
66
65
  for key, value in _locals.items():
67
- if key == "__builtins__":
66
+ if key.startswith("__"):
68
67
  continue
69
68
  if inspect.iscoroutine(value) or inspect.iscoroutinefunction(value):
70
69
  continue
@@ -73,7 +72,12 @@ def eval_unsafe(
73
72
  if isinstance(value, EXCLUDE_TYPES):
74
73
  continue
75
74
  if not callable(value) or not hasattr(value, "__name__"):
76
- all_vars[key] = value
75
+ # Only keep if it can be pickled (serialized) successfully
76
+ try:
77
+ pickle.dumps(value)
78
+ all_vars[key] = value
79
+ except Exception:
80
+ pass
77
81
 
78
82
  # Safely derive context
79
83
  try:
@@ -1,15 +1,20 @@
1
- from typing import Annotated, Any, List
1
+ from typing import Annotated, Any
2
2
 
3
- from langgraph.prebuilt.chat_agent_executor import AgentState
3
+ from langchain.agents import AgentState
4
4
  from pydantic import BaseModel, Field
5
5
 
6
6
 
7
- class PlaybookPlan(BaseModel):
8
- steps: List[str] = Field(description="The steps of the playbook.")
7
+ class AgentBuilderPlan(BaseModel):
8
+ steps: list[str] = Field(description="The steps of the agent.")
9
9
 
10
10
 
11
- class PlaybookCode(BaseModel):
12
- code: str = Field(description="The Python code for the playbook.")
11
+ class AgentBuilderCode(BaseModel):
12
+ code: str = Field(description="The Python code for the agent.")
13
+
14
+
15
+ class AgentBuilderMeta(BaseModel):
16
+ name: str = Field(description="Concise, title-cased agent name (3-6 words).")
17
+ description: str = Field(description="Short, one-sentence description (<= 140 chars).")
13
18
 
14
19
 
15
20
  def _enqueue(left: list, right: list) -> list:
@@ -41,9 +46,13 @@ class CodeActState(AgentState):
41
46
  """Dictionary containing the execution context with available tools and variables."""
42
47
  add_context: dict[str, Any]
43
48
  """Dictionary containing the additional context (functions, classes, imports) to be added to the execution context."""
44
- playbook_mode: str | None
45
- """State for the playbook agent."""
49
+ agent_builder_mode: str | None
50
+ """State for the agent builder agent."""
46
51
  selected_tool_ids: Annotated[list[str], _enqueue]
47
52
  """Queue for tools exported from registry"""
48
53
  plan: list[str] | None
49
- """Plan for the playbook agent."""
54
+ """Plan for the agent builder agent."""
55
+ agent_name: str | None
56
+ """Generated agent name after confirmation."""
57
+ agent_description: str | None
58
+ """Generated short description after confirmation."""