Skip to content

Agent 为什么需要另一种工作目录?从 16 个问题理解 LayerFS ​

从廉价分叉、完整环境快照,到并发、发布与事务的边界。

假设你有一个 Web 项目,希望三个 Agent 同时尝试优化:A 调整查询逻辑,B 增加缓存,C 改写前端。它们从相同代码出发,安装依赖、修改文件、运行测试,最后交回结果。

模型是否能写出代码,只是其中一个问题。它们在哪里工作?会不会覆盖彼此的文件?失败后从哪里重新开始?几十次尝试需要多少磁盘空间?各自通过的测试,能否说明合并后的项目仍然正确?

LayerFS 试图降低其中一部分成本:让 Agent 使用完整、可写、可以快速派生的文件系统工作区,把结果保存为共享历史,再进行比较、分叉和合并。

这篇文章沿着这些问题解释它的设计,也讨论它的能力边界。文中将项目已公开的行为、作者的基准结果和本文的分析建议分开说明。

核查日期:2026 年 10 月 28 日。版本背景为 LayerFS v1.1.0,并参考当时的官网与 main 分支文档。需要注意,文中引用的性能与存储数字来自 v1.0.0 的发布测量(core at main e5c7aaae05),并非 v1.1.0 的复测结果。本文基于文档和源码阅读,没有自行部署或复跑基准。接口与限制可能继续变化。发布记录

1. 让三个 Agent 同时修改一个项目,会发生什么? ​

如果直接把同一个目录交给它们,最明显的风险是覆盖。但更常见的问题可能出现在覆盖发生之前。

A 正在修改函数签名,B 此时运行测试,读到了尚未完成的代码。C 更新依赖,A 的下一次构建突然换了一套运行条件。某个 Agent 执行清理命令,删掉了另一个 Agent 刚生成的文件。

给每个 Agent 单独复制目录,可以隔离大部分干扰。不过,随着尝试数量增加,又要处理复制、安装、缓存、清理和结果汇合。运行失败后,还需要判断:重新开始,还是恢复某个中间状态?

这里有三个不同目标:执行时互不污染,尝试过程可以恢复,结果能够可靠集成。它们需要配合,但不能互相替代。后文会看到,LayerFS 对前两个目标给出了相当完整的机制,而第三个目标需要上层补足协议。

2. 这些问题以前没有吗?为什么 Agent 让它们变得突出? ​

它们早已存在。多人开发、并行构建、持续集成和分布式计算都遇到过类似问题。

Agent 改变的是工作负载的比例,而且是四个比例同时反转:

第一,准备时间与任务时长的比值。 人类开发者可能用同一个目录数周,准备几分钟可以忽略;Agent 的一次尝试可能只运行几十秒,而安装依赖要花几分钟,准备就成了主导项。一个 0.05 秒的挂载对人类没有意义,对 Agent 却是数量级差异。

第二,试探的次数。 人通常为一个想法开一个分支试一次;Agent 会为一个任务试几十条假设。并发不再只来自"多人",更来自"一个人的多个猜想"。这意味着隔离成本必须低到可以为每个猜想单独开一条线,否则并行探索在经济上不成立。

第三,失败的常态性。 人类改错了是"改回去";Agent 需要的是"从第七步重来,但保留第三步的成果"。这要求检查点足够细,且检查点的存储成本接近零——否则探索越充分,空间涨得越快。

第四,写入的速度。 人类不会一小时提交三百次。Agent 会。这个差异直接决定了并发架构该怎么做:如果按人类频率设计,加锁位置和提交开销都可以很随意;按 Agent 频率设计,就必须把重活挪到锁外,只在"检查、合并、发布"这一步短暂独占。

此外,Agent 频繁经历"读取—思考—修改—验证"。文件系统看见的是分散的读写调用,却不知道这些调用属于同一次判断,也不知道修改代码的依据来自几分钟前读过的配置。这是本文对需求的解读,并非项目的表述:它意味着只记录"当前状态"是不够的,还需要让"哪一次判断产生了哪些变化"可追溯。

官网还用一句"Agent 八成时间在思考、一成时间在 Bash"来说明设计取向,并明确这些数字是示意而非测量。这个取向本身值得注意:如果绝大多数时间不需要计算,那么为整个任务持续持有一个执行环境就是一种浪费。

因此,需求可以归纳为:让环境派生、状态保存和变化汇合足够便宜,并为上层保留清楚的任务边界。旧工具仍然可用,但需要更系统地组织它们。

3. LayerFS 到底降低了哪些成本? ​

可以把 LayerFS 理解为管理 Agent 工作环境及其文件历史的基础设施。它提供普通程序可以使用的文件系统视图,也提供直接操作状态的 SDK。

它真正的目标是把四个动作压到几乎免费:

动作通常的做法LayerFS 的做法
派生一个可写环境复制目录,或重新安装依赖(分钟级)元数据 fork,不复制文件(毫秒级)
保存一个中间状态手工提交或打快照每次写调用就是一个 Commit
汇合两个结果手工 diff 与 patchStore 内短发布通道里合并,或拒绝为 Conflict
保留几十份相似状态几十倍磁盘内容分块、去重、差分与局部重建

几个概念各有职责:

概念职责
Store保存内容对象、分支与已提交历史的 SQLite 存储
Commit一次保存下来的不可变文件状态及其历史关系
BranchAgent 持续工作的路线
Workspace某个已提交状态上的可写视图,拥有自己的变化层
Sandbox执行命令的容器,可以承载 Workspace
Layer发布到 main 的文件状态;main 通过新增 Layer 前进

这里的 Commit 与 Git commit 是两套机制。LayerFS 可以保存项目中的 .git 目录,Git 则继续负责代码仓库协作。概念说明

一次工具调用有两条主要路径:

这两条路径的分野比它看起来更重要。文件工具调用完全不需要执行环境:fs.read、fs.write、fs.edit 在自己进程里直接访问 Store,一次写既是一次原子操作,也是一个 Commit。只有 Bash 才需要挂载、执行、提交、卸载。这是 LayerFS 与"给每个 Agent 一个容器"这条路线最根本的分歧——它不打算让容器承担状态,容器只承担计算。

在 Linux 的支持环境中,文件 SDK 调用可以直接访问 Store;普通命令则通过挂载的 Workspace 读写。Bash 执行、提交与卸载是不同操作,需要编排器组织。核心由 Rust 实现,上层提供 JavaScript 包和命令行入口。API 架构与使用指南

4. 已有 Git worktree、容器和快照,为什么还需要它? ​

文件系统一直在适应负载,而不是几十年没有变化。Unix 的字节流与统一 I/O 降低了程序访问数据的复杂度,FFS 改善数据布局,LFS 探索日志式顺序写入,Btrfs 带来写时复制与快照,OverlayFS 把上下层组合起来。Unix 回顾、FFS 论文、LFS 论文、Btrfs 文档、OverlayFS 文档

所以问题不是"传统文件系统只能保存当前目录"——那会低估已有能力。真正的问题是,没有一种现成方案同时提供下面这四件事。逐项对比:

方案它给了什么缺什么
Git worktree独立工作目录,共享仓库对象库只版本化被跟踪的文件;新目录是一份干净检出,node_modules、构建结果、缓存仍要重装;提交粒度是人,不是每次编辑
容器与 OverlayFS分层、写时复制、内核级 POSIX 完整层是构建期产物,不是运行时提交;容器可写层要么丢弃要么留成目录,不会变成带历史的对象;没有合并,没有冲突语义
Btrfs / ZFS 快照O(1) 快照,写时复制共享数据共享单位是数据块而非内容:多数工具习惯整文件重写,一重写就分配新块;跨快照去重需要额外工具异步补,且是块级而非内容定义分块
Nix、devcontainer 一类环境可声明、可复现只解决"环境如何定义",不解决并发写,也不解决状态历史
沙箱即服务很快拿到一台干净的机器机器里的文件系统不版本化、不可分叉、不可合并;没有预装好的依赖,仍然要等安装
文件系统快照加自建编排能力齐全,但需要自己拼这正是 LayerFS 要减少的那层脚本工作

LayerFS 的位置不是替代上面任何一个,而是把这些能力组合成一个 Agent 能直接调用的接口。下表是它具体想减少的工作:

当前操作LayerFS 希望减少的工作
创建新目录,再恢复项目环境从已准备好的状态派生工作分支
手工保存中间结果将文件变化纳入细粒度历史
为每次尝试保存完整副本共享基础内容,仅保存新增数据与元信息
自行协调挂载、差异和历史通过统一接口管理 Workspace 与 Commit

因此,它可能替代工作区管理流程中的一部分脚本与重复操作。Git 审查、运行时配置和集成测试仍然各有用途。若团队已经用快照、共享缓存和成熟编排器解决了这些问题,LayerFS 的增益就应与那套系统比较,而不是与"手工复制目录"比较。

5. 这套设计真正的赌注是什么? ​

这一节是理解后面十个想法的骨架。

Git 的设计前提是:仓库只装源码,环境可以重建。 所以 node_modules、构建产物、缓存被 gitignore 排除,git checkout 交给你一份干净但跑不起来的树。

LayerFS 的前提正相反:环境本身就是状态,重建才是 bug。 所以初始化会把源码、.git 及其索引、依赖、缓存、符号链接和每一个被忽略的文件一起收进 main 的第一层。

这是一个很贵的赌注,而且它会连锁推倒后面所有设计:

这解释了为什么"十个想法"里有一半在讲存储。如果只把源码纳入版本,去重和局部编辑都是优化项;一旦把几 GB 的依赖也纳入版本,它们就变成了这个前提能否成立的必要条件。

反过来看也成立:因为 fork 免费(④),所以可以为每个假设开一条线;因为每条线都在提交(⑦),所以需要汇合规则(③⑨);因为汇合会共享对象,所以对象必须不可变(⑧)。这十个想法是一条因果链,不是十个并列的特性。

6. "Ten ideas, one filesystem" 分别解决什么? ​

官网列出了六个核心想法和四个支撑想法。下面仍按原编号解释,但按上面那条链分组,方便看清每一环在链条上的位置。十个想法的官方说明

前提:环境本身就是状态 ​

⑥ Instant Bootstrap:新工作区继承已经准备好的文件环境。

初始化一次收全,之后每次挂载直接绑定整棵树——不扫描、不复制、不安装,唯一新建的文件是 Workspace 自己的小 overlay 数据库。文件内容按需从 Store 读出,所以第一次读仍然要付真实的 I/O 代价。

这一条是整个设计的立论基础,也是它与其他方案差别最大的地方。前提是所需文件确实位于被保存的范围,且容器镜像与系统运行条件相容——/tmp、HOME 和包缓存并不在被保存的树里。

存取:让"什么都存"负担得起 ​

⑤ Ultra Storage Optimization:让大量相似状态共享内容。

内容寻址让相同内容只存一次;小于 128 KiB 的文件整存,更大的按内容切成 8 到 32 KiB 的块,一次编辑只影响附近几块;文件或块还可以相对上一版存差分,链深有上限,并配有读取预算;全部打包后用 zstd 压缩。项目按 Store 报告两个比值:去重率与压缩率。

没有这一条,检查点越细、探索越多,空间增长越快,前面那个前提直接不成立。

⑩ Big File, Small Edit:处理成本也要尽量随改动量增长。

文件被组织成一棵区间树(extent 树),改一段只需要重建那一段到根的一条路径,其余部分与旧版本共享。写入时 overlay 只保留真正写过的字节,不复制整个文件。

但这里有一个容易忽略的前提:插入或删除文件中间的字节,在 POSIX 里没有对应的局部写接口,大多数程序会重写后面全部内容。这时被保存的就是那次重写。所以"逻辑上只改一行"不等于"只产生少量 I/O",具体取决于应用怎么写。

⑧ Append-only Objects:共享历史时,旧对象必须稳定。

对象按内容哈希命名,写入 pack 后永不被更新或删除(存储层用触发器拒绝 UPDATE 和 DELETE)。多个分支引用同一对象而不复制,读者也从不等待写者。删除一个文件只是让后来的 Commit 不再引用它,更早的 Commit 仍然拥有它。

这一条支撑了共享与历史读取。不过,分支头这类可变指针仍然需要协调,历史也会持续积累。

执行:让"随时派生"变得免费 ​

① Ephemeral Computing:执行资源可以按需出现。

具体机制值得说清:文件工具调用根本不需要机器,它们在调用方进程里直接打 Store;只有 Bash 需要挂载一个临时 Workspace,运行结束后提交并卸载。挂载不复制也不安装,卸载只删掉 overlay,历史保留。

它降低的是维持执行环境的成本。实际收益仍取决于容器启动与资源回收策略——官网的演示里会保留空闲沙箱,正是为了摊掉启动开销。

⑦ Auto Checkpoints:默认留下可检查的变化。

文件写调用在写入时就提交;Bash 的变化需要编排器在命令结束后显式调用提交。没有变化的操作不会生成空 Commit,单独一条消息也不会。

这里要划清一个常见误解:一次 exec 本身不是一次检查点。提交是编排器的动作,LayerFS 从不自行提交、超时或卸载。也正因为如此,回来查看、比较或从某一步分叉,都需要先确认那一步确实被保存过。

④ Recursive Filesystem:任何合适的历史起点都可以继续分叉。

分叉只记录起点,不复制文件,所以可以从 main、任意 Branch、任意 Commit、任意 Layer,以及任意深度的分叉再分叉。A 的查询优化有效,还可以从它的中间结果派生 A1、A2。

它让树状探索变得负担得起,但不会自动选择最优路线;搜索策略和评价标准仍由上层提供。

汇合:让多条线收成一条 ​

③ Multiagent Coordination:在提交时协调变化。

每次提交都读取分支当前的头,与之合并,并在 Store 的短发布通道内发布,所以并发写者不会遇到"头已经移动"的失败。不同路径的变化都能落地;同一路径的双方修改会被拒绝为 Conflict,此时什么都不提交。blame 能指出每一行是哪一个 Commit、也就是哪一次工具调用写的。

它解决的是文件变化的协调,业务逻辑能否成立仍需要验证。

② Decentralized Agents:执行方共享历史。

每个 Agent 的容器运行自己的 daemon,直接打开同一个 Store 文件,没有数据库服务器。多个参与者各自执行,结果进入共同历史,减少了来回复制状态的需求。

但"共享"并不自动带来任意多机能力。当前所有打开者必须共享同一个 Linux 内核,Store 必须位于本地 ext4、xfs 或 btrfs 上,否则在写入前就以 Unsupported 拒绝。macOS 与 Windows 是通过一个长期存活的 Linux worker 中转,底层条件并没有消失。后文会再说明。

⑨ Conflict Rules:根据文件用途选择合并政策。

/.lww 用 gitignore 风格的规则声明"这些路径永不冲突,修改时间较新的一边胜出",平局取 ours。lockfile、生成代码、构建产物和缓存都被工具整体重写,逐行合并没有意义,这条规则让它们机械地收敛。

但"更新"不等于"正确"。两个 Agent 分别增加依赖时,直接挑一个 lockfile 就可能丢掉另一方的结果;通常还应重新生成并验证。规则会直接丢弃较旧一侧的改动,所以只应用于工具能重新生成的文件。项目文档自己也是这么警告的。

这些想法合起来回答了一个问题:在一个每秒都在变的工作目录上,如何同时做到环境完整、派生免费、历史不丢、结果能汇合。

7. 这些想法有学术基础吗?新意在哪里? ​

关键机制都有长期研究基础。需要区分"某种机制有研究支持"和"LayerFS 的具体组合已经得到独立验证"。本文没有找到足以支持后者的同行评审材料。

研究思想对应到哪一层设计
LBFS,SOSP 2001利用文件与版本间的相似内容减少重复传输内容定义分块(⑤)
Venti,FAST 2002内容哈希标识、只写一次、重复块合并内容寻址与不可变对象(⑤⑧)
Versionfs,FAST 2004自动产生文件版本,并管理保留政策每次调用即提交(⑦)
Strata,LISA 2011通过共享软件层组合虚拟设备的文件系统Layer 与 Workspace 的层叠(①②)
Determinator,OSDI 2010私有工作区与受控同步,把并发写入转成可检测冲突分支隔离与短发布通道(③)

对应关系清楚之后,评价问题也就清楚了:它是否把这些能力组合成容易接入 Agent 的接口?完整环境的派生和细粒度历史是否足够便宜?失败与冲突是否容易被上层正确处理?

这些是工程与产品层面的问题,需要实际工作负载验证。论文解释了机制为何有价值,不能代替对当前实现的评估。

8. 完整环境和所有快照都保留,为什么不会复制出几十份项目? ​

因为"每个分支都看见完整内容"和"每个分支都独占一份物理内容"是两回事。

假设基础项目为 2 GB,三个分支只修改少量文件。理想的存储量更接近"一份基础内容,加各分支新增内容,再加历史和索引",而不是简单变成 6 GB。

LayerFS 对小文件与大文件采用不同表示,并限制差分链的深度与读取预算:整文件、块、元数据三条链的深度上限分别是 16、8、8,读取时按链预算,最坏情况约为四倍上限。这样的限制很重要:压缩得更小,如果换来昂贵的历史解码,也未必划算。存储政策源码

成本不会消失。每个 Commit 仍有元数据、索引和空间管理开销,项目自己给出的单次提交增量约在 4 到 5 KB 量级;去重率也取决于内容重复程度。项目对每次写入成本的分析

项目报告过一个包含 157 个快照、约 4.94 GB 目录树的数据集:LayerFS 占用约 100.1 MB,对照 Btrfs + zstd 为约 1,994.6 MB,156 份可完整读回。这是 v1.0.0 在单台 arm64 Mac、Docker Desktop、8 CPU 环境中的单次发布测量(单样本、seed 1、无 best-of),不能外推成所有项目都有约 20 倍收益。基准与方法

公开结果也包含慢于对照组的场景:顺序写只有 FUSE 直通的 0.31 倍,顺序读 0.81 倍,随机写 0.40 倍,原因是每次都走内容寻址与打包路径。项目把这类数字和好看的数字放在同一张表里,这一点值得肯定。

9. A 读完文件,B 随后提交,A 不就在使用旧数据吗? ​

是的。需要分清三个问题:读到的状态是否完整,它是否最新,基于它作出的判断是否仍然有效。

B 尚未提交时,修改留在自己的 Workspace 中。A 从已提交状态读取,不会看到 B 的未完成变化。B 提交后,A 已经读进上下文的文本当然不会自动刷新。

旧快照有时正是需要的:我们希望测试在稳定输入上运行。问题发生在结果要进入新的共享状态时,输入变化是否已经使判断失效。

ref 可以是一个 commit id,也可以是一个分支名。使用固定 Commit ID,可以明确读取哪个历史状态;使用分支名,一次调用只解析一次当时的头,所以连续两次调用之间若有提交,可能读到不同版本。跨多个读取调用需要一致视图时,不能仅凭"它们来自同一个分支"就认为版本相同。引用与读取语义

10. 提交时检查冲突,究竟保护了什么? ​

先看一种有共同起点的情况。A、B 都从 C0 挂载工作区。B 先提交为 C1,A 随后提交。系统知道 A 的起点是 C0,因此可以比较 C0、A 的结果和 C1,合并变化或报告冲突。

这里并非完全没有锁。LayerFS 在检查 head、协调并发布的关键步骤使用短暂独占通道,使发布步骤正确排序;重活(构造树、哈希、压缩、存对象)都在通道之外完成,通道只覆盖必须原子的那一步。它没有在整个模型思考和命令执行期间锁住项目。并发模型

"同一个文件都被修改"也不必一概等于无法合并。源码里有一张三方文本合并与多种冲突判定的表,覆盖两侧都改、双方新增、修改对删除、删除对修改、类型冲突和二进制文件,具体结果取决于变化内容和操作路径。合并实现

还有两个容易被忽略的性质,它们其实比"能合并"更重要:每次调用只尝试一次,失败就返回确切结果,不自动重试,也不二次合并。这排除了"重试导致静默覆盖"这一整类问题,代价是上层必须自己处理 Conflict 和 Busy。

再看另一种情况:

text
t0:A 读取数值 10。
t1:A 思考,决定加 10,算出 20。
t2:B 把数值改成 11,并提交。
t3:A 新发起一次 fs.write,写入 20。

A 的新写入可能绑定在 t3 的状态上。系统看到的是"调用者要把 11 改为 20",并不知道 20 来自更早的 10。因此,写入可能正常成功,但 B 的增加效果没有被保留。

当前公开 fs.write 接口不要求携带此前读取的版本,也没有 If-Match 之类的预期版本参数。其单次调用内部的保护,不能自动覆盖发生在调用之间的推理。fs.edit 的旧文本匹配可以保护局部前提,却无法验证其他文件的依赖。写入接口

11. 那发布呢?为什么"看起来没问题"的发布仍然可能丢掉别人的工作? ​

这是本项目最容易被误读的一处,值得单独说。

上文讲的"读头、合并、发布"适用于三类操作:分支上的文件写入、Workspace 的提交、以及显式的 branch.merge。发布(publish)不在这三类里。

branch.publish 是把一个分支的头追加为 main 的下一层。它对 main 自分叉以来新增的层不做合并,而是跳过,并在返回结果里如实报告:

text
publish(branch) → {
  kind: "Added",
  publishedLayer, basedOn,
  overwroteCount: 3,
  overwrote: [前 50 项],
  overwroteHasMore: false
}

也就是说,如果 cart 从 L1 分叉,期间 search 被发布为 L2,直接发布 cart 会让 L2 的改动从 main 上消失。文档把它写成一条明确的行为,不叫竞态,就叫覆盖。想保住那些工作,正确路径是:从 main 分叉一个新分支,把你的分支合并进去,再发布这个新分支。

把四种操作的语义放在一起,差别就清楚了:

操作起点从哪来遇到已移动的头结果
fs.write 等分支写调用时读取的头读头、合并、发布,全在短通道内不同路径 Committed;同一路径 Conflict,不提交
workspace.commit挂载时绑定的 base同上同上
branch.merge(a, b)显式给定的两边以 merge base 做三方合并干净则产生双 parent 的 Commit;有冲突则不创建任何东西
branch.publish(branch)分支自己的头不合并,跳过追加为下一 Layer,跳过项列在 overwrote

这条设计并非不可理解:main 是只读的层堆叠,覆盖式发布让"发布"保持线性、可预测,也让跳过这件事变成可见的、可报告的。但它意味着**"提交时安全"和"发布时安全"是两个不同强度的保证**,而后者依赖编排器的纪律,不是底层的不变量。

后文讨论插件时,这一点会再次出现:那里说的检查—发布窗口,只是这条更宽的覆盖语义的一个收窄版本。

12. 修改不同文件,为什么仍然可能出错? ​

假设两个文件分别记录 A、B 服务是否启用,要求至少保留一个服务。初始状态下两者都启用。

Agent 1 看到 B 启用,于是关闭 A。Agent 2 看到 A 启用,于是关闭 B。它们修改不同文件,合并可以没有文本冲突;结果却是两个服务都关闭。

这类现象称为写偏斜。它说明,检查写入重叠不足以保护读取依赖。数据库研究也明确区分快照隔离与可串行化:稳定快照并不自动使所有并发执行等价于某种串行顺序。相关论文

在 Web 项目中,同样的问题可能表现为:A 修改缓存键的生成方式,B 修改另一个文件中的失效逻辑,双方测试各自通过,组合后缓存却无法失效。

因此,"文件可以合并""集成测试通过"和"所有业务约束成立"是不同强度的证据。测试非常有用,但覆盖哪些约束仍取决于测试本身。

到这里,前三节可以合成一个结论:文件层能保证不丢写、不半落地、不静默覆盖,但不能保证业务约束成立。 前者是底层的不变量,后者必须由上层声明和验证。

13. 那是否仍然需要 Git worktree,或者把所有文件锁住? ​

LayerFS 的 Workspace 已提供独立可写视图。为了同一个文件隔离目的,不一定还需要加一层 Git worktree。两者都允许各自从旧状态工作,也都不能自动保证汇合后的业务正确性。

锁是可用方案,但需要覆盖正确的依赖。如果任务"读取 X,根据它修改 Y",只锁 Y 仍然可能在 X 变化后提交陈旧判断。判断也可能依赖整个目录,或某个文件不存在。

从读取一直锁到思考、修改和测试完成,可以保护部分前提,却可能长时间阻塞其他任务。另一条路线是乐观执行:先在固定快照上工作,提交时验证必要条件,不成立就拒绝并重新判断。

对于三个优化 Agent,一个实际的流程是:各自形成候选结果,集成到目标版本,在集成后的确切状态上重新验证,再受控发布。对于余额、库存等必须维护的约束,应使用提供相应事务能力的服务。

文件系统也可以提供更强事务,这是研究和设计选择。例如 TxFS 探索了利用文件系统日志支持应用级 ACID 事务。LayerFS 当前的文件历史与合并能力,不能直接等同于这类保证。TxFS 论文介绍

14. 能通过插件补足吗?Agent 应该什么时候调用? ​

可以用插件组织任务、验证和发布。不过,官网的 Plugins 主要是基于公开 SDK 构建的外部集成——官方给插件作者的规则里明确写了"只使用公开调用,不得导入内部模块",因此不能据此推断存在任意底层事务钩子。插件机制

一个协调插件可以替 Agent 保存任务起点,为它创建专属分支,将后续读写关联到任务,并在提交时生成集成候选。当前也已有按假设创建分支、并行执行验证的插件,展示了这类工作流封装的价值。parallel verify 插件

但仅做"发布前检查"仍不够:

text
插件检查:目标仍为 C100。
另一个写入者:发布 C101。
插件发布:基于 C100 的结果。

检查和发布之间存在窗口。要可靠关闭它,要么所有目标更新都经过唯一协调器,在受保护步骤内完成检查与发布;要么底层提供原子条件发布,例如概念上的 publish(candidate, expectedTarget=C100)。当前公开接口中的可选 commit 参数是发布来源,并不是这个预期目标条件;branch.publish 也没有 expected-head 之类的参数。SDK 方法定义

结合上一节,这个窗口的严重程度还要再上调一档:由于发布是覆盖而非合并,插件必须先确认 main 没有前进,再把分支同步到最新 main,才能发布。三步之间任何一步被跳过,损失都会以 overwroteCount 的形式默默发生,而不会以冲突的形式报错。

唯一协调器也有前提:其他参与者不能绕过它更新目标。提示词中的"请使用插件"只是一种约定;需要强制保证时,宿主必须控制相关访问入口。外部协调器还要考虑故障后如何恢复发布状态。

让 Agent 每次判断是否需要并发保护,也不可靠。它无法预知其他 Agent 下一秒的行为,更可能遗漏编译器、目录查询或外部服务中的隐式依赖。

更合适的分工是:基础隔离与发布管理默认执行;项目声明必须维护的约束;运行环境记录可观察依赖;Agent 补充语义条件。追踪不完整时,可以保守地将起始快照视为依赖,目标变化就要求重新检查。

这是本文建议的架构,并非 LayerFS 已提供完整读集合事务。插件适合定义策略,底层仍需提供足够的原子机制。

15. LayerFS 不是什么?当前有哪些实际边界? ​

首先,它不是整台机器的时间机器。文件快照不保存进程内存,也不撤销已经发出的外部请求。dev server、数据库、socket 都不在任何 Commit 里;命令输出也不会被保存。需要重现一次任务,还要另外保存命令、输入、输出与相关运行条件。

其次,它不是任意多机共享的通用存储。当前 Store 的直接访问受同一 Linux 内核和本地文件系统约束;macOS 与 Windows 的支持是通过一个长期存活的 Linux worker 中转,跨平台 worker 的存在没有消除这一底层条件。Store 格式在版本间会变,旧版本会被拒绝而不是迁移。

它也不能仅凭历史不可变就保证断电安全。当前运行 Disposable 模式,SQLite 使用 WAL 与 synchronous=OFF,从不等待磁盘确认;系统崩溃或断电可能导致丢失或损坏。项目文档明确写着"不要把无法重建的数据作为唯一副本放在这里"。历史垃圾回收也尚未提供,Store 只增长。

此外,文件隔离与完整安全沙箱是不同问题。FUSE 视图有意省略 FIFO、socket、设备节点、set-id 位和独立维护的 atime/ctime;fallocate 不在接口范围内。采用前应验证实际工具链,而不是从"能挂载"推断所有 POSIX 行为都受支持。单个调用也有边界:一个 Sandbox 最多十六个 Workspace,fs.read 每次最多 2000 行或 48 KiB,命令执行没有自带超时,容器一旦死亡,其中的 Workspace 与未提交的改动一起消失。

最后,版本号不能代替成熟度评估。v1.1.0 的发布说明披露了 SDK 的破坏性变更——JavaScript 包被拆成三个,与 1.0.0 不兼容——以及未执行的验证项。性能、兼容性与恢复能力都应结合自己的场景检查。发布说明

16. 什么情况下值得使用,什么情况下现有工具就够了? ​

如果工作流有大量短命尝试、反复准备依赖、频繁保存中间状态,而且主要数据可以重建,LayerFS 值得试验。它尤其适合验证"环境管理是否已经成为 Agent 吞吐瓶颈"这一假设。

如果只有少量长期工作目录,Git worktree、共享缓存或成熟快照方案已经运行良好,迁移收益可能有限。对断电持久性、完整文件语义或跨机器部署有严格要求的系统,也应先确认当前边界。

可以用同一个项目,对照现有流程测量四件事:新尝试多久可执行,保存一步需要多久,历史增长消耗多少空间,失败后能否准确恢复和集成。再加入三个并发用例:同文件竞争修改、不同文件共同破坏约束,以及一个新层发布后再发布旧分支——最后一个会直接暴露你是否理解发布语义。

对开头的三个 Agent,LayerFS 的吸引力是让它们更便宜地获得完整环境、保存探索过程并交回结果。最终接受哪一条路线、哪些输入必须重新确认、什么状态允许发布,仍然需要明确规则。

理解这条边界,才能既用上它降低试错成本的能力,也避免把"文件状态已经保存并合并"误认为"整个任务已经正确完成"。

本文由Codex + Workbuddy联合完成。

最后更新: