2026-07-04
Kaçağı Durdurmak
Asla pes etmeyen bir agent ölü bir search'ü sonsuza dek retry eder ve budget'ı yakar. Multi-dimensional budget'lar, bir loop detector ve bir circuit breaker onu graceful bir partial result'a düşürür.
Neler öğreneceksin
Buraya kadarki her parça agent’ı daha yetenekli kıldı. Bu parça ise onu çalışır halde bırakmayı güvenli kılar. Part 1’deki loop tam olarak iki şekilde biter: controller finish() çağırır, ya da tek bir max_steps tavanına çarpar (RAG Part 19’un sahip olduğu tek guard). O zamandan beri her parçanın altında yatan dile getirilmemiş varsayım, loop’un eninde sonunda kendiliğinden duracağıdır. Her zaman durmaz. Gerçek bir controller iki farklı şekilde converge etmeyebilir, ve bunlar birbirine hiç benzemez. Stuck bir agent, aynı search’ü sonsuza dek yeniden çıkararak sıkışıp kalır, hiçbir şey öğrenmez, hiçbir yere varmaz. Wandering bir agent ise istikrarlı, makul, asla bitmeyen bir ilerleme kaydeder, her adımda biraz farklı bir query, ve aslında hiç bitirmeden sessizce büyük bir fatura biriktirir. Tek bir step tavanı bu ikisini sağlıklı bir koşudan ayıramaz, ve daha kötüsü, gerçekten para harcayan boyutları görmezden gelir: token’lar, dolarlar ve zaman. Bu parça, hepsi birlikte “denemeye devam et”i “denemeye devam et, şuraya kadar, ve sonra temizce dur”a çeviren üç guard ekler. Bir multi-dimensional budget (step’ler, token’lar, cost ve simüle edilmiş wall-clock üzerinde bir BudgetMeter) her adımdan önce kontrol edilir ve ilk boyut ceiling’ini geçtiği an koşuyu durdurur. Bir loop detector aynı (action, args) çiftlerini sayar ve bir threshold’da stuck imzasını işaretler. Ve bir circuit breaker ikisini bir araya bağlar: budget aşımında ya da tespit edilen bir loop’ta trip eder, ama trip etmek crash etmez, agent’ın sahip olduğu en iyi partial result’ı, artı durma nedenini döndürür. Tüm parçanın dürüst çerçevelemesi: ne zaman pes edeceğini bilmeyen bir agent bir yükümlülüktür.
Ön koşullar
Temel Python yeterli: fonksiyonlar, bir class, bir dictionary, bir liste ve bir string check. Hepsi bu. Part 1’den Part 5’e kadarını bitirmek yardımcı olur, özellikle Part 5, çünkü bu aynı recovery-ve-limits temasının sonuna kadar taşınmış halidir: Part 5 reflection’ı sınırladı (deneme sayısını kapatıp sonra escalate et), ve bu parça tüm koşuyu sınırlar. Ama yazı kendi içinde bütünlüklü: daha önceki bir fikre yaslandığı yerde onu tek bir cümleyle yeniden ifade eder, ve ReAct loop’unu yeniden öğretmez, onun etrafına birkaç guard sarar. Eşlik eden kod, budget_circuit_breaker.py, API anahtarı, ağ ve bağımlılık olmadan offline çalışır; böylece her satırı okuyup bu yazıdaki her trace’i kendiniz yeniden üretebilirsiniz. Wall-clock sabit bir per-step tahminle simüle edilir ve cost illüstratif bir per-token tahminidir, dolayısıyla demo yeniden üretilebilir, gerçek LLM controller’ı (generate()) tek bir ortam bayrağı uzaklıkta, tam olarak Part 1’den 7’ye kadarki gibi.
RAG Part 19’dan beri yeni olan ne
Neyin yeni olduğu konusunda kesin olmak istiyorum, çünkü hepsinin tohumu zaten oradaydı. RAG Part 19’un agent’ının tam olarak bir durma kontrolü vardı, tek bir max_steps tavanı, ve prose içinde tek bir gayriresmi yan not: “agent aynı search’ü iki kez çalıştırırsa, dur.” O not asla kod olmadı; bir uyarıydı. Bu parça ikisini de tahsil eder ve genelleştirir. Tek max_steps tavanı bir multi-dimensional budget olur, çünkü tek bir sayı “çok fazla token” ya da “çok pahalı” ya da “çok uzun süre çalışıyor”u ifade edemez, ve yalnızca step’ler üzerindeki bir tavan, izin verilen step sayısı içinde bir koşunun tüm token ve dolar budget’ını yakmasına seve seve izin verir. Gayriresmi “aynı search’ü iki kez gördüm” notu, tam (action, args) üzerinde key’lenen ve yapılandırılabilir bir threshold’da trip eden gerçek, yeniden kullanılabilir bir cycle detector olur. Ve ikisi de, crash etmek ya da dönüp durmak yerine bir partial result döndüren bir graceful circuit breaker’ı besler, ki RAG Part 19’un hiç sahip olmadığı bir şey. Burada ReAct loop’unu yeniden öğretmiyorum; Part 1 ona sahip ve ben ona referans veriyorum. Net olarak yeni olan şey, birden fazla boyutu olan budget, bir notu bir check’e formalleştiren detector, ve “dur”u “graceful biçimde dur”a çeviren breaker’dır.
Kaçak
Loop’u hiçbir şey durdurmadığında ne olduğuyla başlayın. İlk koşu, her tek adımda aynı search’ü yeniden çıkaran ve asla finish() çağırmayan bir stuck_controller kullanır, guard’lar kapalı olduğundan yalnızca bir demo tavanı onu bitirebilir. Ceiling’ler referans için her koşunun başında yazdırılır:
Budget ceilings: 4 steps | 600 tokens | $0.02 | 12s (simulated). Loop threshold: 3 identical actions.
Şimdi guard’sız koşuyu izleyin, artefaktın gerçek çıktısından:
step 1: search_policy('discount code for ORD-9999') -> (no discount code on file) [steps 1 | tokens 80 | $0.0008 | ~1.2s]
step 2: search_policy('discount code for ORD-9999') -> (no discount code on file) (x2) [steps 2 | tokens 160 | $0.0016 | ~2.4s]
step 3: search_policy('discount code for ORD-9999') -> (no discount code on file) (x3) [steps 3 | tokens 240 | $0.0024 | ~3.6s]
step 4: search_policy('discount code for ORD-9999') -> (no discount code on file) (x4) [steps 4 | tokens 320 | $0.0032 | ~4.8s]
step 5: search_policy('discount code for ORD-9999') -> (no discount code on file) (x5) [steps 5 | tokens 400 | $0.0040 | ~6.0s]
step 6: search_policy('discount code for ORD-9999') -> (no discount code on file) (x6) [steps 6 | tokens 480 | $0.0048 | ~7.2s]
step 7: search_policy('discount code for ORD-9999') -> (no discount code on file) (x7) [steps 7 | tokens 560 | $0.0056 | ~8.4s]
step 8: search_policy('discount code for ORD-9999') -> (no discount code on file) (x8) [steps 8 | tokens 640 | $0.0064 | ~9.6s]
[demo cap] cut off at 8 steps; an unguarded agent would not stop on its own.
final meter: steps 8 | tokens 640 | $0.0064 | ~9.6s
-> RUNAWAY (no guards): 8 steps and still not done.
Trace’i okuyun, çünkü başarısızlık konunun tamamı. Agent discount code’u arar, (no discount code on file) alır, ve “kod yok” diye sonuçlandırmak yerine yine arar. Ve yine. (x2), (x3), ta (x8)’e kadar olanlar aynı tekrarları sayar, ve metre kilit adımlarla tırmanır: token’lar 80, 160, ... 640, cost $0.0008’den $0.0064’e, simüle edilmiş zaman ~1.2s’den ~9.6s’ye. Loop içinde hiçbir şey asla dur demez. Koşu, agent bittiğine karar verdiği için bitmez; demo onu 8 step’te kestiği için biter, ve trace bunu açıkça söyler: an unguarded agent would not stop on its own. O [demo cap] satırı, yazı için oraya koyduğum güvenlik ağıdır, agent’ın sahip olduğu bir mekanizma değil. O olmasa metre tırmanmaya devam ederdi. Bu uydurma bir endişe değil. Hiç kimse fark etmeden önce sınırsız agent loop’larının ciddi miktarda para yaktığı belgelenmiş olaylar var, çünkü para harcayan boyutları izleyen hiçbir şey yoktu. Spesifik bir dolar rakamı icat etmeyeceğim, ama biçim gerçek: off-switch’i olmayan bir loop, ceiling’i olmayan bir faturadır. Parçanın geri kalanı üç off-switch’tir.
Birden fazla boyutu olan bir budget
İlk guard BudgetMeter’dır. İçgörü şu ki “çok pahalı” tek bir sayı değildir. Bir koşu step’lerde ucuz ama token’larda yıkıcı olabilir (devasa bir dokümanı yeniden okuyan tek bir step), ya da token’larda iyi ama bir latency SLA’sını patlatacak kadar yavaş olabilir, ya da sessizce bir dolar ceiling’ini geçerken diğer her limitin altında olabilir. Dolayısıyla metre dört boyutu aynı anda izler, step’ler, tahmini token’lar, tahmini USD, ve simüle edilmiş wall-clock, her biri per-step sabit bir miktar biriktirir (80 token, $0.0008, ~1.2s), ve her biri kendi ceiling’iyle. Kritik ayrıntı ne zaman ve nasıl kontrol edildiğidir. Her adımdan önce loop meter.exceeded()’i sorar, ki bu boyutları sırayla gezer ve limitini geçen ilk olanı döndürür, ya da None. Bir boyutun aşılması koşuyu durdurmaya yeter; step tavanının altında olduğun için tüm token budget’ını harcayamazsın. Kodun açıkça belirttiği ve burada tekrar edeceğim iki dürüstlük notu: wall-clock simüle edilmiştir, ölçülmüş bir süre değil sabit bir per-step tahmin, ki koşuyu yeniden üretilebilir kılan budur, ve cost illüstratif bir per-token tahmindir, gerçek bir fiyat listesi değil. Ölçülmüş zamanı ve gerçek bir fiyat tablosunu takmak tek satırlık bir değişiklik; mekanizma, her adımdan önce dördünü de kontrol et ve geçen ilk olanda dur, ileriye taşınan kısımdır. Wandering koşusunda aşağıda hangi boyutun trip ettiğini tam olarak göreceğiz.
Loop’u yakalamak
Tek başına bir budget yeterli değildir, çünkü stuck bir koşuyu pahalı bir koşudan, fatura zaten büyük olana kadar ayıramaz. Yukarıdaki stuck koşu eninde sonunda token ya da step budget’ını trip ederdi, ama ancak her step’i aynı işe yaramaz search’e harcadıktan sonra. İkinci guard stuck biçimi doğrudan ve erken yakalar. LoopDetector her step’i tam (action, args) üzerinde key’ler, o key’in kaç kez görüldüğünü sayar, ve sayım bir threshold’a (burada 3) ulaştığında bir loop işaretler. Bu, RAG Part 19’un yalnızca prose içinde yazdığı kod satırıdır: “agent aynı search’ü iki kez çalıştırırsa, dur” notu, herhangi bir controller’ın takabileceği gerçek, yeniden kullanılabilir bir check olur. Guard’ları açın ve aynı stuck controller’ı tekrar çalıştırın, artefaktın gerçek çıktısından:
step 1: search_policy('discount code for ORD-9999') -> (no discount code on file) [steps 1 | tokens 80 | $0.0008 | ~1.2s]
step 2: search_policy('discount code for ORD-9999') -> (no discount code on file) (x2) [steps 2 | tokens 160 | $0.0016 | ~2.4s]
step 3: search_policy('discount code for ORD-9999') -> (no discount code on file) (x3) [steps 3 | tokens 240 | $0.0024 | ~3.6s]
BREAKER TRIPPED: loop detected: identical action repeated 3 times
breaker: tripped (loop detected: identical action repeated 3 times)
final meter: steps 3 | tokens 240 | $0.0024 | ~3.6s (well under budget; the loop caught it first)
-> Stopped gracefully after 3 steps (loop detected: identical action repeated 3 times). Partial result: could not complete 'Find a discount code for ORD-9999'; returning what was gathered instead of looping or crashing.
Farkı izleyin. İlk iki step guard’sız koşuyla aynıdır, ama üçüncü tekrar threshold’a çarpar, detector loop detected: identical action repeated 3 times’i işaretler, ve breaker trip eder. Final metre steps 3 | tokens 240 | $0.0024 | ~3.6s okur, ve trace anahtar gözlemi açıkça yapar: well under budget; the loop caught it first. Bu, loop detector’ın yerini kazandığı yerdir. Tek guard multi-dimensional budget olsaydı, bu koşu bir tavan onu durdurana kadar devam ederdi: 4 step tavanına yalnızca bir step daha, ve 600 token tavanına çok daha uzun süre. Detector stuck imzayı, aynı action’ın tekrarlanmasını, tanır ve faturanın büyümesini beklemek yerine ilk işaretinde durur. Bir budget bir koşunun maliyetini sınırlar; bir loop detector ise hiçbir yere varmama spesifik başarısızlığının maliyetini sınırlar. Diagram 1 detector’ı serer: bir counter’a biriken ve threshold’da trip eden aynı action’lar.
Graceful biçimde trip etmek
Üçüncü guard, bir limite çarpıldığında ne olacağına karar veren kısımdır. Naif bir implementasyon ya bir exception fırlatırdı (caller bir stack trace alır ve partial iş kaybolur) ya da, daha kötüsü, sadece dönüp dururdu (kaçağın gösterdiği başarısızlık). CircuitBreaker ikisini de yapmaz. Üç state’i vardır, closed -> tripped -> graceful-finish: koşu sağlıklı iken closed, budget aşıldığı ya da bir loop tespit edildiği an tripped, ve bir trip’te neden’i kaydeder ve loop, agent’ın o ana kadar topladığı en iyi partial result’ı o nedenle birlikte döndürür. Asla fırlatmaz. Breaker’ın bir loop’ta trip ettiğini az önce gördük; şimdi onun budget’ta trip etmesini izleyin. Üçüncü koşu, her adımda farklı makul bir search çıkaran bir wandering_controller kullanır (policy clause 1, 2, 3, 4), dolayısıyla asla tekrar etmez ve loop detector’ın yakalayacak bir şeyi yoktur. Burada onu durdurabilecek tek şey budget’tır, artefaktın gerçek çıktısından:
step 1: search_policy('policy clause 1') -> clause text for 'policy clause 1' [steps 1 | tokens 80 | $0.0008 | ~1.2s]
step 2: search_policy('policy clause 2') -> clause text for 'policy clause 2' [steps 2 | tokens 160 | $0.0016 | ~2.4s]
step 3: search_policy('policy clause 3') -> clause text for 'policy clause 3' [steps 3 | tokens 240 | $0.0024 | ~3.6s]
step 4: search_policy('policy clause 4') -> clause text for 'policy clause 4' [steps 4 | tokens 320 | $0.0032 | ~4.8s]
BREAKER TRIPPED before step 5: step budget (4) reached
breaker: tripped (step budget (4) reached)
final meter: steps 4 | tokens 320 | $0.0032 | ~4.8s
-> Stopped gracefully after 4 steps (step budget (4) reached). Partial result: could not complete 'Audit every policy clause'; returning what was gathered instead of looping or crashing.
İki başarısızlık biçiminin neyi paylaştığını ve nerede ayrıldığını okuyun. Wandering koşu asla tekrar etmez, dolayısıyla her step gerçek görünümlü iş yapar ve loop detector sessiz kalır, ama pre-step budget check’i onu yakalar: BREAKER TRIPPED before step 5: step budget (4) reached. Zamanlamaya dikkat edin, breaker step 5’ten önce trip eder, loop’un başında meter.exceeded()’e danışıldığında ve step’leri zaten 4 ceiling’inde bulduğunda. Burada geçen ilk boyut budur; farklı bir controller önce token ya da cost boyutunu trip edebilirdi, ve metre hangisiyse onu döndürür. Ve kritik olarak, sonuç tam olarak loop durumunun biçimine sahiptir: breaker tripped, koşu Stopped gracefully after 4 steps, ve agent neyi tamamlayamadığını adlandıran ve returning what was gathered instead of looping or crashing diyen bir Partial result döndürür. O son cümle breaker’ın tüm sözleşmesidir. İster bir loop ister bir budget onu trip etsin, caller asla bir crash almaz ve asla sonsuz bir dönüş almaz; caller agent’ın sahip olduğu en iyiyi, artı durmasının sade-Türkçe bir nedenini alır, ki bu tam olarak sabaha karşı 3’te nöbetteki bir insanın ihtiyaç duyduğu şeydir. Diagram 2 üç state’i çizer, ve aşağıdaki interaktif figür her iki başarısızlık biçimini sürmenizi ve dört boyutlu metrenin bir ceiling breaker’ı trip edene kadar dolmasını izlemenizi sağlar.
💡 Deneyimden. Bana budget’ları öğreten fatura, sessizce kaldırılmış bir tool’dan geldi. Bir gece çalışır halde bıraktığım bir agent, artık boş bir sonuç döndüren bir search endpoint’ini çağırıyordu, ve controller, “no results” okuyarak, doğru hamlenin search’ü tekrar denemek olduğuna karar verdi, her seferinde biraz farklı sözcüklerle yeniden ifade ederek, böylece naif “iki kez gördüm” check’im hiç ateşlenmedi. Sabaha kadar binlerce çağrı yapmıştı, her biri tek başına ucuz, ve toplam faturadaki gerçekten şaşırtıcı bir sayıydı. Çare daha akıllı prompting değildi; iki sıkıcı guard’dı. Action ve argümanları üzerinde key’lenen bir loop detector birebir-tekrar versiyonunu yakalardı, ve her adımdan önce kontrol edilen bir cost ceiling, yeniden-ifade-edilmiş-wandering versiyonunu yine de yakalardı, çünkü dolarlar ben uyanmadan çok önce çizgiyi geçti. Dersin graceful yarısı daha sonra geldi, farklı bir sistemde. Uzun bir agent koşusu sabaha karşı 3’te budget’ını trip etti ve beni page’ledi, ve onun bir yangın yerine temiz bir page olmasının tek nedeni breaker’ın nedeni iliştirilmiş bir partial result döndürmesiydi: altı alt-görevden dördünü bitirmiş ve cost ceiling’inde durmuştu. Tek bir satır okudum, güvenli olanı yaptığını gördüm, ve uyumaya geri döndüm. Sabaha karşı 3’te bir crash bir incident’tir; nedenli bir partial result ise sabah okuduğun bir nottur.
Özet / Çıkarımlar
- Part 1 loop’u yalnızca
finish()’te ya da tek birmax_stepstavanında durur (RAG Part 19’un tek guard’ı), ama gerçek bir controller, bir step tavanının ayıramayacağı iki biçimde converge etmeyebilir: aynı action’ı sonsuza dek yeniden çıkaran bir stuck agent, ve faturayı biriktiren makul asla-bitmeyen bir ilerleme kaydeden bir wandering agent. - Kaçak gerçektir: guard’lar kapalıyken, stuck controller
search_policy('discount code for ORD-9999')’ı 8 step boyunca tekrarladı (metresteps 8 | tokens 640 | $0.0064 | ~9.6s’ye tırmanarak) ve onu yalnızca demo tavanı durdurdu, çünküan unguarded agent would not stop on its own. Sınırsız loop’ların belgelenmiş olayları bu yolla ciddi para yaktı. - Bir multi-dimensional budget (step’ler, token’lar, cost ve simüle edilmiş wall-clock üzerinde
BudgetMeter) her adımdan önce kontrol edilir ve ceiling’ini geçen ilk boyutta durur, çünkü tek bir sayı “çok pahalı”yı ifade edemez. Wall-clock simüle edilmiştir ve cost illüstratif bir per-token tahminidir. - Bir loop detector tam
(action, args)üzerinde key’lenir ve bir threshold’da trip eder (burada 3). Stuck koşudasteps 3 | tokens 240 | $0.0024 | ~3.6s’de ateşlendi,well under budget; the loop caught it first. Bu, RAG Part 19’un gayriresmi “aynı search’ü iki kez gördüm” notunu yeniden kullanılabilir bir check’e formalleştirir. - Bir circuit breaker ikisini
closed -> tripped -> graceful-finishstate’leriyle bir araya bağlar. Wandering koşuda budget onubefore step 5: step budget (4) reached’te trip etti, ve agentStopped gracefully after 4 steps, bir partial resultinstead of looping or crashingdöndürdü. Asla fırlatmaz ve asla dönüp durmaz. - İki başarısızlık biçimi farklı yakalayıcılar gerektirir: loop detector stuck imzayı erken tanır, budget herhangi bir koşunun, bir wandering dahil, maliyetini sınırlar, ve breaker her iki sonucu bir partial result artı bir neden yapar. Burada sahiplenildi, sonra yeniden kullanıldı:
BudgetMeterPart 13’te ve breaker Part 14’te.
Sözlük
- Multi-dimensional budget: tek bir step sayımı yerine birden fazla cost eksenini aynı anda izleyen bir durma kontrolü, çünkü “çok pahalı” tek bir sayıyla yakalanamaz; her adımdan önce kontrol edilir ve herhangi bir eksen ceiling’ini geçtiği an tetiklenir.
- BudgetMeter: dört boyutu biriktiren (per-step sabit
80token,$0.0008ve~1.2s, artı bir step sayımı) veexceeded()’i ortaya koyan nesne, ki bu limitini geçen ilk boyutu ya daNonedöndürür. Part 13’te bir code-execution sandbox’ını sınırlamak için yeniden kullanılır. - Dört boyut: step’ler, tahmini token’lar, tahmini USD, ve simüle edilmiş wall-clock, ceiling’leri
4 steps | 600 tokens | $0.02 | 12s. Wall-clock simüle edilmiştir (ölçülmüş değil sabit bir per-step tahmin) ve cost illüstratif bir per-token tahminidir (gerçek bir fiyat listesi değil); ikisi de tasarım gereği yeniden üretilebilir. - Loop detector: her step’i tam
(action, args)üzerinde key’leyen, tekrarları sayan, ve sayım bir threshold’a (burada3aynı action) ulaştığında bir loop işaretleyen bir check; stuck imzayı budget dolmadan önce, erken tanır. RAG Part 19’un gayriresmi “aynı search’ü iki kez görürsen dur” notunu yeniden kullanılabilir koda formalleştirir. - Circuit breaker: ya bir budget ihlalinde ya da tespit edilen bir loop’ta trip eden üç-state’li bir makine,
closed -> tripped -> graceful-finish. Nedeni kaydeder ve koşuyu bir exception fırlatmadan ve dönüp durmadan bitirir. Part 14’te bir runaway supervisor’ı durdurmak için yeniden kullanılır. - Graceful partial result: breaker’ın bir trip’te döndürdüğü şey, agent’ın topladığı en iyi output artı durmasının sade-Türkçe bir nedeni (örneğin
Stopped gracefully after 4 steps (step budget (4) reached)), böylece caller bir crash ya da sonsuz bir loop yerine faydalı bir şey ve bir açıklama alır.
Bu parçanın kuralı: çalışır halde bırakabileceğin bir agent’ın ne zaman pes edeceğini bilmesi gerekir, ve tek bir sayı bunu ona söyleyemez. Koşuyu para harcayan her boyutta sınırla (her adımdan önce kontrol edilen bir BudgetMeter, geçen ilk olan durdurur), stuck imzayı doğrudan yakala (tam action ve argümanlar üzerinde bir loop detector, RAG Part 19’un notunu formalleştirir), ve bir limite çarpmayı graceful yap (bir partial result artı bir nedene trip eden bir circuit breaker, asla bir crash ve asla bir dönüş). Ama bu parçayı bile atlatan varsayıma dikkat edin: agent tek, kırılgan, in-memory bir process içinde yaşıyor. Metre, detector, breaker, partial result, hepsi yalnızca RAM’de var. Bir breaker’ı temizce trip et, derli toplu bir partial result döndür, ve sonra process’in uçuş ortasında öldürülmesini sağla, ve yarım kalmış iş yok olur. Part 9, The Durable Agent, bunu hayatta kalmakla ilgilidir: her adımı olmadan önce kaydeden bir event journal, bir crash sonrası state’i yeniden inşa eden replay, ve effectively-once execution, böylece process öldüğünde yarısı verilmiş bir refund ne kaybolur ne de iki kez ödenir.