瀏覽 1.1.1 文件
1.1.1

並行 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 的獨立 agent 可以同時下達並執行 ait task start、檢視、 Snapshot、測試與 readiness 命令。每條命令都解析自己的 Task worktree 和 沿革,因此大型佇列不需要共享一個全域性可變 checkout。儲存庫命令鎖保護 AIT 權威更新,但不會把多個執行者對同一 worktree 普通檔案的無協調寫入變安全。

指向同一條 Line 的 finish 刻意不做吞吐競賽。每個候選都會重新校驗目前 目標頭,按需要 rebase 或停止,然後原子推進目標。這就是產品高併發 Task 支援的邊界:獨立編寫與驗證,對共享權威的接納則序列進行。

一個 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 必須先進到那個路徑,再改程式碼。

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

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

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

別把同一個 sprint 條目派給兩個 agent。1.1.1 會在建 Task 之前校驗這個 條目是未完成且可開成 Task 的,但它並不聲稱某個 Plan 條目有一把完整 的、跨客戶端的、服務端持有的唯一性鎖。並行的 Task,應該從各不相同的 確切條目引用起步。

兩個 agent,一條目標 Line#

假設兩個 Task 都從 main 的 Snapshot S0 分出來:

程式碼 · 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 各自在列印出來的路徑裡並行實作和測試。

程式碼 · 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 先 finish,把 mainS0 推到了 S1。Task B 不會拿一個 基於 S0 的候選去覆蓋 S1

程式碼 · 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 保持不動。檢視之後, 明確選擇繼續還是中止:

程式碼 · bash
ait worktree status --json
ait worktree rebase --continue
# or
ait worktree rebase --abort

衝突解完之後,按目前工作流程輸出的指引,把結果修訂版記錄下來並準備好。 ait task finish 消費的是一個已經就緒的選定 Patchset。如果在接納之前 目標頭又變了,這個操作會直接失敗,而不是把中間那次結果蓋過去。

為什麼 AIT_RAM 是這套策略的一部分#

檔案系統隔離是對的,但每個 Task 都要把整個儲存庫還原到磁碟上,代價可能 不小。AIT_RAM 策略把受管的 Task worktree 放在一個驗證過的記憶體檔案 系統上,並維護一份可複用的只讀 main-seed,讓扇出更快。

AIT_RAM 是執行層和快取層。它不是 AIT 權威,不是備份,也不是 ait-server 恢復的替代品。Task、Line、Snapshot 以及其他儲存庫權威, 仍然留在持久的儲存庫狀態或設定好的服務端裡。放進易失記憶體的,只有物化 出來的 checkout。

程式碼 · 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

別猜路徑,直接查解析出來的結果:

程式碼 · bash
ait config show --json
ait worktree list --json

設定投影會報出 task_worktree.memory_root、推匯出來的 ephemeral_root、別名根,以及 main-seed 的 RAM 預算(如果有)。

1.1.1 實際生效的記憶體預設值#

這些預設值故意寫死;1.1.1 不會按主機記憶體的百分比去推。

控制項內建值效果
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.1.1 沒有任何位元組或百分比閾值,會自動把新 Task 挪到 SSD。
ait doctor memory-root只讀這個診斷從不掛載、置備、修復或建立任何根。

ait config show --jsonait doctor memory-root --json 把存下來 的儲存庫策略跟主機目前的容量區分開。前者報的是帶型別的記憶體根選擇和儲存庫 本地的 seed 上限;後者報的是請求的容量、總位元組、可用位元組,以及內建的 零位元組校驗下限,每個值還附帶來源。

記憶體根的校驗與置備#

在真正依賴 RAM 放置之前,先證明設定的這個根確實是記憶體支撐的,而且 可寫:

程式碼 · 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.1.1 不會 自動按 Snapshot 大小截斷。維運可以設定允許走 main-seed 支撐的 RAM 放置的預設 Line Snapshot 大小:

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

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

每個 Task 的放置判定是這樣的:

程式碼 · 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.1.1 不會拿這個 值當作 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:

程式碼 · 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 的檔案變得持久。

意外解除安裝或重啟之後,先診斷,別急著刪後設資料:

程式碼 · 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 歷史。

對中斷之後留下的殘餘,先預演一遍帶歸屬判斷的清理:

程式碼 · 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 讓這份成本變得可見、 可控;它沒有免掉容量規劃這件事。

相關參考#

版本權威

對照 1.1.1 原始碼逐條核對

這一頁是公開文件,不是第二份產品契約。要確認發布權威,請以確切的原始碼和分發契約為準。

所屬元件 Snapshot
  • ait-coreSNP-ED7593DBF982
  • ait-serverSNP-0CCD7DD2A077
  • ait-runnerSNP-35C9C133D2EE
  • ait-pythonSNP-756C731A4CC0
  • ait-nodeSNP-55E90D0A81F1