Tháng 2 năm nay mình đọc bài OpenAI về việc họ ship 1 triệu dòng production code không dòng nào viết tay. Con số ấn tượng, nhưng cái mình để ý là tên họ đặt cho cái framework đằng sau — harness. Đọc xong mình nhìn lại đống CLAUDE.md và hooks mình đang có, thấy mình đã làm cái này từ lâu rồi.
Agent = Model + Harness
Cách OpenAI nhìn một AI agent:
Agent = Model + Harness
Model là phần AI — GPT-4o, Claude Sonnet, Gemini. Phần này mình không build, chỉ chọn và dùng thôi.
Harness là phần còn lại: context, tools, constraints, feedback loops, lifecycle — tất cả những gì anh em xây xung quanh model để nó làm đúng việc. Khác với model, harness hoàn toàn trong tầm tay mình.
Nói nôm na: model quyết định AI “thông minh” đến đâu, harness quyết định nó có “ngoan” không.
harness/
├── context/ # CLAUDE.md, system prompts, conventions
├── tools/ # File read/write, bash, search — phân permission tier
├── sensors/ # Linters, tests, coverage checks
└── lifecycle/ # Context window management, handoff protocol
Anh em đã build harness mà chưa biết
Ví dụ điển hình nhất là CLAUDE.md — file mà nhiều anh em dùng Claude Code đã quen:
# CLAUDE.md
## Stack
Next.js 14, TypeScript, Prisma + PostgreSQL, Tailwind.
Deploy trên Vercel, branch main = production.
## Code style
- Dùng `const` thay `let` khi không cần reassign
- Không dùng `any`, phải có type rõ ràng
- Error handling: throw Error với message tiếng Anh, log tiếng Việt để debug
## Không được làm
- Không xóa migration cũ
- Không commit trực tiếp lên main
- Không dùng `console.log` trong production code
Đây là context layer của harness. Viết một lần, AI cứ theo đó mỗi khi làm việc trong project — không cần nhắc lại từng session. Mỗi lần anh em ngồi viết CLAUDE.md, custom instruction cho Cursor hay system prompt là đang build cái này rồi đó.
Skills trong Claude Code cũng vậy — blog-write.md, blog-review.md là những quy trình đóng gói vào harness, gọi tên là chạy.
Sensors là feedback loop của harness
Martin Fowler gọi sensors là phần quan trọng nhất của harness: những gì đo output của agent và phản hồi lại để nó tự điều chỉnh.
Ví dụ đơn giản nhất là hook trong Claude Code:
{
"hooks": {
"PostToolUse": [{
"matcher": "Write",
"hooks": [{
"type": "command",
"command": "eslint $CLAUDE_TOOL_INPUT_FILE_PATH --fix"
}]
}]
}
}
Mỗi lần AI write file, eslint --fix tự chạy và trả kết quả về. AI thấy lỗi và tự sửa — anh em không cần nhắc “check linting nhé”.
Permission tier hoạt động ngược lại — không đo output mà chặn input nguy hiểm ngay từ đầu:
{
"permissions": {
"allow": ["Read", "Grep", "Glob"],
"deny": ["Bash(git push --force*)"]
}
}
Điểm mấu chốt: những rule này nằm trong harness, không phải trong model. Đổi sang model mới nào đi nữa, behavior vẫn như cũ.
Three-agent pattern khi task đủ lớn
Một agent duy nhất với task lớn thường không ổn. Context window tích lũy lịch sử thì agent bị phân tâm, output kém dần. Anthropic giải quyết bằng cách chia thành ba agent riêng biệt:
# Planner — decompose spec thành tasks rời
# Mỗi task phải self-contained: agent mới pick up cold vẫn hiểu
def planner(spec: str) -> list[Task]:
return tasks # → task_list.json
# Generator — implement từng task trong một context window sạch
# Context reset sau mỗi task: tránh "context anxiety"
def generator(task: Task) -> Artifact:
return artifact # → code + claude-progress.txt
# Evaluator — grade output bằng Playwright MCP
# Không share context với Generator: tránh self-evaluation bias
def evaluator(artifact: Artifact) -> Feedback:
return feedback # → specific critiques, pass/fail
Generator reset context sau mỗi task vì lý do đơn giản: context cũ tích lũy thì agent bị nhiễu. Mỗi task mới cần context sạch, chỉ có task hiện tại trong đó.
Evaluator tách riêng vì nếu để Generator tự check output của nó, nó sẽ nói “ok rồi” hầu như lúc nào cũng vậy. Evaluator không biết gì về quá trình build — nên nó khắt khe hơn nhiều.
Kết
- CLAUDE.md, hooks, skills và permission settings anh em đang có đã là một harness. Không cần làm lại từ đầu — cải thiện từng layer có chủ đích hơn là được.
- Thêm sensors vào:
eslint --fixsau mỗi file write,tsc --noEmitsau mỗi TypeScript change. Agent tự điều chỉnh, không cần anh em nhắc. - Task đủ lớn thì tách Planner và Evaluator ra khỏi Generator. Context sạch và self-evaluation bias là vấn đề thực, không phải lý thuyết.