Kỹ năng phân biệt "cái gì nên giao cho agent" và "cái gì phải tự tay làm" chính là ranh giới quyết định giữa một kỹ sư dùng AI để nhân bản năng suất và một kỹ sư dùng AI để nhân bản rủi ro.
Delegation Framework: Khớp Loại Task Với Năng Lực Agent
Nhiều kỹ sư khi mới làm việc với Claude Code hay Cursor rơi vào một trong hai thái cực: hoặc ôm hết mọi việc, chỉ dùng agent để autocomplete từng dòng; hoặc giao mọi thứ, kể cả những quyết định kiến trúc quan trọng, rồi review qua loa vì tin rằng "AI chắc làm đúng". Cả hai đều sai. Delegate (giao việc) hiệu quả không phải là câu hỏi "agent có làm được không" — agent gần như luôn có thể tạo ra code chạy được — mà là câu hỏi "nếu agent làm sai, cái giá phải trả là gì, và tôi có phát hiện ra kịp không".
Framework thực dụng nhất là đánh giá task theo 5 trục, trước khi viết một dòng prompt nào:
1. Reversibility (khả năng đảo ngược). Một PR (pull request) sai có thể revert trong 30 giây. Một migration đã chạy trên production database, xoá mất cột dữ liệu, thì không đảo ngược được trừ khi có backup. Càng khó đảo ngược, mức kiểm soát con người càng phải cao — không phải vì agent kém, mà vì cái giá của một lần sai bị nhân lên gấp nhiều lần biên độ bạn có thể chấp nhận.
2. Blast radius (bán kính ảnh hưởng). Một hàm helper nội bộ, chỉ một module gọi tới, lỗi thì chỉ một chỗ hỏng. Một thay đổi trong middleware xác thực (authentication), hay trong schema database dùng chung, lỗi thì cả hệ thống sập hoặc lộ dữ liệu người dùng. Blast radius rộng đòi hỏi giao việc theo từng bước nhỏ, có checkpoint (điểm kiểm tra), thay vì giao trọn gói.
3. Mức độ well-specified (task đã được đặc tả rõ ràng đến đâu). "Viết unit test cho hàm calculateDiscount hiện có, cover các case trong file discount.spec.ts đã liệt kê" là task cực kỳ rõ ràng — input, output, hành vi mong đợi đều đã tồn tại trong code. "Làm cho tính năng loyalty program hấp dẫn hơn" là một câu mơ hồ, đòi hỏi quyết định sản phẩm mà agent không có đủ ngữ cảnh (context) để đưa ra đúng.
4. Novelty vs. routine (mới lạ hay lặp lại). Boilerplate CRUD (Create-Read-Update-Delete), refactor đổi tên biến hàng loạt, viết lại một tầng data-access theo pattern đã có sẵn trong codebase — đây là việc lặp lại, agent làm cực tốt vì có nhiều pattern tương tự để tham chiếu. Nhưng thiết kế một thuật toán matching mới, hay chọn chiến lược caching cho một hệ thống chưa từng có tiền lệ trong codebase, là việc đòi hỏi tư duy mới, nơi agent dễ "bịa" ra giải pháp nghe hợp lý nhưng sai bản chất.
5. Yêu cầu về architectural judgment (đánh giá kiến trúc). Đây là trục quan trọng nhất và hay bị bỏ qua. Agent không thiếu năng lực viết code đúng cú pháp — nó thiếu khả năng cân nhắc trade-off dài hạn: chọn architecture nào để dễ mở rộng trong 2 năm tới, đánh đổi giữa consistency và availability, hay quyết định service boundary nào hợp lý cho tổ chức của bạn. Những quyết định này cần bối cảnh về roadmap, đội ngũ, và ràng buộc kinh doanh mà agent không có.
Đặt 5 trục này lên nhau, ta có một ma trận thực dụng:
- Full delegation (giao trọn, review nhẹ): viết test cho behavior đã tồn tại, CRUD boilerplate theo pattern có sẵn, refactor có before/after rõ ràng (đổi tên, tách file, đổi cách gọi API nội bộ giữ nguyên hành vi), viết documentation từ code, sửa lỗi lint/type theo rule đã định.
- Partial delegation (giao có giám sát chặt, checkpoint thường xuyên): thêm một feature mới có ảnh hưởng vừa phải tới nhiều file, viết migration có rollback script kèm theo, tích hợp một thư viện mới vào một phần hệ thống.
- Giữ kiểm soát chặt, chỉ dùng agent như trợ lý tư vấn: logic auth/authorization, xử lý payment, migration dữ liệu không thể đảo ngược trên production, quyết định sản phẩm mơ hồ chưa được chốt, thay đổi ảnh hưởng tới compliance/bảo mật (an ninh) dữ liệu người dùng.
Điều quan trọng: framework này không tĩnh. Cùng một loại task — ví dụ "thêm rate limiting" — có thể là full delegation trong một service nội bộ ít người dùng, nhưng phải là partial delegation nghiêm ngặt nếu đó là API công khai đang phục vụ khách hàng trả phí. Bối cảnh (context) của hệ thống luôn là biến số quyết định cuối cùng, framework chỉ là công cụ để bạn đặt câu hỏi đúng trước khi bấm Enter.
Mẹo: Trước khi giao bất kỳ task nào, tự hỏi một câu duy nhất: "Nếu agent hiểu sai yêu cầu 100%, tôi mất bao lâu để phát hiện và bao nhiêu để sửa?" Câu trả lời quyết định mức giám sát cần thiết.
Viết Delegation Prompt Xác Định Đúng Ranh Giới Phạm Vi
Một prompt delegate tốt không phải là mô tả "làm cái gì" càng chi tiết càng tốt — mà là mô tả rõ ranh giới phạm vi (scope boundary): agent được động vào đâu, không được động vào đâu, và khi gặp điểm mơ hồ thì phải dừng lại hỏi hay tự quyết rồi báo cáo. Thiếu ba yếu tố này, ngay cả một agent giỏi cũng có xu hướng "helpful quá tay" — sửa luôn cả những chỗ không ai nhờ, thêm dependency mới không cần thiết, hay tự ý refactor code xung quanh vì "nhìn thấy chưa tối ưu".
Một delegation prompt có cấu trúc tốt thường gồm 5 phần:
- Goal — mục tiêu cuối cùng, viết bằng ngôn ngữ nghiệp vụ, không phải chi tiết kỹ thuật.
- In-scope files/directories — liệt kê rõ những file, thư mục được phép sửa.
- Out-of-scope / do-not-touch — liệt kê rõ những gì tuyệt đối không được đụng vào, kể cả khi "tiện thể" sửa sẽ đẹp hơn.
- Constraints — ràng buộc về dependency (không thêm thư viện mới nếu chưa xin phép), coding convention, performance, security.
- Ambiguity protocol — chỉ dẫn rõ: gặp quyết định không chắc chắn thì dừng và hỏi, hay tự chọn phương án hợp lý nhất rồi flag lại trong response để người review biết chỗ cần chú ý.
Ví dụ, một prompt thiếu ranh giới:
Add caching to the product listing endpoint to make it faster.
Prompt này để agent tự quyết mọi thứ: cache ở tầng nào (in-memory, Redis, CDN), TTL bao lâu, invalidate khi nào, có cần thêm dependency mới không. Agent có thể chọn thêm Redis vào một service vốn chưa có Redis, kéo theo thay đổi docker-compose, biến deployment pipeline phức tạp hơn — toàn bộ nằm ngoài ý định ban đầu của bạn.
So sánh với một prompt có ranh giới rõ ràng:
Goal: reduce p95 latency on GET /api/products by caching the response.
In scope:
- src/routes/products.ts
- src/services/productService.ts
- New test file(s) under src/services/__tests__/
Out of scope — do not modify:
- src/db/ (no schema changes)
- Any file under src/auth/
- package.json — do not add new dependencies
Constraints:
- Use the existing `cacheClient` wrapper in src/lib/cache.ts (already wraps Redis,
already configured in this service). Do not introduce a new caching library.
- TTL: 60 seconds, configurable via existing `config.cache.ttl` if present, otherwise
hardcode 60 and leave a TODO comment referencing this decision.
- Cache key must include the query params (category, page, pageSize) — do not cache
a single global key.
- Preserve existing response shape and status codes exactly.
If ambiguous:
- If you're unsure whether an existing field should be part of the cache key, make
the safest assumption (include it) and note the assumption in your summary at the end.
- Do NOT change error-handling behavior without asking first.
Deliverable: implementation + tests. Stop and show me a summary before touching
any file outside the "in scope" list above.
Prompt thứ hai dài hơn, nhưng mỗi dòng đều cắt giảm một loại rủi ro cụ thể: dùng thư viện có sẵn thay vì tự chọn, giữ nguyên hành vi lỗi, và quan trọng nhất — có một "cửa chặn" (stop-and-show-summary) trước khi agent chạm vào bất cứ thứ gì ngoài phạm vi đã định. Đây chính là khác biệt giữa "ra lệnh" và "giao việc có kiểm soát".
Một điểm hay bị bỏ sót: instruction về ambiguity protocol nên khác nhau tuỳ mức độ rủi ro của task. Với task full-delegation (test, refactor rõ ràng), có thể cho phép agent "tự quyết định hợp lý rồi flag lại" để giữ tốc độ. Với task partial-delegation trở lên (auth, payment, schema), luôn yêu cầu "dừng lại và hỏi trước khi quyết định bất cứ điều gì không được chỉ định rõ trong prompt này".
Mẹo: Viết phần "Out of scope" trước phần "In scope" khi soạn prompt — buộc bản thân nghĩ về rủi ro trước khi nghĩ về công việc, tránh quên liệt kê những chỗ nhạy cảm.
Thực Hành: Giao Triển Khai Feature Với Ranh Giới Rõ Ràng
Hãy đi qua một ví dụ cụ thể: thêm rate limiting cho public API của một service Node.js/Express đang chạy production, phục vụ đối tác bên ngoài gọi vào qua API key.
Bước 1 — Xác định vị trí trên delegation framework. Đây là thay đổi chạm tới tầng middleware dùng chung cho toàn bộ public API (blast radius rộng), ảnh hưởng trực tiếp tới trải nghiệm của đối tác trả phí nếu làm sai (limit quá chặt gây lỗi 429 oan, hoặc quá lỏng không chặn được abuse). Task khá well-specified (rate limiting là pattern quen thuộc, có thư viện chuẩn), nhưng cần architectural judgment ở việc chọn storage cho counter (in-memory sẽ không đúng khi scale nhiều instance). Kết luận: đây là partial delegation — giao chi tiết implementation, nhưng giữ quyền quyết định về storage backend và ngưỡng limit.
Bước 2 — Viết delegation prompt với ranh giới cụ thể:
Goal: add rate limiting to all routes under /api/public/* to protect against abuse
from partner API consumers. We currently have no rate limiting on this service.
Context:
- This service runs 3 instances behind a load balancer (see infra/README.md for
topology — read only, do not modify).
- We already use Redis for session storage; the client is exported from
src/lib/redisClient.ts.
- Partner requests are authenticated via API key, available as req.apiKey after
the existing src/middleware/auth.ts runs.
In scope:
- New file: src/middleware/rateLimiter.ts
- src/app.ts — only to register the new middleware, minimal diff
- New test file: src/middleware/__tests__/rateLimiter.test.ts
- package.json — you MAY add `rate-limit-redis` and `express-rate-limit` if needed,
call this out explicitly in your summary
Out of scope — do not modify:
- src/middleware/auth.ts
- Any route handler files under src/routes/
- infra/ directory
Requirements:
- Limit: 100 requests per minute per API key (not per IP — partners share office IPs).
- Use Redis (via existing redisClient) as the store, so the limit is shared correctly
across all 3 instances. Do not use in-memory store.
- On limit exceeded: return HTTP 429 with JSON body { "error": "rate_limit_exceeded",
"retryAfter": <seconds> }, and set the standard `Retry-After` header.
- Must not add latency > 5ms to the happy path (Redis call should be a single
round-trip, e.g. via a Lua script or an atomic INCR+EXPIRE pattern).
If ambiguous:
- If unsure about applying the limit to health-check or webhook-receiving endpoints
under /api/public/*, EXCLUDE them by default and list which paths you excluded in
your summary, so I can confirm.
- Do not change the 100/min number without asking — that threshold was set by
the product team, treat it as fixed unless I say otherwise.
Deliverable: implementation, unit tests covering (a) under-limit passthrough,
(b) over-limit rejection with correct headers, (c) per-key isolation (key A being
limited doesn't affect key B). Show me a summary of files changed and any
assumptions made before I review the diff.
Bước 3 — Nhận diện một checkpoint response tốt. Sau khi agent chạy xong, một kết quả đáng tin cậy sẽ có dạng tóm tắt rõ ràng: file nào đã tạo/sửa, thư viện nào được thêm và lý do, danh sách path bị loại trừ khỏi rate limit kèm lý do (ví dụ "/api/public/health" và "/api/public/webhooks/stripe" bị loại vì health-check cần luôn pass và webhook đến từ Stripe không mang API key của partner), và một đoạn giải thích ngắn về cách atomic increment tránh race condition khi 3 instance cùng ghi Redis đồng thời. Nếu response chỉ nói "Done, added rate limiting" mà không có breakdown này, đó là dấu hiệu cần hỏi thêm trước khi merge — không phải vì code chắc chắn sai, mà vì bạn không có đủ thông tin để review có trách nhiệm.
Bước 4 — Review có trọng tâm. Với checkpoint như trên, việc review của bạn tập trung vào đúng những chỗ có architectural judgment: có đúng là Redis INCR+EXPIRE atomic không (hay có race condition giữa hai lệnh riêng biệt), ngưỡng 100/phút có áp dụng đúng theo key không theo IP, và danh sách path loại trừ có hợp lý không. Những phần còn lại — cú pháp, cấu trúc file, cách viết test — đã được cover bởi chính prompt ban đầu, không cần bạn đọc từng dòng.
Mẹo: Luôn yêu cầu agent liệt kê rõ path/case nào nó tự loại trừ hoặc tự quyết định — đây là nơi 80% lỗi domain-knowledge ẩn nấp, dễ tìm hơn nhiều so với đọc lại toàn bộ diff.
Giám Sát Agent Giữa Task và Biết Khi Nào Cần Ngắt
Delegate không có nghĩa là "giao rồi biến mất cho tới khi xong". Với những task ở mức partial delegation trở lên, việc quan sát agent hoạt động giữa chừng — qua commentary (những dòng agent tự giải thích nó đang làm gì) và tool call log (agent gọi Read, Edit, Bash ở đâu) — là kỹ năng ngang hàng với việc viết prompt tốt.
Có bốn tín hiệu cảnh báo cụ thể cần để ý:
Chạm vào file ngoài phạm vi đã định. Nếu bạn đã giới hạn "in scope" là src/middleware/ và src/app.ts, nhưng thấy log agent mở src/routes/orders.ts hay infra/terraform/main.tf, đó là tín hiệu đỏ rõ ràng nhất — không cần đoán, cứ thấy là dừng ngay để hỏi tại sao.
Lặp lại cùng một lỗi (looping on the same error). Agent chạy test, fail, sửa, chạy lại, fail cùng kiểu lỗi, sửa theo hướng khác, fail tiếp — nếu vòng lặp này lặp quá 2-3 lần mà không tiến triển, thường là dấu hiệu agent đang thiếu thông tin nền tảng (ví dụ không biết một service khác cũng dùng chung field đó) chứ không phải thiếu năng lực code. Để agent tự loop tiếp thường chỉ tốn thêm token và thời gian, không tự nhiên "đột phá".
Tự đưa ra quyết định kiến trúc vượt quá remit (phạm vi được giao). Ví dụ agent được giao "thêm cache", nhưng giữa chừng commentary xuất hiện kiểu "I'll also refactor the service layer to use a repository pattern for consistency" — đây là agent tự mở rộng phạm vi sang một quyết định kiến trúc không ai yêu cầu. Kể cả khi ý tưởng đó không tệ, nó cần được đề xuất và quyết định riêng, không lẫn vào một PR vốn để làm việc khác.
Scope creep tăng dần (phạm vi phình to từng bước nhỏ). Đây là dạng khó phát hiện nhất vì mỗi bước đều nhỏ và "hợp lý" — sửa một file ngoài scope "vì tiện", rồi thêm một dependency nhỏ "vì cần", rồi đổi luôn một interface dùng chung "vì đằng nào cũng phải sửa". Từng bước riêng lẻ trông vô hại, nhưng cộng dồn lại thành một diff lớn hơn nhiều so với ý định ban đầu, và khó review vì không còn khớp với prompt gốc.
Khi phát hiện một trong bốn tín hiệu này, cách ngắt an toàn nhất không phải là tắt phiên làm việc đột ngột (mất context, mất khả năng hỏi agent nó đang nghĩ gì), mà là gửi một message ngắt giữa chừng, yêu cầu agent dừng thao tác file ngay lập tức và báo cáo trạng thái hiện tại trước khi làm gì tiếp — tương tự nhấn tạm dừng một quy trình sản xuất để kiểm tra, thay vì rút phích cắm.
Stop making further file changes right now. Do not run any more commands that
modify files. Give me:
1. A list of every file you've created or modified so far in this session.
2. A one-paragraph explanation of why you started editing files outside the
original in-scope list (specifically src/routes/orders.ts).
3. The current state of the failing test, if any, without attempting another fix.
Mẹo: Đặt quy tắc cá nhân: cứ sau mỗi 3-5 tool call của agent trong một task partial-delegation, dừng mắt một nhịp để quét qua danh sách file đã đổi — đừng đợi tới khi agent "báo xong" mới kiểm tra lần đầu.
Thực Hành: Ngắt và Khôi Phục Một Task Bị Giao Quá Tay
Tình huống thực tế: bạn giao cho agent task "refactor the order-processing module to improve readability", một prompt nghe hợp lý nhưng phạm vi quá rộng — không có ranh giới file rõ ràng, không có constraint nào về việc thay đổi behavior. Sau vài phút quan sát commentary, bạn nhận ra agent đã: đổi tên hơn chục hàm trong orderService.ts (chấp nhận được), nhưng đồng thời cũng đang sửa logic tính thuế trong taxCalculator.ts (ngoài ý định ban đầu, và đây là logic nhạy cảm), và bắt đầu thêm một dependency mới để "làm code functional hơn". Đây là một task bị over-delegate — phạm vi cho quá rộng ngay từ đầu, và giờ agent đang tự do diễn giải "improve readability" theo hướng vượt xa mong đợi.
Bước 1 — Ngắt ngay, không để agent "làm nốt cho xong". Gửi lệnh ngắt rõ ràng:
Stop all further edits immediately. Do not run any more file-modifying commands.
Report back:
1. Full list of files changed so far, with a one-line description of what changed
in each.
2. Specifically confirm: did you change any logic in taxCalculator.ts, or only
formatting/structure? I need the exact answer, not a summary.
3. Do not attempt to "finish" or "clean up" anything — just report current state.
Bước 2 — Đánh giá cái gì cứu được, cái gì phải rollback. Dựa trên báo cáo, bạn phân loại: việc đổi tên hàm trong orderService.ts là an toàn, giữ nguyên hành vi, review nhanh là merge được. Việc sửa taxCalculator.ts là rủi ro cao — dù agent khẳng định "chỉ refactor cấu trúc, không đổi logic", đây là loại khẳng định bắt buộc phải verify bằng diff thực tế và test, không chấp nhận lời agent nói suông. Dependency mới thêm vào để "functional hơn" — loại bỏ, vì đây là quyết định kiến trúc không ai yêu cầu và không cần thiết cho mục tiêu ban đầu.
Bước 3 — Rollback phần rủi ro bằng git, giữ lại phần an toàn. Dùng git để tách riêng, không revert toàn bộ:
git diff -- src/services/taxCalculator.ts
git checkout HEAD -- src/services/taxCalculator.ts
git diff -- package.json
git checkout HEAD -- package.json package-lock.json
Bước 4 — Re-delegate với prompt hẹp hơn nhiều, tách task thành các phần độc lập. Thay vì một prompt mơ hồ "improve readability" cho cả module, chia lại thành các task nhỏ, mỗi task có ranh giới rõ:
Goal: rename functions in src/services/orderService.ts for clarity only.
No behavior change of any kind.
In scope: src/services/orderService.ts only.
Out of scope: every other file, including src/services/taxCalculator.ts —
do not open or read this file, it is unrelated to this task.
Constraints:
- Renaming and reordering only. Do not change any logic, conditionals, or
return values.
- Run the existing test suite (npm test -- orderService) after your changes
and confirm all tests still pass with no modifications to the test file.
- Do not add any new dependency.
Deliverable: diff + confirmation that test suite passes unchanged.
Nếu vẫn muốn cải thiện taxCalculator.ts, đó phải là một task riêng, delegate ở mức partial (hoặc thậm chí giữ kiểm soát chặt vì đây là logic tính thuế — sai số ở đây ảnh hưởng tiền thật), với review kỹ hơn nhiều so với việc đổi tên hàm thông thường.
Bài học rút ra từ tình huống này không phải là "agent làm sai" — agent chỉ đang lấp đầy khoảng trống mà một prompt mơ hồ để lại, đúng như được huấn luyện để làm: diễn giải "improve readability" theo nghĩa rộng nhất có thể. Vấn đề nằm ở việc giao một task có phạm vi mở cho một module chứa cả code an toàn (đổi tên hàm) lẫn code nhạy cảm (tính thuế) mà không tách bạch ngay từ đầu. Việc ngắt kịp thời, verify bằng diff thay vì tin lời tường thuật, và rollback có chọn lọc thay vì rollback toàn bộ, chính là quy trình khôi phục có kỷ luật giúp bạn giữ được phần công sức tốt trong khi loại bỏ đúng phần rủi ro.
Mẹo: Khi rollback, luôn rollback theo từng file cụ thể (
git checkout HEAD -- <file>) thay vìgit reset --hardtoàn bộ session — tránh mất luôn cả phần việc tốt agent đã làm đúng.
Những điểm chính
- Trước khi delegate, đánh giá task theo 5 trục: reversibility, blast radius, mức độ well-specified, novelty vs. routine, và nhu cầu architectural judgment — kết quả quyết định mức giám sát cần thiết.
- Một delegation prompt tốt luôn có 5 phần: goal, in-scope, out-of-scope, constraints, và ambiguity protocol (agent nên dừng hỏi hay tự quyết rồi flag).
- Task rủi ro cao (auth, payment, migration không đảo ngược, quyết định sản phẩm mơ hồ) nên giữ ambiguity protocol nghiêm ngặt: luôn dừng hỏi, không tự quyết.
- Một checkpoint response tốt phải liệt kê rõ file đã đổi, giả định đã đưa ra, và case nào bị loại trừ — đây là nơi review nên tập trung, không cần đọc lại toàn bộ diff.
- Bốn tín hiệu cần ngắt agent giữa task: chạm file ngoài phạm vi, lặp lại cùng lỗi nhiều lần, tự quyết định kiến trúc vượt remit, và scope creep tăng dần từng bước nhỏ.
- Khi ngắt, yêu cầu agent dừng thao tác file và báo cáo trạng thái — không tắt phiên đột ngột, không để agent "làm nốt cho xong".
- Không tin lời agent tường thuật về việc "chỉ đổi cấu trúc, không đổi logic" — luôn verify bằng
git diffvà test thực tế. - Rollback có chọn lọc theo từng file rủi ro, giữ lại phần việc an toàn, rồi re-delegate phần còn lại với prompt hẹp hơn và ranh giới rõ ràng hơn.