ai

Harness engineering: anh em đã làm mà chưa biết tên

OpenAI build 1 triệu dòng code không viết tay bằng Codex agents. Harness engineering là cách họ làm — và CLAUDE.md của anh em chính là một harness đang chạy.

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 --fix sau mỗi file write, tsc --noEmit sau 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.