Context Engineering: Agent'ın Context'ini Yönetmek, Compaction ve Memory
Context bir bütçedir ve dolduğunda hata vermeden kesilebilir: 4.096 token'la yüklenmiş qwen3:4b, uzayan bir oturumda baştaki bütçe cümlesini kaybedip rakam uyduruyor. MAF CompactionProvider ile eski tool çıktılarını sıkıştırmak, AIContextProvider ile oturumlar arası tercih memory'si ve iş devredilen sub-agent'tan yalnızca özet almak, gerçek token sayılarıyla.
Sürüm notu: Bu yazıdaki kodlar .NET 10, Microsoft.Extensions.AI 10.11.0, OllamaSharp 5.5.2 ve Microsoft.Agents.AI 1.28.0 ile derlenip yerel Ollama 0.34.4 üzerindeki qwen3:4b modeliyle denenmiştir.
Önce Tahmin Et
Müşteri konuşmanın başında "My total budget is 55 dollars." diyor. Ardından agent katalogdan sayfa sayfa veri çekiyor; her sayfa sekiz kitabın JSON (JavaScript Object Notation) karşılığı, yaklaşık 860 karakter. On dört sayfa sonra müşteri soruyor: "What is my total budget?"
Bütçe history'nin en başında, açık ve tek bir cümle. Model 4 milyar parametreli ve kağıt üzerinde 262 bin token'lık bir context'i destekliyor. Cevap 55 mi olur? Yirmi sayfa sonra?
Context Bir Bütçedir
Anthropic'in AI (Artificial Intelligence) agent'ları için yazdığı 29 Eylül 2025 tarihli "Effective context engineering for AI agents" yazısı prompt engineering ile context engineering arasındaki farkı şöyle koyuyor: prompt engineering talimatı yazmak ve düzenlemek; context engineering ise inference sırasında modelin önündeki bütün token'ları (talimat, tool tanımları, dışarıdan gelen veri, mesaj geçmişi) her turda yeniden seçmek. Agent'larda ikincisi belirleyici, çünkü context her tur büyüyor (ikinci yazıdaki token sütununu hatırla).
Aynı yazı iki kavramı öne çıkarıyor. Context rot: context doldukça modelin içindeki bilgiyi doğru hatırlama yeteneği düşer. Attention budget: her yeni token modelin dikkatinden bir pay alır, yani context sınırlı ve getirisi azalan bir kaynak. Pratik sonuç basit: context'e koyduğun her şeyin bir bedeli var ve bir noktadan sonra fazladan bilgi kaliteyi artırmıyor, düşürüyor.
Context Dolunca Ne Olur?
Tahmin sorusunun deneyi şu kod. Bütçeyi bir kez söylüyor, araya katalog sayfaları koyuyor ve sonunda bütçeyi soruyor:
// Uzun bir oturum: bütçe bir kez söyleniyor, sonra tool çıktısı birikiyor
string catalogPage = JsonSerializer.Serialize(data.Books);
List<ChatMessage> history =
[
new(ChatRole.System, "You are the Bookstore shopping assistant."),
new(ChatRole.User, "My total budget is 55 dollars. Keep it in mind."),
new(ChatRole.Assistant, "Noted: your total budget is 55 dollars."),
];
int pages = args.Length > 1 ? int.Parse(args[1]) : 14;
for (int i = 1; i <= pages; i++)
{
history.Add(new(ChatRole.User, $"Catalog page {i}: {catalogPage}"));
history.Add(new(ChatRole.Assistant, $"Received catalog page {i}."));
}
history.Add(new(ChatRole.User, "What is my total budget? Answer with the number only."));
int chars = history.Sum(m => m.Text.Length);
ChatResponse r = await meter.GetResponseAsync(history, new ChatOptions { Temperature = 0, Seed = 42 });
Console.WriteLine($"messages={history.Count}, characters={chars}, input tokens reported={r.Usage?.InputTokenCount}");
Console.WriteLine($"answer: {r.Text.Trim()}");
Dört farklı sayfa sayısıyla gerçek koşular (her koşunun sayfa sayısını satır başına ben ekledim):
pages=3: messages=10, characters=2739, input tokens reported=929
answer: 55
pages=8: messages=20, characters=7004, input tokens reported=2359
answer: 55
pages=14: messages=32, characters=12132, input tokens reported=4085
answer: 100
pages=20: messages=44, characters=17262, input tokens reported=4072
answer: 0
Üç ve sekiz sayfada cevap doğru. On dört sayfada model "100" dedi, yirmi sayfada "0". Token sütunu nedenini gösteriyor: karakter sayısı 12 binden 17 bine çıktığı halde Ollama'nın raporladığı input token sayısı 4.085'ten 4.072'ye düştü. Model'e giden context bir tavana çarpmış. Ollama'nın /api/ps cevabı bu makinede qwen3:4b'nin context_length: 4096 ile yüklendiğini gösteriyor; model 262.144 token'ı destekliyor, ama çalışan sunucu 4.096'yı kullanıyor. Bu sınırı aşan istek hata vermedi; history'nin başı sessizce düştü ve onunla birlikte bütçe cümlesi de gitti. Model de bilmediği bir rakamı uydurdu.
Bu deneyin iki dersi var. Birincisi, modelin desteklediği context ile senin kullandığın context aynı şey değil; yerel çalıştırmada sunucu ayarı, bulutta sağlayıcının ve modelin sınırı belirleyici, ve aşıldığında ne olduğu (hata mı, sessiz kesme mi) sağlayıcıdan sağlayıcıya değişir. İkincisi, sessiz kesme en kötü hata türü: agent çalışmaya devam ediyor ve yanlış cevabı aynı güvenle veriyor. Context'i ölçmeden ve kontrol etmeden uzun süren bir agent, ilk kuralını er geç unutur.
Neyi Tutmalı, Neyi Atmalı?
Context'te kalması gerekenler: görevin hedefi, kullanıcının koyduğu kısıtlar (bütçe, tercih, yasak), o ana kadarki plan ve kararlar, son birkaç gözlem. Atılabilecekler: çoktan işlenmiş eski tool çıktıları, aynı verinin tekrarları, ara reasoning metinleri. Yukarıdaki deneyde atılan şey tam tersiydi: kısıt gitti, işlenmiş katalog sayfaları kaldı.
Anthropic'in yazısı uzun görevler için üç teknik sayıyor. Compaction: context sınıra yaklaşınca geçmişin özetini çıkarıp yeni bir pencerede yalnızca o özeti taşıyarak devam etmek; en hafif biçimi eski tool sonuçlarını temizlemek. Not alma: agent'ın context dışına (bir todo listesi, bir not dosyası) not yazıp gerektiğinde geri okuması. Sub-agent: odaklı bir işi temiz bir context'le başka bir agent'a verip yalnızca özetini almak. Bir de "just-in-time" yaklaşımı var: veriyi baştan context'e doldurmak yerine agent'a hafif referanslar (dosya yolu, kayıtlı sorgu, id) verip içeriği tool'la gerektiğinde çektirmek. Üçüncü yazıdaki search_books'un beş sonuçla sınırlı olması bunun küçük bir örneği.
Microsoft Agent Framework (MAF) 1.28 bunların bir kısmı için hazır parçalar getiriyor: compaction stratejileri, agent'ın bir todo listesi tutmasını sağlayan tool'ları ekleyen TodoProvider ve context provider'lar. Aşağıda compaction'ı, kalıcı memory'yi ve sub-agent'ı çalıştırıyoruz.
Compaction: MAF'ın CompactionProvider'ı
MAF'ın Microsoft.Agents.AI.Compaction namespace'i birkaç strateji içeriyor. Paketin XML (Extensible Markup Language) dokümantasyonuna göre ToolResultCompactionStrategy eski tool çağrısı gruplarını tek satırlık özet mesajlara çeviriyor ve "en yumuşak" strateji; SummarizationCompactionStrategy eski kısmı bir LLM (Large Language Model) ile özetliyor ve system mesajlarını ve son grupları koruyor; SlidingWindowCompactionStrategy en eski kullanıcı turlarını atıyor; TruncationCompactionStrategy en eski grupları siliyor. Tool çağrısı ile sonucu bir "grup" olarak birlikte tutuluyor ya da birlikte atılıyor; böylece sonucu silinmiş bir çağrı kalmıyor. Biz ilk ikisini bir pipeline'da kullandık:
PipelineCompactionStrategy pipeline = new(
// 1) Eski tool çağrıları tek satırlık notlara dönüşür
new ToolResultCompactionStrategy(CompactionTriggers.MessagesExceed(8)),
// 2) Hâlâ büyükse eski turları model özetler
new SummarizationCompactionStrategy(meter, CompactionTriggers.TokensExceed(1500)));
agent = meter.AsBuilder()
.UseAIContextProviders(new CompactionProvider(pipeline))
.BuildAIAgent(options);
Aynı dört turluk alışveriş konuşmasını compaction'lı ve compaction'sız iki agent'la koşturduk. Bütçe yine yalnızca ilk turda söyleniyor ve son tur ona uymayı gerektiriyor:
string[] turns =
[
"My total budget is 55 dollars. Find novels by Mira Holt.",
"Check the stock of both.",
"Now search software books.",
"Add The Quiet Orchard and one software book to my cart, staying within my budget.",
];
| Tur | Compaction yok: input token | Kayıtlı mesaj | Compaction var: input token | Kayıtlı mesaj |
|---|---|---|---|---|
| 1. Mira Holt romanları | 1935 | 4 | 1936 | 3 |
| 2. İkisinin stoku | 1652 | 8 | 1701 | 6 |
| 3. Yazılım kitapları | 3841 | 12 | 3220 | 9 |
| 4. Bütçe içinde sepete ekle | 8549 | 18 | 7402 | 14 |
İki koşu da doğru bitti: The Quiet Orchard (16,50) ve Coffee and Compilers (35,00), toplam 51,50 dolar, bütçenin altında. Compaction'lı agent'ın session'ında 18 yerine 14 mesaj kaldı ve dört turun input token toplamı 15.977'den 14.259'a indi, yaklaşık %11. Bu kısa bir konuşma; compaction'ın değeri onlarca turluk oturumlarda ortaya çıkar, burada önemli olan bütçe kısıtının compaction'dan sağ çıkması. Bir uyarı: Ollama'nın raporladığı input token sayısı ikinci turda, history büyüdüğü halde, birinciden düşük çıktı. Bu sayının neyi kapsadığını (örneğin daha önce işlenmiş bir prefix'in sayılıp sayılmadığını) bu yazıda doğrulamadık; tur tur karşılaştırma bu yüzden gürültülü. Kesin karşılaştırma için gönderdiğin içeriği kendin say.
Kalıcı Memory: Kullanıcı Tercihleri
Compaction tek bir oturumun context'ini yönetiyor. Kullanıcının pazartesi söylediği bir tercihi perşembe günkü yeni bir oturumda hatırlamak ayrı bir iş: kalıcı memory. MAF'ta bunun yeri bir AIContextProvider. Provider her çağrıdan önce context'e talimat, mesaj ya da tool ekleyebiliyor (ProvideAIContextAsync) ve her çağrıdan sonra sonucu işleyebiliyor (StoreAIContextAsync):
public sealed class PreferencesMemory(IChatClient extractor, PreferenceStore store, int customerId) : AIContextProvider
{
protected override ValueTask<AIContext> ProvideAIContextAsync(InvokingContext context, CancellationToken cancellationToken = default)
{
CustomerPreferences? prefs = store.Get(customerId);
string text = prefs is null
? "No stored customer preferences."
: $"Stored customer preferences: genres={string.Join("/", prefs.Genres)}, max price per book={prefs.MaxPrice?.ToString() ?? "none"}.";
return ValueTask.FromResult(new AIContext { Instructions = text });
}
protected override async ValueTask StoreAIContextAsync(InvokedContext context, CancellationToken cancellationToken = default)
{
// Yalnızca müşterinin yazdığı şey memory olabilir; tool sonuçları ve model cevapları olamaz
List<ChatMessage> userMessages = context.RequestMessages.Where(m => m.Role == ChatRole.User).ToList();
if (!userMessages.Any(m => m.Text.Contains("remember", StringComparison.OrdinalIgnoreCase)))
{
return;
}
ChatResponse<CustomerPreferences> extracted = await extractor.GetResponseAsync<CustomerPreferences>(
userMessages,
new ChatOptions { Temperature = 0, Seed = 42, Instructions = "Extract only preferences the customer stated explicitly." },
cancellationToken: cancellationToken);
store.Save(customerId, extracted.Result);
Console.WriteLine($" [memory] saved: genres={string.Join("/", extracted.Result.Genres)}, maxPrice={extracted.Result.MaxPrice}");
}
}
Store, session'a değil oturum açmış müşterinin id'sine bağlı; müşteri id'si modelden değil uygulamadan geliyor. Memory'ye yalnızca müşterinin kendi mesajlarından, açıkça "remember" dediğinde ve structured output ile çıkarılan alanlar yazılıyor. İki ayrı session'la koşu:
// Pazartesi: bir session
AgentSession monday = await agent.CreateSessionAsync();
Console.WriteLine(await agent.RunAsync("Please remember: I only read novels and I never pay more than 20 dollars for a book.", monday));
// Perşembe: yepyeni bir session, history boş
AgentSession thursday = await agent.CreateSessionAsync();
Console.WriteLine(await agent.RunAsync("Suggest one book for me.", thursday));
[memory] saved: genres=novels, maxPrice=20
Here are 2 novels under $20:
1. **The Quiet Orchard** by Mira Holt ($16.50)
2. **The Last Lighthouse** by Mira Holt ($18.90)
Here's a book that fits your preferences:
**Title**: The Quiet Orchard
**Author**: Mira Holt
**Price**: $16.50 (under your $20 limit)
tool: search_books(query: , genre: novel, maxPrice: 20)
tool: search_books(query: novel, genre: , maxPrice: 20)
Perşembe günkü session'ın history'si boştu; "novel" ve "20 dolar" bilgisi provider'ın eklediği talimattan geldi ve öneri ona uydu. İki ayrıntı dikkat çekiyor. Memory'ye "novels" yazıldı, katalogdaki tür adı "novel"; bu koşuda model aradaki farkı kapattı, ama memory'ye yazılan değeri bilinen değerlere (bir enum'a) eşlemek daha sağlam. Pazartesi cevabı da stokta olmayan The Last Lighthouse'u öneriyor; memory doğru, ama agent stok kontrolü yapmadı. Memory, tool kullanımının yerini tutmuyor.
Sub-agent'a İş Devretmek
Context'i küçük tutmanın üçüncü yolu, araştırma gibi çok tool çağrısı gerektiren bir işi temiz context'li bir sub-agent'a verip geri yalnızca özetini almak. MAF'ta bir agent, AsAIFunction() ile başka bir agent'ın tool'u olabiliyor:
AIAgent researcher = subMeter.AsAIAgent(
instructions: "You research the Bookstore catalog with the tools and reply with at most three short lines.",
name: "CatalogResearcher",
description: "Researches books in the catalog and returns a short summary.",
tools: [.. tools.ReadTools()]);
AIAgent lead = meter.AsAIAgent(
instructions: "You are the Bookstore shopping assistant. Delegate catalog research to CatalogResearcher.",
name: "ShoppingAssistant",
tools: [researcher.AsAIFunction()]);
AgentResponse answer = await lead.RunAsync("Which Alan Brook book is cheaper, and is it in stock?");
The cheapest Alan Brook book is **"Async All the Way"** at **$39.90**, and it currently has **9 copies in stock**.
lead: model calls=2, input tokens=1327, output tokens=1340, time=63,3s
researcher: model calls=3, input tokens=6520, output tokens=2845, time=172,6s
tool: search_books(query: Alan Brook, genre: , maxPrice: )
tool: check_stock(bookId: 6)
Cevap doğru: Alan Brook'un iki kitabından ucuz olanı 39,90 dolarlık Async All the Way ve stokta 9 adet var. Asıl ilginç olan iki sayaç: arama ve stok kontrolünü yapan researcher 6.520 input token harcadı, lead ise yalnızca 1.327. Arama sonucunun beş kitaplık listesi ve stok cevabı lead'in context'ine hiç girmedi; lead yalnızca researcher'ın üç satırlık özetini gördü. Toplam maliyet tek agent'tan fazla (iki agent, beş model çağrısı), ama uzun bir oturumda lead'in context'i her araştırmayla şişmiyor. Anthropic'in yazısı da sub-agent'ların kapsamlı keşif yapıp genellikle bin-iki bin token'lık yoğun bir özet döndürdüğünü anlatıyor.
Üretimde Dikkat
Context'i ölç ve sınırını bil. Her model çağrısında gönderdiğin token sayısını logla ve modelin gerçekten kullandığı context penceresiyle karşılaştır. Yerel çalıştırmada bu pencere sunucu ayarıdır (Ollama'da model bu makinede 4.096 ile yüklendi); bulutta modelin ve sağlayıcının sınırıdır. Sınıra yaklaşınca sessizce kesilmesini beklemek yerine compaction'ı sen tetikle.
Kritik kısıtları history'ye emanet etme. Bütçe, yetki, kullanıcının "asla" dediği şeyler ayrı bir state'te (session state, memory store) tutulmalı ve her çağrıda talimata eklenmeli. Böylece ne kesme ne özet çıkarma onları kaybettirir. Compaction'lı koşuda bütçe sağ kaldı, ama bunu şansa bırakmak zorunda değilsin.
Memory'ye yazılanı doğrula ve sınırla. Neyin, kimin için, hangi kaynaktan yazıldığını kaydet; kullanıcı görebilsin ve silebilsin. Memory kişisel veri: saklama süresi ve silme politikası gerekiyor.
Özet çıkarmak da bir model çağrısı. SummarizationCompactionStrategy ana modelle aynı client'ı kullanırsa maliyeti ve gecikmeyi artırır; özet çıkarmak için daha küçük ve hızlı bir model kullanmak çoğu zaman yeterli.
Sık Düşülen Tuzaklar
Özet çıkarırken kritik kısıtı kaybetmek. Özeti çıkaran model "55 dolar bütçe" cümlesini önemsiz bir ayrıntı sanabilir. Kısıtları özetin dışında, yapılandırılmış state'te tut ve özetten sonra hâlâ orada olduklarını test et.
Memory'ye doğrulanmamış içerik yazmak. Tool sonucundaki ya da bir web sayfasındaki "this customer prefers express shipping" gibi bir metin memory'ye yazılırsa, bir sonraki oturumda kullanıcının tercihi gibi davranır. Memory'ye yalnızca kullanıcının kendi söylediği ve senin doğruladığın şey girer; bu prompt injection'a karşı da bir savunma.
Model context'ini sınırsız sanmak. Modelin kağıttaki context uzunluğu ile çalışan sunucunun ayarı farklı olabilir; deneyde 262 bin destekleyen model 4 bin ile çalışıyordu.
Her şeyi context'e doldurmak. Bütün kataloğu, bütün sipariş geçmişini, bütün dokümanı baştan vermek yerine agent'a arama tool'u ver ve gerektiğinde çektir.
Küçük Bir Deneme
★☆☆ Overflow deneyinde bütçe cümlesini history'nin başından sona yakın bir yere (son sorudan hemen önceye) taşı ve yirmi sayfayla tekrar çalıştır. Cevap değişiyor mu? Bu sana kesmenin hangi uçtan yapıldığı hakkında ne söylüyor?
★★☆ Bütçeyi PreferencesMemory benzeri bir provider ile her çağrıda talimata ekle ve overflow deneyini yirmi sayfayla tekrarla.
★★★ Compaction pipeline'ındaki SummarizationCompactionStrategy'yi gemma3:4b kullanan ayrı bir client ile çalıştır, eşiği 800 token'a indir ve dört turluk konuşmayı tekrar koştur. Bütçe son turda hâlâ korunuyor mu? Sepete eklenen kitaplar ne?
Tek bir agent'ın context'ini yönetmeyi gördük. Bir sonraki yazıda işi birden fazla adıma ve modele bölmenin yapılandırılmış yollarına geçiyoruz: prompt chaining, routing, parallelization, orchestrator-workers ve evaluator-optimizer pattern'leri ve MAF'ın WorkflowBuilder'ı.