10 design pattern cũ đang sống lại trong thế giới AI agent
23 Gang-of-Four design pattern tưởng là chuyện của một thời OOP. Nhưng khi nối agent, subagent, MCP, hook và tool-call thành workflow, mình vẫn đang dùng đúng những hình đó — object thành agent, method thành tool, application flow thành orchestration. Đây là những pattern map sạch nhất, và mấy cái map lỏng lẻo mình không cố kéo qua.
10 design pattern cũ đang sống lại trong thế giới AI agent
Mình từng nghĩ 23 Gang-of-Four design pattern là chuyện của một thời OOP: class, object, inheritance, mấy ví dụ cửa gỗ với tổng thống. Nhưng khi bắt đầu nối agent, subagent, MCP, hook và tool-call thành workflow, mình nhận ra mình vẫn đang dùng đúng những “hình” đó, chỉ là vật liệu đã đổi. Design pattern không biến mất; object được thay bằng agent, method được thay bằng tool, còn application flow trở thành orchestration.
Điều thú vị là mình không cần nhớ đủ 23 cái để thấy chúng hữu ích. Một số pattern map rất sạch sang agent system, tới mức nhìn ra tên của nó giúp mình thiết kế workflow gọn hơn ngay lập tức. Một số khác chỉ giống bề ngoài, cố kéo qua sẽ thành content cho đủ số hơn là một cách nghĩ có ích.
Nên đây không phải bài “23 design patterns phiên bản AI”. Mình chỉ chọn những pattern mà mình thiệt sự thấy xuất hiện khi build với agent.
Hóa ra một skill thường chỉ là Facade
Trong sách, Facade là một interface đơn giản nằm trước một subsystem phức tạp. Người dùng bấm nút nguồn, còn phía sau máy tính tự chạy một đống bước mà họ không cần biết.
Một skill hay slash-command tốt cũng làm đúng việc đó. Mình gọi /tony-play, nhưng phía sau nó có thể đọc session state, kiểm tra red flag, xác định hôm nay đang ở Plan, Do, Check hay Act, rồi dispatch sang workflow phù hợp. Người dùng không cần nhớ mười file nằm ở đâu, gọi tool nào trước, hay output bước này phải đưa vào bước nào tiếp theo.
Đó là lúc mình nhận ra skill không chỉ là một prompt được lưu lại. Nó là Facade của cả một subsystem gồm prompt, script, tool, rule và state. Nếu một slash-command vẫn bắt người dùng hiểu toàn bộ plumbing phía sau mới dùng được, Facade đó chưa làm xong việc của nó.
Adapter thì xuất hiện ngay bên cạnh. Một API bên ngoài có authentication, endpoint và response schema riêng; agent không tự nhiên biết nói chuyện với nó. MCP server bọc API đó lại thành những tool có tên, input và output mà agent hiểu được. Card reader ngày xưa nối thẻ nhớ với computer; MCP hôm nay nối một dịch vụ lạ với tool-calling interface.
Còn Proxy là lớp đứng trước tool để thêm quyền kiểm soát. Ví dụ fork-gate hook không tự làm công việc của subagent, nó chỉ đứng trước Task tool, đọc input rồi hỏi lại khi thấy subagent_type == "fork". Tool thật vẫn ở phía sau, nhưng mọi request phải đi qua một cái cổng có permission, logging hoặc policy. Khi cần audit, rate limit, secret protection hay human approval, mình đang nghĩ bằng Proxy dù không gọi tên nó.
Orchestration thật ra là bài toán chọn, giao và chuyển việc
Strategy là pattern dễ thấy nhất khi dùng nhiều model hoặc nhiều cách giải. Cùng một task “nghiên cứu rồi viết”, nhưng mình có thể chọn Claude cho phần đọc context dài, Codex cho phần sửa code, hoặc một fresh agent đọc contract thay vì fork toàn bộ context. Mục tiêu không đổi; strategy thực thi được thay dựa trên loại việc, chi phí và lượng context cần mang theo.
Điểm quan trọng là lựa chọn đó phải nằm ở một chỗ rõ ràng. Nếu mọi prompt tự quyết định model theo cảm tính, mình không có Strategy, mình chỉ có nhiều nhánh behavior rải rác. Một decision matrix kiểu “task cần inherited nuance thì fork, task đã có contract thì fresh agent” biến lựa chọn thành policy có thể kiểm tra và sửa.
Command xuất hiện khi orchestrator đóng gói một hành động thành task card hoặc tool-call rồi giao cho worker. Orchestrator không cần biết worker sẽ dùng shell, MCP hay đọc file nào; nó chỉ cần một contract đủ rõ:
Task: audit authentication flow
Done when: findings có path:line và test tái hiện được
Task này có thể được queue, retry, log, giao lại cho agent khác hoặc chờ dependency hoàn tất. Đó chính là giá trị của Command: tách người yêu cầu hành động khỏi nơi thực thi hành động. Prompt mơ hồ kiểu “xem giúp repo này” chưa phải Command tốt, vì nó chưa đóng gói được intent và completion criteria.
Chain of Responsibility lại phù hợp với routing. Một request đi qua các handler cho tới khi có handler nhận: câu hỏi về billing sang support agent, lỗi code sang debugging agent, thay đổi permission sang security review. Trong thực tế mình hay viết nó thành dispatch rule hoặc decision tree hơn là một chuỗi class, nhưng shape vẫn giống nhau.
Pattern này hữu ích khi không có một orchestrator khổng lồ phải hiểu mọi domain. Mỗi handler chỉ cần biết “việc này có thuộc phần mình không?”. Tuy nhiên phải có điểm dừng; nếu agent nào cũng được quyền đẩy tiếp mà không ai chịu trách nhiệm cuối cùng, Chain of Responsibility rất dễ biến thành vòng chuyền việc đẹp mắt nhưng không có output.
Một agent và một team đôi khi nên nhìn giống nhau
Composite giúp orchestrator đối xử với một worker và một nhóm worker qua cùng một interface. Nó giao task, chờ kết quả, nhận status và đọc output. Bên dưới có thể là một agent duy nhất, hoặc một fan-out gồm ba agent nghiên cứu độc lập rồi một agent synthesis.
Điều này không có nghĩa single agent và team có cùng chi phí hay cùng failure mode. Chúng chỉ có chung “hình dáng” ở boundary. Nhờ vậy orchestrator không cần viết lại toàn bộ flow mỗi khi mình đổi từ một worker sang một team.
Mediator giải quyết phần còn lại: subagent không tự nhắn lung tung cho nhau, mà giao tiếp qua orchestrator. Worker A trả findings, orchestrator quyết định phần nào chuyển cho worker B, rồi tự reconcile kết quả. Cách này giảm coupling và giữ một nơi chịu trách nhiệm cho quyết định cuối cùng.
Mình thấy Mediator đặc biệt quan trọng khi chạy song song. Cho các agent nói chuyện trực tiếp nghe có vẻ autonomous hơn, nhưng rất nhanh sẽ khó biết ai đã quyết định gì, context nào là canonical, và conflict được giải ở đâu. Orchestrator không nhất thiết phải làm hết việc; nó phải giữ được đường đi của thông tin.
Hook và pipeline cũng là những pattern cũ
Observer là hook hoặc event listener. Khi state đổi, một subscriber được báo: tool sắp chạy thì PreToolUse hook kiểm tra permission, task hoàn tất thì gửi notification, file thay đổi thì trigger validation. Producer không cần biết subscriber sẽ log, chặn, báo Slack hay cập nhật dashboard.
Nhưng mình phải phân biệt notification với orchestration. Observer rất hợp cho side effect và phản ứng độc lập; nó không lý tưởng cho một chuỗi bước bắt buộc phải chạy đúng thứ tự. Nếu bước build chỉ được chạy sau khi PRD đã approved, dependency đó nên nằm trong workflow rõ ràng, không nên hy vọng một event nào đó sẽ tự nối mọi thứ lại.
Chỗ đó hợp với Template Method hơn. Mình có thể cố định skeleton:
discovery → PRD → task cards → build → verify
Mỗi project điền implementation khác nhau cho từng bước, nhưng không được bỏ qua contract hoặc verification. Trong agent pipeline, “subclass” không còn nhất thiết là class; nó có thể là một skill, prompt template hay agent chuyên môn được cắm vào một stage.
Template Method giúp workflow có kỷ luật, nhưng skeleton càng dài thì càng dễ giả định sai. Có project không cần fan-out, có project cần security review trước build. Mình giữ phần xương đủ chặt để bảo vệ chất lượng, còn những bước phụ nên được cấu hình thay vì đóng đinh.
Không phải pattern nào cũng cần kéo sang agent
Nói thiệt, mình không thấy lợi ích khi cố map đủ 23 pattern. Flyweight có thể được ví với shared context hoặc cache, nhưng economics của token và context window không giống memory sharing trong object system. Prototype có thể giống clone một agent config, nhưng phần quan trọng của agent thường nằm trong runtime context, tool access và state chứ không chỉ trong bản sao ban đầu.
Memento nghe giống checkpoint và resume, nhưng muốn gọi đúng pattern thì phải thật sự capture rồi restore được state, không phải chỉ lưu một transcript. Visitor còn lỏng hơn nữa; mình chưa thấy analogy nào làm workflow agent sáng rõ hơn thay vì chỉ đổi tên khái niệm. Ngay cả Singleton được ví thành “một canonical config” hay “một collection duy nhất” cũng chủ yếu là lời nhắc về single source of truth, chưa chắc là Singleton theo nghĩa kỹ thuật.
Với mình, giá trị của GoF không nằm ở việc gắn đủ nhãn. Nó cho mình một bộ từ vựng để nhận ra coupling đang nằm ở đâu: interface có đang che complexity không, policy có tách khỏi execution không, worker có phụ thuộc trực tiếp vào nhau không, và state change đang được truyền đi bằng cách nào. Khi tên pattern làm câu hỏi thiết kế rõ hơn thì giữ; khi nó chỉ làm diagram trông thông minh hơn thì bỏ.
Câu hỏi thường gặp
Có cần học đủ 23 GoF pattern trước khi build agent system không?
Không. Mình sẽ bắt đầu với Facade, Strategy, Command, Adapter và Proxy vì chúng xuất hiện rất thường xuyên ở boundary giữa user, orchestrator và tool. Khi workflow có nhiều worker hoặc event hơn, lúc đó Composite, Mediator, Chain of Responsibility và Observer mới thật sự đáng học kỹ.
Một skill luôn luôn là Facade à?
Không hẳn. Một skill chỉ chứa prompt viết bài chưa chắc đang che một subsystem phức tạp; nó có thể đơn giản là reusable instruction. Mình gọi nó là Facade khi nó cung cấp một entry point ổn định cho nhiều bước, nhiều tool hoặc nhiều rule phía sau.
MCP server là Adapter hay Facade?
Có thể là cả hai, tùy boundary mình đang nhìn. Nó là Adapter khi chuyển một API không tương thích thành tool interface mà agent gọi được. Nếu nó còn gom nhiều endpoint và workflow thành vài operation đơn giản, nó đồng thời đang làm công việc của Facade.
Dùng pattern có làm agent workflow bớt lỗi không?
Không tự động. Pattern chỉ giúp mình đặt đúng boundary và nói rõ trách nhiệm; prompt tệ, contract thiếu, permission rộng hoặc verification yếu vẫn làm hệ thống hỏng như thường. Nhưng khi failure xảy ra, một workflow có cấu trúc rõ sẽ dễ biết lỗi nằm ở routing, execution, state hay interface hơn.
Bạn thì sao: trong workflow agent bạn đang build, có pattern nào bạn đã dùng mỗi ngày nhưng tới giờ mới nhận ra nó có tên?