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 52a13f2a57d8c7f6737eefb02ccf569594d44273(2026-01-04 11:11:56 +0800 Update README.md,git 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.py 的 PlanningFlow——计划分解 + 多 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。
停止条件三种:
terminate特殊工具被调用 →_handle_special_tool置state=FINISHED(toolcall.py:210-223,_should_finish_execution默认恒 True);- max_steps 上限(Manus/Browser/MCP 默认
max_steps=20,ToolCallAgent 基类 30,BaseAgent 10); - 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_tokens 抛 TokenLimitExceeded(不重试),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。
工具体系(定义/调用协议/注册/权限)
工具基类 BaseTool(tool/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__ 拼接。
注册:ToolCollection(tool/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-56 调 llm.ask_tool(tools=..., tool_choice=AUTO),execute_tool(toolcall.py:166-208)json.loads arguments 后 available_tools.execute。
权限体系:基本没有。工具执行前无审批门、无 allowlist/denylist、无危险命令拦截。唯一的「人在环」是 AskHuman 工具(tool/ask_human.py,execute 直接 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)
编排入口 PlanningFlow(flow/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.json(config.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|training 在 app/ 下零命中)。唯一沾边的是 is_stuck()/handle_stuck_state() 的重复检测→改 prompt(见 Agent Loop 一节),属被动防卡死,不是学习。RL / 自进化是另一个仓 OpenManus-RL(UIUC + OpenManus 合作,GRPO 类微调 agent),与本代码库解耦,仅 README 互链。
可观测性(日志 / trace 格式)
日志用 loguru:logger.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 + 信任本机环境」,生产用需外层加固。
沙箱与执行隔离
两套沙箱:
- 本地 Docker 沙箱
app/sandbox/core/sandbox.pyDockerSandbox:docker.from_env()起容器,镜像默认python:3.12-slim(config.py:98)。资源限:mem_limit=512m、cpu_quota、network_mode="none"(默认断网,sandbox.py:66 + confignetwork_enabled=False)。容器名sandbox_{uuid},working_dir=/workspace,tail -f /dev/null常驻。文件 IO 走 tar 流 put/get_archive(sandbox.py:166-375),命令走AsyncDockerizedTerminal(terminal.py),默认命令 timeout 300s。全局单例SANDBOX_CLIENT(client.py:201),run 结束 cleanup。 - 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_tool(app/tool/sandbox/),由SandboxManusagent 使用。
默认不启用沙箱: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.py 的 LLM 类,按 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 条)
- 「3 小时原型」极简主义:整个框架就是一条
BaseAgent → ReActAgent → ToolCallAgent继承链 + 一个可选PlanningFlow,无记忆压缩 / 无持久化 / 无 trace / 无权限门,可读性极高、改造成本极低——这与走「工程完备」路线的 harness(分层记忆、结构化 trace、审批门)形成鲜明对照。 - 与同团队 MetaGPT 是两套代码:OpenManus 是同一 MetaGPT 团队做的独立轻量 Manus 复刻,代码仓不共用;MetaGPT 走 SOP/角色协作路线,OpenManus 走单 agent ReAct + 可选 planning 路线。
- 训练与运行彻底解耦:自进化 / RL / 轨迹利用全部外置到独立仓 OpenManus-RL,本仓是纯推理时 harness,不含任何学习闭环——「能跑」和「能学」是两个 repo 的事。
原始源码定位
- repo: https://github.com/FoundationAgents/OpenManus (57k+ star,MetaGPT 团队)
- commit/version analyzed:
52a13f2a57d8c7f6737eefb02ccf569594d44273(2026-01-04 11:11:56 +0800 Update README.md;git clone --depth 1,克隆日期 2026-07-11) - 关键文件列表(相对 repo 根):
app/agent/base.pyapp/agent/react.pyapp/agent/toolcall.pyapp/agent/manus.pyapp/agent/browser.pyapp/agent/mcp.pyapp/agent/sandbox_agent.pyapp/flow/planning.pyapp/flow/base.pyapp/flow/flow_factory.pyapp/tool/base.pyapp/tool/tool_collection.pyapp/tool/mcp.pyapp/tool/planning.pyapp/tool/python_execute.pyapp/tool/bash.pyapp/tool/ask_human.pyapp/tool/terminate.pyapp/prompt/manus.pyapp/prompt/browser.pyapp/prompt/toolcall.pyapp/prompt/planning.pyapp/schema.pyapp/llm.pyapp/config.pyapp/logger.pyapp/sandbox/core/sandbox.pyapp/sandbox/core/terminal.pyapp/sandbox/client.pyapp/daytona/tool_base.pyprotocol/a2a/app/agent_executor.pymain.pyrun_flow.pyREADME.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.pyreact.pytoolcall.pymanus.pybrowser.pymcp.pysrc/flow/:base.pyflow_factory.pyplanning.pysrc/tool/:base.pytool_collection.pymcp.pyplanning.pypython_execute.pybash.pyask_human.pyterminate.pysrc/prompt/:manus.pybrowser.pytoolcall.pyplanning.pysrc/sandbox/:sandbox.pyterminal.pyclient.pysrc/:schema.pyllm.pyconfig.pylogger.py
注:无官方 paper / 设计博客 / docs 站,README 是唯一一手文档;PDF 类一手源不适用(本 harness 无论文),故 sources/llm HF bucket 不涉及本条目。