Agent AI không thất bại vì "AI còn ngu", nó thất bại vì bạn bỏ qua đúng những bước kỷ luật mà bạn vẫn làm khi tự tay code — chỉ khác là giờ bạn phải làm rõ ràng, thành lời, thay vì để trong đầu.
Hiểu Loop Như Một Workflow, Không Phải Cây Đũa Thần
Phần lớn engineer khi mới dùng Claude Code hay Cursor đều rơi vào một mental model rất tự nhiên nhưng sai lầm: gõ một câu yêu cầu, agent chạy một lượt, xong — hoặc là ăn may ra code đúng, hoặc là phải "prompt lại" cho tới khi nào đúng thì thôi. Cách làm này gọi là "vibe prompting" — cứ thử, thấy sai thì sửa câu, thử lại, dựa vào cảm giác nhiều hơn là quy trình. Nó hoạt động được với script 20 dòng. Nó sụp đổ hoàn toàn với một feature thực tế chạm vào state, API, database, hay UI có nhiều edge case.
Vấn đề không nằm ở việc model "chưa đủ thông minh". Vấn đề là bạn đang yêu cầu agent tự suy luận ra toàn bộ context mà lẽ ra chính bạn — người hiểu rõ nhất về sản phẩm, codebase, và ràng buộc nghiệp vụ — phải là người cung cấp. Khi thiếu context, agent không dừng lại hỏi bạn (trừ khi bạn ép nó), nó sẽ đoán. Và nó đoán rất tự tin: code sinh ra trông hợp lý, biên dịch được, có khi test tự viết còn pass, nhưng lại giải quyết sai bài toán, bỏ sót edge case, hoặc đi ngược lại convention của team. Đây chính là cái bẫy nguy hiểm nhất của agentic coding: code "trông đúng" (plausible) không đồng nghĩa với code đúng.
Cách giải quyết không phải là prompt "khéo" hơn, mà là áp dụng lại đúng kỷ luật kỹ thuật mà một senior engineer vẫn dùng khi nhận một task phức tạp — chỉ khác là giờ một phần lớn công đoạn thực thi được giao (delegate) cho agent. Senior engineer giỏi không nhảy thẳng vào code khi nhận một ticket mơ hồ; họ làm rõ yêu cầu, phác thảo hướng tiếp cận, trao đổi với người liên quan trước khi tốn effort viết code, rồi review kỹ trước khi merge. Agentic Loop — Spec → Plan → Implement → Review → Iterate — chính là hình thức hóa lại đúng quy trình đó, với agent đóng vai trò executor ở giữa. Mỗi giai đoạn là một checkpoint (điểm kiểm tra): bạn dừng lại, kiểm tra output của agent, và quyết định đi tiếp hay điều chỉnh trước khi cho phép nó tốn thêm compute (và tốn thêm thời gian review của bạn) vào hướng đi có thể sai.
Bỏ qua giai đoạn nào cũng có giá phải trả cụ thể. Nhảy thẳng từ ý tưởng mơ hồ sang "implement this" khiến agent phải tự bịa ra spec ngầm trong đầu nó — và spec ngầm đó gần như chắc chắn lệch với thứ bạn thực sự muốn. Bỏ qua plan khiến bạn chỉ phát hiện agent chọn sai kiến trúc sau khi nó đã viết xong 300 dòng code, lúc đó chi phí sửa cao hơn nhiều so với việc chặn nó lại ở bước phác thảo. Bỏ qua review kỹ, chỉ lướt qua rồi bấm approve, là cách nhanh nhất để bug âm thầm lọt vào production — agent không có "malice" nhưng cũng không có trực giác về rủi ro nghiệp vụ như bạn. Loop này không làm bạn chậm đi; nó làm bạn nhanh đi vì giảm số lần phải quay lại từ đầu.
Mẹo: Trước khi gõ prompt implement đầu tiên cho bất kỳ task nào lớn hơn "sửa một dòng", tự hỏi: "Mình đã có spec chưa, hay mình đang để agent đoán giúp mình?"
Giai Đoạn 1 — Spec: Neo Giữ Context Cho Agent
Spec (bản đặc tả) là văn bản mô tả rõ ràng: bạn muốn gì, trong ràng buộc nào, và thế nào là "xong". Nó không cần dài dòng như một tài liệu BRD của doanh nghiệp — với agentic coding, spec tốt thường chỉ vài chục dòng markdown, nhưng phải đủ cụ thể để loại bỏ mọi khoảng trống mà agent có thể tự lấp bằng suy đoán. Nhớ lại nguyên lý cốt lõi: agent không "biết" điều bạn không nói ra. Nó chỉ có context window — tất cả những gì nằm trong đó (system prompt, file đã đọc, spec bạn cung cấp, lịch sử hội thoại) là toàn bộ vũ trụ hiểu biết của nó về task này. Không viết vào spec = không tồn tại trong đầu agent.
Một spec đạt chất lượng production cần có tối thiểu năm phần: Goal (mục tiêu — task này giải quyết vấn đề gì, cho ai), Constraints (ràng buộc — công nghệ bắt buộc dùng, pattern phải theo, thứ không được đụng vào), Acceptance Criteria (tiêu chí nghiệm thu — danh sách điều kiện cụ thể, tốt nhất viết dạng có thể kiểm chứng được: "khi X xảy ra thì Y phải đúng"), Non-goals (những gì task này KHÔNG làm — cực kỳ quan trọng để agent không tự ý mở rộng phạm vi), và Edge cases (các trường hợp biên: input rỗng, mạng lỗi, user chưa đăng nhập, race condition...). Thiếu phần Non-goals là lỗi phổ biến nhất: không có nó, agent thường "nhiệt tình" refactor luôn cả những file không liên quan, hoặc tự thêm tính năng nó nghĩ là "nên có".
Dưới đây là ví dụ prompt thực tế để nhờ agent phụ bạn soạn spec (bạn vẫn là người review và chốt cuối):
I need to add a "dark mode toggle" feature to our settings page.
Help me draft a spec.md before we write any code.
Context: React + TypeScript frontend, Zustand for state,
existing settings page at src/pages/Settings.tsx.
Ask me clarifying questions first if anything is ambiguous
(e.g. persistence, default value, scope — per-user or per-device),
then produce a spec.md with these sections:
Goal, Constraints, Acceptance Criteria, Non-goals, Edge Cases.
Do not write implementation code yet.
Điểm mấu chốt trong prompt trên: yêu cầu agent hỏi lại trước khi viết spec. Đây là kỹ thuật quan trọng — agent hiện đại (Claude Code, Cursor) hoàn toàn có khả năng đặt câu hỏi làm rõ nếu bạn ra lệnh rõ ràng cho nó làm vậy; mặc định chúng thường có xu hướng "cứ giả định rồi làm luôn" để tỏ ra hữu ích. Việc thiếu spec dẫn tới "drift" — agent bắt đầu đúng hướng nhưng càng viết càng lệch, vì mỗi quyết định nhỏ (đặt tên biến, chọn thư viện, xử lý lỗi thế nào) đều phải tự suy đoán, và các suy đoán nhỏ cộng dồn lại thành một kết quả không giống thứ bạn hình dung.
Mẹo: Lưu spec thành file
spec.mdtrong repo (hoặc trong thư mụcplan/như quy ước của team) thay vì chỉ gõ trong chat — để agent có thể đọc lại nó ở mọi bước sau, và để bạn có audit trail khi cần xem lại tại sao quyết định như vậy.
Giai Đoạn 2 — Plan: Để Agent Suy Nghĩ Trước Khi Hành Động
Có spec rồi không có nghĩa là bảo agent "implement" ngay. Giai đoạn Plan bắt agent trình bày cách nó định làm trước khi đụng vào một dòng code nào — file nào sẽ sửa, file nào sẽ tạo mới, thứ tự thực hiện, những quyết định kiến trúc quan trọng (dùng context API hay Zustand store mới, migrate database theo kiểu nào, có cần thêm dependency không). Đây là bước rẻ nhất để bắt lỗi: sửa một dòng trong plan tốn vài giây đọc, trong khi sửa một kiến trúc sai sau khi code đã viết xong có thể tốn hàng giờ.
Lý do giai đoạn này quan trọng đến mức gần như bắt buộc: agent, giống người, có xu hướng chọn giải pháp "khả thi đầu tiên" nó nghĩ ra, không nhất thiết là giải pháp phù hợp nhất với codebase của bạn. Nếu bạn để nó code luôn, bạn chỉ phát hiện ra nó chọn sai hướng (ví dụ: viết logic validation trùng lặp thay vì tái sử dụng schema Zod sẵn có, hoặc tạo bảng database mới thay vì thêm cột vào bảng hiện tại) sau khi diff đã dài cả trăm dòng — lúc đó tâm lý "thôi kệ, sửa sau" rất dễ xảy ra và nợ kỹ thuật (technical debt) âm thầm chồng lên. Bắt agent trình bày plan bằng lời trước giúp bạn can thiệp đúng lúc: chi phí sửa một plan sai gần như bằng 0.
Ví dụ prompt yêu cầu plan, dựa trên spec đã có ở bước 1:
Based on spec.md, write an implementation plan before touching code.
The plan should include:
1. List of files to create/modify, with a one-line reason for each.
2. The state management approach (where dark mode preference lives,
how it persists across reload).
3. Any new dependencies needed, and why existing ones aren't enough.
4. Order of implementation steps (checkpoints), each independently
testable.
5. Open questions or assumptions you're making that I should confirm.
Output as plan.md. Do not write any implementation code in this step.
Khi agent trả về plan, việc của bạn là đọc kỹ mục 5 ("Open questions or assumptions") trước tiên — đây là nơi drift tiềm ẩn lộ ra rõ nhất. Nếu agent giả định sai (ví dụ giả định preference lưu ở localStorage trong khi team bạn cần đồng bộ theo user qua API), đây là lúc sửa, không phải sau khi code chạy xong. Cũng nên chú ý format plan thành các checkpoint độc lập kiểm chứng được (verifiable) — điều này chuẩn bị trực tiếp cho giai đoạn Implement kế tiếp.
Mẹo: Nếu plan agent đưa ra có bước nào bạn không tự tin giải thích được cho đồng nghiệp tại sao lại làm vậy, đừng approve — hỏi ngược agent "why this approach over X" trước khi cho nó code.
Giai Đoạn 3 — Implement: Thực Thi Theo Từng Checkpoint
Một khi plan đã chốt, đừng bảo agent "now implement everything" trong một phát. Cách hiệu quả hơn nhiều là thực thi theo từng checkpoint nhỏ — mỗi checkpoint là một đơn vị công việc có thể chạy được, test được, và commit được độc lập. Lý do kỹ thuật rất thực tế: generation càng dài, xác suất agent bị lạc trong chính output của nó (mất track một biến, quên một import, hoặc drift khỏi convention đã thống nhất ở giữa chừng) càng cao. Chia nhỏ theo checkpoint giữ agent luôn "trong tầm kiểm soát", và giữ cho bạn luôn review được diff ở kích thước con người có thể đọc hết, thay vì phải nuốt trọn một PR 800 dòng.
Checkpoint hóa còn có lợi ích trực tiếp lên git history — mỗi checkpoint tương ứng với một commit có ý nghĩa riêng, dễ revert, dễ bisect nếu sau này phát hiện bug. Ngược lại, một lần generation khổng lồ tạo ra một commit "big bang" gộp cả state management, UI, và persistence logic vào chung một khối — nếu phần persistence có bug, bạn không thể revert riêng nó mà không kéo theo phần UI đã đúng.
Ví dụ cách yêu cầu agent implement theo checkpoint dựa trên plan đã có:
Implement plan.md step by step. After each checkpoint:
- Stop and show me the diff.
- Tell me what to manually verify (e.g. which UI state to check,
which command to run).
- Wait for my confirmation before moving to the next checkpoint.
Start with checkpoint 1 only: the Zustand store for theme preference
and its localStorage persistence. Do not touch the Settings.tsx UI yet.
Ở mỗi checkpoint, bạn không chỉ đọc diff mà còn thực sự chạy thử — build, chạy test, hoặc thao tác tay trên UI. Nếu checkpoint 1 (store + persistence) có bug, bạn bắt nó ngay tại nguồn thay vì để nó lan sang checkpoint 2 (UI) rồi checkpoint 3 (áp dụng theme thực tế), lúc đó việc debug sẽ phải xác định bug nằm ở tầng nào trong ba tầng đã chồng lên nhau.
Mẹo: Đặt giới hạn rõ ràng trong prompt kiểu "implement checkpoint 1 only, stop after" — nếu không, nhiều agent (kể cả khi bạn đã có plan nhiều bước) vẫn có xu hướng "tiện thể" chạy luôn hết các bước còn lại.
Giai Đoạn 4 — Review: Bạn Là Biên Tập Viên, Không Phải Người Chỉ Ký Duyệt
Đây là giai đoạn nhiều engineer làm hời hợt nhất, và cũng là giai đoạn quyết định liệu agentic workflow có an toàn hay không. Cám dỗ rất lớn: code compile, test pass, nhìn qua thấy "sạch sẽ" — bấm approve cho nhanh. Nhưng vai trò của bạn ở đây không phải là người gác cổng chỉ kiểm tra syntax, mà là biên tập viên (editor) — người đọc kỹ nội dung để tìm ra chỗ nó nói sai ý, thiếu ý, hoặc nói đúng nhưng theo cách không phù hợp ngữ cảnh.
Editor mindset nghĩa là gì cụ thể? Là đọc diff không phải để tìm lỗi cú pháp (compiler đã lo việc đó), mà để đối chiếu từng thay đổi với spec và acceptance criteria đã viết ở giai đoạn 1: acceptance criteria số 3 nói "theme phải giữ nguyên sau khi reload trang" — bạn có thực sự F5 thử chưa, hay chỉ tin lời agent nói "done"? Là kiểm tra edge case đã liệt kê ở spec có thực sự được xử lý, không chỉ được nhắc tên trong comment. Là để ý những chỗ agent "quá nhiệt tình" — sửa luôn một file không nằm trong scope, đổi tên biến ở chỗ không liên quan, hay âm thầm thêm một dependency mới không có trong plan. Là kiểm tra logic nghiệp vụ có đúng không, không chỉ code có chạy không — hai thứ này khác nhau hoàn toàn: code chạy được một request notification test không có nghĩa là logic gửi notification đúng cho mọi loại user.
Một checklist review thực dụng cho mỗi PR/diff từ agent:
- Diff này có đúng đủ những gì spec yêu cầu — không thiếu, không thừa (so với Non-goals)?
- Từng acceptance criteria: có test hoặc bước verify tay chứng minh nó đã đạt chưa?
- Edge case liệt kê ở spec có code xử lý cụ thể, không chỉ là comment
// TODO: handle this? - Code có tuân theo convention/pattern hiện có của codebase, hay agent tự bịa ra pattern mới?
- Có side-effect nào nằm ngoài phạm vi task (file bị sửa không cần thiết, dependency mới không được yêu cầu)?
Before I approve this diff, walk me through how each acceptance
criterion in spec.md is satisfied by the code you just wrote.
For any criterion you can't point to specific lines for,
tell me honestly instead of assuming it's covered.
Prompt trên rất hữu dụng vì nó ép agent tự đối chiếu lại — nhiều khi chính agent, khi được yêu cầu tự kiểm tra, sẽ phát hiện ra nó bỏ sót một acceptance criteria mà bạn chưa kịp nhận ra khi đọc diff.
Mẹo: Đừng review chỉ bằng mắt — luôn chạy lại app/test suite thật ở máy bạn trước khi approve, kể cả khi agent báo "tests pass"; agent có thể chạy nhầm test suite hoặc bỏ sót test case chưa viết.
Giai Đoạn 5 — Iterate: Prompt Có Mục Tiêu, Không Phải Làm Lại Từ Đầu
Khi review phát hiện vấn đề, phản xạ tệ nhất là gõ một prompt mơ hồ kiểu "it's not working, fix it" hoặc tệ hơn — xoá hết, bắt agent "làm lại từ đầu". Cả hai đều lãng phí: prompt mơ hồ buộc agent phải tự đoán lại bạn đang nói tới cái gì (dễ sửa nhầm chỗ, hoặc sửa quá tay ra ngoài phạm vi), còn làm lại từ đầu vứt bỏ toàn bộ phần đã đúng cùng với phần sai.
Iteration hiệu quả là prompt có mục tiêu (targeted): chỉ đích danh file, dòng, hoặc hành vi cụ thể, mô tả rõ hiện tại nó làm gì sai và kỳ vọng đúng là gì, và giới hạn phạm vi sửa. Agent hoạt động tốt nhất khi feedback của bạn đọc giống một comment review thật trên GitHub/GitLab hơn là một câu than phiền chung chung.
So sánh hai cách prompt cho cùng một bug — theme không áp dụng ngay khi user vừa toggle, phải reload mới thấy:
The dark mode toggle doesn't work right. Fix it.
In src/stores/themeStore.ts, the `toggleTheme` action updates
the Zustand state and localStorage correctly (confirmed via
devtools), but src/components/ThemeProvider.tsx only reads the
theme value on mount — it's not subscribed to store changes,
so the UI doesn't re-render until a full page reload.
Fix ThemeProvider.tsx to subscribe to the store so theme changes
apply immediately. Don't touch themeStore.ts — that part is correct.
Show me the diff for ThemeProvider.tsx only.
Prompt thứ hai làm ba việc quan trọng: chỉ rõ file/nguyên nhân gốc bạn đã tự chẩn đoán được (bạn là engineer, bạn vẫn phải debug, agent không thay bạn làm việc đó), giới hạn rõ phạm vi sửa ("don't touch themeStore.ts"), và yêu cầu diff nhỏ để review nhanh lại. Kết quả gần như luôn là bản sửa gọn, đúng trọng tâm, thay vì agent viết lại cả cụm ba file vì hiểu nhầm phạm vi.
Một nguyên tắc bổ trợ: mỗi vòng iterate chỉ nên nhắm vào một vấn đề (hoặc một nhóm vấn đề liên quan chặt) tại một thời điểm. Dồn năm bug không liên quan vào một prompt khiến agent phải chia sẻ context cho nhiều hướng khác nhau cùng lúc, tăng khả năng sửa chỗ này làm hỏng chỗ khác.
Mẹo: Trước khi viết prompt iterate, tự trả lời câu hỏi "file nào, dòng nào, hành vi đúng là gì" cho chính bạn trước — nếu bạn không trả lời được, nghĩa là bạn chưa debug đủ để giao việc lại cho agent.
Thực Hành: Agentic Loop Hoàn Chỉnh Cho Feature "User Preferences"
Áp dụng toàn bộ 5 giai đoạn vào một feature cụ thể: trang Settings cho phép user chỉnh theme (light/dark/system), notification preferences (bật/tắt email, push), và language (vi/en). Giả sử stack là React + TypeScript, Zustand cho state, REST API backend lưu preference theo user.
Checkpoint 0 — Spec. Bắt đầu với prompt soạn spec, để agent hỏi ngược trước:
I need a User Preferences feature: a settings section where users
can change theme (light/dark/system), notification prefs (email on/off,
push on/off), and language (vi/en). Preferences must persist per-user
across devices via our REST API (not just localStorage).
Ask me clarifying questions about anything ambiguous — e.g. default
values, what happens for logged-out users, whether changes apply
immediately or need a "Save" button — then draft spec.md with:
Goal, Constraints, Acceptance Criteria, Non-goals, Edge Cases.
Kỳ vọng: agent sẽ hỏi lại những câu như "changes save instantly on toggle, or batched with a Save button?", "what's the default language if the API call fails?". Đây chính xác là những câu hỏi bạn muốn nó hỏi — trả lời chúng ngay lúc này rẻ hơn rất nhiều so với để agent tự đoán rồi bạn phát hiện sai ở review. Checkpoint để verify: đọc spec.md, đảm bảo Non-goals có ghi rõ, ví dụ, "does not include a full i18n framework migration — only switches a language flag the app already supports."
Checkpoint 1 — Plan.
Based on spec.md, write plan.md: files to touch, state/API design
(how preferences round-trip with the backend, optimistic update or
wait-for-response), order of implementation checkpoints, and any
open assumptions. No code yet.
Kỳ vọng: plan liệt kê rõ, ví dụ, checkpoint A = Zustand store + API client cho preferences, checkpoint B = UI component cho từng nhóm setting, checkpoint C = optimistic update + error rollback khi API fail. Việc cần verify: đọc mục "open assumptions" — nếu agent giả định sai kiểu update (ví dụ chọn optimistic update trong khi bạn muốn chờ xác nhận từ server trước khi đổi UI), sửa ngay trong plan trước khi qua Implement.
Checkpoint 2 — Implement (theo từng bước nhỏ trong plan).
Implement plan.md checkpoint A only: the preferencesStore (Zustand)
and the API client functions (getPreferences, updatePreference).
Stop after this checkpoint, show the diff, and tell me how to
manually verify it (e.g. via a temporary console call or test).
Verify tay: gọi thử store/API client qua console hoặc unit test nhỏ, xác nhận call đúng endpoint, đúng payload shape, xử lý lỗi API trả về đúng như spec (edge case: API timeout → giữ giá trị cũ, hiện toast lỗi). Chỉ sau khi checkpoint A ổn mới cho tiếp checkpoint B (UI), rồi checkpoint C (optimistic update + rollback).
Checkpoint 3 — Review.
Walk me through how each acceptance criterion in spec.md is satisfied
by the current diff across all three checkpoints. Point to specific
files/lines. Flag anything you're unsure is fully covered.
Bạn tự chạy app, bật/tắt từng preference, F5 kiểm tra đồng bộ giữa các tab/thiết bị (giả lập bằng cách mở 2 tab), tắt mạng giả lập lỗi API để xem rollback có hoạt động đúng như acceptance criteria đã ghi. Đây là lúc phát hiện, ví dụ, notification toggle cập nhật UI ngay nhưng chưa thực sự gọi API (chỉ optimistic mà quên phần "confirm với server") — một lỗi rất dễ trông "đúng" trên UI nhưng sai về data thật.
Checkpoint 4 — Iterate. Giả sử phát hiện đúng lỗi trên:
In src/hooks/useUpdatePreference.ts, the toggle updates local
Zustand state immediately (correct, that's the optimistic UX we want)
but never calls api.updatePreference() — so the change isn't
persisted server-side and is lost on reload.
Add the API call after the optimistic local update, with rollback
to the previous value if the API call fails (show an error toast).
Keep the existing local update behavior unchanged. Diff for
useUpdatePreference.ts only.
Lặp lại chu trình verify → phát hiện lệch → prompt có mục tiêu này cho từng vấn đề còn lại, cho tới khi mọi acceptance criteria trong spec.md đều có bằng chứng verify cụ thể. Kết thúc bằng việc commit theo từng checkpoint đã review, không gộp chung một commit khổng lồ.
Mẹo: Giữ lại spec.md và plan.md trong repo (ví dụ
plan/2026-07/user-preferences.md) sau khi feature xong — nó là tài liệu sống, hữu ích cho lần sau bạn hoặc đồng nghiệp cần agent sửa/mở rộng đúng feature này mà không phải giải thích lại từ đầu.
Những điểm chính
- Agentic Loop = Spec → Plan → Implement → Review → Iterate, mỗi giai đoạn là một checkpoint để bắt lỗi rẻ trước khi nó lan sang giai đoạn sau đắt đỏ hơn.
- Spec phải có đủ Goal, Constraints, Acceptance Criteria, Non-goals, Edge Cases — thiếu Non-goals là nguyên nhân phổ biến khiến agent tự ý mở rộng phạm vi.
- Bắt agent hỏi lại trước khi viết spec/plan; im lặng từ phía bạn = agent tự đoán = drift.
- Plan phải được viết ra và review trước khi có một dòng code nào — sửa một plan sai gần như miễn phí, sửa kiến trúc sai sau khi code xong thì không.
- Implement theo checkpoint nhỏ, độc lập test và commit được — không generation một cục lớn rồi review một PR khổng lồ.
- Review là công việc của editor: đối chiếu diff với từng acceptance criteria bằng bằng chứng thực (chạy thử, test), không phải chỉ đọc lướt cho "trông sạch".
- Iterate bằng prompt trỏ đích danh file/dòng/hành vi sai và hành vi đúng kỳ vọng, giới hạn rõ phạm vi sửa — không dùng "fix it" mơ hồ, không xoá làm lại từ đầu.
- Lưu spec.md và plan.md lại trong repo — chúng là tài sản lâu dài cho việc bảo trì và mở rộng feature sau này, không chỉ là công cụ dùng một lần.