隔離:Git Worktree與Worktrunk實戰(zhàn))
周末我同時開了四個 AI 編程 Agent 幫我改一個開源項目——一個在加新接口一個在重構(gòu)數(shù)據(jù)庫遷移腳本一個在補測試還有一個在處理文檔里的錯誤鏈接。各自跑起來都很順暢結(jié)果下午一合并就崩了兩個 Agent 在同一個文件里改了相鄰的代碼第三個 Agent 不知道第二個改了接口簽名四個 Agent 的上下文里全是過期的代碼快照。那一刻我意識到Agent 寫代碼的能力早就不是瓶頸真正的瓶頸是怎么給每個 Agent 一塊互不干擾的獨立工作區(qū)。Git Worktree 是解決這個問題的標準姿勢但裸用git worktree命令在多個 Agent 并行、會話頻繁創(chuàng)建銷毀的場景下管理成本很快就會失控。于是我把 Git Worktree 管理做成了 Worktrunk 這個 CLI 工具專門服務(wù)于并行 AI Agent 工作流。這篇文章會把從第一次踩坑到最終跑順的完整過程拆開講為什么并行 Agent 工作流需要 worktree、Worktrunk 的核心命令和生命周期設(shè)計、三種真實接入方式以及實踐過程中處理過的現(xiàn)場問題。內(nèi)容不挑 IDE也不綁定某家 Agent 服務(wù)適合正在被多任務(wù)并行和代碼沖突折磨的個人開發(fā)者也適合準備把多個編碼 Agent 納入日常流程的團隊。1. 并行 Agent 撞車現(xiàn)場問題不在模型能力在工作區(qū)隔離1.1 一次真實的四路并發(fā)事故那天的項目是一個 Node.js 服務(wù)端倉庫本地已經(jīng)積壓了三十多個分支改動沒整理。我開了四個會話各自任務(wù)很清晰Agent A 在routes/user.ts里增加一個/v2/profile接口Agent B 把數(shù)據(jù)庫遷移腳本從一套舊方案換成新的遷移工具Agent C 給todosController補集成測試Agent D 負責(zé)修 README 里的過期安裝命令。這四個 Agent 單獨拎出來看推進能力都挺強但問題出在它們?nèi)抗ぷ髟谕粋€ checkout 下。到中午我看了一眼git status工作區(qū)里已經(jīng)是四個 Agent 交替改動的混合狀態(tài)README 的改動和測試文件攪在一起routes/user.ts被 A 改了一半B 在遷移目錄里刪掉了 A 可能依賴的文件。更麻煩的是我沒法單獨把 A 的改動挑出來提交因為同一批文件里殘留著 D 留下的空白修改連 code review 都不知道該以誰為準。這是并行 Agent 工作流里最典型的事故模型多個 Agent 共享同一個工作區(qū)改動互相污染。Agent 本身不知道別人改了什么它的上下文建立在我以為文件是這種狀態(tài)的基礎(chǔ)上一旦真實狀態(tài)變了它后續(xù)每一步操作都在往錯誤的地基上蓋樓。1.2 為什么不能把多個 Agent 塞進同一個工作區(qū)表面上看Agent 只是改文件、跑命令共享工作區(qū)好像沒什么大不了。但實際跑下來至少有三個層面的問題。第一個是文件級沖突。兩個 Agent 就算改的是不同文件構(gòu)建產(chǎn)物、鎖文件、緩存目錄依然會互相覆蓋。舉個最簡單的例子一個 Agent 執(zhí)行了依賴安裝package-lock.json被更新另一個基于舊鎖文件做依賴分析的 Agent后續(xù)所有判斷就都建立在錯誤前提上。這種沖突非常隱蔽因為代碼本身沒語法錯誤CI 一跑才發(fā)現(xiàn)行為不對。第二個是上下文失效。AI 編程 Agent 的能力建立在準確的當前狀態(tài)感知上文件的真實內(nèi)容、最近的 git 歷史、分支狀態(tài)每一項都是它的地圖。共享工作區(qū)意味著這張地圖隨時隨地可能被別人改掉尤其是 Agent 用長會話跑任務(wù)時它看到的狀態(tài)往往已經(jīng)過期。我事后去翻對話日志能看到 Agent 一本正經(jīng)地基于舊代碼重構(gòu)一個已經(jīng)被改掉的函數(shù)——它沒有偷懶只是手里那份世界快照不對了。第三個是不可追溯性。多個 Agent 的改動混在同一個工作區(qū)事后想搞清楚這段代碼是誰寫的、當時基于什么目標、中間經(jīng)歷了哪些嘗試會非常困難。而如果每個 Agent 在獨立分支上工作這條鏈路就清晰得多出了事故可以精準定位到某個 Agent 某個時間點提交的某個操作。所以結(jié)論很清楚要讓多個 Agent 真正并行每個 Agent 必須有一塊獨立的工作區(qū)、獨立的分支、獨立的提交歷史。這正是 Git Worktree 的用武之地。1.3 Git Worktree 為什么是標準答案可能有人說那我開多個倉庫副本不就行了也行但代價是要處理多份.git元數(shù)據(jù)、多個遠端連接以及從副本目錄回到主項目之間的各種狀態(tài)不同步。Git Worktree 的精妙之處在于它在同一個倉庫內(nèi)維護多個工作目錄共享同一個對象庫和引用庫但每個工作目錄有自己獨立的 HEAD、索引和工作區(qū)文件。用一句話理解Git 的對象倉庫是硬盤worktree 是插在硬盤上的讀卡器你想在同一個倉庫里同時瀏覽和修改多份內(nèi)容就多插幾個讀卡器而不是復(fù)制整塊硬盤。每個 worktree 關(guān)聯(lián)一個分支。這意味著我可以給 Agent A 一個feature/v2-profile分支給 Agent B 一個refactor/migration分支它們在各自的目錄里自由操作互不阻塞。合并的時候各分支獨立走 merge 流程主工作區(qū)全程不受影響。這套機制從 Git 2.5 開始已經(jīng)非常成熟但裸用起來依然有痛點——這也是我做 Worktrunk 的直接原因。2. Worktrunk 的核心設(shè)計把 worktree 生命周期變成流水線2.1 原生 git worktree 少了什么原生方法的日常操作其實就四條命令git worktree add ../feat-a -b feat/a git worktree list git worktree remove ../feat-a git worktree prune單獨看每一條都不難但放到并行 Agent 工作流里缺了四個關(guān)鍵能力。第一沒有 Agent 會話綁定。git worktree list只能看到路徑和分支看不到這個 worktree 是給哪個任務(wù)、哪個 Agent 用的任務(wù)上下文全靠人在其他地方另記一份。第二生命周期不完整。創(chuàng)建 worktree 只是一小步后面還有運行 Agent、收集改動、合并分支、清理目錄、刪除分支原生命令把這一長串全推給使用者手工串。第三狀態(tài)不可量化。哪幾個 worktree 已經(jīng)合并、哪幾個還在跑、哪幾個是 Agent 崩潰后留下的孤兒需要人肉判斷。第四缺少批量操作和策略多個 Agent 同時啟動時要逐個創(chuàng)建、逐個判斷沒法一條命令統(tǒng)一處理。2.2 核心命令與狀態(tài)機設(shè)計Worktrunk 的設(shè)計目標很直接把 worktree 的整個生命周期壓縮成一條可觀察、可追溯的流水線。所以核心命令圍繞三條鏈路來設(shè)計——創(chuàng)建、查看、收尾。# 創(chuàng)建為指定 Agent 分配獨立 worktree worktrunk create feat/v2-profile --from main --agent agent-a # 查看觀察所有 worktree 的狀態(tài) worktrunk ls # 收尾合并 清理一條命令完成 worktrunk ship feat/v2-profile --to develop worktrunk drop feat/v2-profile每個 worktree 在我這套設(shè)計里有明確的狀態(tài)機created→running→ready→shipped/dropped。created是剛創(chuàng)建完Agent 還沒接手running是 Agent 正在里面干活A(yù)gent 跑完并提交后自動或手動標記為readyship會做合并檢查把分支合并到目標分支后自動刪除 worktree 并回收磁盤drop則是放棄當前 worktree 里的全部改動。這套狀態(tài)機的價值在于你可以隨時知道現(xiàn)在到底有幾個 Agent 在工作、各自的進展如何而不是靠記憶在十幾個命令行窗口里翻找。2.3 目錄約定讓 Agent 和 IDE 都找得到路很多并行方案失敗在人找得到 worktreeAgent 找不到。所以我要求所有 worktree 統(tǒng)一創(chuàng)建在倉庫根目錄下的.worktrees/name目錄里而不是散落亂放。這個約定的好處有三個。第一路徑可預(yù)測Agent 的 Prompt 里可以直接寫你的工作目錄是.worktrees/feat/v2-profile不用每次動態(tài)發(fā)現(xiàn)。第二IDE 可以按固定規(guī)則打開窗口不會出現(xiàn)開錯倉庫副本的情況。第三.worktrees目錄寫進.gitignore之后主工作區(qū)的git status不會被一堆 worktree 的產(chǎn)物弄臟。實際使用中我還給每個 worktree 生成一個.agent-context文件里面記錄了 Agent 名稱、任務(wù)描述、創(chuàng)建時間、基礎(chǔ)分支等元信息。Agent 在開始干活前如果加載這個文件就能快速理解我在哪、我要干嘛、如何回退。2.4 配置文件與細粒度控制Worktrunk 支持在倉庫根目錄放一個.worktrunk.yml定義項目級策略。下面是我這邊的實際配置# .worktrunk.yml root_dir: .worktrees base_branch: main merge_strategy: no-ff auto_cleanup: true agents: default: command: codex exec paths: exclude_from_copy: [node_modules, vendor, .venv, dist]exclude_from_copy是我特別看中的一個字段。worktree 的工作區(qū)文件是獨立 checkout 出來的如果基礎(chǔ)分支上帶著大量依賴產(chǎn)物每個 worktree 都會白白吃掉一份磁盤。Worktrunk 對這個字段里的目錄統(tǒng)一建立符號鏈接指向主工作區(qū)對應(yīng)的共享依賴目錄加速初始啟動的同時省下大量空間。不過這個優(yōu)化要小心使用稍后我會在踩坑部分詳細說它的副作用。3. 十分鐘跑通第一個并行 Agent 工作區(qū)安裝與上手3.1 環(huán)境準備前置條件不復(fù)雜Git 2.15 以上就支持基礎(chǔ) worktree 功能建議用 Git 2.30 以上Windows 上體驗更好。支持 macOS、Linux以及 Windows 的 Git Bash / WSL。Windows 下如果要用符號鏈接共享依賴建議開啟開發(fā)者模式或者把 Worktrunk 的鏈接模式配成目錄聯(lián)接junction避免權(quán)限問題。安裝方式有三種從 GitHub Releases 下載對應(yīng)平臺的二進制直接放到 PATH 里用 Homebrew 裝brew install worktrunk或者源碼構(gòu)建因為我當前用 Go 實現(xiàn)一條go build就能出干凈的單文件二進制沒有任何運行時依賴。3.2 初始化并創(chuàng)建第一個 worktree在倉庫根目錄執(zhí)行worktrunk initinit會檢查當前倉庫是否有未提交改動有的話提示你先處理避免把臟狀態(tài)帶進并行環(huán)境、確認 Git 版本、生成.worktrees目錄和默認的.worktrunk.yml。接著創(chuàng)建第一個并行工作區(qū)我會帶--agent參數(shù)標明歸屬worktrunk create feat/v2-profile --from main --agent agent-a這條命令內(nèi)部實際做了四件事從 main 拉出feat/v2-profile分支在.worktrees/feat/v2-profile下完成 checkout生成.agent-context元信息處理符號鏈接。命令結(jié)束時輸出一行AGENT_WORKSPACE/path/to/.worktrees/feat/v2-profile,這一行是關(guān)鍵——Agent 啟動時可以直接拿它作為工作目錄。3.3 讓 Agent 在隔離工作區(qū)干活不管你用 Codex CLI、Claude Code、Trae CLI 還是手寫腳本調(diào)模型只要保證 Agent 啟動時的工作目錄指向 worktree 路徑就可以cd .worktrees/feat/v2-profile codex exec 實現(xiàn) GET /v2/profile 接口保持與現(xiàn)有 v1 接口的兼容性對 AI Agent 來說它眼里的世界被限定在這個目錄內(nèi)git 歷史是這個分支的歷史文件狀態(tài)是這個 worktree 的狀態(tài)跑測試也只影響這個目錄。即使另一個 Agent 在主工作區(qū)瘋狂提交它也不會感知到——這正是互相隔離、各自推進的核心。3.4 合并與清理Agent 干完活、把改動提交到自己的分支之后回到主目錄執(zhí)行worktrunk ship feat/v2-profile --to developship的邏輯是檢查 worktree 是否干凈 → 切換到 develop → 合并feat/v2-profile按配置走 no-ff 或 squash→ 推送遠端 → 刪除 worktree → 刪除已合并分支。一條命令把整個收尾鏈路跑完。如果 Agent 中途跑飛了改動不想要直接worktrunk drop feat/v2-profile --forcedrop 會放棄整個 worktree 的改動并回收磁盤分支默認保留但會打上abandoned標記方便以后追溯為什么存在這條分支、當時發(fā)生了什么。4. 三種實戰(zhàn)接入方式把 Worktrunk 裝進現(xiàn)有工作流4.1 多 Agent 并行實現(xiàn)多個 Feature假設(shè)一個迭代里有三個互不依賴的業(yè)務(wù)功能想同時讓三個 Agent 并行開發(fā)。傳統(tǒng)做法是手動開三個終端、三個分支、三個目錄然后精神高度緊張地避免搞混。用 Worktrunk 的做法是三條命令worktrunk create feature/payment --from main --agent agent-1 worktrunk create feature/export --from main --agent agent-2 worktrunk create feature/notification --from main --agent agent-3然后 dispatch 到不同終端去跑 Agent。中途用worktrunk ls看進度做完一個ship一個。最直觀的變化是你不再需要在三個倉庫副本之間人肉同步每個 Agent 的提交歷史天然分開review 時一次看一個分支邏輯非常干凈。這種模式下我強烈建議把merge_strategy配成squash。因為 Agent 在開發(fā)過程中很容易產(chǎn)生十幾次小提交里面不少是fix typo、retry這類噪音squash 成一條干凈提交后develop 的提交圖會好維護得多。4.2 高危實驗與依賴升級隔離第二種場景是我個人最喜歡的——把 worktree 當成實驗艙。有一次我要升級一個核心依賴的大版本這種任務(wù)風(fēng)險很高一旦失敗可能會污染主工作區(qū)好幾天。我把實驗放到獨立 worktreeworktrunk create experiment/upgrade-http-client --from mainAgent 在里面做依賴升級、跑遷移、修測試。一種情況是實驗順利數(shù)據(jù)都驗證過了再決定合并回主分支另一種是實驗結(jié)果不理想一條worktrunk drop experiment/upgrade-http-client --force就把整個現(xiàn)場清理干凈主工作區(qū)毫發(fā)無傷。這類場景尤其適合握讓 Agent 做大規(guī)模重構(gòu)——在強拆主分支之前先在隔離區(qū)跑通全量測試再決定要不要帶回來。對配置中心、構(gòu)建腳本這類牽一發(fā)動全身的文件單獨開一個 worktree 去改比在主干上賭運氣穩(wěn)妥得多。4.3 與 Codex CLI / Claude Code 這類本地 Agent 工具組合最近社區(qū)討論熱度很高的 Codex CLI、Claude Code、Trae CLI本質(zhì)上都是本地命令行 Agent 環(huán)境。它們解決的是怎么讓模型讀寫代碼、執(zhí)行命令、自我迭代的問題但并解決多個這種環(huán)境同時跑的時候怎么互不干擾的問題——而 Worktrunk 恰好補的是這一層。我的用法是每次要起一個新的 Codex 會話先worktrunk create一個 worktree再把路徑傳給codex exec -C會話結(jié)束無論成敗worktrunk ship/drop收尾。這樣即使我同時在跑四五個編碼 Agent每個 Agent 都活在獨立世界里提交、回滾、復(fù)盤都有清晰的邊界。對我來說Worktrunk 和這些 Agent CLI 不是競爭關(guān)系而是上下游配合它們負責(zé)思考與執(zhí)行Worktrunk 負責(zé)空間隔離與生命周期管理。5. 跑了一段時間 Worktrunk我處理的五個現(xiàn)場問題5.1 分支被 worktree 占用git branch -D 刪不掉第一次遇到時很容易愣住。fatal: branch feature/xxx is used by worktree at /path/to/.worktrees/xxx我明明加了-D強制刪除Git 還是拒絕。這個其實是 Git 的保護機制一個分支同一時刻只能被一個 worktree checkout防止兩個位置對同一分支的引用互相覆蓋。解決方式并不難先git worktree remove或worktrunk drop再刪分支。但里面有個容易忽略的點如果 worktree 里有未提交改動git worktree remove也會拒絕執(zhí)行。Worktrunk 在drop時如果檢測到未提交內(nèi)容會先打印 diff 摘要讓你確認這能有效防止手滑丟掉一整個下午的工作。我在改進前曾經(jīng)直接刪過 worktree 目錄導(dǎo)致一堆未提交代碼徹底找不回來只能靠編輯器緩存哭著重寫——這個坑希望大家不要踩。5.2 依賴目錄與磁盤空間問題把 Worktrunk 分享給同事用的時候反饋最多的就是磁盤。一個中型前端倉庫 node_modules 動輒幾個 GB不開符號鏈接的話五個 worktree 就是五個 GB 起步。我的解法就是配置里的paths.exclude_from_copyWorktrunk 對這些目錄統(tǒng)一創(chuàng)建符號鏈接指向主工作區(qū)依賴。Windows 下符號鏈接權(quán)限麻煩建議配成link_mode: junction用目錄聯(lián)接實現(xiàn)。這樣每個 worktree 的依賴都走主工作區(qū)那一份磁盤占用幾乎不增加。但有一副作用必須提醒如果兩個 Agent 同時在跑依賴安裝命令它們會寫到同一個真實目錄互相踩。所以這個優(yōu)化只適合依賴相對穩(wěn)定、Agent 主要改業(yè)務(wù)代碼的場景如果 Agent 的任務(wù)本身就包含依賴變更、版本升級那還是要讓它用自己的 node_modules 更安全。取舍規(guī)則很簡單看任務(wù)是否會動依賴會動就別共享。5.3 IDE 打開的還是舊工作區(qū)用 VS Code 或者某些帶 IDE 聯(lián)動能力的 Agent 工具時有個很隱蔽的坑你先打開了主工作區(qū)窗口再創(chuàng)建新的 worktree新窗口開錯了路徑Agent 的改動看起來就像消失了。實際上不是消失是你盯錯了工作區(qū)。我現(xiàn)在的習(xí)慣是給每個 worktree 指定固定無誤的絕對路徑用一個終端統(tǒng)一跑worktrunk ls查看需要用 IDE 打開時從輸出里直接復(fù)制路徑而不是憑印象輸入。新項目一律先worktrunk init讓.worktrees從第一天就存在避免后續(xù)補建時路徑不一致。這個習(xí)慣養(yǎng)成后我再也沒出現(xiàn)明明改完了卻找不到文件在哪的尷尬。5.4 多個 Agent 合并沖突的現(xiàn)場恢復(fù)多個 Agent 并行開發(fā)最終往 develop 合并時撞車幾乎是不可避免的。Worktrunk 的ship命令遇到?jīng)_突會中途停下來不刪 worktree不強推合并狀態(tài)這點非常重要——它給你留了完整的現(xiàn)場。我最常遇到的沖突模式是兩個 Agent 都修改了同一個配置文件比如package.json或某個公共常量文件。Git 的沖突標記會把雙方改動標出來但讓 Agent 去改沖突標記實際體驗很別扭——它看到的是一堆、、很容易在里面繞暈。我的推薦做法是中立方 worktree 處理沖突單獨開一個 worktree把兩個分支都拉進來用三方合并工具解決確認沒問題后再把解決結(jié)果提交回去最后走 ship。這個 worktree 用完就 drop不占用正線資源又能把沖突處理從多人搶修變成一個隔離區(qū)慢慢解。5.5 孤兒 worktree 與歷史追溯Agent 偶爾會崩潰或者某個會話被人為遺忘。這時 worktree 不會自己消失它會一直占著磁盤和分支引用時間一長就變成一堆鬼魂目錄。Worktrunk 的做法是給每個 worktree 打上明確的狀態(tài)和歸屬標記。worktrunk ls里能看到創(chuàng)建時間、最近活躍時間、Agent 名稱、狀態(tài)是 running 還是 abandoned。配合auto_cleanup配置超過設(shè)定時間仍處于 abandoned 狀態(tài)的 worktree 會在下次執(zhí)行安全回收并輸出一條回收記錄。我在實際使用里發(fā)現(xiàn)只要回收記錄里留有分支名和 Agent 名稱隔很久回查時依然能還原當時的工作軌跡這個能力對團隊協(xié)作特別有價值。6. 進階玩法與團隊落地從單機工具到協(xié)作約定6.1 把 worktree 注入 Agent prompt 模板很多 Agent 工作流的失敗都源于上下文里沒有路徑概念。你讓 Agent 干活卻沒告訴它自己的準確定位它只能模糊地猜。我建議 Worktrunk 在 create 之后把一段上下文直接吐出來方便粘進 prompt你正在 /path/to/repo/.worktrees/feature/payment 目錄工作。 當前分支是 feature/payment基礎(chǔ)分支是 main。 你的任務(wù)是實現(xiàn)支付回調(diào)功能。 可以執(zhí)行任意 git 命令但請在當前 worktree 內(nèi)提交改動。這段上下文對 Agent 的隔離感建立非常重要。實測下來Agent 在明確知道我的世界是哪個目錄之后誤改其他區(qū)域文件的情況大幅減少因為它不再需要靠猜測來定位自己。如果你用的是支持系統(tǒng)提示詞定制的 Agent CLI可以把這段寫成模板變量每次創(chuàng)建后自動替換路徑和任務(wù)描述。6.2 批量啟動多個 Agent 的腳本化如果要一次起五個 Agent每個管一個模塊純手工敲命令顯然不現(xiàn)實。我一般在 shell 里這么跑for task in auth billing notify search report; do worktrunk create feature/$task --from main /dev/null \ echo 啟動 $task $(worktrunk ls --path feature/$task) done啟動完五個 worktree 之后再用 tmux 或者在多個終端里分別 dispatch 給 AI CLI。全部工作區(qū)信息在worktrunk ls里一眼看完哪個 Agent 完成了、哪個還沒開始狀態(tài)一目了然。這條鏈路還能繼續(xù)封裝成更上層的調(diào)度腳本比如按任務(wù)類型自動選擇 Agent 模型、任務(wù)結(jié)束后自動收集測試結(jié)果并通知你都是水到渠成的事。6.3 合并策略與團隊協(xié)作約定最后聊一下合并策略。squash 適合業(yè)務(wù)迭代型并行任務(wù)優(yōu)點是可以有效過濾 Agent 在開發(fā)過程中產(chǎn)生的噪音提交讓主干歷史保持簡潔no-ff 適合需要保留完整工作軌跡的場景比如評審時要看中間狀態(tài)、或者你希望把 Agent 從開始到完成的全過程 保留在 git 歷史里。如果團隊多人協(xié)作建議把 Worktrunk 的約定固化到.worktrunk.yml里并提交到倉庫這樣不管誰在哪個環(huán)境打開行為和命名規(guī)范都一致。worktree 的命名也值得定一套規(guī)范比如類型/業(yè)務(wù)名,避免出現(xiàn)test1、test2這種完全不可讀的目錄。命名規(guī)范一旦沉淀下來配合worktrunk ls看板整個團隊對一個倉庫正在并行推進的所有工作就能做到一目了然。最后再分享一點個人體會工具本身命令不多但把 worktree 生命周期當成流水線來管理之后并行 Agent 工作流才真正變得可管、可控、可回溯。我現(xiàn)在已經(jīng)默認每個 Agent 一個 worktree沒有例外。如果你也在同時跑多個編碼 Agent正被代碼互相污染搞到崩潰我建議先從給 Agent 分配獨立 worktree 開始——有時候一個干凈的空間比模型換大一號更解決問題。