浏览 1.0.0 文档
1.0.0 文档修订 2

并行 Task 隔离

通过隔离的 worktree、AIT_RAM 扇出、不可变证据和原子 Land,并行跑多个 coding agent 的 Task。

适用人群: 开发者、coding agent 与运维

运作模型#

AIT 允许并行编写、串行接纳。多个 coding agent 可以同时干活,但 它们不共用一个可变的 checkout,也不会直接往 main 上写。

每个受管的请求都会拿到自己的 Task、Change、功能 Line 和绑定的 worktree。agent 在这条沿革里记录不可变的 Snapshot。只有当某个候选 确切的 Patchset 攒齐了要求的证据,并且目标头又被重新核对过之后,它 才会被接纳进目标 Line。

这几层分工很重要:

  • Task 绑定的 worktree 加上命令锁,避免工作区操作互相撞车。
  • 不可变的 Snapshot 保证被评审过的那个修订版,不会在证据底下被换掉;
  • rebase 和目标头核对,避免一个过期的候选覆盖掉更新的工作;以及
  • 原子的 Land,避免共享目标 Line 变成"谁最后写谁赢"。

AIT 不会让两个 agent 编辑同一个 worktree 变得安全。高并行度来自开出 更多隔离的 Task worktree,而不是把一个目录共享得更狠。

一个 Task 独占一个可变 checkout#

对象在并行执行里的职责
Sprint 条目给一个 Task 一个明确的目标结果和确切的 [ref]
Task拥有这个有界的工作单元和收尾状态。
Change拥有这个 Task 可变的评审沿革。
功能 Line让 Task 的头和目标 Line 分开。
绑定的 worktree给一个 agent 一份物理上分开、可写的 checkout。
Snapshot把那份 checkout 冻结成一个不可变的修订版。
Patchset选中由 CI、评审、attestation 和策略来评估的那个确切 Snapshot。
Land重新校验,并原子地推进目标 Line。

ait task start 会创建并绑定这些工作身份。它的输出里会打出确切的 cd 命令。agent 必须先进到那个路径,再改代码。

Code · bash
ait task start \
  --from docs/sprints/login.md#login/password-visibility \
  --intent "Add accessible password visibility"

第二个 agent 起的是另一个未完成条目,拿到的是另一套 Task、Change、 Line 和 worktree。

Code · bash
ait task start \
  --from docs/sprints/login.md#login/session-regression \
  --intent "Repair session renewal regression"

别把同一个 sprint 条目派给两个 agent。1.0.0 会在建 Task 之前校验这个 条目是未完成且可开成 Task 的,但它并不声称某个 Plan 条目有一把完整 的、跨客户端的、服务端持有的唯一性锁。并行的 Task,应该从各不相同的 确切条目引用起步。

两个 agent,一条目标 Line#

假设两个 Task 都从 main 的 Snapshot S0 分出来:

Code · text
main @ S0
|
+-- Task A -> Change A -> feature Line A -> worktree A
|              `-> Snapshot A -> Patchset A
|
`-- Task B -> Change B -> feature Line B -> worktree B
               `-> Snapshot B -> Patchset B

两个 agent 各自在打印出来的路径里并行实现和测试。

Code · bash
# Agent A, inside worktree A
ait snapshot create --message "Add password visibility control"
ait workflow ready <change-a> --apply

# Agent B, inside worktree B
ait snapshot create --message "Repair session renewal regression"
ait workflow ready <change-b> --apply

假设 Task A 先 land,把 mainS0 推到了 S1。Task B 不会拿一个 基于 S0 的候选去覆盖 S1

Code · text
Task A: main S0 -> S1

Task B readiness:
  base S0 != current main S1
  |
  +-- clean rebase -> refresh Snapshot/Patchset evidence -> eligible Land
  |
  `-- conflict -> stop with main unchanged -> explicit resolution required

对一个干净的绑定 worktree,引导式的就绪流程可以在发布下一个候选之前, 先把它 rebase 到当前目标上。rebase 出来的候选是新内容,必须为那个 确切选定的 Patchset 带上证据;之前那次 CI 通过,不会拿来给 rebase 之后的修订版当证明。

rebase 冲突时,AIT 会记录下暂停状态,目标 Line 保持不动。查看之后, 明确选择继续还是中止:

Code · bash
ait worktree status --json
ait worktree rebase --continue
# or
ait worktree rebase --abort

冲突解完之后,按当前工作流输出的指引,把结果修订版记录下来并准备好。 ait task land 消费的是一个已经就绪的选定 Patchset。如果在 Land 之前 目标头又变了,这个操作会直接失败,而不是把中间那次结果盖过去。

为什么 AIT_RAM 是这套策略的一部分#

文件系统隔离是对的,但每个 Task 都要把整个仓库还原到磁盘上,代价可能 不小。AIT_RAM 策略把受管的 Task worktree 放在一个验证过的内存文件 系统上,并维护一份可复用的只读 main-seed,让扇出更快。

AIT_RAM 是执行层和缓存层。它不是 AIT 权威,不是备份,也不是 ait-server 恢复的替代品。Task、Line、Snapshot 以及其他仓库权威, 仍然留在持久的仓库状态或配置好的服务端里。放进易失内存的,只有物化 出来的 checkout。

Code · text
Persistent repository
|
+-- .ait/                         Task, Line, Snapshot authority
+-- .ait-worktree-links/task-a --+
`-- .ait-worktree-links/task-b --+---- stable local aliases
                                  |
/Volumes/AIT_RAM/                 |    memory-backed execution
`-- .ait-repos/<repository-key>/  |
    |                             |
    +-- main-seed                 |    read-only main Snapshot
    +-- task-a <------------------+    writable Agent A checkout
    `-- task-b <------------------+    writable Agent B checkout

Repository key 是从权威仓库路径推出来的。它把共享的内存根切分开, 这样即使不同仓库的 Task worktree 名字很像,也不会撞到一起。默认的 稳定别名根是仓库里的 .ait-worktree-links

别猜路径,直接查解析出来的结果:

Code · bash
ait config show --json
ait worktree list --json

配置投影会报出 task_worktree.memory_root、推导出来的 ephemeral_root、别名根,以及 main-seed 的 RAM 预算(如果有)。

1.0.0 实际生效的内存默认值#

这些默认值故意写死;1.0.0 不会按主机内存的百分比去推。

控制项内置值效果
macOS 受管 RAM 卷8 GiB(8,589,934,592 字节,即 16,777,216 个 512 字节扇区)内置 /Volumes/AIT_RAM 镜像的容量。
Linux 或 Windows 的 RAM 容量AIT 不做默认分配AIT 用的是已经挂好的 tmpfs/ramfsDRIVE_RAMDISK 的容量。
仓库的 main_seed_ram_max_bytes未设置(null不对 RAM 资格做 Snapshot 大小的截断。这不代表物理内存是无限的。
Task 放置的空闲空间阈值1.0.0 没有任何字节或百分比阈值,会自动把新 Task 挪到 SSD。
ait doctor memory-root只读这个诊断从不挂载、置备、修复或创建任何根。

ait config show --jsonait doctor memory-root --json 把存下来 的仓库策略跟主机当前的容量区分开。前者报的是带类型的内存根选择和仓库 本地的 seed 上限;后者报的是请求的容量、总字节、可用字节,以及内置的 零字节校验下限,每个值还附带来源。

内存根的校验与置备#

在真正依赖 RAM 放置之前,先证明配置的这个根确实是内存支撑的,而且 可写:

Code · bash
ait doctor memory-root

这个诊断永远是只读的。受管的 Task 放置走的是另一条路:在解析新的 worktree 时,它在 macOS 上可以置备配置的或内置的 RAM 卷。如果这次尝试 拿不出一个可用的根,放置就继续走持久磁盘的回退路径。一个只是名字叫 AIT_RAM 的路径,不能当作"这块存储是内存支撑的"的证明。

平台需要的证明置备行为
macOS确切的 /Volumes/<name> 挂载点,由一个可写的 hdiutil RAM 镜像支撑,且 APFS 卷身份符合预期。新 Task 的放置可以置备带类型的或内置的卷;诊断从来不会。
Linux一个已存在的挂载点,文件系统类型是 tmpfsramfsAIT 只校验,不创建系统挂载。
Windows一个已存在的根,被报告为 DRIVE_RAMDISKAIT 只校验,不创建 RAM 磁盘。

仓库配置用的是这几个带类型的字段:

字段用途
task_worktree.memory_root.kind平台证明:macos_ram_volumelinux_memory_rootwindows_ramdisk
task_worktree.memory_root.root确切的内存根绝对路径。
task_worktree.memory_root.volume_namemacOS RAM 卷标签;Linux 和 Windows 不用。
task_worktree.memory_root.sector_countmacOS ram:// 的正整数扇区数。省略时用 16,777,216 个扇区。
task_worktree.main_seed_ram_max_bytes可选的非负值,是默认 Line 的 Snapshot 大小上限,用来判定 RAM 资格。

已经检测到受支持的根时,ait init 会把它记下来。在 macOS 上,即使 仓库没存内存根,新 Task 的放置照样可以去试内置的 /Volumes/AIT_RAM 规格。这份 Task worktree 契约没有环境变量可以覆盖。

main-seed 扇出#

main-seed 是默认 Line 头上某一个确切 Snapshot 的只读物化结果。它是 缓存,不是一个可变的共享 checkout。

一个 Task 从当前默认 Line 分出来时,引导过程走这条路径:

  1. 解析出验证过的内存根,以及该 Repository 专属的 worktree 根。
  2. 核对 main-seed 与确切的默认 Line Snapshot 是否一致。
  3. seed 缺失、过期或无效时,从 Snapshot 权威刷新或重建。
  4. 把 seed 拷进新的 Task 路径。
  5. 只把拷出来的那棵 Task 树设为可写,并挂上它的 Task、Change 和功能 Line 元数据。
  6. 把稳定的别名暴露出来,打印给 agent。

逐文件拷贝的优先顺序是:

  1. macOS 的 clonefile
  2. Linux 的 reflink
  3. 写时复制用不了时,退回普通文件拷贝。

写时复制让你在 Task 启动时不必付出一份完整独立的物理拷贝。只要 agent 改了某个文件,各个 worktree 在逻辑上照样是彼此独立的。如果拷贝 seed 以安全的方式失败了,引导过程会把拷了一半的目标删掉,直接从功能 Line 的 Snapshot 还原。Task 该隔离还是隔离;变的只是那条加速路径。

让 seed 保持最新#

成功 land 到默认 Line 之后,AIT 会把 main-seed 对齐到 land 下来的 那个 Snapshot。刷新路径会:

  • 用一把 Repository 和 Line 专属的刷新锁;
  • 拿候选 worktree 跟 land 下来的 Snapshot 做校验;
  • 可以把已完成的绑定 worktree 提上去,也可以从 Snapshot 权威重建;
  • 准备一份暂存 seed,以及一份可回退的上一版 seed 备份;
  • 把装好的 seed 设为只读;以及
  • 在原子目录切换之前和切换过程中,重新核对目标 Line 的世代号。

如果刷新还在准备的时候 main 又往前走了,AIT 会拒绝装那份过期的 seed。缓存刷新失败,不会伪造出一个成功的 seed 状态。它会被单独报出来, 免得把 land 下来的代码权威和这份用完可弃的加速缓存混为一谈。

容量与磁盘回退#

RAM 上的并行度必须明确设上限。默认情况下 task_worktree.main_seed_ram_max_bytes 是未设置的,所以 1.0.0 不会 自动按 Snapshot 大小截断。运维可以设定允许走 main-seed 支撑的 RAM 放置的默认 Line Snapshot 大小:

Code · bash
ait config set \
  --task-worktree-main-seed-ram-max-bytes <bytes>

ait config unset task-worktree-main-seed-ram-max-bytes

每个 Task 的放置判定是这样的:

Code · text
configured seed limit exists AND Snapshot bytes > limit
  -> Repository-internal persistent disk
else if a supported RAM root can be selected and its Repository directory created
  -> RAM
else
  -> Repository-internal persistent disk

比较用的是严格大于。正好等于配置上限的 Snapshot,仍然有 RAM 资格。 超出上限时,AIT 会记下 main_seed_ram_budget_exceeded,并选用持久 Repository 根(通常是 SSD)下的 .ait-worktree/<worktree-name>。用内置的未设置值时,这个按大小触发 的切换是关掉的。

当所有符合条件的内存候选都不可用或者用不了时,AIT 同样会选那个持久 根。这包括:Linux 上没检测到 tmpfs/ramfs、Windows 上没有 DRIVE_RAMDISK、macOS 上 RAM 卷不可用或者尝试失败、配置的内存/临时 路径在它声明的根之下不被接受,或者创建 Repository 专属的 worktree 目录失败。

那个只读的内存根诊断,会报出一个内置的最小值零字节。1.0.0 不会拿这个 值当作 Task 放置的自动开关,也没有隐藏的低内存百分比阈值。需要留一份 余量的运维,得靠主机监控来落实,并在文件系统被吃光之前停止接纳新的 Task。

放置不会把一个已有的 Task 中途从 RAM 挪到磁盘。如果在选定 RAM 路径 之后拷贝 main-seed 失败,AIT 会先在同一个路径上直接试 Snapshot 还原;那是物化的回退,不是切到 SSD。如果选中的文件系统完不成这次还原, 或者之后被写满了,命令会失败,运维得保住已记录的 Snapshot、解决容量 问题,再在一个合格的根上重建或重新开始工作。

回退改变的是性能,不是 Task 身份、Snapshot 语义或 Land 的安全性。查 Task/worktree 输出里的 root_sourceephemeral_enabledtarget_pathfallback_reasonroot_sourcerepo_internal_fallbackephemeral_enabled: false,就证明放在了 持久根上;别因为看到一个稳定别名,就推断它一定指向 RAM。

开始大规模扇出之前,先看看当前容量和活着的 worktree:

Code · bash
ait doctor memory-root --json
ait worktree doctor --refresh --json
ait queue summary

持久性与恢复边界#

状态RAM 卷丢了还在吗?恢复来源
Task、Change 和 Line 权威持久的本地权威,或者配置好的 ait-server
已记录的 Snapshot 内容持久的 Snapshot 存储;发布过的话,还有远程内容权威。
main-seed不要求从当前默认 Line 的 Snapshot 重建。
干净的 Task worktree不要求从它记录的功能 Line 头重建。
还没进 Snapshot 的脏改动不在没有任何自动恢复保证。

运维规矩就一条:工作必须扛得住易失执行存储丢失时,就在语义完整的检查 点上创建 Snapshot。AIT_RAM 加速的是 worktree 的物化;它不会让没进 Snapshot 的文件变得持久。

意外卸载或重启之后,先诊断,别急着删元数据:

Code · bash
ait doctor memory-root
ait worktree doctor --refresh --json
ait worktree recreate <worktree-name> --dry-run
ait worktree recreate <worktree-name>

如果注册表的恢复必须从一个已知的 Task 和 Change 绑定开始,用 ait worktree recover-task --help 展示的那套明确的恢复入口。恢复绝不 会仅仅因为某个老目录还在,就把已完成或已取消的权威重新打开。

清理与操作规矩#

Task 成功收尾时,会在确认被接纳的 Snapshot 仍然是头之后,删掉绑定的 worktree 并归档它的 Task 功能 Line。这样既释放了易失容量,又不会删掉 Snapshot 历史。

对中断之后留下的残余,先预演一遍带归属判断的清理:

Code · bash
ait worktree cleanup-candidates --json
ait worktree cleanup --dry-run
ait worktree prune-stale --dry-run

同时开很多 agent 时,守住这几条:

  • 每个 agent 都给一个不同的 sprint 条目和 Task。
  • 只进那个 Task 打印出来的 cd 路径。
  • 永远别把仓库的规范根目录当成共享编辑目录。
  • 在依赖一个易失 worktree 之前,先记下不可变的检查点。
  • 让就绪流程去刷新过期的候选;绝不要绕过基线头的冲突。
  • 把 CI 当成某一个选定 Patchset 的证据,而不是某个 Task 名字的通行证。
  • 通过 AIT 来 Land,这样目标头核对和原子切换才一直有效。
  • 删 worktree 之前,先诊断、先预演清理。

这套东西不保证什么#

worktree 隔离挡住的是文件系统层面的互相覆盖。它证明不了两处文字上不 冲突的改动,行为上也兼容。两个 agent 可以改不同的文件,却动了同一个 运行时契约。仓库自己的回归测试、评审和策略,照样少不了。

AIT 也不提供无限的 RAM 并发。仓库大小、脏增量、工具缓存、测试进程和 runner 容量,仍然决定了安全的扇出规模。AIT_RAM 让这份成本变得可见、 可控;它没有免掉容量规划这件事。

相关参考#

Version authority

Checked against the exact 1.0.0 source

This page is public documentation, not a second product contract. Use the exact source and distribution contract for release authority.

Owning component Snapshots
  • ait-coreSNP-B06A48DA0245
  • ait-serverSNP-E90456E6425E
  • ait-runnerSNP-6B0A1BB3AFAD
  • ait-pythonSNP-973E3BFAF3DE
  • ait-nodeSNP-F962CC66AA62