
上一集回答”外部贡献发生在哪一层”——五个角色 PR,零个核心 PR。这一集只回答一个问题:为什么角色层成了低风险贡献面?
不是”文档好”——CONTRIBUTING.md 只有一小节提到角色。不是”角色简单”——角色定义涉及审查视角、专业领域、启用条件和反模式,这些不是随便写的。真正的原因在于接口本身发出的信号。
三个信号
一个接口要让陌生人愿意改,必须同时发出三个信号:
信号一:看得懂。 外部人能在没有指导的情况下理解格式和语义。
信号二:改得动。 外部人能用现有工具完成修改,不需要搭建环境或理解调用链。
信号三:改坏了不怕。 改错之后最坏的后果是可接受的。
这三个信号不是独立的——它们构成一个递进关系。看得懂但改不动,没用。改得动但改坏了后果严重,不敢改。三个条件同时满足,才会产生实际贡献。
信号一:看得懂——自解释的结构
打开任何一个 roles/*.md 文件:
---
name: performance
title: Performance Specialist
tags: [review]
---
## Identity
You are a performance-focused code reviewer...
## Expertise
- Algorithmic complexity analysis
- Memory allocation patterns
...
## When to Include
- Code touching hot paths or data-intensive loops
...
## Anti-Patterns
- Premature optimization without profiling evidence
...
这个格式有几个特征使它自解释:
命名是语义的。 Identity 不需要解释——就是”你是谁”。Expertise 就是”你擅长什么”。When to Include 就是”什么时候该叫你上场”。Anti-Patterns 就是”什么时候不该做某事”。四个段落名本身就是完整的说明。
示例即规范。 当你看到 roles/frontend.md 的 ## Identity 写的是”You are a frontend specialist…”,你不需要读任何 spec 就知道自己的 ## Identity 也应该以”You are a…”开头。已有的角色文件就是最好的模板。S1E04 讲过”已有代码即文档”——在角色系统里,已有角色即规范。
YAML frontmatter 是自明的。 name: performance、title: Performance Specialist、tags: [review]——不需要知道 tags 会被哪段代码解析,看到 [review] 和 [build] 的对比就知道这是一个分类标签。
#24 的 PR 说明里写道:“Based on the role template in CONTRIBUTING.md.” 但 CONTRIBUTING.md 里只有三行关于角色的说明。真正的模板是 roles/frontend.md 本身——贡献者打开一个已有文件,读懂了结构,然后复制粘贴改成自己的版本。
对比 harness 核心。flow-transition.mjs 里有一个函数处理节点转移:
function resolveNextNode(currentNode, verdict, loopCounts, maxLoops) {
const edges = FLOW_GRAPHS[currentFlow].edges[currentNode];
// ...
}
要理解这段代码,你需要知道:FLOW_GRAPHS 的数据结构是什么?edges 数组的每个元素有哪些字段?verdict 有几种可能的值?loopCounts 是怎么递增的?一个函数签名背后隐含了整个流水线的状态模型。
角色文件的理解成本是 O(1)——打开一个文件就够了。核心代码的理解成本是 O(n)——你需要理解 n 个互相依赖的组件才能改一个。
信号二:改得动——零工具链依赖
改一个角色文件需要什么?
- 一个文本编辑器
- 会写 markdown
- 对某个专业领域有认知
不需要 npm install。不需要理解 OPC 的构建系统。不需要跑测试(角色文件没有对应的测试)。不需要配置本地开发环境。不需要知道 flow-transition.mjs 的存在。
贡献路径是这样的:
Fork → 在 GitHub 网页编辑器里新建 roles/xxx.md → 粘贴四段式模板 → 填入内容 → 提交 PR
整个过程可以在浏览器里完成,不需要 clone 仓库到本地。
对比核心代码的贡献路径:
Fork → Clone → npm install → 理解 flow-transition.mjs →
理解 gate-protocol.md → 理解 synthesize 的 emoji 解析逻辑 →
写代码 → 跑 109 个测试 → 确认不破坏已有流程 → 提交 PR
第一条路径的摩擦接近零。第二条路径的摩擦是一面墙。
Cherry Studio(一个 Electron 桌面应用)有类似的分层现象。它的插件系统分两层:providers/ 下的 AI 模型适配器只需要实现一个标准接口(sendMessage、getModels),社区贡献了 20 余个 provider(截至 2026 年 5 月 GitHub 可查);但核心的对话管理、上下文窗口计算、会话存储——零个外部 PR。Provider 是”改得动”的接口,核心是那面墙。
信号三:改坏了不怕——最大降级幅度
这是最重要的信号。
一个角色文件写错了会发生什么?
最好的情况: 审查流程多了一个有效视角。Performance 角色帮审查者看到了隐藏的 O(n²)。
最坏的情况: 审查流程多了一个无效视角。某个角色的 ## Expertise 写得太宽泛,审查时产生了噪声建议。OPC 的门禁系统不会因为一个角色的建议质量差而崩溃——synthesize 按 emoji 计数判定,一个角色的噪声不会改变整体裁决。
最坏情况的恢复成本: 删除或修改一个 markdown 文件。十秒钟。
现在看核心代码。gate-protocol.md 里的一个判定条件改错了会发生什么?
最坏的情况: 所有门禁判定反转。本该 PASS 的判 FAIL,本该 FAIL 的判 PASS。S2E08 发现的”FAIL 从未触发”可能变成”PASS 从未触发”。整条流水线的行为翻转。
最坏情况的恢复成本: 需要理解门禁状态机、emoji 解析逻辑、回环计数器之间的交互,定位错误,回滚到正确版本。可能需要几个小时。
用一个公式表达:
贡献意愿 ∝ 1 / (最大降级幅度 × 恢复成本)
角色文件:最大降级幅度低,恢复成本几乎为零。贡献意愿高。
核心代码:最大降级幅度高,恢复成本高。贡献意愿近乎为零——除非你真正理解系统。
三个信号的交汇点
把三个信号放在一起看:
| 维度 | 角色文件 | 核心代码 |
|---|---|---|
| 看得懂 | O(1),打开一个文件 | O(n),理解 n 个组件 |
| 改得动 | 浏览器里就能改 | 需要完整本地环境 |
| 改坏了不怕 | 最坏:多一个噪声视角 | 最坏:流水线行为翻转 |
| 实际贡献 | 5 个 PR | 0 个 PR |
这张表不是角色系统设计的功劳——坦白说,这些特征是巧合多于设计。角色文件用 markdown 格式是因为 Claude Code 的 skill 系统就用 markdown。四段式结构是因为我在 S1E04 需要一个简单的角色定义格式,凑巧选了一个自解释的命名方案。CONTRIBUTING.md 里提到角色贡献是后加的,不是预设的入口。
但巧合产生的效果是真实的。至少在 OPC 的案例中,好的扩展点不一定是设计出来的——有时候是选对了一个简单格式,恰好满足了三个信号。
低风险贡献面:信任转移的入口
EP01 说信任有五层。EP02 说信任在叶子层转移了、在核心层停住了。这一集补充了中间的机制:
低风险贡献面(Low-Risk Contribution Surface) 是信任转移的入口。
一个开源项目想要外部贡献,第一件事不是写更好的文档,不是简化代码,不是搞 “good first issue” 标签。第一件事是确保存在一个同时满足”看得懂、改得动、改坏了不怕”的接口层。
OPC 的角色系统恰好是这样一个接口层。它不是最重要的部分——核心是 harness 和 gate。但它是最容易产生外部贡献的部分。信任不从核心开始——信任从最安全的接口开始。
LangChain 的核心抽象层有 67,700 行代码(S2E04 数据),但它最活跃的外部贡献不在核心——在 integrations/ 目录下的第三方工具适配器。LobeHub 有超过 400 个社区贡献的 AI 模型 provider(截至 2026 年 5 月官方数据),但核心的 agent runtime 改动几乎全来自内部团队。
模式是一致的:外部信任从叶子开始,核心信任需要更深的理解才能获得。
不是所有叶子都是好的贡献面
一个反面例子。
OPC 的 pipeline/ 目录下有协议文件——gate-protocol.md、loop-protocol.md、report-format.md。这些也是 markdown 文件,也有固定结构。但它们不是好的贡献面:
看得懂? 部分。你能读懂每个段落,但不知道修改一个阈值会怎样影响门禁判定。
改得动? 是。改 markdown 不需要工具链。
改坏了不怕? 不。改 gate-protocol.md 里的 severity 映射规则,所有门禁判定都会受影响。最大降级幅度等同于核心代码。
同样是 markdown 文件,roles/*.md 是好的贡献面,pipeline/*.md 不是。文件格式不决定贡献门槛——降级幅度决定贡献门槛。
设计启示:如何创造低风险贡献面
从角色系统的经验里可以提炼几条可操作的原则:
1. 让格式自解释。 四段式命名不需要注释——Identity、Expertise、When to Include、Anti-Patterns 本身就是说明。如果你的接口需要一页文档来解释怎么用,它不够自解释。
2. 让已有实例当模板。 不要写 spec,写一个好的示例文件。贡献者会复制粘贴并修改,不会从零开始读 spec。OPC 有 roles/frontend.md 当模板,#24 的 Performance 角色就是从它复制出来的。
3. 隔离降级幅度。 一个角色文件出错不会影响其他角色、不会影响门禁、不会影响流水线路由。这种隔离不是偶然的——每个角色是一个独立的 markdown 文件,不是一个大配置文件里的一个段落。如果所有角色定义在一个 roles.yaml 里,改一个角色的格式错误可能导致整个解析失败。一个文件一个角色,是隔离降级幅度的最简单方法。
4. 让恢复成本可见。 当贡献者知道”最坏的情况就是删掉我加的这个文件”,心理负担是最低的。CONTRIBUTING.md 可以明确说:改角色文件的风险是什么、改核心代码的风险是什么。让贡献者自己选择风险等级。
下一集从反面看同一个问题:为什么没有人碰核心?不只是因为核心的降级幅度高——还因为核心的”可改”邀请从未被发出过。
硅基团队 S3: 从”我能用”到”别人也能用” ← S3E02: 他们敢加角色,但不敢碰核心 | S3E04: 没人碰 gate,说明核心还不是公共资产 →