Một batch content tôi từng chạy có tám worker cùng viết tám bài trong một series. Prompt lúc đó nghe hợp lý: “tự tìm bài tiếp theo còn thiếu, giữ seriesOrder đúng”. Tất cả worker đều đọc repo gần như cùng lúc, đều thấy số tiếp theo là 9, và đều viết bài seriesOrder: 9.
Không có lỗi syntax. Không có conflict Git ngay lập tức, vì mỗi worker tạo file khác nhau. Lỗi chỉ hiện ra khi render series nav: tám bài chen cùng một vị trí. Sửa thì dễ, nhưng bài học rất rõ: nếu nhiều agent cùng đọc một nguồn “next available”, đó là race condition.
Từ đó tôi dùng tracker JSON cho những việc lặp và có state. Không phải vì JSON sang. Vì nó đủ đơn giản để agent đọc, đủ strict để validate, và đủ rõ để con người review trong diff.
Tracker giải quyết vấn đề gì
Một tracker tốt trả lời bốn câu hỏi:
- việc nào đã được giao;
- ai hoặc worker nào đang làm;
- file nào thuộc ownership của việc đó;
- trạng thái hiện tại là gì.
Ví dụ tối giản:
{
"tasks": [
{
"id": "post-08",
"status": "done",
"owner": "worker-blog-08",
"files": [
"src/content/blog/ai-coding-thuc-chien-bai-8-git-workflow.md"
],
"updatedAt": "2026-05-25T09:10:00+07:00",
"notes": "Drafted and diff-check passed."
},
{
"id": "post-09",
"status": "in_progress",
"owner": "worker-blog-09",
"files": [
"src/content/blog/ai-coding-thuc-chien-bai-9-agent-progress-tracker.md"
],
"updatedAt": "2026-05-25T09:14:00+07:00",
"notes": "Writing first draft."
}
]
}
Điểm quan trọng không nằm ở schema này. Điểm quan trọng là state được ghi ở một nơi có thể diff, validate, và resume. Nếu chỉ nằm trong chat history, worker mới không biết. Nếu chỉ nằm trong đầu người điều phối, agent không biết. Nếu chỉ nằm trong task board ngoài repo, PR reviewer không thấy liên hệ giữa file và assignment.
Pre-allocate thay vì để agent tự pick
Trong batch song song, tôi không để agent tự chọn số tiếp theo. Parent session hoặc người điều phối phải pre-allocate:
{
"id": "ai-coding-08",
"seriesOrder": 8,
"slug": "ai-coding-thuc-chien-bai-8-git-workflow",
"owner": "worker-08",
"status": "assigned"
}
Sau đó prompt worker:
You own only this file:
src/content/blog/ai-coding-thuc-chien-bai-8-git-workflow.md
Use seriesOrder 8.
Do not create another slug.
Do not edit tracker unless explicitly instructed.
Pre-allocation hơi tốn công ở đầu, nhưng rẻ hơn nhiều so với cleanup collision. Nó cũng giúp review dễ hơn: nếu worker 8 sửa file worker 10, đó là lỗi scope thấy ngay.
Tôi dùng cùng nguyên tắc cho code task. Nếu có ba component cần migration, tôi không prompt “pick remaining components”. Tôi tạo danh sách:
[
{
"id": "migrate-profile-card",
"files": ["src/features/profile/ProfileCard.tsx"],
"status": "assigned"
},
{
"id": "migrate-billing-panel",
"files": ["src/features/billing/BillingPanel.tsx"],
"status": "assigned"
}
]
Agent chỉ nhận item của nó. Không có “tự tìm tiếp”.
Status không cần nhiều, nhưng phải rõ
Tôi thường bắt đầu với năm status:
pending: chưa ai nhận;assigned: đã giao, chưa bắt đầu hoặc chưa có bằng chứng chạy;in_progress: worker đang sửa;blocked: cần người quyết định;done: đã xong validation tối thiểu.
Thêm reviewed hoặc merged nếu tracker sống qua PR lifecycle. Đừng thêm 12 trạng thái ngay từ đầu. State machine càng phức tạp, agent càng dễ cập nhật sai.
Điều cần strict hơn là rule chuyển trạng thái. Ví dụ:
pending -> assigned: parent allocates owner and files
assigned -> in_progress: worker has read file and started edit
in_progress -> blocked: worker cannot proceed without decision
in_progress -> done: worker completed edit and ran required validation
done không có nghĩa là “tôi nghĩ xong”. Nó phải có evidence: command đã chạy, file đã sửa, diff đã check.
Progress loop: đọc, làm, ghi, verify
Loop tôi muốn agent theo rất đơn giản:
- Đọc tracker item của mình.
- Đọc file liên quan.
- Làm đúng scope.
- Chạy validation.
- Ghi lại kết quả.
Nếu được phép cập nhật tracker, worker update ngay sau khi xong. Nếu không được phép, worker báo về parent để parent update. Quan trọng là không để trạng thái bị trễ quá lâu.
Một progress note hữu ích:
{
"status": "done",
"updatedAt": "2026-05-25T10:02:00+07:00",
"notes": "Added branch/commit/PR field guide. Ran git diff --check on owned file."
}
Một progress note kém:
{
"status": "done",
"notes": "done"
}
done không nói worker đã làm gì, đã validate gì, có bỏ qua gì không. Khi resume sau một ngày, note này vô dụng.
JSON phải validate được
JSON tracker hỏng là một dạng outage nhỏ. Agent tiếp theo không parse được, script không đọc được, parent session phải mở file bằng mắt.
Tôi luôn chạy:
jq empty progress/tracker.json
Nếu repo không muốn thêm dependency, có thể dùng Node:
node -e 'JSON.parse(require("fs").readFileSync("progress/tracker.json", "utf8"))'
Với tracker lớn, tôi thích thêm script check invariant:
import fs from "node:fs";
const tracker = JSON.parse(fs.readFileSync("progress/tracker.json", "utf8"));
const seenFiles = new Map();
for (const task of tracker.tasks) {
for (const file of task.files ?? []) {
const previous = seenFiles.get(file);
if (previous) {
throw new Error(`${file} owned by both ${previous} and ${task.id}`);
}
seenFiles.set(file, task.id);
}
}
Invariant quan trọng nhất trong parallel work là “một file chỉ có một owner”. Có ngoại lệ, nhưng phải explicit. Nếu hai worker thật sự cần cùng file, tôi không để họ chạy song song trên file đó.
Tracker không thay Git
Tracker nói kế hoạch và tiến độ. Git nói thay đổi thật. Hai thứ phải khớp, nhưng không thay nhau.
Một item done mà Git diff không có file tương ứng là đáng nghi. Một diff có file không nằm trong tracker cũng đáng nghi. Review tốt là đối chiếu hai bên:
jq -r '.tasks[] | select(.status=="done") | .files[]' progress/tracker.json
git diff --name-only main...HEAD
Nếu danh sách lệch, có thể tracker thiếu update, hoặc worker đã đụng ngoài scope. Cả hai đều cần xử lý trước khi PR.
Tôi từng thấy tracker báo 14 task done, nhưng PR chỉ có 12 file. Hai task còn lại thực ra worker đã dừng vì lint fail, nhưng vẫn mark done. Từ đó tôi không cho done nếu validation chưa chạy hoặc fail mà chưa ghi rõ.
Incident: stale tracker sau khi user sửa tay
Một lỗi khác: con người sửa trực tiếp file nhưng quên update tracker. Agent sau đó đọc tracker, thấy item pending, và rewrite lại file đã được sửa.
Cách giảm rủi ro:
- tracker item có
files; - worker phải đọc file hiện tại trước khi edit;
- nếu file đã tồn tại và nội dung không khớp trạng thái tracker, worker dừng hỏi hoặc báo blocked;
- parent refresh tracker trước batch mới.
Đừng tin tracker tuyệt đối. Nó là source of coordination, không phải source of truth duy nhất. Source of truth cuối cùng vẫn là file thật trong repo.
Khi nào không cần tracker
Không phải task nào cũng cần JSON. Một bug một file, một người làm, một commit nhỏ thì git status đủ. Tracker có cost: phải maintain, validate, review. Nếu task chỉ mất 15 phút và không có parallelism, tracker dễ thành ceremony.
Tôi dùng tracker khi có ít nhất một điều kiện:
- nhiều worker song song;
- nhiều file ownership cần chia trước;
- batch có thể bị ngắt và resume;
- trạng thái cần sống qua nhiều ngày;
- có script hoặc CI đọc tiến độ.
Nếu không có các điều kiện đó, tôi dùng checklist trong issue hoặc PR description.
Chốt lại bằng một nguyên tắc
AI coding làm việc nhanh, nhưng progress trong chat rất dễ bay hơi. Tracker JSON biến progress thành artifact. Nó không cần đẹp. Nó cần đúng, validate được, và phản ánh ownership rõ.
Điều tôi muốn tránh không phải là agent chậm. Tôi muốn tránh việc cả nhóm không biết ai đang sửa gì, file nào đã xong, file nào bị blocked, và vì sao một bài hoặc một component bị làm hai lần. Một tracker nhỏ giải quyết đúng vấn đề đó.