Spool 是怎么做出来的

一份关于本地优先项目背景中枢的产品与工程记录:它写给谁用,什么东西会越过网络边界,AI 可以写入什么,以及哪些地方是在系统遇上真实工作之后才改掉的。

写给谁用

Spool 写给同时推进好几件长期事情、而手上的工具彼此之间没有记忆的人:研究者、研究生、开发者,以及独自做一件东西的人。真正的代价不是“记笔记”,而是每开一场新的 AI 对话,都要把同一个项目重新讲一遍——哪篇论文重要、哪个决定已经失败过、上周之后又变了什么。

这个产品的目标刻意收得很窄:重新进入一个项目,应该只花一次粘贴,而不是回到聊天记录、邮件、标签页和记不太清的决定里做考古。它待在文档工具的上游。项目是一份安静的、只往后追加的记录,不是协作画布,也不用来取代 Notion。

调研边界。第一版是开发者拿自己手上的多个项目边用边改做出来的。这带来了几处有价值的反转,但它不是面向外部用户的调研;和其他用户一起验证,仍然是发布之后的事。

产品做的事

三个动作构成一个闭环。它们都不需要模型出现在关键路径上。

  1. 捕捉 复制之后连按两下 ⌥,把剪贴板连同时间和来源一起存进项目;同时弹出的确认浮窗接住你当时觉得它值得保存的那个想法。
  2. 项目 把碎片按时间顺序留在一条记录里,结构正好两层:工作区,然后是项目。
  3. Pack 把当前项目组装成确定的 Markdown,可以粘贴进任何 AI 客户端。同一个项目在同一天生成的字节完全一样。
隔离的 Spool 演示捕捉场景:浏览器页面停在原处,Spool 的确认浮窗出现在右上角,显示捕捉到的来源和批注框
隔离演示构建里的捕捉:来源应用一直看得见,Spool 在角落里确认那条已保存的片段。

三个界面之间的关系

桌面界面、MCP 服务器和命令行引擎位,是围着同一份事实来源的三个本地界面。下图把“运行在你自己的电脑上”和“属于 Spool 进程”分开画,这样关于网络的说法可以直接看,而不是靠推断。

Spool 的本地架构,以及三条对外的路径 Spool 进程里装着桌面图形界面、捕捉浮窗、Rust 与 Tauri 内核、spool --mcp 的 stdio 服务器,以及一个 SQLite 数据库。外部 MCP 客户端通过 stdio 连进来。内核可以把 Claude Code、Codex CLI 或 Gemini CLI 作为独立子进程启动;打开 API 引擎之后,还会启动随包的 spool-ai 子进程。这三个程序分别通过三条不同的网络路径联系各自的服务商。没有任何一条箭头从 Spool 主进程自身指向网络。 你的电脑 SPOOL · 一个本地程序 桌面图形界面 React · TypeScript 捕捉浮窗 全局手势 · 本地窗口 Rust / Tauri core 捕捉 · IPC · 策略 确定性打包器 子进程边界 没有 HTTP 客户端 SQLite 一个本地文件 唯一的 事实来源 FTS5 spool --mcp stdio MCP 服务器 外部 MCP 客户端 Claude Desktop · Cursor · … ChatGPT 桌面版里的 Codex 与服务商的连接归客户端所有 命令行引擎子进程 Claude Code · Codex CLI Gemini CLI · 没有“跟进” 用户自己安装 · 用户自己登录 API 引擎子进程 spool-ai · bundled binary 默认关闭 · 用你自己的 key 整个 app 里唯一开 socket 的 MCP 客户端的服务商 网络路径 1 · 在 Spool 之外 命令行 AI 的服务商 网络路径 2 · 在 Spool 之外 你自己填的那个端点 网络路径 3 · 用你自己的 key IPC 本机 SQL stdio · MCP 启动 + stdio 启动 + stdio 网络路径 1 网络路径 2 网络路径 3 Spool 主进程自己不发出任何 HTTP 请求。 网络边界 —— 只有那三条高亮路径会穿过它
这条网络边界是字面意义上的,而且它画在进程的边上,不是一句承诺:外部 MCP 客户端、你自己登录过的命令行,以及打开 API 引擎之后随包启动的 spool-ai 子进程,各自去联系一家服务商。Spool 主进程自己不发 HTTP 请求,也不监听任何网络端口——它里面根本没编进 HTTP 客户端。三条路里只有 API 引擎那一条要拿 key,它默认是关的,而且 key 存在系统钥匙串里,不在 Spool 的设置文件里。

图形界面和捕捉浮窗在本机与 Rust/Tauri 内核通信。内核拥有唯一的那个 SQLite 数据库。运行 spool --mcp 会把同一个库通过 stdio 交给 AI 客户端读取;命令行引擎位走的是另一条路——把 Claude Code、Codex CLI 或 Gemini CLI 作为独立进程启动。Gemini 支持“周回顾”和起草跟进目标,但不支持“跟进”。API 引擎是第三条,也是最新的一条:它启动 spool-ai,一个装在包里的小程序,从 stdin 收一个 JSON 请求,发一次 HTTPS,再把一个 JSON 信封写回来。它不接受明文 http://,而 key 只从 stdin 进去——绝不走命令行,那里同机器上任何程序都读得到。三条路保住的是同一个区分:哪些是 Spool 在本机做的事,哪些是另一个进程在联网做的事。

隔离的 Spool 演示库里的“机器学习课程”项目:左侧是固定的工作区与 Spool 侧边栏,中间是带编号的时间线块和一条被当作标题用的批注,右侧是 AI 客户端活动栏
当前的一个项目界面:左边是固定的导航和 Spool 面板,记录里是带编号的块,批注可以当标题用,右边是 AI 客户端的活动。

隐私与写入边界

读取和写入是两种分开的权限。内容只会装在你自己安装并授权过的程序里离开这台机器;每一条路径上,越过网络的那个程序都写明了名字。

路径可能越过边界的内容联网的程序
MCP 客户端客户端从本机 stdio 服务器明确读走的内容那个外部 AI 客户端
命令行引擎动作所选动作需要的那些项目块Claude Code、Codex CLI 或 Gemini CLI;Gemini 不含“跟进”
其他任何情况没有没有
  • AI 可以追加,不能覆盖。机器写的块带着由服务端强制加上的来源标签,无法替换用户自己写的块。
  • AI 不能借用用户的身份。批注的作者会被记下来,所以机器写的批注呈现出来就是机器的说法,而不是你的判断。
  • AI 可以提议更正,由你决定。原文一直看得见、搜得到。把材料标记为作废,是人做的动作。
当前隔离演示项目的一处细节:用户写的第 4 块下面跟着第 5 块,标着 Claude · MCP,并引用了前面那条来源
当前构建里的写入边界:一个署名 Claude · MCP 的块被追加在用户那条来源后面,并引用了它;它没有覆盖它,也没有把自己冒充成用户。

分发、签名与公证

macOS 上,Spool 直接以经 Developer ID 签名、并由 Apple 公证过的磁盘镜像分发。不走 Mac App Store,是因为沙盒和全系统范围的捕捉手势冲突。应用和磁盘镜像是分开检查的:用户下载的是外面那层镜像,所以“公证过的应用装在没公证的镜像里”并不够。Windows 安装包由 CI 产出,首版未签名发布——证书是一笔按年续的开销,而这个平台还没被验证过;在下载页上假装它签过,比 SmartScreen 那一次提示更糟。

发布证据

v0.4.0 的签名与公证回执

应用提交
89ebaceb-f883-4b1c-a6eb-86392769d132 已通过
DMG 提交
f7a15d9a-737d-4132-a54e-578d9f41fd7f 已通过
打了标签的提交
84625db
产物
Spool_0.4.0_aarch64.dmg
SHA-256
933b9a7fb10a25f72cbd922c7c0a1d89fe02ef83b6a3885fba0dc0ec08b7df54
Gatekeeper
accepted · source=Notarized Developer ID · 两个产物都是

来源:Case Study Ledger §1.2,记录于 2026-08-08。

到 GitHub 查看这次发布和它的产物。官网上那个固定的下载地址,指向与带版本号的产物一同发布的固定文件名副本。

实测改变了什么

案例研究台账为每一个对外公开的数字都记下了命令和证据来源。其中两次实测改变了产品的优先级,而不只是描述了做完的系统。

去重是一次性的补救

实测到的那一对重复内容占了 Pack 的 13%。把它标记为作废解决了眼前的体量问题,同时那条更早的块仍留在库里,也仍然搜得到。

对外的写入路径是能被摸索出来的

只用一句大白话的请求,一个外部客户端就建好了项目并存进 11 个块,平均 970 字符。它自己从 2 次出错里恢复过来,没有求助,之后还用 Pack 工具检查了自己的成果。

另有一次真实联网搜索的运行花了大约 $0.45,暴露出来的问题,是拿模拟数据和只读提示词都没能发现的。

改变了这个系统的那些故障

事后复盘里真正有用的单位不是那个补丁,而是留下来的那道防线。

线上数据库被清空了

一个开发构建遇上了更新过的线上库结构,走进了一条无条件重建的分支。恢复时从 SQLite 的空闲页里刨回了 33 个块,但项目标题没了。留下来的改动是:迁移改成出错即停、带名字的迁移注册表加跨语言版本核对、迁移前自动快照、装机验证走隔离构建,以及一条规矩——首次运行的种子数据只能从空库那条路进来。

一条提示词规则在第一次真实运行时就没兑现

跟进结果要求带上来源链接,但 3 条提议里有 2 条没带,因为模型把链接放进了结尾那段话里。提示词改成针对具体字段之后,下一次实测的运行里 5 条提议全带上了链接。现在这条规矩很简单:提示词里写了,不等于它就是行为——要有一次真实运行来证明。

打包后的窗口是白的,而所有自动检查都通过了

一个状态选择器每次调用都新建一个数组,触发了无限渲染。自动化测试里没有任何一条会打开打包后的窗口。现在发布验证包含亲眼看一眼隔离的签名构建;界面是否正常,不再靠“能编译”来推断。

这些是边界,不是承诺

  • 两个平台,深度并不一样。Windows 从 0.5.0 开始支持,跑的是同一个库、同一套捕捉手势——只是用 Raw Input 读键,而不是键盘钩子。没有跟着过去的是 macOS 那部分与焦点处理、浏览器标签页来源相关的实现,所以 Windows 版记录的是来源应用,而不是你当时在读的那个页面。
  • 没有自动更新。换新版本要自己到发布页手动下载。
  • 源码可见,保留所有权利。仓库里没有任何授权他人复用的许可证。
  • 有两项检查仍然由人来做。已授权的捕捉手势位于合成事件的上游,打包后的 webview 也没法靠合成点击来确认;这两项都交给人,而不是宣称已经自动化了。
  • Spool 不是服务器、同步服务、团队工作区,也不是文档编辑器。它保存一份本地的项目记录,并为你早就选好的那些工具准备好背景。

这里没有占位视频。浏览器里的交互演示会走完捕捉 → 项目 → Pack 的整个流程;上面那些截图来自当前的隔离桌面构建。


Spool 由 Ocean(KIM-ocean-HZ)设计并开发。源码、路线图和完整的产品准则在 GitHub;可复现的公开数字在 Case Study Ledger