Bạn spawn 4 agent song song để viết 4 module. Sau 10 phút tất cả báo xong. Bạn mở code ra thấy package.json chứa nội dung từ agent 3, còn import statement trong src/index.ts của agent 1 đã bị ghi đè bởi agent 4. Agent 2 không để lại dấu vết gì. Bài học: nhiều agent cùng lúc không tự nhiên đồng nghĩa với làm việc nhanh hơn.

Race condition trong multi-agent không phải lỗi model. Đó là lỗi thiết kế task. Model không biết agent khác đang làm gì. Nó chỉ biết file system tại thời điểm nó read, và nó ghi output không hỏi ai. Việc phòng ngừa phải làm từ phía bạn, trước khi spawn.

Bài này cover ba cấp isolation để ngăn race, các slice strategy khi muốn dùng cấp nhanh nhất mà vẫn an toàn, coordination pattern cho N agent cùng nhau, và checklist trước khi spawn để không quên bước nào.

Race condition trong multi-agent là gì

Hình dung hai agent A và B cùng nhận nhiệm vụ liên quan đến một file:

t=0  Agent A: Read(src/index.ts)        -> nội dung X
t=0  Agent B: Read(src/index.ts)        -> nội dung X

t=5  Agent A: Edit(src/index.ts, ...)   -> ghi X + thay đổi A
t=6  Agent B: Edit(src/index.ts, ...)   -> ghi X + thay đổi B

Agent B read ở t=0 trước khi A ghi, nên B không biết về thay đổi của A. Khi B ghi ở t=6, nó ghi đè toàn bộ. Thay đổi của A biến mất. Không có lỗi, không có warning. Last writer wins.

Đây là race condition dạng Write-Write. Còn có Read-Write: agent A đang đọc file giữa chừng để ra quyết định, agent B ghi file đó. A kết luận dựa trên snapshot cũ, ra quyết định sai.

Cả hai dạng có cùng một nguyên nhân: hai agent share state mà không có coordination.

Ba cấp isolation

Có ba cấp để kiểm soát mức độ share state giữa các agent.

Cấp 1: No isolation (mặc định)

Tất cả agent spawn từ main session đều chia sẻ cùng working tree. File nào trên disk đều visible và writable với mọi agent. Không có cơ chế khóa.

Main session (branch: feature/SCRUM-123)
    |
    +-- Agent A  <---+
    |                |
    +-- Agent B  <---+--- cùng working tree, cùng branch
    |                |
    +-- Agent C  <---+

Risk cao nhất. Chỉ an toàn khi file scope mỗi agent hoàn toàn disjoint và bạn đã verify điều đó trước spawn.

Cấp 2: File scope slicing

Vẫn cùng working tree, nhưng mỗi agent được giao scope file riêng và có rule cứng trong prompt “chỉ touch folder X, không đụng folder Y”.

Agent A: src/api/**        (không touch src/ui/)
Agent B: src/ui/**         (không touch src/api/)
Agent C: docs/**           (không touch src/)

Risk trung bình. Agent vẫn có thể đọc file ngoài scope (model không bị block ở filesystem level), chỉ bị block bởi instruction trong prompt. Nếu model “quên” instruction hoặc tự đánh giá cần touch shared file, scope vẫn có thể bị vi phạm.

Shared file (như package.json, README.md, root config) cần có owner rõ ràng, hoặc không được assign cho bất kỳ agent nào.

Cấp 3: Worktree isolation

Mỗi agent chạy trong một git worktree riêng, branch riêng. File system hoàn toàn tách biệt ở filesystem level.

Main branch: feature/SCRUM-123
    |
    +-- worktree/agent-api   (branch riêng, folder riêng)
    +-- worktree/agent-ui    (branch riêng, folder riêng)
    +-- worktree/agent-docs  (branch riêng, folder riêng)

Risk thấp nhất. Race condition không thể xảy ra giữa hai worktree vì chúng không share file. Sau khi agent xong, mỗi agent mở PR về target branch và bạn merge lần lượt.

Worktree isolation được nói chi tiết ở bài 17. Ở đây chỉ cần biết: khi nào cần CI gate, cần review, hoặc cần chắc chắn không có race, dùng cấp 3.

Bảng so sánh

CấpRisk raceOverhead setupMerge sau
No isolationCaoThấpKhông cần merge
File scope slicingTrung bìnhThấpKhông cần merge
Worktree isolationThấpCao hơnCần merge/PR từng worktree

Cấp nào dùng tùy theo context. Session đơn giản với scope rõ ràng, dùng cấp 2 là đủ. Session phức tạp cần CI hoặc review, dùng cấp 3.

Anti-pattern hay gặp

Anti-pattern A: spawn nhiều agent vào cùng folder

BAD:
Agent A: "Viết src/auth/*.ts"
Agent B: "Viết src/auth/middleware.ts + src/auth/utils.ts"
Agent C: "Review src/auth/ và thêm test"

A và B có thể cùng tạo src/auth/utils.ts. B và C có thể race khi C read file B chưa xong ghi. Folder src/auth/ là shared scope, không phải disjoint scope.

OK:
Agent A: "src/auth/handlers/ chỉ, không touch các folder khác trong src/auth/"
Agent B: "src/auth/middleware/ chỉ, không touch src/auth/handlers/"
Agent C: "tests/auth/ chỉ, không touch src/"

Disjoint ở folder level, không chỉ ở file level. Dễ verify và dễ brief trong prompt.

Anti-pattern B: shared file không có owner

Các file sau hay bị nhiều agent đụng vào:

  • package.json (thêm dependency)
  • tsconfig.json (thêm path alias)
  • .env.example (thêm env var mới)
  • src/types/index.ts (thêm type mới)
  • README.md (cập nhật docs)

Nếu agent A và agent B cùng cần add dependency vào package.json, một trong hai sẽ thắng và dependency của bên kia mất.

Pattern đúng: chỉ định một agent là “owner” cho mỗi shared file. Agent khác nếu cần thêm vào shared file thì để lại note trong output (“cần add zod vào package.json”), và agent owner (hoặc bạn) xử lý.

OK:
Agent A (api-owner): src/api/**, package.json (owner)
Agent B (ui-owner): src/ui/**, không touch package.json
                    -> output: "cần add @radix-ui/react-dialog"
Agent A đọc note của B và add dependency sau khi xong nhiệm vụ chính

Hoặc dùng cấp 3 (worktree): mỗi agent có package.json riêng trong worktree của nó. Sau merge, bạn resolve conflict package.json một lần. Phức tạp hơn nhưng không mất thay đổi.

Slice strategy

Khi dùng cấp 2, cách slice phổ biến:

Slice theo folder

Đơn giản và trực quan nhất:

Agent A: src/api/
Agent B: src/ui/
Agent C: src/lib/
Agent D: tests/

Hiệu quả khi architecture đã module hóa theo folder. Không hiệu quả khi các module phụ thuộc nhau nhiều và agent cần đọc qua boundary để viết đúng.

Slice theo concern

Agent A: implementation (*.ts, không test)
Agent B: test (*.test.ts, *.spec.ts)
Agent C: migration (db/migrations/)

Concern thường ít overlap hơn folder khi feature span nhiều folder. Agent B chỉ viết test dựa trên interface của A, không cần biết implementation chi tiết.

Slice theo file pattern

Agent A: *.ts (logic)
Agent B: *.tsx (UI component)
Agent C: *.css, *.module.css (styles)

Phù hợp với frontend stack. Ít overlap nhưng file Component.tsxComponent.module.css thường đi chung. Cẩn thận khi agent A viết className mới mà agent C chưa define.

Slice theo phase

Đây là slice “thời gian” thay vì “không gian”:

Phase 1: Agent A viết types + interface (src/types/)
Phase 2: sau khi A xong -> Agent B viết implementation, Agent C viết test (parallel)
Phase 3: sau khi B+C xong -> Agent D viết documentation

Không phải parallel hoàn toàn, nhưng an toàn hơn vì phase sau không race với phase trước. Main session làm aggregator giữa các phase.

Coordination pattern

Pattern 1: Aggregator

Main session phân chia task, spawn N agent parallel, đợi tất cả xong, aggregate output.

Main session:
  1. Đọc spec
  2. Chia task: [A: api, B: ui, C: tests]
  3. Spawn A, B, C song song (foreground parallel)
  4. Đợi tất cả xong
  5. Chạy build + test, review output, commit

Phù hợp khi:

  • Task A, B, C hoàn toàn independent
  • Main session không cần làm gì trong khi agent chạy
  • Bạn muốn review tổng thể trước khi commit

Diagram:

Main     ----spawn----> Agent A ----done---->
session  ----spawn----> Agent B ----done----> aggregate
         ----spawn----> Agent C ----done---->

Pattern 2: Pipeline

Main session feed output của agent A vào agent B tuần tự. Agent B biết kết quả của A trước khi bắt đầu.

Main session:
  1. Spawn Agent A (viết API spec + types)
  2. Nhận output của A
  3. Spawn Agent B với output A làm input (viết implementation)
  4. Nhận output của B
  5. Spawn Agent C với output B (viết test)

Phù hợp khi B phụ thuộc vào A. Chậm hơn parallel nhưng không có dependency risk.

Diagram:

Main -> Agent A -> output A -> Main -> Agent B -> output B -> Main -> Agent C

Pattern 3: Reviewer pair

Một agent viết, một agent review (team mode từ bài 18). Cả hai chạy persistent trong cùng session, chuyền context qua SendMessage.

Main session tạo team:
  - "writer": viết implementation
  - "reviewer": review code của writer

Main: SendMessage(to="writer", "Viết src/api/orders.ts theo spec này...")
Writer báo xong.
Main: SendMessage(to="reviewer", "Review src/api/orders.ts vừa viết, focus security + error handling")
Reviewer báo feedback.
Main: SendMessage(to="writer", "Sửa các điểm reviewer comment: ...")

Phù hợp cho task cần quality gate trước khi tiếp. Chậm hơn parallel nhưng output tốt hơn.

Cả ba pattern không loại trừ nhau. Dự án lớn thường là aggregator ở level cao, pipeline ở level trung, reviewer pair ở task quan trọng.

Integration sau khi agent xong

Khi dùng cấp 2 (no isolation/file scope), không cần integration, agent đã commit trực tiếp vào branch hiện tại của main session. Chỉ cần review và test.

Khi dùng cấp 3 (worktree isolation), mỗi agent có branch riêng và bạn cần merge/integrate:

Local merge

Phù hợp khi target là personal feature branch, không cần CI gate, không cần review:

git fetch origin
git merge --ff-only origin/worktree/agent-api
git merge --ff-only origin/worktree/agent-ui

Merge lần lượt, không parallel. Nếu hai agent touch file khác nhau, cả hai merge clean. Nếu có overlap nhẹ, resolve conflict một lần sau khi merge đầu.

PR + auto-merge

Phù hợp khi target là shared branch (main, development, staging) hoặc cần CI chạy trước merge:

# Agent đã tự mở PR (brief agent: "mở PR về feature/SCRUM-123")
# Sau khi CI pass:
gh pr merge <PR-api> --squash
gh pr merge <PR-ui> --squash

Merge lần lượt. Branch thứ hai có thể cần rebase nếu CI state đã đổi sau khi branch đầu merge.

Cherry-pick

Phù hợp khi muốn chọn lọc commit từ worktree, bỏ qua commit debug/draft:

git cherry-pick <sha-api-final-commit>
git cherry-pick <sha-ui-final-commit>

Thủ công nhất. Dùng khi agent commit nhiều lần và chỉ cần commit cuối.

Checklist trước khi spawn

Trước khi spawn N agent parallel, chạy qua checklist này:

[ ] File scope: mỗi agent có scope folder/pattern riêng, không overlap
[ ] Shared file: liệt kê shared file (package.json, tsconfig, root index...)
       -> assign owner, hoặc không assign ai + brief agent bỏ qua
[ ] Rule trong prompt: mỗi agent có rule "chỉ touch [scope], không đụng ngoài scope"
[ ] Integration plan: sau khi tất cả xong, merge theo thứ tự nào
[ ] CI gate: nếu target là shared branch, plan PR thay vì local merge
[ ] Dependency: task nào phụ thuộc task nào? parallel hay pipeline?

Checklist này không đảm bảo zero bug, nhưng đảm bảo không mất thay đổi vì race.

Ví dụ thực tế: 3 agent, 1 feature

Feature: thêm endpoint /orders/{id}/cancel vào một REST API.

Phân tích scope:

Shared files: package.json, src/types/index.ts, README.md
Agent A scope: src/api/orders/ (handler, service, repo)
Agent B scope: tests/api/orders/ (unit + integration test)
Agent C scope: docs/api/ (OpenAPI spec)

Brief từng agent:

Agent A prompt: “Viết handler cancelOrder trong src/api/orders/. Chỉ touch folder src/api/orders/. Nếu cần type mới, define local trong folder đó trước, để note trong output là type nào cần move lên src/types/index.ts sau. Không touch package.json, src/types/index.ts.”

Agent B prompt: “Viết test cho cancelOrder trong tests/api/orders/. Mock handler của agent A qua interface, không import trực tiếp implementation. Chỉ touch tests/api/orders/. Nếu test fail vì thiếu fixture, để lại note.”

Agent C prompt: “Viết OpenAPI spec cho DELETE /orders/{id}/cancel trong docs/api/orders.yaml. Chỉ touch docs/api/. Không touch src/.”

Sau khi tất cả xong:

  1. Đọc note của agent A về type mới cần move.
  2. Sửa src/types/index.ts tay (hoặc spawn agent nhỏ riêng làm việc này).
  3. Chạy npm test, xem output agent B.
  4. Review PR/diff tổng thể.

Không race vì ba scope hoàn toàn disjoint. src/types/index.ts không được assign, agent nào cũng biết không touch, và bạn xử lý sau cùng.

Tóm tắt và bài tiếp theo

  • Race condition trong multi-agent là Write-Write hoặc Read-Write trên file dùng chung. Model không biết agent khác đang làm gì, nên coordination phải làm từ phía bạn.
  • Ba cấp isolation: no isolation (share working tree), file scope slicing (disjoint folder/pattern), worktree isolation (branch riêng, filesystem riêng). Cấp cao hơn an toàn hơn nhưng overhead merge nhiều hơn.
  • Anti-pattern hay gặp: scope overlap giữa các agent, shared file không có owner.
  • Coordination pattern: aggregator (main spawn N, đợi kết quả), pipeline (output A feed B), reviewer pair (team mode).
  • Integration: local merge nếu personal branch, PR + auto-merge nếu shared branch, cherry-pick nếu cần chọn lọc commit.

Bài 21 sẽ trình bày một concrete workflow đầy đủ: production debug đang chạy trên main session, đồng thời hai hotfix song song trên hai worktree, merge lại không mất trạng thái debug. Đây là kịch bản nhiều agent trong thực tế phức tạp nhất và rõ nhất lý do cần worktree.

Liên quan: skill nf-agents Decision 4 mô tả cooperative shutdown protocol cụ thể (SendMessage type: shutdown_request + 10s wait + zombie surfacing), một implementation của coordination pattern đề cập ở bài này.


Bài thuộc series Claude Code từ zero. Series plan tại bài giới thiệu.