From 2d728fdefe05a89594a38492e68aaf26f5cd2c81 Mon Sep 17 00:00:00 2001 From: ilonae Date: Sun, 5 Apr 2026 03:02:41 +0700 Subject: [PATCH 1/2] feat: implement session-based output organization and improve tool parameter validation Session Output Organization (Fixes #1032): - Generate unique timestamped session folders at agent startup - Create organized subfolders: guides/, code/, data/ - Dynamically inject session folder information into system prompt - Add get_session_path() helper method for tools to reference session locations - Automatically create folder structure with proper validation Tool Parameter Validation Improvements: - Enhanced system prompt with EXPLICIT guidelines for tool usage - Detailed requirements for str_replace_editor 'create' command - Clear instructions that 'file_text' parameter is NEVER optional for 'create' - Explicit guidance for python_execute, browser_use_tool, and ask_human tools - Added CRITICAL instruction: always provide ALL required parameters Error Handling Enhancements: - Improved JSON parse error messages with detailed context - Better error guidance when tool calls have missing parameters - Enhanced logging with parameter information for debugging Configuration Fixes: - Handle optional Daytona configuration gracefully - Fixed initialization when daytona_api_key is not provided --- .gitignore | 4 +++ app/agent/manus.py | 66 +++++++++++++++++++++++++++++++++++++++++-- app/agent/toolcall.py | 10 +++++-- app/config.py | 2 +- app/prompt/manus.py | 17 +++++++++-- requirements.txt | 14 +++++---- 6 files changed, 99 insertions(+), 14 deletions(-) diff --git a/.gitignore b/.gitignore index 41dbbf2d8..7f37eb461 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,8 @@ ### Project-specific ### +# Config with secrets +config/config.toml +config/config.daytona.toml + # Logs logs/ diff --git a/app/agent/manus.py b/app/agent/manus.py index df40edbba..67cf32749 100644 --- a/app/agent/manus.py +++ b/app/agent/manus.py @@ -1,3 +1,5 @@ +import os +from datetime import datetime from typing import Dict, List, Optional from pydantic import Field, model_validator @@ -24,6 +26,36 @@ class Manus(ToolCallAgent): system_prompt: str = SYSTEM_PROMPT.format(directory=config.workspace_root) next_step_prompt: str = NEXT_STEP_PROMPT + def get_system_prompt_with_session(self) -> str: + """Get system prompt with session folder information.""" + base_prompt = self.system_prompt + if self.session_folder: + session_info = ( + f"\n\nSESSION OUTPUT FOLDER: All research outputs should be saved to: {self.session_folder}\n" + f"Use the following subfolder structure:\n" + f"- Guides/documentation: {self.session_folder}/guides/\n" + f"- Code files: {self.session_folder}/code/\n" + f"- Data files: {self.session_folder}/data/\n" + ) + return base_prompt + session_info + return base_prompt + + def get_session_path(self, subfolder: str = "") -> str: + """Get the full path for saving files in the session folder. + + Args: + subfolder: One of 'guides', 'code', 'data', or empty string for session root + + Returns: + Full path to the requested subfolder + """ + if not self.session_folder: + return "" + + if subfolder: + return os.path.join(self.session_folder, subfolder) + return self.session_folder + max_observe: int = 10000 max_steps: int = 20 @@ -50,6 +82,29 @@ class Manus(ToolCallAgent): ) # server_id -> url/command _initialized: bool = False + # Session output folder for organizing research outputs + session_folder: Optional[str] = None + + @model_validator(mode="after") + def initialize_session_folder(self) -> "Manus": + """Initialize session output folder with unique timestamp.""" + if not self.session_folder: + # Generate unique session folder with timestamp + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + slug = "session" # Could be customized based on task type + session_path = os.path.join("output", f"{slug}_{timestamp}") + + # Create the session folder and subfolders + os.makedirs(session_path, exist_ok=True) + os.makedirs(os.path.join(session_path, "guides"), exist_ok=True) + os.makedirs(os.path.join(session_path, "code"), exist_ok=True) + os.makedirs(os.path.join(session_path, "data"), exist_ok=True) + + self.session_folder = session_path + logger.info(f"📁 Created session output folder: {session_path}") + + return self + @model_validator(mode="after") def initialize_helper(self) -> "Manus": """Initialize basic components synchronously.""" @@ -143,7 +198,11 @@ async def think(self) -> bool: await self.initialize_mcp_servers() self._initialized = True - original_prompt = self.next_step_prompt + # Update system prompt with session folder information + original_system_prompt = self.system_prompt + self.system_prompt = self.get_system_prompt_with_session() + + original_next_step_prompt = self.next_step_prompt recent_messages = self.memory.messages[-3:] if self.memory.messages else [] browser_in_use = any( tc.function.name == BrowserUseTool().name @@ -159,7 +218,8 @@ async def think(self) -> bool: result = await super().think() - # Restore original prompt - self.next_step_prompt = original_prompt + # Restore original prompts + self.system_prompt = original_system_prompt + self.next_step_prompt = original_next_step_prompt return result diff --git a/app/agent/toolcall.py b/app/agent/toolcall.py index 65f31d988..ed91e80dd 100644 --- a/app/agent/toolcall.py +++ b/app/agent/toolcall.py @@ -196,10 +196,14 @@ async def execute_tool(self, command: ToolCall) -> str: ) return observation - except json.JSONDecodeError: - error_msg = f"Error parsing arguments for {name}: Invalid JSON format" + except json.JSONDecodeError as je: + error_msg = ( + f"Invalid JSON format for tool '{name}'. The tool arguments could not be parsed. " + f"Make sure all required parameters are provided and properly formatted as JSON. " + f"Error details: {str(je)}" + ) logger.error( - f"📝 Oops! The arguments for '{name}' don't make sense - invalid JSON, arguments:{command.function.arguments}" + f"📝 JSON parsing error for '{name}': {str(je)}\nArguments: {command.function.arguments}" ) return f"Error: {error_msg}" except Exception as e: diff --git a/app/config.py b/app/config.py index a881e2a5e..2a71a9722 100644 --- a/app/config.py +++ b/app/config.py @@ -294,7 +294,7 @@ def _load_initial_config(self): if daytona_config: daytona_settings = DaytonaSettings(**daytona_config) else: - daytona_settings = DaytonaSettings() + daytona_settings = None mcp_config = raw_config.get("mcp", {}) mcp_settings = None diff --git a/app/prompt/manus.py b/app/prompt/manus.py index 99e7e8315..1d76608b7 100644 --- a/app/prompt/manus.py +++ b/app/prompt/manus.py @@ -1,6 +1,19 @@ SYSTEM_PROMPT = ( - "You are OpenManus, an all-capable AI assistant, aimed at solving any task presented by the user. You have various tools at your disposal that you can call upon to efficiently complete complex requests. Whether it's programming, information retrieval, file processing, web browsing, or human interaction (only for extreme cases), you can handle it all." - "The initial directory is: {directory}" + "You are OpenManus, an all-capable AI assistant, aimed at solving any task presented by the user. You have various tools at your disposal that you can call upon to efficiently complete complex requests. Whether it's programming, information retrieval, file processing, web browsing, or human interaction (only for extreme cases), you can handle it all.\n" + "\n" + "IMPORTANT GUIDELINES:\n" + "1. Only use tools when truly necessary. Answer straightforward questions directly without tools.\n" + "2. python_execute: ALWAYS provide the 'code' parameter with complete, runnable Python code.\n" + "3. browser_use_tool: Use for web browsing, research, and information gathering from the internet.\n" + "4. str_replace_editor:\n" + " - For 'create': MUST provide 'command', 'path', AND 'file_text' parameters. Never omit file_text.\n" + " - For 'view': Provide 'command' and 'path' to view file/directory contents.\n" + " - For 'str_replace'/'insert': Provide all required parameters (command, path, old_str, new_str, etc.)\n" + "5. ask_human: Use ONLY in extreme cases when user interaction is absolutely required.\n" + "6. CRITICAL: When calling ANY tool, provide ALL required parameters. Incomplete tool calls will fail.\n" + "7. Use the session folder for saving outputs: {directory}/output/\n" + "\n" + "The initial workspace directory is: {directory}" ) NEXT_STEP_PROMPT = """ diff --git a/requirements.txt b/requirements.txt index aa7e6dc93..aa405f5bc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,9 @@ -pydantic~=2.10.6 -openai~=1.66.3 +pydantic~=2.11.5 +openai~=1.108.1 tenacity~=9.0.0 pyyaml~=6.0.2 loguru~=0.7.3 +structlog~=25.1.0 numpy datasets~=3.4.1 fastapi~=0.115.11 @@ -20,7 +21,6 @@ baidusearch~=1.0.3 duckduckgo_search~=7.5.3 aiofiles~=24.1.0 -pydantic_core~=2.27.2 colorama~=0.4.6 playwright~=1.51.0 @@ -33,10 +33,14 @@ httpx>=0.27.0 tomli>=2.0.0 boto3~=1.37.18 +daytona>=0.161.0 requests~=2.32.3 beautifulsoup4~=4.13.3 -crawl4ai~=0.6.3 +crawl4ai~=0.7.4 huggingface-hub~=0.29.2 -setuptools~=75.8.0 +setuptools~=80.9.0 + +# Dependency conflict resolution +orjson>=3.11.5 From ccd9fc044f0f2fe6fff7319ccfb69580c94127e6 Mon Sep 17 00:00:00 2001 From: ilonae Date: Sun, 5 Apr 2026 03:14:36 +0700 Subject: [PATCH 2/2] fix: correct session folder path to use workspace_root The session folder was being created relative to the current directory instead of inside the workspace root. This caused the agent to fail when trying to write files because the paths didn't match. Changes: - Session folder now created at: workspace/output/session_YYYYMMDD_HHMMSS/ - Updated system prompt to provide absolute paths for file operations - Verified that files are now created successfully in the correct location Fixes the FileNotFoundError when creating files in session subfolders. --- app/agent/manus.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/agent/manus.py b/app/agent/manus.py index 67cf32749..4eafda1b5 100644 --- a/app/agent/manus.py +++ b/app/agent/manus.py @@ -32,10 +32,10 @@ def get_system_prompt_with_session(self) -> str: if self.session_folder: session_info = ( f"\n\nSESSION OUTPUT FOLDER: All research outputs should be saved to: {self.session_folder}\n" - f"Use the following subfolder structure:\n" - f"- Guides/documentation: {self.session_folder}/guides/\n" - f"- Code files: {self.session_folder}/code/\n" - f"- Data files: {self.session_folder}/data/\n" + f"Absolute paths for file operations:\n" + f"- Guides/documentation: {os.path.join(self.session_folder, 'guides')}/\n" + f"- Code files: {os.path.join(self.session_folder, 'code')}/\n" + f"- Data files: {os.path.join(self.session_folder, 'data')}/\n" ) return base_prompt + session_info return base_prompt @@ -92,7 +92,7 @@ def initialize_session_folder(self) -> "Manus": # Generate unique session folder with timestamp timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") slug = "session" # Could be customized based on task type - session_path = os.path.join("output", f"{slug}_{timestamp}") + session_path = os.path.join(config.workspace_root, "output", f"{slug}_{timestamp}") # Create the session folder and subfolders os.makedirs(session_path, exist_ok=True)