Bài 1 (anatomy của async coding) đã đặt nền cho khái niệm “agent chạy nền, làm việc trong sandbox riêng, push PR khi xong”. Bây giờ đến phần cụ thể: Claude Code (CC) làm việc đó như thế nào trên máy của bạn, không phải trên một service cloud xa lạ.

Bài này tôi viết khi đang ngồi trong một background session của chính CC. Worktree branch worktree/agent-ad07a..., dispatch từ một parent session đang theo dõi 12 agent song song khác. Không phải lý thuyết. Là dogfooding.

Khi nào bạn cần BG mode

Mỗi lần tôi mở một session Claude Code interactive trong terminal, terminal đó bị giữ chân. Tôi không thể đóng iTerm tab đó, không thể reboot máy, không thể đi cà phê quá lâu vì WiFi sleep sẽ làm session timeout. Đó là sync mode.

BG mode đảo ngược bài toán. Session vẫn chạy nhưng không cần terminal nào attach. Tôi đóng iTerm tab, tôi reboot WiFi router, tôi đi ngủ. Daemon CC giữ process sống trong khi session làm việc của nó. Khi cần xem progress, gõ claude agents ở bất kỳ terminal nào, FleetView hiện list.

Bốn use case tôi dùng BG mode hàng tuần:

Batch write nhiều bài blog song song. Như chính bài này. Parent session spawn 12 agent, mỗi agent một bài, mỗi agent một worktree riêng. Parent ngồi không, đợi notification. Thay vì tôi viết tuần tự 12 bài trong 12 ngày, agent batch xong trong một giờ.

Long refactor không cần babysit. Đổi tên một symbol qua 200 file, chạy codemod, fix các edge case không match regex. Sync mode tôi sẽ phải ngồi nhìn 30 phút. BG mode tôi tab sang Slack đọc tin nhắn, để session tự xong, ping tôi khi done.

Test/build chạy nền song song với edit foreground. Tôi edit code ở main session, BG session chạy npm test --watch plus phân tích failure plus đề xuất fix. Hai bên cùng làm việc trên repo. Có isolation đúng cách thì không conflict.

Reconnect-friendly remote work. SSH từ iPad qua Termius vào homelab Ubuntu. Mạng café chập chờn. Nếu session là sync interactive, mỗi lần disconnect là mất terminal state. Với BG, session ở phía homelab, daemon giữ. Tôi reconnect, claude agents, attach lại session bằng ID.

/bg command, cách deterministic nhất để vào BG mode

Có ba cách bắt đầu một BG session. Cách phổ biến nhất là /bg từ trong session interactive đang chạy.

# Bạn đang trong session sync, gõ trong prompt:
/bg

Session detach khỏi terminal. Daemon adopt nó. Terminal về shell prompt bình thường. Session vẫn chạy task của nó ở nền. Nếu task xong, daemon giữ session ở idle, không kill.

Cách thứ hai là start ngay từ shell:

cd ~/WORK/blog
claude --bg "viết bài về Astro 5"

--bg flag khiến CC không bao giờ attach terminal, in ra session ID rồi return. Hữu ích cho automation, cron job, hoặc khi bạn muốn fire and forget từ một script.

Cách thứ ba là dispatch từ FleetView:

claude agents
# Trong UI, gõ prompt vào prompt bar, Enter
# Session mới spawn với source "fleet", tự động là BG

Tất cả ba đều cho cùng kết quả: một session sống trong daemon, không gắn terminal, có thể xem trong FleetView. Chỉ khác cách khởi tạo.

Sự khác biệt với sync mode chỉ gói gọn ở: ai giữ stdio. Sync mode terminal giữ. BG mode daemon giữ. Tool loop, context window, model invocation đều giống nhau.

FleetView, một list view cho mọi session

claude agents mở FleetView, một TUI hiển thị toàn bộ Claude Code session đang sống trên máy. Bốn cột chính: tên session (hoặc ID nếu chưa đặt tên), trạng thái (working, idle, blocked, completed), nguồn (cli, fleet, slash, bg), và cwd.

Một search bar phía trên để filter. Một prompt input phía dưới để dispatch session mới ngay tại chỗ. Phím tắt thường dùng: Enter dispatch, / focus search, q quit FleetView nhưng không kill session, k kill session đang focus.

Tôi đã viết một bài riêng về FleetView (FleetView, một màn hình thay cho 6 tmux pane) nói chi tiết về workflow của tôi với nó. Bài này tôi focus vào góc khác: FleetView trong vai trò control panel cho BG agent batch.

Khi parent session spawn 12 agent BG, FleetView của tôi trông như sau:

NAME                   STATUS    SOURCE  CWD
agent-001-non-tech-a   Working   fleet   .claude/worktrees/agent-aa...
agent-002-non-tech-b   Working   fleet   .claude/worktrees/agent-bb...
agent-003-non-tech-c   Idle      fleet   .claude/worktrees/agent-cc...
agent-004-mcp-bai-4    Working   fleet   .claude/worktrees/agent-dd...
... (12 dòng)
parent-session         Idle      cli     ~/WORK/HENIA/blog-heniart

Parent session ở dưới cùng (cwd là repo gốc), 12 agent ở trên (mỗi agent cwd là worktree riêng của nó). Tôi nhìn một mắt biết được agent nào đang busy, agent nào đã done, có agent nào stuck vì lỗi không.

Source fleet quan trọng. Nó cho biết session đó được dispatch từ FleetView hoặc từ Agent tool call (cả hai cùng nhánh code bên trong CC). Source cli là session foreground gõ claude trong terminal. Source bg là session spawn bằng claude --bg ... từ shell. Source slash là dispatch từ RemoteTrigger hoặc Telegram bridge.

EnterWorktree, vì sao BG session cần isolation

Đây là chỗ BG mode khác biệt với sync mode về mặt cơ chế.

Sync mode bạn ngồi trong cwd của mình, edit file của repo trực tiếp. Mỗi session một terminal, một cwd. Nếu bạn mở hai sync session cho cùng repo trong hai terminal, hai session đó dùng chung working tree, last writer wins khi cả hai edit cùng file. Đôi khi OK (bạn biết phân chia thư mục), đôi khi đau đầu.

BG mode đẩy vấn đề này lên thêm bậc: vì daemon giữ session, bạn không thấy nó đang chạy gì ở foreground. Nếu BG session edit working tree của bạn trong khi bạn đang edit ở main session, race condition không tha cho ai.

CC giải bằng cách mặc định: trước khi BG session bắt đầu dùng tool, nó phải gọi EnterWorktree. Tool này tạo một git worktree riêng tại <git-root>/.claude/worktrees/<slug>/, branch riêng tên worktree/<slug>, và switch cwd của session vào worktree đó. Mọi edit từ đây xảy ra trong worktree, không động chạm working tree gốc của bạn.

Bạn có thể thấy điều này nếu nhìn vào harness output khi BG session khởi động:

[harness] Background session detected without worktree.
[harness] Calling EnterWorktree tool...
[harness] Worktree created: .claude/worktrees/agent-ad07a..../
[harness] Session cwd updated. Tool loop ready.

Hai feature liên quan nhưng khác nhau hoàn toàn, dễ nhầm:

FeatureÁp dụng choKhi nào xảy ra
isolation: "worktree"Subagent spawn từ session khácLúc spawn subagent qua Agent tool
EnterWorktree toolBackground session chính nóLúc BG session bắt đầu dùng tool

isolation: "worktree" là param parent truyền vào khi gọi Agent tool. EnterWorktree là tool BG session tự gọi cho chính nó. Một bên parent quyết, một bên session tự quyết. Tôi đã viết chi tiết hơn về phân biệt này ở bài worktree.bgIsolation setting.

worktree.bgIsolation, khi nào nên tắt

Từ CC 2.1.143, có setting worktree.bgIsolation cho phép tắt cơ chế EnterWorktree mặc định:

{
  "worktree": {
    "bgIsolation": "none"
  }
}

Set "none", BG session bỏ qua bước EnterWorktree, edit working tree gốc trực tiếp. Mặc định (không set hoặc set giá trị khác) BG session vẫn vào worktree riêng.

Khi nào tôi tắt isolation:

Repo có submodule lớn. Worktree mới phải init lại submodule. Nếu submodule vài GB, thời gian init đôi khi dài hơn cả task BG. Tắt isolation, dùng chung submodule với main session.

Build cache theo path tuyệt đối. Bazel, CMake, Rust target. Path mới đồng nghĩa cache miss, build lại từ đầu. Một task BG đơn giản như format code không xứng đáng để rebuild dependency 30 phút.

Task nhỏ và reversible. BG session chỉ update CHANGELOG.md hoặc rename một symbol. Overhead worktree không cần. Edit thẳng working tree, nếu sai thì git checkout undo.

Khi nào để mặc định ON (worktree isolation):

Parallel BG batch. Như trường hợp tôi đang dùng cho bài này. 12 agent edit 12 file khác nhau trong cùng repo. Nếu tất cả share working tree, race condition không cứu được.

BG session đang chạy khi tôi cũng đang edit foreground. Cả hai cùng working tree là công thức cho conflict không tracked được.

Risky refactor. Một BG session có thể đi sai hướng và edit nhiều file không mong muốn. Worktree riêng cho tôi rollback dễ: git worktree remove .claude/worktrees/<slug> là xong, không cần git reset.

Setting có thể đặt ở ba mức: global (~/.claude/settings.json), project (<repo>/.claude/settings.json), hoặc local per-machine (<repo>/.claude/settings.local.json). Tôi để global default ON, override "none" local cho repo nào có submodule lớn.

Hands-on: dispatch một batch 12 agent

Workflow của tôi cho bài blog series tuần này, nguyên văn:

Bước 1: parent session viết plan ra file dưới .claude/jobs/<job-id>/batch-plan.md. Mỗi agent có pre-allocated file path, frontmatter, outline. Pre-allocate quan trọng để tránh hai agent cùng pick filename trùng.

Bước 2: parent spawn 12 Agent tool call song song, mỗi call một section của plan, isolation: "worktree", run_in_background: true. Mỗi agent tự EnterWorktree, tự checkout worktree/agent-<short> từ HEAD của parent.

Bước 3: parent ngồi không. FleetView của tôi list 13 dòng: parent plus 12 agent. Tôi tab sang Slack, đọc tin nhắn.

Bước 4: agent xong việc, mỗi agent tự git add, git commit, git push -u origin worktree/<slug>, gh pr create --base main. Notification về parent qua Stop hook. Telegram bot tôi cài cũng push qua phone.

Bước 5: parent verify từng PR (lint, em-dash grep, content check), merge tuần tự, pull về main session.

Toàn bộ chu trình từ spawn đến merge xong 12 PR khoảng 90 phút. Sync mode tôi sẽ mất 12 ngày, mỗi ngày một bài, vì tôi không thể type 12 bài cùng lúc.

Một chi tiết kỹ thuật ít người chú ý: vì isolation: "worktree" base trên parent HEAD (qua WorktreeCreate hook tôi đã cài, xem bài worktree isolation), agent thấy đúng trạng thái uncommitted của parent. Không phải origin/main cũ. Quan trọng khi parent có 5 commit chưa push mà agent cần thấy.

Cost và latency

BG mode không miễn phí. Mấy điều thực tế:

Token cost giống sync mode. Daemon không tiết kiệm token. Một BG session dùng cùng model với một sync session sẽ tiêu cùng token cho cùng task. Tôi chỉ tiết kiệm thời gian của mình, không tiết kiệm API budget.

Daemon process consume RAM. Mỗi BG session sống tốn 100-300 MB RAM (Node process plus context buffer). 12 agent batch consume khoảng 2-3 GB. MacBook M3 Max 32 GB của tôi không vấn đề. Ubuntu homelab 16 GB phải cẩn thận, đặc biệt khi chạy Docker stack song song.

Disk cho worktree. Mỗi worktree là một checkout .git riêng (share object store, nhưng working files duplicate). Repo 100 MB working files, 12 worktree là 1.2 GB extra. SSD nuốt được, nhưng cứ giả định 10x working size cho batch lớn.

Latency dispatch. Spawn 12 agent song song mất khoảng 5-10 giây (CC phải tạo worktree, init context, fetch tool list). Không phải instant. Với batch lớn hơn (30 plus agent) có thể mất 30 giây setup. Sau đó tool loop chạy đều.

Latency reconnect. Reconnect SSH plus claude agents từ phone mất 1-2 giây để daemon trả roster. Acceptable. Tốt hơn tmux ls plus attach plus scan từng pane.

Pitfall đã gặp

Sáu pitfall tôi vấp khi mới chuyển sang BG mode. Tôi liệt kê chính xác chúng đây để bạn không phải vấp lại.

Pitfall 1: parent session vẫn idle khi tất cả agent done. Sync mode session sẽ tự kết thúc khi task xong. BG mode session ở trạng thái idle, daemon giữ chờ task tiếp theo. Nếu bạn quên kill, sau một tuần FleetView của bạn sẽ có 30 plus session idle, mỗi cái consume RAM. Cleanup định kỳ: claude agents, focus session idle cũ, nhấn k để kill. Hoặc gõ claude daemon prune (CLI mới có từ 2.1.144) tự dọn session idle quá X ngày.

Pitfall 2: agent push branch nhưng forget PR. Nếu bạn brief agent prompt “commit và push”, agent push xong dừng. PR không tự mở. Phải brief rõ ràng “commit, push, gh pr create”. Tôi từng spawn 10 agent, sau một giờ thấy 10 branch trên remote, không có PR nào. Phải lặp lại task tạo PR thủ công.

Pitfall 3: parent HEAD không khớp ý định. Agent worktree base trên parent HEAD lúc spawn (qua hook). Nếu trước khi spawn bạn vừa pull origin/main, agent thấy main mới. Nếu bạn đang ở feature branch có 5 commit chưa push, agent thấy 5 commit đó. Ý định của bạn là “agent dựa trên feature work của tôi” hay “agent dựa trên main sạch sẽ”? Quyết định trước khi spawn, đừng để hook quyết hộ.

Pitfall 4: shared resource collision. Tôi spawn 8 agent viết 8 bài blog. Mỗi agent đọc memory bằng seriesOrder để biết mình là bài thứ mấy trong series. Tất cả 8 agent cùng đọc cùng lúc, cùng pick seriesOrder: 9 (số rảnh kế tiếp), kết quả là 8 bài cùng order 9. Race condition do shared “next available” resource. Fix: pre-allocate trong prompt mỗi agent (“you are agent 3, your seriesOrder is 11”), không để agent tự pick.

Pitfall 5: token budget cắt giữa batch. Spawn 12 agent song song với plan 90 phút. Tới phút 60, account budget hit 5h limit (đặc biệt với plan Pro). Mọi agent đang chạy bị cắt giữa chừng. Một số đã commit, một số chưa. Recovery rất đau. Mitigation: kiểm tra /cost hoặc claude usage trước khi spawn batch lớn. Nếu budget còn ít, dùng Sonnet thay Opus, hoặc giảm batch size.

Pitfall 6: worktree không tự clean sau merge. Sau khi PR merge, worktree branch và folder vẫn còn. Khác với CC desktop tự cleanup, CLI worktree giữ lại. Tôi phải định kỳ git worktree prune plus git branch -d worktree/<slug> sau khi merge. Setting cleanupPeriodDays chỉ áp dụng cho worktree subagent crash, không áp dụng cho worktree user create.

Cheatsheet

Bảng tổng hợp lệnh thường dùng:

Tác vụLệnh
Vào BG từ session đang chạy/bg trong prompt
Start BG ngay từ shellclaude --bg "<prompt>"
Spawn BG batch programmaticParent session call Agent tool với run_in_background: true
Xem list sessionclaude agents
Xem list session theo projectclaude agents --cwd ~/WORK/blog
Attach session bằng IDclaude attach <short-id>
Kill session đang focus trong UIk trong FleetView
Cleanup session idle cũclaude daemon prune
Xem JSON roster cho automationclaude agents --json
Resume bao gồm BGclaude --resume (list cả foreground và bg)

Setting cần biết:

SettingDefaultKhi nào đổi
worktree.bgIsolation(worktree on)"none" khi submodule lớn hoặc cache nặng
worktree.baseRefhead (theo hook user)Giữ head để agent thấy uncommitted work
cleanupPeriodDays7Tăng nếu cần giữ worktree subagent lâu hơn

Source detection trong FleetView:

SourceCách spawn
cliclaude trực tiếp trong terminal
bgclaude --bg ... từ shell
fleetDispatch từ FleetView prompt input hoặc Agent tool
slashRemoteTrigger, Telegram bridge, scheduled job

So sánh với async coding agent khác

Một câu hỏi tôi nhận được nhiều: BG mode CC khác Cursor BG agent thế nào? Khác Devin thế nào?

Khác biệt cốt lõi là deploy model. CC BG chạy local trên máy của bạn, daemon là một process Node trên máy. Cursor BG agent chạy trên cloud sandbox của Cursor, repo bị clone lên cloud, agent edit trong sandbox đó, PR tự push về repo của bạn. Devin tương tự, cloud sandbox riêng plus một desktop view ảo.

Trade-off:

CC BG (local daemon): ưu là dữ liệu không rời máy, có thể chạy với LLM local nếu cấu hình, không phải lo về kích thước repo gốc upload. Nhược là tốn RAM/disk máy bạn, không xem được session từ máy khác (mỗi máy có FleetView riêng, không sync).

Cursor/Devin (cloud sandbox): ưu là không tốn tài nguyên local, có thể xem từ phone/web, sandbox isolation thật sự (network egress bị giới hạn). Nhược là repo bị clone lên cloud của họ, phụ thuộc availability của service, không dùng được với private LLM.

Tôi dùng cả hai. CC BG cho project local của tôi, batch nhiều agent. Cursor BG cho task nhỏ, single-agent, lúc tôi đang ở phone và muốn dispatch nhanh không cần SSH vào homelab. Mỗi tool cho một niche.

Series này bài 3 sẽ deep dive Cursor BG. Bài 4 sẽ là Devin plus Replit. Bài 5 là tradeoff matrix tổng hợp khi nào dùng BG, khi nào sync.

Bước tiếp

Bài 3 của series sẽ deep dive Cursor BG agent: cùng concept (async coding agent) nhưng model deploy khác (cloud sandbox, không phải local daemon). So sánh trade-off, cost model, integration với GitHub PR flow.

Nếu bạn đang dùng Claude Code sync mode và chưa thử /bg, một lần thử là đủ để thấy khác biệt. Đặc biệt nếu workflow của bạn có bất kỳ phần nào là “spawn task, đi làm việc khác, quay lại check”. Đó là use case BG mode được thiết kế cho.

Tôi sẽ không nói BG mode thay thế hoàn toàn sync mode. Có những task tôi vẫn ngồi nhìn từng tool call: debug production, hỏi kiến trúc, review code commit. Nhưng với task có thể delegate plus đi xa plus quay về xem kết quả, BG mode tiết kiệm thời gian thật.


Bài thuộc series Background Agents 2026. Series plan tại bài 1: anatomy của async coding.