Bài 1 đã phác thảo 5 lớp cấu thành prompt mỗi turn. Bài này đi sâu hơn: lắp ráp theo thứ tự nào, mỗi lớp đến từ đâu trong filesystem, và tại sao turn đầu tiên của một session bao giờ cũng chậm hơn turn thứ hai.
Để không chỉ nói lý thuyết, bài này kéo thẳng từ session log thực (~/.claude/projects/<hash>/sessions/*.jsonl) để xem binary ghi lại gì cho mỗi turn.
Session log: nguồn dữ liệu thực
Mỗi session CLI là một file .jsonl trong ~/.claude/projects/<workdir-hash>/. Mỗi dòng là một event JSON, ghi toàn bộ vòng đời của session: message user, message assistant, tool call, tool result, hook execution, usage token.
Đây là 4 event đầu của một session thực (đã white-label content, giữ nguyên schema):
{"type":"system","subtype":"bridge_status",
"entrypoint":"cli","cwd":"/path/to/project",
"version":"2.1.117","gitBranch":"main","sessionId":"86164..."}
{"type":"user",
"message":{"role":"user","content":"<user message here>"},
"promptId":"1a8ae41d-...","permissionMode":"default"}
{"type":"attachment","attachment":{
"type":"deferred_tools_delta",
"addedNames":["AskUserQuestion","EnterWorktree","ExitWorktree",
"Monitor","WebFetch","WebSearch",
"mcp__claude-in-chrome__navigate","..."],
"removedNames":[]}}
{"type":"attachment","attachment":{
"type":"skill_listing",
"skillCount":26,
"content":"- update-config: ...\n- nf-memory: ...\n- claude-authoring: ..."}}
Bốn event này xảy ra trước khi binary gọi API lần đầu. Đây chính là quá trình lắp ráp 5 lớp.
Thứ tự lắp ráp: L1 trước, L5 sau
Binary build prompt theo thứ tự từ dưới lên. Trong lần gọi API thực tế, messages array trông như sau:
[system] <- L1 + L2 (hợp nhất thành system prompt)
[user] <- L4 auto-context (dưới dạng <system-reminder> tag)
[user] <- L5: user message thực
Nhưng trước khi gọi API, binary còn phải thu thập L3 (tool schema) và inject vào request body riêng. Toàn bộ quá trình:
+------------------------------------------+
| Bước 1: Nạp L1 (system prompt binary) |
+------------------------------------------+
|
+------------------------------------------+
| Bước 2: Thu thập L2 |
| - ~/.claude/CLAUDE.md (global) |
| - <project>/CLAUDE.md (project) |
| - ~/.claude/rules/*.md |
| (lọc theo paths: frontmatter) |
+------------------------------------------+
|
+------------------------------------------+
| Bước 3: Liệt kê L3 (tool schema) |
| - Core tools (Bash, Read, Edit, ...) |
| - MCP tools (mcp__<server>__<tool>) |
| - Deferred tools: chỉ list tên, |
| schema load on demand qua ToolSearch |
+------------------------------------------+
|
+------------------------------------------+
| Bước 4: Dựng L4 (auto-context) |
| - Memory (MEMORY.md hoặc |
| autoMemoryDirectory) |
| - skill_listing (danh sách skill) |
| - MCP instructions delta |
| - Ngày/giờ, shell, workdir, git branch|
| Wrap vào tag <system-reminder> |
+------------------------------------------+
|
+------------------------------------------+
| Bước 5: Gắn L5 (user message) |
| + toàn bộ conversation history |
+------------------------------------------+
|
v
Gọi Anthropic API
Tổng số token trong request = L1 + L2 + L3 + L4 + L5. Phần lớn token nằm ở L1-L4, phần ít nhất là L5 (chính là message bạn gõ).
Ví dụ từ log thực: turn 1 vs turn 2
Từ session log thực, đây là usage object của hai lần gọi API liên tiếp:
Turn 1 (lần gọi API đầu tiên của session):
"usage": {
"input_tokens": 6,
"cache_creation_input_tokens": 34188,
"cache_read_input_tokens": 0,
"output_tokens": 225,
"cache_creation": {
"ephemeral_1h_input_tokens": 34188,
"ephemeral_5m_input_tokens": 0
}
}
Turn 2 (sau khi cache đã warm, chỉ vài giây sau):
"usage": {
"input_tokens": 1,
"cache_creation_input_tokens": 2558,
"cache_read_input_tokens": 35895,
"output_tokens": 1804,
"cache_creation": {
"ephemeral_1h_input_tokens": 1278,
"ephemeral_5m_input_tokens": 1280
}
}
Đọc các con số này:
input_tokens: token phải trả phí full price (không cache). Turn 1 là 6, turn 2 là 1. Gần như bằng không.cache_creation_input_tokens: token được ghi vào cache lần đầu. Turn 1 tạo 34,188 token cache (toàn bộ L1-L4). Turn 2 chỉ tạo thêm 2,558 (L5 mới từ tool results).cache_read_input_tokens: token đọc từ cache với giá rẻ hơn nhiều. Turn 2 đọc 35,895 token từ cache. Đây là toàn bộ L1-L4 không đổi.
Kết luận thực tế: từ turn 2 trở đi, chi phí thực sự của một message chỉ là token bạn gõ (L5), không phải 35,000+ token của toàn bộ system context. Cache là lý do session dài không tốn kém như bạn nghĩ.
L1: System prompt của binary
L1 là instruction gốc của Claude Code: “bạn là CLI agent của Anthropic, đây là cách bạn hành xử, đây là các ràng buộc safety…”. Nội dung này compiled vào binary, không expose qua API hay file nào.
Bạn không đọc được L1 trực tiếp. Nhưng behavior nó tạo ra thì quan sát được: tại sao CC luôn hỏi confirm trước khi chạy command có side-effect, tại sao nó không tự push git, tại sao nó có style trả lời nhất định.
Khi nâng cấp CC (ví dụ v2.1.117 lên v2.1.143), L1 có thể thay đổi. Một số behavior thay đổi sau upgrade không phải do CLAUDE.md của bạn mà do L1 mới. Check release notes sau upgrade.
L2: User instructions
Đây là lớp bạn kiểm soát. Binary thu thập theo thứ tự:
~/.claude/CLAUDE.md(global, mọi session)<project>/.claude/rules/*.md(nếu cópaths:frontmatter, lọc theo file đang touch)<project>/CLAUDE.mdhoặcCLAUDE.mdở workdir (project-scoped)
Tất cả được nối lại và inject vào system prompt sau L1, dưới dạng:
Codebase and user instructions are shown below. Be sure to adhere
to these instructions. IMPORTANT: These instructions OVERRIDE any
default behavior...
<user-instructions>
[nội dung CLAUDE.md và rules của bạn]
</user-instructions>
Cơ chế paths: trong rules là một tối ưu token quan trọng. Rule rules/frontend/react.md với frontmatter paths: "**/*.tsx" chỉ vào prompt khi bạn đang edit file .tsx. Khi đang sửa Python, rule đó không có mặt, không tốn token.
L3: Tool list và deferred tools
Binary gửi tool schema vào request body dưới key tools. Mỗi tool là một object JSON với name, description, và input_schema.
Nhưng một số tool không có sẵn ngay. Từ log thực, event deferred_tools_delta liệt kê các tool chỉ có tên, không có schema:
"addedNames": [
"AskUserQuestion", "EnterWorktree", "ExitWorktree",
"Monitor", "NotebookEdit", "WebFetch", "WebSearch",
"mcp__claude-in-chrome__navigate",
"mcp__claude-in-chrome__read_page",
"..."
]
Model thấy tên này trong system-reminder. Khi cần dùng, model phải gọi ToolSearch trước để fetch schema, rồi mới gọi tool đó. Đây là pattern “lazy load” để không nhồi schema của 40+ tool vào mỗi turn khi phần lớn không được dùng.
MCP tools cũng load tương tự. Event mcp_instructions_delta inject instruction của từng MCP server vào system-reminder khi server đó active.
L4: Auto-context và system-reminder
L4 là lớp bạn ít thấy nhất vì nó không đến từ file bạn tự tạo (hoặc không hoàn toàn). Tag <system-reminder> trong conversation là nơi L4 hiện diện.
Những gì binary nhét vào L4:
Memory: Nếu project có autoMemoryDirectory trong settings.local.json, content của folder đó được nạp vào. Đây là cơ chế khiến CC “nhớ” qua session. Không có memory file thì binary cố tìm ~/.claude/projects/<hash>/MEMORY.md.
Skill listing: Danh sách skill available, kèm description ngắn để model biết khi nào trigger. Từ log thực, skill_listing với 26 skills inject vào mỗi session.
MCP instructions: Mỗi MCP server có thể kèm instruction text. Ví dụ claude-in-chrome inject hướng dẫn phải dùng ToolSearch trước khi gọi bất kỳ mcp__claude-in-chrome__* tool nào.
Platform context: currentDate, userEmail, shell, workdir, git branch. Binary biết ngày hôm nay là 2026-05-17, biết shell là zsh, biết bạn đang ở nhánh main.
TodoWrite reminder: Đôi khi binary nhắc model dùng TodoWrite để track task phức tạp.
Toàn bộ L4 được wrap:
<system-reminder>
# Available skills
- skill-name: description...
# Deferred tools
The following deferred tools are available via ToolSearch...
# currentDate
Today's date is 2026-05-17.
# userEmail
...
</system-reminder>
L5: User message và conversation history
L5 là phần bạn nhìn thấy trong REPL. Mỗi turn, binary nối thêm message mới vào messages array. Cả assistant message trước đó (kèm tool calls và tool results) cũng nằm ở đây.
Conversation history tăng dần theo turn. Đây là lý do session dài dần chiếm nhiều context window hơn. Khi gần đến giới hạn, binary chạy compaction: tóm tắt history cũ thành một summary, drop các message gốc. Bài 5 sẽ mổ xẻ cơ chế này.
Một điều ít ai biết: tool result cũng nằm ở L5. Khi model gọi Read và đọc một file 500 dòng, content đó được inject vào messages dưới dạng tool_result. Nó không biến mất sau turn đó, nó ở lại trong history cho đến khi compaction xóa đi. Đây là lý do đọc nhiều file lớn liên tục sẽ nhanh chóng phình context window.
Prompt cache và chiến lược tiết kiệm token
Từ số liệu turn 2 ở trên: 35,895 token đọc từ cache thay vì 35,895 token input full price.
Cache hoạt động tốt nhất khi L1-L4 ổn định. Mỗi khi bạn thay đổi CLAUDE.md, thêm rule, hoặc memory file thay đổi nội dung, cache bị invalidate và turn tiếp theo phải tạo lại cache (tốn thêm một lần).
ENABLE_PROMPT_CACHING_1H=1 trong settings.json đặt TTL cache là 1 giờ. Mặc định là 5 phút (ephemeral_5m). Với session làm việc thực (30-60 phút), 1 giờ TTL tiết kiệm đáng kể.
| Tình huống | Cache behavior |
|---|---|
| Turn 1 của session mới | Cache miss. Tạo cache L1-L4. Chậm hơn. |
| Turn 2-N (L4 không đổi) | Cache hit L1-L4. Chỉ trả input tokens cho L5. |
| Sau khi sửa CLAUDE.md | Cache invalidate L2. Turn tiếp tạo lại. |
| Sau khi memory cập nhật | Cache invalidate L4. Turn tiếp tạo lại. |
| Session idle > 1h (1h TTL) | Cache hết hạn. Turn tiếp tạo lại. |
Implication thực tế: không nên thoát CLI và mở lại session giữa chừng chỉ vì “muốn fresh context”. Mỗi lần mở session mới là một lần cache miss. Dùng /clear để xóa L5 (conversation history) mà giữ cache L1-L4 warm.
Tóm tắt và bài tiếp theo
- Binary lắp ráp prompt theo 5 lớp: L1 (binary, cố định), L2 (CLAUDE.md + rules), L3 (tool schema), L4 (memory, skills, platform context), L5 (user message + history).
- Session log tại
~/.claude/projects/<hash>/*.jsonlghi lại toàn bộ event bao gồm token usage.cache_creation= token lần đầu,cache_read= token đọc từ cache với giá rẻ hơn. - Turn đầu của session phải tạo cache (~34,000 token với setup đầy đủ). Từ turn 2 trở đi phần lớn được đọc từ cache.
- L4 là nơi skills, memory, và MCP instructions vào. L5 là nơi tool results tích lũy theo turn. Cả hai cần được quản lý chủ động để tránh phình context.
Bài 3 đi vào tool use loop: cụ thể vòng lặp model gọi tool, tool chạy, kết quả trả về, lặp lại diễn ra thế nào. Đặc biệt là pattern parallel tool calls và tại sao subagent có thể làm nhiều việc song song mà main session không cần chờ.
Bài thuộc series Claude Code từ zero. Series plan tại bài giới thiệu.