Claude Code: khi nào dùng fork, khi nào dùng fresh subagent
Fork subagent trong Claude Code kế thừa toàn bộ context của agent cha ×N — nếu bạn đã viết contract doc rồi thì đó là trả tiền hai lần cho cùng một context. Cách chọn, và một PreToolUse hook để tự nhắc mình.
Claude Code: khi nào dùng fork, khi nào dùng fresh subagent
Trả lời ngắn: Trong Claude Code, fork tạo một subagent kế thừa toàn bộ conversation context của agent cha (mọi message, file, tool result tới thời điểm fork). Một fresh subagent bắt đầu với context rỗng và chỉ đọc những gì bạn giao. Nếu bạn đã viết một contract/brief doc chứa mọi quyết định quan trọng, hãy dùng fresh subagent đọc doc đó — đừng fork. Fork kế thừa context lớn ×N worker, tốn token, và trong tình huống này gần như không mua thêm gì mà doc chưa có. Chỉ fork khi chính phần context được kế thừa mới là giá trị, và nó quá giàu nuance để viết ra doc.
Dưới đây là câu chuyện thật khiến mình rút ra rule này, và cách mình cài một guardrail ngay trong Claude Code để không lặp lại.
Chuyện gì đã xảy ra?
Mình đang làm một website template gồm 5 trang. Để chạy nhanh hơn, mình chia 5 trang cho 5 subagent làm song song trong Claude Code.
Trước khi dispatch, mình viết một file CONTRACT.md khá kỹ: brand, voice, HTML chính xác của header và footer, danh sách CSS class, tên file ảnh và các quy ước chung. Những gì có thể làm 5 trang lệch nhau đều đã được gom vào một chỗ.
Sau đó mình vẫn dùng fork để tạo 5 worker.
Đây là chỗ mình trả tiền hai lần cho cùng một context.
File contract đã mang đủ signal cần thiết. Nhưng mỗi fork vẫn kế thừa toàn bộ conversation context của agent cha: mọi message, nội dung file đã paste, tool result và các trao đổi trước thời điểm fork. Nhân phần context lớn đó cho 5 worker, chi phí token tăng lên rõ rệt. Trong trường hợp này, phần kế thừa gần như không mua thêm được gì mà CONTRACT.md chưa có.
Fork và fresh subagent trong Claude Code khác nhau ở đâu?
Một fork bắt đầu công việc với toàn bộ context của agent cha. Có thể hiểu là nó thức dậy và đã “biết mọi thứ mình biết” tới thời điểm đó.
Điểm này hữu ích khi quyết định phụ thuộc vào một chuỗi trao đổi dài: design đã bàn nhiều vòng, các hướng đã bị loại, hoặc cảm giác tổng thể khó diễn đạt thành rule. Fork giữ được phần đó mà không cần mình viết lại.
Fresh agent thì bắt đầu với context rỗng. Nó chỉ biết những gì mình giao trong prompt và những file mình yêu cầu nó đọc. Nếu mình bảo nó đọc CONTRACT.md, mở file layout liên quan rồi làm một trang cụ thể, nó chỉ trả token cho phần contract, vài file cần thiết và phần output nó tạo ra.
Khác biệt không nằm ở việc fork mạnh hơn hay fresh agent giỏi hơn, mà ở nơi context được chứa. Nếu context đã được đóng gói tốt trong một tài liệu, fresh agent có đủ dữ liệu để làm đúng. Kế thừa thêm toàn bộ conversation chỉ tạo dữ liệu trùng, rồi bị tính N lần cho N worker.
Vậy nên chọn cái nào?
Rule mình dùng là: nếu shared contract hoặc brief doc đã chứa các quyết định load-bearing, dispatch fresh agents đọc doc. Đừng fork.
Contract cần giữ những gì ảnh hưởng trực tiếp tới kết quả: interface, tên file, cấu trúc markup, constraint, convention và tiêu chí hoàn thành. Mỗi agent đọc cùng một nguồn sự thật rồi làm phần việc riêng.
Fork đáng chi phí khi inherited context chính là giá trị: context quá giàu nuance để serialize mà không mất ý, và mình chủ động không viết handoff doc. Worker khi đó giữ được lý do đằng sau các quyết định, kể cả những dead-end đã bị loại.
Đây không phải lý do để tránh fork. Vấn đề là dùng nó sau khi mình đã làm luôn công việc của một contract.
Dấu hiệu dễ nhận ra nhất: vừa viết handoff doc rất kỹ, rồi lại chuẩn bị fork. Nếu doc đủ dùng, hãy tạo fresh agent. Nếu doc chưa mang được nuance quan trọng, hãy fork có chủ đích hoặc bổ sung doc cho đủ.
Làm sao để tự nhắc mình trước khi lỡ tay fork?
Quyết định này do agent orchestration đưa ra, nên mình thêm hai lớp nhắc ngay tại nơi nó làm việc.
Lớp đầu là một rule trong global ~/.claude/CLAUDE.md. Mỗi session đều đọc nó và đặt đúng câu hỏi trước khi dispatch: context nằm trong doc hay cần được kế thừa?
Lớp thứ hai là một PreToolUse hook cho Task tool trong ~/.claude/settings.json. Hook đọc .tool_input.subagent_type bằng jq. Nếu giá trị là fork, nó đổi permission decision thành ask, tạo confirmation prompt để mình xem lại: thật sự cần fork, hay fresh agent đọc doc là đủ?
Hook này là một cái thắng nhẹ, không phải lệnh cấm. Có những task nên fork, và mình vẫn có thể xác nhận để tiếp tục.
Claude Code có setting để tắt fork không?
Không. Claude Code không có built-in setting để tắt riêng fork. Permission matcher chỉ match theo tên tool, không match field trong tool input. Muốn gate đúng subagent_type == "fork", hook phải tự đọc .tool_input.subagent_type.
Cách này dựa trên một hook thật đã chạy ổn để chặn secret file, qua đó xác nhận .tool_input có thể được đọc bằng jq bên trong hook. Nếu phiên bản Claude Code của bạn không populate field này, command không trả ra quyết định nào và hook trở thành một no-op vô hại.
Khi delegate công việc, hãy quyết định context sống ở đâu: trong DOC hoặc trong INHERITANCE. Cả hai đều hợp lý. Trả tiền cho cả hai cùng lúc thường không hợp lý.
Cheatsheet
Để bạn paste cho agent của bạn.
Rule một dòng: Đã có contract/brief doc chứa context quan trọng thì dùng fresh agent đọc doc; chỉ fork khi chính inherited context là phần giá trị không thể đóng gói gọn mà không mất ý.
Rule cho ~/.claude/CLAUDE.md:
“fork vs fresh subagent: nếu đã có contract/brief doc chứa context quan trọng thì dispatch FRESH agents đọc doc — đừng fork. Fork kế thừa toàn bộ context của mình ×N (tốn), chỉ đáng khi context quá giàu để viết ra doc VÀ mình cố tình bỏ qua doc. Viết doc kỹ MÀ vẫn fork = trả tiền cho cùng context hai lần.”
PreToolUse Task hook cho ~/.claude/settings.json:
{
"matcher": "Task",
"timeout": 3,
"hooks": [
{
"type": "command",
"command": "st=$(jq -r '.tool_input.subagent_type // empty'); if [ \"$st\" = \"fork\" ]; then printf '{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"ask\",\"permissionDecisionReason\":\"fork inherits full context xN. Neu da co contract/brief doc thi dung fresh agent thay vi fork.\"}}\\n'; fi; exit 0"
}
]
}
Câu hỏi thường gặp
Fork trong Claude Code là gì? Fork tạo một subagent kế thừa toàn bộ conversation context của agent cha tới thời điểm fork — mọi message, file đã đọc, tool result. Nó bắt đầu “biết mọi thứ agent cha biết”, khác với một subagent thường bắt đầu với context rỗng.
Fork có tốn token hơn fresh subagent không? Có, thường là vậy. Fork nạp lại toàn bộ context của agent cha cho mỗi worker. Chạy N fork song song nghĩa là nhân phần context đó lên N lần. Fresh subagent chỉ trả token cho prompt, vài file nó đọc, và output.
Khi nào thì fork mới đáng dùng? Khi chính phần context được kế thừa là giá trị và quá giàu nuance để viết thành doc mà không mất ý — ví dụ một cuộc thảo luận design dài, các hướng đã loại, lý do đằng sau từng quyết định. Lúc đó fork rẻ hơn việc cố serialize tất cả ra tài liệu.
Có cách nào tắt hẳn fork trong Claude Code không?
Không có setting built-in để tắt riêng fork. Cách gate duy nhất là một PreToolUse hook trên Task tool đọc .tool_input.subagent_type và trả về permissionDecision: "ask" khi giá trị là fork.