Một spec chỉ thực sự hữu ích khi có thể trả lời được câu hỏi "làm sao biết là xong" — và câu trả lời đó phải rõ ràng đến mức một AI agent, chứ không chỉ con người, có thể tự kiểm tra được.
Điều Gì Khiến Acceptance Criteria "Verifiable By An Agent" (Agent Tự Kiểm Chứng Được)
Trong phát triển phần mềm truyền thống, acceptance criteria (tiêu chí nghiệm thu) thường được viết cho con người đọc và con người đánh giá. Một dòng như "tính năng đăng nhập phải hoạt động đúng" là đủ với một QA có kinh nghiệm, vì họ tự biết cách suy luận ra các trường hợp cần test, tự phán đoán thế nào là "đúng". Nhưng khi người thực thi task là một AI agent — ví dụ Claude Code nhận một spec và tự viết code, tự chạy test, tự quyết định khi nào dừng — thì kiểu tiêu chí mơ hồ này gần như vô dụng.
Vấn đề cốt lõi: agent không có trực giác nghề nghiệp tích lũy qua nhiều năm như một senior engineer. Nó không "biết" rằng "hoạt động đúng" bao gồm cả việc xử lý input rỗng, xử lý race condition, hay trả về đúng HTTP status code. Nếu spec không nói rõ, agent sẽ tự bịa ra định nghĩa "đúng" của riêng nó — và thường là định nghĩa lạc quan, dễ dãi, chỉ đủ để pass qua các test hạnh phúc (happy path) mà nó tự nghĩ ra.
Acceptance criteria "verifiable by an agent" là loại tiêu chí mà một chương trình (hoặc chính agent đó) có thể kiểm tra bằng cách chạy một lệnh, gọi một API, hoặc so sánh một giá trị — không cần con người ngồi đọc và phán đoán chủ quan. Điểm khác biệt nằm ở tính cụ thể và đo lường được:
- Mơ hồ: "API phải xử lý lỗi hợp lý."
-
Verifiable: "Khi request thiếu trường
email, API trả về HTTP 400 với body{ "error": "email is required" }." -
Mơ hồ: "Trang phải load nhanh."
-
Verifiable: "P95 thời gian phản hồi của endpoint
/api/productsphải dưới 200ms với tải 100 request/giây." -
Mơ hồ: "Không được lộ thông tin nhạy cảm."
- Verifiable: "Log ở mọi level không được chứa trường
password,ssn, hoặccreditCardNumberdưới bất kỳ hình thức nào, kể cả đã mask một phần."
Lý do điều này quan trọng đặc biệt với agentic workflow: một AI agent vận hành theo vòng lặp "thực thi → quan sát kết quả → điều chỉnh". Nếu tiêu chí nghiệm thu là thứ agent có thể tự chạy để kiểm tra (một test suite, một script curl, một linter), thì agent có thể tự đóng vòng lặp đó mà không cần con người can thiệp ở giữa. Ngược lại, nếu tiêu chí chỉ tồn tại dưới dạng câu văn mô tả cảm tính, agent buộc phải tự diễn giải — và mọi lần diễn giải sai đều âm thầm trở thành một loại "specification debt" tích lũy vào codebase.
Nói cách khác: acceptance criteria tốt cho agent chính là cầu nối biến spec thành test. Nếu bạn không thể tưởng tượng ra ngay một đoạn code hoặc một câu lệnh để kiểm tra tiêu chí đó là đúng hay sai, thì tiêu chí đó chưa đủ verifiable.
Mẹo: Khi viết một acceptance criterion, hãy tự hỏi "nếu tôi paste câu này cho agent và bảo nó viết một test để kiểm tra, agent có thể viết ngay không hay phải hỏi lại tôi?". Nếu câu trả lời là "phải hỏi lại", hãy cụ thể hóa thêm.
Viết Given/When/Then Criteria Cho Các Tác Vụ Code
Given/When/Then (còn gọi là cú pháp Gherkin, phổ biến trong BDD — behavior-driven development) là định dạng lý tưởng để viết acceptance criteria cho agentic workflow, vì cấu trúc của nó gần như ánh xạ 1:1 sang cấu trúc của một automated test: Given là phần setup/arrange, When là hành động được thực thi (act), Then là assertion (kiểm tra kết quả).
Cấu trúc chuẩn:
- Given: trạng thái ban đầu của hệ thống, dữ liệu, hoặc context.
- When: hành động cụ thể xảy ra (một lời gọi API, một click, một lệnh CLI).
- Then: kết quả có thể quan sát và đo lường được.
Hãy xét ví dụ thực tế: tính năng "áp dụng mã giảm giá (discount code) vào giỏ hàng" trong một hệ thống e-commerce.
Feature: Apply discount code to shopping cart
Scenario: Valid percentage discount code is applied successfully
Given a cart containing 1 item priced at $100.00
And a discount code "SAVE10" exists with type "percentage", value 10, status "active"
When the user applies discount code "SAVE10" to the cart
Then the cart subtotal becomes $90.00
And the response includes a line item "discount: -$10.00"
And the HTTP response status is 200
Scenario: Expired discount code is rejected
Given a discount code "SUMMER22" exists with status "expired"
When the user applies discount code "SUMMER22" to a cart
Then the cart total remains unchanged
And the API returns HTTP 422
And the response body contains { "error": "DISCOUNT_CODE_EXPIRED" }
Scenario: Discount code below minimum cart value is rejected
Given a discount code "BIG50" requires a minimum cart subtotal of $200.00
And the cart subtotal is $150.00
When the user applies discount code "BIG50"
Then the API returns HTTP 422
And the response body contains { "error": "MINIMUM_SUBTOTAL_NOT_MET", "minimumRequired": 200.00 }
Scenario: Two discount codes cannot be stacked
Given the cart already has discount code "SAVE10" applied
When the user applies a second discount code "SAVE20"
Then the API returns HTTP 409
And the response body contains { "error": "DISCOUNT_ALREADY_APPLIED" }
And the cart still reflects only the "SAVE10" discount
Scenario: Discount code is case-insensitive
Given a discount code "SAVE10" exists
When the user applies discount code "save10"
Then the discount is applied successfully
And the HTTP response status is 200
Điểm mấu chốt là mỗi Scenario ở trên gần như đã là một test case hoàn chỉnh — chỉ còn thiếu code thật. Khi đưa spec này cho agent, agent có thể ánh xạ trực tiếp sang một test file:
describe('POST /cart/discount', () => {
it('applies a valid percentage discount code', async () => {
const cart = await createCart([{ price: 100.00, qty: 1 }]);
await createDiscountCode('SAVE10', { type: 'percentage', value: 10, status: 'active' });
const res = await applyDiscountCode(cart.id, 'SAVE10');
expect(res.status).toBe(200);
expect(res.body.subtotal).toBe(90.00);
expect(res.body.lineItems).toContainEqual(
expect.objectContaining({ label: 'discount', amount: -10.00 })
);
});
it('rejects an expired discount code', async () => {
const cart = await createCart([{ price: 50.00, qty: 1 }]);
await createDiscountCode('SUMMER22', { status: 'expired' });
const res = await applyDiscountCode(cart.id, 'SUMMER22');
expect(res.status).toBe(422);
expect(res.body.error).toBe('DISCOUNT_CODE_EXPIRED');
});
});
Đây chính là lý do Given/When/Then trở thành "ngôn ngữ chung" hiệu quả giữa spec và test: nó ép người viết spec phải nghĩ đến state cụ thể, hành động cụ thể, và kết quả đo lường được — thay vì mô tả chung chung như "mã giảm giá phải hoạt động đúng".
Một lưu ý quan trọng khi viết Given/When/Then cho agent: đừng chỉ viết happy path. Trong ví dụ trên, 4/5 scenario là edge case (hết hạn, chưa đạt ngưỡng, đã áp dụng rồi, case-insensitive). Agent thường tự suy ra happy path khá tốt từ tên feature, nhưng edge case thì gần như luôn cần được liệt kê tường minh — nếu không, agent (và cả engineer) đều có xu hướng bỏ sót.
Mẹo: Đặt tên error code dạng constant (
DISCOUNT_CODE_EXPIREDthay vì message tự do "Mã đã hết hạn") ngay trong acceptance criteria. Điều này giúp cả agent lẫn test đều assert vào một giá trị ổn định, không phụ thuộc câu chữ có thể bị đổi khi refactor.
Viết Behavioral Constraint — Performance Budget, Security Rule Và API Compatibility
Acceptance criteria dạng Given/When/Then trả lời câu hỏi "tính năng này có hoạt động đúng theo từng kịch bản không". Nhưng có một lớp yêu cầu khác không gắn với một scenario cụ thể nào, mà áp dụng xuyên suốt toàn bộ task — đó là behavioral constraints (ràng buộc hành vi). Constraint không phải là "cái gì đúng", mà là "cái gì không được vi phạm, bất kể implementation ra sao".
Ba nhóm constraint phổ biến nhất trong thực tế:
1. Performance budget — ngưỡng đo lường được về tốc độ, tài nguyên:
- P95 latency of GET /api/products must stay under 200ms at 100 req/s sustained load.
- Memory usage of the CSV import worker must not exceed 512MB when processing a 100,000-row file.
- Database query count per request to /api/orders/:id must not exceed 3 (no N+1 queries).
- Bundle size added by this feature must not exceed 15KB gzipped.
2. Security rule — ràng buộc về dữ liệu nhạy cảm, quyền truy cập:
- No PII (email, phone, address, national ID) may appear in application logs at any log level.
- All new database columns storing user financial data must be encrypted at rest.
- The new endpoint must enforce the same authorization middleware as existing /api/admin/* routes.
- User input passed to the report-generation SQL query must be parameterized; string concatenation into SQL is prohibited.
3. API compatibility — ràng buộc về việc không phá vỡ hợp đồng đã tồn tại:
- The response schema of /v1/orders must remain backward-compatible: no field may be removed
or have its type changed; new fields must be optional and additive only.
- Existing clients sending requests without the new `discountCode` field must continue
to receive HTTP 200 with unchanged behavior.
- Deprecated endpoint /v1/legacy-cart must continue returning its current response shape
until the documented sunset date.
Khác biệt cốt lõi giữa constraint và acceptance criteria: acceptance criteria được kiểm tra per scenario (mỗi Given/When/Then là một test riêng, pass/fail độc lập), còn constraint là guardrail xuyên suốt — nó phải đúng cho toàn bộ task, không phân biệt scenario nào đang chạy. Một agent có thể pass toàn bộ 5 scenario Given/When/Then của tính năng discount code, nhưng vẫn vi phạm constraint nếu nó vô tình thêm một câu log in ra toàn bộ object user (chứa email) mỗi khi áp mã giảm giá thất bại.
Vì constraint không tự nhiên "lộ ra" trong lúc code chạy một happy path, chúng cần được kiểm tra bằng công cụ riêng: linter tùy chỉnh, static analysis, load test, hoặc contract test (ví dụ dùng schema snapshot cho response API) — chứ không chỉ unit test thông thường. Khi viết spec, nên chỉ rõ luôn constraint sẽ được verify bằng cách nào, để agent biết nó cần chạy thêm loại kiểm tra gì ngoài test suite chức năng.
constraints:
- id: perf-latency-p95
rule: "P95 latency of POST /cart/discount must be under 150ms under normal load"
verify: "run `npm run bench:cart-discount` and check p95 field in output"
- id: sec-no-pii-logs
rule: "No PII fields (email, cardNumber) may appear in logs"
verify: "run `npm run lint:no-pii-logs` — static scan of log statements"
- id: api-backward-compat
rule: "Response schema of GET /cart must remain backward compatible"
verify: "run `npm run test:contract -- cart.schema.json` against snapshot"
Mẹo: Luôn gắn một cách verify cụ thể (lệnh chạy được) cho từng constraint, giống hệt cách bạn gắn test cho acceptance criteria. Một constraint không có cách verify rõ ràng sẽ bị agent — và cả con người — âm thầm bỏ qua khi deadline gấp.
AI Agent Dùng Acceptance Criteria Để Tự Kiểm Chứng (Self-Verify) Như Thế Nào
Đây là phần biến acceptance criteria từ "tài liệu tham khảo" thành "công cụ vận hành" trong agentic workflow. Một agent như Claude Code, khi nhận một spec có acceptance criteria viết tốt, có thể thực hiện một vòng lặp tự kiểm chứng (self-verification loop) độc lập, không cần con người ngồi review từng bước:
- Đọc acceptance criteria trước khi viết code implementation — agent phân tích từng Given/When/Then, và trước tiên viết ra test case tương ứng (đây chính là tinh thần TDD — test-driven development — áp dụng cho agent).
- Chạy test suite — ở giai đoạn này test sẽ fail vì implementation chưa tồn tại (red).
- Viết implementation tối thiểu để test pass (green).
- Chạy lại toàn bộ test suite cộng với các lệnh verify constraint (lint, benchmark, contract test).
- Nếu có test fail hoặc constraint bị vi phạm — agent đọc lỗi, tự sửa code, quay lại bước 4. Lặp lại đến khi mọi thứ pass.
- Chỉ báo "done" khi tất cả acceptance criteria và constraint đã được verify bằng lệnh thực tế — không phải khi agent "cảm thấy" code đã đúng.
Giá trị lớn nhất ở đây là bước 6: acceptance criteria được viết dưới dạng chạy được (runnable) chính là cơ chế chống lại hiện tượng agent "hallucinate xong việc" — tức là agent tuyên bố hoàn thành task trong khi thực chất chưa test kỹ, hoặc bỏ sót edge case. Nếu tiêu chí "xong" chỉ là một câu mô tả mơ hồ, agent (giống người, nhưng thiếu kinh nghiệm để tự nghi ngờ) rất dễ dừng lại sớm. Nhưng nếu "xong" được định nghĩa là "lệnh npm test và npm run verify:constraints đều exit code 0", thì agent buộc phải chạy các lệnh đó và không thể giả vờ.
Một ví dụ prompt thực tế để chỉ đạo agent tự implement và tự verify:
You are implementing the "apply discount code to cart" feature.
Acceptance criteria (Given/When/Then) are in `specs/discount-code.feature`.
Behavioral constraints are in `specs/discount-code.constraints.yaml`.
Instructions:
1. Before writing any implementation code, write automated tests that map
directly to each Given/When/Then scenario in the spec file. Do not skip
any scenario, including edge cases.
2. Run the tests and confirm they fail (red) before implementation exists.
3. Implement the minimal code needed to make the tests pass.
4. Run `npm test -- discount-code` and `npm run verify:constraints` after
every change. Do not proceed to the next scenario until the current one
is green.
5. Do NOT declare the task complete until:
- Every scenario in the .feature file has a passing automated test.
- `npm run verify:constraints` exits 0 with no violations.
- You have pasted the full test output (not a summary) as evidence.
6. If any test or constraint check fails after 3 fix attempts, stop and
report the exact failure — do not keep guessing silently.
Điều quan trọng trong prompt này là mục 5 và 6: yêu cầu agent trích dẫn output thật (không phải tóm tắt bằng lời), và có điều kiện dừng rõ ràng khi bế tắc. Đây là cách thực tế để biến "tự tin báo cáo xong việc" (một điểm yếu phổ biến của LLM) thành hành vi có bằng chứng kiểm chứng được.
Mẹo: Luôn yêu cầu agent paste nguyên văn output của lệnh test/verify vào câu trả lời cuối, thay vì chỉ nói "tests pass". Việc này buộc agent phải thực sự chạy lệnh, và giúp bạn review nhanh hơn nhiều so với việc tự chạy lại từ đầu.
Thực Hành: Viết Và Sử Dụng Acceptance Criteria Mà Agent Có Thể Tự Kiểm Chứng
Hãy đi qua toàn bộ quy trình với một tính năng nhỏ nhưng đủ thực tế: bulk CSV import với validate từng dòng (row-level validation) — một tính năng cực kỳ phổ biến trong các hệ thống backoffice, admin panel, hoặc công cụ import dữ liệu khách hàng.
Bước 1 — Xác định phạm vi tính năng. Người dùng upload một file CSV chứa danh sách nhân viên (name, email, department, salary). Hệ thống phải validate từng dòng độc lập, import các dòng hợp lệ, báo cáo chi tiết các dòng lỗi — không được để một dòng lỗi làm hỏng toàn bộ batch.
Bước 2 — Viết acceptance criteria dạng Given/When/Then:
Feature: Bulk CSV import with row-level validation
Scenario: All rows valid — full import succeeds
Given a CSV file with 3 valid rows (name, email, department, salary all present and well-formed)
When the admin uploads the file to POST /admin/import/employees
Then the API returns HTTP 200
And the response body reports { "imported": 3, "failed": 0, "errors": [] }
And all 3 employees exist in the database afterward
Scenario: Partial failure — invalid rows are skipped, valid rows still import
Given a CSV file with 5 rows, where row 2 has an invalid email ("not-an-email")
And row 4 has a negative salary (-500)
When the admin uploads the file
Then the API returns HTTP 207
And the response reports { "imported": 3, "failed": 2 }
And the errors array contains:
| row | field | reason |
| 2 | email | INVALID_EMAIL_FORMAT |
| 4 | salary | SALARY_MUST_BE_POSITIVE |
And rows 1, 3, 5 exist in the database; rows 2 and 4 do not
Scenario: Missing required column in header
Given a CSV file whose header is missing the "email" column
When the admin uploads the file
Then the API returns HTTP 422
And the response body contains { "error": "MISSING_REQUIRED_COLUMN", "column": "email" }
And no rows are imported
Scenario: Empty file
Given a CSV file with only a header row and no data rows
When the admin uploads the file
Then the API returns HTTP 200
And the response reports { "imported": 0, "failed": 0, "errors": [] }
Scenario: File exceeds row limit
Given a CSV file with 10,001 data rows
And the configured maximum is 10,000 rows per import
When the admin uploads the file
Then the API returns HTTP 413
And the response body contains { "error": "ROW_LIMIT_EXCEEDED", "maxRows": 10000 }
And no rows are imported
Scenario: Duplicate email within the same file
Given a CSV file where row 2 and row 7 both have email "a@x.com"
When the admin uploads the file
Then row 2 is imported successfully
And row 7 fails with { "row": 7, "field": "email", "reason": "DUPLICATE_EMAIL_IN_FILE" }
Bước 3 — Viết behavioral constraints đi kèm:
constraints:
- id: perf-large-file
rule: "Importing a 10,000-row file must complete within 30 seconds"
verify: "run `npm run bench:csv-import -- fixtures/10k-rows.csv`"
- id: sec-no-raw-csv-in-logs
rule: "Raw CSV row content (which may contain PII) must never be written to logs"
verify: "run `npm run lint:no-pii-logs`"
- id: atomicity-per-row
rule: "A failure in one row must never roll back or block successfully validated rows"
verify: "covered by the partial-failure scenario test; must remain green"
- id: no-new-heavy-deps
rule: "Do not add a new CSV parsing library; use the existing `csv-parse` dependency already in package.json"
verify: "manual review of package.json diff"
Bước 4 — Viết prompt giao cho agent, gắn cả acceptance criteria lẫn constraint, yêu cầu tự implement và tự verify:
Implement the "bulk CSV import with row-level validation" feature for
POST /admin/import/employees.
Full acceptance criteria: specs/csv-import.feature (Given/When/Then, 6 scenarios).
Behavioral constraints: specs/csv-import.constraints.yaml (4 constraints).
Requirements:
1. Write an automated test for every scenario in csv-import.feature BEFORE
writing implementation code. Use the existing test framework (Jest) and
the fixtures in test/fixtures/csv-import/.
2. Implement row-level validation such that one invalid row never blocks
or rolls back other valid rows in the same file (see constraint
`atomicity-per-row`).
3. Use the existing `csv-parse` library already in package.json. Do not
add a new CSV parsing dependency (see constraint `no-new-heavy-deps`).
4. After implementation, run in order:
- `npm test -- csv-import` (all 6 scenarios must pass)
- `npm run lint:no-pii-logs`
- `npm run bench:csv-import -- fixtures/10k-rows.csv` (must finish < 30s)
5. Only report the task as done after pasting the full output of all three
commands above, showing passing/green results. If any command fails,
fix the root cause and re-run all three again — do not report partial
success as done.
6. If you find the spec ambiguous on any scenario, stop and ask rather
than guessing the behavior.
Với prompt này, agent có một lộ trình rõ ràng: viết test trước, implement, chạy đủ 3 loại kiểm tra (functional test, security lint, performance benchmark), và chỉ dừng khi có bằng chứng cụ thể. Đây chính là cách acceptance criteria + constraint biến một task mơ hồ ("làm tính năng import CSV") thành một hợp đồng có thể tự động hóa hoàn toàn quy trình implement-verify.
Mẹo: Luôn bao gồm ít nhất một edge case "khó nhằn" (duplicate trong cùng file, file rỗng, vượt giới hạn) trong bước thực hành. Đây là những case agent (và cả engineer) hay bỏ sót nếu spec không liệt kê tường minh.
Sự Khác Biệt Giữa Output Criteria Và Process Constraint
Có một phân biệt quan trọng thường bị nhập nhằng: output criteria (tiêu chí về kết quả) mô tả cái gì phải đúng khi task hoàn thành, còn process constraints (ràng buộc về quy trình) mô tả cách agent được phép hành xử trong lúc thực hiện task — bất kể kết quả cuối cùng có đúng hay không.
Acceptance criteria (Given/When/Then) và behavioral constraint (performance, security, API compatibility) ở các phần trên đều thuộc nhóm output criteria: chúng chỉ quan tâm trạng thái quan sát được ở cuối — response trả về gì, database chứa gì, latency đo được bao nhiêu. Agent có thể đạt được các output đó bằng bất kỳ cách nào nó muốn.
Nhưng trong thực tế vận hành agent trên một codebase thật, đôi khi chỉ kiểm soát output là không đủ. Có những rủi ro nằm ở cách agent làm việc, không phải ở kết quả cuối:
- Agent có thể đạt đúng output nhưng sửa lan man sang 15 file không liên quan, khiến diff không thể review.
- Agent có thể "tự tiện" cài thêm 3 package mới để giải quyết task nhanh hơn, tạo ra rủi ro supply chain không ai duyệt.
- Agent có thể gộp toàn bộ thay đổi vào một commit khổng lồ, khiến việc rollback về sau gần như bất khả thi.
- Agent có thể xóa hoặc sửa test hiện có để "làm cho suite pass" thay vì sửa code cho đúng.
Đây là lý do cần thêm process constraint — ràng buộc về hành vi trong lúc thực thi, ví dụ:
- Do not modify any file outside the `src/payments/` directory.
- Do not add, remove, or upgrade any dependency in package.json without
explicit confirmation from a human reviewer first.
- Commit in small, logically separated increments (one commit per scenario
implemented), not a single large commit at the end.
- Never modify or delete an existing passing test in order to make the
suite green; if an existing test appears wrong, flag it instead of
changing it silently.
- Always run the full existing test suite (not just new tests) before
declaring the task complete, to catch regressions.
Output criteria trả lời "agent có làm đúng việc không". Process constraint trả lời "agent có làm việc đó đúng cách không". Hai loại này bổ trợ cho nhau chứ không thay thế được nhau: một agent tuân thủ process constraint hoàn hảo (commit nhỏ, không đụng file ngoài phạm vi, không thêm dependency) nhưng vẫn có thể giao ra code sai logic nếu không có output criteria rõ ràng để tự test. Ngược lại, một agent đạt đúng mọi output criteria nhưng bỏ qua process constraint có thể tạo ra một diff không ai review nổi, âm thầm thêm rủi ro bảo mật, hoặc phá vỡ khả năng rollback an toàn — những vấn đề không hiện ra trong bất kỳ test nào.
Trong thực hành spec-driven development, một spec trưởng thành nên có cả hai phần tách biệt rõ ràng: một mục "Acceptance Criteria & Constraints" định nghĩa kết quả, và một mục "Working Constraints" (hoặc đưa vào system prompt / CLAUDE.md của agent) định nghĩa luật chơi trong lúc làm việc. Gộp lẫn hai loại này vào chung một danh sách khiến người đọc — và cả agent — khó phân biệt đâu là thứ cần verify bằng test, đâu là thứ cần tuân thủ như một quy tắc ứng xử.
Mẹo: Nếu một yêu cầu bắt đầu bằng "agent phải/không được làm..." nhưng không thể viết thành một assertion trong test, gần như chắc chắn đó là process constraint, không phải acceptance criterion — hãy đặt nó vào đúng chỗ (ví dụ CLAUDE.md hoặc phần hướng dẫn vận hành của spec) thay vì cố nhét vào Given/When/Then.
Những điểm chính
- Acceptance criteria chỉ có giá trị với agentic workflow khi verifiable — nghĩa là có thể kiểm tra bằng một lệnh/script cụ thể, không cần con người phán đoán chủ quan.
- Given/When/Then là định dạng lý tưởng cho acceptance criteria vì nó ánh xạ gần như 1:1 sang cấu trúc test tự động (arrange/act/assert), và ép người viết phải cụ thể hóa cả happy path lẫn edge case.
- Behavioral constraint (performance budget, security rule, API compatibility) khác acceptance criteria ở chỗ nó là guardrail xuyên suốt toàn task, không gắn với một scenario riêng — và cần được gắn kèm cách verify cụ thể.
- Vòng lặp self-verify của agent (viết test trước → implement → chạy test và constraint check → sửa đến khi green → chỉ báo done kèm bằng chứng) chính là cơ chế thực tế chống lại việc agent "báo cáo xong việc" khi chưa thực sự xong.
- Process constraint (không sửa file ngoài phạm vi, không tự thêm dependency, commit nhỏ) bổ trợ cho output/acceptance criteria — một agent cần cả hai để giao việc vừa đúng kết quả, vừa an toàn và dễ review trong quá trình thực hiện.
- Khi viết spec cho agentic workflow, luôn tách rõ ba lớp: acceptance criteria (Given/When/Then per scenario), behavioral constraints (guardrail đo lường được toàn task), và process constraints (luật chơi trong lúc agent làm việc).