OpenManus

一句话定位

OpenManus 是 MetaGPT 团队在 Manus 走红后用「3 小时原型」气质做出的开源轻量复刻:一个分层继承的 ReAct + OpenAI 原生 function-calling 通用 agent 框架。核心是单文件级 agent loop、List[Message] 滑动窗口记忆、pydantic 工具 + OpenAI schema、可选的 PlanningFlow 计划分解与按标签多 agent 派发、Docker/Daytona 双沙箱(默认关)、MCP 作插件通道。它刻意不做记忆压缩、会话持久化、自进化、结构化 trace、权限门——胜在轻、可读、易改,生产化需自行加固安全与可观测性。无官方 paper / 设计博客 / docs 站,README 是唯一一手文档;RL / 轨迹训练在独立仓 OpenManus-RL,本仓不含。

核心架构总览(目录结构关键路径 + 引用的 commit)

分析基于 commit 52a13f2a57d8c7f6737eefb02ccf569594d442732026-01-04 11:11:56 +0800 Update README.mdgit clone --depth 1 浅克隆)。纯 Python 单进程 asyncio 框架,app/ 下约 12.7k 行,无微服务、无前端。

agent 分层继承链:

BaseAgent (app/agent/base.py)           # run loop + memory + stuck 检测
  └─ ReActAgent (app/agent/react.py)    # 抽象 think()/act()
       └─ ToolCallAgent (app/agent/toolcall.py)   # function-calling 核心
            ├─ Manus (app/agent/manus.py)          # 主力通用 agent(本地工具 + MCP)
            ├─ BrowserAgent (app/agent/browser.py)
            ├─ MCPAgent (app/agent/mcp.py)
            ├─ SWEAgent (app/agent/swe.py)
            ├─ DataAnalysis (app/agent/data_analysis.py)
            └─ SandboxManus (app/agent/sandbox_agent.py)  # Daytona 云沙箱版

编排层(可选):app/flow/planning.pyPlanningFlow——计划分解 + 多 agent 派发。 入口:main.py(单 Manus agent)/ run_flow.py(PlanningFlow)/ run_mcp.py(MCPAgent)。 基础设施:app/llm.py(LLM 封装)、app/schema.py(Message/Memory/枚举)、app/config.py(TOML 配置)、app/logger.py(loguru)、app/sandbox/(Docker)、app/daytona/(云沙箱)、protocol/a2a/(A2A 协议适配)。

Agent Loop(主循环 / 何时继续何时停)

主循环在 BaseAgent.run()agent/base.py:116-154,已核对):while current_step < max_steps and state != FINISHED 逐步调 self.step()。每步先递增 step、打 Executing step {n}/{max} 日志,执行后立即跑 is_stuck() 检测。达到 max_steps 会重置 current_step=0、置 IDLE 并追加 Terminated: Reached max steps(base.py:149-152);循环结束调 SANDBOX_CLIENT.cleanup()(base.py:153)。

单步 = 经典 ReAct:ReActAgent.step()agent/react.py:33-38,已核对)执行 think(),返回 False 则 “Thinking complete - no action needed” 直接结束该步,返回 True 再 act()ToolCallAgent.think()agent/toolcall.py:39-129)调 llm.ask_tool() 拿 tool_calls 并把 assistant 消息入 memory;act()(toolcall.py:131-164)逐个执行 tool_call、结果作 tool message 入 memory。

停止条件三种:

  1. terminate 特殊工具被调用 → _handle_special_toolstate=FINISHED(toolcall.py:210-223,_should_finish_execution 默认恒 True);
  2. max_steps 上限(Manus/Browser/MCP 默认 max_steps=20,ToolCallAgent 基类 30,BaseAgent 10);
  3. token 超限时置 FINISHED(toolcall.py:60-72)。

唯一自纠偏机制is_stuck()(base.py:170-186)统计最近 assistant 消息内容重复次数 ≥ duplicate_threshold=2 即判卡死;handle_stuck_state()(base.py:163-168)往 next_step_prompt 前插一句 “Observed duplicate responses… avoid repeating”。属被动防卡死,非学习。状态用 AgentState 枚举 IDLE/RUNNING/FINISHED/ERROR(schema.py:32-38),state_context 异步上下文管理器保证异常时转 ERROR(base.py:58-82)。

记忆与上下文管理(压缩、长期记忆、会话持久化)

Memory 就是一个 List[Message]schema.py:159-168,已核对)。add_message 时若超 max_messages=100尾部截断 self.messages[-max_messages:]——这是唯一的「上下文压缩」,纯滑动窗口,无摘要 / 无语义压缩 / 无向量检索 / 无长期记忆

无会话持久化:memory 全在进程内存,run 结束不落盘、不可恢复,无 DB、无 session store。

上下文相关的唯一「智能」是 token 计数与硬闸门:LLM.check_token_limit()(llm.py:249-254)超 max_input_tokensTokenLimitExceeded(不重试),agent 收到后置 FINISHED。TokenCounter(llm.py:45-171)用 tiktoken 精确算文本 / 图片 / tool_call token(含 OpenAI 图片 tile 算法 _calculate_high_detail_tokens)。browser 截图作为 base64 塞进 memory(browser.py:65-71),用完即消费清空 _current_base64_image

工具体系(定义/调用协议/注册/权限)

工具基类 BaseTooltool/base.py:78-137):pydantic 模型,字段 name/description/parameters(JSON Schema dict),抽象 execute()to_param()(tool/base.py:124-137)输出 OpenAI function-calling 格式 {"type":"function","function":{name,description,parameters}}。结果类型 ToolResult(tool/base.py:38-75):output/error/base64_image/system 四字段,支持 __add__ 拼接。

注册:ToolCollectiontool/tool_collection.py)构造时传入工具实例,内部 tool_map = {tool.name: tool}add_tool 遇同名跳过并 warning(tool_collection.py:56-62);to_params() 汇总 schema,execute(name, tool_input) 派发(tool_collection.py:25-35,未知工具返回 ToolFailure)。

Manus 默认工具集(agent/manus.py:34-42,已核对):PythonExecute, BrowserUseTool, StrReplaceEditor, AskHuman, Terminate。仓内另有 web_search、crawl4ai、chart_visualization、computer_use、file_operators 等(见 app/tool/)。调用协议全走 LLM 原生 function calling:toolcall.py:47-56llm.ask_tool(tools=..., tool_choice=AUTO)execute_tool(toolcall.py:166-208)json.loads arguments 后 available_tools.execute

权限体系:基本没有。工具执行前无审批门、无 allowlist/denylist、无危险命令拦截。唯一的「人在环」是 AskHuman 工具(tool/ask_human.pyexecute 直接 input() 阻塞式问人),但由 LLM 自主决定何时调用,非强制门;system prompt 甚至明示 human interaction “only for extreme cases”(prompt/manus.py:2)。

Prompt 设计(系统提示结构、动态组装)

结构极简:每个 agent 两段 prompt——system_prompt(角色)+ next_step_prompt(每步追加的 user 提示),定义在 app/prompt/*.py。Manus system prompt(prompt/manus.py:1-4)是一句话 “You are OpenManus, an all-capable AI assistant…” + {directory} 占位(manus.py:24 用 config.workspace_root format 填入);next_step_prompt(prompt/manus.py:6-10)提示按需选工具、复杂任务拆解、用完解释结果、停止用 terminate。

动态组装很轻ToolCallAgent.think()(toolcall.py:41-43)每步把 next_step_prompt 作为新 user message 追加到 messages 尾部,system_prompt 每次请求作为 system message 前置(toolcall.py:49-53)。

Browser 场景有动态上下文注入Manus.think()(manus.py:146-165)检测最近 3 条消息是否用过 browser,若是则把 next_step_prompt 换成 BrowserContextHelper.format_next_step_prompt()(browser.py:47-79)——注入实时 URL/title/tabs/滚动像素/截图,用完 restore 原 prompt。Browser system prompt(prompt/browser.py:1-71)是重头戏:强制 JSON 输出(current_state{evaluation_previous_goal, memory, next_goal} + action 列表),带 few-shot action 序列示例、“memory 里数 0/10 计数”等指令,风格明显借鉴 browser_use 库。

Router / 编排(任务分解、多 agent、子 agent)

编排入口 PlanningFlowflow/planning.py:45-443,经 FlowFactory.create_flow(FlowType.PLANNING, agents) 创建,由 run_flow.py 驱动)。流程:execute()(planning.py:94-131)→ _create_initial_plan()(planning.py:136-211)让 LLM 调 PlanningTool 一次性生成 steps 列表 → while 循环 _get_current_step_info() 找首个未完成步 → get_executor() 选 agent → _execute_step()executor.run(step_prompt) → 标记 completed → 全部完成后 _finalize_plan() 让 LLM 总结。

多 agent 派发get_executor(step_type)(planning.py:77-92)按 step 文本里的 [TYPE] 标签(如 [SEARCH]/[CODE],正则 \[([A-Z_]+)\] 提取,planning.py:243-247)匹配 agents dict 的 key 选对应 agent,无匹配则回退首个 executor。_create_initial_plan 在有多 agent 时把各 agent 的 name+description 塞进 planning system prompt 让 LLM 用 [agent_name] 标注步骤(planning.py:145-160)。

任务分解 = 一次性 LLM 生计划(无重规划 / 无动态 replan;步骤失败仅 log,不回插步骤)。计划状态存 PlanningTool.plans 内存 dict,步状态 not_started/in_progress/completed/blocked子 agent_execute_step 对每 step 调 executor.run(),是完整嵌套一个 agent loop(子 agent 有独立 memory/steps),算轻量子 agent 语义,但无并发、无 spawn 树、无结果聚合协议(子 agent 返回字符串拼接)。

另有 A2A 协议支持(protocol/a2a/):ManusExecutor(agent_executor.py)把 Manus 包成 Google A2A server(agent.invoke(query, context_id) → enqueue completed_task),是对外暴露 agent 的协议适配,非内部编排。

Skill / 插件体系

无独立 skill/plugin 抽象。扩展方式 = 写新 BaseTool 子类并加进 ToolCollection,或接 MCP server。MCP 是主要「插件」通道:config/mcp.jsonconfig.py:148-171 load_server_config,读 mcpServers 字段,支持 sse/stdio)→ Manus.initialize_mcp_servers()(manus.py:67-89)启动时连接 → connect_mcp_server(manus.py:91-112)把远端工具动态 add_tools 进 available_tools,远端工具名 mcp_{server_id}_{tool} 并 sanitize(tool/mcp.py:107-145)。

MCPAgent(agent/mcp.py)还支持运行时工具热更新_refresh_tools()(agent/mcp.py:87-132)每 _refresh_tools_interval=5 步重新 list_tools,diff added/removed/changed 并往 memory 注入 system 消息通知;工具全没了则 FINISHED。这是最接近「动态 skill 加载」的机制。

自进化能力(自我改进 / 学习型记忆 / eval 驱动纠错)

未实现。本仓无任何 self-improvement / 学习型记忆 / eval 驱动纠错 / 反思循环(grep reflect|reward|trajectory|self-improv|trainingapp/ 下零命中)。唯一沾边的是 is_stuck()/handle_stuck_state() 的重复检测→改 prompt(见 Agent Loop 一节),属被动防卡死,不是学习。RL / 自进化是另一个仓 OpenManus-RL(UIUC + OpenManus 合作,GRPO 类微调 agent),与本代码库解耦,仅 README 互链。

可观测性(日志 / trace 格式)

日志用 logurulogger.py:12-29 define_log_level 配双 sink——stderr(INFO) + logs/{name_timestamp}.log(DEBUG)。全流程 emoji 化 info 日志:toolcall.py:81-89 打印 thoughts / 选了几个工具 / 工具名 / 首个工具 arguments,toolcall.py:150-152 打印工具结果,base.py:140 打 Executing step {n}/{max}。token 用量日志:LLM.update_token_count()(llm.py:238-247)每次请求打印 Input/Completion/累计。

无结构化 trace / span / OpenTelemetry / LangSmith 等(grep 无 telemetry/opentelemetry/langfuse/span 命中;仅 app/utils/logger.py 有 structlog dict_tracebacks,daytona sandbox 里把 ANONYMIZED_TELEMETRY=false)。可观测性 = 人读日志文件,无 trace UI、无 run 元数据落盘。

安全与权限(审批门、密钥管理)

审批门:无强制门。工具(含 bash/python/browser)执行前不请求确认。AskHuman 是 LLM 自愿调用的问询工具,非安全门(见工具体系一节)。

密钥管理:LLM api_key 从 config/config.toml(TOML,config.py:228-231)读;Daytona api_key、VNC 密码同理明文存 config(config.py:108-124,VNC 默认 123456)。无 secret vault、无 env 注入约定、无脱敏。

有限的路径安全:DockerSandbox._safe_resolve_path()(sandbox/core/sandbox.py:232-253)拒绝含 .. 的路径穿越。本地 PythonExecute 有极弱隔离:safe_globals 仅限定 __builtins__(tool/python_execute.py:57-60),并非真沙箱(能 import os 等),靠 multiprocessing.Process + 5s timeout 限时(python_execute.py:61-74)。结论:安全模型基本是「信任 LLM + 信任本机环境」,生产用需外层加固。

沙箱与执行隔离

两套沙箱:

  1. 本地 Docker 沙箱 app/sandbox/core/sandbox.py DockerSandboxdocker.from_env() 起容器,镜像默认 python:3.12-slim(config.py:98)。资源限:mem_limit=512mcpu_quotanetwork_mode="none"(默认断网,sandbox.py:66 + config network_enabled=False。容器名 sandbox_{uuid}working_dir=/workspacetail -f /dev/null 常驻。文件 IO 走 tar 流 put/get_archive(sandbox.py:166-375),命令走 AsyncDockerizedTerminal(terminal.py),默认命令 timeout 300s。全局单例 SANDBOX_CLIENT(client.py:201),run 结束 cleanup。
  2. Daytona 云沙箱 app/daytona/SandboxToolsBase(daytona/tool_base.py:50-139)通过 daytona SDK 起远端沙箱(含 VNC 6080 / web 8080 preview link),配套 sb_shell_tool/sb_files_tool/sb_browser_tool/sb_vision_toolapp/tool/sandbox/),由 SandboxManus agent 使用。

默认不启用沙箱SandboxSettings.use_sandbox=False(config.py:97)——PythonExecute/Bash 默认直接在宿主机跑,沙箱是 opt-in。Bash 工具(tool/bash.py)在宿主机起 /bin/bash 持久 session(asyncio.create_subprocess_shell + os.setsid),用 sentinel <<exit>> 界定输出,120s timeout,非隔离执行。

与模型的协同设计

LLM 封装在 app/llm.pyLLM 类,按 config_name 单例(__new__ llm.py:177-184)。走 OpenAI SDK;api_type 支持 openai / azure(AsyncAzureOpenAI)/ aws(自封 BedrockClient,llm.py:216-225),可对接任意 OpenAI 兼容端点(Ollama/PPIO 等,见 config/config.example-model-*.toml)。三个调用面:ask()(纯文本,支持流式)、ask_with_images()(多模态)、ask_tool()(function calling,强制非流式)。

模型能力探测靠硬编码白名单(已核对 llm.py:34-42):REASONING_MODELS=["o1","o3-mini"](走 max_completion_tokens 而非 max_tokens+temperature,llm.py:411-417);MULTIMODAL_MODELS 仅含 gpt-4-vision-preview / gpt-4o / gpt-4o-mini / claude-3-{opus,sonnet,haiku}(决定是否把 base64 图塞进 content,format_messages llm.py:305-339)。这两个列表明显偏老:gpt-4.1 / claude-4 / o3 等新模型不在内,会退化处理。

重试:tenacity wait_random_exponential(1,60) + stop_after_attempt(6),但 TokenLimitExceeded 显式不重试(llm.py:354-360 注释 + 462-464 re-raise)。无模型路由 / 多模型混用 / 成本优化调度——每 agent 绑一个 config_name 对应一个模型,可给不同 agent 配不同模型(base.py:52-53 用 name.lower() 作 config_name)。

轨迹利用(session/trajectory 是否反哺训练/评测)

本仓未实现。run 轨迹只落在 loguru 日志文件,无结构化 trajectory 导出、无 replay、无喂回训练 / 评测的 pipeline。轨迹→训练是独立仓 OpenManus-RL(README 提及,UIUC + OpenManus,GRPO 等 RL tuning for LLM agents)的职责,本代码库不含其代码,两者仅 README 互链。examples/benchmarks/ 目录存在但基本空(仅 __init__.py),无跑分 / eval 框架落地。

与同类 harness 的关键差异(1-3 条)

  1. 「3 小时原型」极简主义:整个框架就是一条 BaseAgent → ReActAgent → ToolCallAgent 继承链 + 一个可选 PlanningFlow,无记忆压缩 / 无持久化 / 无 trace / 无权限门,可读性极高、改造成本极低——这与走「工程完备」路线的 harness(分层记忆、结构化 trace、审批门)形成鲜明对照。
  2. 与同团队 MetaGPT 是两套代码:OpenManus 是同一 MetaGPT 团队做的独立轻量 Manus 复刻,代码仓不共用;MetaGPT 走 SOP/角色协作路线,OpenManus 走单 agent ReAct + 可选 planning 路线。
  3. 训练与运行彻底解耦:自进化 / RL / 轨迹利用全部外置到独立仓 OpenManus-RL,本仓是纯推理时 harness,不含任何学习闭环——「能跑」和「能学」是两个 repo 的事。

原始源码定位

  • repo: https://github.com/FoundationAgents/OpenManus (57k+ star,MetaGPT 团队)
  • commit/version analyzed: 52a13f2a57d8c7f6737eefb02ccf569594d442732026-01-04 11:11:56 +0800 Update README.mdgit clone --depth 1,克隆日期 2026-07-11)
  • 关键文件列表(相对 repo 根):
    • app/agent/base.py app/agent/react.py app/agent/toolcall.py app/agent/manus.py app/agent/browser.py app/agent/mcp.py app/agent/sandbox_agent.py
    • app/flow/planning.py app/flow/base.py app/flow/flow_factory.py
    • app/tool/base.py app/tool/tool_collection.py app/tool/mcp.py app/tool/planning.py app/tool/python_execute.py app/tool/bash.py app/tool/ask_human.py app/tool/terminate.py
    • app/prompt/manus.py app/prompt/browser.py app/prompt/toolcall.py app/prompt/planning.py
    • app/schema.py app/llm.py app/config.py app/logger.py
    • app/sandbox/core/sandbox.py app/sandbox/core/terminal.py app/sandbox/client.py
    • app/daytona/tool_base.py
    • protocol/a2a/app/agent_executor.py
    • main.py run_flow.py README.md

一手源存档(sources/)

存档于 /Users/zhao/projects/self-wiki/ai-research/sources/harness/openmanus/

  • NOTES.md — Phase 1 源码级调研笔记(元信息 + 12 维度,含 file:line 溯源)
  • src/(约 248K,23 个核心文件,相对路径保留):
    • src/agent/base.py react.py toolcall.py manus.py browser.py mcp.py
    • src/flow/base.py flow_factory.py planning.py
    • src/tool/base.py tool_collection.py mcp.py planning.py python_execute.py bash.py ask_human.py terminate.py
    • src/prompt/manus.py browser.py toolcall.py planning.py
    • src/sandbox/sandbox.py terminal.py client.py
    • src/schema.py llm.py config.py logger.py

注:无官方 paper / 设计博客 / docs 站,README 是唯一一手文档;PDF 类一手源不适用(本 harness 无论文),故 sources/llm HF bucket 不涉及本条目。