读完 3 个 Agent Skill 框架的源码后,我发现一个反直觉的结论:读源码最难的从来不是代码本身,而是找不到入口

打开 ECC 的 skills/ 目录(github.com/affaan-m/ECC),249 个子文件夹一字排开,每个里面还有自己的子目录和配置。这就是读源码的第一堵墙——你连从哪开始都不知道。

这一篇不讲设计哲学,讲方法论:怎么从"打开文件夹就晕"到"能画出完整调用链路"。所有示例都来自 Hermes Agent(github.com/NousResearch/hermes-agent)本地安装包的真实源码。

第一步:入口定位 —— 先找最大的文件,别找 main()

读源码的第一个陷阱是"从入口函数开始读"。入口函数通常是框架初始化代码,跟你想理解的核心逻辑毫无关系。

正确做法是从文件大小反推核心模块

1. 先 ls -lh 排序,找最大的文件——代码量大的地方,一定是核心逻辑

2. 再看 pyproject.toml 的 [project.scripts],确认 CLI 入口在哪

3. 两相对照,入口负责初始化,大文件负责干活

真实踩坑记录(以 Hermes Agent 为例):

我一开始以为对话主循环是全项目最大的文件——结果它根本不在安装包里。pip install 后 agent/ 目录 60 个 .py 文件,总共 3.8 万行。**真正的主宰是 auxiliary_client.py,4892 行,一个文件顶一部中篇小说**。

而 CLI 入口 hermes_cli/main.py 只有参数定义和调度逻辑,跟 Agent 核心逻辑关系不大。

这个发现让我意识到:读源码第一步应该是"找最大的文件",而不是"找 main()"

第二步:核心链路 —— 追一条完整调用链

不要试图理解每一行代码。先追一条完整链路——从"用户输入"到"系统输出",中间只关注 3-5 个关键节点。

具体做法

1. 自己先跑一次这个功能,理解"用户看到的输入和输出是什么"

2. 从入口函数开始,逐层跟踪函数调用,每层只看函数签名和返回值

3. 画一张调用链草图,标注每个节点的"输入是什么,输出是什么"

以 Hermes Agent 的 Skill 加载为例(真实调用链):

用户输入: "帮我写一篇文档"    ↓ hermes_cli/main.py: main() → 解析参数,进入对话循环    ↓ prompt_builder.py: build_skills_system_prompt()   ← 真正的核心    ↓ 内部调用:    ├─ _build_skills_manifest(skills_dir)          ← 扫描所有 SKILL.md    ├─ _parse_skill_file(skill_file)                ← 解析单个文件 YAML    └─ skill_utils.parse_frontmatter(content)       ← 底层 YAML 解析    ↓ 输出: 拼入 system prompt 的 skill 定义文本

真实踩坑记录

  • 同名文件误导:agent/curator.py(1781行)管运行时状态,hermes_cli/curator.py(599行)管技能安装。两个文件同名但职责完全不同——追调用链时跟进了 agent/curator.py,结果里面只有 load_state() 和 _load_config(),跟 Skill 加载没有半点关系。**真正的 Skill 重载是 skill_commands.reload_skills()**

追核心链路的诀窍是"允许自己看不懂 80% 的代码"。你只需要理解主干上的 3-5 个节点即可。

第三步:数据结构 —— 你以为有类,其实全是 dict

代码逻辑会变,但数据结构是项目的骨架。理解了核心数据长什么样,你就理解了项目 60% 的设计意图。

具体做法

1. 搜你想找的类型名——如果搜不到,本身就是发现

2. 找到数据实际是怎么定义和传递的

3. 画一张数据流草图,标注"数据从哪来,到哪去"

以 Hermes Agent 为例(真实踩坑):

我先搜 class SkillConfig,整个 agent/ 目录 0 结果。再搜 SkillConfig(不带 class),还是 0。

原来 Hermes Agent 根本没有给 Skill 配置定义类型。Skill 的配置全是通过 YAML frontmatter 解析成 dict——name、description、triggers 这些字段靠 skill_utils.parse_frontmatter() 提取,没有任何类型约束。

这就是为什么你创建的 Skill 有时候不生效但代码不报错——字段写错了只是静默失效,dict 不会帮你校验。

我以为有 SkillConfig 类,结果全是 dict——这个发现比找到类定义更有价值。它解释了 Hermes Agent 的一个设计取舍:灵活优先,安全靠人。

第四步:验证理解 —— 别信代码,信测试

你"觉得"自己读懂了不算数。真正的验证是:看测试怎么用这个函数,然后自己改一行代码看效果。

具体做法

1. 找到核心函数的测试文件 —— 通常命名规则是 test_<模块名>.py

2. 读测试的 setUp 部分 —— 这里展示了"这个函数正常运转需要哪些前置条件"

3. 读测试的 assert 部分 —— 这里展示了"这个函数的正确输出应该长什么样"

4. 自己在测试里加一个 print,跑一遍看实际数据

真实踩坑记录

  • mock 太多分不清:有些项目的测试里 mock('fs')、mock('../../utils/config')、mock('../../services/ai') 三层 mock 之后根本分不清哪部分是真实行为
  • 解决方案:只改一个 mock 为真实实现,跑测试看效果。比如把 fs.readFileSync 的 mock 去掉,让它真的读一个测试用的 SKILL.md

测试是最好的文档。代码注释可能会过时,但测试过时了会直接报错。所以你读到的测试行为就是当前代码的真实行为。

方法论提炼

四步法的本质是一套"从外向内、从具体到抽象"的路径:

用户行为(CLI命令)    → 文件大小排序(auxiliary_client.py 最大)    → 核心链路(prompt_builder.py: build_skills_system_prompt)    → 数据结构(没有 SkillConfig,全是 YAML→dict)    → 测试验证(test_curator.py)

这不是我发明的,而是被 249 个 Skill 目录逼出来的。面对信息过载时,大脑天然需要一条确定的路径。四步法就是这条路径。

三种读者,三种用法

  • 想快速了解这个项目 → 走第一步即可(10分钟)
  • 想改个功能/修个 bug → 走前两步(1小时)
  • 想深度理解架构 → 走完四步(半天)

你上次读一个陌生项目的源码,花了多久才找到真正的入口?

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐