Năm ngoái, tôi đã viết về sự trỗi dậy của các subagent và lý do tại sao việc tách biệt các tác vụ vào những agent tập trung—với ngữ cảnh, công cụ và chỉ dẫn riêng—lại giúp cải thiện độ tin cậy. Bài viết đó đã đề cập đến việc subagent là gì và tại sao bạn nên cần chúng. Kể từ đó, các mô hình ngôn ngữ đã trở nên giỏi hơn trong việc lập kế hoạch, sử dụng công cụ và thực hiện các chỉ dẫn đa bước. Điều này mở ra những mô hình mà trước đây vốn quá mong manh: các agent quản lý một nhóm nhân viên thường trực, hoặc các agent nhắn tin trực tiếp cho nhau.
Dưới đây là bốn mô hình, được sắp xếp theo mức độ kiểm soát của agent chính (main agent) đối với vòng đời của subagent.
1. Inline Tool: Subagent đóng vai trò như một lời gọi hàm (Function Call)
Đây là mô hình đơn giản nhất. Agent chính gọi một công cụ (tool) để khởi tạo một subagent và nhận kết quả trả về như một phản hồi của công cụ đó. Từ góc nhìn của agent chính, việc gọi một subagent hoàn toàn giống với việc gọi các hàm như
read_file hay run_command.
Agent chính sở hữu một công cụ, ví dụ:
call_agent. Nó gửi đi một nhiệm vụ và nhận lại kết quả. Subagent chạy trong ngữ cảnh riêng với các công cụ và chỉ dẫn riêng của nó. Agent chính không bao giờ quản lý trực tiếp vòng đời của subagent.
[
{ "role": "user", "content": "Kiểm tra PR #42 xem có lỗi bảo mật không" },
{ "role": "assistant", "tool_calls": [
{ "name": "call_agent", "arguments": { "task": "Kiểm tra PR #42 về bảo mật. Tập trung vào các thay đổi xác thực.", "agent": "security-reviewer", "tools": ["read_file", "search_code"] } }
]},
{ "role": "tool", "name": "call_agent", "content": "Tìm thấy 2 lỗi: 1) Auth token không được xác thực ở endpoint /api/users. 2) Session cookie thiếu cờ HttpOnly trong hàm xử lý đăng nhập." },
{ "role": "assistant", "content": "Kết quả kiểm tra phát hiện 2 vấn đề bảo mật..." }
]
Mô hình này có sự phân biệt giữa đồng bộ (sync) và bất đồng bộ (async) ở cấp độ công cụ, bởi vì bản thân công cụ có thể chọn chặn (block) hoặc phản hồi ngay lập tức:
- Đồng bộ (như ví dụ trên): Lời gọi công cụ sẽ chặn tiến trình. Lượt của agent chính bị tạm dừng cho đến khi subagent hoàn thành. Kết quả trả về như một phản hồi công cụ thông thường.
- Bất đồng bộ: Công cụ trả về ngay lập tức một ID của agent. Kết quả của subagent được đưa vào cuộc hội thoại dưới dạng một thông báo (notification) khi nó kết thúc. Agent chính có thể thực hiện các cuộc gọi công cụ khác trước khi kết quả đó đến.
[
{ "role": "user", "content": "Review PR #42 và kiểm tra cả log của CI nữa" },
{ "role": "assistant", "tool_calls": [
{ "name": "call_agent", "arguments": { "task": "Review PR #42 về lỗi bảo mật", "agent": "security-reviewer", "background": true } },
{ "name": "read_file", "arguments": { "path": "ci/logs/latest.txt" } }
]},
{ "role": "tool", "name": "call_agent", "content": "agent_id: agent-x1. Đang chạy trong nền." },
{ "role": "tool", "name": "read_file", "content": "Build thành công. 142 bài test vượt qua." },
{ "role": "assistant", "content": "CI có vẻ ổn. Đang chờ kết quả review bảo mật..." },
{ "role": "user", "content": "<notification agent_id='agent-x1' status='completed'>Tìm thấy 2 lỗi: ...</notification>" },
{ "role": "assistant", "content": "Review bảo mật đã tìm thấy 2 lỗi..." }
]
Trong chế độ đồng bộ, phản hồi công cụ chứa kết quả. Trong chế độ bất đồng bộ, phản hồi công cụ chứa một ID, và kết quả sẽ đến sau đó dưới dạng một tin nhắn người dùng được chèn vào.
Khi nào nên dùng: Các công việc độc lập. Tra cứu nghiên cứu, review code, phân tích tệp, tạo test case. Hầu hết các trường hợp sử dụng subagent đều bắt đầu và duy trì ở mô hình này.
Hạn chế: Hoạt động với bất kỳ mô hình nào hỗ trợ sử dụng công cụ (tool use), bao gồm cả các mô hình nhỏ và rẻ hơn. Tuy nhiên, không có cách nào để gửi chỉ dẫn tiếp theo, kiểm tra tiến độ hoặc hủy bỏ sớm. Nếu subagent hiểu sai nhiệm vụ, bạn chỉ biết được khi kết quả đã trả về.
2. Fan-Out: Khởi tạo hàng loạt Agent và chờ kết quả
Agent chính khởi tạo các subagent và thu thập kết quả bằng công cụ
wait_agent. Khác với mô hình Inline Tool, việc khởi tạo và thu thập là hai bước riêng biệt. spawn_agent luôn trả về ngay lập tức. Mô hình sẽ quyết định việc cần làm tiếp theo: gọi wait_agent ngay, tự thực hiện công việc của mình trước, hoặc khởi tạo thêm nhiều agent khác rồi mới bắt đầu chờ.
Hai công cụ:
spawn_agent điều phối công việc và trả về ID ngay lập tức. wait_agent sẽ chặn (block) tiến trình cho đến khi một hoặc nhiều agent hoàn thành.Mô hình (model) sẽ kiểm soát trình tự thực hiện. Nó có thể khởi tạo các agent rồi gọi wait_agent ngay lập tức (tương đương với chạy đồng bộ), hoặc xen kẽ các công việc riêng của nó trước:
[
{ "role": "user", "content": "Tái cấu trúc (refactor) module auth và cập nhật tất cả các bài test liên quan" },
{ "role": "assistant", "tool_calls": [
{ "name": "spawn_agent", "arguments": { "task": "Refactor module auth để sử dụng JWT tokens", "agent": "backend-engineer" } },
{ "name": "spawn_agent", "arguments": { "task": "Cập nhật tất cả các bài test liên quan đến auth cho luồng JWT mới", "agent": "test-writer" } },
{ "name": "read_file", "arguments": { "path": "docs/auth-spec.md" } }
]},
{ "role": "tool", "name": "spawn_agent", "content": "Đã khởi tạo agent-a (backend-engineer). Đang chạy." },
{ "role": "tool", "name": "spawn_agent", "content": "Đã khởi tạo agent-b (test-writer). Đang chạy." },
{ "role": "tool", "name": "read_file", "content": "# Auth SpecnJWT tokens với RS256..." },
{ "role": "assistant", "content": "Tôi đã bắt đầu cả hai tác vụ. Tài liệu kỹ thuật auth xác nhận chúng ta nên sử dụng RS256." },
{ "role": "assistant", "tool_calls": [{ "name": "wait_agent", "arguments": { "timeout": 300 } }]},
{ "role": "tool", "name": "wait_agent", "content": "2 agent đã hoàn thành:n- agent-a: Đã refactor 3 tệp (auth.ts, middleware.ts, config.ts)n- agent-b: Đã cập nhật 12 bài test, tất cả đều vượt qua" },
{ "role": "assistant", "content": "Cả hai tác vụ đã xong. Module auth hiện đã sử dụng JWT..." }
]
Mô hình đã khởi tạo hai agent và đọc một tệp trong cùng một lượt gọi công cụ, sau đó mới quyết định chờ đợi. Một mô hình khác có thể gọi
wait_agent ngay sau khi khởi tạo, hoặc thực hiện vài lượt đọc tệp trước. Giao diện công cụ là như nhau; chính mô hình sẽ quyết định việc đan xen các bước.Sự khác biệt so với Mô hình 1: Mô hình kiểm soát khi nào thì thu thập kết quả. Nó có thể chạy song song 5 agent, tự thực hiện các lượt đọc tệp của mình, rồi mới gọi
wait_agent. wait_agent đóng vai trò như một hòm thư chung, "thức tỉnh" khi subagent hoàn thành và trả về tất cả kết quả đang có sẵn.Khi nào nên dùng: Khi có nhiều tác vụ độc lập có thể chạy đồng thời. Agent chính không cần kết quả trung gian từ subagent này để bắt đầu subagent kia.
Hạn chế: Mô hình cần quyết định chính xác khi nào nên chờ. Nếu mô hình gọi
wait_agent ngay sau mỗi lần khởi tạo thì sẽ không mang lại lợi ích gì so với Mô hình 1. Giá trị của mô hình này phụ thuộc vào khả năng đan xen công việc riêng giữa bước khởi tạo (spawn) và bước chờ (wait). Kết quả được thu thập hàng loạt thông qua wait_agent, vốn trả về tất cả đầu ra của các agent đã hoàn thành kể từ lần gọi cuối cùng. Đây vẫn là kiểu "gửi rồi quên" (fire-and-forget): không có cách nào để gửi chỉ dẫn bổ sung hoặc điều chỉnh hướng đi cho subagent giữa chừng.3. Agent Pool: Nhóm Agent thường trực với hệ thống nhắn tin
Agent chính khởi tạo các subagent có vòng đời dài (long-lived) và giao tiếp với chúng thông qua các tin nhắn. Các agent này vẫn tồn tại duy trì qua các lần tương tác. Agent chính có thể gửi chỉ dẫn tiếp theo, kiểm tra trạng thái và điều phối công việc giữa các agent.
Mô hình này yêu cầu một giao diện công cụ phong phú hơn:
spawn_agent, send_message, wait_agent, list_agents, kill_agent.
Khác với Mô hình 2, các agent ở đây có trạng thái (stateful) và tính tương tác. Agent chính gửi một tin nhắn, nhận phản hồi, rồi lại gửi tiếp một tin nhắn khác cho chính agent đó. Subagent sẽ lưu giữ toàn bộ lịch sử hội thoại của nó. Điều này hỗ trợ các quy trình làm việc đa bước (multi-step workflows), nơi agent chính đóng vai trò điều phối giữa các chuyên gia.
Mô hình (model) sẽ kiểm soát việc nó làm việc với từng agent một (tuần tự) hay gửi tin nhắn cho nhiều agent giữa các khoảng chờ (song song). Nó có thể gửi yêu cầu cho cả người nghiên cứu (researcher) và người viết (writer) cùng lúc, hoặc chờ người nghiên cứu hoàn thành trước:
[
{ "role": "user", "content": "Viết một bài blog về hiệu suất của WebAssembly" },
{ "role": "assistant", "tool_calls": [
{ "name": "spawn_agent", "arguments": { "role": "researcher", "tools": ["web_search", "read_url"] }},
{ "name": "spawn_agent", "arguments": { "role": "writer", "tools": ["write_file", "read_file"] }}
]},
{ "role": "tool", "name": "spawn_agent", "content": "Đã khởi tạo agent-r (researcher)" },
{ "role": "tool", "name": "spawn_agent", "content": "Đã khởi tạo agent-w (writer)" },
{ "role": "assistant", "tool_calls": [
{ "name": "send_message", "arguments": { "to": "agent-r", "message": "Nghiên cứu các bài đo hiệu năng (benchmark) của WebAssembly. Tìm 5 nguồn có con số cụ thể." } },
{ "name": "send_message", "arguments": { "to": "agent-w", "message": "Phác thảo cấu trúc bài blog về hiệu suất WebAssembly. Gồm 4 phần." } }
]},
{ "role": "tool", "name": "send_message", "content": "Đã chuyển đến agent-r" },
{ "role": "tool", "name": "send_message", "content": "Đã chuyển đến agent-w" },
{ "role": "assistant", "tool_calls": [{ "name": "wait_agent", "arguments": {} }]},
{ "role": "tool", "name": "wait_agent", "content": "agent-w phản hồi: Đã xong phác thảo: giới thiệu, benchmark, thực tế, kết luận" },
{ "role": "assistant", "tool_calls": [{ "name": "wait_agent", "arguments": {} }]},
{ "role": "tool", "name": "wait_agent", "content": "agent-r phản hồi: Tìm thấy 5 nguồn: 1) Benchmark từ Chrome cho thấy tốc độ gấp 1.2 lần so với native..." },
{ "role": "assistant", "tool_calls": [{ "name": "send_message", "arguments": { "to": "agent-w", "message": "Điền nội dung vào phác thảo dựa trên các nguồn này: 1) Benchmark từ Chrome..." } }]},
{ "role": "tool", "name": "send_message", "content": "Đã chuyển đến agent-w" },
{ "role": "assistant", "tool_calls": [{ "name": "wait_agent", "arguments": {} }]},
{ "role": "tool", "name": "wait_agent", "content": "agent-w phản hồi: Đã xong bản thảo. Tiêu đề: 'WebAssembly nhanh hơn bạn tưởng'..." },
{ "role": "assistant", "tool_calls": [{ "name": "send_message", "arguments": { "to": "agent-r", "message": "Hãy kiểm chứng (fact-check) bản thảo này dựa trên các nguồn của bạn: 'WebAssembly nhanh hơn bạn tưởng'..." } }]},
{ "role": "tool", "name": "send_message", "content": "Đã chuyển đến agent-r" },
{ "role": "assistant", "tool_calls": [{ "name": "wait_agent", "arguments": {} }]},
{ "role": "tool", "name": "wait_agent", "content": "agent-r phản hồi: Có 2 điểm cần sửa: đoạn 3 ghi tốc độ gấp 2 lần nhưng nguồn ghi 1.5 lần..." },
{ "role": "assistant", "tool_calls": [
{ "name": "send_message", "arguments": { "to": "agent-w", "message": "Áp dụng các chỉnh sửa sau: đoạn 3 nên ghi 1.5 lần thay vì 2 lần..." } },
{ "name": "kill_agent", "arguments": { "agent_id": "agent-r" }}
]},
{ "role": "tool", "name": "send_message", "content": "Đã chuyển đến agent-w" },
{ "role": "tool", "name": "kill_agent", "content": "agent-r đã bị chấm dứt" },
{ "role": "assistant", "content": "Đây là bài viết hoàn chỉnh cuối cùng..." }
]
agent-r được nhắn tin hai lần (nghiên cứu, sau đó là kiểm chứng) và nó vẫn giữ được các nguồn dữ liệu từ tác vụ đầu tiên. Mô hình đã gửi việc cho cả hai agent song song lúc bắt đầu, sau đó điều phối tuần tự cho bước kiểm chứng. Một mô hình khác có thể thực hiện mọi thứ theo trình tự nối tiếp (serialize). Các công cụ là giống nhau; chính mô hình điều khiển việc dàn dựng (orchestration).Kết quả trả về theo từng tin nhắn thông qua
wait_agent. Mỗi lần gọi wait_agent sẽ trả về phản hồi tiếp theo của agent, giúp agent chính xử lý kết quả theo kiểu tăng tiến (incremental) và có thể điều chỉnh tin nhắn tiếp theo dựa trên những gì nhận được.Khi nào nên dùng: Các quy trình làm việc đa bước cần các agent cộng tác với nhau. Agent chính đóng vai trò luân chuyển thông tin giữa các chuyên gia.
Hạn chế: Agent chính phải theo dõi trạng thái của nhiều agent, quyết định khi nào gửi tiếp tin nhắn thay vì chờ đợi, và điều phối thông tin chính xác. Các mô hình nhỏ dễ bị mất dấu việc agent nào có ngữ cảnh nào, hoặc quên gọi
kill_agent khi xong việc. Các mô hình hàng đầu (frontier models) có thể xử lý tốt khoảng 2-4 agent.4. Teams: Các Agent tự trò chuyện với nhau
Các agent nhắn tin trực tiếp cho nhau mà không cần thông qua agent chính. Agent chính chỉ thiết lập đội ngũ (team), định nghĩa các vai trò rồi lùi về phía sau. Các agent tự điều phối với nhau bằng cách nhắn tin trực tiếp hoặc qua một hòm thư chung.
Giao diện công cụ bao gồm khả năng định danh chéo giữa các agent. Mỗi agent đều được cấp công cụ
send_message trong bộ công cụ riêng của nó và có thể nhắn tin cho các agent khác theo tên hoặc đường dẫn.Từ góc nhìn của agent chính, cuộc hội thoại diễn ra rất ngắn gọn. Nó chỉ thiết lập đội ngũ, kích hoạt người lập kế hoạch (planner) và chờ đợi báo cáo cuối cùng. Mô hình có thể chọn cách chờ đợi trong im lặng hoặc định kỳ kiểm tra tiến độ bằng lệnh
list_agents:
[
{ "role": "user", "content": "Xây dựng tính năng X" },
{ "role": "assistant", "tool_calls": [
{ "name": "spawn_agent", "arguments": { "role": "planner", "tools": ["read_file", "search_code", "send_message"] } },
{ "name": "spawn_agent", "arguments": { "role": "implementer", "tools": ["read_file", "write_file", "run_command", "send_message"] } },
{ "name": "spawn_agent", "arguments": { "role": "reviewer", "tools": ["read_file", "search_code", "send_message"] } }
]},
{ "role": "tool", "name": "spawn_agent", "content": "Đã khởi tạo agent-p (planner)" },
{ "role": "tool", "name": "spawn_agent", "content": "Đã khởi tạo agent-i (implementer)" },
{ "role": "tool", "name": "spawn_agent", "content": "Đã khởi tạo agent-v (reviewer)" },
{ "role": "assistant", "tool_calls": [{ "name": "send_message", "arguments": { "to": "agent-p", "message": "Lập kế hoạch và điều phối tính năng X. Đồng đội của bạn: agent-i (implementer), agent-v (reviewer). Hãy nhắn tin trực tiếp cho họ. Báo cáo lại cho tôi khi hoàn thành." } }]},
{ "role": "tool", "name": "send_message", "content": "Đã chuyển đến agent-p" },
{ "role": "assistant", "tool_calls": [{ "name": "wait_agent", "arguments": { "timeout": 60 } }]},
{ "role": "tool", "name": "wait_agent", "content": "Hết thời gian chờ. Không có agent nào phản hồi." },
{ "role": "assistant", "tool_calls": [{ "name": "list_agents", "arguments": {} }]},
{ "role": "tool", "name": "list_agents", "content": "agent-p: đang chạy, agent-i: đang chạy, agent-v: đang rảnh (idle)" },
{ "role": "assistant", "tool_calls": [{ "name": "wait_agent", "arguments": { "timeout": 120 } }]},
{ "role": "tool", "name": "wait_agent", "content": "agent-p phản hồi: Tính năng X đã xong. 3 tệp thay đổi, tất cả bài test đều vượt qua. Tóm tắt: ..." },
{ "role": "assistant", "content": "Tính năng X đã được xây dựng xong. Đây là những gì đã thực hiện..." }
]
Việc planner nhắn tin cho implementer, implementer gửi mã cho reviewer, reviewer yêu cầu sửa lỗi — tất cả đều diễn ra bên trong các cuộc hội thoại riêng của các agent đó. Ngữ cảnh của agent chính vẫn hoàn toàn sạch sẽ. Mô hình đã tự kiểm tra bằng
list_agents sau khi hết thời gian chờ (timeout). Nó cũng có thể gọi wait_agent mà không cần thời gian chờ để chặn tiến trình cho đến khi hoàn thành.Agent chính chỉ thu thập kết quả khi một agent nhắn tin ngược lại một cách rõ ràng (ở đây là planner báo cáo hoàn tất). Mọi thứ khác đều vô hình với nó. Các cách triển khai hệ thống định danh chéo rất đa dạng: đường dẫn phân cấp (/root/planner đến /root/implementer), hòm thư dựa trên tệp, hoặc các giao thức IPC.
Khi nào nên dùng: Các tác vụ lớn, nơi logic điều phối vượt quá khả năng quản lý từng bước của một agent đơn lẻ.
Hạn chế: Mọi agent trong đội ngũ đều cần năng lực của các mô hình hàng đầu (frontier-class), chứ không chỉ mình agent chính. Mỗi thành viên phải tự quyết định khi nào nhắn tin cho đồng đội, bao gồm những gì và khi nào báo cáo lại. Các mô hình có thể nhắn nhầm agent, quên báo cáo hoàn thành hoặc bị kẹt trong vòng lặp. Ngoài năng lực mô hình, còn có các thách thức về hạ tầng: phát hiện vòng lặp (A đợi B, B đợi A), giải quyết xung đột (hai agent cùng sửa một tệp) và điều phối việc tắt hệ thống. Việc gỡ lỗi cũng rất khó vì các chuỗi tin nhắn giữa các agent khó truy vết và lỗi thường kéo theo hiệu ứng dây chuyền.
Lựa chọn mô hình phù hợp
|Mô hình | Công cụ chính | Vai trò Agent chính | Vòng đời Agent |
|------|-----|-----|
| Inline Tool |
call_agent | Người gọi (Caller) | Tác vụ đơn lẻ || Fan-Out |
spawn_agent, wait_agent | Người điều phối (Dispatcher) | Tác vụ đơn lẻ || Agent Pool | spawn, send, wait, list, kill | Người phối hợp (Coordinator) | Đa lượt tương tác |
| Teams | Tất cả trên + nhắn tin chéo | Người giám sát (Supervisor) | Thường trực |
- Hãy bắt đầu với Mô hình 1. Hầu hết các tác vụ mà bạn cảm thấy cần hệ thống đa agent thực chất đều hoạt động tốt với một lời gọi công cụ inline được viết prompt kỹ lưỡng.
- Chuyển sang Mô hình 2 cho những công việc song song thực sự độc lập.
- Chuyển sang Mô hình 3 khi các agent cần cộng tác qua nhiều bước.
- Mô hình 4 chỉ dành cho khi logic điều phối vượt quá tầm quản lý của một agent duy nhất.
Mỗi cấp độ tăng lên đều đòi hỏi một mô hình có năng lực mạnh hơn. Mô hình 1 chạy được với mọi model biết gọi tool. Mô hình 2 cần model biết lập luận khi nào nên chờ. Mô hình 3 cần model theo dõi được trạng thái của nhiều agent qua các lượt. Mô hình 4 cần các model mạnh nhất cho mọi agent thành viên. Nếu dùng model nhỏ hoặc rẻ, hãy trung thành với Mô hình 1 hoặc 2.
Cách thu thập kết quả cũng thay đổi theo từng mô hình. Mô hình 1 trả về kết quả trực tiếp. Mô hình 2 gom nhóm kết quả qua wait_agent. Mô hình 3 cung cấp kết quả theo kiểu tăng tiến. Mô hình 4 chỉ hiển thị những gì các agent báo cáo lại; mọi thứ khác đều nằm trong các cuộc hội thoại nội bộ.
Đối với các mô hình từ 2 đến 4,
spawn_agent luôn trả về ngay lập tức và wait_agent sẽ thu thập kết quả khi mô hình quyết định gọi nó. Framework cung cấp công cụ, còn mô hình sẽ kiểm soát việc dàn dựng. Một tác vụ hôm nay cần 4 agent phối hợp thì ngày mai có thể được giải quyết chỉ bởi một agent duy nhất với một mô hình tốt hơn.Nguồn bài viết từ Tác giả Phil Schmid