004 · ai · agent · 09.2026 ·8 phút đọc

Skill là luật, không phải gợi ý

Mình dặn con agent viết bài: giọng ngôi thứ nhất, câu ngắn, đừng dùng sáo ngữ. Nó gật, lượt sau làm đúng. Vài lượt nữa thì lọt một đoạn “trong thời đại số”. Mình nhắc, nó xin lỗi, sửa ngay. Rồi lại tái diễn.

Chuyện này không phải lỗi của model. Nó là lỗi của cách mình đặt luật.

Prompt dài lên thì luật loãng đi

Cách làm mặc định của hầu hết người dùng Claude Code hay Codex là nhồi hết vào system prompt: vai trò, quy trình, cấm kỵ, format output. Lúc đầu ổn. Rồi mỗi lần agent làm sai một thứ, mình thêm một dòng. Prompt phình ra.

Vấn đề với cách đó có hai lớp.

Lớp thứ nhất: prompt được đọc một lần, ở đầu session, khi agent còn chưa biết nó sắp phải làm gì. Đến lúc thật sự bắt tay vào việc — mười lăm lượt hội thoại sau, sau khi đã đọc năm file và chạy ba lệnh — thì cái mục “quy trình viết bài” ở giữa prompt chỉ còn là ký ức mờ. Agent không mở lại. Nó làm theo cái nó nhớ là mình đã đọc.

Lớp thứ hai, khó chịu hơn: mình không có cách nào biết nó đã bỏ qua mục nào. Bài ra sai giọng thì mình chỉ biết là sai, không biết nó quên mục nào, cũng không biết lần sau có quên đúng mục đó nữa không. Debug một thứ không để lại vết là chuyện vô vọng.

Sau vài vòng như vậy mình đổi mô hình. Luật không nằm trong prompt nữa. Luật nằm trong file, và trong prompt chỉ còn đúng một câu bắt buộc mở file đó ra.

Luật là “mở file trước khi làm”

Trong phần định danh của mỗi agent trong hệ của mình có nguyên văn dòng này:

Có skill trong .claude/skills/ cho nghiệp vụ chính: MỞ SKILL trước khi làm, không làm theo trí nhớ.

Cả mô hình gói gọn ở đó. Khác biệt so với prompt không phải là nội dung — nội dung vẫn là mấy quy tắc cũ. Khác biệt là thời điểm.

Prompt là thứ agent đã đọc. Skill là thứ agent phải mở lại, ngay tại lúc bắt tay vào việc, khi đã biết chính xác mình sắp làm gì. Nội dung luật đến đúng lúc nó có ích, chứ không phải đến sớm rồi trôi.

Và vì mở skill là một lần đọc file thật, nó để lại vết. Nhìn log của lượt chạy là biết agent có mở hay không. Bài ra sai mà không thấy dòng đọc file thì lỗi là “nó không mở skill” — chứ không phải “nó hiểu sai luật”. Hai lỗi đó cần hai cách sửa khác nhau, và trước đây mình không phân biệt được.

Cấu trúc thư mục đơn giản đến mức nhàm: mỗi nghiệp vụ một thư mục trong .claude/skills/, bên trong một SKILL.md. Cái đáng nói nằm ở chỗ file đó được viết như thế nào.

Một skill thật trông ra sao

Đây là skill của con agent làm tổng biên tập cho blog này — file .claude/skills/brief-and-review/SKILL.md, 44 dòng. Phần checklist duyệt bản thảo:

## Checklist duyệt draft (đối chiếu từng mục, trả góp ý CỤ THỂ — trích nguyên văn → yêu cầu đổi thành gì)
- [ ] Đúng brief: góc nhìn, dàn ý, độ dài. Lệch → chỉ rõ mục nào.
- [ ] Người đọc rút ra được điều gì LÀM ĐƯỢC NGAY?
- [ ] Giọng "tôi", không sáo ngữ AI, mở bài thẳng.
- [ ] Có ví dụ/lệnh/config thật, không mô tả trừu tượng.
- [ ] Không bịa số liệu/benchmark/trích dẫn — nghi ngờ thì bắt writer kiểm chứng hoặc bỏ.
- [ ] Frontmatter đủ: title, description (cụ thể, không nhồi), pubDate, tags tái dùng.
- [ ] Mọi marker `[CẦN PINE XÁC NHẬN]` đã được giải quyết.

Ba thứ làm cái này thành luật chứ không phải lời khuyên.

Từng mục kiểm được. “Viết cho hay” không kiểm được. “Frontmatter đủ title, description, pubDate, tags” thì mở file ra là biết đúng hay sai. Mục nào không trả lời được bằng có/không thì mình viết lại cho đến khi trả lời được.

Có ngưỡng dừng. Cũng trong file đó: Sửa tối đa 2 vòng cho cùng một lỗi; vòng 3 vẫn hỏng → NEEDS_INPUT hỏi Pine. Không có dòng này thì con biên tập và con viết bài cứ đẩy qua đẩy lại: một bên góp ý, một bên sửa, bên kia vẫn thấy chưa đạt, lặp đến hết session. Cả hai đều làm đúng vai, chỉ là không bên nào có quyền tuyên bố dừng. Ngưỡng dừng là chỗ trả quyền đó về cho người.

Ranh giới quyền rõ. KHÔNG tự sửa file, KHÔNG commit/push/deploy/merge. Mọi thay đổi repo qua @blog-dev. Con biên tập chỉ được đọc và góp ý. Muốn đổi gì trong repo phải nhờ con khác. Chuyện phân quyền này ăn khớp với cách mình dựng cả hệ agent trên relay tự host — mỗi agent một danh tính riêng, làm gì cũng thành sự kiện tra lại được.

LESSONS.md: chỗ để bài học chưa chín

Skill là luật đã chốt. Nhưng agent chạy thật thì ngày nào cũng vấp phải thứ chưa có trong luật. Nếu chỗ duy nhất để ghi lại là chính file skill, agent sẽ tự sửa luật của mình. Nên mỗi con trong team có thêm một file LESSONS.md riêng, append-only.

Quy tắc: gặp chuyện mới thì append đúng một dòng, kèm bằng chứng — event id, lệnh đã chạy, hoặc output thật. Không được sửa dòng cũ. Không được đụng vào skill.

Một dòng thật trong LESSONS.md của con viết bài:

Đổi “tôi” → “mình” không được sed mù: bài cũ đã có sẵn “mình” ở nghĩa phản thân (“relay của riêng mình”, “chính mình”). Bằng chứng: grep đếm bo-wordpress 8 “tôi” / 2 “mình”, buzz-team-agent 15 “tôi” / 8 “mình”.

Bài học này không đáng lên thành luật — nó chỉ đúng cho một lần đổi giọng văn. Nhưng nó đáng được ghi lại kèm số đếm, để lần sau ai đọc cũng biết vì sao không nên chạy sed cho việc đó. Đó là chỗ đứng của LESSONS.md: giữa “vừa mới xảy ra” và “đã thành luật”.

Hệ này chạy được một thời gian thì file dày lên. Đếm bằng wc -l ngày 31/08:

agent dòng LESSONS.md
wordsmith 241
cataloger 238
dispatcher 227
curator 94
sentinel 58
blog-editor 12
analyst 8
blog-writer 8
blog-dev 4
blog-seo 4

Đọc được đúng một điều từ bảng này: agent chạy càng lâu thì LESSONS.md càng dày. Năm con trên cùng chạy lâu hơn và va nhiều hơn. Bốn con lo blog thì mới lập nên còn mỏng. Không có kết luận nào sâu xa hơn thế.

Vì sao agent bị cấm sửa skill của chính nó

Đây là phần mình nghĩ nhiều nhất, và cũng là phần dễ bị coi là thừa nhất.

Cuối tháng 8 mình đổi luật giọng văn của blog: bỏ ngôi “tôi”, chuyển sang “mình”, gọi người đọc là “bạn”. Lúc đó cả skill brief-and-review lẫn phần định danh của con biên tập đều đang ghi ngôi “tôi”.

Con agent không sửa file skill. Nó append vào LESSONS.md một dòng, kèm event id của tin nhắn mình chốt luật, và ghi rõ: skill vẫn ghi ngôi “tôi” — luật mới hơn nên ưu tiên, chờ giám tuyển fold vào skill. Rồi nó báo ngược lên cho mình.

Cái giá phải trả là có thật, và mình không định tô hồng nó. Trong lúc chờ mình fold luật mới vào file skill, luật đó chỉ sống trong bộ nhớ của agent. Session reset mà bộ nhớ không nạp lại là luật trôi — agent mở skill ra, đọc thấy ngôi “tôi”, và viết đúng theo cái luật đã bị thay. Cấm tự sửa nghĩa là chấp nhận một khoảng trễ giữa lúc luật đổi và lúc luật vào file.

Mình vẫn chọn cái giá đó, vì đầu kia tệ hơn nhiều.

Nếu agent được sửa skill của chính nó, nó sẽ viết lại luật theo cách nó đang hiểu tại thời điểm đó. Mà cách nó hiểu thì đã lệch sẵn — nếu không lệch thì đâu có sinh ra bài học để ghi. Một lần hiểu sai được ghi thẳng vào file luật là hiểu sai đó thành vĩnh viễn. Session sau mở skill ra, đọc phải bản đã méo, làm theo, rồi lại sửa tiếp. Không ai truy được luật đã đổi lúc nào, vì sao, do ai — vì chính con vi phạm là con cầm bút sửa luật.

Nên ranh giới rất cứng: agent được ghi bằng chứng, người quyết định luật. Bài học nằm trong LESSONS.md chờ ở đó. Đến lúc mình đọc lại, thấy một dòng lặp lại đủ nhiều lần và đủ rõ, mình mới nhấc nó lên thành một mục trong SKILL.md. Việc nhấc đó là một thao tác của người, có chủ ý, và để lại vết trong git.

Đổi độ trễ lấy tính truy vết. Với một hệ mà mình phải tin vào output của nó hằng ngày, đó là cuộc đổi chác đáng.