OpenManus is an async-first, Python framework for building general-purpose LLM agents (version 0.1.0, MIT-licensed, by contributors from the MetaGPT community). Its architecture is a layered agent class hierarchy that runs a ReAct (reason → act) loop over a pluggable tool collection, backed by a provider-agnostic LLM client and configured entirely through TOML. This guide is the operational and developer reference: architecture, configuration, deployment, security, internals, and improvement notes.
Note
OpenManus has no server-side persistence, no database, and no built-in authentication. It is a locally run agent runtime. Its operational surface is: an LLM API connection, a set of tools that touch the local machine (shell, Python, files, browser), and optional MCP / Daytona / A2A integrations. Plan security accordingly — see §5.
Table of Contents
- Executive Summary
- Architecture
- Requirements
- Configuration Mastery
- Security Analysis
- Operations
- Developer Internals
- Code Quality & Patterns
- Potential Improvements
- Troubleshooting
- Quick Reference
1. Executive Summary
| Attribute | Value |
|---|---|
| Project | OpenManus |
| Version | 0.1.0 |
| Language | Python (≥ 3.12; runtime warns outside 3.11–3.13) |
| Paradigm | ReAct agent loop + tool-calling, async (asyncio) |
| Core dependencies | openai, pydantic v2, tiktoken, tenacity, mcp, browser-use, playwright, crawl4ai, fastapi/uvicorn, boto3, loguru |
| LLM backends | OpenAI-compatible (default), Azure OpenAI, AWS Bedrock, plus Ollama/Gemini/aggregators via OpenAI-compat |
| Persistence | None (in-memory message history per run) |
| Auth | None |
| Network services | Optional: MCP server (stdio/SSE), A2A server (HTTP :10000) |
| License | MIT |
What it is. A framework whose value is the orchestration pattern: how an agent reasons, picks tools, executes them, observes results, and iterates. It is not a CLI utility or a web service — the six entry-point scripts are thin launchers around different agent/flow configurations.
Deployment shape. Single process, run from a terminal or wrapped in a service. All tool execution happens in-process on the host by default; isolation is opt-in (Docker sandbox or Daytona cloud sandbox).
2. Architecture
2.1 The mental model: agent class hierarchy
The single most important thing to understand is the agent inheritance chain. Everything else (tools, flows, prompts) hangs off it.
BaseAgent (ABC, pydantic BaseModel)
│ state machine (IDLE/RUNNING/FINISHED/ERROR), Memory, run() loop,
│ is_stuck()/handle_stuck_state(), max_steps
│
└── ReActAgent (ABC)
│ step() = await think() then (if needed) await act()
│
└── ToolCallAgent
│ think(): call llm.ask_tool() with available_tools.to_params()
│ act(): execute each tool call, append observations to memory
│ _handle_special_tool(): terminate handling
│
├── Manus (main.py) python_execute, browser_use,
│ str_replace_editor, ask_human,
│ terminate, + MCP tools
├── BrowserAgent (library) browser_use, terminate
├── SWEAgent (library) bash, str_replace_editor, terminate
├── DataAnalysis (run_flow) python_execute, viz_prepare,
│ data_visualization, terminate
├── MCPAgent (run_mcp.py) tools from a connected MCP server
└── SandboxManus (sandbox_main) ask_human, terminate + Daytona
sandbox tools (shell/files/browser/vision)
Important
BaseAgent.run()is the engine. It transitions the agent toRUNNING, then loopscurrent_step < max_steps and state != FINISHED, callingstep()each iteration, checkingis_stuck(), and finally callingSANDBOX_CLIENT.cleanup(). Theterminatetool is what sets state toFINISHEDand ends the loop early.
2.2 The ReAct loop in detail
run(request)
└─► loop until FINISHED or max_steps:
step() ── ReActAgent ──►
think() ── ToolCallAgent ──►
├─ append next_step_prompt as a user message
├─ llm.ask_tool(messages, system, tools, tool_choice)
├─ store assistant message (content + tool_calls)
└─ return True if there are tool calls / content
act() ── ToolCallAgent ──►
└─ for each tool_call:
execute_tool() → available_tools.execute(name, args)
append tool result to memory (truncated to max_observe)
tool_choiceisAUTOfor most agents (the model may answer or call tools);BrowserAgentalso usesAUTO.max_observecaps how much of each tool result is fed back (e.g. Manus = 10000 chars, DataAnalysis = 15000) to control context growth.- Stuck detection: if the last assistant message content repeats ≥
duplicate_threshold(2) times,handle_stuck_state()prepends a "consider new strategies" nudge to the next-step prompt. - Token-limit handling: a
TokenLimitExceededsurfaced through atenacityRetryErrorends the run gracefully (setsFINISHED) rather than crashing.
2.3 Component inventory
| Layer | Module(s) | Responsibility |
|---|---|---|
| Entry points | main.py, run_flow.py, run_mcp.py, run_mcp_server.py, sandbox_main.py, protocol/a2a/app/main.py |
Launch a specific agent/flow/server. |
| Agents | app/agent/*.py |
The ReAct hierarchy above. |
| LLM client | app/llm.py, app/bedrock.py |
Provider-agnostic ask / ask_tool / ask_with_images; token counting; Bedrock adapter. |
| Config | app/config.py |
Singleton Config loading config/config.toml into pydantic settings models. |
| Schema | app/schema.py |
Message, Memory, Role, ToolChoice, AgentState, ToolCall. |
| Tools | app/tool/*.py |
The tool implementations + ToolCollection. |
| Flows | app/flow/*.py |
PlanningFlow + FlowFactory for multi-agent orchestration. |
| Prompts | app/prompt/*.py |
System & next-step prompts per agent. |
| MCP | app/mcp/server.py, app/tool/mcp.py |
Serve tools over MCP / consume remote MCP tools. |
| Sandboxes | app/sandbox/*, app/daytona/* |
Local Docker sandbox client + Daytona cloud sandbox. |
| Protocols | protocol/a2a/* |
Agent-to-Agent (A2A) server wrapping Manus. |
2.4 The LLM client (app/llm.py)
LLM is an instance-cached class (_instances keyed by config name, so LLM("default") and LLM("vision") are distinct singletons). It exposes three async methods, all wrapped with tenacity retry (stop_after_attempt(6), exponential backoff):
| Method | Use |
|---|---|
ask() |
Plain completion (optionally streaming). |
ask_tool() |
Tool/function-calling completion — the agent workhorse. Always non-streaming. |
ask_with_images() |
Multimodal completion with image attachments. |
Provider routing (in __init__): api_type == "azure" → AsyncAzureOpenAI; api_type == "aws" → BedrockClient (custom OpenAI-shape adapter over boto3 bedrock-runtime); everything else → AsyncOpenAI(base_url=...). This is why Ollama, Gemini, Jiekou, PPIO, DeepSeek, etc. all work — they expose OpenAI-compatible endpoints.
Token accounting (TokenCounter): counts text via tiktoken (falls back to cl100k_base for unknown models) and estimates image tokens by OpenAI's tiling rules. max_input_tokens, if set, caps cumulative input and raises TokenLimitExceeded.
Note
MULTIMODAL_MODELSandREASONING_MODELSare hard-coded allowlists inapp/llm.py. A new vision or reasoning model not in those lists won't get image support /max_completion_tokenshandling until added. This is a maintenance point — see §9.
2.5 The tool system
All tools subclass BaseTool (ABC + pydantic BaseModel) with name, description, parameters (a JSON Schema), and an async execute(). to_param() renders the OpenAI function-calling spec. ToolResult (with output / error / base64_image / system) is the uniform return type; CLIResult and ToolFailure are subtypes.
ToolCollection holds tools, maps them by name, renders to_params() for the LLM, and dispatches execute(name, tool_input). Agents add/remove tools dynamically (notably MCP tools).
2.6 Planning flow (app/flow/planning.py)
PlanningFlow (created via FlowFactory.create_flow(FlowType.PLANNING, agents)) is the multi-agent orchestrator behind run_flow.py:
_create_initial_plan()asks the LLM (with thePlanningTool) to draft a titled, numbered plan. If the LLM doesn't call the tool, a default 3-step plan is created.- The execute loop repeatedly finds the first non-completed step, marks it
in_progress, picks an executor agent (by[TYPE]tag in the step text, else the first executor / primary agent), and runs it with a step-scoped prompt. - Steps are marked
completed; the loop ends when no active step remains or an agent reachesFINISHED. _finalize_plan()asks the LLM for a summary.
Step status markers: [ ] not started, [→] in progress, [✓] completed, [!] blocked. The only registered FlowType is PLANNING.
2.7 MCP integration
- As a client (
app/tool/mcp.py):MCPClients(aToolCollection) connects viaconnect_sse(url)orconnect_stdio(command, args), lists server tools, and wraps each as anMCPClientToolnamedmcp_{server_id}_{tool}(sanitized, ≤ 64 chars).MCPAgentrefreshes tools every 5 steps and ends when the server's tool map empties. - As a server (
app/mcp/server.py):MCPServerwrapsFastMCP, registeringbash,browser(browser_use),editor(str_replace_editor), andterminateas MCP tools with reconstructed signatures/docstrings.
2.8 Sandboxes
- Local Docker (
app/sandbox/core/*):DockerSandbox+SandboxManager+AsyncDockerizedTerminalprovide containerized command/file execution, gated by[sandbox] use_sandbox.LocalSandboxClient(SANDBOX_CLIENT) is the singleton used by file operators. - Daytona cloud (
app/daytona/*):SandboxManuscreates a remote Daytona sandbox, surfaces VNC + website preview URLs, and adds sandbox-scoped tools (SandboxBrowserTool,SandboxFilesTool,SandboxShellTool,SandboxVisionTool).
3. Requirements
| Component | Requirement |
|---|---|
| Python | ≥ 3.12 (per setup.py); 3.11–3.13 tolerated at runtime |
| LLM API | An OpenAI-compatible endpoint + key, or Azure/Bedrock credentials |
| Memory/CPU | Modest for the framework itself; the browser (Playwright/Chromium) and crawl4ai dominate footprint |
| Node.js | ≥ 18 — only for the chart-visualization toolchain |
| Docker | Only if [sandbox] use_sandbox = true |
| Daytona | API key only for sandbox_main.py |
| Playwright | playwright install to download Chromium for browser automation |
Python dependencies (from requirements.txt)
Core: pydantic~=2.10.6, openai~=1.66.3, tenacity~=9.0.0, pyyaml~=6.0.2, loguru~=0.7.3, numpy, tiktoken~=0.9.0, pydantic_core~=2.27.2.
LLM/cloud: boto3~=1.37.18 (Bedrock), mcp~=1.5.0, httpx>=0.27.0, tomli>=2.0.0.
Web/automation: browser-use~=0.1.40, playwright~=1.51.0, crawl4ai~=0.6.3, beautifulsoup4~=4.13.3, requests~=2.32.3, html2text~=2024.2.26, googlesearch-python, baidusearch, duckduckgo_search~=7.5.3, browsergym~=0.13.3.
Serving/data: fastapi~=0.115.11, uvicorn~=0.34.0, datasets~=3.4.1, aiofiles~=24.1.0, pillow~=11.1.0, unidiff~=0.7.5, huggingface-hub~=0.29.2.
Warning
requirements.txt(the source of truth) andsetup.py'sinstall_requiresdisagree.setup.pyomitstiktoken,mcp,fastapi,boto3,playwright,crawl4ai, and more, and pins different version ranges (e.g.openai>=1.58.1,<1.67.0). Install fromrequirements.txt. Thesetup.pylist should be treated as stale.
4. Configuration Mastery
Configuration is loaded once by the Config singleton (app/config.py) at import time from config/config.toml, falling back to config/config.example.toml if the former is absent (FileNotFoundError if neither exists). MCP server entries load separately from config/mcp.json.
4.1 [llm] and named LLM profiles (LLMSettings)
| Key | Type | Default | Notes |
|---|---|---|---|
model |
str | required | Model name. |
base_url |
str | required | API endpoint. |
api_key |
str | required | Key (unused for Bedrock but still required). |
max_tokens |
int | 4096 |
Per-response cap. |
max_input_tokens |
int? | None |
Cumulative input cap; None = unlimited. |
temperature |
float | 1.0 |
Set 0.0 for agents. |
api_type |
str | "" |
azure / aws route to special clients; all else → OpenAI-compatible. |
api_version |
str | "" |
Azure only. |
The loader builds a "default" profile from the top-level [llm] keys, then merges any nested tables (e.g. [llm.vision]) as additional named profiles on top of the defaults. Agents request a profile by name; BaseAgent.initialize_agent() falls back to LLM(config_name=self.name.lower()).
Tip
Per-role / per-purpose models are a first-class feature: define
[llm.vision],[llm.<custom>], etc., and different parts of the system use different models in one run. This is the "killer recipe" — e.g. a cheap fast model for reasoning and a vision model only for screenshots.
4.2 [browser] (BrowserSettings)
| Key | Default | Notes |
|---|---|---|
headless |
false |
A visible browser opens by default. |
disable_security |
true |
Browser security relaxed by default. |
extra_chromium_args |
[] |
Extra Chromium flags. |
chrome_instance_path |
None |
Attach to an existing Chrome. |
wss_url |
None |
Connect via WebSocket. |
cdp_url |
None |
Connect via Chrome DevTools Protocol. |
max_content_length |
2000 |
Cap on extracted page content. |
[browser.proxy] |
— | server, username, password. |
4.3 [search] (SearchSettings)
| Key | Default |
|---|---|
engine |
Google |
fallback_engines |
["DuckDuckGo", "Baidu", "Bing"] |
retry_delay |
60 (s) |
max_retries |
3 |
lang |
en |
country |
us |
4.4 [sandbox] (SandboxSettings)
| Key | Default |
|---|---|
use_sandbox |
false |
image |
python:3.12-slim |
work_dir |
/workspace |
memory_limit |
512m (example file uses 1g) |
cpu_limit |
1.0 (example uses 2.0) |
timeout |
300 (s) |
network_enabled |
false (example uses true) |
4.5 [daytona] (DaytonaSettings)
| Key | Default |
|---|---|
daytona_api_key |
required for sandbox_main |
daytona_server_url |
https://app.daytona.io/api |
daytona_target |
us (eu/us) |
sandbox_image_name |
whitezxj/sandbox:0.1.0 |
sandbox_entrypoint |
supervisord command |
VNC_password |
123456 |
4.6 [mcp] (MCPSettings) and config/mcp.json
server_reference defaults to app.mcp.server. Server connections come from config/mcp.json (MCPServerConfig: type = sse/stdio, url, command, args).
4.7 [runflow] (RunflowSettings)
use_data_analysis_agent (default false) — when true, run_flow.py adds the DataAnalysis agent alongside Manus.
Note
PROJECT_ROOTis computed fromapp/config.py's location, andWORKSPACE_ROOT = PROJECT_ROOT/workspace. The agent's working/scratch area and chart outputs live there.
5. Security Analysis
Caution
OpenManus executes arbitrary code and shell commands on the host by default. This is inherent to what an autonomous agent does, but it makes the security posture the most important operational topic. Treat any OpenManus deployment as equivalent to handing an LLM a shell on the machine.
5.1 Arbitrary code & command execution (HIGH)
Several default-equipped tools execute attacker-influenceable (LLM-generated, prompt-influenced) code:
| Tool | Mechanism | Isolation by default |
|---|---|---|
python_execute |
exec(code, safe_globals, safe_globals) inside a multiprocessing.Process |
None. safe_globals includes the full __builtins__ — open, os, subprocess import, network, filesystem all available. Only a timeout (default 5 s) bounds it. |
bash |
Spawns /bin/bash via asyncio.create_subprocess_shell |
None. Full shell on the host. |
str_replace_editor |
Reads/writes/creates arbitrary absolute paths | Local filesystem unless sandbox enabled. |
computer_use |
Drives a real desktop (mouse/keyboard/screenshots) | Used with sandbox; full GUI control. |
Manus (the default agent in main.py) ships python_execute; SWEAgent ships bash; the MCP server exposes bash. The "sandbox" name on PythonExecute refers only to the timeout/subprocess wrapper — it is not a security sandbox.
Mitigations:
- [ ] Enable the Docker sandbox:
[sandbox] use_sandbox = true(routes file ops and shell through a container with CPU/memory/network/timeout limits). - [ ] Or use
sandbox_main.py(Daytona) so all execution happens in an isolated cloud sandbox. - [ ] Run the whole process as an unprivileged user, inside a rootless container, with an egress allowlist and dropped capabilities.
- [ ] Restrict each agent's tool list in code (
agent.available_tools) to removebash/python_executefor untrusted-input scenarios. - [ ] Never expose the MCP server (which registers
bash) on an untrusted network.
5.2 Browser exposure (MEDIUM)
Defaults are headless = false and disable_security = true. A visible, security-relaxed browser is driven by the agent and can be steered to arbitrary URLs (including via web_search → auto-open first result). Prompt injection from a visited page can influence the agent. Set headless = true, keep disable_security = false where possible, and treat visited content as untrusted input.
5.3 Secrets handling (MEDIUM)
API keys live in plaintext in config/config.toml. There is no secrets manager and no .env integration in the core (the A2A entry point does call load_dotenv()). Keep config/config.toml out of version control (the repo's config/.gitignore covers this), restrict file permissions, and inject keys via mounted secrets in containerized deployments.
5.4 Network services (MEDIUM)
- A2A server (
:10000) and MCP SSE endpoints have no authentication. Bind to localhost or place behind an authenticating reverse proxy; never expose directly to the internet. - The MCP server exposing
bashto any connecting client is a remote-code-execution vector if reachable.
5.5 Prompt injection & loop safety (MEDIUM)
The agent ingests web pages, files, and tool outputs into its context; malicious content can attempt to redirect behavior. Built-in is_stuck() mitigates loops but not adversarial steering. The max_steps cap (20) and run_flow.py's 60-minute timeout bound runaway execution.
5.6 OWASP Agentic-AI mapping (summary)
| Threat | Relevance | Default state |
|---|---|---|
| Tool misuse / RCE | python_execute, bash |
Not mitigated by default; sandbox is opt-in |
| Excessive agency | autonomous multi-tool loop | Bounded by max_steps, terminate |
| Prompt injection | web/file content in context | Not mitigated; user must trust sources |
| Insecure output handling | tool results fed back verbatim | Truncated via max_observe only |
| Unauthenticated services | A2A / MCP | Not authenticated; deploy behind proxy |
6. Operations
6.1 Running as a service
There is no daemon mode for the agent scripts (they are interactive or single-prompt). For service use, wrap main.py --prompt ... or the A2A server in a process manager (systemd, supervisor, a container CMD). The A2A server runs under uvicorn:
python protocol/a2a/app/main.py --host 0.0.0.0 --port 10000
Warning
Binding
--host 0.0.0.0exposes an unauthenticated agent endpoint. Only do this behind a trusted network boundary or an authenticating proxy.
6.2 Logging
Logging uses loguru (app/logger.py). On each run, define_log_level() adds a stderr sink at INFO and a file sink at DEBUG under logs/{timestamp}.log.
Caution
A new timestamped log file is created per run (GitHub issue #1008 — "Wrong creation of a large number of log files"). Without rotation,
logs/grows unbounded. Add a cron/cleanup job or alogururotation/retention policy.
6.3 Token & cost control
- Set
[llm] max_input_tokensto cap cumulative input and force a graceful stop viaTokenLimitExceeded. - Tune
max_tokensand choose cheaper models for simple agents. - The LLM client logs cumulative input/completion tokens at
INFOafter each call — scrape these for cost monitoring.
6.4 Resource cleanup
BaseAgent.run() calls SANDBOX_CLIENT.cleanup() at the end. ToolCallAgent.cleanup() calls each tool's async cleanup() (closing browser contexts, MCP sessions, aiohttp sessions). SandboxManus.cleanup() also deletes the Daytona sandbox. Always let the process shut down cleanly (Ctrl+C is handled) so the browser and sandboxes are released.
6.5 Backups / state
There is no persistent state to back up — message memory is in-process and discarded at exit. The only durable artifacts are files the agent writes (working directory, workspace/, logs/).
7. Developer Internals
7.1 Repository structure
OpenManus/
├── main.py run_flow.py run_mcp.py run_mcp_server.py sandbox_main.py
├── setup.py requirements.txt Dockerfile
├── app/
│ ├── agent/ base, react, toolcall, manus, browser, swe, mcp,
│ │ data_analysis, sandbox_agent
│ ├── tool/ base, bash, browser_use_tool, python_execute,
│ │ str_replace_editor, web_search, crawl4ai, planning,
│ │ terminate, ask_human, create_chat_completion,
│ │ computer_use, file_operators, mcp, tool_collection,
│ │ search/ (google,baidu,bing,duckduckgo), sandbox/,
│ │ chart_visualization/
│ ├── flow/ base, flow_factory, planning
│ ├── prompt/ manus, browser, swe, mcp, planning, toolcall, visualization
│ ├── mcp/ server
│ ├── sandbox/ client, core/(sandbox, manager, terminal, exceptions)
│ ├── daytona/ sandbox, tool_base
│ ├── llm.py bedrock.py config.py schema.py logger.py exceptions.py
├── config/ config.example*.toml, mcp.example.json
├── protocol/a2a/ A2A server (agent, agent_executor, main)
├── examples/ benchmarks, use cases
├── tests/sandbox/ sandbox test suite
└── workspace/ agent scratch / outputs
7.2 Key abstractions
| Abstraction | Defined in | Contract |
|---|---|---|
BaseAgent |
app/agent/base.py |
run() loop, state_context(), update_memory(), abstract step(). |
ReActAgent |
app/agent/react.py |
abstract think() + act(); step() = think→act. |
ToolCallAgent |
app/agent/toolcall.py |
concrete tool-calling think/act; special-tool handling. |
BaseTool |
app/tool/base.py |
name/description/parameters + async execute() + to_param(). |
ToolCollection |
app/tool/tool_collection.py |
tool registry + dispatch. |
Message/Memory |
app/schema.py |
OpenAI-shaped messages; bounded history (max_messages=100). |
LLM |
app/llm.py |
provider-agnostic ask/ask_tool/ask_with_images. |
BaseFlow/PlanningFlow |
app/flow/* |
multi-agent orchestration. |
FileOperator |
app/tool/file_operators.py |
LocalFileOperator vs SandboxFileOperator strategy. |
7.3 Extending OpenManus
Add a tool:
from app.tool.base import BaseTool, ToolResult
class MyTool(BaseTool):
name: str = "my_tool"
description: str = "What it does (the model reads this to decide to call it)."
parameters: dict = {
"type": "object",
"properties": {"arg": {"type": "string", "description": "..."}},
"required": ["arg"],
}
async def execute(self, arg: str) -> ToolResult:
return ToolResult(output=f"did something with {arg}")
Then add it to an agent's available_tools (a ToolCollection).
Add an agent: subclass ToolCallAgent, set name, description, system_prompt, next_step_prompt, available_tools, and (optionally) override think().
Add a flow: subclass BaseFlow, implement execute(), and register it in FlowFactory.flows under a new FlowType.
Add an LLM provider: if it's OpenAI-compatible, just set base_url. Otherwise add a branch in LLM.__init__ (as aws/azure do) and an adapter like app/bedrock.py.
7.4 Testing
The test suite under tests/sandbox/ (test_client.py, test_sandbox.py, test_sandbox_manager.py, test_docker_terminal.py) covers the Docker sandbox layer and requires Docker. Run with pytest:
pytest tests/sandbox -q
Note
Test coverage is concentrated on the sandbox subsystem. The agent loop, tool implementations, LLM client, and flows have no dedicated unit tests in-repo — a notable gap (see §9).
8. Code Quality & Patterns
Strengths.
- Clean layered inheritance (
BaseAgent→ReActAgent→ToolCallAgent) makes new agents cheap. - Strategy pattern for file operations (
LocalFileOperator/SandboxFileOperator) cleanly toggles local vs sandbox. - Provider-agnostic LLM client with a uniform
ToolResultkeeps tool code simple. - Pydantic v2 settings models give validated, self-documenting config.
- Consistent async throughout; graceful resource cleanup;
tenacityretries on API calls. - Pre-commit hooks (
black,autoflake,isort, standard hygiene hooks) are configured.
Weaknesses / risks.
setup.pydependency list is stale and inconsistent withrequirements.txt(missingtiktokenetc.).- Hard-coded model allowlists (
MULTIMODAL_MODELS,REASONING_MODELS) require code edits for new models. app/bedrock.pyuses a module-globalCURRENT_TOOLUSE_ID(commented "Tmp solution") — not concurrency-safe across simultaneous Bedrock tool calls.- Bug in
web_search.py(WebContentFetcher.fetch_content): the HTTP header is keyed"WebSearch"instead of"User-Agent"(GitHub issue #1334), so the intended browser UA is never sent. - Broad
retry_if_exception_type((OpenAIError, Exception, ValueError))retries everything, masking some non-transient failures. - No unit tests outside the sandbox subsystem.
- Code mixes English and Chinese comments/docstrings (e.g.
__add__docstrings, sandbox-agent comments).
9. Potential Improvements
- [ ] Reconcile
setup.pywithrequirements.txt— addtiktoken,mcp,fastapi,boto3,playwright,crawl4ai, etc., or havesetup.pyread fromrequirements.txt. Eliminates the #1019tiktokenfailure. - [ ] Fix the
web_searchUA header bug (#1334): change{"WebSearch": ...}to{"User-Agent": ...}inWebContentFetcher.fetch_content. - [ ] Make
python_executegenuinely sandboxed by default, or rename it so its lack of isolation isn't implied to be safe. Restrict__builtins__, or always route through the Docker/Daytona sandbox. - [ ] Externalize model capability lists (
MULTIMODAL_MODELS,REASONING_MODELS) to config so new models don't need code changes. - [ ] Add log rotation/retention (#1008) to stop unbounded
logs/growth. - [ ] Make
CURRENT_TOOLUSE_IDinstance-scoped inapp/bedrock.pyfor concurrency safety. - [ ] Authenticate the A2A and MCP-SSE endpoints, or document a required reverse-proxy auth layer.
- [ ] Add unit tests for the agent loop, tool execution, and the LLM client (currently only the sandbox is covered).
- [ ] Loosen or validate pinned deps (#1360 crawl4ai/pillow conflict) and document a known-good lockfile.
- [ ] Tighten retry predicates so non-transient errors aren't retried six times.
10. Troubleshooting
Error: ModuleNotFoundError: No module named 'tiktoken'
Installed via setup.py. Run pip install -r requirements.txt (or pip install tiktoken). Root cause: setup.py omits the dependency (#1019).
Error: No configuration file found in config directory
Neither config/config.toml nor config/config.example.toml exists. Restore the example file and copy it: cp config/config.example.toml config/config.toml.
Error: Docker Initialization failing / sandbox tools fail
Docker isn't running, or the [sandbox] image can't be pulled. Start Docker, verify docker ps, pull python:3.12-slim, or set use_sandbox = false. The sandbox raises RuntimeError: Sandbox not initialized when used without a live container.
Error: password must be provided (sandbox_main.py)
Daytona key/VNC password missing. Set [daytona] daytona_api_key (and VNC_password if needed), or use a non-sandbox entry point.
Error: Server URL is required for SSE connection / Command is required for stdio connection
MCP connection args are incomplete. For SSE pass --server-url; for stdio ensure a command is configured.
Error: Invalid 'tools[1].function.description': string too long (HTTP 500)
A tool description (often a verbose MCP tool) exceeds the provider's limit (#993). Shorten tool descriptions or disconnect the offending MCP server.
Error: Tool calls required but none provided
A REQUIRED tool-choice agent got plain text. Use a strong function-calling model; keep temperature = 0.0.
Error: TokenLimitExceeded / "Maximum token limit reached"
Cumulative input exceeded max_input_tokens. Raise the cap, shorten the task, or use a larger-context model. The agent ends gracefully rather than crashing.
Error: No replacement was performed, old_str ... did not appear verbatim
str_replace_editor requires an exact, unique match. Provide more surrounding context so old_str is unique, mind whitespace, or use view first.
Error: web search yields nothing
All engines failed (All search engines failed... Giving up.). Change [search] engine, raise max_retries, check network/proxy. Note the User-Agent header bug (#1334) can break full-content fetching even when search itself succeeds.
Error: Gemini 2.0 doesn't work (#1037)
Tool-calling incompatibility on Gemini's OpenAI-compatible endpoint. Prefer a strongly function-calling model; verify the rest of the pipeline with OpenAI/Anthropic.
Reference: canonical error sources
app/exceptions.py defines ToolError, OpenManusError, and TokenLimitExceeded. Tool-level ToolError messages (from planning, str_replace_editor, bash) are the most user-facing and are surfaced verbatim in tool observations.
11. Quick Reference
Entry points → agent/flow
| Script | Agent / flow | Notable args |
|---|---|---|
main.py |
Manus |
--prompt |
run_flow.py |
PlanningFlow over Manus (+DataAnalysis if enabled) |
(interactive) |
run_mcp.py |
MCPAgent |
-c/--connection, --server-url, -i/--interactive, -p/--prompt |
run_mcp_server.py / app/mcp/server.py |
MCPServer (FastMCP) |
--transport stdio |
sandbox_main.py |
SandboxManus (Daytona) |
--prompt |
protocol/a2a/app/main.py |
A2A server wrapping A2AManus |
--host, --port (10000) |
Agent defaults
| Agent | max_steps |
max_observe |
Tools |
|---|---|---|---|
ToolCallAgent (base) |
30 | — | create_chat_completion, terminate |
Manus |
20 | 10000 | python_execute, browser_use, str_replace_editor, ask_human, terminate, +MCP |
BrowserAgent |
20 | 10000 | browser_use, terminate |
SWEAgent |
20 | — | bash, str_replace_editor, terminate |
DataAnalysis |
20 | 15000 | python_execute, viz_prepare, data_visualization, terminate |
MCPAgent |
20 | — | remote MCP tools (refresh every 5 steps) |
SandboxManus |
20 | 10000 | ask_human, terminate + Daytona sandbox tools |
Agent states
IDLE → RUNNING → (FINISHED on terminate/done, or ERROR on exception, reverted via state_context).
Plan step markers
[ ] not_started · [→] in_progress · [✓] completed · [!] blocked
Default ports / limits
| Item | Value |
|---|---|
| A2A server | :10000 |
| MCP SSE default | http://127.0.0.1:8000/sse |
| Planning-flow timeout | 3600 s |
Default max_steps |
20 |
| Bash command timeout | 120 s |
python_execute timeout |
5 s |
| Sandbox command timeout | 300 s |
Memory max_messages |
100 |
| LLM retry attempts | 6 (exponential backoff) |