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

问题从哪里来

最近我花了一周时间学习 Hermes—— 一个功能丰富的 Agent 开发框架。它的架构包含 Gateway 路由、多平台适配、MCP 集成、浏览器自动化、语音与视觉、自进化机制等多个模块。打开代码仓库的第一感觉是:东西太多,从哪里开始

常见的学习方式是顺着文档和代码一路读下去,但这种方法的问题是 —— 读完了不等于理解了。你可能知道每个模块叫什么,但问你」这个系统为什么这样设计」,你答不上来。

我在这一周尝试了另一种学习路径,效果明显好于纯阅读。

真正卡住的是哪一步

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

读了一遍 Gateway 的代码,你以为你懂了。但如果你换个平台运行呢?如果你尝试去掉一个模块看看哪里会坏呢?如果你动手写一个最简版本,你会发现很多」以为懂」的地方其实经不起推敲。

这就是问题本质:静态阅读给的是幻觉,动态交互给的是真相

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

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

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

我最后怎么处理

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

解剖:按照系统分模块阅读,但不是逐行读代码,而是先理清模块边界和职责。例如: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 阶段的事。