Đọc lý thuyết về spec, plan, delegation, staged implementation, code review hay PR workflow là một chuyện — tự tay chạy cả sáu bước đó liền mạch cho một feature thật, từ dòng yêu cầu đầu tiên đến khi merge, mới là lúc bạn thực sự biết mình đã học được gì.
Capstone Này Bao Gồm Những Gì
Đây là bài thực hành tổng hợp (capstone) của Module 4. Toàn bộ 6 topic trước — spec, plan, delegation, staged implementation, AI code review, git/PR workflow — sẽ được ráp lại thành một vòng lặp agentic workflow (quy trình phát triển có AI đồng hành) hoàn chỉnh, áp dụng lên đúng MỘT feature từ đầu đến cuối. Mục tiêu không phải là học thêm khái niệm mới, mà là luyện phản xạ: khi nào chuyển giai đoạn, khi nào dừng lại review, khi nào tin AI và khi nào phải tự mình kiểm tra.
Feature dùng xuyên suốt bài này: "Saved Searches With Email Alerts" (lưu tìm kiếm và nhận email báo tin) cho một job board tên JobHub — một web app kiểu Vietnamworks/LinkedIn Jobs thu nhỏ, nơi ứng viên tìm việc theo từ khóa, địa điểm, mức lương, loại hình (remote/onsite/hybrid). Hiện tại app chỉ hỗ trợ tìm kiếm one-shot: gõ filter, xem kết quả, đóng tab, quên luôn. Vấn đề thực tế: ứng viên đang tìm việc chủ động phải quay lại search thủ công mỗi ngày để không bỏ lỡ tin mới, rất mất công và tỷ lệ họ bỏ cuộc (drop-off) cao.
Scope ở mức vừa đủ để một senior engineer làm trong khoảng 3-5 ngày công: cho phép người dùng đã đăng nhập lưu lại một bộ filter tìm kiếm (đặt tên, chọn tần suất nhận alert là daily hoặc weekly), hệ thống chạy job định kỳ để tìm job mới khớp với filter đã lưu và gửi email tổng hợp, kèm link hủy đăng ký (unsubscribe) không cần đăng nhập lại. Không bao gồm: SMS alert, machine-learning based recommendation, hay app di động — những thứ đó để dành cho các iteration sau.
Sáu giai đoạn ta sẽ đi qua, theo đúng thứ tự một feature thật sự đi qua trong đời sống hàng ngày của một dev làm việc với AI coding agent (Claude Code, Cursor...):
- Sinh spec — biến một câu yêu cầu mơ hồ từ product thành tài liệu rõ ràng, có acceptance criteria.
- Lập kế hoạch triển khai — bẻ spec thành danh sách task có thứ tự, có phụ thuộc (dependency).
- Staged implementation với commit — thực thi plan theo từng checkpoint (điểm kiểm tra), review và commit sau mỗi giai đoạn.
- AI code review — soát lại toàn bộ diff qua 4 lăng kính: correctness, security, performance, architectural fit.
- Sinh PR description — để reviewer con người đọc hiểu nhanh, không phải đoán ý từ diff.
- Review cuối và commit — con người đọc lại lần cuối, đối chiếu spec, dọn lịch sử commit, merge.
Mẹo: Trước khi bắt tay vào Stage 1, hãy tự hỏi: "Nếu feature này fail ngoài production, root cause nhiều khả năng nằm ở đâu nhất — spec mơ hồ, plan thiếu bước, hay code review bỏ sót?" Câu trả lời sẽ quyết định bạn nên đầu tư thời gian kỹ hơn ở giai đoạn nào trong 6 giai đoạn dưới đây.
Stage 1: Sinh Spec
Mọi thứ bắt đầu từ một yêu cầu rất người: product manager nhắn "mình muốn user có thể lưu search và nhận email khi có job mới, tương tự LinkedIn". Câu này thiếu gần như mọi chi tiết kỹ thuật cần thiết — tần suất gửi email, giới hạn số lượng saved search mỗi user, xử lý unsubscribe ra sao, có cần rate-limit không. Việc đầu tiên không phải là code, mà là ngồi cùng AI agent để ép câu yêu cầu mơ hồ đó thành một spec có thể kiểm chứng được.
Kỹ thuật ở đây là đối thoại hai chiều: đừng yêu cầu agent "viết spec" ngay, hãy yêu cầu nó hỏi ngược lại bạn trước, giống hệt cách một tech lead giỏi sẽ làm khi nhận ticket mơ hồ.
I need to write a feature spec for "Saved Searches with Email Alerts" on
JobHub, a job-listing web app (Next.js frontend, Node/Express API,
PostgreSQL). Rough idea from product: logged-in users can save a search
(keywords, location, salary range, job type) and get emailed when new
matching jobs are posted.
Before drafting the spec, ask me up to 8 clarifying questions covering:
scope boundaries, notification frequency options, volume/rate limits,
unsubscribe/opt-out flow, and anything about existing auth or email
infra I should tell you about. Don't write the spec yet.
Agent sẽ hỏi lại những câu như: tần suất alert có cho chọn nhiều option không hay chỉ daily/weekly, giới hạn số saved search mỗi user là bao nhiêu, hệ thống email hiện tại dùng provider nào (SendGrid, SES...), có cần track "đã gửi job nào rồi" để tránh gửi trùng không, unsubscribe có cần xác thực token hay chỉ cần link là đủ. Bạn trả lời từng câu bằng kiến thức nghiệp vụ thật của mình — đây chính là chỗ domain knowledge của bạn, không phải của AI, quyết định chất lượng spec.
Sau khi trả lời xong, yêu cầu agent tổng hợp thành spec chính thức:
Based on my answers, write the full feature spec as a markdown document
with these sections: Goal, User Stories, Constraints, Acceptance
Criteria (as a checklist), Non-Goals, and Open Questions. Keep it
implementation-agnostic — no code, no schema yet.
Spec excerpt thực tế sẽ trông như sau:
## Goal
Let logged-in JobHub users persist a search filter set and receive
periodic email digests of new matching jobs, reducing manual re-search.
## Constraints
- Max 5 saved searches per user (avoid email spam / DB bloat).
- Alert frequency: "daily" or "weekly" only, chosen at save time.
- Digest email must batch all new matches into ONE email per saved
search per run — never one email per job.
- Must reuse existing SES-based email service (`services/mailer.ts`).
- Unsubscribe must work via a signed, expiring link with no login
required.
## Acceptance Criteria
- [ ] User can save current search filters with a custom name.
- [ ] User can view, edit frequency, and delete their saved searches.
- [ ] A daily cron identifies new jobs matching each saved search since
last run and sends one digest email if there's ≥1 match.
- [ ] Weekly-frequency searches only get emailed on their weekly cycle.
- [ ] Unsubscribe link disables alerts for that saved search only.
- [ ] No duplicate job ever appears across two consecutive digests.
## Non-Goals
- SMS/push notifications.
- ML-based job recommendation beyond exact filter match.
- Saved search sharing between users.
Đừng để agent tự chốt "Constraints" mà không kiểm tra: đây là chỗ dễ sai lệch nhất vì AI không biết services/mailer.ts của bạn có rate limit riêng hay không, không biết business đã từng bị report vì gửi email trùng lần trước. Đọc kỹ từng dòng constraint, sửa tay nếu cần, trước khi cho nó "chốt" thành file spec chính thức lưu vào repo (ví dụ docs/specs/saved-search-alerts.md).
Mẹo: Luôn yêu cầu agent liệt kê "Open Questions" cuối spec thay vì tự đoán rồi im lặng — đó là danh sách rủi ro bạn cần chốt với product/design trước khi bước sang Stage 2, chứ không phải để trống rồi hy vọng đoán đúng.
Stage 2: Lập Kế Hoạch Triển Khai
Có spec rồi, bước kế là bẻ nó thành một plan thực thi được — danh sách task cụ thể, có thứ tự phụ thuộc rõ ràng, mỗi task nhỏ đủ để review trong một lần đọc diff. Đây là lúc bạn cho agent thấy toàn bộ codebase liên quan (schema hiện tại, cấu trúc route API, cấu trúc thư mục frontend) để plan sinh ra khớp với convention thật, không phải convention tưởng tượng.
Read the spec at docs/specs/saved-search-alerts.md. Also inspect our
current DB schema (prisma/schema.prisma), the existing job search API
(src/api/routes/jobs.ts), and how cron jobs are currently wired
(src/jobs/README.md).
Produce an implementation plan as an ordered task list. For each task
include: what changes, which files, and what it depends on. Group tasks
into phases that can each be committed and verified independently.
Flag any task that touches the email-sending path as needing extra
care around idempotency.
Plan excerpt hợp lý mà agent trả về:
## Phase 1 — Data model
1. Add `SavedSearch` model (userId, name, filters JSON, frequency enum,
lastNotifiedAt, unsubscribeToken, isActive). Depends on: none.
2. Migration + index on (frequency, isActive) for cron query
efficiency. Depends on: Task 1.
## Phase 2 — CRUD API
3. POST /api/saved-searches (create, enforce 5-per-user limit).
Depends on: Phase 1.
4. GET/PATCH/DELETE /api/saved-searches/:id (list, edit frequency,
delete). Depends on: Task 3.
## Phase 3 — Alert matching + email job
5. New cron `jobs/sendSavedSearchAlerts.ts`: query active saved
searches due today, find jobs posted after lastNotifiedAt matching
filters. Depends on: Phase 1, 2.
6. Digest email template + send via services/mailer.ts, update
lastNotifiedAt only after successful send (idempotency!).
Depends on: Task 5.
7. Unsubscribe endpoint (signed token verification, sets isActive =
false). Depends on: Task 1.
## Phase 4 — Frontend
8. "Save this search" button on search results page. Depends on:
Task 3.
9. "My saved searches" management page (list/edit/delete).
Depends on: Task 4.
## Phase 5 — Tests & hardening
10. Unit tests for matching logic + idempotency edge cases.
11. Rate-limit check on create endpoint; load-test the cron query.
Điểm mấu chốt của một plan tốt: task được nhóm theo phase có thể commit độc lập (Phase 1 chạy được migration mà không cần Phase 2 tồn tại), và mỗi dependency được ghi rõ để bạn biết task nào có thể giao song song nếu có nhiều agent/nhiều dev, task nào bắt buộc tuần tự. Đọc lại plan này, bạn — con người — là người quyết định thứ tự phase có hợp lý không (ví dụ: có nên làm Task 11 rate-limit sớm hơn để tránh việc cron chạy tràn trong lúc dev Phase 3 không), agent chỉ đề xuất.
Mẹo: Yêu cầu agent gắn nhãn "extra care" hoặc "risky" vào những task đụng tới side-effect không thể hoàn tác (gửi email, charge tiền, xoá dữ liệu) ngay trong plan — đây là các task bạn sẽ review kỹ hơn hẳn ở Stage 4, đừng đợi đến lúc code xong mới nhận ra.
Stage 3: Staged Implementation Với Commit
Giờ mới tới lúc gõ code — nhưng không phải "làm hết một lèo rồi commit một phát". Thực thi plan theo staged implementation nghĩa là mỗi phase là một checkpoint: agent code, bạn build/test/đọc diff, xác nhận đúng ý rồi mới commit, rồi mới cho agent tiếp tục phase kế. Cách này giới hạn bán kính ảnh hưởng (blast radius) của mỗi lỗi trong phạm vi một phase, và cho bạn điểm quay lui rõ ràng nếu phase sau đi sai hướng.
Phase 1 — data model, prompt giao việc:
Implement Phase 1 from the plan only (SavedSearch model + migration).
Do not touch anything in Phase 2 or later. After the migration, run it
against the dev DB and show me the resulting schema diff.
Checkpoint: chạy npx prisma migrate dev, mở schema xem index có đúng như plan không, kiểm tra migration có reversible không. Xong thì commit:
git add prisma/schema.prisma prisma/migrations
git commit -m "feat(saved-search): add SavedSearch data model and migration"
Phase 2 — CRUD API:
Implement Phase 2 (CRUD endpoints for saved searches). Enforce the
5-per-user limit at the service layer, not just in the controller, so
it's also protected if we add a GraphQL API later. Reuse the existing
auth middleware pattern from src/api/routes/jobs.ts.
Checkpoint: gọi thử 4 endpoint bằng curl/Postman, cố tình tạo saved search thứ 6 để xác nhận limit hoạt động, kiểm tra response shape có khớp convention API hiện tại (camelCase, envelope { data, error }...) không. Commit riêng.
Phase 3 — phase nhạy cảm nhất vì đụng tới gửi email thật:
Implement Phase 3 (alert matching cron + digest email). Critical
requirement: lastNotifiedAt must only update AFTER the email send
succeeds — if sendEmail() throws, the saved search must remain
eligible for retry on the next cron run, and must NOT re-include jobs
already sent in a previous successful digest. Write this with an
explicit transaction or two-step commit so a crash between "find
matches" and "send email" can't cause either double-send or silent
drop. Use a test double for the mailer so I can verify this locally
without sending real emails.
Checkpoint quan trọng nhất của cả bài: dựng test giả lập cron chạy 2 lần liên tiếp, giả lập mailer throw lỗi ở lần chạy đầu, xác nhận lần chạy thứ hai vẫn gửi đúng (không mất job, không gửi trùng). Đây chính là chỗ "staged implementation" phát huy giá trị — nếu làm hết Phase 1-5 rồi mới test, bug idempotency ở Phase 3 sẽ lẫn vào hàng trăm dòng diff khác, cực khó cô lập.
Phase 4 (frontend) và Phase 5 (tests/rate-limit) lặp lại chu kỳ tương tự: giao việc hẹp theo đúng phase, verify bằng tay (click thử UI, chạy test suite, thử load cron query với vài nghìn saved search giả để xem index Phase 1 có phát huy tác dụng không), rồi mới commit. Mỗi commit nên atomic — tự đứng được, message theo convention feat(saved-search): ..., test(saved-search): ....
Mẹo: Bất kỳ phase nào đụng tới side-effect không thể hoàn tác (ở đây là Phase 3, gửi email), hãy luôn yêu cầu agent dựng test double/dry-run trước, và tự tay chạy thử kịch bản "crash giữa chừng" ít nhất một lần trước khi commit — đừng tin lời agent nói "đã handle idempotency", hãy bắt nó chứng minh bằng test.
Stage 4: AI Code Review
Code đã chạy được và có test pass không có nghĩa là code tốt. Trước khi viết PR, chạy toàn bộ diff qua một vòng AI code review độc lập theo 4 lăng kính: correctness (đúng logic không), security (có lỗ hổng không), performance (có nghẽn cổ chai không), và architectural fit (có khớp convention & pattern hiện có của codebase không). Mẹo quan trọng: dùng một agent session mới, không phải session vừa viết code — để tránh thiên vị xác nhận (confirmation bias), agent vừa code thường có xu hướng tự bảo vệ quyết định của chính nó.
Review the full diff for the saved-search-alerts feature
(git diff main...feature/saved-search-alerts) across 4 dimensions:
1. Correctness — logic errors, edge cases (timezone handling for
"daily" cron, empty filter sets, race conditions).
2. Security — auth bypass, injection, unsubscribe token forgery,
IDOR (can user A hit /api/saved-searches/:id belonging to user B?).
3. Performance — N+1 queries, missing indexes, unbounded queries in
the cron job as saved-search count grows.
4. Architectural fit — does this match existing patterns in
src/api and src/jobs, or does it introduce a one-off style?
For each finding, cite the file/line, explain the failure scenario
concretely, and rate severity (blocker/major/minor).
Findings thực tế đáng có ở review này (và là loại bug rất dễ lọt qua nếu chỉ code + chạy happy-path test):
- [Blocker – Security]
GET /api/saved-searches/:idkhông kiểm trasavedSearch.userId === req.user.idtrước khi trả dữ liệu → IDOR, user A đọc được saved search của user B chỉ bằng cách đổi ID trên URL. - [Blocker – Correctness] Unsubscribe token không có hạn dùng (expiry) và không bị invalidate sau khi dùng một lần → link cũ trong email cũ vẫn tắt được alert của người khác nếu bị lộ/forward.
- [Major – Performance] Cron job Phase 3 query tất cả saved search rồi loop từng cái gọi job-matching riêng lẻ → N+1 query, sẽ chậm tuyến tính theo số user khi lên vài chục nghìn saved search.
- [Major – Architectural fit] Endpoint CRUD mới trả lỗi bằng cách
throw new Error(string)trong khi toàn bộ API còn lại dùng classApiErrorcó mã lỗi chuẩn hoá — không nhất quán, frontend hiện tại parse lỗi theo shape cũ sẽ vỡ. - [Minor – Correctness] So sánh "daily" cron chạy đúng ngày dùng
new Date()theo giờ server thay vì UTC chuẩn hoá, có thể lệch một ngày cho user ở múi giờ khác so với server.
Bạn không tự sửa ngay theo lời agent — verify từng finding bằng cách đọc code thật tại vị trí được chỉ ra, xác nhận đúng là bug trước khi fix, vì AI review đôi khi báo false positive (ví dụ nhầm middleware auth đã check ở tầng router nhưng không thấy vì không đọc hết file). Sau khi xác nhận, quay lại giao cho implementation agent fix từng finding một, ưu tiên blocker trước.
Mẹo: Luôn bắt AI review liệt kê "failure scenario cụ thể" (input/state nào dẫn đến hỏng gì) thay vì nhận xét chung chung như "nên thêm validation" — nhận xét mơ hồ không giúp bạn biết có nên block PR hay không, failure scenario cụ thể thì có.
Stage 5: Sinh PR Description
Diff đã sạch, giờ cần một PR description để reviewer con người (tech lead, teammate) hiểu nhanh mà không phải tự lần mò qua hàng chục file thay đổi. Cho agent đọc toàn bộ commit history và diff của branch, kết hợp với spec gốc ở Stage 1, để sinh description vừa đủ chi tiết vừa đúng trọng tâm — không chỉ liệt kê file đổi, mà giải thích "tại sao" và "đánh đổi gì".
Read the spec at docs/specs/saved-search-alerts.md, the full commit
log on this branch (git log main..HEAD), and the full diff
(git diff main...HEAD). Draft a PR description with these sections:
Summary, Changes, Motivation, Technical Decisions, Testing, Checklist.
In "Technical Decisions", specifically call out the idempotency
approach for the email cron and the reasoning behind the 5-saved-
search-per-user limit. In "Testing", list exactly what was verified
manually vs. covered by automated tests.
PR description mà agent sinh ra, sau khi bạn đọc và chỉnh vài chỗ cho chính xác:
## Summary
Adds "Saved Searches with Email Alerts" to JobHub — logged-in users
can persist a search filter set and receive a daily or weekly email
digest of newly posted matching jobs, with self-service unsubscribe.
## Changes
- New `SavedSearch` Prisma model + migration (filters, frequency,
lastNotifiedAt, unsubscribeToken, isActive).
- CRUD API: `POST/GET/PATCH/DELETE /api/saved-searches`, enforced
5-per-user limit at the service layer.
- New cron `jobs/sendSavedSearchAlerts.ts`: matches active saved
searches due for their frequency, batches new jobs into one digest
email per saved search via `services/mailer.ts`.
- Signed, expiring unsubscribe endpoint (no login required).
- Frontend: "Save this search" action on results page + "My saved
searches" management page (edit frequency, delete).
- Unit tests for matching logic, idempotency, and rate limiting.
## Motivation
Candidates actively job-hunting currently must manually re-run
searches to catch new postings, which our funnel data shows causes
meaningful drop-off after the third session. This closes that gap
without requiring a native app or push infra.
## Technical Decisions
- **Idempotency**: `lastNotifiedAt` is only advanced after the mailer
call resolves successfully, inside the same transaction boundary as
the "mark as sent" write. A crash between matching and sending
re-runs safely on the next cron tick with no duplicate or dropped
digest — verified with a fault-injected mailer double in tests.
- **5 saved searches per user**: chosen to bound both DB growth and
cron query cost as adoption scales; enforced server-side (not just
UI) so it also protects any future API consumer.
- **Digest batching**: one email per saved search per run, never one
email per job, to avoid inbox spam — matches product's explicit
constraint from the spec.
## Testing
- Automated: unit tests for saved-search CRUD limit enforcement, cron
matching logic, and the crash-recovery/idempotency scenario (mailer
throws mid-run, re-run doesn't duplicate or drop).
- Manual: created/edited/deleted saved searches via UI; ran the cron
locally against seeded data for both daily and weekly frequencies;
clicked unsubscribe links from a real sent digest (staging SES
sandbox) and confirmed only that saved search was disabled.
- Not covered yet: load test of the cron query at >50k saved searches
(tracked as a follow-up, see Open Questions in the spec).
## Checklist
- [x] Matches acceptance criteria in docs/specs/saved-search-alerts.md
- [x] No IDOR on saved-search endpoints (ownership check added post
code-review finding)
- [x] Unsubscribe tokens expire and are single-use
- [x] Error responses use `ApiError` for consistency with existing API
- [x] Cron timezone handling normalized to UTC
- [ ] Follow-up ticket filed for cron query load-testing at scale
Description này đủ để một reviewer chưa đọc code vẫn hiểu được phạm vi, lý do, và biết chính xác cái gì đã test/chưa test — đặc biệt phần "Technical Decisions" là nơi bạn thể hiện tư duy kỹ thuật của mình cho người review, không phải chỗ để AI paraphrase lại diff.
Mẹo: Luôn tự tay verify phần "Testing" trong PR description khớp với những gì bạn THỰC SỰ đã chạy — đây là phần dễ bị agent "lạc quan tếu" nhất, ghi những thứ nghe hợp lý nhưng chưa ai kiểm chứng thật.
Stage 6: Review Cuối Và Commit
Đây là lượt review cuối cùng, và là lượt duy nhất không giao cho AI — bạn, kỹ sư chịu trách nhiệm, đọc lại toàn bộ diff từ đầu đến cuối như thể mình chưa từng thấy nó, dù bạn đã đọc từng phase ở Stage 3. Lý do: đọc theo phase giúp bắt lỗi cục bộ, nhưng chỉ đọc trọn diff một lượt liền mạch mới lộ ra vấn đề xuyên-phase — ví dụ convention đặt tên field lệch giữa Phase 1 và Phase 4, hay một thay đổi ở Phase 2 vô tình phá điều kiện mà Phase 3 đang dựa vào.
Quy trình review cuối gồm ba bước. Thứ nhất, đọc git diff main...HEAD toàn bộ, không dùng công cụ tóm tắt — mở từng file, đọc thật. Thứ hai, đối chiếu ngược lại với checklist acceptance criteria ở Stage 1: tick từng dòng, xác nhận bằng hành động cụ thể (không phải "chắc là ổn") — ví dụ "User có thể edit frequency" thì bạn tự vào UI đổi thử daily→weekly và query DB xác nhận field đổi đúng. Thứ ba, dọn lịch sử commit: các commit ở Stage 3 có thể có những commit "fix typo", "address review comment" lặt vặt sau khi sửa finding ở Stage 4 — squash chúng lại theo phase logic để lịch sử git sạch, dễ đọc cho người sau này git blame.
git log --oneline main..HEAD
Nếu thấy 12 commit trong đó có 3 commit chỉ để fix review finding, squash lại còn 5 commit theo đúng 5 phase:
git rebase -i main
Sau squash, chạy lại toàn bộ test suite một lần cuối để chắc chắn rebase không làm hỏng gì, rồi mới merge (qua PR trên GitHub/GitLab, không merge thẳng bằng tay để giữ lại review trail).
npm test && npm run build
gh pr merge --squash --delete-branch
Điểm quan trọng cần nhấn mạnh: nếu ở bước đối chiếu acceptance criteria bạn phát hiện một dòng chưa thực sự đúng — ví dụ "No duplicate job ever appears across two consecutive digests" test giả lập ở Stage 3 chỉ cover crash giữa matching và sending, chưa cover trường hợp cron bị trigger hai lần chồng chéo (double cron trigger do infra) — đừng ngại quay lại Stage 2/3 để bổ sung task, dù đã đến Stage 6. Vòng lặp agentic workflow không tuyến tính tuyệt đối; phát hiện gap ở cuối và quay lại đầu là chuyện bình thường, thậm chí là dấu hiệu review cuối đang làm đúng việc của nó.
Mẹo: Đừng bao giờ tự bỏ qua bước "tự tay xác nhận từng acceptance criterion bằng hành động cụ thể" chỉ vì bạn đã đọc AI review nói pass — checklist ở Stage 1 là hợp đồng với product, không phải với AI, nên chỉ con người ký xác nhận mới có giá trị.
Tổng Kết Toàn Bộ Workflow
Nhìn lại cả sáu giai đoạn, có một mạch xuyên suốt: mỗi giai đoạn tạo ra một artifact (tài liệu/sản phẩm trung gian) làm input cho giai đoạn kế tiếp, và ở mỗi giai đoạn con người luôn giữ vai trò xác nhận/quyết định trước khi đi tiếp. Spec ở Stage 1 là hợp đồng nghiệp vụ, trở thành nguồn tham chiếu cho plan ở Stage 2, cho review ở Stage 4, và cho đối chiếu cuối ở Stage 6. Plan ở Stage 2 định hình ranh giới các checkpoint ở Stage 3. Code sinh ra ở Stage 3 là input cho cả review ở Stage 4 lẫn PR description ở Stage 5. Và Stage 6 là nơi mọi artifact trước đó được đối chiếu lại một lần cuối trước khi feature chính thức vào production.
Cái hay của mô hình này không nằm ở việc AI làm hộ bạn nhiều việc — mà ở chỗ nó buộc bạn tách rõ "việc gì nên giao cho AI làm nháp trước" và "việc gì chỉ con người quyết được". AI rất giỏi sinh spec draft, sinh plan, viết code theo plan, rà lỗi theo checklist, tóm tắt diff thành PR description — tất cả đều là những việc "sinh ra nhiều lựa chọn hợp lý nhanh". Nhưng chọn đúng ràng buộc nghiệp vụ nào là quan trọng (Stage 1), quyết định thứ tự phase nào rủi ro cần làm cẩn thận hơn (Stage 2), xác nhận một checkpoint có thực sự đúng ý trước khi commit (Stage 3), phân biệt cảnh báo AI review nào là false positive (Stage 4), và ký xác nhận cuối cùng feature đã sẵn sàng lên production (Stage 6) — tất cả đều đòi hỏi phán đoán con người mà không AI nào thay được, vì chúng phụ thuộc vào ngữ cảnh nghiệp vụ, rủi ro thật, và trách nhiệm thật ngoài đời.
Feature "Saved Searches with Email Alerts" chỉ là một ví dụ cụ thể để bạn cầm tay chỉ việc lần này. Lần sau khi bạn nhận một feature bất kỳ — dashboard mới cho admin, tích hợp payment gateway, refactor một module legacy — thứ tự sáu bước này không đổi: ép yêu cầu mơ hồ thành spec có acceptance criteria kiểm chứng được, bẻ spec thành plan có checkpoint, thực thi có commit từng chặng, review đa chiều trước khi mở PR, sinh description giúp reviewer đọc nhanh, và tự tay làm lượt soát cuối trước khi merge. Cái thay đổi mỗi lần chỉ là nội dung nghiệp vụ bên trong từng bước, không phải bản thân quy trình.
Mẹo: Lưu spec, plan, và PR description của feature này lại thành một bộ "reference example" trong repo (ví dụ
docs/examples/saved-search-alerts/) — lần sau bắt đầu feature mới, bạn có thể chỉ agent đọc bộ ví dụ này để bắt chước đúng độ chi tiết và văn phong bạn muốn, thay vì giải thích lại từ đầu mỗi lần.
Những điểm chính
- Agentic development workflow là một chuỗi sáu giai đoạn có thứ tự: spec → plan → staged implementation → AI code review → PR description → review cuối của con người — mỗi giai đoạn tạo artifact làm input cho giai đoạn sau.
- Spec tốt bắt đầu từ việc để agent hỏi ngược lại bạn trước khi viết, không phải để agent đoán rồi viết luôn; acceptance criteria phải ở dạng checklist kiểm chứng được bằng hành động cụ thể.
- Plan tốt nhóm task thành các phase commit-độc-lập, có dependency rõ ràng, và gắn nhãn sớm những task đụng side-effect không thể hoàn tác (gửi email, xoá dữ liệu, charge tiền) để biết chỗ nào cần cẩn trọng hơn.
- Staged implementation giới hạn bán kính ảnh hưởng của lỗi trong từng phase: code, verify bằng tay, commit, rồi mới sang phase kế — đừng bao giờ để cả feature chạy một lèo rồi mới bắt đầu kiểm tra.
- AI code review nên chạy trên một session độc lập với session đã viết code, xét đủ bốn chiều correctness/security/performance/architectural fit, và mọi finding phải đi kèm failure scenario cụ thể để bạn tự verify trước khi tin.
- PR description sinh từ diff + commit history + spec gốc giúp reviewer hiểu "tại sao" chứ không chỉ "cái gì đổi" — nhưng phần "Testing" luôn phải do chính bạn xác nhận là đã thực sự chạy, không chỉ nghe agent nói.
- Review cuối cùng luôn thuộc về con người: đọc trọn diff một lượt liền mạch, đối chiếu tay từng acceptance criterion, dọn lịch sử commit trước khi merge — đây là bước duy nhất trong cả sáu bước không nên giao phó hoàn toàn cho AI.
- Quy trình sáu bước này không đổi giữa các feature khác nhau; thứ thay đổi chỉ là nội dung nghiệp vụ cụ thể bên trong mỗi bước — nắm chắc quy trình này chính là kỹ năng cốt lõi của một agentic engineer.