如何真正理解一个复杂系统 —— 从学习 Hermes 到复刻最小版本

问题从哪里来

最近花了一周学习 Hermes—— 一个功能丰富的 Agent 开发框架。

它的架构包含 Gateway 路由、多平台适配、MCP 集成、浏览器自动化、语音与视觉、自进化机制等多个模块。

打开代码仓库的第一感觉是:东西太多,从哪里开始

常见的学习方式是顺着文档和代码一路读下去。

但这种方法的问题:读完了不等于理解了。你可能知道每个模块叫什么,但问」这个系统为什么这样设计」,答不上来。

真正卡住的是哪一步

学习复杂系统时,真正卡住的不是」代码看不懂」,而是 没有一个检验理解程度的标准

读了一遍 Gateway 的代码,你以为懂了。但换个平台运行呢?去掉一个模块看看哪里会坏呢?动手写一个最简版本呢?

你会发现很多」以为懂」的地方经不起推敲。

静态阅读给的是幻觉,动态交互给的是真相。

我在 Windows 上跑 Hermes,终端功能直接挂了。原因有两个:

  1. 代码默认启动 WSL 下的 bash,但我的环境里 WSL 不可用。
  2. 临时文件路径硬编码为 /tmp/...,Windows 下 Python 和 bash 指向不同位置。

这些问题文档里不会写,代码 review 也难看出来。只有实际运行才暴露。

我最后怎么处理

整个过程分三步:解剖、修复、复刻

flowchart LR
    A[解剖] --> B[修复]
    B --> C[复刻最小版本]
    C --> D[验证理解]

解剖:按系统分模块阅读。不是逐行读代码,先理清模块边界和职责。

例如 Gateway 负责统一入口和路由,平台适配器抹平 OS 差异,自进化体系由技能创作闭环、生命周期 Hook、对话轨迹记录三块组成。每个模块问一个问题 ——「如果没有这个模块,系统会怎么样?」

修复:在 Windows 上把系统跑起来,遇到报错不跳过,追到底。

写了 LocalBackend._find_bash() 静态方法定位 Git Bash 路径;把硬编码临时目录改成 tempfile.gettempdir() 动态获取。修 bug 的过程,逼你理解系统对运行环境的假设。

复刻:最关键的一步。写一个 Agent 项目,复刻 learn-hermes 的基本功能。

先只跑通对话 loop,其他高级功能用 mock 占位。这不是偷懒 —— 最小可用版本让你在不受细节干扰的前提下,验证对核心架构的理解。骨架跑稳了,再逐个接真实功能。

同时在课程设计项目里实践了 Skill 驱动开发:先写 PRD 定边界,再做 Tech Plan(只规划文件名,不写实现),再用 TDD 推进编码,大里程碑之间做审计。

这件事留下的经验

第一,跑起来比读完更重要。

文档和代码随时能查。但不跑一次,你永远不知道漏掉什么。Windows 下的两个 bug 很小,背后反映的是系统对 POSIX 环境的隐含假设 —— 只有动手才能获得这个认知。

第二,复刻最小版本是终极检验。

你说你理解了某个系统,那你应该能写出它的最简版本。不需要功能完整,核心骨架要能跑。mock 是合法的设计选择 —— 让你聚焦架构而非细节。

第三,遇到 bug 是好事。

在理想环境正常工作的系统,和你能在各种环境让它正常工作的系统,是两种理解层次。环境兼容性问题往往暴露了设计中」未经审视的假设」。

第四,计划阶段克制细节冲动。

Tech Plan 只写文件名不写实现内容。这个策略在保护你:强制把注意力放在模块划分和职责边界上,而不是某个函数的实现。后者是 TDD 阶段的事。