DEV Community

EME GUG
EME GUG

Posted on

Agents Don't Need Memory, They Need Documentation: A Practical AGENTS.md Playbook

Mấy tháng gần đây, mình thấy team nào dùng AI coding agent (Claude Code, Codex CLI, Cursor, Aider...) cũng gặp chung một vấn đề. Hôm nay agent làm rất tốt. Sang hôm sau, nó lại quên sạch, dùng sai convention, chạy sai lệnh test, sửa nhầm file generated. Phản xạ đầu tiên của nhiều người là đi tìm giải pháp "memory": vector DB, plugin nhớ hội thoại, tóm tắt session. Nhưng sau khi thử khá nhiều cách, mình đồng ý với một bài đang hot trên Hacker News: agent không cần memory, nó cần documentation. Bài này chia sẻ cách mình tổ chức docs cho agent trong repo thật, kèm script để docs không bị outdated.

Vì sao memory không giải quyết được vấn đề

Memory kiểu "nhớ lại hội thoại cũ" có ba điểm yếu chết người:

  1. Không kiểm chứng được: bạn không biết agent đang "nhớ" gì, nhớ đúng hay sai. Một quyết định đã bị revert từ tuần trước vẫn có thể nằm trong memory.
  2. Không review được: memory không đi qua pull request, không ai approve.
  3. Không chia sẻ được: memory của máy bạn khác memory của đồng nghiệp, khác luôn memory của agent chạy trên CI.

Documentation trong repo thì ngược lại: nó được version bằng Git, review qua PR, và mọi agent, mọi người đọc cùng một nguồn sự thật. Khi agent làm sai, bạn sửa docs một lần là xong cho tất cả.

flowchart LR
    A[Session mới] --> B{Nguồn context}
    B -->|Memory| C[Tóm tắt hội thoại cũ]
    C --> D[Không review, dễ lỗi thời]
    B -->|Docs trong repo| E[AGENTS.md + docs/agents]
    E --> F[Version bằng Git, review qua PR]
    F --> G[Mọi agent dùng chung một sự thật]

Một ý nữa liên quan tới bài "866 commits trong 5 tuần" trên Dev.to: khi agent viết code nhanh hơn tốc độ team hiểu code, docs chính là chỗ để con người bắt kịp. Viết docs cho agent cũng là viết docs cho chính mình của 3 tháng sau.

Cấu trúc docs mà mình đang dùng

Đừng nhét mọi thứ vào một file 2000 dòng. Agent có context window giới hạn, và file càng dài thì phần quan trọng càng bị "loãng". Mình chia ba tầng:

  • AGENTS.md ở root (hoặc CLAUDE.md, tùy tool, có thể symlink cho nhau): ngắn, dưới 150 dòng, chỉ chứa luật và lệnh.
  • docs/agents/*.md: mỗi module một file, agent chỉ đọc khi cần.
  • docs/adr/: Architecture Decision Records, giải thích vì sao chọn A mà không chọn B.

Đây là một AGENTS.md rút gọn từ một dự án Node.js 22 + PostgreSQL 16 của mình:

# AGENTS.md

## Lệnh bắt buộc
- Cài deps: `pnpm install --frozen-lockfile`
- Test 1 file: `pnpm vitest run src/payment/refund.test.ts`
- Trước khi commit: `pnpm lint && pnpm typecheck`

## Luật cứng
- KHÔNG sửa file trong `src/generated/` (sinh từ `pnpm codegen`)
- Migration mới: `pnpm db:migrate:new <ten>`, không sửa migration cũ
- Tiền tệ lưu bằng integer (đơn vị nhỏ nhất), không dùng float

## Bản đồ module
- Thanh toán: đọc `docs/agents/payment.md` trước khi sửa `src/payment/`
- Auth: đọc `docs/agents/auth.md`
- Tổng quan cấu trúc: `docs/agents/REPO_MAP.md`

## Khi không chắc
- Tìm ADR liên quan trong `docs/adr/` trước khi đề xuất thay đổi kiến trúc
Enter fullscreen mode Exit fullscreen mode

Vài nguyên tắc rút ra sau nhiều lần agent làm hỏng việc:

  • Viết lệnh chính xác, copy-paste được. "Chạy test" là vô dụng. pnpm vitest run <file> thì dùng được ngay.
  • Luật cứng phải kèm lý do ngắn khi luật đó không hiển nhiên. Agent tuân thủ tốt hơn hẳn khi hiểu vì sao.
  • Mỗi lần agent làm sai, thêm đúng một dòng. Đừng viết docs "cho đủ". Docs tốt nhất là docs được viết từ lỗi thật.

Tự sinh bản đồ repo thay vì viết tay

Phần mô tả cấu trúc thư mục là phần lỗi thời nhanh nhất. Mình không viết tay nữa mà sinh bằng script, chạy trong pre-commit hoặc CI:

#!/usr/bin/env bash
# scripts/repo-map.sh - sinh bản đồ repo cho agent
set -euo pipefail

OUT=docs/agents/REPO_MAP.md
{
  echo '# Repo map (auto-generated, đừng sửa tay)'
  echo
  echo '## Thư mục chính (số file)'
  echo '```

'
  git ls-files \
    | grep -vE '(^|/)(__tests__|fixtures|generated)/' \
    | awk -F/ 'NF>2 {print $1"/"$2}' \
    | sort | uniq -c | sort -rn | head -25
  echo '

```'
  echo
  echo '## Entry points'
  git ls-files | grep -E '(^|/)(index|main|server)\.(ts|js)$' | head -20
} > "$OUT"

echo "Updated $OUT"
Enter fullscreen mode Exit fullscreen mode

Dùng git ls-files thay vì tree để tự động bỏ qua mọi thứ trong .gitignore như node_modules, dist. Kết quả là một file vài chục dòng, agent đọc trong vài trăm token là nắm được repo đang có gì.

Chặn docs lỗi thời bằng CI

Docs sai còn tệ hơn không có docs, vì agent sẽ tin nó một cách tuyệt đối. Cách mình làm: mỗi file trong docs/agents/ khai báo nó mô tả path nào qua một dòng covers:. Script Python dưới đây so sánh lần commit cuối của docs với code. Nếu code đã thay đổi quá 14 ngày mà docs chưa được động tới thì CI báo đỏ.

#!/usr/bin/env python3
# scripts/check_doc_drift.py - Python 3.10+
import pathlib, re, subprocess, sys

MAX_LAG_DAYS = 14

def last_commit_ts(path: str) -> int:
    out = subprocess.run(
        ['git', 'log', '-1', '--format=%ct', '--', path],
        capture_output=True, text=True, check=True,
    ).stdout.strip()
    return int(out) if out else 0

stale = []
for doc in sorted(pathlib.Path('docs/agents').glob('*.md')):
    m = re.search(r'^covers:\s*(.+)$', doc.read_text(encoding='utf-8'), re.M)
    if not m:
        continue
    doc_ts = last_commit_ts(str(doc))
    for target in (t.strip() for t in m.group(1).split(',')):
        lag = (last_commit_ts(target) - doc_ts) / 86400
        if lag > MAX_LAG_DAYS:
            stale.append(f'{doc}: chậm {lag:.0f} ngày so với {target}')

print('\n'.join(stale) if stale else 'Docs OK')
sys.exit(1 if stale else 0)
Enter fullscreen mode Exit fullscreen mode

Trong GitHub Actions, nhớ dùng actions/checkout@v4 với fetch-depth: 0. Nếu không, git log chỉ thấy một commit và script luôn báo OK. Mình đã mất nửa buổi chiều vì quên đúng dòng này.

Vòng đời đầy đủ trông như sau:

flowchart TD
    A[Agent nhận task] --> B[Đọc AGENTS.md]
    B --> C[Đọc docs/agents của module liên quan]
    C --> D[Sửa code + chạy lệnh test trong docs]
    D --> E[Mở PR]
    E --> F{CI: repo-map + doc drift}
    F -->|Docs lỗi thời| G[Yêu cầu cập nhật docs]
    G --> E
    F -->|OK| H[Review và merge]

Mẹo nhỏ: thêm vào AGENTS.md một dòng "Nếu bạn thay đổi hành vi của module, cập nhật file docs/agents tương ứng trong cùng PR". Agent hiện đại làm việc này khá đều tay, và CI sẽ bắt những lần nó quên.

Kết luận

Thay vì đi tìm một hệ thống memory phức tạp, hãy coi docs là "bộ nhớ" được version, review và chia sẻ. Những việc bạn có thể làm ngay trong tuần này:

  1. Tạo AGENTS.md dưới 150 dòng với ba phần: lệnh chính xác, luật cứng, bản đồ module. Symlink sang CLAUDE.md nếu team dùng nhiều tool.
  2. Mỗi lần agent làm sai, thêm một dòng vào docs thay vì gõ lại lời nhắc trong chat. Sau hai tuần bạn sẽ có bộ docs sát thực tế hơn bất kỳ template nào.
  3. Tự sinh phần dễ lỗi thời (cấu trúc thư mục, entry points) bằng script như repo-map.sh.
  4. Đưa doc drift check vào CI với fetch-depth: 0, để docs sai không âm thầm làm agent đi lạc.
  5. Ghi lại quyết định kiến trúc bằng ADR. Agent cần biết vì sao, không chỉ cái gì.

Agent sẽ còn thay đổi liên tục, hôm nay là tool này, mai là tool khác. Nhưng một repo có docs rõ ràng thì agent nào vào cũng làm việc tốt, và đồng nghiệp mới vào team cũng vậy.

Top comments (0)