·

Một AI agent làm việc liên tục trong nhiều giờ, thậm chí nhiều ngày, sẽ tích lũy một lượng conversation history (lịch sử hội thoại) khổng lồ. Nếu không xử lý, context window (cửa sổ ngữ cảnh) sẽ đầy lên nhanh chóng bởi những thứ không còn giá trị: log lệnh ls, nội dung file đã đọc rồi bỏ qua, những vòng debug thất bại, hay các đoạn hội thoại "để tôi thử cách khác". Conversation summarization (tóm tắt hội thoại) là kỹ thuật nén (compress) toàn bộ lịch sử đó thành một bản tóm tắt ngắn, giữ lại đúng những gì agent — và con người review lại sau này — cần biết để tiếp tục công việc mà không đi lùi hay lặp lại sai lầm.

Đây không phải là việc "làm ngắn văn bản" theo nghĩa tóm tắt văn học. Đây là một bài toán kỹ thuật: xác định đâu là tín hiệu (signal) mang tính quyết định cho công việc, đâu là nhiễu (noise) chỉ tồn tại trong quá trình thực thi. Sai một ly trong việc phân loại này — tóm tắt quá tay và làm mất một constraint (ràng buộc) quan trọng — có thể khiến agent quay lại vi phạm một quyết định đã bị bác bỏ từ ba giờ trước, gây lãng phí token gấp nhiều lần so với số token bạn tiết kiệm được từ việc tóm tắt.

Understanding What Belongs in a Summary

Nguyên tắc cốt lõi: một bản tóm tắt hội thoại tốt phải bảo toàn quyết định (decision), ràng buộc (constraint), và trạng thái (state) — còn tất cả những gì chỉ là "con đường để đi tới quyết định đó" thì có thể bỏ.

Hãy tưởng tượng agent của bạn vừa làm việc 40 turn để tìm ra nguyên nhân một bug memory leak. Trong 40 turn đó có: 15 lần đọc file để định vị đoạn code liên quan, 8 lần chạy test thất bại với stack trace dài hàng trăm dòng, 3 giả thuyết bị bác bỏ, và cuối cùng là 1 giả thuyết đúng dẫn tới fix. Nếu tóm tắt giữ lại toàn bộ 40 turn, bạn không tiết kiệm được gì. Nếu tóm tắt chỉ giữ "đã fix xong bug" mà bỏ luôn 3 giả thuyết đã bị bác bỏ, agent thế hệ sau (hoặc chính agent này sau khi bị compact) có thể vô tình đề xuất lại đúng giả thuyết đã test và sai.

Khung phân loại signal vs. noise mà tôi hay dùng khi huấn luyện agent tóm tắt:

Loại thông tin Giữ hay bỏ Vì sao
Quyết định kỹ thuật đã chốt (chọn library, chọn kiến trúc) Giữ Ảnh hưởng lâu dài đến toàn bộ phần còn lại của công việc
Constraint / requirement từ người dùng Giữ Vi phạm constraint là lỗi nghiêm trọng, khó phát hiện
Trạng thái hiện tại của hệ thống (file nào đã sửa, test nào đang fail) Giữ Cần biết để tiếp tục đúng chỗ
Raw tool output (nội dung file, log, stack trace đầy đủ) Bỏ Chỉ cần kết luận rút ra từ đó, không cần dữ liệu thô
Exploratory dead-end (nhánh thử nghiệm không dẫn tới đâu) Bỏ, nhưng ghi lại kết luận "đã thử và không dùng" Chi tiết quá trình thử không cần, nhưng việc "đã bị bác bỏ" thì cần giữ
Việc đọc file lặp lại nhiều lần cùng nội dung Bỏ hoàn toàn Không mang thông tin mới
Câu hỏi làm rõ (clarifying question) và câu trả lời Giữ nếu câu trả lời thay đổi hướng đi, bỏ nếu chỉ xác nhận cái đã rõ Chỉ giữ khi nó tạo ra quyết định mới

Một cách kiểm tra nhanh: với mỗi đoạn trong hội thoại, tự hỏi "Nếu tôi xoá đoạn này, agent tiếp theo có làm sai việc gì không, hoặc có tốn công làm lại việc gì không?" Nếu câu trả lời là không, đoạn đó là noise. Nếu có, nó là signal cần được cô đọng (không cần giữ nguyên văn, chỉ cần giữ kết luận).

Một điểm dễ bị bỏ qua: noise không có nghĩa là vô giá trị tại thời điểm nó xảy ra. 8 lần chạy test thất bại rất có giá trị lúc debug — nó là cách agent thu thập thông tin. Nhưng giá trị đó đã được "rút chiết" (extract) vào kết luận cuối cùng rồi. Giữ lại raw log sau khi đã có kết luận là giữ lại một bản sao dữ liệu không còn tác dụng gì thêm.

Mẹo:
- Khi agent báo cáo "tôi đã thử X và Y, không hiệu quả, giải pháp cuối là Z", hãy huấn luyện việc tóm tắt giữ nguyên câu "đã thử X và Y — không dùng" chứ không xoá sạch, vì đây chính là "bộ nhớ chống lặp sai lầm".
- Đừng tóm tắt theo trình tự thời gian (timeline) một cách máy móc. Hãy tóm tắt theo trạng thái hiện tại — tức là "bây giờ mọi thứ đang như thế nào", không phải "chuyện gì đã xảy ra theo thứ tự nào".
- Với QA/tester: áp dụng đúng nguyên tắc này khi tóm tắt bug report dài — giữ steps-to-reproduce đã xác nhận, bỏ các lần thử tái hiện lỗi thất bại không mang thêm thông tin.

The Structured Summary Format

Một bản tóm tắt dạng văn xuôi tự do (freeform prose) rất dễ bị viết lan man, thiếu nhất quán, và khó agent (hoặc con người) quét nhanh để lấy đúng thông tin cần. Giải pháp là dùng một template có cấu trúc cố định, buộc quá trình tóm tắt phải phân loại thông tin vào đúng "ô" của nó — điều này tự động loại bỏ phần lớn noise vì noise thường không thuộc vào ô nào rõ ràng.

Dưới đây là template tôi khuyến nghị dùng làm chuẩn cho conversation summarization trong agentic workflow:

## Session Summary

### Objective
<1-2 sentences: what is the overall goal of this session/task>

### Decisions Made
- <Decision 1> — Reason: <why this decision was made>
- <Decision 2> — Reason: <why this decision was made>

### Constraints & Requirements
- <Constraint from user, spec, or system that MUST be respected>
- <Constraint 2>

### Work Completed
- <File/module changed> — <what changed, one line>
- <File/module changed> — <what changed, one line>

### Current State
- <What is working right now>
- <What is NOT working / in progress>
- <Which branch/commit/environment this applies to>

### Open Questions
- <Question that still needs an answer before proceeding>

### Known Issues / Blockers
- <Issue> — <impact> — <status: investigating/waiting on X/deferred>

### Do Not Revisit
- <Approach X> — Rejected because: <concrete reason>
- <Approach Y> — Rejected because: <concrete reason>

Giải thích ý nghĩa từng phần:

  • Objective giữ agent luôn "định hướng bắc" — kể cả sau nhiều lần compact, agent vẫn biết nó đang làm gì tổng thể, không chỉ nhớ chi tiết vụn.
  • Decisions Made khác với "Work Completed" — đây là các lựa chọn có tính chi phối (governing), ví dụ "dùng PostgreSQL thay vì MongoDB", "áp dụng optimistic locking cho bảng orders". Luôn kèm lý do (Reason), vì lý do mới là thứ giúp agent tương lai đánh giá liệu quyết định đó còn hợp lệ khi hoàn cảnh thay đổi.
  • Constraints & Requirements là những điều bất biến do bên ngoài áp vào (người dùng, security policy, business rule) — khác quyết định kỹ thuật, constraint không phải để agent tự do đánh giá lại.
  • Work Completed là nhật ký thay đổi thực tế, súc tích, dùng để biết "đã đụng vào đâu".
  • Current State là ảnh chụp (snapshot) hiện tại — trả lời câu "nếu tôi bắt đầu từ đây, tôi đang đứng ở đâu".
  • Open Questions ngăn agent tự bịa câu trả lời cho những gì chưa được xác nhận.
  • Known Issues / Blockers tách bạch với "Current State" để nhấn mạnh đây là vấn đề cần hành động, không phải trạng thái bình thường.
  • Do Not Revisit là phần quan trọng nhất và thường bị bỏ sót nhất. Đây là "danh sách đen" các hướng đi đã bị thử và loại bỏ, cùng lý do. Giá trị của phần này: nó ngăn agent — vốn không có trực giác về "đã từng thử cái này rồi" như con người — đề xuất lại đúng một giải pháp đã bị bác bỏ, khiến nhóm phải giải thích lại từ đầu, hoặc tệ hơn, khiến agent lặng lẽ áp dụng lại một cách đã biết là sai.

Mẹo:
- Giữ mỗi mục trong template là một dòng, một ý. Nếu một quyết định cần giải thích dài, tách phần giải thích ra một ghi chú riêng, đừng để nó phình to trong bảng tóm tắt.
- Đặt "Do Not Revisit" ngay dưới "Known Issues" (như cấu trúc trên) để nó luôn được agent đọc lại trước khi bắt đầu lượt làm việc tiếp theo — thứ tự trong file ảnh hưởng đến việc agent có "để ý" tới nó hay không.
- Với các task dạng QA/testing, có thể thêm một dòng biến thể: "Test cases confirmed failing" và "Test cases confirmed passing" vào đúng chỗ Current State, để tránh nhầm giữa test đã pass và test chưa từng chạy lại.

Summarization Prompt Patterns

Để agent (hoặc một LLM call riêng dùng làm "summarizer") tạo ra bản tóm tắt đúng theo cấu trúc trên một cách nhất quán, bạn cần một prompt pattern rõ ràng, chỉ định chính xác định dạng đầu ra và các quy tắc lọc thông tin. Dưới đây là ba mẫu prompt cụ thể bạn có thể dùng trực tiếp hoặc chỉnh sửa cho hệ thống của mình.

Mẫu 1 — Full session summarization (dùng khi kết thúc phiên hoặc trước khi compact context):

You are compressing a long agent conversation into a structured summary
so that work can continue in a fresh context window without losing any
decision, constraint, or blocking issue.

Rules:
- Preserve every decision that affects future work, with its reasoning.
- Preserve every constraint or requirement stated by the user or found
  in specs/config, verbatim where precision matters (numbers, thresholds,
  API contracts).
- Discard raw tool output, file contents already superseded by later
  edits, and exploratory dead-ends — but KEEP the conclusion of any
  dead-end as an entry in "Do Not Revisit" with a concrete reason.
- Do not merge or average conflicting decisions. If a later message
  overrides an earlier one, keep only the LATEST decision and drop the
  earlier one entirely — do not mention it was "changed", just state the
  current decision as fact.
- Be terse. Each bullet is one line. No prose paragraphs.

Output strictly in this Markdown format:
[paste the structured template here]

Conversation to summarize:
[conversation history]

Mẫu 2 — Incremental re-summarization (dùng khi đã có bản tóm tắt cũ, cần cập nhật thêm phần hội thoại mới):

You already have a structured summary of prior work (below). New
conversation turns have happened since. Update the summary in place:

- Merge new "Decisions Made" and "Work Completed" entries into the
  existing lists.
- If a new decision contradicts an existing one, REPLACE the old entry
  entirely with the new one. Do not keep both. Do not note it as
  "updated" — the summary should read as if the latest decision was
  always the only decision.
- Move any newly resolved item from "Open Questions" or "Known Issues"
  into "Work Completed" or "Decisions Made" as appropriate, and remove
  it from its original section.
- Add any new rejected approach to "Do Not Revisit".
- Keep the same section structure. Do not invent new sections.

Existing summary:
[current structured summary]

New conversation turns:
[new turns only]

Mẫu 3 — Targeted extraction (dùng khi bạn chỉ cần trích ra constraints, không cần tóm tắt toàn bộ, ví dụ để đưa cho một sub-agent khác):

Extract ONLY the constraints and decisions from the conversation below
that are still valid as of the latest message. Ignore anything that was
explicitly reversed or superseded later in the conversation. Output as
a flat bullet list, no other commentary, no section headers:

- <constraint or decision, one line each>

Conversation:
[conversation history]

Vì sao ba mẫu này khác nhau về mục đích: Mẫu 1 dùng cho tóm tắt toàn phần một lần — chi phí token cao hơn nhưng chỉ chạy một lần khi cần "reset" context. Mẫu 2 rẻ hơn vì chỉ xử lý phần mới, phù hợp với chiến lược incremental summarization chạy định kỳ (xem thêm ở topic kế tiếp trong module này). Mẫu 3 dùng khi bạn cần đưa context tối thiểu cho một sub-agent chuyên biệt (ví dụ agent viết test chỉ cần biết constraint, không cần biết lịch sử debug).

Mẹo:
- Luôn ghi rõ trong prompt câu "do not merge or average conflicting decisions" — nếu không, LLM có xu hướng tự nhiên tổng hợp hai quan điểm trái ngược thành một câu mơ hồ kiểu "có thể dùng cách A hoặc cách B tùy tình huống", vốn không phản ánh đúng quyết định thật đã được chốt.
- Test prompt tóm tắt của bạn bằng cách cố tình đưa vào một đoạn hội thoại có mâu thuẫn rõ (đổi ý giữa hai giải pháp) và kiểm tra xem output có giữ đúng quyết định cuối hay không.
- Nếu dùng LLM riêng để làm summarizer (tách khỏi agent chính), chọn model rẻ hơn/nhanh hơn cho việc này — tóm tắt là tác vụ có định dạng rõ, không cần model mạnh nhất, và chạy khá thường xuyên nên chi phí cộng dồn đáng kể.

Before/After: A Concrete Example

Hãy xem một ví dụ thực tế: một agent được giao nhiệm vụ triển khai retry logic cho Payment Module trong hệ thống thanh toán.

BEFORE — trích đoạn hội thoại thô (khoảng 35 turn, rút gọn còn phần minh họa):

User: Thêm retry cho payment gateway call, đang bị timeout ngẫu nhiên.

Agent: Đọc file PaymentService.java... [nội dung file 220 dòng]
Tôi thấy có 1 call tới gateway.charge(). Tôi sẽ thêm retry với
exponential backoff, 5 lần, delay bắt đầu 1s.

User: Không, đừng retry charge() — nếu retry mà charge() thành công ở
lần gọi trước nhưng response timeout, sẽ bị double charge. Chỉ retry
cho các lỗi network trước khi request được gửi tới gateway thành công.

Agent: Hiểu rồi. Tôi sẽ dùng idempotency key để tránh double charge,
retry tối đa 5 lần.

User: 5 lần nhiều quá, gateway của mình rate-limit ở 3 request/giây
cho mỗi merchant. Giảm xuống 3 lần, backoff base 2s.

Agent: [chạy test] Test thất bại, NullPointerException tại
IdempotencyKeyGenerator.

Agent: [đọc file IdempotencyKeyGenerator.java lần 1]
Agent: [đọc lại file IdempotencyKeyGenerator.java lần 2 do quên nội dung]
Agent: Tìm ra nguyên nhân: key generator cần merchantId nhưng
PaymentRequest không có field này trong context hiện tại.

Agent: Tôi định thêm merchantId vào PaymentRequest DTO.

User: Không được sửa PaymentRequest DTO, nó là shared contract với
3 service khác, mọi thay đổi phải qua API versioning riêng. Lấy
merchantId từ SecurityContext hiện có của request thay vì thêm field.

Agent: Đã sửa, dùng SecurityContext.getMerchantId(). Test pass.

Agent: [chạy full test suite, 45 test, 43 pass, 2 fail không liên quan
tới thay đổi này — 2 test đó fail do thiếu mock cho EmailService]

User: 2 test đó là bug cũ, biết rồi, để team khác fix, đừng đụng vào.

Agent: Rõ. Vậy tôi hoàn tất phần retry logic. Còn phần logging cho mỗi
lần retry thì sao, có cần log ra mức nào?

User: Log ở mức WARN, kèm attempt number và merchantId, không log
raw request body vì có dữ liệu thẻ.

Agent: Đã thêm log. Xong phần này.

AFTER — bản tóm tắt theo template chuẩn:

## Session Summary

### Objective
Add retry logic for payment gateway timeouts in Payment Module.

### Decisions Made
- Retry only network-layer failures before the request reaches the
  gateway — never retry after a charge() call may have succeeded, to
  avoid double charges. Reason: gateway response timeout does not mean
  the charge failed.
- Use idempotency key per request to make retries safe.
- Retry count = 3, backoff base = 2s. Reason: gateway rate limit is
  3 req/sec per merchant.
- Get merchantId from SecurityContext.getMerchantId(), not from
  PaymentRequest DTO. Reason: PaymentRequest is a shared contract with
  3 other services; changes require API versioning.
- Retry logs at WARN level, include attempt number and merchantId.
  Never log raw request body (contains card data).

### Constraints & Requirements
- Never modify PaymentRequest DTO without going through API versioning.
- Gateway rate limit: 3 requests/second per merchant.
- No card data in logs, ever.

### Work Completed
- PaymentService.java — added retry wrapper around gateway call with
  idempotency key, 3 attempts, 2s exponential backoff.
- IdempotencyKeyGenerator.java — now sources merchantId from
  SecurityContext instead of requiring it as a parameter.
- Added WARN-level retry logging (attempt number + merchantId only).

### Current State
- Full test suite: 45 tests, 43 passing. Retry logic verified working.
- 2 pre-existing failures in EmailService-related tests, unrelated to
  this change — owned by another team.

### Open Questions
- (none currently)

### Known Issues / Blockers
- EmailService test mocks missing — pre-existing, out of scope, owned
  by another team. Do not fix as part of this task.

### Do Not Revisit
- Retrying charge() call directly — Rejected because: risks double
  charging when the charge succeeded but the response timed out.
- Adding merchantId as a new field on PaymentRequest DTO — Rejected
  because: PaymentRequest is a shared contract across 3 services;
  breaking changes require formal API versioning, not ad-hoc field
  additions.
- Retry count of 5 — Rejected because: exceeds the gateway's 3 req/sec
  rate limit per merchant.

So sánh hai bản: bản BEFORE có khoảng 35 turn, chứa toàn văn file 220 dòng, hai lần đọc trùng lặp file, chi tiết stack trace, và một đề xuất bị bác bỏ (thêm field vào DTO) được diễn giải dài dòng. Bản AFTER cô đọng toàn bộ vào chưa tới 40 dòng, nhưng không mất bất kỳ constraint, quyết định, hay lý do nào. Đặc biệt, phần "Do Not Revisit" giữ lại chính xác ba hướng đi bị bác bỏ — đây là phần sẽ cứu agent (hoặc agent mới tiếp nhận task) khỏi việc đề xuất lại "retry charge() trực tiếp" hoặc "thêm field vào DTO" ở một session sau.

Mẹo:
- Khi luyện agent của bạn tóm tắt, hãy tự tay viết vài ví dụ before/after như trên và dùng chúng làm few-shot example ngay trong system prompt của summarizer — hiệu quả hơn nhiều so với chỉ mô tả quy tắc bằng lời.
- Chú ý cách bản AFTER loại bỏ hoàn toàn việc "2 lần đọc file trùng lặp" — đây là ví dụ điển hình của noise do agent tự gây ra (self-inflicted), không phải do người dùng, và rất dễ bị bỏ sót nếu bạn chỉ tập trung lọc theo message của user.
- Test nhanh chất lượng tóm tắt: đưa bản AFTER cho một người (hoặc một agent) chưa từng thấy bản BEFORE, hỏi họ "giờ bạn có thể tiếp tục task này không, và có nguy cơ lặp lại sai lầm nào không?" Nếu câu trả lời là "có nguy cơ", tóm tắt chưa đạt.

Handling Contradictions and Course Corrections

Trong bất kỳ hội thoại đủ dài, gần như chắc chắn sẽ có lúc một quyết định trước đó bị đổi. Người dùng đổi ý ("thôi dùng Redis thay vì trong-memory cache"), agent tự phát hiện quyết định trước không khả thi ("hoá ra library X không hỗ trợ tính năng cần thiết, phải chuyển sang Y"), hoặc một constraint mới xuất hiện làm vô hiệu hoá lựa chọn cũ. Đây gọi là course correction (điều chỉnh hướng đi), và cách một bản tóm tắt xử lý nó quyết định trực tiếp việc agent có lặp lại sai lầm hay không.

Quy tắc bắt buộc: tóm tắt phải phản ánh quyết định MỚI NHẤT, không phải một phiên bản trộn lẫn (merged) hay trung bình (averaged) giữa các quyết định. Đây là lỗi phổ biến nhất khi LLM tự động tóm tắt hội thoại có mâu thuẫn — vì bản chất LLM có xu hướng "hoà giải" (reconcile) hai phát biểu trái ngược thành một câu mơ hồ, ví dụ: "Retry count nên là 3-5 lần tuỳ tình huống" — trong khi thực tế quyết định cuối cùng và duy nhất đang có hiệu lực là "3 lần". Câu mơ hồ này trông có vẻ an toàn nhưng thực chất đã xoá bỏ một quyết định rõ ràng đã chốt, khiến agent tương lai phải tự đoán lại.

Cách xử lý đúng gồm ba bước:

  1. Xác định chuỗi thời gian của quyết định. Với mỗi chủ đề (ví dụ: "số lần retry"), liệt kê mọi lần chủ đề đó được nhắc tới, theo đúng thứ tự xuất hiện trong hội thoại.
  2. Chỉ giữ giá trị ở lần nhắc cuối cùng, miễn là lần nhắc đó là một quyết định thật (không phải câu hỏi, không phải đề xuất chưa được chốt). Toàn bộ giá trị trước đó bị loại khỏi phần "Decisions Made" — không ghi "trước đó là X, sau đổi thành Y", chỉ ghi Y như một sự thật hiện tại.
  3. Nếu giá trị cũ có lý do bị bác bỏ đáng nhớ (ví dụ: "5 lần vượt rate limit"), chuyển nó sang "Do Not Revisit" kèm lý do — đây là chỗ duy nhất lịch sử của quyết định cũ còn được lưu, và chỉ lưu dưới dạng "đừng làm lại", không lưu dưới dạng "đây từng là lựa chọn".

Một trường hợp khó hơn là khi sự mâu thuẫn không rõ ràng — ví dụ người dùng nói "cân nhắc dùng cache" ở đầu hội thoại, và ở cuối nói "tạm thời bỏ ý tưởng cache, ưu tiên fix bug trước". Đây không hẳn là quyết định kỹ thuật bị đảo ngược, mà là một ưu tiên bị hoãn (deferred, not rejected). Trường hợp này nên đưa vào "Open Questions" hoặc một mục riêng, không đưa vào "Do Not Revisit" — vì "Do Not Revisit" ngụ ý "đã xem xét và bác bỏ vĩnh viễn", còn "hoãn lại" ngụ ý "có thể quay lại sau, khi có thời gian". Nhầm hai loại này sẽ khiến agent tương lai bỏ qua một ý tưởng còn giá trị, hoặc ngược lại, cứ lặp lại đề xuất một ý tưởng đã bị bác bỏ hẳn.

Với các đội làm QA/kiểm thử, tình huống tương tự xảy ra khi bug report ban đầu mô tả một hiện tượng, sau đó điều tra sâu hơn phát hiện nguyên nhân gốc khác hẳn ban đầu. Bản tóm tắt bug không nên giữ cả "giả thuyết ban đầu" và "nguyên nhân thật" ở cùng mức ưu tiên — nguyên nhân thật (root cause) là quyết định mới nhất và duy nhất còn hiệu lực, giả thuyết ban đầu chỉ nên xuất hiện (nếu cần) trong "Do Not Revisit" để tránh điều tra viên sau quay lại giả thuyết sai.

Mẹo:
- Khi viết prompt cho summarizer, luôn thêm câu tường minh: "if a later statement contradicts an earlier one, the later statement wins completely — do not blend them." Không có câu này, model rất dễ tự "làm hoà" hai quan điểm.
- Phân biệt rõ hai loại "quyết định cũ không còn hiệu lực": loại bị bác bỏ hẳn (đưa vào Do Not Revisit) và loại bị hoãn/chưa quyết (đưa vào Open Questions). Gộp chung hai loại này là nguồn lỗi phổ biến.
- Nếu hệ thống của bạn cho phép, gắn timestamp hoặc turn-number vào mỗi decision trong log gốc trước khi tóm tắt — việc này giúp summarizer (và con người review) dễ xác định "cái nào mới nhất" một cách khách quan, không phải suy đoán.

Automating Summarization in Your Workflow

Tóm tắt hội thoại chỉ có giá trị thực tế khi nó được kích hoạt đúng lúc và đúng cách, không đòi hỏi con người phải nhớ "bấm nút tóm tắt". Có ba nhóm điểm kích hoạt (trigger point) phổ biến, và bạn thường cần kết hợp cả ba trong một hệ thống production:

1. Ngưỡng token (token threshold). Đây là trigger phổ biến nhất: khi context window đã dùng vượt một tỷ lệ nhất định (ví dụ 70-80% capacity), hệ thống tự động gọi summarizer trước khi tiếp tục, để tránh bị cắt cụt context giữa lượt làm việc. Ưu điểm: phản ứng đúng với tải thực tế, không tóm tắt khi chưa cần. Nhược điểm: nếu ngưỡng đặt quá cao, bạn có nguy cơ tóm tắt "khẩn cấp" ngay trước khi hết chỗ, dễ tóm vội và mất chi tiết.

2. Số lượt hội thoại (turn count). Kích hoạt tóm tắt sau mỗi N turn cố định (ví dụ mỗi 20 turn), bất kể đã dùng bao nhiêu token. Cách này dự đoán được (predictable), dễ lên lịch, nhưng không tối ưu — với những task có turn "nặng" (nhiều tool output lớn), 20 turn có thể đã đầy context; với task "nhẹ", 20 turn có thể vẫn còn dư nhiều chỗ.

3. Kết thúc phiên / cột mốc công việc (session end / milestone). Kích hoạt khi agent hoàn tất một task lớn, chuyển giao cho người khác, hoặc khi phiên làm việc (session) đóng lại. Đây là điểm tự nhiên nhất để tạo một bản tóm tắt "chính thức" dùng làm điểm khởi đầu cho phiên tiếp theo — tương tự khái niệm checkpoint. Trigger này thường kết hợp với hai trigger trên: dùng token threshold / turn count để tóm tắt "dọc đường" giữ context gọn, và dùng session-end summary để tạo bản ghi chính thức, đầy đủ nhất.

Ai/cái gì kích hoạt lời gọi tóm tắt? Trong thực tế có ba mô hình:

  • Orchestrator-driven: một lớp điều phối (orchestrator) bên ngoài agent theo dõi token count sau mỗi lượt, và tự động chèn một lời gọi summarization khi vượt ngưỡng — agent chính không cần "biết" việc này đang xảy ra, nó chỉ nhận lại context đã được thay bằng bản tóm tắt ở lượt kế tiếp.
  • Self-triggered: agent chính được cấp một tool riêng (ví dụ summarize_and_checkpoint) và một hướng dẫn trong system prompt để tự gọi tool này khi nó nhận thấy context đang dài hoặc khi hoàn tất một milestone. Cách này linh hoạt hơn nhưng phụ thuộc vào việc agent "nhận biết đúng lúc" — cần test kỹ để đảm bảo agent không quên gọi.
  • Human-triggered: người dùng chủ động gõ lệnh (ví dụ slash command /compact hoặc tương tự) khi họ cảm thấy hội thoại đã dài. Phù hợp cho công cụ tương tác trực tiếp với người dùng, nhưng không đáng tin cậy cho pipeline tự động chạy không giám sát.

Về mặt tooling, một pattern triển khai thực tế:

1. Middleware đo token count sau mỗi response.
2. Nếu token count > threshold (ví dụ 75% context window):
   a. Gọi summarizer LLM (mẫu prompt "Full session summarization"
      ở phần trên) với toàn bộ conversation history hiện tại.
   b. Validate output: đúng format Markdown, đủ các section bắt buộc,
      "Do Not Revisit" không rỗng nếu có ít nhất 1 dead-end đã xảy ra.
   c. Nếu validate fail: retry summarization tối đa 1 lần với prompt
      nhấn mạnh lỗi format; nếu vẫn fail, giữ nguyên context cũ và
      log cảnh báo (không âm thầm xoá dữ liệu khi tóm tắt lỗi).
   d. Nếu validate pass: thay toàn bộ conversation history bằng
      [system message chứa bản tóm tắt] + [N turn gần nhất giữ nguyên
      nguyên văn, để không mất chi tiết vừa xảy ra].
3. Lưu bản tóm tắt ra một checkpoint file/log riêng (không chỉ giữ
   trong context) để con người có thể audit lại sau.

Chi tiết đáng chú ý ở bước 2d: không nên tóm tắt luôn cả những turn gần nhất. Giữ nguyên vài turn cuối (raw) giúp agent không bị "mất trí nhớ ngắn hạn" ngay sau khi vừa tóm tắt — vì bản tóm tắt luôn có độ trễ nhận thức nhẹ so với chi tiết vừa xảy ra.

Mẹo:
- Luôn validate output của summarizer trước khi dùng để thay thế context cũ. Một bản tóm tắt lỗi format hoặc thiếu section quan trọng còn nguy hiểm hơn không tóm tắt, vì nó tạo cảm giác an toàn giả.
- Lưu mọi bản tóm tắt ra log/file bên ngoài (không chỉ giữ trong context của agent) — đây là "audit trail" giúp con người truy lại lý do một quyết định, đặc biệt quan trọng khi có sự cố production cần biết "ai/cái gì quyết định thông số này".
- Đặt ngưỡng token threshold có biên an toàn (ví dụ 75% chứ không phải 95%) — tóm tắt cần chỗ trống để chính bản thân quá trình tóm tắt (đọc toàn bộ history, sinh output) không làm tràn context ngay trong lúc đang cố giải quyết vấn đề tràn context.
- Với team chưa có hạ tầng orchestrator riêng, bắt đầu đơn giản: cấu hình self-triggered với một checklist rõ trong system prompt ("sau mỗi 15-20 turn hoặc khi hoàn tất 1 task con, hãy tự tóm tắt") — chi phí triển khai thấp, đủ dùng cho nhiều trường hợp thực tế.

Summary

Conversation summarization không phải là "cắt ngắn cho gọn" — đó là một kỹ thuật kỹ thuật nghiêm túc nhằm bảo toàn decision, constraint, và state trong khi loại bỏ raw tool output, dead-end không còn giá trị, và các lần đọc/thử lặp lại. Một template có cấu trúc rõ ràng — với các section như Decisions Made, Constraints & Requirements, Current State, và đặc biệt là Do Not Revisit — buộc quá trình tóm tắt phải phân loại đúng thông tin, thay vì viết văn xuôi mơ hồ. Khi hội thoại có mâu thuẫn hoặc course correction, nguyên tắc bất biến là giữ quyết định mới nhất, không trộn lẫn các phiên bản trái ngược. Cuối cùng, để hệ thống vận hành ổn định, việc tóm tắt cần được tự động hoá với các trigger point rõ ràng (token threshold, turn count, session end) và có bước validate output trước khi thay thế context — vì một bản tóm tắt sai còn nguy hiểm hơn là không tóm tắt.