universal-mcp-agents 0.1.19__tar.gz → 0.1.20rc1__tar.gz

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.

Files changed (76) hide show
  1. {universal_mcp_agents-0.1.19 → universal_mcp_agents-0.1.20rc1}/PKG-INFO +1 -1
  2. {universal_mcp_agents-0.1.19 → universal_mcp_agents-0.1.20rc1}/pyproject.toml +1 -1
  3. {universal_mcp_agents-0.1.19 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/agents/__init__.py +2 -5
  4. {universal_mcp_agents-0.1.19 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/agents/base.py +2 -2
  5. universal_mcp_agents-0.1.20rc1/src/universal_mcp/agents/codeact0/__init__.py +3 -0
  6. {universal_mcp_agents-0.1.19 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/agents/codeact0/__main__.py +2 -2
  7. universal_mcp_agents-0.1.19/src/universal_mcp/agents/codeact0/playbook_agent.py → universal_mcp_agents-0.1.20rc1/src/universal_mcp/agents/codeact0/agent.py +54 -122
  8. {universal_mcp_agents-0.1.19 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/agents/codeact0/langgraph_agent.py +1 -1
  9. {universal_mcp_agents-0.1.19 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/agents/codeact0/prompts.py +37 -7
  10. {universal_mcp_agents-0.1.19 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/agents/codeact0/sandbox.py +31 -1
  11. {universal_mcp_agents-0.1.19 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/agents/codeact0/state.py +3 -1
  12. universal_mcp_agents-0.1.20rc1/src/universal_mcp/agents/codeact0/tools.py +303 -0
  13. {universal_mcp_agents-0.1.19 → universal_mcp_agents-0.1.20rc1}/uv.lock +1 -1
  14. universal_mcp_agents-0.1.19/bech.py +0 -38
  15. universal_mcp_agents-0.1.19/src/universal_mcp/agents/codeact/__init__.py +0 -3
  16. universal_mcp_agents-0.1.19/src/universal_mcp/agents/codeact/__main__.py +0 -33
  17. universal_mcp_agents-0.1.19/src/universal_mcp/agents/codeact/agent.py +0 -240
  18. universal_mcp_agents-0.1.19/src/universal_mcp/agents/codeact/models.py +0 -11
  19. universal_mcp_agents-0.1.19/src/universal_mcp/agents/codeact/prompts.py +0 -82
  20. universal_mcp_agents-0.1.19/src/universal_mcp/agents/codeact/sandbox.py +0 -85
  21. universal_mcp_agents-0.1.19/src/universal_mcp/agents/codeact/state.py +0 -11
  22. universal_mcp_agents-0.1.19/src/universal_mcp/agents/codeact/utils.py +0 -68
  23. universal_mcp_agents-0.1.19/src/universal_mcp/agents/codeact0/__init__.py +0 -4
  24. universal_mcp_agents-0.1.19/src/universal_mcp/agents/codeact0/agent.py +0 -144
  25. universal_mcp_agents-0.1.19/src/universal_mcp/agents/codeact0/tools.py +0 -198
  26. {universal_mcp_agents-0.1.19 → universal_mcp_agents-0.1.20rc1}/.github/workflows/evals.yml +0 -0
  27. {universal_mcp_agents-0.1.19 → universal_mcp_agents-0.1.20rc1}/.github/workflows/lint.yml +0 -0
  28. {universal_mcp_agents-0.1.19 → universal_mcp_agents-0.1.20rc1}/.github/workflows/release-please.yml +0 -0
  29. {universal_mcp_agents-0.1.19 → universal_mcp_agents-0.1.20rc1}/.github/workflows/tests.yml +0 -0
  30. {universal_mcp_agents-0.1.19 → universal_mcp_agents-0.1.20rc1}/.gitignore +0 -0
  31. {universal_mcp_agents-0.1.19 → universal_mcp_agents-0.1.20rc1}/.pre-commit-config.yaml +0 -0
  32. {universal_mcp_agents-0.1.19 → universal_mcp_agents-0.1.20rc1}/GEMINI.md +0 -0
  33. {universal_mcp_agents-0.1.19 → universal_mcp_agents-0.1.20rc1}/PROMPTS.md +0 -0
  34. {universal_mcp_agents-0.1.19 → universal_mcp_agents-0.1.20rc1}/README.md +0 -0
  35. {universal_mcp_agents-0.1.19 → universal_mcp_agents-0.1.20rc1}/bump_and_release.sh +0 -0
  36. {universal_mcp_agents-0.1.19 → universal_mcp_agents-0.1.20rc1}/src/evals/__init__.py +0 -0
  37. {universal_mcp_agents-0.1.19 → universal_mcp_agents-0.1.20rc1}/src/evals/dataset.py +0 -0
  38. {universal_mcp_agents-0.1.19 → universal_mcp_agents-0.1.20rc1}/src/evals/datasets/codeact.jsonl +0 -0
  39. {universal_mcp_agents-0.1.19 → universal_mcp_agents-0.1.20rc1}/src/evals/datasets/exact.jsonl +0 -0
  40. {universal_mcp_agents-0.1.19 → universal_mcp_agents-0.1.20rc1}/src/evals/datasets/tasks.jsonl +0 -0
  41. {universal_mcp_agents-0.1.19 → universal_mcp_agents-0.1.20rc1}/src/evals/evaluators.py +0 -0
  42. {universal_mcp_agents-0.1.19 → universal_mcp_agents-0.1.20rc1}/src/evals/prompts.py +0 -0
  43. {universal_mcp_agents-0.1.19 → universal_mcp_agents-0.1.20rc1}/src/evals/run.py +0 -0
  44. {universal_mcp_agents-0.1.19 → universal_mcp_agents-0.1.20rc1}/src/evals/utils.py +0 -0
  45. {universal_mcp_agents-0.1.19 → universal_mcp_agents-0.1.20rc1}/src/tests/test_agents.py +0 -0
  46. {universal_mcp_agents-0.1.19 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/agents/bigtool/__init__.py +0 -0
  47. {universal_mcp_agents-0.1.19 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/agents/bigtool/__main__.py +0 -0
  48. {universal_mcp_agents-0.1.19 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/agents/bigtool/agent.py +0 -0
  49. {universal_mcp_agents-0.1.19 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/agents/bigtool/context.py +0 -0
  50. {universal_mcp_agents-0.1.19 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/agents/bigtool/graph.py +0 -0
  51. {universal_mcp_agents-0.1.19 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/agents/bigtool/prompts.py +0 -0
  52. {universal_mcp_agents-0.1.19 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/agents/bigtool/state.py +0 -0
  53. {universal_mcp_agents-0.1.19 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/agents/bigtool/tools.py +0 -0
  54. {universal_mcp_agents-0.1.19 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/agents/builder/__main__.py +0 -0
  55. {universal_mcp_agents-0.1.19 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/agents/builder/builder.py +0 -0
  56. {universal_mcp_agents-0.1.19 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/agents/builder/helper.py +0 -0
  57. {universal_mcp_agents-0.1.19 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/agents/builder/prompts.py +0 -0
  58. {universal_mcp_agents-0.1.19 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/agents/builder/state.py +0 -0
  59. {universal_mcp_agents-0.1.19 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/agents/cli.py +0 -0
  60. {universal_mcp_agents-0.1.19 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/agents/codeact0/config.py +0 -0
  61. {universal_mcp_agents-0.1.19 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/agents/codeact0/llm_tool.py +0 -0
  62. {universal_mcp_agents-0.1.19 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/agents/codeact0/utils.py +0 -0
  63. {universal_mcp_agents-0.1.19 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/agents/hil.py +0 -0
  64. {universal_mcp_agents-0.1.19 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/agents/llm.py +0 -0
  65. {universal_mcp_agents-0.1.19 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/agents/react.py +0 -0
  66. {universal_mcp_agents-0.1.19 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/agents/sandbox.py +0 -0
  67. {universal_mcp_agents-0.1.19 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/agents/shared/__main__.py +0 -0
  68. {universal_mcp_agents-0.1.19 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/agents/shared/prompts.py +0 -0
  69. {universal_mcp_agents-0.1.19 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/agents/shared/tool_node.py +0 -0
  70. {universal_mcp_agents-0.1.19 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/agents/simple.py +0 -0
  71. {universal_mcp_agents-0.1.19 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/agents/utils.py +0 -0
  72. {universal_mcp_agents-0.1.19 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/applications/filesystem/__init__.py +0 -0
  73. {universal_mcp_agents-0.1.19 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/applications/filesystem/app.py +0 -0
  74. {universal_mcp_agents-0.1.19 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/applications/llm/__init__.py +0 -0
  75. {universal_mcp_agents-0.1.19 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/applications/llm/app.py +0 -0
  76. {universal_mcp_agents-0.1.19 → universal_mcp_agents-0.1.20rc1}/src/universal_mcp/applications/ui/app.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: universal-mcp-agents
3
- Version: 0.1.19
3
+ Version: 0.1.20rc1
4
4
  Summary: Add your description here
5
5
  Project-URL: Homepage, https://github.com/universal-mcp/applications
6
6
  Project-URL: Repository, https://github.com/universal-mcp/applications
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
6
6
 
7
7
  [project]
8
8
  name = "universal-mcp-agents"
9
- version = "0.1.19"
9
+ version = "0.1.20-rc1"
10
10
  description = "Add your description here"
11
11
  readme = "README.md"
12
12
  authors = [
@@ -3,8 +3,7 @@ from typing import Literal
3
3
  from universal_mcp.agents.base import BaseAgent
4
4
  from universal_mcp.agents.bigtool import BigToolAgent
5
5
  from universal_mcp.agents.builder.builder import BuilderAgent
6
- from universal_mcp.agents.codeact import CodeActAgent as CodeActScript
7
- from universal_mcp.agents.codeact0 import CodeActPlaybookAgent as CodeActRepl
6
+ from universal_mcp.agents.codeact0 import CodeActPlaybookAgent
8
7
  from universal_mcp.agents.react import ReactAgent
9
8
  from universal_mcp.agents.simple import SimpleAgent
10
9
 
@@ -20,10 +19,8 @@ def get_agent(
20
19
  return BuilderAgent
21
20
  elif agent_name == "bigtool":
22
21
  return BigToolAgent
23
- elif agent_name == "codeact-script":
24
- return CodeActScript
25
22
  elif agent_name == "codeact-repl":
26
- return CodeActRepl
23
+ return CodeActPlaybookAgent
27
24
  else:
28
25
  raise ValueError(
29
26
  f"Unknown agent: {agent_name}. Possible values: react, simple, builder, bigtool, codeact-script, codeact-repl"
@@ -49,7 +49,7 @@ class BaseAgent:
49
49
  run_metadata.update(metadata)
50
50
 
51
51
  run_config = {
52
- "recursion_limit": 25,
52
+ "recursion_limit": 50,
53
53
  "configurable": {"thread_id": thread_id},
54
54
  "metadata": run_metadata,
55
55
  "run_id": thread_id,
@@ -116,7 +116,7 @@ class BaseAgent:
116
116
  run_metadata.update(metadata)
117
117
 
118
118
  run_config = {
119
- "recursion_limit": 25,
119
+ "recursion_limit": 50,
120
120
  "configurable": {"thread_id": thread_id},
121
121
  "metadata": run_metadata,
122
122
  "run_id": thread_id,
@@ -0,0 +1,3 @@
1
+ from .agent import CodeActPlaybookAgent
2
+
3
+ __all__ = ["CodeActPlaybookAgent"]
@@ -4,13 +4,13 @@ from langgraph.checkpoint.memory import MemorySaver
4
4
  from rich import print
5
5
  from universal_mcp.agentr.registry import AgentrRegistry
6
6
 
7
- from universal_mcp.agents.codeact0.agent import CodeActAgent
7
+ from universal_mcp.agents.codeact0.agent import CodeActPlaybookAgent
8
8
  from universal_mcp.agents.utils import messages_to_list
9
9
 
10
10
 
11
11
  async def main():
12
12
  memory = MemorySaver()
13
- agent = CodeActAgent(
13
+ agent = CodeActPlaybookAgent(
14
14
  name="CodeAct Agent",
15
15
  instructions="Be very concise in your answers.",
16
16
  model="anthropic:claude-4-sonnet-20250514",
@@ -1,4 +1,3 @@
1
- import inspect
2
1
  import json
3
2
  import re
4
3
  from collections.abc import Callable
@@ -6,7 +5,6 @@ from typing import Literal, cast
6
5
 
7
6
  from langchain_core.messages import AIMessage, ToolMessage
8
7
  from langchain_core.tools import StructuredTool
9
- from langchain_core.tools import tool as create_tool
10
8
  from langgraph.checkpoint.base import BaseCheckpointSaver
11
9
  from langgraph.graph import START, StateGraph
12
10
  from langgraph.types import Command, RetryPolicy
@@ -16,50 +14,21 @@ from universal_mcp.types import ToolConfig, ToolFormat
16
14
  from universal_mcp.agents.base import BaseAgent
17
15
  from universal_mcp.agents.codeact0.llm_tool import ai_classify, call_llm, data_extractor, smart_print
18
16
  from universal_mcp.agents.codeact0.prompts import (
17
+ PLAYBOOK_CONFIRMING_PROMPT,
18
+ PLAYBOOK_GENERATING_PROMPT,
19
+ PLAYBOOK_PLANNING_PROMPT,
19
20
  create_default_prompt,
20
21
  )
21
- from universal_mcp.agents.codeact0.sandbox import eval_unsafe, execute_ipython_cell
22
+ from universal_mcp.agents.codeact0.sandbox import eval_unsafe, execute_ipython_cell, handle_execute_ipython_cell
22
23
  from universal_mcp.agents.codeact0.state import CodeActState
23
24
  from universal_mcp.agents.codeact0.tools import (
24
25
  create_meta_tools,
25
26
  enter_playbook_mode,
26
27
  get_valid_tools,
27
28
  )
28
- from universal_mcp.agents.codeact0.utils import inject_context, smart_truncate
29
29
  from universal_mcp.agents.llm import load_chat_model
30
30
  from universal_mcp.agents.utils import convert_tool_ids_to_dict, filter_retry_on, get_message_text
31
31
 
32
- PLAYBOOK_PLANNING_PROMPT = """Now, you are tasked with creating a reusable playbook from the user's previous workflow.
33
-
34
- TASK: Analyze the conversation history and code execution to create a step-by-step plan for a reusable function. Do not include the searching and loading of tools. Assume that the tools have already been loaded.
35
-
36
- Your plan should:
37
- 1. Identify the key steps in the workflow
38
- 2. Mark user-specific variables that should become the main playbook function parameters using `variable_name` syntax. Intermediate variables should not be highlighted using ``
39
- 3. Keep the logic generic and reusable
40
- 4. Be clear and concise
41
-
42
- Example:
43
- ```
44
- 1. Connect to database using `db_connection_string`
45
- 2. Query user data for `user_id`
46
- 3. Process results and calculate `metric_name`
47
- 4. Send notification to `email_address`
48
- ```
49
-
50
- Now create a plan based on the conversation history. Enclose it between ``` and ```. Ask the user if the plan is okay."""
51
-
52
-
53
- PLAYBOOK_CONFIRMING_PROMPT = """Now, you are tasked with confirming the playbook plan. Return True if the user is happy with the plan, False otherwise. Do not say anything else in your response. The user response will be the last message in the chain.
54
- """
55
-
56
- PLAYBOOK_GENERATING_PROMPT = """Now, you are tasked with generating the playbook function. Return the function in Python code.
57
- Do not include any other text in your response.
58
- The function should be a single, complete piece of code that can be executed independently, based on previously executed code snippets that executed correctly.
59
- The parameters of the function should be the same as the final confirmed playbook plan.
60
- Do not include anything other than python code in your response
61
- """
62
-
63
32
 
64
33
  class CodeActPlaybookAgent(BaseAgent):
65
34
  def __init__(
@@ -92,24 +61,19 @@ class CodeActPlaybookAgent(BaseAgent):
92
61
  async def _build_graph(self):
93
62
  meta_tools = create_meta_tools(self.registry)
94
63
  additional_tools = [smart_print, data_extractor, ai_classify, call_llm, meta_tools["web_search"]]
95
- self.additional_tools = [t if isinstance(t, StructuredTool) else create_tool(t) for t in additional_tools]
96
-
97
- async def call_model(state: CodeActState) -> Command[Literal["sandbox", "execute_tools"]]:
98
- self.exported_tools = []
99
- if self.tools_config:
100
- # Convert dict format to list format if needed
101
- if isinstance(self.tools_config, dict):
102
- self.tools_config = [
103
- f"{provider}__{tool}" for provider, tools in self.tools_config.items() for tool in tools
104
- ]
105
- if not self.registry:
106
- raise ValueError("Tools are configured but no registry is provided")
107
- # Langchain tools are fine
108
- self.tools_config.extend(state.get("selected_tool_ids", []))
109
- self.exported_tools = await self.registry.export_tools(self.tools_config, ToolFormat.LANGCHAIN)
110
- self.final_instructions, self.tools_context = create_default_prompt(
111
- self.exported_tools, self.additional_tools, self.instructions
112
- )
64
+ self.additional_tools = [
65
+ t if isinstance(t, StructuredTool) else StructuredTool.from_function(t) for t in additional_tools
66
+ ]
67
+ if self.tools_config:
68
+ # Convert dict format to list format if needed
69
+ if isinstance(self.tools_config, dict):
70
+ self.tools_config = [
71
+ f"{provider}__{tool}" for provider, tools in self.tools_config.items() for tool in tools
72
+ ]
73
+ if not self.registry:
74
+ raise ValueError("Tools are configured but no registry is provided")
75
+
76
+ async def call_model(state: CodeActState) -> Command[Literal["execute_tools"]]:
113
77
  messages = [{"role": "system", "content": self.final_instructions}] + state["messages"]
114
78
 
115
79
  # Run the model and potentially loop for reflection
@@ -128,25 +92,7 @@ class CodeActPlaybookAgent(BaseAgent):
128
92
  else:
129
93
  return Command(update={"messages": [response], "model_with_tools": model_with_tools})
130
94
 
131
- # if response.tool_calls:
132
- # if len(response.tool_calls) > 1:
133
- # raise Exception("Not possible in Claude with llm.bind_tools(tools=tools, tool_choice='auto')")
134
- # if response.tool_calls[0]["name"] == "enter_playbook_mode":
135
- # return Command(goto="playbook", update = {"playbook_mode": "planning"})
136
- # if response.tool_calls[0]["name"] != "execute_ipython_cell":
137
- # raise Exception(
138
- # f"Unexpected tool call: {response.tool_calls[0]['name']}. Expected 'execute_ipython_cell'."
139
- # )
140
- # if (
141
- # response.tool_calls[0]["args"].get("snippet") is None
142
- # or not response.tool_calls[0]["args"]["snippet"].strip()
143
- # ):
144
- # raise Exception("Tool call 'execute_ipython_cell' requires a non-empty 'snippet' argument.")
145
- # return Command(goto="sandbox", update={"messages": [response]})
146
- # else:
147
- # return Command(update={"messages": [response]})
148
-
149
- async def execute_tools(state: CodeActState) -> Command[Literal["call_model", "playbook", "sandbox"]]:
95
+ async def execute_tools(state: CodeActState) -> Command[Literal["call_model", "playbook"]]:
150
96
  """Execute tool calls"""
151
97
  last_message = state["messages"][-1]
152
98
  tool_calls = last_message.tool_calls if isinstance(last_message, AIMessage) else []
@@ -156,6 +102,8 @@ class CodeActPlaybookAgent(BaseAgent):
156
102
  ask_user = False
157
103
  ai_msg = ""
158
104
  tool_result = ""
105
+ effective_previous_add_context = state.get("add_context", {})
106
+ effective_existing_context = state.get("context", {})
159
107
 
160
108
  for tool_call in tool_calls:
161
109
  try:
@@ -170,7 +118,17 @@ class CodeActPlaybookAgent(BaseAgent):
170
118
  update={"playbook_mode": "planning", "messages": [tool_message]}, # Entered Playbook mode
171
119
  )
172
120
  elif tool_call["name"] == "execute_ipython_cell":
173
- return Command(goto="sandbox")
121
+ code = tool_call["args"]["snippet"]
122
+ output, new_context, new_add_context = await handle_execute_ipython_cell(
123
+ code,
124
+ self.tools_context,
125
+ self.eval_fn,
126
+ effective_previous_add_context,
127
+ effective_existing_context,
128
+ )
129
+ effective_existing_context = new_context
130
+ effective_previous_add_context = new_add_context
131
+ tool_result = output
174
132
  elif tool_call["name"] == "load_functions": # Handle load_functions separately
175
133
  valid_tools, unconnected_links = await get_valid_tools(
176
134
  tool_ids=tool_call["args"]["tool_ids"], registry=self.registry
@@ -201,61 +159,31 @@ class CodeActPlaybookAgent(BaseAgent):
201
159
 
202
160
  if new_tool_ids:
203
161
  self.tools_config.extend(new_tool_ids)
204
- self.exported_tools = await self.registry.export_tools(self.tools_config, ToolFormat.LANGCHAIN)
162
+ self.exported_tools = await self.registry.export_tools(new_tool_ids, ToolFormat.LANGCHAIN)
205
163
  self.final_instructions, self.tools_context = create_default_prompt(
206
164
  self.exported_tools, self.additional_tools, self.instructions
207
165
  )
208
-
209
166
  if ask_user:
210
167
  tool_messages.append(AIMessage(content=ai_msg))
211
- return Command(update={"messages": tool_messages, "selected_tool_ids": new_tool_ids})
212
-
213
- return Command(goto="call_model", update={"messages": tool_messages, "selected_tool_ids": new_tool_ids})
214
-
215
- # If eval_fn is a async, we define async node function.
216
- if inspect.iscoroutinefunction(self.eval_fn):
217
- raise ValueError("eval_fn must be a synchronous function, not a coroutine.")
218
- # async def sandbox(state: StateSchema):
219
- # existing_context = state.get("context", {})
220
- # context = {**existing_context, **tools_context}
221
- # # Execute the script in the sandbox
222
- # output, new_vars = await eval_fn(state["script"], context)
223
- # new_context = {**existing_context, **new_vars}
224
- # return {
225
- # "messages": [{"role": "user", "content": output}],
226
- # "context": new_context,
227
- # }
228
- else:
229
-
230
- def sandbox(state: CodeActState) -> Command[Literal["call_model"]]:
231
- tool_call = state["messages"][-1].tool_calls[0] # type: ignore
232
- code = tool_call["args"]["snippet"]
233
- previous_add_context = state.get("add_context", {})
234
- add_context = inject_context(previous_add_context, self.tools_context)
235
- existing_context = state.get("context", {})
236
- context = {**existing_context, **add_context}
237
- # Execute the script in the sandbox
238
-
239
- output, new_context, new_add_context = self.eval_fn(
240
- code, context, previous_add_context, 180
241
- ) # default timeout 3 min
242
- output = smart_truncate(output)
243
-
244
168
  return Command(
245
- goto="call_model",
246
169
  update={
247
- "messages": [
248
- ToolMessage(
249
- content=output,
250
- name=tool_call["name"],
251
- tool_call_id=tool_call["id"],
252
- )
253
- ],
254
- "context": new_context,
255
- "add_context": new_add_context,
256
- },
170
+ "messages": tool_messages,
171
+ "selected_tool_ids": new_tool_ids,
172
+ "context": effective_existing_context,
173
+ "add_context": effective_previous_add_context,
174
+ }
257
175
  )
258
176
 
177
+ return Command(
178
+ goto="call_model",
179
+ update={
180
+ "messages": tool_messages,
181
+ "selected_tool_ids": new_tool_ids,
182
+ "context": effective_existing_context,
183
+ "add_context": effective_previous_add_context,
184
+ },
185
+ )
186
+
259
187
  def playbook(state: CodeActState) -> Command[Literal["call_model"]]:
260
188
  playbook_mode = state.get("playbook_mode")
261
189
  if playbook_mode == "planning":
@@ -343,16 +271,20 @@ class CodeActPlaybookAgent(BaseAgent):
343
271
  update={"messages": [mock_assistant_message, mock_exit_tool_response], "playbook_mode": "normal"}
344
272
  )
345
273
 
346
- def route_entry(state: CodeActState) -> Literal["call_model", "playbook"]:
274
+ async def route_entry(state: CodeActState) -> Literal["call_model", "playbook"]:
347
275
  """Route to either normal mode or playbook creation"""
276
+ self.exported_tools = []
277
+ self.tools_config.extend(state.get("selected_tool_ids", []))
278
+ self.exported_tools = await self.registry.export_tools(self.tools_config, ToolFormat.LANGCHAIN)
279
+ self.final_instructions, self.tools_context = create_default_prompt(
280
+ self.exported_tools, self.additional_tools, self.instructions
281
+ )
348
282
  if state.get("playbook_mode") in ["planning", "confirming", "generating"]:
349
283
  return "playbook"
350
-
351
284
  return "call_model"
352
285
 
353
286
  agent = StateGraph(state_schema=CodeActState)
354
287
  agent.add_node(call_model, retry_policy=RetryPolicy(max_attempts=3, retry_on=filter_retry_on))
355
- agent.add_node(sandbox)
356
288
  agent.add_node(playbook)
357
289
  agent.add_node(execute_tools)
358
290
  agent.add_conditional_edges(START, route_entry)
@@ -1,6 +1,6 @@
1
1
  from universal_mcp.agentr.registry import AgentrRegistry
2
2
 
3
- from universal_mcp.agents.codeact0.playbook_agent import CodeActPlaybookAgent
3
+ from universal_mcp.agents.codeact0 import CodeActPlaybookAgent
4
4
 
5
5
 
6
6
  async def agent():
@@ -10,16 +10,15 @@ uneditable_prompt = """
10
10
  You are **Wingmen**, an AI Assistant created by AgentR — a creative, straight-forward, and direct principal software engineer with access to tools.
11
11
 
12
12
  Your job is to answer the user's question or perform the task they ask for.
13
- - 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.
13
+ - 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.
14
14
  - For task requiring operations or access to external resources, you should achieve the task by executing Python code snippets.
15
15
  - You have access to `execute_ipython_cell` tool that allows you to execute Python code in an IPython notebook cell.
16
- - 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.
17
- - Prefer pre-loaded or functions already available when possible.
16
+ - 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.
18
17
  - Prioritize connected applications over unconnected ones from the output of `search_functions`.
19
- - When multiple apps are connected, or none of the apps are connected, ask the user to choose the application(s).
20
- - 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.
18
+ - 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.
19
+ - 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.
21
20
  - 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.
22
- - 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.
21
+ - 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.
23
22
  - If needed, feel free to ask for more information from the user (without using the `execute_ipython_cell` tool) to clarify the task.
24
23
 
25
24
  GUIDELINES for writing code:
@@ -27,7 +26,7 @@ GUIDELINES for writing code:
27
26
  - 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.
28
27
  - 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.
29
28
  - In a single code snippet, try to achieve as much as possible.
30
- - You can only import libraries that come pre-installed with Python. For external functions, use the search and load tools to access them in the code.
29
+ - 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.
31
30
  - 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.
32
31
  - Call all functions using keyword arguments only, never positional arguments.
33
32
  - Async Functions (Critical): Use them only as follows-
@@ -46,6 +45,37 @@ Rules:
46
45
  - Never nest asyncio.run() calls
47
46
  """
48
47
 
48
+ PLAYBOOK_PLANNING_PROMPT = """Now, you are tasked with creating a reusable playbook from the user's previous workflow.
49
+
50
+ TASK: Analyze the conversation history and code execution to create a step-by-step plan for a reusable function. Do not include the searching and loading of tools. Assume that the tools have already been loaded.
51
+
52
+ Your plan should:
53
+ 1. Identify the key steps in the workflow
54
+ 2. Mark user-specific variables that should become the main playbook function parameters using `variable_name` syntax. Intermediate variables should not be highlighted using ``
55
+ 3. Keep the logic generic and reusable
56
+ 4. Be clear and concise
57
+
58
+ Example:
59
+ ```
60
+ 1. Connect to database using `db_connection_string`
61
+ 2. Query user data for `user_id`
62
+ 3. Process results and calculate `metric_name`
63
+ 4. Send notification to `email_address`
64
+ ```
65
+
66
+ Now create a plan based on the conversation history. Enclose it between ``` and ```. Ask the user if the plan is okay."""
67
+
68
+
69
+ PLAYBOOK_CONFIRMING_PROMPT = """Now, you are tasked with confirming the playbook plan. Return True if the user is happy with the plan, False otherwise. Do not say anything else in your response. The user response will be the last message in the chain.
70
+ """
71
+
72
+ PLAYBOOK_GENERATING_PROMPT = """Now, you are tasked with generating the playbook function. Return the function in Python code.
73
+ Do not include any other text in your response.
74
+ The function should be a single, complete piece of code that can be executed independently, based on previously executed code snippets that executed correctly.
75
+ The parameters of the function should be the same as the final confirmed playbook plan.
76
+ Do not include anything other than python code in your response
77
+ """
78
+
49
79
 
50
80
  def make_safe_function_name(name: str) -> str:
51
81
  """Convert a tool name to a valid Python function name."""
@@ -10,7 +10,7 @@ from typing import Any
10
10
 
11
11
  from langchain_core.tools import tool
12
12
 
13
- from universal_mcp.agents.codeact0.utils import derive_context
13
+ from universal_mcp.agents.codeact0.utils import derive_context, inject_context, smart_truncate
14
14
 
15
15
 
16
16
  def eval_unsafe(
@@ -53,6 +53,14 @@ def eval_unsafe(
53
53
  if thread.is_alive():
54
54
  result_container["output"] = f"Code timeout: code execution exceeded {timeout} seconds."
55
55
 
56
+ # If NameError for provider__tool occurred, append guidance (no retry)
57
+ try:
58
+ m = re.search(r"NameError:\s*name\s*'([^']+)'\s*is\s*not\s*defined", result_container["output"])
59
+ if m and "__" in m.group(1):
60
+ result_container["output"] += "\nHint: If it is a valid tool, load it before running this snippet."
61
+ except Exception:
62
+ pass
63
+
56
64
  # Filter locals for picklable/storable variables
57
65
  all_vars = {}
58
66
  for key, value in _locals.items():
@@ -99,3 +107,25 @@ def execute_ipython_cell(snippet: str) -> str:
99
107
 
100
108
  # Your actual execution logic would go here
101
109
  return f"Successfully executed {len(snippet)} characters of Python code"
110
+
111
+
112
+ async def handle_execute_ipython_cell(
113
+ code: str,
114
+ tools_context: dict[str, Any],
115
+ eval_fn,
116
+ effective_previous_add_context: dict[str, Any],
117
+ effective_existing_context: dict[str, Any],
118
+ ) -> tuple[str, dict[str, Any], dict[str, Any]]:
119
+ """
120
+ Execute a code cell with shared state, supporting both sync and async eval functions.
121
+
122
+ Returns (output, new_context, new_add_context).
123
+ """
124
+ add_context = inject_context(effective_previous_add_context, tools_context)
125
+ context = {**effective_existing_context, **add_context}
126
+ if inspect.iscoroutinefunction(eval_fn):
127
+ output, new_context, new_add_context = await eval_fn(code, context, effective_previous_add_context, 180)
128
+ else:
129
+ output, new_context, new_add_context = eval_fn(code, context, effective_previous_add_context, 180)
130
+ output = smart_truncate(output)
131
+ return output, new_context, new_add_context
@@ -6,6 +6,8 @@ from langgraph.prebuilt.chat_agent_executor import AgentState
6
6
  def _enqueue(left: list, right: list) -> list:
7
7
  """Treat left as a FIFO queue, append new items from right (preserve order),
8
8
  keep items unique, and cap total size to 20 (drop oldest items)."""
9
+
10
+ # Tool ifd are unique
9
11
  max_size = 30
10
12
  preferred_size = 20
11
13
  if len(right) > preferred_size:
@@ -20,7 +22,7 @@ def _enqueue(left: list, right: list) -> list:
20
22
  if len(queue) > preferred_size:
21
23
  queue = queue[-preferred_size:]
22
24
 
23
- return queue
25
+ return list(set(queue))
24
26
 
25
27
 
26
28
  class CodeActState(AgentState):