Sertaç Yıldırım saha notları

Ana sayfa → Teknik

Agent İki Kez Çağırdı: Otonomi Değil, Idempotency

Cuma 16:20, test ortamı. Log’da aynı satır iki kez: cekim_onayla(WD-24817). İkisi de başarılı. Sebep üç satır yukarıdaydı: tool 32 saniyede döndü, bizim zaman aşımımız 30 saniye. Agent cevabı alamadı, “olmamış” diye düşündü ve tekrar denedi. Oysa olmuştu. Test ortamında iki kayıt, 1.250 $. Canlıda aynı log, çift ödeme demek.

Özet
  • Agent eklemek, sisteme at-least-once bir çağırıcı eklemektir. Model belirsizlikte tekrar dener; bu bir hata değil, davranış. Tasarımın buna göre yapılması gerekir.
  • Tekrarın üç kaynağı var: zaman aşımı, anlaşılmayan hata, yarıda kalıp yeniden başlayan koşu. Üçü de aynı sonucu verir: aynı iş iki kez.
  • Anahtarı model üretmez, kod üretir. Doğal anahtardan hash: kayıt no + işlem türü + iş günü. Model anahtar uydurursa iki çağrıda iki anahtar olur ve koruma çalışmaz.
  • İkinci çağrı hata değil, ilk sonucun kendisini döndürür. Hata dönersen model üçüncü kez dener. “Zaten yapıldı, sonucu bu” cevabı döngüyü bitirir.
  • Geri alınamaz işler iki aşamalıdır: agent plan üretir, kuyruğa yazılır, onaydan sonra kod çalıştırır. Onay akışta durur; prompt’taki “önce sor” cümlesi güvence değildir.
  • Denetim log’u pazarlık konusu değil. Kim, hangi koşuda, hangi anahtarla, hangi argümanlarla. Bu olmadan çift onayı iki gün sonra bulamazsın.

Neden tekrar ediyor?

Mesaj kaç kez gelir yazısındaki mesele burada aynen geçerli: ağ üzerinden yapılan bir çağrının cevabı gelmediğinde, çağrının yapılmadığını bilemezsin. İki ihtimal vardır ve ikisi de aynı görünür: istek gitmedi, ya da istek gitti cevap dönmedi. Agent’ta bu klasik problemin üstüne bir katman daha biniyor: karar veren taraf bir model ve model, emin olmadığında tekrar denemeye meyilli.

Tekrarın kaynağıNasıl görünürSavunma
Zaman aşımıTool aslında çalıştı, cevap geç geldiIdempotency anahtarı + sunucuda tekil kayıt
Anlaşılmayan hata“500” gören model aynı çağrıyı tekrarlarNe yapacağını söyleyen hata mesajı
Modelin kararsızlığıAynı tool, aynı argüman, arka arkayaTekrar koruması: 2. çağrı önbellekten, 3. çağrı hata
Koşunun yeniden başlamasıTamamlanmış adım yeniden çalışırKoşu durumu kaydı: adım tamamlandıysa atla
Kullanıcı tekrarıAynı gece işi iki kez tetiklenirKoşu anahtarı: tarih + iş adı tekildir
Agent’a otonomi vermek, sisteme “bir daha denerim” diyen yeni bir çağırıcı eklemektir. Tekrarı engelleyemezsin; zararsız hâle getirirsin.

Sahadan: 32 saniyelik onay

O cumanın dökümü. Agent, gece biriken 6 çekim için “kural setine uyanları onayla” görevini çalıştırıyordu (test ortamı, gerçek para yok).

SaatOlay
16:19:41cekim_onayla(WD-24817) çağrıldı
16:20:11Agent tarafında 30 sn zaman aşımı; çağrı iptal edildi, model “hata” gördü
16:20:13Ödeme servisi işlemi tamamladı: onay kaydedildi (32 sn sürmüş)
16:20:14Model aynı tool’u aynı argümanla tekrar çağırdı
16:20:16İkinci onay kaydedildi. Kayıt iki kez onaylı
16:24Koşu “6 çekim onaylandı” diye bitti; aslında 5 çekim, biri iki kez

Burada modelin yaptığı şey yanlış değil: cevap gelmedi, tekrar denedi. Ben olsam ben de denerdim. Yanlış olan, aynı işin ikinci kez kabul edilmesiydi. Bunu bir modelin “dikkatli olması”na bırakmak, iki thread’li bir yarışı “dikkatli kod”a bırakmak gibi: bir süre işe yarar, sonra yaramaz.

Anahtarı kim üretir?

İlk düzeltme denememiz şuydu: tool şemasına idempotency_key alanı ekledik, model doldursun. İki koşuda iki farklı anahtar üretti — biri wd-24817-onay, diğeri WD24817_approve_2. Koruma çalışmadı; sadece veritabanında iki satır oldu.

Doğrusu: anahtar modele hiç gösterilmez. Orchestrator, işin doğal anahtarından üretir ve çağrıya kendisi ekler.

Anahtar uretimi (kod tarafinda)
# dogal anahtar: bu isi benzersiz yapan alanlar
def idempotency_key(tool_adi, argumanlar, is_gunu):
    dogal = "|".join([
        tool_adi,                    # cekim_onayla
        argumanlar["kayit_no"],      # WD-24817
        is_gunu.isoformat(),         # 2026-09-20
    ])
    return sha256(dogal).hexdigest()[:32]

# ayni is -> ayni anahtar; farkli is -> farkli anahtar
# model bu alani ne gorur ne de doldurur

İş günü neden var? Aynı kaydı bir hafta sonra kasten yeniden onaylamak isteyebiliriz; anahtar sonsuza kadar aynı kalırsa meşru ikinci işlem de engellenir. Pencereyi işin doğasına göre seç: bizde bir iş günü doğru cevaptı.

Sunucu tarafı: ikinci çağrıya ne dönmeli?

Anahtar tek başına bir şey yapmaz; onu kabul eden tarafın davranışı önemli. Teslim garantileri yazısındaki kuralın aynısı:

Tool tarafi
def cekim_onayla(kayit_no, _key):
    kayitli = idempotency_tablosu.bul(_key)
    if kayitli:
        # ONEMLI: hata degil, ilk sonucun kendisi
        return {**kayitli.sonuc, "tekrar": True}

    with transaction():
        # unique index: ayni anda gelen iki istekten biri duser
        idempotency_tablosu.ekle(_key, durum="calisiyor")
        sonuc = odeme_servisi.onayla(kayit_no)
        idempotency_tablosu.tamamla(_key, sonuc)
    return sonuc

İki ayrıntı acıyla öğrenildi:

  • İkinci çağrıya hata dönme. İlk denememizde 409 Conflict dönüyorduk; model bunu “olmadı” diye okuyup üçüncü kez denedi, sonra da kullanıcıya “onaylayamadım” dedi. Oysa onaylanmıştı. Doğru cevap ilk sonucun kendisidir; "tekrar": true alanı yalnızca log için.
  • Tekillik veritabanında olmalı. “Önce bak, yoksa ekle” kodu iki eş zamanlı çağrıda ikisini de geçirir. Unique index olmadan bu bir race condition’dır; paralel alt agent’lar varsa çok daha olasıdır.

Geri alınamaz işler: iki aşama

Idempotency, “iki kez yapılmasın”ı çözer. “Hiç yapılmamalıydı”yı çözmez. Para gönderme, mail atma, kayıt silme, dış sisteme çağrı — bunlar için tool’u ikiye böldük:

Plan ve uygulama
# 1. asama: agent yalnizca plan uretir (yan etki yok)
cekim_onayla_plan(kayit_no)
# -> {"kayit_no": "WD-24817", "tutar": 1250, "para_birimi": "USD",
#     "kural": "limit_alti", "anahtar": "a91f...", "durum": "onay_bekliyor"}

# 2. asama: onay sonrasi kod calistirir; model cagirmaz
# onay kurali akista:
#   tutar < 500 USD ve musteri KYC tam  -> otomatik
#   digerleri                            -> insan onayi

Plan bir kuyruğa yazılıyor; kuyruğu boşaltan şey agent değil, akış. Bunun üç faydası oldu:

  • Agent yanlış karar verirse bedel bir kuyruk satırı; geri almak için “sil” yeter.
  • Onay kuralı kodda olduğu için okunabiliyor ve test edilebiliyor. Prompt’taki “500 doların üstünde onay iste” cümlesi test edilemez.
  • Prompt injection ile agent’ı kandıran biri bile yalnızca kuyruğa satır yazdırabiliyor; parayı hareket ettiremiyor.
Prompt’a yazılan “önce onay al” bir güvence değil, bir temennidir. Onay akışta durur.

Geri alınamazlar listesi

Bir sayfa tuttuk; adı tam olarak bu. Her yan etkili tool listeye giriyor ve üç soruya cevap veriyor: geri alınabilir mi, kim onaylar, tekrarı ne yapar?

ToolGeri alınabilir mi?OnayTekrar edilirse
cekim_onaylaHayır (para çıktı)Tutar eşiği + insanAnahtar yakalar, ilk sonuç döner
musteri_notu_ekleEvet (not silinir)GerekmezAnahtar yakalar
kyc_reddetKısmen (müşteriye mail gitti)İnsanMail ikinci kez gitmez
rapor_mail_atHayır (mail geri gelmez)Kod: yalnızca iç alıcılarAnahtar yakalar
mutabakat_kapatEvetGerekmezAnahtar yakalar

Listenin asıl faydası tool eklerken çıktı: yeni bir yan etkili tool yazan kişi bu üç soruya cevap vermeden PR açamıyor. Soruların cevabı yoksa tool okuma tool’u olarak kalıyor.

Ne izlemeli?

  • Idempotency isabet sayısı. Kaç çağrı “zaten yapılmış” diye döndü? Sıfırsa koruma hiç sınanmamıştır; artıyorsa zaman aşımı eşiğine bak.
  • Yan etkili çağrı / koşu. Beklenenden fazlaysa model gereksiz yazma yapıyordur.
  • Zaman aşımı oranı ve tool süresi p99. Bizdeki olayın kök sebebi buydu: p99 31 saniye, eşik 30.
  • Onay kuyruğunda bekleme süresi. Uzuyorsa insan onayı darboğaz olmuştur; eşiği gözden geçir.
  • Otomatik onay oranı. Yükseliyorsa eşik gevşemiş olabilir; ayda bir bakılır.
  • Denetim log’u eksiksizliği. Her yan etkili çağrıda koşu kimliği, anahtar, argüman ve sonuç var mı?

Kontrol listesi

Agent’a yazma yetkisi vermeden önce
  • Hangi tool’lar yan etkili? Şemada işaretli mi?
  • Idempotency anahtarını kim üretiyor: kod mu, model mi?
  • Anahtarın doğal bileşenleri ne? Zaman penceresi ne kadar?
  • İkinci çağrıya ne dönüyor: hata mı, ilk sonuç mu?
  • Tekillik veritabanında mı, kodda mı?
  • Tool zaman aşımı, tool’un p99 süresinden büyük mü?
  • Geri alınamaz adımlar plan/uygulama olarak ikiye bölündü mü?
  • Onay kuralı kodda mı, prompt’ta mı?
  • Yarıda kalan koşu yeniden başlarsa tamamlanmış adımları atlıyor mu?
  • Denetim log’unda koşu kimliği, anahtar ve argümanlar var mı?
  • Çift işlem olursa bunu hangi rapor yakalar? Ne kadar sürede?

Sonuç

O cuma agent bir şeyi yanlış yapmadı: cevap gelmedi, tekrar denedi. Sistem yanlıştı, çünkü aynı işi ikinci kez kabul etti. Bu yazının tamamı aslında eski bir konunun yeni bir kılığı: ağ güvenilmez, çağrılar tekrarlanır, tekillik veritabanında durur. Yeni olan tek şey, tekrar eden tarafın artık bir model olması ve ne zaman tekrar edeceğini önceden bilememen.

Düzelttikten sonraki hâl: anahtarı kod üretiyor, ikinci çağrı ilk sonucu döndürüyor, para hareketi olan adım plan–onay–uygula şeklinde üçe ayrılmış durumda. Agent hâlâ tekrar deniyor — log’da ayda 3–4 kez “tekrar: true” görüyoruz — ama artık bu bir olay değil, bir satır.

Akılda kalacak cümle: agent’ın otonomisi, tool’larının idempotency’si kadardır. İkincisi yoksa birincisi bir özellik değil, açık bir risktir.