Spool:管理持久化 Claude Code 循环的开源工具
How I manage persistent Claude Code loops: Spool
Spool 是一个开源工具,用于创建和管理持久化的 Claude Code 循环,让多个循环协同工作并通过网页控制台、Telegram 或 Slack 与操作者保持对话。
你的 Claude Code 循环协同工作,让你始终参与对话。
Spool 让创建、管理持久化的 Claude Code 循环并与之对话变得简单。给它们分配工作,让它们相互协调,并通过聊天保持参与。
spool-demo.mp4
Spool 让什么变得更简单?
- 让工作持续推进。 在一个地方创建和管理持久化循环,支持定时唤醒和可恢复的会话。
- 保持参与。 在 Web 控制室、Telegram 或 Slack 中与你的循环对话,无需坐在它们的终端会话里。
- 协调工作。 让循环在 fleet 频道中直接通信,这样你就不必转达每一个问题、结果或交接。
减少在会话之间切换和在循环之间传递消息的时间。把更多时间用于决定它们该做什么,以及审查它们的产出。
快速开始
从克隆到在 Telegram 上回复你的循环。每一步只需一条命令或一个操作。
- 准备好前置条件。 Linux 或 macOS,已安装并登录 Claude Code(
claude在 PATH 中),Docker 正在运行,以及用于构建的 Go 1.25+ 和 Node 20.19+ 或 22.12+。 - 构建二进制文件:
make build,它会构建 Web UI 和单个bin/spool二进制文件。 - 构建镜像:
make image,它会构建循环运行所在的工作站及其出口代理。Spool 从你的本地 Docker 运行它们;它从不拉取它们。 - 启动 hub:
./bin/spool。控制室位于 127.0.0.1:8080,数据存放在~/.spool。循环监听器位于你的工作站可以访问的地址,hub 日志会记录该地址;参见 What contains a loop。 - 登录控制室: 打开 http://127.0.0.1:8080,使用 hub 首次启动时打印的一次性密码以
admin身份登录,然后设置你自己的密码。错过了?./bin/spool user reset admin会打印一个新的。 - 将你的 Claude 登录信息提供给工作站: 运行
claude setup-token,然后在 Settings → Claude token 下粘贴令牌。工作站无法使用你机器上的~/.claude,因此循环就是这样在你的套餐上运行的。 - 创建一个循环: New loop,然后填写名称、任务和 tick 间隔。将工作区留空;它仅用于裸循环。
- 关联 Telegram: 在 Telegram 中,向 @BotFather 发送
/newbot。在循环页面的 Surfaces 下,选择 Attach Telegram 并粘贴令牌。 - 允许你自己: 私信该机器人。它会回复一个配对码,你会在 Access 页面显示为 pending。将该码输入你的行并点击 Allow,然后在循环页面上选择你自己作为 owner。
- 打个招呼: 再次私信该机器人。你的消息会唤醒循环,它会在该私信中回复。
没有 Docker? 改用 ./bin/spool --runtime bare 开始,并跳过第 3 步和第 6 步。此时循环会作为你账户下的主机子进程运行,不受容器隔离。在选择之前请阅读 What contains a loop。
要将循环引入 Telegram 群组,以及了解谁可以在那里访问它们,请参见 Telegram。
工作原理
- 底层是普通的
claudeCLI。 循环是通过 stdin/stdout 使用 stream-json 通信的claude子进程——使用你正常的 Claude Code 登录、套餐和会话令牌限制。无需 API 密钥,无需 SDK。 - 直接协调。循环向 fleet 频道或私密对话发送显式寻址的消息。
@mention来自 Telegram、Web 或另一个循环的消息,Spool 将其投递。最终轮次的回复作为状态备注留在时间线中。循环到循环的链式调用有风暴防护。 - 唤醒/休眠引擎。空闲循环不产生任何开销:其进程退出,之后通过
--resume带着完整上下文恢复。循环可以用[next-wake: 45m]尾部标记自行调节节奏,并被限制在你设定的范围内。 - 默认隔离。每个循环都在自己的容器中运行,带有出口允许列表,除了一个 hub 端口外没有通往你机器的路由。没有 Docker 时,Spool 会拒绝启动,而不是悄悄在你的主机上运行循环——非隔离是你主动要求的,当你这样做时它会明确告知。参见 什么隔离了一个循环。
- 工作树隔离。将多个循环指向同一个仓库,每个循环都会在
loop/<name>上获得自己的 git 工作树——它们不会互相覆盖。 - 控制室。实时对话时间线(包括 token 流式传输)、调度、每轮/每天的成本、暂停/唤醒/终止。
- 计划护栏。Fleet 页面显示你的计划中 5 小时和 7 天窗口已使用的比例(从循环自身的流中读取),以及每个循环在当日支出中的份额。超过上限后(默认任一窗口的 90%),所有循环都会休眠直到该窗口重置:消息会等待,什么都不会丢失。立即恢复可提前解除限制,设置 → 计划护栏可设置阈值或将其关闭(ADR-0047)。
Spool 处于 1.0 之前的阶段,由一个 Claude Code 循环舰队及其操作员公开开发——本仓库 issue 和 PR 中的四个循环正运行在 Spool 上,构建 Spool。提交由编写它们的模型共同署名;每项变更都通过 CI 把关、审阅者阅读的拉取请求落地,由操作员负责合并。发现问题?CONTRIBUTING.md 说明了如何报告以及由谁回应。
什么隔离了一个循环
Spool 用 --permission-mode bypassPermissions 运行每个循环:循环
从不被要求确认它所做的任何事情。隔离它的是它的 工作站
—— 它运行所在的运行时 —— 而不是你指向的工作区目录。
工作区是一个工作目录;它什么都阻止不了。
当 Docker 可访问时,循环会获得自己的容器,位于内部
网络上,除了循环所需主机的允许列表(spool-egress-proxy)以及
提供 MCP 端点的单个 hub 端口外,没有离开该网络的路由。
你在 设置 → 出口 下向该允许列表添加主机。更改会在几秒内
到达每个 docker 循环,无需重启任何东西。
你附加到循环的 http MCP 服务器通过 hub 访问,hub 会添加
其凭据,因此循环调用服务器时从不持有其密钥。
它无法访问你的控制室 API、你的文件或你网络的其余部分。
没有可访问的 Docker 守护进程时,--runtime auto 拒绝启动,并
告诉你两条前进之路:安装或启动 Docker,或者用 --runtime bare 有意
要求主机子进程。第二种是真正的 Claude
Code,权限被绕过,在你自己的用户账户下运行,使用你的
网络和你的文件,在控制室中标记为 非隔离。这是
一个合理的选择,却是一件糟糕的馈赠,这就是为什么没有任何东西
替你选择它——守护进程宕机不构成同意。
要让 Docker 保持为默认选项,同时仍允许在受控循环之外刻意运行一个裸循环,请从 --allow-bare 开始;没有它,控制室会拒绝创建。裸循环之后可以移入它自己的容器:Move into a docker container,位于其页面 Workstation 下,会保留其名称、机器人、频道、任务、日程、连接和历史记录,并根据交接说明在那里启动一个新会话。无法将其移回。
有两件事是隔离做不到的。它无法阻止循环滥用你赋予它的凭据——循环环境中的令牌就是该循环拥有的令牌。而工作站能访问的 hub 端口仍然是你机器上的端口;保护它的是其上的每循环凭据,而不是网络。
循环可以访问 Spool 自己的 MCP 服务器,该服务器携带它发送消息所需的唯一工具,以及你作为连接附加到它的任何 MCP 服务器。你 Claude 账户上的 MCP 服务器和连接器永远不会传递给循环(--strict-mcp-config)。
Spool 提供两个监听器。--listen 是你的:控制室及其 API。--mcp-listen 属于循环:它是容器化工作站被允许访问的唯一端口,仅提供 MCP 端点,别无其他。请将它们分开。能访问 API 端口的工作站可以读取所有对话并创建一个未受控的循环。
如果没有 --mcp-listen,hub 会选择循环监听器的位置,在 --mcp-port(8081)上。在 Linux 上使用 Docker 工作站时,它会绑定 Docker 网桥的地址(通常是 172.17.0.1)。你的工作站可以访问该地址。你的网络默认不会路由到它:同一链路上的主机只有通过将网桥子网路由到你的机器才能访问它,而主机防火墙可以丢弃该流量。使用 Docker Desktop 时,其引擎在 VM 中运行并将工作站转发到你机器的回环地址,或者仅使用裸循环时,它会绑定 127.0.0.1。hub 会记录它选择了哪个以及原因(ADR-0039)。显式的 --mcp-listen 始终优先。--listen 保持在 localhost 上。
首次启动时,Spool 创建用户 admin 并一次性打印其一次性密码。控制室使用用户名和密码登录,要求用新密码替换一次性密码,然后持有会话 cookie。spool user 添加、重置、列出和删除用户(ADR-0048)。首次启动还会打印一个 operator token 并将其存储在数据目录中:它用于 CLI 和脚本,作为 bearer 头发送,spool token 会再次打印它。除 health 和 version 外的每个 /api 路由都需要会话或令牌。绑定到 localhost 不是边界,因为任何其他本地进程以及你浏览器中的任何页面都可以访问该端口(ADR-0030)。
Spool 还会生成一个 hub key,位于数据目录中的 hub.key,并将其存储的每个机密都密封在其下:连接机密、机器人令牌、setup-token。仅 spool.db 的副本不包含其中任何一个。将 hub.key 与数据库分开备份:同时包含两者的备份与机密一样敏感,而没有密钥,机密就会丢失。如果密钥丢失,没有它就无法恢复机密,并且 hub 不会启动,直到你恢复它,或者用 --forget-secrets 启动一次,这会撤销连接、解除机器人绑定并清除 setup-token,以便你可以重新输入它们(ADR-0046)。docs/OPERATIONS.md 有一个备份和恢复方法,将两者分开,并涵盖工作站卷。
Telegram
Telegram 是可选的。一个 loop 启动时没有 surface,只在控制室里和你对话;当你想从 Telegram 也能联系它时,再挂接一个 bot。每个 loop 都有它自己的 bot 身份(Telegram bot 看不到其他 bot 的消息,所以 loop 到 loop 的投递始终发生在 Spool 内部——Telegram 是人类的界面和镜像):
- 在 Telegram 中,与 @BotFather 对话:
/newbot→ 用你的 loop 给它命名。 - 仍在 BotFather 中:
/setprivacy→ Disable(这样 bot 才能看到群消息)。 - 在控制台中该 loop 的页面上,Surfaces 下选择 Attach Telegram 并粘贴 token。
- 把 bot 加入你的群。第一条群消息会完成绑定。
然后,从群里:@<botname> status? 到达 loop;loop 以自身身份发送一条群消息来回复。私聊 bot 可私下对话——loop 的私密回复会回到该私聊中,绝不会出现在群里。loop 到 loop 的群消息(必须 @提及收件人)在内部投递,并由发送方的 bot 发布到群里,所以你可以看着你的机群对话。群里的 /spool_status 会让 bot 报告其 loop 的状态。
表情回应也会传递。对某个 loop 的消息做出回应,该 loop 会在下一轮得知;只有你在它的私聊中的回应才会唤醒它。loop 也可以不回复而做出回应,其 bot 会设置 emoji。Telegram 的限制适用:在群中,bot 只有作为管理员才能听到回应,所以如果你想要这些,就把它设为管理员;并且 bot 每条消息只能从 Telegram 自带集合中设置一个回应,因此 loop 在那里的第二个回应会替换掉第一个。loop 对另一个 loop 消息的回应只在超级群中显示在 Telegram 上:在普通群里,每个 bot 为自己的消息副本编号,永远看不到另一个 bot 的,所以它没有可回应的对象。无论如何,该回应仍会到达 hub 和另一个 loop。当群聊历史对新成员可见时,Telegram 就会把群变成超级群,Spool 会跟随它到新的聊天。
谁可以与你的 loop 对话
Telegram bot 是公开可达的,所以 Spool 维护一份发送者允许列表。任何给你的 bot 发消息但不在列表上的人都会被忽略:他们的消息永远不会到达 loop,他们无法绑定群,/spool_status 也不会回复他们。在私聊中,他们会收到一次性配对码;随后该发送者会作为待处理出现在控制台的 Access 页面上,你在那里输入他们提供的配对码并点击 Allow(或 Block——被阻止的发送者完全不会收到回复)。loop 到 loop 的流量是内部的,不受影响。
Slack
Slack 是像 Telegram 一样的 surface:一个 loop 在 Slack 中拥有其所有者私聊和频道。每个 loop 都是自己的 Slack 应用,通过 Socket Mode 连接,因此 hub 不需要公共 URL:
-
在控制台中该 loop 的页面上,Surfaces 下选择 Attach Slack。
-
按照 Create app from manifest 操作:Slack 的创建页面会打开,并已填入该 loop 的 manifest。选择工作区并创建它。(或者复制 manifest 并粘贴到 Create New App → From a manifest。)
-
将应用安装到工作区。bot token,
xoxb-…,位于 OAuth & Permissions 下。 -
在 Basic Information → App-Level Tokens 下,生成一个应用级 token,
xapp-…,带有connections:write权限范围。manifest 无法创建这个。 -
把两个 token 都粘贴到循环的 Attach Slack 步骤中。应用会连接,循环的 Surfaces 面板会显示其 socket 是否已启动。
Reactions 在 Slack 上的传递方式与在 Telegram 上相同,循环的应用会保留它添加的每个 reaction。从旧 manifest 创建的应用没有 reaction 权限范围。在 OAuth & Permissions 下添加 reactions:read 和 reactions:write,并在 Event Subscriptions 下添加 reaction_added 和 reaction_removed bot 事件,然后重新安装应用。在此之前,reactions 会留在 hub 上。
循环在 Slack 上的 poll 是其应用发布的一篇帖子,每个选项对应一个按钮,点击其中一个即为投票(ADR-0041)。从旧 manifest 创建的应用关闭了交互功能:在 Interactivity & Shortcuts 下将其打开。Socket Mode 负责传递点击,因此不需要 request URL。在此之前,其 poll 会显示,但点击它们不会有任何效果。
节奏如何运作
每个循环都有一个 tick 间隔(默认 30m)。每完成一个 turn 后,Spool 会安排下一次唤醒:如果循环的 [next-wake: …] trailer 存在,则以它为准(限制在 min_wake/max_wake 范围内),否则使用该间隔。任何入站消息都会立即唤醒循环并重置计时。暂停的循环会将其邮件排队。
Flags
spool --listen 127.0.0.1:8080 --mcp-port 8081 --data-dir ~/.spool --claude-bin claude --partial-messages
spool --mcp-listen 172.17.0.1:8081 # name the loop listener's address yourself
spool token --data-dir ~/.spool # print the operator token again
# behind a TLS proxy (docs/OPERATIONS.md has a systemd unit and a Caddy
# recipe), name the host it is reached as — otherwise the Host and
# Origin checks refuse every request that posture produces
spool --listen 127.0.0.1:8080 --trusted-host spool.example.com
Development
make server # Go binary only (uses last-built UI) make dev # run backend on :8080 make ui-dev # vite dev server with /api proxy make test # tier 1: unit + architecture tests make itest # tier 2: real binary vs fakeclaude (protocol fake), ~10min # (~7min without a docker daemon — two files skip) make image # build the spool-workstation and spool-egress images locally make lint # gofmt + vet (as Linux and as macOS) + golangci-lint make e2e-m1 # tier 3: real claude sessions (spends plan tokens)
测试分为三层(见 docs/QUALITY.md):单元测试和由 fakeclaude 支持的集成测试在 CI 中把关每个 PR;真实 claude 的 e2e 套件(scripts/e2e/)按里程碑在本地运行——跨进程死亡和 orchestrator 重启的会话恢复(m1)、tick 调度和 trailer 限制(m2)、mention 中继 + 风暴防护(m4)、Telegram(m5)、worktree 隔离(m6)。
Project docs
docs/VISION.md— 产品愿景和 L0–L7 里程碑阶梯docs/ARCHITECTURE.md— 形态、接缝、通用语言docs/CONVENTIONS.md— 工作流、风格、测试层级docs/QUALITY.md— 基线和 CI 关卡docs/adr/— 决策记录AGENTS.md— 面向在此仓库上工作的 AI agent 的入门指南(CLAUDE.md是指向它的符号链接)SECURITY.md— 支持哪些内容、如何私下报告漏洞,以及设计中的哪些特性是已知的而非 bug
来源:Hacker News · github.com