Một lần tôi giao task kiểu: “Fix export bị sai timezone ở report”. Agent đọc code, thấy có formatDate, sửa helper đó để ép timezone về local. Test report pass. Nhưng một màn hình audit log bắt đầu hiển thị sai vì nó cũng dùng helper này để render UTC timestamp. Task đúng về mặt câu chữ, sai về mặt ý định. Cái tôi muốn là “chỉ export CSV dùng timezone của workspace”; cái tôi viết lại là “timezone sai”.
Đây là lỗi task brief. Với human teammate, tôi có thể nói thiếu rồi bổ sung qua Slack vì họ biết domain. Với agent, phần thiếu sẽ được lấp bằng suy luận. Suy luận có thể đúng, có thể phá shared helper.
Task brief tốt không cần dài. Nó cần đủ rõ để agent biết ba thứ: sửa cái gì, không sửa cái gì, và bằng chứng nào cho thấy đã xong. Bài này là template thực dụng tôi dùng khi giao AI coding task trong repo thật.
Một task brief không phải prompt trang trí
Prompt kiểu này nghe lịch sự nhưng yếu:
Please fix the timezone issue in export report. Make sure the code is clean and tests pass.
Nó thiếu gần như mọi thứ agent cần:
- Issue xảy ra ở đâu?
- Expected behavior là gì?
- Current behavior là gì?
- Scope file nào?
- Có helper shared nào không được sửa?
- Test nào cần chạy?
- Có non-goal không?
Một brief tốt hơn:
Problem:
CSV export in Reports uses browser timezone. It should use workspace timezone.
Expected:
For workspace timezone Asia/Ho_Chi_Minh, exported `createdAt` should be formatted as local date in that timezone regardless of browser timezone.
Scope:
Allowed:
- src/features/reports/exportCsv.ts
- src/features/reports/exportCsv.test.ts
Read-only:
- src/utils/formatDate.ts
- src/api/workspace.ts
Do not:
- Change global date helpers
- Change UI table rendering
- Add a new API endpoint
Validation:
- pnpm test src/features/reports/exportCsv.test.ts
- pnpm typecheck
Brief này dài hơn, nhưng tiết kiệm thời gian. Nó biến “timezone issue” thành contract.
Bắt đầu bằng tình huống, không bắt đầu bằng solution
Một brief nên mở bằng problem thật. Đừng nhảy ngay vào solution nếu bạn chưa chắc.
Kém:
Use dayjs timezone plugin in exportCsv.ts.
Tốt hơn:
CSV export currently formats timestamps using browser timezone. Users in the same workspace get different exported dates if their laptop timezone differs. We need export to use workspace timezone consistently.
Câu thứ hai cho agent bối cảnh để tự kiểm tra solution. Nếu codebase đã có Intl.DateTimeFormat wrapper dùng timezone sẵn, agent có cơ hội reuse. Nếu bạn ép dayjs ngay, nó có thể thêm dependency không cần thiết.
Tôi chỉ đưa solution cụ thể khi đã quyết định architecture. Ví dụ “use existing formatInWorkspaceTimezone helper” là instruction tốt nếu helper đó là source of truth. Còn “hãy tìm cách fix” thì nên mô tả problem và constraints, để agent search code.
Viết expected behavior bằng ví dụ
Expected behavior càng cụ thể, agent càng ít tự diễn giải. Với bug, tôi thích viết input/output.
Example:
- Input timestamp: 2026-05-25T00:30:00Z
- Workspace timezone: Asia/Ho_Chi_Minh
- Browser timezone: America/Los_Angeles
- CSV should show: 2026-05-25
- It should not show: 2026-05-24
Ví dụ nhỏ này đáng giá hơn một paragraph giải thích timezone. Nó cũng gợi ý test case trực tiếp.
Với UI, expected behavior có thể là state:
Expected:
- Empty state appears only when API returns zero items.
- Loading skeleton appears while request is pending.
- Error banner appears when request fails.
- Existing filters stay visible in all three states.
Với backend, expected behavior nên có status code, response shape, idempotency nếu cần:
Expected:
- Re-running the same import with the same source file must not create duplicate rows.
- API returns 200 with `{ imported, skipped }`.
- Validation errors return 400 and do not partially import rows.
Đừng viết “handle errors properly”. Properly là khoảng trống để agent tự chế.
Scope là phần quan trọng nhất
Agent coding rất hay overreach. Nó thấy một helper hơi xấu, một type hơi thừa, một component có thể refactor. Nếu brief không cấm, nó có thể sửa “cho sạch”. Trong production repo, sạch ngoài scope vẫn là rủi ro.
Tôi dùng format ba nhóm:
Allowed files:
- src/features/billing/PlanSelector.tsx
- src/features/billing/usePlans.ts
Read-only context:
- src/api/billing.ts
- src/types/billing.ts
Out of scope:
- Billing API changes
- Payment gateway config
- Database migrations
- Shared Button component
Nếu chưa biết file nào sẽ sửa, brief có thể nói scope theo khu vực:
Explore first, then report the intended file list before editing.
Likely area: src/features/reports/.
Do not edit src/components/shared/ without asking.
Đây là cách tốt khi task là bug chưa rõ root cause. Agent được explore, nhưng chưa được sửa bừa. Sau phase explore, bạn approve scope rồi mới cho edit.
Non-goals cứu bạn khỏi diff thừa
Non-goals là phần nhiều người bỏ qua. Với AI coding, non-goals gần như bắt buộc.
Ví dụ task thêm filter:
Non-goals:
- Do not redesign the table.
- Do not change API pagination.
- Do not add saved filters.
- Do not change mobile layout except what is needed for the new filter control.
Những câu này nghe thừa với human, nhưng không thừa với agent. Model tối ưu theo “hoàn thiện trải nghiệm”. Nó có thể nghĩ thêm saved filters là hay. Bạn thì chỉ cần một dropdown để unblock ticket hôm nay.
Non-goals cũng giúp review. Nếu diff có mobile redesign, bạn reject ngay vì brief đã cấm, không cần tranh luận code đẹp hay xấu.
Acceptance criteria phải kiểm được
Acceptance criteria yếu:
- Works correctly
- Code is clean
- Good UX
Acceptance criteria tốt:
- User can select `Last 7 days`, `Last 30 days`, or `Custom range`.
- Query params update without full page reload.
- Refreshing the page preserves selected filter from URL.
- Existing pagination resets to page 1 when date filter changes.
- Unit tests cover URL param parsing.
Mỗi dòng phải kiểm được bằng mắt, test, hoặc command. Nếu không kiểm được, nó là mong muốn, không phải criteria.
Tôi thường thêm “done means” ở cuối:
Done means:
- Code changed only in allowed files.
- Tests listed below pass.
- No console.log/debug logs remain.
- Final response includes changed files and validation output.
Đây là một contract vận hành. Agent biết báo cáo gì. Bạn biết kiểm gì.
Validation command phải chạy được từ repo root
Đừng ghi “run tests” chung chung. Ghi command thật:
Validation:
- pnpm test src/features/reports/exportCsv.test.ts
- pnpm typecheck
- pnpm lint -- src/features/reports/exportCsv.ts
Nếu command có thể fail vì môi trường, ghi trước:
Known issue:
- `pnpm test` full suite currently has unrelated flaky test in `SearchBox.test.ts`.
- For this task, run targeted report tests plus typecheck.
Điều này tránh agent mất 30 phút sửa flaky test ngoài scope. Tôi đã thấy chuyện đó nhiều lần: task A, full suite fail ở module B, agent đi sửa module B vì nghĩ đó là trách nhiệm của mình. Brief nên chặn.
Cách viết brief cho bug chưa rõ root cause
Không phải task nào cũng đủ rõ để giao implementation ngay. Với bug mơ hồ, tách làm hai phase.
Phase 1: investigate only.
Problem:
Users report that invoice PDF sometimes misses the tax line.
Tasks:
- Reproduce from existing tests or code path.
- Identify likely root cause.
- Propose file scope for fix.
- Do not edit files yet.
Output:
- Root cause summary
- Proposed changes
- Test plan
Sau khi agent trả investigation, bạn mới giao phase 2:
Phase 2: implement the approved fix.
Approved scope:
- src/features/invoices/pdf/buildInvoicePdf.ts
- src/features/invoices/pdf/buildInvoicePdf.test.ts
Use the test case from phase 1.
Pattern này giảm drift rất nhiều. Agent không vừa tìm hiểu vừa sửa vừa tự mở rộng scope. Bạn giữ quyền quyết định ở điểm quan trọng: root cause và file scope.
Brief cho refactor
Refactor là loại task nguy hiểm nhất để giao AI nếu brief lỏng. “Clean up this module” gần như mời agent rewrite.
Brief refactor phải có invariant:
Goal:
Split `ReportListPage.tsx` into smaller components without changing behavior.
Invariants:
- No API behavior changes.
- No visual layout changes.
- No route/query param changes.
- Existing tests should pass without snapshot updates unless necessary.
Allowed:
- Extract local components into named files under src/features/reports/components/.
- Move pure formatting helpers to src/features/reports/utils/.
Do not:
- Change shared components.
- Rename public types.
- Update dependencies.
Từ khóa ở đây là “without changing behavior”. Sau đó phải nói behavior gồm những gì. Nếu không, agent có thể “cải thiện” layout, state handling, hoặc API call.
Brief tốt làm agent nhanh hơn, không chậm hơn
Nhiều người ngại viết brief vì tưởng mất thời gian. Với task 5 phút, đúng là có thể không cần. Nhưng với task đủ lớn để giao agent, brief 10 phút thường tiết kiệm 30-60 phút review. Brief mơ hồ tạo nhiều vòng sửa; brief rõ giúp agent search đúng chỗ, sửa đúng file, chạy đúng test, báo đúng output.
AI coding không loại bỏ requirement engineering. Nó làm requirement engineering lộ ra sớm hơn. Nếu bạn không viết rõ điều mình muốn, agent sẽ viết code cho điều nó nghĩ bạn muốn.
Chốt lại bằng template ngắn tôi dùng nhiều nhất:
Goal:
Problem / current behavior:
Expected behavior:
Allowed files:
Read-only context:
Out of scope:
Constraints:
Acceptance criteria:
Validation commands:
Final response should include:
- changed files
- tests run
- anything not done
Không phải task nào cũng cần điền đủ mọi mục. Nhưng nếu một mục bị bỏ trống, hãy biết là bạn đang để agent tự đoán ở chỗ đó. Có lúc đáng đánh đổi. Có lúc không.