Context window 1 triệu token nghe rất thoải mái. Nhưng nếu bạn đã từng ngồi debug một vấn đề khó trong hai đến ba giờ liên tục, bạn sẽ nhận ra một điều: CC bắt đầu trả lời mơ hồ hơn về những gì đã làm trước đó. Không phải model “kém thông minh” hơn. Là compaction đã chạy và compress đi những chi tiết nuance trong history.

Bài 1 đã nhắc đến compaction như một dòng chú thích ngắn trong phần L5. Bài này đi sâu đúng như đã hứa: cơ chế bên trong, tại sao nó là đánh đổi tất yếu, và cách làm việc với nó thay vì chống lại nó.

Nửa sau bài này xử lý phần song hành: prompt cache 1h TTL, lý do nó tồn tại, và tại sao warmed session lại rẻ và nhanh hơn cold start nhiều đến vậy.

Tại sao context window finite vẫn là vấn đề

Trước khi vào cơ chế, cần hiểu tại sao giới hạn vẫn bị chạm dù context window ngày càng lớn.

Một session làm việc điển hình tích lũy token rất nhanh:

User message          ~  200-500 token mỗi turn
Assistant response    ~  500-2000 token mỗi turn
Tool call + result    ~  200-5000 token (Read file lớn = nhiều token)
System prompt L1      ~  2000-4000 token (binary-generated, mỗi turn)
L2 rules + CLAUDE.md  ~  1000-3000 token
L3 tool schemas       ~  3000-8000 token (phụ thuộc số tool enabled)
L4 memory             ~  500-2000 token

Một turn trung bình có thể tốn 10.000 đến 20.000 token. Conversation 50 turn là 500.000 đến 1 triệu token. Ngay cả với context window 1M token, một session coding dài hơi vẫn sẽ chạm ngưỡng.

Ngoài ra: context window lớn hơn không có nghĩa là xử lý toàn bộ context đó là miễn phí. Token càng nhiều, latency càng tăng, cost càng cao. Binary không đợi đến khi tràn mới hành động.

Khi nào binary trigger compaction

Binary theo dõi token count của conversation liên tục. Không có một ngưỡng cứng duy nhất được document chính thức, nhưng hành vi quan sát được cho thấy:

Ngưỡng xấp xỉ 70-80% context window là điểm binary bắt đầu cân nhắc compaction. Không phải 100%, vì cần để dành room cho response và tool call tiếp theo.

Signals cụ thể binary dùng:

- Token count trong conversation history vượt threshold
- Số message trong history quá dài
- User gõ lệnh /compact thủ công

Lệnh /compact là cách thủ công trigger compaction bất cứ lúc nào. Hữu ích khi bạn biết mình sắp chuyển sang task hoàn toàn mới trong cùng session, muốn “dọn dẹp” context nhưng không muốn mất tiến trình.

Compaction hoạt động thế nào bên trong

Khi trigger, compaction không đơn giản là “cắt bớt tin nhắn cũ”. Nó làm thứ tinh tế hơn và cũng đánh đổi nhiều hơn.

Bước 1: Tách history thành “cold” và “warm”

Binary chia conversation history thành hai phần:

+---------------------------+
|  Recent messages (warm)   |  <- giữ nguyên, không nén
+---------------------------+
|  Older messages (cold)    |  <- candidates for compaction
+---------------------------+

Phần warm (thường là một số turn gần nhất) được giữ nguyên để đảm bảo continuity với những gì đang xảy ra. Phần cold là input cho bước tiếp theo.

Bước 2: Model tóm tắt phần cold

Binary gửi phần cold sang model với một prompt yêu cầu tóm tắt thành một summary compact. Summary này cố gắng capture:

  • Các quyết định đã thống nhất
  • Các file đã edit và tại sao
  • Các vấn đề đã giải quyết
  • Context quan trọng còn relevant

Đây là điểm mấu chốt: quá trình này chạy thêm một lần inference riêng. Có latency, có cost.

Bước 3: Replace cold messages bằng summary

Sau khi có summary, binary replace toàn bộ cold messages bằng summary đó. Kết quả là history lúc này có cấu trúc:

+-------------------------------+
|  Summary: "đã làm X, Y, Z..." |  <- thay cold messages
+-------------------------------+
|  Recent messages (warm)       |  <- giữ nguyên
+-------------------------------+
|  Current turn                 |
+-------------------------------+

Token count giảm mạnh. Session có thể tiếp tục.

Bước 4: Session tiếp tục với compressed context

Model nhận summary thay vì raw history. Từ góc nhìn của model, “nó nhớ” đã làm X Y Z vì summary nói thế. Nhưng chi tiết nguyên bản của X Y Z đã mất.

Limitation: cái gì bị mất trong compaction

Đây là phần quan trọng nhất của bài, và cũng là phần ít được nói đến nhất.

Summary không giống lịch sử gốc. Khi model tóm tắt, nó ưu tiên những gì có vẻ “quan trọng” và bỏ qua những gì có vẻ “chi tiết nhỏ”. Nhưng trong coding, chi tiết nhỏ thường là thứ quan trọng nhất.

Các loại thông tin hay bị mất hoặc flatten:

DẠNG BỊ MẤT                    VÍ DỤ
---
Chi tiết về lý do quyết định    "Chọn cách này vì cách kia có bug X"
                                 -> summary chỉ ghi "đã chọn cách này"

Nuance về trạng thái đang dở    "File A.ts đang trong trạng thái
                                  half-refactored, chưa xong"
                                 -> summary có thể ghi "đã refactor A.ts"

Những gì đã thử và không hoạt   "Thử approach B, thất bại vì Y"
động                             -> bị drop khỏi summary hoàn toàn

Exact content của file đã read  Binary đã read file, nhưng raw
                                  content biến mất sau compaction

Thứ tự sự kiện                  Đôi khi summary đảo ngược thứ
                                  tự: mô tả kết quả trước nguyên nhân

Kết quả thực tế: sau compaction, nếu bạn hỏi CC “tại sao hồi nãy mình chọn cách này?”, nó có thể trả lời sai hoặc nói “tôi không có đủ context để trả lời chắc chắn”.

Ví dụ từ session thực

Đây là pattern phổ biến mà nhiều người gặp nhưng không biết nguyên nhân:

Bạn debug một vấn đề phức tạp. Sau 1 tiếng, CC giúp bạn tìm ra root cause ở file Z. Bạn sửa xong, tiếp tục làm task khác. 30 phút sau, bạn hỏi lại về vấn đề đó. CC trả lời nghe “mơ hồ” hoặc không nhất quán với kết luận trước.

Lý do: compaction đã chạy và compress quá trình debug 1 tiếng đó thành một dòng summary. Chi tiết về root cause, quá trình suy luận, các file liên quan, tất cả đã bị flatten.

Best practice khi làm việc với compaction

Biết cơ chế rồi thì làm gì với nó? Câu trả lời là làm việc theo cách giảm thiểu damage khi compaction xảy ra.

/clear khi đổi context lớn

Khi bạn xong một task hoàn toàn và chuyển sang task không liên quan, đừng tiếp tục trong cùng session. Gõ /clear để reset conversation history về trống.

Lợi ích: L2 (CLAUDE.md, rules), L3 (tools), L4 (memory) vẫn còn nguyên. Chỉ L5 (conversation history) bị xóa. Bạn không mất config, chỉ mất history mà bạn không cần nữa.

So sánh:

Hành viKết quả
Tiếp tục session cũ sang task mớiHistory cũ tích lũy, cuối cùng bị compaction bất ngờ giữa chừng
/clear chủ động khi đổi taskHistory sạch, compaction không xảy ra đột ngột, context rõ ràng

Dùng memory để persist quyết định quan trọng

Compaction compress conversation history, nhưng memory (~/.claude/projects/.../memory/MEMORY.md) không bị touch bởi compaction. Đây là lớp L4, không phải L5.

Workflow: sau khi đưa ra một quyết định kiến trúc quan trọng, yêu cầu CC ghi vào memory. Quyết định đó sẽ sống trong L4, tồn tại qua mọi compaction và mọi /clear.

# Trong session, sau khi quyết định xong
> ghi vào memory: "auth flow dùng cookie-based session,
  không dùng JWT vì X"

Bài 12 sẽ đi sâu vào auto-memory: cách memory hoạt động, cấu trúc file, và best practice để giữ memory clean và không bị bloat.

/compact thủ công trước khi vào task phức tạp

Trái với intuition, đôi khi nên trigger compaction chủ động trước khi bắt đầu một task phức tạp. Lý do: nếu bạn biết task sắp tới sẽ dài và nặng, bạn muốn có nhiều “room” trong context window nhất có thể ngay từ đầu. Thay vì để compaction tự trigger ở giữa chừng task, bạn trigger nó trước khi bắt đầu.

Ghi chú ra ngoài những gì critical

Cho những quyết định cực kỳ quan trọng, đừng phụ thuộc hoàn toàn vào CC nhớ giúp bạn. Viết vào một file note, một ticket, một PR description. CC là assistant, không phải source of truth.

Prompt cache 1h TTL

Phần này độc lập với compaction nhưng liên quan chặt với hiểu biết về 5 lớp prompt.

Tại sao cache tồn tại

Mỗi turn, binary build prompt từ 5 lớp. Nhưng L1-L4 thay đổi rất ít giữa các turn:

L1: System prompt    -> cố định giữa mọi turn
L2: Rules/CLAUDE.md  -> cố định cho cả session
L3: Tool schemas     -> thay đổi khi load deferred tool, không thường xuyên
L4: Memory/context   -> thay đổi khi memory update, không mỗi turn
L5: User message     -> thay đổi MỖI turn

Gửi toàn bộ L1-L4 (có thể 10.000-15.000 token) sang model mỗi turn là lãng phí. Anthropic API hỗ trợ prompt caching: binary đánh dấu breakpoint sau L4, API cache phần đó và reuse cho các turn tiếp theo trong cùng session.

Kích hoạt cache 1h TTL bằng env var trong settings.json:

{
  "env": {
    "ENABLE_PROMPT_CACHING_1H=1": "1"
  }
}

Mặc định không có env var này, TTL cache ngắn hơn (5 phút). Với 1h TTL, cache giữ được lâu hơn giữa các lần bạn pause session và quay lại.

Cơ chế hoạt động: L1-L4 cached, L5 fresh

+----------------------------------+  CACHED (1h TTL)
|  L1: System prompt               |
+----------------------------------+
|  L2: User instructions           |
+----------------------------------+
|  L3: Tools available             |
+----------------------------------+
|  L4: Auto-context                |
+----------------------------------+  FRESH (mỗi turn)
|  L5: User message + new history  |
+----------------------------------+

Turn đầu tiên (cold start): toàn bộ L1-L4 + L5 gửi sang API. API lưu cache breakpoint sau L4.

Turn thứ hai trở đi (warmed): chỉ L5 được gửi mới. L1-L4 được serve từ cache. Cost chỉ tính trên L5 + cache retrieval fee (thường ~10% của cache read), không phải full token count.

Con số thực tế

Với một session điển hình có L1-L4 khoảng 15.000 token và L5 mỗi turn khoảng 2.000-3.000 token:

TurnKhông có cacheCó cache
Turn 1 (cold start)18.000 token input18.000 token (cache write)
Turn 2-N (warmed)18.000 token/turn~2.500 token/turn
Session 20 turn~360.000 token input~65.000 token input

Mức tiết kiệm: 50-90% tuỳ tỷ lệ L4/L5. Session dài với nhiều turn ngắn (bạn hỏi nhanh, CC trả lời nhanh) được lợi nhất vì L5 nhỏ so với L1-L4.

Ngoài cost, còn có latency: cache read nhanh hơn nhiều so với process fresh token. Session warmed phản hồi nhanh hơn rõ rệt so với cold start.

Cache invalidation: khi nào cache bị bust

Cache bị invalidate và phải build lại (= cold start) khi:

1. Load deferred tool: Khi model gọi ToolSearch để fetch schema của một tool mới, L3 thay đổi. Breakpoint cache sau L3 giờ trỏ đến content khác. Cache miss.

2. Memory update: Khi memory file thay đổi (bạn save thông tin mới vào memory giữa turn), L4 thay đổi. Cache miss.

3. Compaction xảy ra: Sau compaction, L5 history thay đổi cấu trúc hoàn toàn (summary thay raw messages). Cache của L5 miss. Nhưng L1-L4 vẫn còn cache nếu không có gì thay đổi ở đó.

4. Session timeout (>1h không hoạt động): Cache TTL hết hạn. Turn đầu tiên sau khi quay lại là cold start.

5. Rules / CLAUDE.md thay đổi: Nếu bạn edit L2 file trong khi session đang chạy (ví dụ cập nhật CLAUDE.md từ terminal khác), binary detect và bust cache L2 trở xuống.

Hiểu cache invalidation quan trọng vì: nếu bạn thấy turn đột nhiên chậm hơn hẳn giữa session, khả năng cao vừa xảy ra một trong các events trên.

Cache không phải lúc nào cũng 1h

Env var ENABLE_PROMPT_CACHING_1H=1 kích hoạt TTL 1h cho cache. Không có env var này, Anthropic API dùng TTL mặc định ngắn hơn.

Trên session ngắn (< 5 phút), sự khác biệt không đáng kể. Trên session làm việc bình thường (pause giữa chừng để đọc tài liệu, uống nước, họp nhanh), 1h TTL là khác biệt giữa “quay lại vẫn warmed” và “quay lại phải cold start lại”.

Interaction giữa compaction và cache

Hai cơ chế này không độc lập hoàn toàn. Cần hiểu interaction:

Compaction xảy ra:
  -> L5 history thay đổi (summary replaces raw messages)
  -> Cache của L5 bust
  -> L1-L4 cache vẫn còn (nếu chưa timeout)

Kết quả: turn ngay sau compaction có thể slower một chút
(L5 cold), nhưng không tệ bằng full cold start (L1-L4 vẫn warm)

Đây là lý do /clear khác với session mới hoàn toàn: /clear reset L5 và bust L5 cache, nhưng L1-L4 vẫn còn warmed nếu bạn /clear trong vòng 1h kể từ turn trước.

Tóm tắt và bài tiếp theo

  • Compaction là cơ chế CC dùng để duy trì session khi conversation history sắp vượt context window. Binary trigger tự động khi đạt ngưỡng ~70-80%, hoặc bạn trigger thủ công với /compact.
  • Compaction hoạt động bằng cách model tóm tắt phần cold history thành summary, replace raw messages. Chi tiết nuance trong history bị mất trong quá trình này. Đây là lý do session dài “quên” những gì làm 30 phút trước.
  • Best practice: dùng /clear chủ động khi đổi task lớn, dùng memory để persist quyết định quan trọng qua compaction, không phụ thuộc vào CC nhớ hộ những thứ critical.
  • Prompt cache 1h TTL (kích hoạt qua ENABLE_PROMPT_CACHING_1H=1) cache L1-L4 vì chúng ít thay đổi giữa turn. Chỉ L5 tốn token mới mỗi turn. Kết quả: session warmed rẻ hơn 50-90% và nhanh hơn đáng kể so với cold start.
  • Cache bị bust khi load deferred tool, update memory, compaction xảy ra, hoặc session timeout hơn 1h.

Bài 6 sẽ đi vào lớp L2 trong detail: cấu trúc CLAUDE.md, hệ thống rules với path-scope, và cách organize instruction file để CC đọc đúng context vào đúng lúc, không nhồi tất cả vào mọi session.


Bài thuộc series Claude Code từ zero. Series plan tại bài giới thiệu.