OpenHarness源码研究-6-架构全景与设计模式总结
type
Post
status
Published
date
Jun 30, 2026
slug
250630-openhasness-6
summary
把前5篇的东西串起来,看整个项目靠什么设计模式撑起来的
tags
开发
category
技术分享
icon
password
前言
把前5篇的东西串起来,看整个项目靠什么设计模式撑起来的
完整数据流
一次
oh -p "帮我改个bug" 从头到尾经过的路径:这是一个典型的管道架构——数据单向流动,每层有自己的职责,层与层之间通过定义好的接口通信。
六个核心设计模式
Protocol-策略模式
第3篇分析过的。
SupportsStreamingMessages 是个 Protocol(结构化子类型),四种 Client 谁也不继承谁,但都能被 QueryEngine 使用。对比传统的 ABC + Factory 方案:ABC 要求显式继承,Factory 要求显式注册。Protocol 把这两步都省了。
适用场景:你有多种后端实现、它们之间没有共享代码、你不想引入依赖框架。
声明式注册表
api/registry.py 的 PROVIDERS 元组是声明式的极致——42个 Provider,每个是一个 dataclass 实例,没有任何函数调用。检测逻辑是独立的三层扫描(key前缀 → URL关键字 → 模型名),不依赖注册顺序。同样的模式出现在 Hook(
hooks/schemas.py 的 HookDefinition)、Skill(声明式 markdown)、Plugin(manifest 文件)。适用场景:你需要管理大量同类配置项、配置需要被不同子系统按不同维度查询。
分层覆盖
Settings 的四层覆盖:cli arg → env var → settings.json → default。
这不是什么新奇模式,但实现上的细节值得注意——
merge_cli_overrides() 只覆盖非 None 的值,意味着用户可以不传大部分参数,只传需要覆盖的那一个。适用场景:你的应用有多个配置来源,需要明确的优先级。
决策链
PermissionChecker 的 evaluate() 是一个顺序决策链。每个环节只检查一件事,拦截了就返回,不拦截就往下走。
这种写法比一个巨大的 if-elif-else 函数清晰得多。新增一个检查条件只需要在链中插入一个新步骤,不修改现有逻辑。
适用场景:你需要做多重条件判断,每层条件来源不同、优先级明确。
事件总线
引擎产生的 6 种 StreamEvent 是狭义的事件总线。更广义的——Hook 系统也是事件驱动(4 个 HookEvent → 3 种 Handler)。
两个事件系统的共同点:生产者不关心消费者是谁,消费者不关心事件是谁产生的。这让 UI 层可以从 Textual TUI 换成 React TUI(
backend_only 模式),引擎层一行代码都不需要改。适用场景:你需要解耦数据生产和消费、或者同一个数据流有多个消费方。
原子文件写入
Mailbox(
swarm/mailbox.py)和记忆系统都用了同样的模式:os.replace 在 POSIX 系统上是原子的——读取方要么看到旧文件,要么看到新文件,绝不会看到写了一半的残缺文件。Mailbox 还加了一层文件锁(
exclusive_file_lock),防止并发写入冲突。这在多 Agent 协作场景下是必须的。适用场景:多进程/多 Agent 并发读写同一文件系统,需要保证数据一致性。
工程启发
减少依赖,用标准库
OpenHarness 没有引入 langchain、没有用任何 LLM 抽象框架。它的 API 层全部是直接封装原生 SDK 或裸 HTTP。代价是 4 个 Client 类加起来约 1300 行代码,收益是不会被第三方框架的版本升级绑架,也不会有"这个框架不支持某 API 的某个特性"的困境。
对于个人项目而言,引入一个依赖的决策门槛应该很高。尤其是当依赖做的事情你可以用 200 行代码自己写完的时候。
数据类优于字典
整个项目大量使用 dataclass 和 Pydantic BaseModel 做数据传递。从
ApiMessageRequest 到 TeammateSpawnConfig,没有看到裸 dict 在模块间传递。dict 的问题是:没有类型提示,没有字段校验,拼错了 key 要到运行时才发现。dataclass 的成本几乎为零,但能让 IDE 帮你检查字段名。
不可变数据
ApiMessageRequest、StreamEvent、ResolvedAuth 全部是 frozen=True。不可变数据消除了数据在传递过程中被意外修改的可能性。这在异步代码中尤其重要——你不会想知道一个协程在 await 期间,另一个协程改了你手上的对象。延迟导入
cli.py 的函数体内部到处是
from openharness.xxx import ...。这是 CLI 工具的常见优化——oh --help 不需要加载 anthropic SDK 和所有工具实现。启动时间从可能的好几秒降到毫秒级。错误分类
API Client 层的错误不是一把抓的
Exception,而是分类为 AuthenticationFailure、RateLimitFailure、RequestFailure。重试逻辑据此决定是否重试——认证错误不重试(没用),限流错误要等一等再重试,网络错误可以立即重试。这个思路可以用在任何需要调外部 API 的项目里。哪怕你只分了"可重试"和"不可重试"两类,也比统一重试所有错误要好。
总结
- 数据流是单向管道:CLI → Runtime → Engine → API Client → LLM → Tool → 循环
- 六个核心模式中,Protocol 策略模式和决策链是最有实用价值的两个
- 减少依赖、用 dataclass 代替 dict、不可变数据、延迟导入、错误分类——这些不是创新,但组合在一起构成了工程上的可靠性
- 197 个文件但耦合度低的本质原因是:层间接口清晰(Protocol + 数据类),模块内部高内聚,模块间通过事件解耦
写到最后
Prev
将进酒
Next
OpenHarness源码研究-5-基础设施-配置/认证/权限/扩展
Loading...

