第 1 章 · 02 "薄 harness"哲学与顶层布局


文档摘要

第 1 章 · 02 "薄 harness"哲学与顶层布局 本节摘要:本节把上一节的六条原则落到代码与目录。"薄 harness"(thin harness)的精确定义是——核心只懂 interface,具体模型和工具靠注册表按名字解析、在 config 中声明、由插件注入,核心不持有任何具体类型的硬编码分支。随后巡视 Reasonix 的顶层布局( 入口与 blank-import 自注册、 40+ 内核包、 第三方 sidecar、 Wails+React、 Cloudflare Workers),理解一个二进制如何装下这么多能力。最后与 Claude Code 做一次定位对比,并预告贯穿后五章的主线——"三前端共用单 Controller"。

第 1 章 · 02 "薄 harness"哲学与顶层布局

本节摘要:本节把上一节的六条原则落到代码与目录。"薄 harness"(thin harness)的精确定义是——核心只懂 interface,具体模型和工具靠注册表按名字解析、在 config 中声明、由插件注入,核心不持有任何具体类型的硬编码分支。随后巡视 Reasonix 的顶层布局(cmd/ 入口与 blank-import 自注册、internal/ 40+ 内核包、sdk/go 第三方 sidecar、desktop Wails+React、workers Cloudflare Workers),理解一个二进制如何装下这么多能力。最后与 Claude Code 做一次定位对比,并预告贯穿后五章的主线——"三前端共用单 Controller"。

内容来源:原项目源码 docs/SPEC.md §2 Layout、cmd/reasonix/main.goREASONIX.md(Conventions)、internal/ 目录树。

⚠️ 注意:"薄 harness"不是"功能少"。Reasonix 内核 Go 代码约 16.5 万行、40+ 包,桌面 React 约 12.6 万行。"薄"指的是核心抽象的薄——核心不认具体模型/工具,只认 interface;具体能力靠注册表按名注入。

学习目标

阅读完本节,你应当能够:

  1. 用一句话精确解释"薄 harness",并指出它的反例(硬编码 switch model)。
  2. 读懂 cmd/reasonix/main.go 中四行 blank-import 的作用。
  3. 列举 internal/ 至少 8 个核心包及其职责。
  4. 说清 sdk/godesktopworkers 三者与内核的关系。
  5. 复述 REASONIX.md "One transport-agnostic control.Controller" 这条约定。
  6. 对比 Reasonix 与 Claude Code 的定位差异。

一、"薄 harness"的精确定义

上一节讲了六条原则,本节把"配置与插件驱动核心"翻译成可操作的代码定义。"薄 harness"指的是:

  • 核心代码只 import interface 定义所在的包,不 import 任何具体实现。
  • 具体实现通过注册表按名字解析(provider.New(kind, cfg)tool.Registry.Get(name))。
  • 名字来自 config(如 [[providers]] kind = "openai"),实现来自 init() 自注册或运行时插件。
  • 核心任何路径都不存在 switch modelName { case "deepseek": ... } 这样的分支。

反例一眼能认出来:如果某天你在 internal/agent/ 里看到 if provider == "deepseek" { ... },那就是破坏了"薄 harness"——这个分支应该被一次 config 编辑或一个 provider 实现替换掉。

💡 契约要点:SPEC §1.1 原文"No hardcoded switch model"不是修辞,而是 code review 的检查项。"薄"是核心抽象的薄,不是代码量的薄。

二、顶层布局总览

SPEC §2 给出顶层目录(docs/SPEC.md:28-52),精简后如下:

reasonix/ ├── go.mod / go.sum # module reasonix; require BurntSushi/toml ├── Makefile # build / cross / vet / fmt / test ├── reasonix.example.toml # 配置示例(260 行,第 2 章精读) ├── docs/SPEC.md # 工程契约 ├── cmd/ │ ├── reasonix/ # 入口;blank-import 内置 provider/tool │ ├── reasonix-plugin-example/ # MCP stdio 插件参考实现 │ └── ...(launcher/migrator/signpath/e2ebench) ├── internal/ # 内核(40+ 包) ├── sdk/go/ # 第三方 Go 编写 sidecar 的 SDK ├── desktop/ # Wails + React 桌面应用 └── workers/ # Cloudflare Workers(accounts/forum/crash-report)

下面挑三个最关键的目录展开。

三、cmd/reasonix 入口:blank-import 自注册

入口文件 cmd/reasonix/main.go 极简,核心只有十几行。最关键的是这四行 blank-import(cmd/reasonix/main.go:10-17):

// Blank imports wire compile-time built-ins into their registries. _ "reasonix/internal/provider/anthropic" _ "reasonix/internal/provider/openai" _ "reasonix/internal/provider/responses" _ "reasonix/internal/tool/builtin"

下划线 _ 表示"只为副作用而 import"。副作用就是这些包的 init() 函数会执行,把各自的 provider/tool 注册到全局注册表:

  • internal/provider/openaiinit() 调用 provider.Register("openai", New)(internal/provider/openai/openai.go:57-58)。
  • internal/provider/anthropic 注册 "anthropic" kind。
  • internal/provider/responses 注册 "responses""dashscope-responses" 两个 kind。
  • internal/tool/builtin 整个包的多个 init()(每个工具文件一个)注册全部内置工具。

这是 Go 实现"插件自注册"的经典手法。核心(internal/agentinternal/cli)从不 import 这些子包,只 import internal/providerinternal/tool 这两个父包(里面只有 interface 和注册表)。依赖方向因此是单向的:子包 import 父包自注册,父包永不 import 子包。第 2 章第 1 节会专门讲这条"依赖方向无环"约束。

cmd/reasonix-plugin-example 是一个可运行的 MCP stdio 插件参考实现(实现了 echowordcount 两个工具),给第三方写插件做样板。

四、internal/ 内核:40+ 包各司其职

internal/ 是 Reasonix 的内核,约 40+ 包。下表按职责分组列出最核心的包(完整目录在源码 internal/ 下):

职责 章节
cli 子命令路由、flag、装配、退出码 第 2、8 章
config TOML 加载(四级覆盖) 第 2 章
provider Provider interface + 类型 + kind→factory 注册表 第 3 章
tool Tool interface + Registry 第 4 章
permission 每次调用的 Policy(allow/ask/deny→Decision) 第 5 章
plugin stdio JSON-RPC(MCP)客户端;适配远端工具 第 7 章
extension Extension Protocol v1 sidecar 第 7 章
agent Session + harness loop + fleet/coordinator 第 5、9 章
control 传输无关 Controller(三前端共享) 第 8 章
boot 系统提示装配(缓存稳定前缀) 第 6 章
serve HTTP/SSE 前端 第 8、10 章
bot IM 机器人(飞书/QQ/微信) 第 10 章
remote SSH Remote-SSH(forward/sftpfs/bootstrap) 第 10 章
sandbox OS 级沙箱(Seatbelt/bubblewrap) 第 5、9 章
memory / history 长期记忆 / 会话历史检索 第 6 章
skill 可调用 playbook(Skill 文件) 第 9 章

注意 internal/ 还有一批"工具型"小包:shellparse/shellrun/shellsafe(shell 解析与安全)、frontmatter(Skill/命令前页解析)、diff(文件变更)、i18n(双语)、gitcmdlsp(语言服务器)等。这些小包体现了原则 6"演进不过度工程"——每个包只做一件事,长出来才独立成包。

💡 契约要点:internal/ 每个包都拥有一个 package comment 说明自己负责的 concern(见 REASONIX.md Conventions 第一条)。编辑代码时"匹配周围注释密度与习惯"是项目明文约定。

五、sdk/go、desktop、workers

sdk/go:第三方用 Go 编写 Extension sidecar 时使用的 SDK。它把 Extension Protocol v1 的 JSON 通信、事件拦截、Provider 注入封装成易用的 API。第 7 章详讲。

desktop:Wails(Go 后端 + Web 前端打包成桌面应用)+ React 19 的桌面客户端。后端 Go 约 11.1 万行,前端 React 约 12.6 万行,提供 8 套主题(graphite/aurora/slate/carbon/nocturne/amber 等)。桌面与 CLI/HTTP 共用同一个 internal/control.Controller,只是传输层不同。第 8、10 章详讲。

workers:Cloudflare Workers 部署的边缘服务,目前含 accounts(账户)、forum(社区论坛)、crash-report(崩溃上报)三个 worker。与内核解耦,独立部署。第 10 章发布部分提及。

三者都不在 CGO_ENABLED=0 单二进制的主路径上:sdk/go 是给别人的库,desktop 是另一个构建产物(Wails 打包),workers 是边缘 JS/TS。主二进制 cmd/reasonix 只包含 internal/

六、三前端共用单 Controller:贯穿后五章的主线

REASONIX.md 的 Conventions 第二条(REASONIX.md:12-14)写下了一条全局约定:

One transport-agnostic control.Controller sits behind every frontend (chat TUI, HTTP/SSE serve, Wails desktop). Add behavior to the controller, not a frontend, so all three inherit it.

翻译:一个传输无关control.Controller 位于所有前端之后(聊天 TUI、HTTP/SSE serve、Wails 桌面)。把行为加到 controller,而不是某个前端,这样三者自动继承。

这条约定是第 8 章的主题,本节先记住它的含义:

  • internal/control.Controller 不依赖任何具体传输(TTY、HTTP、WebSocket、IPC)。
  • 三个前端——CLI 的 Bubble Tea TUI、reasonix serve 的 HTTP/SSE、Wails 桌面——都把用户输入转给 Controller,Controller 把模型输出与工具事件转给前端。
  • 新功能加在 Controller,三个前端自动获得;反之加在某个前端的功能,另两个前端拿不到。

这条约定让 Reasonix 避免了"三个前端各写一遍逻辑"的常见陷阱,也是它能把这么多能力塞进一个项目的架构基石。

⚠️ 注意:control.Controller 是"传输无关",不是"线程安全无关"。多前端并发访问 Controller 时,前端层需自行处理并发;Controller 内部用 sync 保护自己的状态。

七、与 Claude Code 的定位对比

Reasonix 与 Claude Code 都是终端 AI coding agent,定位相近但有明显侧重:

维度 Claude Code Reasonix
主模型 Anthropic Claude DeepSeek(首选),任何 OpenAI 兼容端点
协议 闭源 MIT 开源
缓存优化 Anthropic prompt cache 围绕 DeepSeek prefix cache 调优(全书高潮★)
插件协议 MCP MCP + Extension Protocol sidecar(两层)
桌面 无(纯 CLI) Wails + React 桌面客户端
IM 集成 飞书/QQ/微信 bot
社区 Anthropic 官方 中国开发者社区(esengine 等)

最本质的差异在第二行——Claude Code 闭源,Reasonix 开源,且把"prefix cache 友好的上下文维护"作为头号工程目标。这是第 6 章的主题,也是 Reasonix 区别于其他 coding agent 的灵魂。

另一个差异是 Reasonix 的"三前端共用单 Controller"——Claude Code 只有 CLI,没有多前端共享的问题;Reasonix 因为同时支持 CLI/HTTP/桌面/IM,被迫长出了 control.Controller 这层抽象,这是开源 + 多前端的副产品。

本节要点回顾

  1. 薄 harness 定义:核心只懂 interface,具体模型/工具靠注册表按名解析、config 声明、插件注入,无硬编码 switch model
  2. 顶层布局:cmd/reasonix(入口 + 4 行 blank-import 自注册)、internal/(40+ 内核包)、sdk/go(sidecar SDK)、desktop(Wails+React)、workers(CF Workers)。
  3. blank-import 自注册:_ "reasonix/internal/tool/builtin" 触发各 init() 调用 Register,父包永不 import 子包。
  4. internal 包职责:cli/config/provider/tool/permission/plugin/extension/agent/control/boot/serve/bot/remote/sandbox/memory 等。
  5. 三前端共用单 Controller:internal/control.Controller 传输无关,新功能加在 Controller 三个前端自动继承。
  6. vs Claude Code:Reasonix 开源、多前端、围绕 DeepSeek prefix cache 调优;Claude Code 闭源、纯 CLI。

下一章我们正式进入配置体系——SPEC §2 的 Layout 与依赖方向无环是第一条硬约束,config 四级覆盖是第一条软约定。第 2 章共 2 节,先讲 Layout 与依赖方向,再精读 reasonix.example.toml 260 行。


作者与出处
原作者: 灏天文库
整理: 灏天文库整理
本站整理收录,版权归原作者/开源协议所有;欢迎通过原文链接访问源仓库。
发布者: 作者: 灏天文库 转发
评论区 (0)
U