# OpenManus Administrator & Developer Guide

OpenManus is an async-first, Python framework for building general-purpose LLM agents (version `0.1.0`, MIT-licensed, by contributors from the [MetaGPT](https://github.com/geekan/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](#5-security-analysis).

---

## Table of Contents

1. [Executive Summary](#1-executive-summary)
2. [Architecture](#2-architecture)
3. [Requirements](#3-requirements)
4. [Configuration Mastery](#4-configuration-mastery)
5. [Security Analysis](#5-security-analysis)
6. [Operations](#6-operations)
7. [Developer Internals](#7-developer-internals)
8. [Code Quality & Patterns](#8-code-quality--patterns)
9. [Potential Improvements](#9-potential-improvements)
10. [Troubleshooting](#10-troubleshooting)
11. [Quick Reference](#11-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 to `RUNNING`, then loops `current_step < max_steps and state != FINISHED`, calling `step()` each iteration, checking `is_stuck()`, and finally calling `SANDBOX_CLIENT.cleanup()`. The `terminate` tool is what sets state to `FINISHED` and 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_choice`** is `AUTO` for most agents (the model may answer or call tools); `BrowserAgent` also uses `AUTO`.
- **`max_observe`** caps 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 `TokenLimitExceeded` surfaced through a `tenacity` `RetryError` ends the run gracefully (sets `FINISHED`) 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_MODELS` and `REASONING_MODELS` are hard-coded allowlists in `app/llm.py`. A new vision or reasoning model not in those lists won't get image support / `max_completion_tokens` handling until added. This is a maintenance point — see [§9](#9-potential-improvements).

### 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`:

1. `_create_initial_plan()` asks the LLM (with the `PlanningTool`) to draft a titled, numbered plan. If the LLM doesn't call the tool, a default 3-step plan is created.
2. 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.
3. Steps are marked `completed`; the loop ends when no active step remains or an agent reaches `FINISHED`.
4. `_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` (a `ToolCollection`) connects via `connect_sse(url)` or `connect_stdio(command, args)`, lists server tools, and wraps each as an `MCPClientTool` named `mcp_{server_id}_{tool}` (sanitized, ≤ 64 chars). `MCPAgent` refreshes tools every 5 steps and ends when the server's tool map empties.
- **As a server** (`app/mcp/server.py`): `MCPServer` wraps `FastMCP`, registering `bash`, `browser` (browser_use), `editor` (str_replace_editor), and `terminate` as MCP tools with reconstructed signatures/docstrings.

### 2.8 Sandboxes

- **Local Docker** (`app/sandbox/core/*`): `DockerSandbox` + `SandboxManager` + `AsyncDockerizedTerminal` provide containerized command/file execution, gated by `[sandbox] use_sandbox`. `LocalSandboxClient` (`SANDBOX_CLIENT`) is the singleton used by file operators.
- **Daytona cloud** (`app/daytona/*`): `SandboxManus` creates 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) and `setup.py`'s `install_requires` **disagree**. `setup.py` omits `tiktoken`, `mcp`, `fastapi`, `boto3`, `playwright`, `crawl4ai`, and more, and pins different version ranges (e.g. `openai>=1.58.1,<1.67.0`). Install from `requirements.txt`. The `setup.py` list 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_ROOT` is computed from `app/config.py`'s location, and `WORKSPACE_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 remove `bash`/`python_execute` for 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 `bash` to 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`:

```bash
python protocol/a2a/app/main.py --host 0.0.0.0 --port 10000
```

> [!WARNING]
> Binding `--host 0.0.0.0` exposes 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 a `loguru` rotation/retention policy.

### 6.3 Token & cost control

- Set `[llm] max_input_tokens` to cap cumulative input and force a graceful stop via `TokenLimitExceeded`.
- Tune `max_tokens` and choose cheaper models for simple agents.
- The LLM client logs cumulative input/completion tokens at `INFO` after 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:**

```python
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`:

```bash
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](#9-potential-improvements)).

---

## 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 `ToolResult` keeps tool code simple.
- Pydantic v2 settings models give validated, self-documenting config.
- Consistent async throughout; graceful resource cleanup; `tenacity` retries on API calls.
- Pre-commit hooks (`black`, `autoflake`, `isort`, standard hygiene hooks) are configured.

**Weaknesses / risks.**

- `setup.py` dependency list is stale and inconsistent with `requirements.txt` (missing `tiktoken` etc.).
- Hard-coded model allowlists (`MULTIMODAL_MODELS`, `REASONING_MODELS`) require code edits for new models.
- `app/bedrock.py` uses a module-global `CURRENT_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.py` with `requirements.txt`** — add `tiktoken`, `mcp`, `fastapi`, `boto3`, `playwright`, `crawl4ai`, etc., or have `setup.py` read from `requirements.txt`. Eliminates the #1019 `tiktoken` failure.
- [ ] **Fix the `web_search` UA header bug** (#1334): change `{"WebSearch": ...}` to `{"User-Agent": ...}` in `WebContentFetcher.fetch_content`.
- [ ] **Make `python_execute` genuinely 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_ID` instance-scoped** in `app/bedrock.py` for 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) |

---

_Generated June 2026 from FoundationAgents/OpenManus at 52a13f2. AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it. Unofficial; not affiliated with or reviewed by the project._
