·

Tiếng Việt: Dynamic Context Assembly

Dynamic Context Assembly

Với một repository vài nghìn file, bạn không thể nhét toàn bộ codebase vào context window — vấn đề không còn là "AI có hiểu code không" mà là "AI có tìm đúng đoạn code cần đọc trước khi trả lời không".

RAG Nghĩa Là Gì Khi Codebase Chính Là Tài Liệu

RAG (Retrieval-Augmented Generation) vốn được biết đến nhiều nhất trong bối cảnh chatbot hỏi đáp tài liệu: hệ thống chẻ nhỏ (chunk) một tập PDF hay wiki nội bộ, nhúng (embedding) từng đoạn vào một vector database, rồi khi có câu hỏi thì tìm những đoạn gần nghĩa nhất để nhét vào prompt cho LLM. Khi áp dụng ý tưởng này vào công việc lập trình, "tài liệu" không còn là văn bản tĩnh nữa — nó là chính codebase của bạn, thứ thay đổi theo từng commit, có cấu trúc lồng nhau (module, class, function gọi lẫn nhau), và có ngữ nghĩa phụ thuộc chặt vào ngôn ngữ lập trình.

Điểm khác biệt cốt lõi so với RAG cho document truyền thống nằm ở ba chỗ. Thứ nhất, đơn vị truy xuất (retrieval unit) hợp lý cho code thường không phải là một đoạn văn bản cắt theo độ dài cố định, mà là một function, một class, hoặc một file — những ranh giới có ý nghĩa cú pháp và ngữ nghĩa rõ ràng. Thứ hai, code có quan hệ tham chiếu tường minh: một function gọi function khác, một class kế thừa class khác, một biến được import từ module khác — những liên kết này không "trôi nổi" trong không gian ngữ nghĩa như văn bản tự nhiên, mà có thể lần theo bằng cách phân tích cú pháp (parsing) chính xác. Thứ ba, độ chính xác đòi hỏi cao hơn nhiều: sai một dấu ngoặc, thiếu một tham số, hay nhầm signature của function là code không chạy được, trong khi văn bản tự nhiên vẫn "đọc được" dù thiếu sót một vài chi tiết.

Vì vậy, khi một AI coding tool nói nó dùng RAG cho code, thực chất nó đang kết hợp nhiều kỹ thuật retrieval khác nhau, không chỉ thuần vector search dựa trên embedding. Nó có thể kết hợp cả tìm kiếm theo từ khóa (keyword search, ví dụ dùng công cụ như ripgrep), phân tích symbol thông qua AST (Abstract Syntax Tree — cây cú pháp trừu tượng biểu diễn cấu trúc code sau khi parse), và cây thư mục (file tree) để hiểu bố cục dự án. Hiểu rõ điều này quan trọng vì nó thay đổi cách bạn ra prompt: bạn không chỉ đang "hỏi một câu hỏi ngữ nghĩa" mà còn đang điều khiển một pipeline tìm kiếm hỗn hợp, và biết cách "nói chuyện" với pipeline đó — bằng tên file, tên function, từ khóa chính xác — sẽ cho kết quả tốt hơn nhiều so với một câu hỏi mơ hồ.

Một hệ quả thực tế: retrieval-based context không thay thế được static context (như CLAUDE.md đã học ở bài trước), nó bổ sung. Static context cho agent biết "quy tắc chung của dự án là gì", còn dynamic retrieval cho agent biết "đoạn code cụ thể liên quan đến yêu cầu này nằm ở đâu". Một agent giỏi cần cả hai: convention từ file cấu hình, và khả năng tự đi tìm code liên quan khi convention không đủ chi tiết.

Mẹo: Khi agent trả lời sai vì "không biết" một pattern nào đó trong dự án, đừng vội nghĩ là model yếu — nhiều khả năng là bước retrieval chưa tìm đúng file. Hãy tự hỏi: nếu tôi là agent, tôi sẽ search từ khóa gì để tìm ra đoạn code đó? Nếu bạn không nghĩ ra từ khóa hợp lý, agent cũng vậy.

Các AI Coding Tool Index Và Search Repository Của Bạn Như Thế Nào

Mỗi công cụ AI coding phổ biến hiện nay — Claude Code, Cursor, GitHub Copilot — đều xây dựng một lớp retrieval riêng phía sau, và cách chúng thực hiện việc này khác nhau khá nhiều dù nhìn từ ngoài có vẻ giống nhau.

Claude Code không xây một vector index cố định cho toàn bộ repo từ đầu như một số công cụ khác. Thay vào đó, nó hoạt động theo mô hình "agentic search": khi nhận một yêu cầu, agent tự quyết định dùng các tool như Grep, Glob, Read để dò tìm trong thời gian thực, giống hệt cách một kỹ sư thực thụ mở terminal gõ grep -r "getUserById" rồi mở file lên đọc. Cách tiếp cận này có ưu điểm là luôn phản ánh đúng trạng thái code mới nhất (không có index bị lỗi thời), và cho phép agent "suy luận" về việc nên tìm gì tiếp theo dựa trên kết quả tìm kiếm trước đó — một quá trình lặp (iterative) giống con người debug hơn là một lần truy vấn vector duy nhất. Nhược điểm là nó tốn nhiều lượt gọi tool hơn, và chất lượng phụ thuộc vào việc agent chọn từ khóa search có đúng không.

Cursor và một số extension khác (Copilot ở một mức độ nào đó) xây dựng thêm một index ngữ nghĩa: toàn bộ file trong repo (hoặc các file được chọn) được chia nhỏ, nhúng thành vector, lưu vào một vector store cục bộ hoặc trên cloud. Khi bạn gõ câu hỏi, hệ thống nhúng câu hỏi thành vector rồi tìm các đoạn code gần nghĩa nhất bằng cosine similarity, sau đó ghép các đoạn này vào context trước khi gửi cho model. Cách này nhanh hơn cho các câu hỏi kiểu "tìm đoạn code làm việc X" khi bạn không nhớ chính xác tên hàm, vì embedding có thể bắt được sự tương đồng ngữ nghĩa mà keyword search bỏ lỡ (ví dụ bạn hỏi "chỗ nào xử lý thanh toán" thì nó có thể tìm ra function tên processCheckout dù không có từ "thanh toán" nào trong code). Nhược điểm là index cần được cập nhật liên tục khi code thay đổi, và có độ trễ (staleness) nhất định — bạn vừa sửa code xong, index có thể chưa kịp cập nhật.

Bên cạnh embedding, nhiều công cụ còn dùng phân tích symbol dựa trên AST hoặc Language Server Protocol (LSP) — cùng chuẩn giao tiếp mà các IDE như VS Code dùng để cung cấp tính năng "go to definition", "find references". Lớp này cho phép agent trả lời chính xác các câu hỏi cấu trúc như "function này được gọi ở những đâu", "class này kế thừa từ đâu" — những câu hỏi mà embedding search thuần túy trả lời kém vì nó dựa trên độ tương đồng ngữ nghĩa chứ không dựa trên quan hệ tham chiếu thực sự trong code.

Điều quan trọng cho bạn với tư cách người dùng: biết công cụ mình đang dùng nghiêng về loại retrieval nào sẽ giúp bạn ra prompt hiệu quả hơn. Với Claude Code (agentic search), việc gợi ý rõ agent nên bắt đầu tìm ở đâu ("tìm trong thư mục src/api/", "grep từ khóa PaginationParams") tiết kiệm rất nhiều lượt gọi tool và tăng độ chính xác. Với công cụ dựa nhiều vào embedding, việc mô tả ý nghĩa chức năng rõ ràng (thay vì chỉ tên biến) lại hiệu quả hơn.

Mẹo: Đừng coi tất cả AI coding tool là "hộp đen giống nhau". Hãy dành 15 phút đọc tài liệu retrieval của công cụ bạn dùng hàng ngày — biết nó dùng agentic search hay embedding index sẽ thay đổi cách bạn viết prompt để tận dụng đúng cơ chế đó.

Chiến Lược Manual Retrieval Mà Bạn Tự Kiểm Soát

Dù công cụ có retrieval pipeline tự động tốt đến đâu, bạn — với tư cách engineer — vẫn nên chủ động can thiệp vào bước tìm kiếm thay vì phó mặc hoàn toàn cho agent tự đoán. Đây là những kỹ thuật manual retrieval hiệu quả nhất trong thực tế.

Explicit file mentions. Hầu hết công cụ hỗ trợ cú pháp @đường/dẫn/file.ts hoặc tương đương để chỉ định chính xác file cần đưa vào context, bỏ qua hoàn toàn bước tìm kiếm mơ hồ. Đây là cách nhanh nhất và chính xác nhất khi bạn đã biết rõ file liên quan. Ví dụ:

Xem cách @src/controllers/UserController.ts implement validation,
rồi áp dụng pattern tương tự cho @src/controllers/OrderController.ts

Grep-first workflow. Thay vì để agent tự quyết định search gì, bạn có thể yêu cầu agent chạy một lệnh search cụ thể trước khi làm bất cứ việc gì khác. Cách này đặc biệt hữu ích khi bạn nghi ngờ agent sẽ "bịa" cách implement thay vì tuân theo pattern có sẵn:

Trước khi viết code, hãy grep toàn bộ repo tìm các chỗ dùng
"withTransaction(" để xem convention xử lý database transaction
hiện tại là gì. Sau đó implement theo đúng convention đó, không
tự nghĩ ra cách khác.

Yêu cầu agent search trước khi hành động (search-before-act). Đây là kỹ thuật prompt quan trọng nhất trong retrieval thủ công: chèn một bước bắt buộc "khảo sát trước, code sau" vào prompt, để tránh agent nhảy thẳng vào viết code dựa trên giả định sai về codebase:

Bước 1: Tìm và liệt kê tất cả các file định nghĩa API route
trong dự án này (không sửa gì cả, chỉ báo cáo lại danh sách
và pattern chung bạn quan sát được).
Bước 2: Sau khi tôi xác nhận, mới bắt đầu implement route mới.

Kỹ thuật này buộc agent tạm dừng ở bước retrieval, cho bạn cơ hội kiểm tra xem nó có hiểu đúng codebase trước khi để nó viết hàng trăm dòng code sai hướng.

Cung cấp directory listing. Với repo lớn, đôi khi cách nhanh nhất để agent định hướng không phải là để nó tự đi search từng bước một (tốn nhiều lượt gọi tool, tốn thời gian), mà là cung cấp sẵn cấu trúc thư mục ở tầng cao:

Đây là cấu trúc thư mục chính của backend:
src/
  api/          - REST endpoint handlers
  services/     - business logic
  repositories/ - data access layer (Prisma)
  models/       - domain types
Dựa trên cấu trúc này, hãy tìm file phù hợp để thêm logic
tính discount cho đơn hàng.

Cung cấp directory listing giống như đưa bản đồ cho một người mới đến thành phố lạ — thay vì để họ dò từng ngã tư, bạn cho họ biết luôn khu nào là khu nào.

Điểm chung của bốn kỹ thuật trên: chúng đều là cách bạn "lập trình" lại hành vi retrieval của agent bằng ngôn ngữ tự nhiên, thay vì để mặc định pipeline retrieval tự động quyết định. Với các tác vụ quan trọng — sửa logic thanh toán, migrate database, thay đổi authentication — luôn ưu tiên manual retrieval rõ ràng thay vì tin tưởng agent tự tìm đúng.

Mẹo: Với các thay đổi rủi ro cao (payment, auth, migration), luôn bắt agent liệt kê lại các file nó định sửa TRƯỚC khi cho phép nó sửa. Việc này tốn thêm một lượt trao đổi nhưng giảm đáng kể rủi ro agent sửa nhầm chỗ.

Static Context Với Dynamic Context: Khi Nào Dùng Cái Nào

Bài trước đã giới thiệu CLAUDE.md như một dạng static context — thông tin luôn được nạp sẵn vào mọi phiên làm việc bất kể task là gì. Dynamic context, ngược lại, là thông tin được truy xuất theo yêu cầu (on-demand), chỉ xuất hiện khi liên quan đến câu hỏi hiện tại. Hiểu ranh giới giữa hai loại này là kỹ năng then chốt của context engineering.

Quy tắc chọn lựa dựa trên hai trục: tần suất sử dụngtính ổn định của thông tin.

Đưa vào static context (CLAUDE.md, pinned file) khi thông tin: (1) được dùng lại trong gần như mọi task, (2) hiếm khi thay đổi, (3) không thể suy ra được từ việc đọc code trực tiếp. Ví dụ điển hình: coding convention của team, lệnh chạy test, cấu trúc thư mục tổng quan, danh sách các "không được làm" (ví dụ "không bao giờ sửa trực tiếp file migration đã chạy production"). Những thông tin này không nằm "trong" code — chúng là ngữ cảnh tổ chức mà không có lệnh search nào tìm ra được.

Dựa vào dynamic retrieval khi thông tin: (1) chỉ liên quan đến task cụ thể đang làm, (2) thay đổi thường xuyên theo từng commit, (3) có thể tìm chính xác hơn bằng cách đọc trực tiếp source code thay vì mô tả lại bằng văn xuôi. Ví dụ: chi tiết implementation của một function cụ thể, signature hiện tại của một API, schema hiện tại của một bảng database. Nhồi nhét những thứ này vào CLAUDE.md là phản tác dụng — vừa làm phình context window một cách lãng phí (tốn token cho mọi task kể cả khi không liên quan), vừa có nguy cơ bị lỗi thời ngay khi code thay đổi mà bạn quên cập nhật file.

Có một sai lầm phổ biến mà kỹ sư mới làm quen với context engineering hay mắc: cố gắng biến CLAUDE.md thành một "bản sao tài liệu kỹ thuật đầy đủ" của toàn bộ hệ thống, liệt kê chi tiết từng module, từng function quan trọng. Cách làm này sai vì hai lý do. Một, nó tạo ra hai nguồn sự thật (source of truth) song song — code thực tế và mô tả trong CLAUDE.md — và chúng sẽ lệch nhau theo thời gian, khiến agent tin vào thông tin sai còn nguy hiểm hơn không có thông tin gì. Hai, nó lãng phí context window: mọi task, dù chỉ sửa một dòng CSS, cũng phải tải toàn bộ tài liệu khổng lồ đó.

Cách tiếp cận đúng: CLAUDE.md nên đóng vai trò như một "bản đồ chỉ đường" (index), không phải "toàn bộ nội dung". Thay vì mô tả chi tiết cách authentication hoạt động, hãy viết một dòng trỏ đường: "Logic authentication nằm ở src/auth/, xem AuthMiddleware.ts để hiểu flow chính — luôn grep trước khi sửa gì trong thư mục này." Dòng này chỉ tốn vài chục token nhưng đủ để agent biết nên retrieval ở đâu khi cần, thay vì cố nhồi cả nghìn token mô tả logic auth chi tiết mà 90% task không cần đến.

Một cách nhớ đơn giản: static context trả lời câu hỏi "quy tắc chơi là gì", dynamic context trả lời câu hỏi "hiện trạng sân chơi ra sao". Quy tắc chơi ít thay đổi và cần biết trước; hiện trạng sân chơi thay đổi liên tục và chỉ cần biết khi cần hành động.

Mẹo: Định kỳ (ví dụ mỗi sprint) rà soát lại CLAUDE.md và tự hỏi: "Dòng này có phải quy tắc/convention không, hay nó đang mô tả chi tiết implementation có thể lỗi thời?" Xóa mọi thứ thuộc loại thứ hai, thay bằng một dòng trỏ đường ngắn gọn.

Chiến Lược Chunking Cho File Code

Chunking — chia nhỏ nội dung thành các đoạn (chunk) trước khi đưa vào embedding hoặc context — là bước kỹ thuật ẩn phía sau mọi hệ thống retrieval, và với code, cách chunking sai có thể phá vỡ hoàn toàn ý nghĩa của đoạn được truy xuất.

Cách chunking ngây thơ nhất — cũng là cách nhiều hệ thống RAG tổng quát ban đầu áp dụng khi mang thẳng kỹ thuật xử lý văn bản sang code — là cắt theo số dòng hoặc số ký tự cố định (ví dụ mỗi 500 dòng một chunk, có thể overlap — chồng lấn — một phần nhỏ giữa các chunk để giảm mất ngữ cảnh ở ranh giới). Cách này thất bại thảm hại với code vì ranh giới cắt hoàn toàn không quan tâm đến cấu trúc cú pháp: một chunk có thể kết thúc giữa thân một function, cắt đứt một class thành hai nửa không liên quan, hoặc tách phần khai báo type ra khỏi phần dùng nó. Khi model chỉ nhận được nửa function, nó buộc phải "đoán" phần còn thiếu — và thường đoán sai, dẫn đến code sinh ra bị lỗi logic hoặc cú pháp.

Chiến lược chunking đúng đắn cho code phải tôn trọng ranh giới ngữ nghĩa, thường dựa trên việc parse AST thay vì đếm dòng. Có ba cấp độ chunking phổ biến:

Function-level chunking. Mỗi chunk là một function hoặc method hoàn chỉnh, kèm theo docstring/comment ngay phía trên nó. Đây là mức chunking phổ biến nhất vì function thường là đơn vị hành vi (behavior) nhỏ nhất có ý nghĩa độc lập — bạn có thể hiểu một function mà không cần đọc toàn bộ file chứa nó. Ưu điểm là chunk nhỏ, retrieval chính xác, không lãng phí context. Nhược điểm là mất ngữ cảnh về class chứa nó (ví dụ các field của class, hoặc các method khác cùng class có thể liên quan).

Class-level chunking. Mỗi chunk là toàn bộ một class, bao gồm tất cả method và field của nó. Phù hợp khi các method trong cùng class phụ thuộc lẫn nhau chặt chẽ (ví dụ một class State Machine mà các method liên tục gọi chéo nhau, đọc riêng một method sẽ không hiểu được toàn bộ hành vi). Nhược điểm là chunk lớn hơn, với class có hàng trăm dòng thì retrieval một class có thể chiếm quá nhiều context window cho một câu hỏi chỉ liên quan đến một method nhỏ trong đó.

File-level chunking. Toàn bộ file là một chunk. Phù hợp với file cấu hình, file nhỏ (dưới 100-150 dòng), hoặc file có nhiều phần liên kết chặt (ví dụ file định nghĩa toàn bộ route của một module, nơi hiểu một route riêng lẻ không đủ để hiểu tổng thể module). Nhược điểm rõ ràng: với file lớn hàng nghìn dòng, chunk theo file gây lãng phí context nghiêm trọng.

Trong thực tế, các công cụ retrieval tốt kết hợp cả ba mức: parse AST để xác định ranh giới function/class chính xác, sau đó quyết định gộp nhiều function nhỏ liên quan vào cùng một chunk hay tách class lớn thành nhiều chunk theo method, tùy vào kích thước. Đây chính là lý do một số công cụ AI coding tool "hiểu code tốt hơn" công cụ khác dù dùng cùng một model nền tảng — sự khác biệt nằm ở chất lượng của tầng chunking và retrieval, không phải ở bản thân LLM.

Với engineer sử dụng công cụ AI hàng ngày, hiểu chunking giúp bạn giải thích được vì sao đôi khi agent "hiểu nhầm" một đoạn code — rất có thể retrieval chỉ lấy về một chunk cắt cụt, thiếu phần định nghĩa type hoặc import quan trọng nằm ở chunk khác. Khi gặp tình huống này, giải pháp thực dụng nhất là quay lại kỹ thuật manual retrieval ở phần trước: chỉ định rõ file hoặc yêu cầu agent đọc toàn bộ file thay vì tin vào chunk được tự động lấy về.

Mẹo: Nếu nghi ngờ agent đang "nhìn" một phiên bản cắt cụt của file (ví dụ nó nhắc đến một function nhưng bỏ sót import quan trọng ở đầu file), hãy yêu cầu thẳng "đọc toàn bộ file X, đừng chỉ xem đoạn trích" — buộc nó dùng Read tool đọc đầy đủ thay vì dựa vào kết quả search/embedding từng phần.

Thực Hành: Xây Dựng API Endpoint Mới Với Dynamic Context

Hãy đi qua một tình huống thực tế: bạn cần thêm endpoint GET /api/orders vào một backend đã có sẵn nhiều endpoint khác, và bạn muốn endpoint mới tuân thủ đúng convention hiện có thay vì để agent tự bịa ra cách làm riêng.

Bước 1 — Khảo sát pattern hiện có trước khi viết bất cứ dòng code nào. Đừng yêu cầu agent "viết endpoint GET /api/orders" ngay lập tức — làm vậy agent sẽ dùng kiến thức tổng quát của nó về REST API thay vì convention riêng của dự án bạn. Thay vào đó, bắt đầu bằng một prompt buộc retrieval trước:

Tìm cách các endpoint hiện có trong codebase này xử lý pagination,
sau đó cho tôi biết pattern chung: các endpoint dùng framework gì,
validate query params ra sao, và trả response theo format nào.
Chưa viết code, chỉ báo cáo lại những gì bạn tìm thấy.

Agent sẽ (tùy công cụ) grep các endpoint hiện có, đọc một vài file mẫu, rồi tóm tắt lại pattern — ví dụ: dùng Express với middleware validateQuery, pagination dùng page/limit query param, response bọc trong { data, meta: { total, page } }. Bước này chính là "retrieval" theo đúng nghĩa RAG — bạn đang buộc agent tìm ngữ cảnh thật từ codebase thay vì suy diễn từ kiến thức chung.

Bước 2 — Xác nhận và bổ sung ngữ cảnh còn thiếu. Đọc lại tóm tắt của agent. Nếu nó bỏ sót điều gì (ví dụ không nhắc đến cách xử lý lỗi authorization), bổ sung thêm bằng một prompt retrieval tiếp theo:

Tốt. Giờ hãy tìm thêm cách endpoint hiện tại xử lý trường hợp
user không có quyền truy cập resource (authorization), và
model Order được định nghĩa ở đâu (Prisma schema hay TypeORM entity).

Bước 3 — Implement dựa trên pattern đã xác nhận, với prompt chỉ định rõ ràng.

Dựa trên pattern pagination và authorization bạn vừa tìm thấy,
implement GET /api/orders:
- Route file theo đúng cấu trúc thư mục như các route khác
  (xem @src/api/routes/products.ts làm ví dụ tham chiếu)
- Áp dụng cùng middleware validateQuery và requireAuth
- Trả response theo đúng format { data, meta } như các endpoint khác
- Chỉ trả order thuộc về user hiện tại (dùng req.user.id để filter)
- Viết kèm unit test theo pattern test hiện có trong
  @src/api/routes/products.test.ts

Lưu ý cách prompt này kết hợp cả hai nguồn context: dynamic (kết quả retrieval ở bước 1-2, mô tả pattern chung) và static/explicit (chỉ định rõ file tham chiếu bằng @). Đây chính là "dynamic context assembly" trong thực hành — bạn không nạp sẵn toàn bộ codebase, cũng không để agent đoán mò, mà lắp ráp đúng những mảnh ngữ cảnh cần thiết cho task cụ thể này.

Bước 4 — Yêu cầu agent tự kiểm tra tính nhất quán trước khi hoàn tất.

Sau khi viết xong, hãy đọc lại các route khác một lần nữa để xác
nhận endpoint mới nhất quán 100% về style, error handling, và
naming convention với các route hiện có. Liệt kê rõ nếu có điểm
khác biệt và giải thích lý do.

Bước cuối này quan trọng vì nó dùng chính retrieval để tự kiểm chứng — một vòng lặp nhỏ giữa "viết code" và "đối chiếu lại context đã tìm được" giúp bắt lỗi consistency mà một lần sinh code duy nhất dễ bỏ sót.

Mẹo: Luôn tách rõ bước "khảo sát pattern" và bước "implement" thành hai lượt trao đổi riêng biệt. Gộp chung một prompt duy nhất khiến agent dễ nhảy thẳng vào viết code trước khi kịp retrieval đủ ngữ cảnh.

Thực Hành: Khám Phá Một Repository Lớn Chưa Quen Thuộc

Tình huống khác thường gặp: bạn vừa gia nhập một dự án, hoặc được giao phụ trách một service mà trước giờ chưa từng động vào, với hàng chục nghìn dòng code trải trên hàng trăm file. Đọc tuần tự từng file là bất khả thi — bạn cần một chiến lược khám phá có hệ thống, dùng agent như một "hướng dẫn viên" retrieval thông tin theo từng tầng, từ tổng quan đến chi tiết.

Tầng 1 — Kiến trúc tổng quan (architecture overview). Bắt đầu bằng câu hỏi rộng nhất, tập trung vào bức tranh lớn chứ chưa đi vào chi tiết implementation:

Đây là lần đầu tôi làm việc với repo này. Hãy khảo sát cấu trúc
thư mục cấp cao, file package.json (hoặc go.mod/pom.xml tùy stack),
và README nếu có, rồi cho tôi một bản tóm tắt kiến trúc tổng quan:
đây là loại hệ thống gì (monolith, microservices, monorepo — kho
mã nguồn hợp nhất nhiều package/service), dùng framework/ngôn ngữ
chính nào, và các thành phần lớn (service, package, module) là gì.

Tầng 2 — Entry point (điểm khởi động của luồng xử lý). Sau khi có bức tranh tổng quan, thu hẹp vào việc tìm nơi luồng xử lý bắt đầu — đây là "sợi chỉ" giúp bạn lần theo toàn bộ hệ thống một cách có định hướng thay vì đọc ngẫu nhiên:

Tìm entry point chính của service này (file main/index khởi động
server, hoặc file định nghĩa route gốc). Từ entry point đó, hãy
vẽ ra (dạng text) luồng đi của một request điển hình: nó đi qua
những middleware/layer nào trước khi chạm tới business logic,
rồi tới data layer.

Tầng 3 — Module quan trọng nhất theo domain nghiệp vụ. Yêu cầu agent xác định các module cốt lõi, ưu tiên theo mức độ quan trọng nghiệp vụ thay vì kích thước file:

Dựa trên luồng bạn vừa mô tả, đâu là 3-5 module/package quan trọng
nhất về mặt nghiệp vụ (không phải file nhiều dòng nhất, mà là nơi
chứa logic nghiệp vụ cốt lõi — ví dụ xử lý đơn hàng, thanh toán,
authentication). Với mỗi module, cho tôi biết file chính cần đọc
đầu tiên nếu tôi muốn hiểu module đó.

Tầng 4 — Đào sâu có mục tiêu, không dàn trải. Khi đã có bản đồ tổng thể, việc đào sâu tiếp theo nên gắn với mục tiêu công việc cụ thể của bạn (ví dụ bạn được giao sửa bug ở module thanh toán), không nên cố "đọc hiểu toàn bộ repo" một lượt — điều đó vừa mất thời gian vừa không cần thiết:

Tôi được giao sửa một bug liên quan đến việc tính sai discount khi
áp dụng nhiều voucher cùng lúc. Dựa trên bản đồ kiến trúc đã có,
hãy tìm chính xác đoạn code xử lý logic tính discount, liệt kê tất
cả các nơi function này được gọi tới, và giải thích luồng dữ liệu
đi vào/đi ra của nó.

Tầng 5 — Ghi lại bản đồ vừa khám phá. Bước cuối cùng, thường bị bỏ qua nhưng rất giá trị: yêu cầu agent tóm tắt lại toàn bộ những gì vừa khám phá thành một file ghi chú ngắn (có thể lưu tạm, không nhất thiết phải commit vào CLAUDE.md ngay), để bạn — và cả agent trong phiên làm việc sau — không phải retrieval lại từ đầu:

Tóm tắt toàn bộ kiến trúc và các module chính chúng ta vừa khảo
sát thành một file ghi chú ngắn gọn (dưới 50 dòng), theo dạng
bullet points, tập trung vào những gì một engineer mới cần biết
trong ngày đầu tiên để định hướng nhanh trong repo này.

Ghi chú này chính là hạt giống tốt cho một CLAUDE.md tương lai — bạn vừa biến kết quả của một phiên dynamic retrieval thành ứng viên cho static context, đúng như nguyên tắc đã bàn ở phần trước: những gì lặp lại và ổn định nên được "đóng băng" thành context tĩnh, còn những gì đặc thù cho một task thì để retrieval xử lý theo yêu cầu.

Mẹo: Khi khám phá repo lạ, luôn đi từ tổng quan xuống chi tiết theo đúng thứ tự (kiến trúc → entry point → module → chi tiết), đừng nhảy thẳng vào đọc file cụ thể ngay từ đầu. Nhảy cóc khiến bạn (và agent) dễ hiểu sai vị trí của một đoạn code trong bức tranh toàn cục.

Những điểm chính

  • RAG áp dụng cho code khác RAG cho document ở chỗ đơn vị truy xuất cần tôn trọng ranh giới cú pháp (function/class/file), không phải cắt theo độ dài cố định.
  • Các AI coding tool khác nhau dùng cơ chế retrieval khác nhau — agentic search (grep/read theo thời gian thực) hoặc embedding-based index — hiểu cơ chế công cụ bạn dùng giúp ra prompt hiệu quả hơn.
  • Manual retrieval (explicit file mention, grep-first, search-before-act, directory listing) luôn nên được ưu tiên cho các thay đổi rủi ro cao thay vì phó mặc cho retrieval tự động.
  • Static context trả lời "quy tắc chơi là gì" (ít đổi, dùng lại nhiều); dynamic context trả lời "hiện trạng code ra sao" (đổi liên tục, chỉ cần khi liên quan) — đừng nhồi nội dung động vào file tĩnh.
  • Chunking theo AST (function/class-level) cho kết quả retrieval chính xác hơn nhiều so với chunking ngây thơ theo số dòng, vốn dễ cắt đứt ngữ cảnh giữa thân function.
  • Khi xây feature mới, luôn tách bước "khảo sát pattern có sẵn" ra khỏi bước "implement" để tránh agent bịa cách làm không nhất quán với codebase.
  • Khi khám phá repo lớn chưa quen, đi từ kiến trúc tổng quan → entry point → module nghiệp vụ → chi tiết cụ thể, và lưu lại bản đồ khám phá được để tái sử dụng cho các phiên sau.