jvinhit//lab

Search posts

Type to search across journal entries.

navigate open esc close

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 typeWhere it livesLifetimeAgent pattern
Short-term / workingContext window (this request)One inference callFull conversation + tool results in prompt
Long-termExternal store (DB, vector index, file)PersistentRetrieve on demand, inject top-k into window
EpisodicSession store + summarizationSession or user-scopedRolling summary + recent turns
SemanticEmbeddings over documentsPersistentRAG 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àobỏ 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”.

SegmentTypical sizeStabilityNotes
System prompt200–2,000 tokensHighPersona, policies, output format, safety rules
Tool definitions500–8,000+ tokensHighJSON Schema / OpenAI function specs scale with tool count
Conversation historyUnboundedLowGrows every turn; largest overflow source
Retrieved documents (RAG)200–2,000 per chunkPer-queryTop-k chunks × chunk size
Tool results / scratchpadVariablePer-stepRaw JSON, logs, intermediate reasoning
Output reserve256–8,192 tokensFixed policySpace 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:

  1. Working — 2–4 turn cuối nguyên văn trong window.
  2. Session summary — đoạn compact cập nhật mỗi chu kỳ compaction.
  3. 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.

DecisionQuestion
How many chunks (k)?More recall vs. less room for history
Chunk sizeLarger context per doc vs. more docs
OrderingWhere in the window — start, middle, or end?
DeduplicationOverlapping chunks waste tokens
Re-ranking thresholdDrop low-score chunks before injection

Về embedding, indexing, chunking, xem Vector Embeddings Deep DiveRAG 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ế:

  1. System + policy quan trọng — luôn ở đầu.
  2. Tool definitions — ngay sau system (stable prefix).
  3. Retrieved docs — ưu tiên sau history gần hoặc interleave chunk score cao nhất cuối trước user message.
  4. User message mới nhất — luôn ở cuối.
  5. 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:

SymptomFix
Model repeats old wrong answersCompact or drop pre-correction turns
Ignores retrieved docsMove top chunk closer to user message
Forgets tool resultRe-inject critical tool output in latest turn
Slow + expensiveAggressive 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.

SegmentCache-friendly?
Static system promptYes
Tool schemas (fixed set)Yes
Session summary (updated often)No — keep out of cached block
RAG chunksNo
Latest user turnNo

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.

StrategyLatency impactCost impactQuality impact
Full history, no compactionHigh (long prefill)High input tokensBest short-term recall
Rolling truncationLowMediumLoses early context
Summarization+1 LLM call per cycleSummary tokens + callGood balance
Smaller k RAGLowLowRisk missing relevant docs
Prompt cachingLower prefill on hit50–90% off cached prefixNeutral
Smaller model for summaryLowMuch lowerSummary 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

  1. 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ì.
  2. Working memory ≠ long-term memory — window là cache; persist bên ngoài và retrieve có chọn lọc.
  3. Reserve output token trước — input và output dùng chung một budget.
  4. History cần eviction policy — truncation, rolling window, summarization, hoặc hierarchy; không overflow âm thầm.
  5. Vị trí quan trọng — lost-in-the-middle nghĩa là ordering quan trọng ngang retrieval quality.
  6. Context rot có thật — nhiều token có thể nghĩa là câu trả lời tệ hơn; đo và compact.
  7. 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 shapeHow much context engineering
Single-turn Q&A, short promptMinimal — 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

  1. Trừ output reserve trướcinputBudget = contextSize - outputReserve trước khi pack bất cứ gì.
  2. Kiểm kê packing listsystem, tool, history, RAG, tool result; đo từng cái bằng tokenizer thật.
  3. 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.
  4. 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.
  5. 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

  1. Tokens & Context Windows
  2. Sampling: temperature, top_p, top_k
  3. Prompt Engineering for Agents
  4. Stopping Criteria & Output Control
  5. Context Engineering & Memory
  6. Fine-tuning vs Prompting vs RAG
  7. Evaluating LLMs & Agents
  8. Choosing a Model
  9. Function Calling & Tool Use
  10. Agent Patterns: ReAct, Reflection, Planning