Một plan (kế hoạch triển khai) tồi sẽ khiến agent code rất nhanh — nhưng code sai thứ, sai thứ tự, và bạn phải trả giá gấp ba lần khi sửa lại.
Từ Spec Đến Plan Thực Thi Được: Vì Sao Bước Này Tồn Tại
Ở topic trước, bạn đã học cách viết spec (đặc tả) rõ ràng — mô tả "cái gì" cần làm, acceptance criteria (tiêu chí nghiệm thu), các ràng buộc nghiệp vụ và kỹ thuật. Nhiều engineer, sau khi có spec tốt, mắc một sai lầm rất tự nhiên: đưa thẳng spec đó cho agent và nói "implement giúp tôi". Với việc nhỏ, cách này ổn. Nhưng với bất kỳ feature nào có độ phức tạp vừa trở lên — chạm vào nhiều file, nhiều layer, có migration, có side effect — nhảy thẳng từ spec sang code là một canh bạc.
Vấn đề không nằm ở việc agent "không đủ thông minh". Vấn đề là spec mô tả trạng thái đích (what), còn việc implement đòi hỏi một chuỗi hành động có thứ tự (how) mà từ spec không tự động suy ra một cách duy nhất. Cùng một spec, agent có thể chọn viết migration trước hay viết API trước, có thể tạo file mới hay sửa file cũ, có thể gộp UI và logic vào một bước hay tách riêng. Nếu bạn không tường minh hóa thứ tự và ranh giới công việc, agent sẽ tự quyết — và quyết định đó có thể generate ra 800 dòng diff trong một lần chạy, trộn lẫn năm mối quan tâm khác nhau (concerns), khiến review gần như bất khả thi và rollback từng phần cũng bất khả thi.
Đây chính là lý do bước lập kế hoạch (planning) tồn tại như một layer trung gian bắt buộc giữa spec và code. Plan là bản dịch spec thành một danh sách task (nhiệm vụ) cụ thể, có thứ tự, mỗi task có phạm vi đủ nhỏ để review và verify độc lập. Khi bạn yêu cầu agent viết plan trước — thay vì code trước — bạn đang buộc nó phải "suy nghĩ thành tiếng" về cách nó sẽ tiếp cận vấn đề, trước khi bất kỳ dòng code nào được viết ra. Đây gần như là một dạng rubber-duck debugging ngược: thay vì bạn giải thích vấn đề cho một con vịt cao su, bạn để agent giải thích giải pháp cho bạn, và bạn bắt lỗi tư duy trước khi nó tốn effort implement.
Lợi ích lớn nhất của việc này là: nó phơi bày độ phức tạp ẩn và các giả định (assumption) ngầm trước khi tốn công viết code. Rất nhiều thứ chỉ lộ ra khi bạn buộc phải liệt kê từng bước — ví dụ: "À, để export CSV thì cần thêm quyền (permission) mới, mà bảng permission hiện tại chưa có chỗ cho việc này" hoặc "Muốn thêm field mới vào bảng orders thì phải backfill dữ liệu cũ, đây là việc tốn thời gian và cần chạy riêng, không thể gộp chung với deploy code". Những phát hiện này, nếu xuất hiện ở giai đoạn plan, chỉ tốn của bạn vài phút đọc và sửa văn bản. Nếu xuất hiện ở giai đoạn code, nó tốn của bạn một buổi rewrite.
Một điểm quan trọng khác: plan là artifact (sản phẩm trung gian) rẻ để vứt bỏ và viết lại. Nếu agent hiểu sai hướng tiếp cận, sửa một file markdown liệt kê task chỉ mất vài phút. Nhưng nếu agent hiểu sai và đã viết 500 dòng code theo hướng sai, bạn phải đọc hết 500 dòng đó để nhận ra vấn đề, rồi quyết định sửa hay viết lại từ đầu — cả hai lựa chọn đều đắt hơn nhiều so với sửa plan. Đầu tư thời gian vào review plan trước khi có code chính là cách rẻ nhất để tránh rework (làm lại).
Mẹo: Với bất kỳ feature nào chạm vào từ 3 file trở lên hoặc có thay đổi schema/API, luôn yêu cầu agent "chỉ viết plan, chưa code" trước — đừng để nó tự quyết định khi nào nên dừng lại để hỏi ý kiến bạn.
Task Decomposition Tốt và Tệ Khác Nhau Ở Đâu
Task decomposition (phân rã công việc) là kỹ năng cốt lõi của bước lập kế hoạch, và cũng là kỹ năng dễ bị làm hời hợt nhất. Một danh sách task tệ trông vẫn có vẻ "giống một plan" — có gạch đầu dòng, có số thứ tự — nhưng lại vô dụng khi thực thi. Hiểu rõ sự khác biệt giữa decomposition tốt và tệ giúp bạn review plan của agent nhanh và chính xác hơn, thay vì chỉ gật đầu vì "trông có vẻ đầy đủ".
Task tốt có bốn đặc điểm. Thứ nhất, phạm vi nhỏ và đơn trách nhiệm (single-responsibility): mỗi task chỉ nên làm một việc, chạm vào một layer hoặc một concern. Thứ hai, có thể verify độc lập: bạn phải biết được, chỉ nhìn vào task đó, làm sao để xác nhận nó đã xong — chạy test nào, mở màn hình nào, gọi API nào. Thứ ba, done-criteria (tiêu chí hoàn thành) rõ ràng: task không kết thúc mơ hồ ở "cải thiện X" mà kết thúc ở một trạng thái cụ thể, quan sát được. Thứ tư, độ hạt (granularity) vừa phải: đủ nhỏ để review trong một lần đọc diff, nhưng không nhỏ đến mức mỗi task chỉ là "thêm một dòng import" — over-decomposition (phân rã quá mức) biến việc quản lý task thành gánh nặng hành chính, làm chậm hơn là làm nhanh.
Ngược lại, task tệ thường rơi vào bốn cái bẫy. Bẫy phổ biến nhất là task mơ hồ kiểu "implement the feature" — không nói implement cái gì cụ thể, ở đâu, theo cách nào. Bẫy thứ hai là task gộp nhiều concern: một task vừa sửa database schema, vừa viết API, vừa sửa UI, vừa viết test — khi nó fail ở đâu đó, bạn không biết fail ở tầng nào, và không thể review từng phần riêng biệt. Bẫy thứ ba là task không thể verify: task như "cải thiện performance" hay "làm cho code sạch hơn" không có tiêu chí dừng, agent (và cả bạn) không bao giờ biết khi nào là "đủ". Bẫy thứ tư, ngược lại với over-decomposition, là micromanagement task vụn vặt — chia nhỏ đến mức "tạo file X", "thêm dòng import Y" trở thành các task riêng, làm phình to danh sách mà không tăng thêm giá trị kiểm soát nào.
Xem một ví dụ before/after cụ thể. Giả sử spec yêu cầu: "Thêm tính năng export báo cáo doanh thu sang CSV, có filter theo khoảng thời gian." Một plan tệ (quá thô, gộp mọi thứ) sẽ viết:
Task 1: Implement CSV export feature for revenue report
Đây thực chất không phải decomposition — nó chỉ là spec được paste lại dưới dạng một dòng. Nó không nói cho bạn biết agent sẽ tạo endpoint mới hay tái sử dụng cái cũ, xử lý filter ở đâu, format CSV ra sao, và bạn không có cách nào review từng phần.
Một plan tốt sẽ tách thành các task nhỏ hơn, mỗi task có phạm vi và done-criteria riêng:
Task 1: Add `GET /api/reports/revenue/export` endpoint that accepts
`from` and `to` query params (ISO date), reuses existing
`RevenueReportService.query()` for data fetching.
Done when: endpoint returns 200 with correct filtered rows
via a manual curl test, and returns 400 on invalid date range.
Task 2: Implement CSV serialization (columns: date, category, amount,
currency) as a pure function `toCsv(rows: RevenueRow[]): string`,
no I/O inside.
Done when: unit test covers empty rows, single row, and rows
with commas/quotes in text fields (CSV escaping).
Task 3: Wire endpoint (Task 1) to set `Content-Type: text/csv` and
`Content-Disposition: attachment; filename=revenue-<from>-<to>.csv`,
streaming the output of Task 2's serializer.
Done when: browser download test produces a file Excel/Sheets
opens without encoding errors.
Task 4: Add "Export CSV" button to the reporting dashboard, wired to
the current filter state, calling the endpoint from Task 3.
Done when: clicking the button with an active date filter
downloads a file matching the filtered date range.
Chú ý: mỗi task ở đây độc lập về mặt layer (API, business logic thuần, HTTP wiring, UI), có done-criteria kiểm chứng được bằng một hành động cụ thể, và không bị chẻ nhỏ tới mức vô nghĩa (ví dụ không có task riêng cho "import thư viện CSV"). Đây là độ hạt phù hợp cho một agent thực thi và cho bạn review.
Mẹo: Đọc lại từng task và tự hỏi "làm sao tôi biết task này xong?" — nếu câu trả lời không phải một hành động cụ thể (chạy lệnh, mở trang, gọi API), task đó cần được viết lại rõ hơn.
Thực Hành: Phân Rã Spec Thành Danh Sách Task Có Thứ Tự
Hãy đi qua một ví dụ đầy đủ, từ spec đến plan hoàn chỉnh, với prompt thật bạn có thể copy dùng ngay. Giả sử bạn đã có spec sau (rút gọn) cho tính năng "Thêm export CSV cho reporting dashboard":
Feature: CSV export for reporting dashboard
Context: Dashboard hiện hiển thị revenue report dạng bảng, có filter
theo khoảng thời gian (from/to) và theo category. Người dùng (finance
team) cần tải báo cáo về dạng CSV để đưa vào Excel làm phân tích thêm.
Acceptance criteria:
- Có nút "Export CSV" trên dashboard, cạnh bộ filter hiện tại.
- File CSV phản ánh đúng filter đang active (from, to, category).
- File tải về mở được bằng Excel và Google Sheets không bị lỗi encoding
(UTF-8 BOM cho tiếng Việt có dấu).
- Nếu không có dữ liệu trong khoảng filter, vẫn tải về file CSV chỉ có
header, không báo lỗi.
- Endpoint export phải tuân theo cùng cơ chế authorization như endpoint
xem báo cáo hiện tại (chỉ role "finance" và "admin" được export).
Bước đầu tiên: đừng yêu cầu agent code ngay. Yêu cầu nó viết plan trước, và ép nó liệt kê task theo layer, theo thứ tự thực thi, kèm done-criteria. Đây là một prompt ví dụ thực tế:
I have the attached spec for adding CSV export to the reporting
dashboard. Do NOT write any code yet.
Break this spec down into an ordered list of implementation tasks.
For each task, include:
- A one-line title
- Which layer it touches (backend endpoint / business logic /
frontend / infra-config / tests)
- A concrete "done when" criterion I can verify manually or via a test
- Any file(s) you expect to touch or create
Keep each task small enough to review in a single diff. Do not merge
multiple concerns (e.g. don't mix schema changes with UI changes in
one task). Flag any task where you're making an assumption not stated
in the spec, so I can confirm it before you proceed.
Output as a numbered markdown list. Stop after producing the list —
wait for my review before implementing anything.
Với prompt này, một kết quả tốt từ agent sẽ trông tương tự bốn task ở phần trước, cộng thêm một vài mục agent chủ động flag lên vì spec chưa nói rõ — ví dụ: "Assumption: dùng thư viện CSV writer hiện có trong project (csv-stringify) thay vì viết serializer thủ công — xác nhận giúp?" hoặc "Spec không nói rõ tên cột trong CSV có theo locale hiện tại của user hay cố định tiếng Anh — giả định dùng tiếng Anh, xác nhận?". Đây chính là giá trị bạn mong đợi: agent chủ động phơi bày điểm mù thay vì tự quyết định âm thầm.
Sau khi có danh sách task, bước tiếp theo là bạn review và edit thủ công — đừng chấp nhận nguyên trạng. Kiểm tra ba điều: (1) task có bỏ sót phần nào trong acceptance criteria không — ví dụ ở đây, criteria về UTF-8 BOM và về authorization theo role có xuất hiện thành task riêng không, hay bị agent gộp ẩn vào task khác mà không nói rõ; (2) thứ tự task có hợp lý về mặt kiến trúc không — ví dụ task frontend không nên đứng trước task backend nếu frontend cần gọi API đó; (3) có task nào quá to cần tách, hoặc quá vụn cần gộp không. Nếu thiếu, bổ sung trực tiếp bằng một câu ngắn:
Add a task for the UTF-8 BOM requirement explicitly — it's currently
implied inside Task 2 but not verifiable on its own. Also add an
authorization test task: verify a user with role "viewer" gets 403
on the export endpoint.
Việc lặp lại plan bằng những câu sửa ngắn như trên rẻ hơn rất nhiều so với để sót rồi phát hiện lúc code review hoặc, tệ hơn, lúc production.
Mẹo: Luôn yêu cầu agent "flag assumption" ngay trong prompt đầu tiên — phần lớn plan tệ không phải vì agent lười, mà vì nó âm thầm giả định thay vì hỏi.
Dependency Mapping: Tường Minh Hóa Các Quan Hệ Blocking
Có một danh sách task tốt là chưa đủ — nếu quan hệ phụ thuộc (dependency) giữa các task không được nói rõ, agent (và cả bạn, khi tự làm tay) rất dễ thực thi sai thứ tự, dẫn đến rework. Dependency ngầm là một trong những nguồn gây lãng phí phổ biến nhất trong agentic workflow: agent viết UI gọi một endpoint chưa tồn tại, rồi phải quay lại sửa; hoặc viết migration sau khi đã viết code phụ thuộc vào schema mới, khiến local dev bị lỗi giữa chừng.
Cách khắc phục là buộc dependency phải được viết tường minh ngay trong plan, dưới dạng "Task X depends on Task Y, Z" — không để nó nằm ngầm trong đầu agent hoặc trong đầu bạn. Khi bạn yêu cầu agent trình bày plan, hãy yêu cầu thêm một cột hoặc một dòng "Depends on" cho mỗi task:
For the task list you just produced, add explicit dependency
annotations. For each task, list which other task numbers it depends
on (must be done first) using "Depends on: none" or "Depends on: #1, #2".
Then group tasks into "can be done in parallel" batches — tasks with
no dependency on each other and no shared files can be grouped together.
Show the result as a dependency-ordered execution plan, batch by batch.
Áp dụng vào ví dụ CSV export, kết quả có thể trông như sau:
Batch 1 (parallel, no dependencies):
Task 2: Implement CSV serialization (toCsv) — Depends on: none
Task 5: Add UTF-8 BOM helper utility — Depends on: none
Batch 2 (sequential, depends on Batch 1):
Task 1: Add export endpoint — Depends on: #2, #5
Task 6: Add authorization test scaffold — Depends on: none
(can run in Batch 1 too, listed here for clarity)
Batch 3:
Task 3: Wire endpoint output headers + streaming — Depends on: #1
Task 7: Authorization test for export endpoint — Depends on: #1, #6
Batch 4:
Task 4: Add "Export CSV" button on dashboard — Depends on: #3
Cách trình bày theo batch này cho bạn hai lợi ích thực tế. Thứ nhất, nó cho biết task nào có thể chạy song song (parallelizable) — nếu bạn dùng nhiều agent session hoặc nhiều worktree cùng lúc (đã học ở module trước), bạn biết chính xác task nào an toàn để giao song song mà không đụng file nhau, và task nào bắt buộc phải tuần tự (sequential) vì output của task này là input của task kia. Thứ hai, nó là một lưới an toàn (safety net) chống lại việc agent "nhảy cóc" — nếu bạn thấy agent tự ý bắt đầu Task 4 (UI) trước khi Task 3 (wiring endpoint) xong, bạn có bằng chứng rõ ràng để chặn lại ngay, thay vì chỉ cảm thấy "có gì đó sai sai".
Một lưu ý quan trọng: dependency không chỉ đến từ logic (task này cần output của task kia), mà còn có thể đến từ file conflict — hai task không phụ thuộc logic lẫn nhau nhưng cùng sửa một file thì vẫn nên xếp tuần tự, nếu không muốn xử lý merge conflict giữa hai lần chạy agent. Khi yêu cầu agent lập dependency map, luôn nhắc nó cân nhắc cả hai loại phụ thuộc này, không chỉ phụ thuộc logic.
Mẹo: Trước khi giao task chạy song song, hỏi thẳng agent "Do task #X and #Y touch any of the same files? If yes, they should not run in parallel" — đừng tự suy đoán.
Validate Plan Trước Khi Viết Code
Plan đã có task rõ ràng, đã có dependency tường minh — bước cuối cùng trước khi bấm nút "implement" là validate (thẩm định) toàn bộ plan một lần nữa, với vai trò của bạn là reviewer chứ không phải người viết. Đây là bước dễ bị bỏ qua nhất vì cảm giác "đã làm đủ rồi", nhưng lại là bước rẻ nhất để bắt lỗi trước khi tốn compute và thời gian implement.
Việc validate nên đi qua bốn lượt kiểm tra. Lượt một: đối chiếu từng dòng plan với acceptance criteria trong spec gốc — mọi criteria phải map được tới ít nhất một task, không có criteria nào "rơi rớt". Cách làm nhanh nhất là yêu cầu chính agent tự làm việc đối chiếu này, vì nó thường phát hiện ra khoảng trống mà bạn đọc lướt có thể bỏ sót:
Go through the acceptance criteria in the original spec one by one.
For each criterion, tell me which task number(s) cover it. If any
criterion is not covered by any task, call it out explicitly and
propose a new task for it.
Lượt hai: rà soát edge case còn thiếu. Spec thường chỉ mô tả happy path; plan tốt phải có task hoặc ít nhất một dòng ghi chú xử lý các trường hợp biên — dữ liệu rỗng, input không hợp lệ, quyền truy cập sai, timeout, dữ liệu quá lớn. Một prompt hữu ích:
List the edge cases this plan currently does NOT explicitly handle
(empty results, invalid input, oversized payloads, concurrent access,
partial failures). For each one you find, tell me whether it's already
implicitly covered by an existing task or needs a new one.
Lượt ba: kiểm tra thứ tự task có khớp với ràng buộc kiến trúc thật của hệ thống hay không — đây là phần agent dễ sai nhất vì nó không có toàn bộ ngữ cảnh vận hành mà bạn có. Ví dụ: nếu team bạn có quy định migration phải được review và merge riêng, tách khỏi PR code, plan cần phản ánh đúng ràng buộc quy trình đó chứ không chỉ đúng về mặt logic. Đây là chỗ bạn, với vai trò senior engineer, mang kiến thức tổ chức vào — agent không tự biết được, phải bạn tự đọc và đối chiếu.
Lượt bốn: chủ động hỏi ngược agent thay vì chỉ đọc thụ động. Đặt câu hỏi buộc agent phải bảo vệ (justify) các quyết định trong plan, thay vì bạn tự đoán xem nó đúng hay sai:
Before I approve this plan: what is the riskiest task in this list —
the one most likely to require rework or reveal a wrong assumption
once you start implementing? What would you need from me to reduce
that risk before starting?
Câu hỏi này thường moi ra thông tin giá trị nhất trong cả quá trình — agent buộc phải tự đánh giá độ tự tin của chính nó, và câu trả lời cho bạn biết chính xác nên tập trung review kỹ ở đâu khi implementation bắt đầu. Chỉ sau khi plan đã qua đủ bốn lượt kiểm tra này, bạn mới nên chuyển sang giai đoạn thực thi từng task theo đúng batch và thứ tự đã map.
Mẹo: Luôn kết thúc bước validate bằng câu hỏi "task nào rủi ro nhất" — đó thường là nơi bạn nên ngồi cạnh theo dõi agent thực thi trực tiếp, thay vì để nó chạy tự động.
Những điểm chính
- Không nhảy thẳng từ spec sang code với feature phức tạp — luôn yêu cầu agent viết plan trước, dùng plan như bước phơi bày giả định ẩn với chi phí thấp.
- Task tốt: nhỏ, đơn trách nhiệm, có done-criteria kiểm chứng được, độ hạt vừa phải — không mơ hồ, không gộp nhiều concern, không vụn vặt.
- Dùng prompt yêu cầu agent tự flag assumption ngay khi phân rã task, thay vì để nó âm thầm tự quyết định.
- Tường minh hóa dependency bằng "Task X depends on Task Y" và nhóm task thành các batch song song/tuần tự — nhớ tính cả file conflict, không chỉ phụ thuộc logic.
- Trước khi cho agent code, validate plan qua bốn lượt: đối chiếu acceptance criteria, rà soát edge case, kiểm tra ràng buộc kiến trúc/quy trình thực tế, và hỏi ngược agent về task rủi ro nhất.
- Sửa plan bằng vài câu ngắn luôn rẻ hơn sửa code đã viết sai hướng — đầu tư thời gian review ở bước plan là khoản đầu tư có lãi suất cao nhất trong cả workflow.