Context Engineering & Agent Memory — Packing the Window Without Losing the Thread
How senior engineers pack system prompts, tools, history, RAG, and output reserve into a fixed context window — and manage memory when the budget breaks.
Prompt engineering nói model làm gì. Sampling quyết định trả lời sáng tạo đến mức nào. Stopping criteria giới hạn chạy bao lâu. Context engineering là kỹ năng quyết định cái gì thực sự vừa window — và theo thứ tự nào — trước khi các knob kia có ý nghĩa.
Nếu Phần 1 dạy token là ô RAM, bài này dạy agent cấp phát, evict, và retrieve các ô đó dưới áp lực production. Mọi agent multi-turn, mọi pipeline RAG, mọi vòng tool-calling cuối cùng đều là bài toán pack context.
Mô hình tư duy:Bạn là stage manager với một sân khấu nhỏ. Chỉ đạo cụ cảnh này được lên sân khấu; phần còn lại chờ trong cánh gà và chỉ mang ra khi kịch bản cần. Context engineering là quyết định cái gì trên sân khấu mỗi lượt — không phải dồn cả kho vào tầm mắt.
Mở demo đầy đủ:
Context window là working memory, không phải storage
LLM không có persistent memory giữa các API call. Mỗi request gửi chuỗi token mới; model chỉ attend đúng những gì bạn cung cấp. Vì vậy context window là working memory của agent — volatile, giới hạn kích thước, và chia sẻ bởi mọi concern cạnh tranh.
| Memory type | Where it lives | Lifetime | Agent pattern |
|---|---|---|---|
| Short-term / working | Context window (this request) | One inference call | Full conversation + tool results in prompt |
| Long-term | External store (DB, vector index, file) | Persistent | Retrieve on demand, inject top-k into window |
| Episodic | Session store + summarization | Session or user-scoped | Rolling summary + recent turns |
| Semantic | Embeddings over documents | Persistent | RAG retrieval at query time |
Quy tắc thiết kế: Coi window như cache, không phải database. Mọi thứ phải sống sót truncation thuộc external storage với đường retrieval.
Về token math và window sizing, xem Tokens & Context Windows — ở đây tập trung nhét gì vào và bỏ gì.
Cái gì tiêu budget: packing list
Trước khi optimize, kiểm kê mọi segment vào prompt. Prompt agent hiếm khi chỉ là “user message”.
| Segment | Typical size | Stability | Notes |
|---|---|---|---|
| System prompt | 200–2,000 tokens | High | Persona, policies, output format, safety rules |
| Tool definitions | 500–8,000+ tokens | High | JSON Schema / OpenAI function specs scale with tool count |
| Conversation history | Unbounded | Low | Grows every turn; largest overflow source |
| Retrieved documents (RAG) | 200–2,000 per chunk | Per-query | Top-k chunks × chunk size |
| Tool results / scratchpad | Variable | Per-step | Raw JSON, logs, intermediate reasoning |
| Output reserve | 256–8,192 tokens | Fixed policy | Space reserved for generation — not optional |
┌────────────────────────────────────────────────────────────── context window ──┐
│ SYSTEM │ TOOLS │ turn₁ │ turn₂ │ … │ RAG₁ │ RAG₂ │ … │ [OUTPUT RESERVE] │
└────────────────────────────────────────────────────────────────────────────────┘
▲ stable prefix (cacheable) ▲ volatile middle ▲ reserved
Reserve output budget trước
Bug production phổ biến: pack input 100% window, rồi model truncate giữa generation hoặc chạm max_tokens với tool call dở. Luôn trừ output reserve trước khi size input.
const CONTEXT = 32_768;
const OUTPUT_RESERVE = 2_048;
const INPUT_BUDGET = CONTEXT - OUTPUT_RESERVE; // 30,720 tokens for everything else
function fits(inputTokens) {
return inputTokens <= INPUT_BUDGET;
}
Nếu agent emit JSON tool argument dài hoặc chain-of-thought, reserve phải phản ánh output worst-case — không phải trung bình.
Tool definitions là overhead âm thầm
Mười tool document kỹ có thể tốn nhiều token hơn cả conversation.
- Tool routing — classify intent trước, inject chỉ 2–3 schema liên quan.
- Schema compression — bỏ mô tả model đã biết; dùng pattern
$ref. - Dynamic tool lists — đổi tool set theo phase workflow.
Quản lý history: khi conversation vượt window
Agent multi-turn tích lũy token tuyến tính (hoặc tệ hơn nếu tool results dài). Khi sum(segments) > budget, cần eviction policy rõ ràng — không hy vọng API âm thầm truncate đúng đầu.
1. Truncation (bỏ cũ nhất)
Chiến lược đơn giản nhất: giữ system + tools + N turn cuối. Nhanh, deterministic, nhưng mất context sớm — preference user nói ở turn 1 biến mất.
function truncateOldest(turns, maxTurns) {
return turns.slice(-maxTurns);
}
Dùng khi: task ngắn, Q&A stateless, hoặc fact quan trọng được re-retrieve từ long-term memory.
2. Rolling window với token cap
Thay vì đếm turn, cap theo token — bỏ message cũ nhất đến khi dưới budget.
function rollingWindow(turns, tokenBudget, countTokens) {
const kept = [];
let used = 0;
for (let i = turns.length - 1; i >= 0; i--) {
const t = countTokens(turns[i]);
if (used + t > tokenBudget) break;
kept.unshift(turns[i]);
used += t;
}
return kept;
}
Ưu tiên token cap hơn turn cap — một turn với tool result 4K có thể phá policy “10 turn cuối”.
3. Summarization / compaction
Thay turn cũ bằng summary nén. Pattern thường gặp: mỗi K turn hoặc khi history vượt 60% input budget, gọi model rẻ/nhanh để summarize.
[SYSTEM][TOOLS][SUMMARY of turns 1–18][turn 19][turn 20][RAG][user msg]
Trade-off:
- Ưu — giữ thread ngữ nghĩa; nhỏ hơn history thô.
- Nhược — latency + cost summary; rủi ro mất chi tiết (ID, quote chính xác, correction user).
Tip production: Lưu transcript thô bên ngoài; summary là cache lossy có thể regenerate.
4. Hierarchical memory
Cấu trúc memory theo lớp:
- Working — 2–4 turn cuối nguyên văn trong window.
- Session summary — đoạn compact cập nhật mỗi chu kỳ compaction.
- Long-term facts — vector store hoặc key-value (pref user, project ID) retrieve mỗi turn.
{
"working": ["turn_19", "turn_20"],
"session_summary": "User is debugging CORS on api.example.com. Prefers curl examples.",
"long_term": ["user_id: 42", "preferred_language: vi"]
}
Giống cách engineer giữ sticky note (summary), file đang mở (working), và wiki (long-term).
Retrieval injection: RAG không lặp deep dive
RAG thêm chunk retrieve vào prompt lúc query.
| Decision | Question |
|---|---|
| How many chunks (k)? | More recall vs. less room for history |
| Chunk size | Larger context per doc vs. more docs |
| Ordering | Where in the window — start, middle, or end? |
| Deduplication | Overlapping chunks waste tokens |
| Re-ranking threshold | Drop low-score chunks before injection |
Về embedding, indexing, chunking, xem Vector Embeddings Deep Dive và RAG Guide — loạt bài này giả định bạn biết cách retrieve và tập trung cách pack phần đã retrieve.
function injectRag(promptParts, chunks, maxRagTokens, countTokens) {
const ranked = [...chunks].sort((a, b) => b.score - a.score);
const selected = [];
let used = 0;
for (const c of ranked) {
const t = countTokens(c.text);
if (used + t > maxRagTokens) continue;
if (c.score < 0.5) continue; // drop low-confidence
selected.push(c);
used += t;
}
return { ...promptParts, rag: selected };
}
Token-aware retrieval — truyền maxRagTokens vào pipeline retriever để lấy ít chunk chất lượng cao thay vì retrieve 20 rồi truncate 15.
Lost in the middle: suy giảm attention theo vị trí
Nghiên cứu và kinh nghiệm production cho thấy LLM thường dùng kém thông tin ở giữa context dài. Attention không đều theo vị trí — đầu (system, instruction ban đầu) và cuối (user message mới nhất, tool output gần) được trọng số cao hơn.
Attention strength (illustrative)
▲
│ ████ ████
│ ████ ████
│ ████ ░░░░ ████
└──────────────────────────────────────────► position
system/tools RAG/old turns latest user
Chiến lược sắp xếp thực tế:
- System + policy quan trọng — luôn ở đầu.
- Tool definitions — ngay sau system (stable prefix).
- Retrieved docs — ưu tiên sau history gần hoặc interleave chunk score cao nhất cuối trước user message.
- User message mới nhất — luôn ở cuối.
- Nhắc lại task — một dòng sau RAG: “Using the documents above, answer: …”.
Anti-pattern: Nhét 8 chunk RAG giữa system prompt và câu hỏi user, rồi thắc mắc vì sao model bỏ qua chunk 4.
Context rot: khi thêm history làm câu trả lời tệ hơn
Context rot là hiện tượng chất lượng câu trả lời suy giảm khi lấp window — kể cả trước hard truncation.
- Distraction — turn cũ không liên quan cạnh tranh với task hiện tại.
- Contradiction — instruction cũ mâu thuẫn correction sau.
- Middle-position decay — fact chôn giữa context bị bỏ qua.
- Tool noise — log stderr dài từ turn 3 vẫn còn ở turn 30.
Cách giảm thiểu:
| Symptom | Fix |
|---|---|
| Model repeats old wrong answers | Compact or drop pre-correction turns |
| Ignores retrieved docs | Move top chunk closer to user message |
| Forgets tool result | Re-inject critical tool output in latest turn |
| Slow + expensive | Aggressive compaction; smaller k for RAG |
Đo rot bằng needle-in-haystack eval — giấu fact ở các độ sâu khác nhau và test recall rate.
Prompt caching và stable prefix
Provider như Anthropic và OpenAI có prompt caching — prefix giống hệt lặp lại bỏ qua re-computation, giảm latency và cost. Cache hit cần stable prefix byte-identical.
Thiết kế thứ tự pack để cache được:
[CACHEABLE — never change mid-session]
system prompt
tool definitions (same set per session)
static few-shot examples
[VOLATILE — changes every turn]
session summary
RAG chunks
conversation tail
user message
Đừng inject timestamp, random ID, hoặc metadata per-turn vào system block — nó bust cache cho cả prefix.
| Segment | Cache-friendly? |
|---|---|
| Static system prompt | Yes |
| Tool schemas (fixed set) | Yes |
| Session summary (updated often) | No — keep out of cached block |
| RAG chunks | No |
| Latest user turn | No |
Trade-off cost và latency
Mỗi token có giá và cost thời gian. Context engineering là nơi performance engineering gặp ML.
| Strategy | Latency impact | Cost impact | Quality impact |
|---|---|---|---|
| Full history, no compaction | High (long prefill) | High input tokens | Best short-term recall |
| Rolling truncation | Low | Medium | Loses early context |
| Summarization | +1 LLM call per cycle | Summary tokens + call | Good balance |
| Smaller k RAG | Low | Low | Risk missing relevant docs |
| Prompt caching | Lower prefill on hit | 50–90% off cached prefix | Neutral |
| Smaller model for summary | Low | Much lower | Summary may lose detail |
Agent turn cost ≈ input_tokens × input_price
+ output_tokens × output_price
+ retrieval_latency
+ (optional) summarization_call
Với agent window 32K ở $3/M input token, bỏ 8K token history thừa tiết kiệm $0.024 mỗi call — nhỏ một lần, đáng kể ở 100K call/ngày.
Thuật toán pack production
Tổng hợp — flow tham chiếu có thể adapt:
async function buildAgentContext(session, userMessage, config) {
const { contextSize, outputReserve, maxRagTokens, maxHistoryTokens } = config;
const inputBudget = contextSize - outputReserve;
const system = session.systemPrompt;
const tools = selectToolsForIntent(userMessage, session.toolCatalog);
let history = session.turns;
// 1. Retrieve long-term + RAG
const facts = await session.longTermStore.getRelevant(session.userId, userMessage);
const ragChunks = await session.retriever.search(userMessage, { limit: 20 });
// 2. Compact history if needed
const fixedCost = countTokens(system) + countTokens(tools) + countTokens(facts);
const ragSelected = packRag(ragChunks, maxRagTokens);
const historyBudget = inputBudget - fixedCost - countTokens(ragSelected);
if (countTokens(history) > historyBudget) {
if (historyBudget > 800) {
history = await compactHistory(history, historyBudget);
} else {
history = rollingWindow(history, historyBudget, countTokens);
}
}
// 3. Order for attention + caching
return assemblePrompt({
system,
tools,
facts,
history,
rag: orderRagForAttention(ragSelected),
userMessage, // always last
});
}
Checklist trước khi ship:
- Output reserve subtracted from input budget
- Token counting uses the production tokenizer, not chars ÷ 4
- Eviction policy is explicit (truncate / compact / hierarchical)
- RAG k and chunk size tuned to remaining budget
- Critical facts not only in middle-position RAG
- Stable prefix designed for prompt caching
- Raw transcript persisted externally for audit and re-summarization
Điểm chính
- Context engineering là kỹ năng agent cốt lõi — quyết định model thấy, nhớ, và hành động trên gì.
- Working memory ≠ long-term memory — window là cache; persist bên ngoài và retrieve có chọn lọc.
- Reserve output token trước — input và output dùng chung một budget.
- History cần eviction policy — truncation, rolling window, summarization, hoặc hierarchy; không overflow âm thầm.
- Vị trí quan trọng — lost-in-the-middle nghĩa là ordering quan trọng ngang retrieval quality.
- Context rot có thật — nhiều token có thể nghĩa là câu trả lời tệ hơn; đo và compact.
- Thiết kế cho caching — prefix system + tools ổn định tiết kiệm latency và tiền ở scale.
Lỗi thường gặp
- Pack input đầy 100% windowkhông chừa output reserve, nên model truncate giữa câu trả lời hoặc giữa tool call.
- Đếm token bằng
ký tự ÷ 4dùng tokenizer production; ước lượng lệch nặng với code và tiếng không phải Anh. - Hy vọng API “truncate đúng đầu”eviction phải là policy rõ ràng, không phải overflow âm thầm.
- Nhét hết chunk RAG vào giữalost-in-the-middle nghĩa là model bỏ qua chúng.
- Coi “nhiều context” là “câu trả lời tốt hơn”context rot làm chất lượng giảm khi window đầy.
- Chèn timestamp/random ID vào system blockphá prompt caching cho cả prefix.
Khi nào nên dùng
| Agent shape | How much context engineering |
|---|---|
| Single-turn Q&A, short prompt | Minimal — reserve output, count tokens properly |
| Multi-turn chat | + explicit eviction policy (rolling window or summary) |
| RAG / tool-calling agent | + token-aware retrieval, attention ordering, dedup |
| Long-horizon agent at scale | + hierarchical memory + prompt caching + needle-in-haystack evals |
Khi nào giữ đơn giảnnếu cả hội thoại luôn vừa một phần nhỏ window, đừng dựng pipeline summarization không cần. Ngay khi history phình vô hạn hoặc tool result dài dòng, bạn cần eviction policy.
Bắt đầu nhanh
- Trừ output reserve trước
inputBudget = contextSize - outputReservetrước khi pack bất cứ gì. - Kiểm kê packing listsystem, tool, history, RAG, tool result; đo từng cái bằng tokenizer thật.
- Chọn một eviction policybắt đầu với rolling window cap theo token; thêm summarization khi mất context sớm quan trọng.
- Sắp xếp theo attentionsystem + tool trước (cache được), user message mới nhất cuối, chunk RAG tốt nhất gần cuối.
- Lưu transcript thô bên ngoàisummary trong window là cache lossy luôn regenerate được.
Dùng demo trên để stress-test quyết định pack trước khi lên production traffic. Tiếp theo trong loạt bài: khi context engineering chưa đủ — Fine-tuning vs Prompting vs RAG.
Loạt bài Building AI Agents
- Tokens & Context Windows
- Sampling: temperature, top_p, top_k
- Prompt Engineering for Agents
- Stopping Criteria & Output Control
- Context Engineering & Memory
- Fine-tuning vs Prompting vs RAG
- Evaluating LLMs & Agents
- Choosing a Model
- Function Calling & Tool Use
- Agent Patterns: ReAct, Reflection, Planning