Query Cache mi Abonelik mi: State'i Kim Üretiyor?
State'i bir worker, agent ya da başka bir kullanıcı üretiyorsa intent gönderin ve state'e abone olun. Request handler üretiyorsa query cache'te kalın.
Query cache, bir yazma isteğine dönen HTTP yanıtını cevap olarak kabul eder. Mutation tamamlanır, ilgili query’ler invalidate edilir ve refetch yeni state’i getirir. Bu sıra ancak request handler state’i yanıt dönmeden önce ürettiğinde doğrudur. State’i bir worker, bir agent ya da başka bir kullanıcı üretiyorsa yanıt yalnızca isteğin alındığını bildirir. Ardından gelen refetch de çoğu zaman henüz değişmemiş veriyi okur.
Bu ikinci tür state için istemci bir intent göndermeli ve onu bekleyen bir katman (overlay) olarak göstermelidir. Cevap, yetkili state’e açılan ve kaldığı yerden devam edebilen bir abonelikten gelir. Yetkili state, sunucunun kabul ettiği son hâldir. Handler’ın ürettiği state için doğru varsayılan hâlâ query cache ve başarı sonrası invalidation’dır. Belirleyici soru, state değişikliğini kimin ürettiğidir. Ekipler bu soruyu özellik bazında yanıtlar, çünkü aboneliğin de yetkilendirme ve operasyon tarafında bir maliyeti vardır. 2027 için beklentim, abonelik gerektiren türün çoğu üründe istisna olmaktan çıkması. Agent iş yükleri bu durumu yaygınlaştırıyor, istemci tarafı collection’lar da geçişi ucuzlatıyor.
Onay ile Cevap Arasındaki Boşluk#
Asenkron bir backend’de yazma isteği, iş kuyruğa girer girmez 202 döner. Mutation fonksiyonu bu 202 ile sonuçlandığı için onSuccess çalışır ve invalidation, çoğu zaman worker henüz hiçbir şey yazmadan refetch’i başlatır. Ekranda eski state, yanında da bir başarı mesajı görünür.
Ekipler bunu genellikle her özellikte ayrı bir yamayla çözer: bir özelliğe refetchInterval, bir tamamlandı bayrağı ve bir durma koşulu eklenir, sıradaki özellik de bunların kendi kopyasını alır. Bu döngülerin hiçbiri, aynı satırları başka bir kullanıcının, bir arka plan işinin ya da bir agent’ın değiştirdiğini fark etmez. Request/response cache’i bir yazmayı ancak bir şey refetch tetiklediğinde öğrenir. Sonunda istemci aynı verinin iki sürümünü tutar: beklediği yanıtı ve daha sonra başka bir yoldan gelen state’i.
Başarı Sonrası Invalidation’ın Hâlâ Geçerli Olduğu Durumlar#
Bu boşluk yalnızca state’i handler dışında bir şey yazdığında açılır. Handler satırı çoktan yazmışsa request/response ile bir query cache doğru tasarımdır. TanStack Query rehberi bunun standart biçimini gösterir: ilgili query’ler mutation’ın onSuccess callback’inde invalidate edilir (yeni sekmede açılır). Yanıt tamamlanmış bir olguyu doğrular, refetch onu okur ve her okuma aynı endpoint’ten ve aynı yetkilendirme kontrollerinden geçer. Formlar, ayar sayfaları ve CRUD ekranlarının çoğu bu tarife uyar ve oldukları gibi kalmalıdır.
Daha ileri gitmeye yönelik en güçlü itiraz, alternatifi geliştiren firmalardan gelir. PowerSync’in mühendislik blogu (yeni sekmede açılır), sync engine’lerin mevcut backend’lerle entegrasyonunun zor olduğunu kabul eder. Birçoğunun kodun kendi varsayımlarına göre yeniden yazılmasını gerektirdiğini ve ekibi tek bir veritabanına ve sorgu katmanına bağlayabildiğini de ekler. Local-first bir veritabanı olan RxDB, local-first yaklaşımın olumsuz yanlarını (yeni sekmede açılır) tek bir sayfada toplar. Listede çakışma çözümü, yetkiler, depolama sınırları ve istemci tarafındaki veri migration’ları da var. Tek istediği ekranın daha hızlı görünmesi olan ekip, bunun büyük kısmını mevcut cache üzerindeki bir optimistic update ile elde eder. Bu altyapının hiçbirine ihtiyaç duymaz. Handler’ın ürettiği state için bu görüşe katılıyorum.
İsteği gönderip sonucu polling ile sormak da aynı özenle değerlendirilmelidir. Tek bir ekranın beklediği tek bir uzun iş için 202 yanıtı, bir durum endpoint’i ve makul bir polling aralığı yeterlidir. MCP Tasks uzantısı (yeni sekmede açılır) aynı tercihi protokol düzeyinde yapar: “Polling is the default.” (Varsayılan polling’dir.) Polling ile okunan bir durum kaydı da yetkili state’tir. Bu, aboneliğin en basit biçimidir: yalnızca düz HTTP gerektirir ve açık bağlantı tutmaz.
Uçtan Uca Bir Intent#
Her özellikteki yamayı gereksiz kılan sözleşme küçüktür. Yazma tarafı yalnızca bir alındı onayı döndürür. HTTP, bir 202 yanıtının daha fazlasını taşımasına izin verir: işin güncel durumu ya da bir durum kaynağına bağlantı gibi. Sonucu bu yanıtın dışında tutmak, istemciye cevabı okuyacağı tek bir yer bırakan bir tasarım tercihidir. Okuma tarafı bir snapshot’tan başlar ve ondan sonraki her event’e abone olur.
// Tasarım gereği yazma tarafı yalnızca bir alındı onayı döndürür.
interface IntentAck {
intentId: string; // istemcide üretilir, idempotency key olarak da kullanılır
acceptedAt: string;
}
type IntentOutcome = "completed" | "failed" | "cancelled";
interface StateEvent<T> {
offset: string;
data: T; // bu event'ten sonraki yetkili state
causationId?: string; // bu event'e yol açan intent
outcome?: IntentOutcome; // yalnızca o intent'i sonuçlandıran event'te dolu
}
// Snapshot, offset'ine kadarki bütün event'leri içerir.
interface Snapshot<T> {
data: T;
offset: string;
}
interface StateSubscription<T> {
snapshot(): Promise<Snapshot<T>>;
// Offset'ten sonraki event'leri iletir, stream canlı olunca resolve olur.
subscribeFrom(
offset: string,
listener: (event: StateEvent<T>) => void,
): Promise<() => void>;
}
Örnek, tek bir intent’i bu sözleşme boyunca izler. Kullanıcı profil adını değiştirir, bir worker da yeni adı doğruladıktan sonra profili yazar.
interface Profile {
displayName: string;
}
const pending = new Map<string, Partial<Profile>>();
let current: Profile;
let lastOffset: string;
// Yetkili state ve hâlâ süren bütün intent'ler.
const view = (): Profile => Object.assign({ ...current }, ...pending.values());
async function watchProfile(
sub: StateSubscription<Profile>,
render: (profile: Profile) => void,
onFailed: (intentId: string) => void,
): Promise<() => void> {
const snap = await sub.snapshot();
current = snap.data;
lastOffset = snap.offset;
render(view());
return sub.subscribeFrom(lastOffset, (event) => {
current = event.data;
lastOffset = event.offset; // yeniden bağlanma buradan devam eder
if (event.causationId && event.outcome) {
pending.delete(event.causationId); // katmanı yalnızca sonuçlandıran event kaldırır
if (event.outcome === "failed") onFailed(event.causationId);
}
render(view());
});
}
async function rename(
displayName: string,
render: (profile: Profile) => void,
): Promise<IntentAck> {
const intentId = crypto.randomUUID();
pending.set(intentId, { displayName });
render(view());
// Ağ hatasında aynı intentId ile yeniden dene: sunucu tekilleştirir.
const res = await fetch("/profile/intents", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ intentId, displayName }),
});
if (res.status !== 202) {
pending.delete(intentId); // kuyruğa girmeden reddedildi
render(view());
throw new Error("rename rejected");
}
// 202 kabul demektir, bitti demek değildir: katman kalır.
return res.json();
}
Arayüz, yetkili state’i ve hâlâ süren bütün intent’leri birlikte çizer. 202 ekranda hiçbir şeyi değiştirmez. Bekleyen katmanı yalnızca işi sonuçlandıran bir event (tamamlanma, başarısızlık ya da iptal) kaldırır, başarısızlık da o intent için ayrıca bir hata bildirir. Intent ID nadiren domain satırında durur, çünkü bir profil güncellemesi yeni profili saklar, güncellemeyi isteyen isteği saklamaz. Bu yüzden ID, işin ürettiği event’lerde causationId olarak ya da ayrı bir intent durum kaydında taşınır. Tek bir intent birden çok event üretebilir, bu yüzden katmanı yalnızca sonuçlandıran event bitirir. ID’yi istemci ürettiği için ağ hatasından sonra yeniden denemek güvenlidir: sunucu yazmayı tekilleştirir.
Tek bir işin taşıma ayrıntıları, yani SSE, yeniden deneme ve idempotency, Web ve Mobil Uygulamalarda Uzun Süren API İsteklerini Yönetmek yazısında anlatılıyor. Orada istemci işe abone olur. Aynı satırları birden çok ekran gösterdiğinde daha yararlı abonelik hedefi, işin değiştirdiği veridir. Bekleyen ve başarısız durumların ekrandaki karşılığı ise Optimistic UI vs Decoupled Akış yazısında ele alınıyor. Intent’in bir command, state değişikliğinin ise bir event olduğu mesaj tarafı Bounded Context Sınırında Command mı Event mi yazısında anlatılıyor.
Backend’in Vermesi Gereken Garantiler#
İstemci kodu, yalnızca backend’in verebileceği garantilere dayanır. İlk ikisi olmadan, snapshot ile abonelik arasına düşen bir event kaybolur.
- Snapshot ile event log’u, snapshot’ın offset’inde tutarlıdır.
- Offset exclusive’dir: snapshot o noktaya kadarki bütün event’leri içerir, abonelik yalnızca sonrakileri iletir.
- Offset’ler sıralıdır ve yalnızca tek bir stream ya da partition içinde anlam taşır.
- Log, istemci bağlanırken ya da yeniden bağlanırken aradaki boşluğu replay edebilecek kadar uzun süre event saklar.
- Bir intent’in yol açtığı her event onun ID’sini
causationIdolarak taşır ve sonucu tam olarak bir sonuçlandırıcı event taşır. - Aynı intent ID ile yeniden gönderilen bir yazma tekilleştirilir.
Agent İş Yükleri ve Kaldığı Yerden Süren İşler#
Agent özellikleri, state’i worker’ın ürettiği durumu sıradanlaştırır. Bir üretim ya da araç çalıştırması dakikalar sürebilir, yarı yolda kullanıcıdan girdi isteyebilir ve sayfa yenilendiğinde kaybolmamalıdır. Frontend açısından bu, bir handle tutmak ve state’i izlemek demektir. Agent’ların altındaki protokoller, iş bitene kadar tek bir bağlantıyı açık tutmanın yanlış sözleşme olduğu konusunda hemfikirdir ve teslim biçiminde ayrışır. Tasks’ta istemci varsayılan olarak bir durum kaydını belirli aralıklarla sorgular. Durable Streams (yeni sekmede açılır)’te ise istemci, offset ile adreslenen bir stream’i long-poll ya da SSE ile izler.
MCP Tasks uzantısının sunucular için tarif ettiği davranış şudur: “return a durable handle instead of blocking” (bloklamak yerine kalıcı bir handle döndürmek). Bir task working, input_required, completed, failed ve cancelled durumlarından geçer. Tasks isteğe bağlı bir uzantıdır: istemcinin de sunucunun da onu bildirmesi gerekir ve host’ların desteği farklılık gösterir. Bloklamama gerekçeleri tarayıcı için de geçerlidir. Açık tutulacak uzun ömürlü bir bağlantı yoktur ve kalıcı ID, çökme sonrasında devam etmeyi sağlar. İlerleme görünür ve iş sürerken girdi verilebilir.
Electric’in Durable Streams protokolü açık kaynaklıdır. Protokol şu gözlemden yola çıkar: SSE ve WebSocket bağlantıları geçicidir, bağlantı koptuğunda aktarım hâlindeki veri kaybolur. Durable Streams düz HTTP üzerinden bir offset’ten devam eder. Duyurusu, gerekçelerden biri olarak AI token stream’lerini sayar: kopan stream kaldığı yerden sürer ve üretim baştan başlamaz.
AI SDK dokümantasyonu, hangi kütüphane kullanılırsa kullanılsın, kaldığı yerden devam etmenin bir uygulamadan ne istediğini gösteren iyi bir birincil kaynaktır. resume açıkken useChat mount sırasında etkin bir stream olup olmadığına bakar ve varsa ona yeniden bağlanır (yeni sekmede açılır). Aynı sayfa, bunun mesajlar ve etkin stream’ler için kalıcı depolama gerektirdiğini belirtir. Böyle bir kurulumda istemci tarafındaki abort’un bağlantı kopması sayıldığını da açıklar. Bu nedenle üretimi durdurmak için sunucuda ayrı bir endpoint gerekir.
Bir ürünün ana etkileşimi böyle çalışmaya başladığında, aynı sözleşmenin dışa aktarma, içe aktarma ve onay akışlarına da yayılmasını bekliyorum.
Bekleyen Katman ve İstemci Tarafı Collection’lar#
Intent tarafı için artık elle yazılmış state gerekmiyor. React 19 bekleyen katmanı bir hook olarak sunar ve useOptimistic referansı (yeni sekmede açılır) onu net biçimde tanımlar: “Optimistic state only renders while an Action is in progress, otherwise value is rendered.” (Optimistic state yalnızca bir Action sürerken render edilir, diğer durumlarda value render edilir.) Devir anını da şöyle anlatır: “The optimistic and real state converge in the same render when the Transition completes.” (Transition tamamlandığında optimistic state ile gerçek state aynı render’da birleşir.) Convex de kendi optimistic update’lerini (yeni sekmede açılır) “temporary, local changes to your query results” (query sonuçlarınızdaki geçici, yerel değişiklikler) olarak tarif eder: mutation tamamlandığında bunlar geri alınır ve gerçek değeri reaktif query getirir.
İkisi de yetkili state’in üzerinde duran ve bu state güncellendiğinde atılan bekleyen bir katmanı anlatır. React hook’unun sınırı ömrüdür. Bekleyen katman, Action’ın promise’i sonuçlandığında biter ve asenkron bir backend’de bu an, worker yazmadan önce gelen 202’dir. Bu durumda ya Action aboneliği bekler ya da örnekteki pending map’i gibi bir store, sonuçlandırıcı event gelene kadar katmanı tutar. Tek başına bir useOptimistic çağrısı bu eşleştirmeyi uygulamaya bırakır, sync destekli bir collection ise eşleştirmeyi kendisi yapar.
Böyle bir collection için eskiden tam bir sync engine gerekiyordu, TanStack DB ise bu giriş maliyetini düşürüyor. Kütüphane 1.0 sürüm adayı (RC) aşamasında ve RC duyurusu (yeni sekmede açılır) şunu söylüyor: “the API is powerful, production-ready, and stable” (API güçlü, production’a hazır ve kararlı). Sürüm adayı aşamasındaki bir pakette kararlı bir API, pilot bir özellik için makul, bütün bir ürün için ise daha zayıf bir dayanaktır. Geçiş yolu kademelidir. Bir collection, TanStack Query üzerinden mevcut REST endpoint’leriyle beslenebilir ve üzerine live query’ler ile optimistic yazmalar gelir. Yerel kalıcılık, çevrimdışı mutation’lar ve gerçek zamanlı sync sonradan eklenebilir. Dolayısıyla bir ekip, backend’ini değiştirmeden tek bir özelliği bir collection’a taşıyabilir. Bu ilk adım hâlâ refetch’e dayanır. Özellik, collection’ı bir sync kaynağı beslediğinde aboneliğe geçer.
Tam sync engine’ler de olgunlaştı, ancak çekinceler sürüyor. Rocicorp’un durum sayfası (yeni sekmede açılır) Zero’yu genel kullanıma açık (GA) olarak listeler. InfoQ’nun 1.0 sürümüne dair haberi (yeni sekmede açılır) ise topluluğun tepkisini “positive on developer experience but more cautious on production readiness” (geliştirici deneyimi konusunda olumlu, production’a hazır olma konusunda daha temkinli) sözleriyle aktarır. Haberin derlediği çekinceler arasında Postgres dışındaki veritabanlarının desteklenmemesi, sync edilmeyen view’lar, bundle boyutu ve sunucu tarafı render eksikliği var.
Özellik Bazında Karar#
Karar state’i kimin ürettiğinden başlar, araç seçimine ancak ondan sonra gelir.
Varsayılanın altındaki her dal bir maliyet ekler. Özelliğe özel bir stream için kaldığı yerden devam edebilen bir endpoint, her yazmada bir intent ID’si ve hâlâ süren stream’ler için depolama gerekir. Stream hiç son event göndermezse bu yaklaşım tıkanır, çünkü bekleyen katman sonsuza kadar bekler. İstemci tarafı bir collection’ın işletmesi, mevcut API ile beslendiği sürece ucuzdur. Collection yine de istemcide ikinci bir veri katmanıdır ve bekleyen katmanı ancak sunucu yazması istemcinin eşleştirebileceği bir ID, örneğin intent ID’si ya da bir transaction ID’si taşıyorsa eşleştirebilir.
Sync engine bir replikasyon katmanı ekler ve yetkilendirmenin yerini değiştirir. Okumalar artık endpoint handler’larından geçmez. Bu yüzden erişim kuralları verinin çoğaltıldığı yerde ifade edilmelidir. Electric’in auth rehberi (yeni sekmede açılır) iki desen önerir: shape isteklerini bir proxy’de yetkilendirmek ya da bir gatekeeper endpoint’i üzerinden shape’e özel token vermek. Yetkinin geri alınması da plan ister. Erişim kaldırıldığında açık bir abonelik kapatılmalı ya da kapsamı daraltılmalıdır. Aksi hâlde kullanıcının artık okuyamayacağı satırları iletmeye devam eder. Çevrimdışı destek bunu zorlaştırır. Bir cihaz, erişimini kaybettiğini yeniden bağlanana kadar öğrenemez. Yerelde saklanan satırlara ne olacağı, çevrimdışı yazma açılmadan önce kararlaştırılmalıdır. AWS’de abonelik tarafını AppSync de sağlayabilir: AppSync Subscription’larını AppSync Dışından Tetiklemek yazısı, yetkili state’in request yolunun dışından istemcilere nasıl iletildiğini gösteriyor.
Geçişi Ölçmek#
Geçişin işe yarayıp yaramadığını dört sayı gösterir:
- Asenkron yazmalara bağlı polling döngüleri ve elle yapılan
invalidateQueriesçağrıları; bunlar azalmalıdır. - Intent’in gönderilmesinden bekleyen katmanın kalkmasına kadar geçen sürenin p50 ve p95 değerleri: kullanıcının tamamlanma olarak algıladığı gecikme.
- Geri alınan ya da hiç eşleşmeyen katmanların oranı: doğruluk göstergesi.
- Yeniden bağlanma sonrasında stream’e devam etme başarısı: taşıma göstergesi.
Yarım Kalan Geçişte Yapılan Hatalar#
202, isteğin kabul edildiği anlamına gelir. O anda başarı gösteren bir arayüz, henüz var olmayan bir sonucu bildirir. Bekleyen katman, abonelik o intent ID’si için işi sonuçlandıran bir event gösterene kadar kalmalıdır.
Biten bir iş akışı, biten bir sonuç anlamına gelmez. Tüketilen bir kuyruk mesajı ya da tamamlanan bir state machine çalıştırması, adımların çalıştığını gösterir. İş state’i ise hâlâ başka bir sistemi bekliyor olabilir. İstemci iş state’ini izlemelidir, çünkü kullanıcının değiştirmek istediği şey odur.
State’i asenkron yazan bir backend karşısında yerinde bırakılan başarı sonrası invalidation, refetch’i erken başlatmaya devam eder. Invalidation’ın yeri, sonucu taşıyan state event’idir.
Yalnızca istemci isteğini abort eden bir durdur düğmesi, kaldığı yerden devam edebilen bir stream’i durdurmaz. Sunucu bunu bağlantı kopması olarak görür ve üretim sürer. İptal için ayrı bir endpoint gerekir. Bu endpoint intent ID’sini alır ve idempotent çalışır. İptalin sonucu da tamamlanma gibi abone olunan state üzerinden gelir.
Tek bir asenkron özellik için benimsenen sync engine, beraberinde bir replikasyon katmanı ve yeni bir yetkilendirme modeli getirir. Oysa bu sorunu bir stream endpoint’i zaten çözer. Sync engine, birkaç özellik aynı abone olunan veriyi paylaştığında ya da çevrimdışı yazma bir ürün gereksinimi olduğunda makul hâle gelir.
İstemci koduna taşınan satır ve filtre kararları yetkilendirmeyi zayıflatır. Bir kullanıcının hangi tabloları ve hangi satırları sync edebileceğine sunucuda, proxy’de ya da token’da karar verilir.
Nereden Başlamalı#
202’nin ardından çalışan bütün onSuccess invalidation’larını listeleyin. Her biri, state’ini bir worker’ın, bir agent’ın ya da başka bir kullanıcının ürettiği bir özelliği işaret eder. En çok polling kodu taşıyan özelliği önce taşıyın, handler’ın ürettiği ekranları ise query cache’te bırakın.
Kaynaklar#
- TanStack Query: Invalidations from Mutations (yeni sekmede açılır) - Dokümante edilmiş varsayılan: ilgili query’leri mutation’ın
onSuccesscallback’inde invalidate etmek. - PowerSync: Unleashing the power of sync (yeni sekmede açılır) - Bir sync firmasının kendi kaleminden sync engine’lerin zayıf yanları, mevcut backend’lerle entegrasyon zorluğu dahil.
- RxDB: Downsides of Local First (yeni sekmede açılır) - Local-first bir veritabanı firmasının maliyet listesi: çakışmalar, yetkiler, depolama sınırları ve istemci migration’ları.
- MCP Tasks extension (yeni sekmede açılır) - Uzun süren işler için yaşam döngüsü durumları olan kalıcı task handle’ları, varsayılan olarak polling ile.
- Announcing Durable Streams (yeni sekmede açılır) - Electric’in offset’ten devam edebilen HTTP stream’leri için açık protokolü ve AI token stream’lerinden gelen gerekçesi.
- AI SDK: Chatbot Resume Streams (yeni sekmede açılır) - Etkin bir üretime mount sırasında yeniden bağlanma, bunun gerektirdiği kalıcı depolama ve istemci abort’unun neden bağlantı kopması sayıldığı.
- React useOptimistic reference (yeni sekmede açılır) - Optimistic state’in bir Action sürerken nasıl render edildiği ve Transition tamamlanınca gerçek state ile nasıl birleştiği.
- Convex optimistic updates (yeni sekmede açılır) - Reaktif query’ler üzerinde geçici yerel değişiklikler olarak optimistic update’ler ve mutation tamamlanınca geri alınmaları.
- TanStack DB 1.0 RC1 (yeni sekmede açılır) - Collection’ları, live query’leri, optimistic yazmaları ve TanStack Query’den geçiş yolunu anlatan sürüm adayı duyurusu.
- Zero project status (yeni sekmede açılır) - Rocicorp’un Zero’nun genel kullanıma açık olduğunu belirttiği sayfa ve yol haritası.
- InfoQ: Zero reaches version 1 (yeni sekmede açılır) - Zero 1.0 sürümünün haberi, topluluğun production’a hazır olma konusundaki çekinceleri ve aktarılan kısıtlar.
- Electric auth guide (yeni sekmede açılır) - Sync için önerilen iki yetkilendirme deseni: proxy auth ve gatekeeper auth.
İlgili yazılar
Kinesis tek isim altında dört ayrı AWS servisidir. Dördünün ayrımı, altındaki Data Streams shard motoru, maliyet şekli ve ne zaman başka bir şey seçmeli.
aws · real-time · event-driven +1
Async backend'le çalışan tasarımcılar için pragmatik rehber: üç etkileşim şekli, hangisi ne zaman, ve karşı durmanız gereken dört anti-pattern.
event-driven · state-management · design-patterns +2
Bounded context sınırını hangi mesajın geçeceğine karar verin: command şemasının sahibi alıcı, event şemasının sahibi yayıncıdır. Varsayılan integration event.
domain-driven-design · event-driven · messaging +4
Skill, model görevi açıklamasıyla eşleştirdiğinde ya da siz adını yazdığınızda yüklenir. SKILL.md prosedürü taşısın, proje bilgilerini repodan okusun.
ai-agents · ai-tools · claude-code +3
Planın işi, ajanın aksi halde sessizce vereceği kararları önceden vermek. Bu işi yapan doküman iskeleti, yayınlanmış tek bir değişiklik üzerinden adım adım.
claude-code · ai-agents · documentation +2