Paperclip 执行工作区与 git worktree:一个 issue 到底跑在哪份代码上

2026-08-17

让多个 AI Agent 同时改一个代码库,第一个绕不过去的问题不是模型能力,是它们各自在哪份代码上干活。两个 issue 同时开工,如果共用一个 checkout、共用一个分支,那第二个 Agent 看到的就是第一个 Agent 改了一半的文件;如果各自开一份完整 clone,磁盘和依赖安装的开销又会随任务数线性膨胀。

Paperclip 的答案叫 执行工作区(execution workspace),底下用的是 git_worktree。但真正麻烦的不是 worktree 本身,而是围绕它的一圈问题:这个工作区里的开发服务器谁来起、起几次、什么时候停;一次运行改出来的代码怎么传到下一次运行;私有仓库的凭据从哪儿来。官方那份《Execution Workspaces And Runtime Services》指南把这些都写了,这篇就照着它拆。

先说一句读文档的前提。这份指南开头自己写的是「documents the intended runtime model」——它描述的是预期中的运行时模型;文档末尾另起了一节 Current implementation guarantees(当前实现保证),单独列了五条已经落地的行为。这两块不是一回事,看的时候最好分开对待:正文讲的是模型设计,末尾那节才是官方明确背书「现在就是这样」的部分。下文我会在关键处标出来。

先改口径:不是「运行时 JSON」,是 Services 和 Jobs

文档说 Paperclip 现在把这块呈现为 workspace-command 模型,两类东西:

类型含义生命周期
Services长期运行、受监督的命令起来后一直supervised,需要显式停
Jobs一次性命令跑完就退出

原始的运行时 JSON 还在,作为高级配置保留,但按官方说法它「不再是主要的心智模型」。也就是说,你该拿「这个项目有哪些常驻服务、有哪些一次性任务」去理解配置,而不是拿一坨 JSON 字段去理解。

配置定义在项目工作区自身上:它描述这个项目 checkout 里有哪些 service 和 job、怎么跑。有一句很容易被忽略——定义配置本身不会启动任何东西。写完配置去看没进程,这是设计如此,不是坏了。

谁来启动服务:手动,和心跳自动

启动路径有两条。

第一条是手动:项目工作区的服务从项目工作区那边启停,项目 job 也在那边按需跑;执行工作区的服务从执行工作区那边启停,执行工作区的 job 同理。两级是分开控制的。

第二条是心跳自动。issue run 开始时,心跳会自动把这个工作区的运行时服务拉起来。文档给了具体的函数和文件位置:ensureRuntimeServicesForRun,在 server/src/services/workspace-runtime.ts,由 server/src/services/heartbeat.ts 调用。它启动的是「desired state 解析为 running」的那些服务——而没有显式设置 per-service desired state 时,默认就是 running。所以默认行为是「一跑 issue,服务全起」。

已经在跑、并且匹配现有 reuse key 的服务会被复用而不是重启。这一条决定了多 issue 共享一个工作区时不会互相把对方的 dev server 踢掉。

不想要自动启动的服务,把它的 desired state 设成 stoppedmanual,这类服务就只受界面控制,心跳不碰。

还有一条运维上必须知道的:Paperclip 不会在服务器启动时自动恢复工作区服务。服务器重启之后,服务不会自己回来,要等下一次 run 把它们带起来,或者你手动启动。别指望重启机器后一切照旧。

执行工作区怎么从项目工作区继承

执行工作区的定位是「把代码和运行时状态从项目主工作区里隔离出来」。文档列的四条:

  • 一个隔离的执行工作区有自己的 checkout 路径、自己的分支、自己的本地 runtime 实例。
  • 运行时配置默认可以从关联的项目工作区继承
  • 执行工作区可以用自己的工作区级设置覆盖这份配置。
  • 继承来的配置回答的是「有哪些命令、怎么跑」,但跑起来的服务进程仍然属于那个执行工作区自己

最后这条是理解整套东西的关键。继承的是「菜谱」,不是「那锅菜」。两个执行工作区继承同一份配置,各自起的仍是两个独立进程——端口冲突这类问题得你自己在配置里处理,文档没说 Paperclip 会替你分配端口。

issue 和执行工作区的三种关系

issue 挂的是「执行工作区的行为」,不是自动的运行时管理。三种情况:

  1. 新建:选隔离工作区模式时,issue 可以创建一个新的执行工作区。
  2. 复用:选 reuse 时,issue 复用已有的执行工作区。
  3. 共享:多个 issue 可以有意共享同一个执行工作区,这样它们能在同一个分支、同一批运行中的服务上干活。

第三种是刻意设计的,不是副作用。一组紧密相关的任务(比如同一个 feature 拆的几个 issue)放一个工作区里,它们看到的是彼此的改动。反过来说,需要互不干扰的任务就别共享。

运行 issue 会自动起服务,但运行结束时不会停——除非服务是 ephemeral 的,且没有别的 run 还持有租约(lease)。这意味着一次 run 跑完,dev server 大概率还在那儿占着资源。

关于任务本身在 Paperclip 里的流转规则,可以对着 Agent 不干活怎么排查 一起看,心跳和看门狗那套机制正是驱动这里 run 的上游。

心跳运行时到底做了哪五步

文档把心跳 run 里解析工作区的顺序写得很细:

  1. 心跳为这次 run 解析出一个 base workspace(基础工作区)。
  2. Paperclip 实现(realize)出实际生效的执行工作区,需要时创建或复用 worktree
  3. Paperclip 把执行工作区的元数据持久化下来——路径、ref、provisioning 设置这些。
  4. 心跳把解析好的代码工作区交给 agent run。
  5. 心跳调用 ensureRuntimeServicesForRun 启动 running-desired 的服务;如果配了 lazy runtime provision 命令且还没跑过,先跑那条命令

排查时按这五步定位就行:分支/路径不对是第 2、3 步的问题,Agent 拿到的代码目录不对是第 4 步,服务没起来是第 5 步。

懒加载运行时预置:把重活推迟到第一次启动服务前

有些工作区需要一次性重活才能跑起来——文档举的例子是给数据库灌种子数据、预热缓存。全放在工作区准备阶段做,会让每次开工作区都变得很慢,哪怕这次 run 根本不需要跑服务。

Paperclip 的做法是把这件事推迟:配一条 runtime provision command,位置在项目的 workspace strategy(Project properties → execution workspace),也可以在单个执行工作区的 Configuration 页上覆盖。

配了以后:工作区准备阶段保持轻量,这条命令恰好执行一次,就在这个工作区第一次启动运行时服务之前。留空则走旧的 eager 路径——所有 setup 都在工作区 provisioning 时做完。

执行结果记为执行工作区上的一个 workspace_runtime_provision 操作,三种状态:

状态含义
Deferred已配置但还没跑(还没有任何运行时服务启动过)
Provisioned at <时间>命令成功完成
Provisioning failed命令失败,工作区详情会链到失败操作的运行时日志

命令执行期间,运行时服务会先处于 Provisioning… 状态,之后才转入 starting/running。所以看到服务卡在 Provisioning,该去查的是这条预置命令,不是服务本身。

repo-only 项目与私有仓库的 token

项目工作区可以是 repo-only 的:只给 Repo URL,不给本地路径。这时服务器按需 git clone 到一个托管目录里;对于隔离的 git_worktree run,每次准备 worktree 之前还会 git fetch 刷新 base ref。

这里有个安全上的关键点:这两个操作都跑在服务器上,在任何 agent 进程之外——所以 agent 作用域的凭据环境绑定对它们不生效。很多人第一次配私有仓库栽在这儿:明明给 Agent 绑了 token,clone 还是失败。

私有 GitHub 仓库的正确做法,是把 token 存成公司级 secret,名字用下面三个之一(按此顺序检查):

GITHUB_TOKEN
GH_TOKEN
PAPERCLIP_GITHUB_TOKEN

存放位置是 Settings → Secrets。服务器按 run 解析它,用来认证托管 clone 和 base-ref fetch。文档列的几条边界值得抄下来:

  • 适用范围:只有 https://github.com/... 形式的 repo URL 走这条认证。SSH URL、GitHub Enterprise 主机、其它代码托管商,仍然走服务器宿主机的环境行为(系统 git 配置 / credential helper)。URL 里自带凭据的,永远不会被覆盖。
  • 回退顺序:没有匹配的公司 secret 时,回退到服务器进程环境里的 GITHUB_TOKENGH_TOKEN 变量(文档说这对自托管单租户部署有用),再回退到匿名访问——公有仓库不配任何东西也能用。
  • token 不落地:不出现在命令行、URL 或磁盘上,通过一个临时 credential helper 传给 git。每次解析都会记一条 secret access 事件。
  • 和推送凭据是两码事:Agent 要推分支、开 PR,仍然需要在 agent 或 project 作用域绑定 GH_TOKEN/GITHUB_TOKEN,让 token 进到 agent 进程环境里。同一个公司 secret 可以通过绑定同时支撑这两种用途。

密钥这条线的完整机制在 Paperclip 密钥管理 里,配私有仓库之前最好先把作用域搞清楚。

跨运行持久化:明令禁止 git push

这一节是整份文档里最反直觉的部分,也最值得单独记住。

Paperclip 的代码状态只通过本地执行工作区的 cwd 在两次 run 之间流转,不经过 git remote。文档管这叫 no-remote-git contract。具体是:

  • 每次 run 的 prepare 步骤,把本地 worktree 通过 ssh **打包(bundle)**到这次 run 的远端目录,不配置任何 git remote
  • run 结束时,适配器的 restore 步骤把远端新产生的 commit 直接写回本地 worktree
  • 适配器绝不能从运行时代码里 git push,也绝不能假定存在 remote。
  • restore 失败是 run 级错误,会在执行工作区上记 workspace_finalize=failed,并且会阻塞依赖这次 run 的 issue 唤醒,直到下一次 finalize 成功。

这条不变式有测试兜着:packages/adapter-utils/src/ssh-fixture.test.ts 里的 “no-remote-git contract” 用例,断言一个只存在于远端的 commit 能在全程没有配置任何 remote 的情况下到达本地 worktree。

对你意味着什么?自己写适配器的时候,不要在运行时里 push。这是文档写死的硬约束,不是风格建议。适配器怎么写、边界在哪,可以对着 Agent 在 Paperclip 里怎么被驱动 一起理解。

另外,看到某个 issue 一直不被唤醒、卡着不动时,workspace_finalize=failed 是一个应该优先排查的方向——它是门控(gate),不是单纯的报错记录。

工作区什么时候消失

执行工作区是持久的,直到有人(human)关掉它。文档给的三条:

  • 界面上可以归档(archive)一个执行工作区。
  • 关闭执行工作区会停掉它的运行时服务,并在允许的情况下清理工作区产物。
  • 指向项目主 checkout 的共享工作区,在清理时会被比一次性隔离工作区更保守地对待

最后这条是保护措施:共享工作区往往就是你的主代码目录,误清理的代价太大。

官方明确背书的五条

文档末尾单列的 Current implementation guarantees,是唯一写明「当前实现就是这样」的部分:

  1. 项目工作区的命令配置,是执行工作区界面控制的 fallback。
  2. 执行工作区的运行时覆盖配置,存在执行工作区上。
  3. 心跳 run 通过 ensureRuntimeServicesForRun 自动启动 running-desired 的运行时服务;设为 stopped/manual 的服务只受界面控制。
  4. 配置了 runtime provision 命令的话,它会懒加载执行、恰好一次、在第一次启动运行时服务之前。
  5. 服务器启动不会自动重启工作区服务。

要做运维预案,就照这五条来。

这套模型没解决什么

几件文档没写、别自己脑补的事:

端口分配。 多个执行工作区各自跑 dev server,端口怎么不打架,文档没有提供机制说明。共享同一份继承配置时,这个问题需要你在命令层面自己解决。

服务的资源上限。 一次 run 结束不停服务,那么同时存在多少工作区、总共开多少服务进程算安全,文档没有给上限或建议值。

清理策略的细节。 「在允许的情况下清理产物」里的「允许」具体判定条件是什么,文档只说共享工作区更保守,没有列判据。

私有仓库的非 GitHub 场景。 SSH、GitHub Enterprise、其它托管商都被明确排除在 token 机制之外,落到「服务器宿主机的 ambient 行为」——也就是要你自己在服务器上配好 git credential helper,Paperclip 不管。

还有前面提过的那点:这份指南自述为 intended runtime model,只有末尾五条是标明的实现保证。中间那些机制描述——尤其是继承、共享、lease 这些细节——建议先在一个无关紧要的项目上验一遍再往生产里搬。想从头搭一遍环境的话,建第一家公司的完整步骤 是更靠前的一环,工作区策略是在那之后配的。

延伸阅读


本文依据 Paperclip 官方仓库(github.com/paperclipai/paperclip,MIT 协议)的 docs/ 用户文档 与 doc/ 下的规范、运维与连接器手册整理,核对日 2026-08-17。 我们没有部署或运行过 Paperclip,因此不涉及界面外观与操作手感; 部分规范文档描述的是目标架构而非当前实现,文中已就地标注,不构成对实际行为的保证。 请以仓库最新内容为准。

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。