2026-07-06
Duraklat, Onayla, Devam Et, Yönlendir
Bazı action'lar insan onayı gerektirir ve bir kullanıcı ajanı koşu ortasında düzeltmek isteyebilir. Journal destekli bir interrupt, resume ve steer, bir insanı döngüye durable biçimde dahil eder.
Neler öğreneceksin
Part 9 ajanı durable kıldı: iki step arasında crash edip state’i kaybetmeden ya da bir müşteriyi iki kez charge etmeden geri dönebilir. Ama hâlâ inatçı bir alışkanlığı var. Başı eğik, kendi başına baştan sona koşuyor, hiç durup sormuyor. Gerçek bir deployment’ın ihtiyaç duyduğu iki şey eksik, ve ikisi de koşunun durup kontrolü bir kişiye devretmesini gerektirir. İlki onay: bazı action’lar bir insan evet demeden gerçekleşmemeli, ve kanonik durum paradır. Bir $100 policy threshold’unun üzerindeki bir $180.00 refund, yetkisi olan biri onaylamadan post edilmemeli. İkincisi düzeltme: koşuyu izleyen bir kişi, ajanın bir sonraki adımda ne yapacağını yalnızca kutsamak ya da engellemek yerine değiştirmek isteyebilir, ve ikili bir onayla-ya-reddet gate’i “yap, ama daha düşük bir tutar için” ifadesini taşıyamaz. Bu parça ikisini de, Part 9’un journal’ının üzerine, dört hamleyle ekler. İlki, interrupt(): gate’li bir action’da ajan tool’u çağırmaz, bir token’la birlikte bir pending_approval event’ini journal’a yazar ve serializable bir PendingApproval raise eder, ki bu en üst seviyede yakalanır, böylece koşu duraklatılır ve token döndürülür, öldürülmez. İkincisi, resume(run_id, decision): daha sonra bir insan karar verir, ve resume journal’ı tam olarak Part 9’daki gibi replay eder, kararı kaydeder ve devam eder, gate’li refund idempotency key’ini koruyarak, böylece onaylanmış bir resume retry edilse bile effectively-once çalışır. Üçüncüsü, steer, yalnızca approve ya da deny değil: karar first-class bir action’dır, APPROVE, DENY ya da bir düzeltmeyi STEER etmek (burada refund’ı threshold’un altına indirmek), ve kullanıcıya bir clarifying question’ı geri yanıtlamak aynı mekanizmadır. Dördüncüsü, streaming progress: her step zaten bir journal event’i olduğundan, canlı bir feed aynı log’un üzerinde bir okumadan ibarettir, ve pause, karar ve resume hepsi onda görünür.
Ön koşullar
Temel Python yeterli: fonksiyonlar, bir dictionary, bir liste ve bir loop. Hepsi bu. Part 9 çok yardımcı olur, çünkü bu parça doğrudan onun iki fikrinin üzerine kurulu, state’in log’un üzerindeki fold olduğu append-only journal ve bir side-effect’i effectively-once kılan idempotency key, ama yazı kendi içinde bütünlüklü. Part 9’a yaslandığı yerde fikri tek bir cümleyle yeniden ifade eder ve journal’ı ya da ReAct loop’unu yeniden inşa etmez; bunlar Part 1 ve 9’a aittir ve burada referans verilir. Eşlik eden kod, pause_approve_resume.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. Timestamp’ler ve run id (run-aa10) donduruludur, dolayısıyla journal byte-reproducible’dır, gerçek LLM path’i (generate()) tek bir ortam bayrağı uzaklıkta, tam olarak Part 1’den 9’a kadarki gibi.
Part 9’dan beri yeni olan ne
Bir insan için duraklamanın kendi failure mode’larıyla kendi katmanı olduğu için, neyin yeni olduğu konusunda kesin olmak istiyorum. Part 9 durability ekledi, bir crash’ten ve bir recovery’den sağ çıkma yeteneği, ama durable bir ajan hâlâ yalnız çalışır: ya biter ya da crash edip kendini resume eder, ve hiçbir noktada bilerek durup bir kişiyi beklemez. Bu parça o bilerek durmayı ekler. Önceki serideki en yakın şey RAG Part 20’nin conversational ajanıydı, ama o farklı bir hayvan: turn’leri same-process’ti, bir kullanıcı bir follow-up gönderdi ve aynı canlı loop, baştan sona state RAM’de olacak şekilde devam etti. Gate yoktu, token yoktu, ikinci process yoktu. Ve RAG Part 19’un ajanının yalnızca read-only tool’ları vardı, dolayısıyla hiç onaya ihtiyaç duymadı, çünkü yaptığı hiçbir şey para hareket ettiremez ya da engellenmeye değer bir side-effect commit edemezdi. Burada gerçekten yeni olan, onay için cross-process pause ve resume ile mid-run steering, ve ikisi de durable territory, conversational territory değil. Pause, journal’a persist olan ve bir token döndüren gerçek bir kesintidir; resume ise ayrı bir act, muhtemelen ayrı bir process içinde, o journal’ı geri okuyan. Steer, pause ile resume arasında bir insan tarafından enjekte edilen bir düzeltmedir. Bunların hiçbiri daha önce yoktu. Bu parça Part 9’un journal’ını ve idempotency key’ini substrate olarak yeniden kullanır, onlara referans verir ve onları yeniden inşa etmez; yeni kod interrupt, resume ve bir düzeltme olabilen karardır.
Interrupt
Koşu güvenle gidebildiği kadar gider, ve sonra gate’te durur. İlk step, search_policy, read-only’dir ve onay gerektirmez, dolayısıyla çalışır ve sonucunu journal’a yazar. İkinci step refund’dır, ve $180.00, $100 threshold’unun üzerindedir, dolayısıyla ajan gate’e ulaşır ve refund tool’unu çağırmaz. Bunun yerine bir token ve pending action taşıyan bir pending_approval event’ini journal’a yazar ve bir PendingApproval raise eder. İşte pause, artefaktın gerçek çıktısından alıntılanmış:
step 0 search_policy: Refunds after the window are refundable minus a 10% restocking fee.
PAUSED: refund $180.00 exceeds the $100 threshold
returned token 'appr-1'; the run is persisted, not dead. Ledger so far: (empty, no money moved yet)
Ne olup ne olmadığını oku. Step 0 çalıştı, policy text journal’da. Sonra gate, step 1’i payment provider’a dokunmadan önce durdurdu, ki ledger’ın (empty, no money moved yet) olmasının nedeni bu: para hareket etmedi, çünkü gate side-effect’ten önce kontrol edilir, sonra değil. Kritik tasarım seçimi persisted kelimesinde. Interrupt bir PendingApproval exception’ıdır, sys.exit değil. Bu göründüğünden daha önemli. sys.exit interpreter’ı yıkar, ki bu tek seferlik bir script için iyidir ama bir notebook’ta ya da uzun ömürlü bir server’da felakettir, soru sormak için process’i öldürmek saçmadır. En üst seviyede yakalanan serializable bir exception bunun tersini yapar: caller onu yakalar, journal’ı persist eder ve token’ı refund’ı isteyen kişiye döndürür. Koşu paused, not dead. Token 'appr-1', bir insanın daha sonra duran tam koşuyu resume etmek için zikredeceği handle’dır, ve diskteki pending_approval event’i pause’un onu raise eden process’ten sağ çıkmasını sağlayan şeydir. Journal üzerindeki stream aynı üç ritmi gösterir:
> run started
> done: Refunds after the window are refundable minus a 10% restocking fee.
> PAUSED for approval (refund $180.00 exceeds the $100 threshold); token appr-1
O son satır pause’un tamamı, bir journal event’i olarak ifade edilmiş: bir crash değil, bir exit değil, koşunun nerede durduğunu ve hangi token’ın onu yeniden açacağını söyleyen kaydedilmiş bir waypoint.
Resume, effectively-once
Daha sonra bir insan karar verir. resume(run_id, decision) taze bir koşu başlatmaz; duraklatılmış olanı journal’ı replay ederek rehydrate eder, tam olarak Part 9 fold’u, kararı bir approval_decision event’i olarak kaydeder ve loop’a yeniden girer. Replay resume’u güvenli kılan şeydir: loop’a tam olarak hangi step’lerin zaten bittiğini, dolayısıyla onları yeniden yapmamasını ve hangi step’in hâlâ gate’te beklediğini söyler. Approve path’ini izle, artefaktın gerçek çıktısından alıntılanmış:
[human] decision = approve
step 0 search_policy: memoized -> 'Refunds after the window are refundable minus a 10% restocking fee.'
step 1 process_refund: refunded $180.00 to ORD-3300
finished -> Refund of $180.00 for ORD-3300 is complete.
ledger: {'ORD-3300': 180.0}
Part 9’dan iki mekanizma burada işi yapıyor. Step 0 memoized: journal üzerindeki fold onun kaydedilmiş sonucunu buldu, dolayısıyla resume search’ü yeniden çalıştırmadan memoized -> 'Refunds after the window...' döndürdü. Sonra step 1 gerçekten çalışır, refund post edilir, refunded $180.00 to ORD-3300, koşu biter, ve ledger {'ORD-3300': 180.0} okur, bir refund. Bunun resume iki kez çağrılsa bile güvenli olmasının nedeni, diyelim ki dengesiz bir ağdan sonra bir retry ya da approve’a tekrar tıklayan sabırsız bir operatör, Part 9’dan gelen idempotency key’dir. Gate’li refund key’ini pause boyunca korudu, dolayısıyla ikinci bir onaylanmış resume provider’ın zaten honor ettiği bir key’e çarpar ve tekrar charge etmek yerine orijinal sonucu geri alır. İşte bir insan pause’u boyunca taşınan effectively-once budur: refund kararı bir retry’da yeniden verilebilir, ama effect tam olarak bir kez gerçekleşir. Pause Part 9’un garantisini zayıflatmadı; onu miras aldı. Diagram 1 iki fazı izler, token’ı journal’a yazan interrupt ve replay edip action alan resume.
Karar bir action’dır: approve, deny, steer
İşte gerçek bir human-in-the-loop’u bir checkbox’tan ayıran kısım. Karar ikili bir gate değildir. First-class bir action’dır, ve ajan APPROVE, DENY ve STEER’ı insanın yapabileceği üç farklı şey olarak ele alır. Approve’u az önce gördük. Deny temiz reddir: insan hayır der, ve koşu hiç para hareket etmeden biter, artefaktın gerçek çıktısından alıntılanmış:
[human] decision = deny
step 0 search_policy: memoized -> 'Refunds after the window are refundable minus a 10% restocking fee.'
step 1 process_refund: DENIED by approver -> no money moved
ledger: (empty)
DENIED by approver -> no money moved, ve ledger (empty) kalır. Bu ikili yarısı, ve çoğu sistem orada durur. Ama ikili bir gate, ajan neredeyse haklı olduğunda kötü bir seçimi dayatır: refund haklı ama tutar yanlış, ve approve fazla öderken deny kimseye yardım etmez. Steer üçüncü seçenektir, ve ilginç olan da o. Onaylayan kişi yalnızca evet ya da hayır demez; ajanın bir sonraki adımda ne yapacağını değiştiren bir düzeltme enjekte eder. Burada refund’ı $180.00’dan $90.00’a indirirler, ki bu onu threshold’un altına düşürür, ve ajan sonra düzeltilmiş action’ı gerçekleştirir, artefaktın gerçek çıktısından alıntılanmış:
[human] decision = steer (correction: $90.00)
step 0 search_policy: memoized -> 'Refunds after the window are refundable minus a 10% restocking fee.'
step 1 process_refund: STEERED by approver -> amount lowered to $90.00 (now under the $100 threshold)
step 1 process_refund: refunded $90.00 to ORD-3300
finished -> Refund of $90.00 for ORD-3300 is complete.
ledger: {'ORD-3300': 90.0}
Step 1 için iki satırı oku. Önce STEERED by approver -> amount lowered to $90.00 (now under the $100 threshold), düzeltme uygulandı; sonra refunded $90.00 to ORD-3300, düzeltilmiş action gerçekleştirildi; ve ledger {'ORD-3300': 90.0} okur. İnsan yanlış şeyi onaylamadı ya da gerekli bir refund’ı reddetmedi; onu koşu ortasında düzeltti ve ajanın doğru şeyi bitirmesine izin verdi. Kararın first-class bir action olmasının önemi bu yüzden: bir düzeltme yalnızca ikiliden daha zengin bir gate’in taşıyabileceği bir şeydir. Ve mekanizma paranın ötesine genelleşir. Kullanıcıya bir clarifying question sormak için duraklayan bir ajan tam olarak aynı şeyi yapıyor, bir pause’u journal’a yazıyor, kontrolü geri veriyor ve insanın cevabı planına bir düzeltme olarak enjekte edilmiş şekilde resume ediyor. Onay, ret, steering ve bir clarifying question, dört şapka takan tek bir mekanizmadır. Diagram 2 gate’i üç yönlü bir branch olarak ortaya koyar, ve aşağıdaki interaktif figür üç çözümün hepsini canlı journal ve ledger’a karşı sürmenizi sağlar.
Journal’dan streaming
Part 9’da her step’i bir journal event’i yapmış olmanın sessiz bir getirisi var. Canlı bir progress feed’i inşa etmek hiçbir ekstra maliyet getirmez, çünkü o yalnızca koşuyu durable kılan aynı log üzerinde bir okumadır. Pause bir event’tir, karar bir event’tir, resume’un sonuçları event’lerdir, dolayısıyla streaming state’i yeniden inşa eden değil, onları geldikçe yazdıran bir fold’dur. İşte steer timeline’ı, pause ve düzeltmesi dahil bir koşunun tüm yaşamı, artefaktın gerçek çıktısından alıntılanmış:
> run started
> done: Refunds after the window are refundable minus a 10% restocking fee.
> PAUSED for approval (refund $180.00 exceeds the $100 threshold); token appr-1
> human decision: steer
> done: refunded $90.00 to ORD-3300
> finished: Refund of $90.00 for ORD-3300 is complete.
Yukarıdan aşağı oku ve o koşunun tüm hikayesidir: başladı, search bitti, token appr-1 ile onay için duraklattı, bir insan onu steer etti, $90.00’lık düzeltilmiş refund geçti, ve bitti. Bu feed’i tail eden bir izleyici ajanın durduğunu görür, insanın müdahale ettiğini görür ve tekrar devam ettiğini görür, hepsi bir crash’ten sonra yeni bir process’in koşuyu resume etmesini sağlayacak aynı event’lerden. Durability ve observability iki şekilde okunan aynı log’dur: onu bir process’e replay et resume alırsın, bir ekrana replay et stream alırsın. Her step’i yazıya dökmenin anlamı, onu yalnızca bir kez yazman gerekmesidir.
Process’ler arasında
Cross-process hikayesi konusunda dürüst olmak istiyorum, çünkü tek dosyalık demo yanıltabilir. Gerçek bir deployment tek bir nefeste pause edip resume etmez. Onay gate’ine çarpan request token’ı döndürür ve biter, bir process içinde; koşu artık journal’da oturuyor, duraklatılmış. Bir süre sonra, bir insan ona gerçekten baktıktan sonra, farklı bir process içinde, daha sonraki bir web request’i ya da bir CLI re-invocation’ı, resume o token’la aynı journal üzerinde çağrılır, ve koşu durduğu yerden devam eder. İki faz zamanda ve process’te gerçekten ayrılmıştır, ve onları bağlayan tek şey durable log ve duraklatılmış koşuyu adlandıran token’dır. Bu dosyadaki demo her iki fazı tek bir programda modeller, PendingApproval’ı yakalayarak, dünyayı persist ederek ve sonra o persist edilmiş dünya üzerinde resume çağırarak, böylece tüm yayı tek bir trace’te izleyebilirsin. Bu bir kısayol değil, sadık bir modeldir: resume gerçekten journal’ı in-memory bir değişkene güvenmek yerine baştan replay eder, ki bu tam olarak ikinci bir process’in yapmak zorunda olacağı şeydir. CLI iki-process versiyonu aynı fikrin literal hali, duraklatıp journal’ı yazan ilk invocation, o journal’ı okuyup resume eden ikinci invocation. Tek dosya timeline’ı okunabilirlik için sıkıştırır; durable log, sıkıştırmayı geri açmayı güvenli kılan şeydir.
💡 Deneyimden. Bir onay gate’i bir keresinde beni gerçekten kötü bir günden kurtardı. Refund işleyen bir ajanımız vardı, ve bozuk bir upstream kaydı, bir müşterinin makul herhangi bir şeyden kabaca iki kat büyüklük mertebesinde daha büyük bir refund’a hak kazandığına karar vermesine yol açtı. Post edilmemesinin tek nedeni aptal bir threshold’du: birkaç yüz dolardan fazla herhangi bir şey duraklatıp bir insan kuyruğuna ping atıyordu. Onu ele aldım, sayıyı gördüm, ve onaylamak ya da reddetmek yerine üçüncü şeyi yaptım, onu doğru tutara steer ettim ve bitmesine izin verdim, çünkü müşteri bir refund’a hak kazanmıştı, sadece o refund’a değil. Gate ikili olsaydı, meşru bir refund’ı reddedip manuel bir ticket başlatmak ya da bir fraud review ve bir clawback tetikleyecek bir sayı onaylamak zorunda kalacaktım. Steer, neredeyse bir incident’i otuz saniyelik bir düzeltmeye dönüştürdü. Diğer ders, hiç pause’u olmayan daha eski bir sistemdeki tersi hatadan geldi: bir ajan koşu ortasında raydan çıktığında tek aracımız process’i öldürüp sıfırdan restart etmekti, ki bu zaten yaptığı iyi işi çöpe atıyor ve side-effect’leri yeniden yapma riskini taşıyordu. Bir koşuyu durdurabilmek, onu tekrar raya sokabilmek ve tam duraklattığı yerden resume edebilmek, öldürüp restart etmek yerine, bir arabayı sürmek ile sıfırdan başlamak için onu yoldan dışarı itmek arasındaki farktır.
Özet / Çıkarımlar
- Part 9’un durable ajanı bir crash’ten sağ çıkar ama hâlâ yalnız çalışır, durup sormanın bir yolu yoktur. Gerçek deployment’lar eksik olduğu iki şeye ihtiyaç duyar: gate’li bir action’dan önce onay (bir
$100threshold’unun üzerinde bir$180.00refund) ve ajanın koşu ortasında düzeltilmesi, ve ikisi de koşunun duraklayıp kontrolü bir insana devretmesini gerektirir. interrupt()bir token’la birlikte birpending_approvalevent’ini journal’a yazar ve en üst seviyede yakalanan serializable birPendingApprovalraise eder,sys.exitdeğil, böylece caller journal’ı persist eder ve token’ı ('appr-1') döndürürken ledger(empty, no money moved yet)kalır. Koşu paused, not dead, dolayısıyla notebook’lar ve server’lar çalışabilir halde kalır.resume(run_id, decision)journal’ı replay eder (Part 9’un fold’u), zaten bitmişsearch_policystep’ini memoize eder, kararı kaydeder ve devam eder. Gate’li refund idempotency key’ini korur, dolayısıyla onaylanmış bir resume retry edilse bile effectively-once çalışır:refunded $180.00 to ORD-3300, ledger{'ORD-3300': 180.0}.- Karar first-class bir action’dır, ikili bir gate değil:
DENIED by approver -> no money moved(ledger empty), ya daSTEERED by approver -> amount lowered to $90.00sonrarefunded $90.00 to ORD-3300(ledger{'ORD-3300': 90.0}). Steer bir düzeltme enjekte eder; kullanıcıya bir clarifying question’ı geri yanıtlamak aynı mekanizmadır. - Streaming aynı log üzerinde bir okumadır. Steer timeline’ı (
run started,done,PAUSED for approval ... token appr-1,human decision: steer,done: refunded $90.00,finished) yalnızca koşuyu zaten durable kılan journal event’leri üzerinde bir fold’dur. Her step’i bir kez yaz ve hem resume hem de canlı bir feed elde edersin. - Cross-process, dürüstçe: gerçek bir deployment bir process içinde pause eder (request token’ı döndürür) ve başka birinde resume eder (daha sonraki bir request ya da CLI re-invocation’ı) aynı journal üzerinde. Tek dosyalık demo her iki fazı
PendingApproval’ı yakalayıp sonra persist edilmiş dünya üzerinderesumeçağırarak modeller, ki bu sadıktır çünkü resume RAM’e güvenmek yerine baştan replay eder.
Sözlük
- Human-in-the-loop: ajanın tam otonom çalışmak yerine belirli noktalarda bilerek durup bir kişinin yapmak üzere olduğu şeyi onaylamasına, reddetmesine ya da düzeltmesine izin verdiği bir tasarım; burada Part 9’un journal’ı üzerine kurulu, böylece pause process’ten sağ çıkar.
- Approval gate / threshold: side-effect üreten bir action’dan önce bir policy check’i (burada
$100’ün üzerindeki refund’lar) ki insan onayı gerektirir; gate effect’ten önce kontrol edilir, ki bir duraklatılmış refund’ın ledger’ı boş, para hareket etmemiş bırakmasının nedeni budur. interrupt(): gate’li bir action’a ulaşma, bir token ve pending action’la birpending_approvalevent’ini journal’a yazma ve tool’u çağırmak yerinePendingApprovalraise etme act’ı; koşuyu duraklatır ve kontrolü caller’a devreder.PendingApproval(serializable,sys.exitdeğil): interrupt’ın raise ettiği exception, token’ı, pending action’ı ve nedeni taşıyan; en üst seviyede yakalanır, böylece caller journal’ı persist eder ve token’ı döndürür. Bilereksys.exitdeğildir, böylece bir notebook ya da server duraklatılır, öldürülmez.- Token: bir koşu duraklatıldığında döndürülen handle (burada
'appr-1'); bir insan daha sonra duran tam koşuyu resume etmek için onu zikreder, vepending_approvaljournal event’ine kaydedilir, böylece pause process’ten sağ çıkar. resume(run_id, decision): duraklatılmış bir koşuyu journal’ını replay ederek (Part 9’un fold’u) rehydrate etmek, birapproval_decisionevent’i kaydetmek ve karara göre action almak için loop’a yeniden girmek; bitmiş step’ler memoize edilir, böylece yalnızca gate’li step çalışır.- Steer (first-class karar): ikili bir evet ya da hayır yerine bir düzeltme enjekte eden (burada refund’ı threshold’un altında
$90.00’a indiren) bir insan kararı; ajan düzeltmeyi uygular ve düzeltilmiş action’ı gerçekleştirir. Kullanıcı tarafından yanıtlanan bir clarifying question aynı mekanizmadır. - Streaming progress: journal event’lerini eklendikçe okuyarak üretilen canlı bir feed; durability zaten her step’i kaydettiğinden, pause, karar ve resume stream’de bedavaya görünür.
- Part 9’u yeniden kullanır: append-only journal ve idempotency key Part 9’dan miras alınır, yeniden inşa edilmez; journal pause ve resume’u durable kılar, ve key onaylanmış bir resume’u retry edilse bile effectively-once kılar.
Bu parçanın kuralı: durable bir ajan tam otonom çalışmak ile hiç çalışmamak arasında seçim yapmak zorunda kalmamalı, çünkü bazı action’lar bir insan gerektirir ve bazı koşular uçuş ortasında düzeltilmeyi gerektirir. Journal’ı koşunun durabileceği bir yere çevir: gate’li bir action’da, bir token’ı journal’a yaz ve sys.exit yerine serializable bir PendingApproval raise et, böylece koşu duraklatılır ve token döndürülür, öldürülmez (interrupt). Bir insanın o journal’ı replay edip karara göre action alarak daha sonra karar vermesine izin ver, idempotency key onaylanmış bir retry’ı güvenli kılarak (resume, effectively-once). Kararı first-class bir action yap, böylece bir kişi yalnızca ikili bir switch’i çevirmek yerine approve, deny ya da bir düzeltmeyi steer edebilsin. Ve progress’i bedavaya stream et, çünkü o aynı journal’ın bir ekrana okunmasıdır. Ama hâlâ iyi yapamadığımız şeye dikkat et: bu koşulardan biri production’da pause, resume, steer ve side-effect’ler boyunca yanlış gittiğinde, elle okuyabileceğimiz bir journal’a ve fazla bir şeye sahip değiliz. İçini göremediğin bir durable ajan undebuggable’dır, ve “JSONL’i elle oku” gerçek bir incident’le temasta hayatta kalmaz. Part 11, The Observable Agent, core capstone’dur: bu aynı journal’ı gerçek tracing için OpenTelemetry şeklindeki span’lere fold et, ham token sayıları yerine cost-per-success ölç, ve inşa ettiğimiz ajanı gerçekten operate edebileceğin birine dönüştür.