MetaGPT

一句话定位

MetaGPT 是一个以”软件公司 SOP”为原始隐喻的多智能体框架,核心是 Environment 消息总线 + 角色(Role)订阅/发布,早期版本靠固定流程角色(ProductManager→Architect→Engineer→QaEngineer)模拟瀑布式协作;当前默认路径(software_company.py)已切换为以 RoleZero(ReAct+工具调用基类)为基础的 TeamLeader/DataAnalyst/Engineer2 等角色,由 TeamLeader(“Mike”)做 hub-and-spoke 式编排。商业化产品是 mgx.dev。

核心架构总览

分析基于 commit 11cdf466d042aece04fc6cfd13b28e1a70341b1f(2026-01-21,clone/读取于 2026-07-07),MIT 协议。关键路径(相对 metagpt/ 包根):

  • 角色与循环:roles/role.py(legacy SOP 基类)、roles/di/role_zero.py(现代 ReAct+工具角色)、roles/di/team_leader.pyroles/di/data_interpreter.py
  • 编排:team.py(顶层 run loop)、environment/base_env.py(消息总线)、environment/mgx/mgx_env.py(hub-and-spoke 路由)、software_company.py(CLI 入口/默认团队组成)
  • 工具:tools/tool_registry.pytools/tool_recommend.pytools/libs/terminal.pyactions/di/execute_nb_code.py
  • Prompt:prompts/di/role_zero.py
  • 记忆:memory/memory.pymemory/longterm_memory.pymemory/role_zero_memory.py
  • 自进化:exp_pool/decorator.pyexp_pool/manager.py
  • 可观测性:utils/report.py
  • 配置/安全:config2.py

官方架构文档不在仓库内,而是外部 docs 站点的 RFC 系列:docs.deepwisdom.ai 的 RFC-116(消息路由从”共享公共内存”改为”私有 per-Role 缓冲 + Environment 路由”的设计变更)被 role.py/memory.py 代码注释直接引用,是目前能找到的最接近正式架构文档的一手材料。

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

两套并存的循环实现:

  • Legacy SOP 循环role.py:454-496):RoleReactMode 枚举 react | by_order | plan_and_actreact 模式做 _think(LLM 挑下一个”state”/action 序号)→_act(执行 Action.run),循环上限 max_react_loop(默认 1);plan_and_act 模式先由 Planner 生成计划,再逐 task 调 _act_on_task
  • 现代 ReAct+工具循环role_zero.py:303-336):每次 run() 先走 _quick_think——LLM 把意图分类为 QUICK/SEARCH/TASK/AMBIGUOUS,简单请求走快速路径、不进入完整循环;否则进入 _observe→_think→_act 循环,上限 max_react_loopRoleZero 默认 50TeamLeader 默认 3,见 role_zero.py:71 / team_leader.py:29)。达到上限时若 max_react_loop>=10role_zero.py:328)会触发”询问人类是否继续”的检查点——一个内建的失控循环安全阀。
  • 终止条件:角色发出 {"command_name":"end"}_run_special_commandrole_zero.py:431 附近);或 Plan.is_plan_finished;或预算超支(Team._check_balanceNoMoneyExceptionteam.py:98-100)。
  • 顶层 Team.run(n_round)team.py:122-138)本身是有界循环:每轮调用 Environment.run(),用 asyncio.gather 并发触发所有非 idle 角色,env.is_idle 时提前退出。

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

三套彼此独立、未统一的机制:

  • 基础 Memorymemory/memory.py):纯进程内 list + 按 cause_by action 索引的 dict,无压缩、无容量上限、默认不持久化。
  • RoleZeroLongTermMemorymemory/role_zero_memory.py,现代路径):短期存储超过 memory_k(默认 200 条)时,把溢出的旧消息灌入 Chroma 后端的 RAG 引擎(SimpleEngine);读取时若最后一条是用户需求,会检索相关长期记忆前置到近期窗口。是否启用由 config.role_zero.enable_longterm_memory 控制,默认关闭
  • LongTermMemorymemory/longterm_memory.py,legacy 路径):供旧 SOP 角色使用,find_news 上做基于 embedding 相似度的去重。
  • 会话持久化Team.serialize()/deserialize()team.py:59-81)把整个 team+env+role 状态 dump 到 storage/team/team.json,用于崩溃恢复(官方文档称”breakpoint recovery”),这是一个独立于记忆压缩的机制。
  • 没有发现显式的上下文窗口截断/摘要步骤;memory_k 起到硬截断作用,RAG 是”召回被截断部分”的补偿机制。

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

  • @register_tool 装饰器(tool_registry.py:94-118)通过 AST/inspect(tool_convert.py,未逐行读)从 docstring+签名自动生成 JSON schema,存入全局单例 TOOL_REGISTRY,可带 tags
  • 支持运行时从任意文件路径动态注册工具(register_tools_from_pathtool_registry.py:185-194)——用户提供的本地 .py 文件可以直接变成可调用工具。
  • 选择:ToolRecommendertool_recommend.py)做两段式 召回→排序:BM25ToolRecommender 用 BM25 对工具 docstring 召回候选,再用 LLM 调用(TOOL_RECOMMENDATION_PROMPT)排序取 top-k;force=True 跳过排序、直接全量返回(TeamLeader 用这个模式,因为它固定需要 Plan/RoleZero/TeamLeader 系列工具)。
  • 调用协议:RoleZero._run_commandsrole_zero.py:385-415)执行一个 JSON 数组 {"command_name": "Class.method", "args": {...}},逐条对照一个普通 Python dict tool_execution_mapset_tool_execution 构建,role_zero.py:118-171)分发,例如 "Editor.read"self.editor.read;批次中某条失败即 break 中断后续命令。不是 OpenAI function-calling 或 MCP 那种正式协议,而是烘焙进系统提示词里的自定义 JSON 命令数组约定。
  • 权限:没有正式的按工具权限/白名单系统;唯一的可执行动作限制是 terminal 的 2 条 forbidden_commands 子串黑名单(见下)。

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

SYSTEM_PROMPTprompts/di/role_zero.py:25-50)逐轮动态组装,拼接内容包括:角色信息(name/goal/constraints + 含同伴角色名的环境描述)、硬编码的 Task pydantic schema(以字面 Python 源码形式展示给模型)、task_type_descavailable_commands(工具推荐器给出的 JSON schema)、从经验池检索到的 example,以及静态 instruction 块。CMD_PROMPT 在此之上叠加当前 plan/task 状态、工具”state”、以及严格的输出格式要求(必须恰好输出一个 JSON 命令数组,允许 JSON 前有思考文字),其中有非常字面化的抗幻觉格式约束,例如明确要求输出必须以 ```json [ 开头(prompts_role_zero.py)。另有一个更轻量的 QUICK_THINK_SYSTEM_PROMPT 专供意图分类快速路径使用。Legacy SOP 角色则用 role.py:51-79PREFIX_TEMPLATE/STATE_TEMPLATE/ROLE_TEMPLATE,走”从状态列表选序号”的更简单模式。

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

多智能体编排是核心而非附加功能:

  • Environmentenvironment/base_env.py)是发布订阅式消息总线:publish_message 把消息广播给所有订阅地址匹配 message.send_to 的角色;Role._watch() 加默认的”自身类名/自身 name”订阅决定角色对什么消息有反应(对应 RFC-116 的设计)。
  • MGXEnvenvironment/mgx/mgx_env.py)重写 publish_message强制所有消息经过 TeamLeader(角色名 “Mike”),除非属于已建立的人类↔角色直接会话(direct_chat_roles 集合)——默认拓扑是通过一个指定编排角色的 hub-and-spoke,而非扁平广播。这一”强制走 TeamLeader”的设计已用官方 RFC-116 文档交叉核实。
  • TeamLeaderroles/di/team_leader.py)是 RoleZero 子类,额外工具只有 publish_team_message(content, send_to)——它通过发出”给队友 X 发消息”的命令来做任务分解/委派;真正的任务分解逻辑在共享的 Planner/plan-task 机制(metagpt/strategy/planner.py,本轮未完整读)里,而非一个独立的层级化 planner 类。
  • 默认团队组成(software_company.py:44-54):TeamLeader, ProductManager, Architect, Engineer2, DataAnalyst;更早的 ProjectManager/Engineer/QaEngineer SOP 角色已被注释掉——即 RoleZero 系角色已取代原始”软件公司 SOP”角色成为默认路径。

Skill / 插件体系

没有与工具系统分离的独立”skill”抽象——工具注册表本身就是插件系统:任何 Python 类/函数都可以通过 @register_tool 变成”技能”,放进 metagpt/tools/libs/,或从运行时任意外部文件路径注册(register_tools_from_path)。仓库里还有一个看起来是遗留、未被 RoleZero/TeamLeader/DataInterpreter 引用的 metagpt/skills/ 包(早于 tool_registry 设计,本轮未深读)以及未读的 metagpt/learn/。结论:工具系统即技能系统,二者统一,无独立分层。

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

两个容易混淆、但需分开看的机制:

  • 经验池 / exp_cacheexp_pool/decorator.pyexp_pool/manager.py):包裹 RoleZero.llm_cached_aask 的请求-响应缓存回放循环。每次 LLM 调用前先向向量/BM25 后端的经验存储查询相似的过去 (req, resp) 对,若被 SimplePerfectJudge 判定为”完美”匹配,直接跳过 LLM 调用复用旧响应;否则正常执行,用 SimpleScorer 给新响应打分并写回经验池。这是 MetaGPT 里最接近”从自身轨迹学习”的机制,但本质是显式的基于案例推理/缓存,不是梯度学习,且默认关闭config.exp_pool.enabled=False)。挂载点:RoleZero.llm_cached_aaskrole_zero.py:267 附近)经 @exp_cache(context_builder=RoleZeroContextBuilder(), serializer=RoleZeroSerializer())
  • 长期记忆检索role_zero_memory.py)有时被误认为自进化,实际只是 RAG 式上下文管理(见”记忆”节),不做存储内容的打分/筛选。
  • 核心框架内未发现 eval 驱动的自我纠错循环或自动 prompt/权重调优。metagpt/ext/aflow/(AFlow,ICLR 2025 oral,工作流自动生成优化)、metagpt/ext/spo/(SPO,Self-Supervised Prompt Optimization,arXiv 2502.06855)、metagpt/ext/sela/(SELA,数据科学 agent 搜索)是独立的研究扩展模块,README 链接的论文显示它们确实把轨迹/rollout 当优化信号用,但属于 opt-in 的示例/研究模块,不在默认 agent 运行时里,本轮未深读,作为后续 dossier 加强”自进化”证据的线索标注。

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

ResourceReporter 及其类型化子类(utils/report.py)把结构化 JSON 块(按 BlockType 枚举打标:TERMINAL/TASK/BROWSER/BROWSER_RT/EDITOR/GALLERY/NOTEBOOK/DOCS/THOUGHT)POST 到可配置的 callback_url(常量 METAGPT_REPORTER_DEFAULT_URL),支持 HTTP(S) 和 Unix-socket(requests_unixsocket)——这是给 mgx.dev 前端做实时 UI 流式展示(思考过程、终端输出、notebook cell、浏览器动作)的管道,在 role_zero.py/execute_nb_code.py 里能看到 ThoughtReporter/NotebookReporter/TerminalReporter 的调用点。普通日志走 metagpt/logs.py(loguru,未完整读)。没有发现 OTel 式的标准 trace/span 格式——reporter 协议是面向自家 UI 的专用协议,不是通用可观测性标准。

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

设计上非常薄:

  • 密钥管理:Configconfig2.py)从 ~/.metagpt/config2.yaml明文 YAML)与 os.environ 合并加载(优先级:env < 默认配置路径 < kwargs),API key 等敏感字段是普通 pydantic 字符串字段,无 keyring/vault/加密存储
  • 审批门:RoleZero/TeamLeader 路径下未发现工具执行前的审批机制——LLM 输出的 JSON 命令数组直接经 tool_execution_map 执行。唯一例外是达到 max_react_loop 后的人类确认检查点(见 Agent Loop 节),这是失控循环安全阀,不是逐动作权限门。
  • 唯一发现的访问限制是 Terminal.forbidden_commandsterminal.py:41-44):2 条子串黑名单("run dev""serve "),命中后静默替换为无操作命令——明确是为了防止 agent 错误地把 dev server 挂到后台,不是安全控制。

沙箱与执行隔离

框架层面没有沙箱

  • Terminaltools/libs/terminal.py)用 asyncio.create_subprocess_exec 起真实本地 bash/cmd.exe 子进程,env=os.environ.copy(),工作目录默认就是真实工作区——完整宿主机访问权限,无容器/chroot/VM/seccomp。
  • ExecuteNbCodeactions/di/execute_nb_code.py)通过 nbclient/nbformatNotebookClient)跑本地 Jupyter kernel,同样是真实本地 Python 进程,未沙箱化;仅有的缓解措施是每个 cell 的超时(默认 600s,CellTimeoutError 触发 kernel interrupt)和死内核恢复(reset())。
  • 全仓库未发现 Docker/gVisor/Firecracker/E2B 一类隔离层。

与模型的协同设计

未发现模型级协同设计产物(无定制模型 checkpoint、无 MetaGPT 专属微调模型、无专用模型 server)。MetaGPT 是纯粹的多 provider LLM harness(metagpt/provider/,未深读,但 config2.pyLLMConfig/LLMType 的导入暗示广泛支持 openai/azure/ollama/groq 等,与 README 一致)。框架确实针对特定 LLM 行为做了绕行式适配:JSON_REPAIR_PROMPTprompts/di/role_zero.py:131-146)是专门用一次额外 LLM 调用修复畸形 JSON 工具调用输出的 prompt;repair_llm_raw_output/extract_state_value_from_output(在 role.py/role_zero.py 中引用,未完整读)暗示一整套”LLM 输出不可靠、加修复通道”的模式——即 MetaGPT(至少在 RoleZero 路径上)是靠自由文本 prompting + 正则/JSON 解析约定与模型交互,而非原生 tool-calling/structured-output API。

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

  • 经验池(见”自进化”节)是唯一确认的”过去轨迹反哺未来行为”机制,但是推理时的缓存回放,不是训练数据导出。
  • Team.serialize() 把完整会话状态 dump 成 JSON 用于恢复,技术上是可导出的轨迹产物,但核心框架内未发现把它转成训练数据或 benchmark 格式的代码路径。
  • metagpt/ext/aflow/metagpt/ext/spo/metagpt/ext/sela/ 是独立研究项目(工作流优化/prompt 优化/数据科学 agent 搜索),按 README 论文链接(AFlow arXiv/ICLR 2025 oral,SPO arXiv 2502.06855)大概率确实把轨迹/rollout 当优化信号用,但内部实现本轮未读,标记为待跟进的线索而非已确认发现。

与同类 harness 的关键差异

  • 与偏”单 agent + 工具”的 harness(如 Claude Code / Warp)相比,MetaGPT 的默认心智模型始终是多角色团队:即使 RoleZero 取代了 SOP 角色,编排层(MGXEnv 强制走 TeamLeader)依然是框架级、非可选的一等公民,而不是上层拼出来的功能。
  • “quick think 快速路径”(role_zero.py 的意图分类 QUICK/SEARCH/TASK/AMBIGUOUS)是一个相对少见的显式设计:在进入完整 ReAct 循环前先做一次分类判断是否需要走重量级路径。
  • 自进化机制是显式的”完美经验缓存回放”(exp_cache),而非隐式的长期记忆学习或权重更新——且默认关闭,说明团队认为这更像一个性能优化开关而非核心能力。
  • 跨 harness 系统对比(与 Claude Code、Warp、OpenHands 等的详细比较矩阵)留待 synthesis 阶段统一做。

原始源码定位

  • repo: https://github.com/FoundationAgents/MetaGPT
  • commit/version analyzed: 11cdf466d042aece04fc6cfd13b28e1a70341b1f(2026-01-21 18:12:32 +0800 提交,2026-07-07 clone/分析)
  • 关键文件列表(相对 metagpt/ 包根):
    • roles/role.py
    • roles/di/role_zero.py
    • roles/di/team_leader.py
    • roles/di/data_interpreter.py
    • team.py
    • environment/base_env.py
    • environment/mgx/mgx_env.py
    • software_company.py
    • prompts/di/role_zero.py
    • tools/tool_registry.py
    • tools/tool_recommend.py
    • tools/libs/terminal.py
    • actions/di/execute_nb_code.py
    • memory/memory.py
    • memory/longterm_memory.py
    • memory/role_zero_memory.py
    • exp_pool/decorator.py
    • exp_pool/manager.py
    • utils/report.py
    • config2.py

一手源存档(sources/)

/Users/zhao/projects/self-wiki/ai-research/sources/harness/metagpt/

  • NOTES.md — stage-1 完整调研笔记(逐维度发现、行号引用、未找到项说明)
  • keyfiles/ — 19 份原样保存的源文件:role.pyrole_zero.pyteam_leader.pydata_interpreter.pyprompts_role_zero.pyteam.pyenvironment_base_env.pymgx_env.pyexecute_nb_code.pyterminal.pytool_registry.pytool_recommend.pymemory.pylongterm_memory.pyrole_zero_memory.pyexp_pool_decorator.pyexp_pool_manager.pyreport.pysoftware_company.pyconfig2.py