·

Một AI agent chạy liên tục trong nhiều giờ — debug một hệ thống phân tán, review hàng chục pull request, hoặc điều phối một chiến dịch test end-to-end — sẽ sớm gặp phải giới hạn cứng: context window. Ở topic trước, chúng ta đã nói về kỹ thuật tóm tắt "one-shot" (tóm tắt một lần khi context đầy). Cách đó hoạt động, nhưng có một nhược điểm âm thầm: mỗi lần tóm tắt lại, agent phải nạp lại toàn bộ lịch sử hội thoại vào LLM để sinh ra bản tóm tắt mới — tốn token, tốn thời gian, và với các session dài (hàng nghìn turn), chi phí này tăng theo cấp số nhân.

Incremental summarization (tóm tắt tăng dần) giải quyết vấn đề này bằng một chiến lược khác: duy trì một rolling summary (bản tóm tắt xoay vòng, liên tục cập nhật) — một tài liệu "sống", chỉ cần hợp nhất (merge) thông tin mới vào, không cần đọc lại từ đầu. Đây là kỹ thuật bắt buộc phải nắm vững nếu bạn build agent cho các use case chạy dài: coding agent, agent vận hành (DevOps agent), agent QA tự động chạy suite hàng giờ, hoặc trợ lý PM theo dõi tiến độ dự án qua nhiều ngày.

Bài viết này dành cho mọi vai trò trong vòng đời phát triển phần mềm — không chỉ engineer. QA cần hiểu cơ chế này để biết cách viết test case cho tính năng "resume session" của agent. PM cần hiểu để đánh giá rủi ro khi agent quên mất quyết định quan trọng đã chốt từ tuần trước. Và tất nhiên, engineer cần hiểu để tự tay implement nó.

The Core Concept: The Living Summary Document

Hãy tưởng tượng rolling summary như một cuốn sổ tay công tác (working notebook) mà agent luôn mang theo. Mỗi khi có việc mới xảy ra — một quyết định được chốt, một lỗi được phát hiện, một file được sửa — agent không viết lại toàn bộ cuốn sổ, mà chỉ cập nhật vào đúng chỗ cần cập nhật. Đó chính là bản chất của "living summary document" (tài liệu tóm tắt sống): một object dữ liệu duy nhất, tồn tại suốt đời session, được sửa đổi liên tục thay vì bị tái tạo (regenerate) từ đầu mỗi lần.

So sánh với cách tóm tắt "one-shot" mà ta đã học trước đó:

Đặc điểm One-shot summarization Incremental summarization
Thời điểm tóm tắt Khi context sắp đầy (một lần, hoặc vài lần rời rạc) Liên tục, theo từng N turn hoặc theo ngưỡng token
Input cho LLM khi tóm tắt Toàn bộ lịch sử thô Rolling summary hiện tại + phần mới (delta)
Chi phí token khi tóm tắt Tăng theo độ dài toàn session Gần như cố định (bounded) mỗi lần merge
Rủi ro Tóm tắt trễ, mất chi tiết dồn cục Trôi thông tin (drift) nếu merge nhiều lần không cẩn thận

Điểm mấu chốt: rolling summary không phải là một bản ghi chép đầy đủ (nó không thể, và không nên, chứa lại toàn bộ raw transcript). Nó là một trạng thái được chưng cất (distilled state) — chỉ giữ lại thông tin có giá trị lâu dài: quyết định đã chốt, ràng buộc (constraint) đã biết, việc đã hoàn thành, việc còn tồn đọng (open items), và các fact quan trọng cần nhớ xuyên suốt session.

Một cách hình dung khác: rolling summary giống hệt biến state trong một reducer (kiểu Redux/event sourcing) — mỗi turn mới là một "action", và summary là "state" được reduce() qua action đó. Bạn không replay lại toàn bộ list action để có state hiện tại; bạn chỉ áp action mới nhất lên state đã có.

Cấu trúc thực tế của một rolling summary tốt thường có các block cố định, ví dụ (mô tả bằng schema, không phải code thật):

ROLLING_SUMMARY = {
  "goal": "Mục tiêu tổng thể của session (ít thay đổi)",
  "decisions": [ "Quyết định 1 đã chốt", "Quyết định 2 đã chốt" ],
  "constraints": [ "Ràng buộc kỹ thuật/nghiệp vụ đang áp dụng" ],
  "completed": [ "Việc đã xong, không cần nhắc lại chi tiết" ],
  "open_items": [ "Việc còn đang làm, cần theo dõi tiếp" ],
  "key_facts": { "biến/API/file quan trọng": "giá trị hoặc mô tả" },
  "last_updated_turn": 142
}

Việc chia thành các block như trên (thay vì một đoạn văn xuôi tự do) giúp cho bước merge ở phần sau dễ kiểm soát hơn rất nhiều — LLM biết chính xác nên thêm vào block nào, và con người review cũng dễ dò lỗi hơn.

Mẹo:
- Đừng để rolling summary trở thành một "đoạn văn" tự do dài dằng dặc — hãy ép nó vào một schema có cấu trúc (JSON hoặc Markdown với heading cố định) để dễ merge và dễ audit.
- Giới hạn kích thước tối đa cho rolling summary (ví dụ 1500–2500 token). Nếu summary tự nó phình to hơn ngưỡng này, đó là dấu hiệu cần "prune" (cắt bỏ) các mục cũ đã hết giá trị (ví dụ: completed items quá cũ không còn liên quan).
- Luôn gắn timestamp hoặc turn index vào summary để biết nó "tươi" (fresh) đến mức nào — quan trọng khi resume session sau này.
- Tách riêng phần "goal" (ít đổi) khỏi phần "open_items" (đổi liên tục) — giúp giảm số token bị viết lại mỗi lần merge.

The Architecture of an Incremental Summarization Loop

Bây giờ đến phần kỹ thuật: làm sao để vòng lặp (loop) này chạy tự động trong suốt session? Kiến trúc chuẩn gồm 4 thành phần chính: buffer (bộ đệm turn thô), rolling summary (đã nói ở trên), trigger condition (điều kiện kích hoạt merge), và merge step (bước gọi LLM để hợp nhất).

Luồng hoạt động (flow) như sau, mô tả dạng pseudo-code:

rolling_summary = INITIAL_SUMMARY  # rỗng hoặc từ session trước load lên
buffer = []                        # các turn thô, chưa được gộp
TOKEN_THRESHOLD = 3000              # ngưỡng kích hoạt merge
TURN_THRESHOLD = 15                 # hoặc ngưỡng theo số turn

def on_new_turn(turn):
    buffer.append(turn)

    if count_tokens(buffer) >= TOKEN_THRESHOLD or len(buffer) >= TURN_THRESHOLD:
        # Bước merge: gọi LLM hợp nhất buffer vào rolling_summary
        new_summary = call_llm_merge(rolling_summary, buffer)
        rolling_summary = new_summary
        buffer.clear()          # xoá các turn thô đã được "hấp thụ"
        persist(rolling_summary)  # lưu xuống disk/DB — nói ở phần sau

def build_context_for_next_call():
    # Đây là context thực sự gửi cho model ở lượt gọi tiếp theo
    return [
        SYSTEM_PROMPT,
        rolling_summary.render(),   # bản tóm tắt gọn
        *buffer,                    # các turn thô CHƯA merge (vẫn còn "tươi", chi tiết đầy đủ)
        current_user_message
    ]

Điểm quan trọng nhất trong kiến trúc này: context thực sự gửi lên model không phải là "chỉ rolling summary", mà là rolling summary + các turn thô gần nhất chưa merge. Đây là lý do buffer tồn tại — những turn rất gần đây thường cần độ chi tiết cao (ví dụ: output đầy đủ của một lệnh git diff, hoặc log lỗi chi tiết), còn những turn đã cũ hơn ngưỡng thì có thể chấp nhận bị "làm mờ" đi qua summary mà không ảnh hưởng nhiều đến chất lượng phản hồi.

Sơ đồ hoá luồng dữ liệu:

[Turn 1] [Turn 2] ... [Turn 15]  →  (đạt ngưỡng)  →  MERGE  →  rolling_summary v1
                                                         ↓
                                                   buffer.clear()
[Turn 16] [Turn 17] ... [Turn 30] → (đạt ngưỡng) → MERGE(v1, buffer) → rolling_summary v2
                                                         ↓
                                                   buffer.clear()

Context gửi cho model tại Turn 25 = rolling_summary v1 + [Turn 16..25]
Context gửi cho model tại Turn 35 = rolling_summary v2 + [Turn 31..35]

Việc chọn TOKEN_THRESHOLD và TURN_THRESHOLD là một bài toán đánh đổi (trade-off) thực sự cần cân nhắc:
- Ngưỡng thấp (merge thường xuyên) → context luôn gọn, nhưng tốn nhiều lệnh gọi LLM phụ (extra LLM call) chỉ để merge, tăng latency và chi phí.
- Ngưỡng cao (merge ít) → buffer phình to giữa hai lần merge, làm giảm hiệu quả compression, đôi khi context vẫn tràn trước khi kịp merge.

Trong thực tế, nhiều hệ thống agent production (ví dụ các coding agent chạy trong CI hoặc agent vận hành 24/7) dùng ngưỡng hỗn hợp: merge khi buffer vượt X token HOẶC đã qua Y turn HOẶC đã qua Z phút — cái nào đến trước thì trigger trước. Điều này tránh trường hợp một session có ít turn nhưng mỗi turn siêu dài (ví dụ paste cả file log 5000 dòng) làm buffer nổ token mà chưa kịp đạt ngưỡng turn.

Một biến thể kiến trúc khác đáng nhắc: merge bất đồng bộ (async merge). Thay vì chặn (block) lượt trả lời của agent để đợi LLM merge xong, hệ thống có thể trả lời user ngay bằng buffer thô, và chạy merge ở background thread/job riêng, cập nhật rolling_summary cho lượt gọi tiếp theo. Cách này giảm latency cảm nhận được của user, đổi lại rolling summary luôn "chậm" hơn thực tế đúng một nhịp — cần đánh giá xem use case của bạn có chấp nhận được độ trễ này không.

Mẹo:
- Luôn merge theo cặp điều kiện "token HOẶC turn HOẶC thời gian", không chỉ dựa vào một tiêu chí duy nhất.
- Log lại mỗi lần merge (trước/sau, số token buffer, thời gian merge) — đây là dữ liệu quý để tối ưu ngưỡng sau này và để debug khi agent "quên" thông tin.
- Nếu dùng async merge, đảm bảo có cơ chế lock hoặc versioning để tránh race condition khi hai merge job chạy song song ghi đè lên nhau.
- Với agent coding, nên giữ lại nguyên văn (không merge) các đoạn diff/code output gần nhất — chi tiết code rất dễ "mất nghĩa" khi bị tóm tắt bởi LLM.

The Merge Prompt: Getting the Integration Right

Bước merge là trái tim của toàn bộ kỹ thuật này, và cũng là nơi dễ sai nhất. Một merge prompt tồi sẽ khiến rolling summary dần dần bị trôi (drift): lặp lại thông tin, mất quyết định cũ, hoặc tệ hơn — giữ lại thông tin đã bị quyết định mới đó phủ quyết (override).

Ba yêu cầu bắt buộc với merge prompt:
1. Không mất quyết định cũ — trừ khi có thông tin mới mâu thuẫn trực tiếp.
2. Không lặp lại thông tin — nếu một fact đã có trong summary và turn mới chỉ nhắc lại, không cần thêm dòng mới.
3. Ưu tiên thông tin mới nhất khi có mâu thuẫn — ví dụ: nếu summary cũ ghi "dùng PostgreSQL" nhưng turn mới cho biết team đã đổi sang MySQL, bản summary mới phải phản ánh MySQL và ghi rõ đây là thay đổi (không giữ cả hai như thể chúng cùng đúng).

Dưới đây là một ví dụ merge prompt chi tiết, có thể dùng trực tiếp trong code (giữ nguyên tiếng Anh vì đây là prompt gửi cho LLM):

SYSTEM: You are a summary-merging assistant for a long-running AI agent session.
Your job is to integrate NEW_TURNS into an EXISTING_SUMMARY, producing an
UPDATED_SUMMARY that is accurate, non-redundant, and preserves all
still-relevant prior information.

Rules you MUST follow:
1. Preserve every decision, constraint, and fact in EXISTING_SUMMARY unless
   NEW_TURNS explicitly contradicts or supersedes it.
2. If NEW_TURNS contradicts EXISTING_SUMMARY, the NEW information wins.
   Do not keep both versions — replace the outdated one, and add a short
   note like "(updated from: <old value>)" only if the change is important
   enough that future-you would want to know it changed.
3. Do NOT duplicate information. If a fact in NEW_TURNS is already captured
   in EXISTING_SUMMARY, do not add a second entry — merge them into one.
4. Move any item in "open_items" to "completed" if NEW_TURNS shows it was
   finished. Add new open items if NEW_TURNS introduces unfinished work.
5. Keep "goal" unchanged unless NEW_TURNS explicitly redefines the overall
   objective of the session.
6. Be concise. Prefer bullet points over prose. Drop items from
   "completed" that are more than 40 turns old AND have no bearing on
   current open work.
7. Output MUST be valid JSON matching the exact schema of EXISTING_SUMMARY.
   Do not add extra top-level keys. Do not include commentary outside the
   JSON object.

EXISTING_SUMMARY:
{{existing_summary_json}}

NEW_TURNS (raw conversation excerpt to integrate):
{{buffer_turns_text}}

Return ONLY the UPDATED_SUMMARY as JSON.

Vài lưu ý khi thiết kế prompt kiểu này trong thực tế:

  • Ép output có schema cứng (JSON) giúp bạn validate được kết quả bằng code (parse thử, kiểm tra key bắt buộc) trước khi chấp nhận thay thế rolling summary cũ. Nếu LLM trả về JSON lỗi, bạn có thể retry hoặc fallback giữ nguyên summary cũ — tránh việc một lần merge lỗi làm hỏng cả state.
  • Chỉ rõ quy tắc "mới thắng cũ khi mâu thuẫn" (rule 2) là chi tiết cực kỳ quan trọng mà nhiều người bỏ sót — không có quy tắc này, LLM có xu hướng "an toàn" giữ cả hai version, dẫn đến summary ngày càng rối và tự mâu thuẫn.
  • Rule về cắt bớ "completed" quá cũ (rule 6) chính là cơ chế tránh summary phình vô hạn qua hàng trăm lần merge.
  • Nên few-shot thêm 1 ví dụ merge mẫu (input summary + input turns + output mong đợi) ngay trong prompt nếu bạn thấy model hay merge sai — few-shot cải thiện độ chính xác rõ rệt cho task dạng structured-editing như này.

Mẹo:
- Luôn validate JSON output bằng code trước khi ghi đè summary cũ — coi merge step như một "transaction" có thể rollback.
- Thêm field "last_updated_turn" và bắt LLM cập nhật đúng giá trị này — giúp bạn phát hiện ngay nếu một lần merge bị bỏ sót (skip) do lỗi.
- Test merge prompt bằng các case cố ý mâu thuẫn (ví dụ đổi quyết định kỹ thuật giữa chừng) để chắc chắn rule "mới thắng cũ" hoạt động đúng — đừng chỉ test case xuôi chiều.
- Nếu ngân sách cho phép, dùng một model rẻ/nhanh (ví dụ tier nhỏ) riêng cho việc merge — merge là task đơn giản hơn task chính của agent, không cần model mạnh nhất.

Delta-Based vs. Full-Rewrite Compression Strategies

Có hai chiến lược merge khác nhau về bản chất, và việc chọn sai có thể khiến rolling summary của bạn hỏng dần theo thời gian mà không ai nhận ra ngay.

Chiến lược 1 — Delta-based (append-based): mỗi lần merge, bạn chỉ thêm phần thông tin mới (delta) vào cuối summary hiện có, gần như không "viết lại" các phần cũ. Ví dụ: thêm một dòng mới vào list open_items, thêm một entry mới vào key_facts, mà không đụng đến các dòng đã có.

Chiến lược 2 — Full-rewrite: mỗi lần merge, bạn yêu cầu LLM đọc toàn bộ summary cũ + buffer mới, rồi viết lại toàn bộ summary từ đầu — kể cả các phần không có gì thay đổi.

So sánh chi tiết:

Tiêu chí Delta-based Full-rewrite
Chi phí token/lần merge Thấp (chỉ sinh phần mới) Cao hơn (phải sinh lại toàn bộ summary)
Khả năng phát hiện mâu thuẫn Kém — dễ chỉ "thêm" mà không phát hiện thông tin cũ đã sai Tốt hơn — LLM đọc lại toàn cảnh nên dễ nhận ra mâu thuẫn để sửa
Rủi ro phình to (bloat) theo thời gian Cao — càng nhiều lần merge, summary càng dài vì toàn "thêm" ít "xoá" Thấp hơn — mỗi lần rewrite là cơ hội để cô đọng lại, loại bỏ phần dư
Rủi ro trôi thông tin (drift) qua nhiều vòng Thấp với thông tin cũ (không đụng vào) nhưng dễ tích tụ rác Có, nếu rewrite lặp lại nhiều lần mà không neo (anchor) tốt vào summary gốc, chi tiết có thể bị "làm mờ" dần mỗi vòng, giống hiệu ứng photocopy nhiều lần
Độ phức tạp implement Đơn giản hơn (chỉ append có kiểm soát) Phức tạp hơn (cần prompt tốt để không làm mất thông tin khi viết lại)

Trong thực tế, chiến lược tốt nhất là kết hợp cả hai theo tần suất khác nhau — gọi là "hybrid cadence":

  • Dùng delta-based cho các lần merge thông thường (mỗi khi buffer đạt ngưỡng) — nhanh, rẻ, đủ tốt cho phần lớn trường hợp.
  • Định kỳ, sau mỗi N lần merge delta (ví dụ mỗi 10 lần), chạy một lần full-rewrite để "dọn dẹp" toàn bộ summary: loại bỏ trùng lặp đã tích tụ, gộp các mục liên quan, xác nhận lại tính nhất quán tổng thể, và nén lại cho gọn.

Cách này giống hệt chiến lược "incremental backup + full backup định kỳ" quen thuộc trong hệ thống lưu trữ: incremental (delta) rẻ và nhanh cho việc hàng ngày, nhưng full backup định kỳ đảm bảo bạn có một điểm "sạch", nhất quán, không tích tụ lỗi qua thời gian.

Về rủi ro drift theo nhiều vòng: nếu bạn dùng full-rewrite liên tục ở mọi lần merge mà không cẩn thận, có một hiện tượng gọi là "lossy compounding" — mỗi lần LLM viết lại, nó vô tình bỏ sót một vài chi tiết nhỏ (vì đang cố "nén" lại cho ngắn), và qua 20-30 lần rewrite, những chi tiết nhỏ đó cộng dồn thành mất mát lớn. Cách giảm rủi ro này: luôn giữ nguyên literal text (không diễn giải lại) cho các mục cực kỳ quan trọng — ví dụ một constraint kỹ thuật cứng, hoặc một quyết định business — bằng cách đánh dấu chúng là "pinned" (cố định) trong schema, và chỉ đạo prompt merge KHÔNG được diễn giải lại các mục pinned, chỉ được copy nguyên văn hoặc xoá hẳn nếu chúng thực sự không còn hiệu lực.

Mẹo:
- Áp dụng hybrid: delta-based cho merge thường xuyên, full-rewrite định kỳ (ví dụ mỗi 10 lần merge hoặc mỗi khi chuyển sang một "giai đoạn" mới của công việc).
- Đánh dấu "pinned" cho các thông tin sống-còn (quyết định kiến trúc, ràng buộc bảo mật, thoả thuận với khách hàng) để chúng không bị diễn giải lại hoặc vô tình rơi rụng qua nhiều vòng rewrite.
- Theo dõi độ dài summary theo thời gian (vẽ biểu đồ token count qua các lần merge) — nếu đường này tăng tuyến tính không giới hạn, chiến lược delta-based của bạn đang thiếu bước dọn dẹp.
- Với các session ngắn (dưới ~30 turn), full-rewrite đơn giản mỗi lần là đủ tốt — không cần độ phức tạp của hybrid; chỉ cần hybrid khi session thực sự dài (hàng trăm turn).

Handling Session Interruptions and Resumptions

Một trong những lợi ích lớn nhất của rolling summary — nhưng cũng dễ bị bỏ quên nhất khi thiết kế — là khả năng persist (lưu trữ bền) để resume sau khi session bị gián đoạn: terminal bị đóng, máy crash, agent bị kill do hết quota, hoặc đơn giản là user tắt máy đi ngủ và quay lại làm tiếp vào sáng hôm sau.

Vì rolling summary đã là một object nhỏ, có cấu trúc (JSON), việc persist nó cực kỳ rẻ — khác hẳn với việc phải lưu lại toàn bộ raw transcript (có thể lên tới hàng MB text). Quy trình thực tế nên gồm các bước sau:

  1. Lưu ngay sau mỗi lần merge thành công, không chờ đến cuối session. Ghi rolling summary (kèm buffer chưa merge, nếu có) xuống một file/DB record gắn với session ID. Ví dụ: sessions/{session_id}/rolling_summary.json + sessions/{session_id}/buffer.jsonl.
  2. Ghi kèm metadata: timestamp của lần lưu cuối, turn index cuối cùng, hash hoặc version của schema summary (để tránh lỗi khi bạn đổi schema giữa các version của hệ thống).
  3. Khi resume, đọc lại rolling summary + buffer từ storage, khởi tạo lại state agent, rồi mới nạp context (rolling summary + buffer) để tiếp tục gọi model.
  4. Validate tính hợp lệ trước khi tin tưởng summary đã resume — đây là bước hay bị bỏ qua nhất nhưng lại quan trọng.

Về bước validate (bước 4), có vài kỹ thuật thực tế:

  • Sanity check tự động bằng code: kiểm tra summary có đúng schema không, last_updated_turn có khớp với số turn đã log trong session log không, các field bắt buộc (goal, decisions) có rỗng bất thường không.
  • "Freshness check" bằng câu hỏi kiểm tra: khi resume, có thể chạy một lệnh gọi LLM phụ, đưa summary vào và hỏi: "Dựa trên summary này, những thông tin nào có khả năng đã lỗi thời nếu đã qua N giờ/ngày?" — hữu ích với các agent làm việc trên môi trường thay đổi nhanh (ví dụ: agent theo dõi trạng thái CI/CD, trạng thái server — sau một đêm, trạng thái thực tế của hệ thống có thể đã khác hoàn toàn so với lúc summary được ghi).
  • Re-verify các fact "sống" (volatile facts) khi resume: nếu summary có ghi "branch feature/x đang ở commit abc123", khi resume agent (đặc biệt là coding agent) nên tự động chạy lại git log/git status để xác nhận thông tin đó còn đúng, thay vì tin tuyệt đối vào summary cũ. Đây là nguyên tắc chung: rolling summary đáng tin cho lịch sử/quyết định, nhưng không đáng tin tuyệt đối cho trạng thái hệ thống hiện tại — trạng thái hiện tại luôn cần verify lại bằng nguồn thật (ground truth).
  • Hiển thị cho user một "resume banner": khi agent quay lại làm việc, in ra một bản tóm tắt ngắn dạng "Đây là những gì tôi nhớ từ lần trước: ..." để user (đặc biệt là PM hoặc QA không trực tiếp code) xác nhận ngay thông tin có còn đúng hay không, trước khi agent tiếp tục hành động dựa trên nó.

Một tình huống thực tế đáng lưu ý cho QA: khi viết test case cho tính năng "resume", đừng chỉ test happy path (tắt — mở lại — agent nhớ đúng). Hãy test cả các case: resume sau khi một phần buffer bị mất (crash ngay giữa lúc đang merge, chưa kịp persist xong); resume khi schema summary đã đổi version giữa 2 lần chạy hệ thống; resume một session đã "nguội" quá lâu (vài ngày) trong khi trạng thái hệ thống thực tế đã thay đổi hoàn toàn.

Mẹo:
- Persist rolling summary ngay sau mỗi lần merge, không đợi cuối session — giảm tối đa dữ liệu mất khi crash.
- Luôn lưu kèm turn index/timestamp để biết summary "cũ" đến đâu khi resume.
- Không tin tuyệt đối summary cho các fact dễ thay đổi (trạng thái git, trạng thái deploy, trạng thái ticket) — luôn có bước re-verify bằng nguồn thật khi resume.
- Với session nguội quá X giờ (tự định nghĩa ngưỡng theo domain), tự động in "resume banner" và yêu cầu user xác nhận trước khi agent hành động tiếp.

Metrics for Evaluating Incremental Summarization Quality

Không có số đo, bạn không thể biết incremental summarization của mình đang hoạt động tốt hay đang âm thầm làm agent "ngu" dần qua thời gian. Dưới đây là các metric thực tế, đo được, nên đưa vào bộ theo dõi (monitoring/eval suite) của bạn.

1. Compression ratio (tỷ lệ nén)
Công thức đơn giản: token(rolling_summary) / token(raw_history_equivalent). Đo tại nhiều mốc turn khác nhau (turn 50, 100, 200...) để thấy tỷ lệ này có ổn định không, hay tăng dần theo thời gian (dấu hiệu summary đang phình, cần dọn dẹp thêm — xem lại phần full-rewrite định kỳ).

2. Decision retention rate (tỷ lệ giữ lại quyết định)
Đây là metric quan trọng nhất với các use case nghiệp vụ. Cách đo: chuẩn bị một bộ N quyết định "ground truth" đã được chốt trong session (thủ công gắn nhãn, hoặc sinh tự động bằng cách hỏi LLM khác đọc toàn bộ raw transcript để trích ra danh sách quyết định). Sau đó, tại các mốc turn xa hơn, kiểm tra xem rolling summary còn phản ánh đúng bao nhiêu phần trong N quyết định đó — tính bằng số quyết định còn đúng / N. Retention rate dưới ~90% sau vài chục lần merge là dấu hiệu đáng lo, cần siết lại merge prompt hoặc thêm rule "pinned" cho các quyết định quan trọng.

3. Hallucination / drift rate (tỷ lệ bịa đặt hoặc trôi thông tin)
Đo bằng cách so sánh từng entry trong rolling summary với raw transcript gốc (dùng LLM-as-judge hoặc review thủ công một mẫu ngẫu nhiên): entry đó có thực sự xuất hiện/suy ra được từ transcript, hay là LLM đã "sáng tác" thêm khi merge? Tỷ lệ này nên được đo định kỳ trên một sample set cố định (ví dụ 20 session mẫu), không phải đo một lần rồi bỏ qua — vì drift thường chỉ lộ rõ sau nhiều vòng merge.

4. Round-trip fidelity test (kiểm tra độ trung thực khứ hồi)
Kỹ thuật hay: lấy rolling summary tại turn T, đưa cho một LLM khác (không biết gì về transcript gốc) và yêu cầu nó trả lời một bộ câu hỏi cụ thể về session (ví dụ: "Constraint nào đang áp dụng cho việc chọn database?", "Việc X đã hoàn thành hay còn tồn đọng?"). So sánh câu trả lời với ground truth từ raw transcript. Nếu câu trả lời sai hoặc "tôi không biết" cho câu hỏi mà đáng lẽ summary phải trả lời được — đó là fidelity fail, tính tỷ lệ pass/fail trên một bộ câu hỏi chuẩn hoá để theo dõi theo thời gian.

5. Merge cost overhead (chi phí phụ trội của việc merge)
Đo tổng token + tổng thời gian dùng cho các lệnh gọi LLM merge, so với token đã tiết kiệm được nhờ không phải gửi lại toàn bộ raw history. Nếu chi phí merge (bao gồm cả latency cộng dồn) vượt quá lợi ích tiết kiệm được, ngưỡng trigger (threshold) của bạn đang được đặt sai — có thể đang merge quá thường xuyên.

Gợi ý quy trình đánh giá thực tế: xây một bộ 10-20 session mẫu đại diện cho các loại công việc khác nhau (coding task dài, QA task nhiều bước, PM task theo dõi tiến độ), chạy incremental summarization qua các session này, rồi tính đủ 5 metric trên. Lặp lại mỗi khi bạn thay đổi merge prompt hoặc ngưỡng trigger — coi đây như một bộ regression test cho chất lượng context, không khác gì regression test cho code.

Mẹo:
- Xây decision retention rate và round-trip fidelity test thành eval tự động, chạy lại mỗi khi đổi merge prompt — coi summarization pipeline như code cần test, không phải "cấu hình rồi để đó".
- Dùng một model khác (khác với model dùng để merge) làm "judge" khi đo hallucination rate — tránh model tự đánh giá bài của chính nó (self-grading bias).
- Theo dõi compression ratio theo trục thời gian (không phải đo một lần) — xu hướng tăng dần là tín hiệu cảnh báo sớm quan trọng hơn giá trị tuyệt đối tại một thời điểm.
- Chia sẻ các metric này với PM/QA dưới dạng dashboard đơn giản — họ không cần hiểu cơ chế LLM, nhưng cần biết "agent của chúng ta có đang quên việc quan trọng không" bằng số liệu cụ thể, không phải cảm giác.

Summary

Incremental summarization thay thế việc tóm tắt "một lần khi context đầy" bằng một rolling summary — tài liệu sống, được cập nhật liên tục qua một vòng lặp gồm buffer tích luỹ turn mới, ngưỡng trigger (token/turn/thời gian), và bước merge bằng LLM. Chất lượng của toàn bộ hệ thống phụ thuộc rất lớn vào merge prompt — phải quy định rõ: giữ quyết định cũ, ưu tiên thông tin mới khi mâu thuẫn, không lặp lại thông tin, và xuất ra schema có cấu trúc để dễ validate.

Giữa hai chiến lược merge — delta-based (rẻ, nhanh, nhưng dễ phình) và full-rewrite (đắt hơn, nhưng tự dọn dẹp tốt hơn) — chiến lược hybrid (delta thường xuyên, full-rewrite định kỳ) thường là lựa chọn thực tế nhất cho các session dài. Vì rolling summary nhỏ và có cấu trúc, nó rất rẻ để persist, giúp session có thể resume sau gián đoạn — nhưng luôn cần bước validate/re-verify các fact "sống" bằng nguồn thật, không tin tuyệt đối vào summary cũ.

Cuối cùng, đừng triển khai kỹ thuật này mà không đo lường: compression ratio, decision retention rate, hallucination/drift rate, round-trip fidelity, và merge cost overhead là bộ năm metric tối thiểu để biết incremental summarization của bạn đang giúp agent thông minh hơn hay đang âm thầm làm nó quên việc quan trọng.