@sedatdemirAra

Agentic coding: plan, subagent ve review döngüsü

Geçen ay bir side project'te 14 endpoint'lik bir REST API'yi Claude Code ile yazdırdım. İlk denememde ajana "şu API'yi yaz" dedim, tek seferde 800 satır kod üretti. Kodun yarısı çalışıyordu, diğer yarısı hallüsinasyon ürünü API çağrıları içeriyordu. İkinci denememde aynı projeyi plan → subagent → review döngüsüyle böldüm. Sonuç: 14 endpoint'in 13'ü ilk çalıştırmada testleri geçti, toplam token maliyeti ilk denemenin üçte biri oldu.

Bu yazıda o döngüyü nasıl kurduğumu, hangi noktalarda TDD kapısı koyduğumu ve ne zaman ajana güvenip ne zaman güvenmediğimi anlatıyorum.

Döngünün üç aşaması

Agentic coding dediğimiz şey tek bir büyük prompt değil, birbirini besleyen üç aşamalı bir iş akışı.

Birinci aşama plan: ajana kodu yazdırmadan önce, ne yapacağını düz metin olarak çıkartmasını istiyorsun. Dosya listesi, fonksiyon imzaları, bağımlılıklar. İkinci aşama subagent: planın her maddesini ayrı bir alt görev olarak, dar kapsamlı bir context window'da çalıştırıyorsun. Üçüncü aşama review: üretilen kodu hem otomatik testlerle hem de ikinci bir ajan çağrısıyla denetliyorsun.

Bu üç aşamayı sırayla, her birinin neden gerekli olduğunu göstererek açacağım.

Plan aşaması: ajana ne yapacağını söyletmek

Çoğu geliştirici ajana doğrudan "X'i yaz" diyor. Sorun şu: büyük bir görevde ajanın context window'u dolduğunda, başta aldığı kararları unutuyor. Plan aşaması bunu önlüyor çünkü ajan önce kısa bir belge üretiyor, sonra o belge her subagent çağrısına parametre olarak giriyor.

Claude Code'da bunu CLAUDE.md dosyasına bir kural olarak ekliyorum:

# CLAUDE.md

## Workflow kuralı
Her yeni görevde önce PLAN.md dosyası oluştur.
PLAN.md şunları içermeli:
- Etkilenen dosyaların listesi
- Her dosya için public fonksiyon imzaları (TypeScript tipiyle)
- Dış bağımlılık varsa paket adı ve sürümü
- Her adım için kabul kriteri (test senaryosu)

Kod yazmaya PLAN.md onaylanmadan başlama.

Bu kuralı Claude Code custom skills yazısında anlattığım gibi proje kök dizinine koyuyorum. Ajan artık her görevde önce plan çıkarıyor.

Örnek bir plan çıktısı:

# PLAN.md - Kullanıcı modülü

## Dosyalar
1. src/routes/users.ts - GET /users, POST /users, GET /users/:id
2. src/schemas/user.ts - Zod şemaları (createUserSchema, userResponseSchema)
3. src/db/queries/users.ts - Drizzle sorguları
4. tests/users.test.ts - Her endpoint için en az 2 test

## Fonksiyon imzaları
- getUserById(id: string): Promise<User | null>
- createUser(data: CreateUserInput): Promise<User>
- listUsers(opts: { limit: number; offset: number }): Promise<User[]>

## Kabul kriterleri
- POST /users boş body ile 400 döner
- POST /users geçerli body ile 201 döner ve Location header içerir
- GET /users/:id olmayan id ile 404 döner

Plan aşamasının token maliyeti düşük. 14 endpoint'lik projemde toplam plan üretimi yaklaşık 12.000 input token, 3.000 output token tuttu (Claude Sonnet 4 fiyatlarıyla yaklaşık 0.05 USD).

Subagent aşaması: dar kapsamlı görevler

Planı aldıktan sonra her maddeyi ayrı bir ajan çağrısı olarak çalıştırıyorum. Bunun iki nedeni var: context window kirlenmesini önlemek ve hata yüzeyini daraltmak.

Claude Code'un --print modunu veya Claude Agent SDK'yı kullanarak bunu otomatikleştirebilirsiniz. Ben basit bir shell script kullandım:

#!/bin/bash
# run-subtasks.sh

PLAN="PLAN.md"
SUBTASKS=$(grep -E '^[0-9]+\.' "$PLAN" | sed 's/^[0-9]*\. //')

while IFS= read -r task; do
  echo "==> Subtask: $task"
  claude --print "PLAN.md dosyasını oku. Sadece şu adımı uygula: $task. Diğer adımlara dokunma. Bitince testleri çalıştır."
  
  # Review kapısı: testler geçmezse dur
  if ! npm test -- --bail 2>/dev/null; then
    echo "FAIL: $task - Manuel müdahale gerekiyor"
    exit 1
  fi
done <<< "$SUBTASKS"

Burada her subagent yalnızca kendi dosyasını görüyor ve düzenliyor. Planın geri kalanı context'e "oku ama dokunma" olarak giriyor.

Subagent boyutu ne kadar küçük olmalı?

Deneyimlerime göre tek bir subagent görevi 200 satırdan fazla kod üretmemeli. 200 satırı aştığında hallüsinasyon oranı belirgin şekilde artıyor. 14 endpoint'lik projemde ortalama subagent çıktısı 85 satır oldu.

TDD kapıları: ajanı testlerle sınırlamak

Plan aşamasında her adım için kabul kriteri yazdım. Subagent aşamasında bu kriterleri önce test olarak yazdırıyorum, sonra implementasyonu yazdırıyorum. Klasik TDD, ama burada testleri de ajan yazıyor.

Bu noktada bir güven sorunu var: ajan kendi testini yazıyorsa, testi implementasyona uydurmaz mı?

Uyduruyor. Bunu gördüm. Çözüm olarak iki kural koydum:

  1. Testler, implementasyondan önce ayrı bir subagent çağrısında yazılır
  2. Test yazan subagent, implementasyon dosyasını görmez
# Önce testi yazdır
claude --print "PLAN.md'deki kabul kriterlerine göre tests/users.test.ts dosyasını yaz. \
  src/ dizinindeki dosyaları okuma, sadece PLAN.md'deki imzaları kullan."

# Testlerin derlenmesini kontrol et (fail etmeleri bekleniyor)
npx vitest run --reporter=verbose 2>&1 | head -20

# Sonra implementasyonu yazdır
claude --print "tests/users.test.ts dosyasını oku. Bu testleri geçirecek implementasyonu yaz. \
  PLAN.md'deki dosya yapısını takip et."

Bu yaklaşımla testler implementasyondan bağımsız kalıyor. Vitest burada iyi çalışıyor çünkü --bail flag'iyle ilk hatada durmasını sağlayabiliyorum, böylece ajan gereksiz token harcamıyor.

Review aşaması: ikinci bir gözün maliyeti

Subagent kodu yazıp testler geçtikten sonra, üçüncü aşamada ayrı bir ajan çağrısı review yapıyor. Bu çağrıda ajan kodu yazmıyor, sadece okuyor ve sorunları raporluyor.

claude --print "src/routes/users.ts ve tests/users.test.ts dosyalarını oku. \
  Şu kontrol listesini uygula:
  - SQL injection riski var mı?
  - Error handling eksik mi?
  - Tip uyumsuzluğu var mı?
  - Test coverage eksik edge case var mı?
  Sorun yoksa 'LGTM' yaz. Sorun varsa dosya:satır formatında listele."

Review ajanı sorun bulursa, o sorunu düzeltmek için yeni bir subagent çağrısı yapıyorum. Bu döngü genellikle en fazla iki tur sürüyor.

Review aşamasının token maliyeti düşük çünkü kod yazılmıyor, sadece okunuyor. 14 endpoint'lik projemde tüm review çağrıları toplam 0.08 USD tuttu.

Gerçek maliyet tablosu

Projemde (14 endpoint, Hono.js + Drizzle ORM + D1 stack, bu yazıdaki yapıya benzer) Claude Sonnet 4 ile toplam maliyet:

Aşama Input token Output token Maliyet (USD)
Plan 12.400 3.100 0.05
Test yazımı (14 subtask) 48.000 18.500 0.22
İmplementasyon (14 subtask) 62.000 31.000 0.34
Review (14 + 4 düzeltme) 38.000 8.200 0.14
Toplam 160.400 60.800 0.75

Aynı projeyi ilk denemede (tek büyük prompt, döngüsüz) 2.1 USD'ye yazdırmıştım ve sonucun yarısını elle düzeltmem gerekmişti. Döngülü yaklaşım hem ucuz hem de daha az elle müdahale gerektirdi.

Ne zaman güvenilir, ne zaman güvenilmez?

Üç ayda bu döngüyü farklı projelerde kullandıktan sonra net gördüğüm sınırlar var.

Ajan bu döngüde iyi çalışıyor: CRUD endpoint'leri, şema doğrulama (Zod v4 veya ArkType ile), veritabanı sorguları (Drizzle ORM gibi bilinen ORM'ler), test dosyaları.

Ajan bu döngüde kötü çalışıyor: karmaşık state makineleri, race condition içeren concurrent kod, framework'ün belgelenmemiş davranışlarına dayanan konfigürasyonlar, performans optimizasyonu gerektiren sorgular.

Sınırı belirleyen şey ajanın eğitim verisinde o pattern'in ne kadar sık geçtiği. CRUD endpoint kalıpları milyonlarca kez geçmiş, o yüzden ajan bunları güvenilir şekilde üretiyor. Ama projenize özel bir iş kuralını doğru implemente etmesi için o kuralı planın içine açık açık yazmanız gerekiyor.

Döngüyü kıran hatalar ve çözümleri

Üç hata kalıbıyla sık karşılaştım.

Birincisi: subagent, planın dışına çıkıp başka dosyaları düzenliyor. Çözüm olarak --allowedTools parametresiyle dosya yazma iznini tek bir dizinle sınırlandırıyorum.

İkincisi: test yazan subagent, var olmayan bir fonksiyonu import ediyor çünkü plan imzasını yanlış okumuş. Çözüm olarak planı TypeScript interface dosyası olarak yazdırıyorum, böylece ajan serbest metin yerine derlenebilir bir sözleşme görüyor:

// contracts/users.ts
export interface UserQueries {
  getUserById(id: string): Promise<User | null>;
  createUser(data: CreateUserInput): Promise<User>;
  listUsers(opts: { limit: number; offset: number }): Promise<User[]>;
}

Üçüncüsü: review ajanı her şeye "LGTM" diyor, gerçek sorunları yakalamıyor. Bu genellikle kontrol listesi çok genel olduğunda oluyor. "Güvenlik sorunu var mı?" yerine "req.params.id değeri doğrudan SQL sorgusuna giriyor mu? parseInt veya UUID validasyonu yapılıyor mu?" gibi spesifik sorular sormak review kalitesini belirgin şekilde artırıyor.

MCP ile döngüyü otomatikleştirmek

Bu döngüyü tamamen otomatik çalıştırmak istiyorsanız, MCP sunucusu yazarak ajan çağrılarını zincirleyebilirsiniz. Ben henüz tam otomasyona geçmedim çünkü plan onay adımında insan gözünü değerli buluyorum. Ama test-implementasyon-review döngüsü insan müdahalesi olmadan çalışabiliyor.

Kritik olan nokta şu: plan aşamasını atlayan bir otomasyon, hızlı ama kırılgan oluyor. Plan aşamasını atlayıp doğrudan kodu yazdırırsanız, context window'un sonlarına doğru ajan erken kararlarıyla çelişen kod üretiyor ve düzeltme maliyeti ilk yazımın maliyetini geçiyor.

Bu döngü her proje için gerekli değil. 50 satırlık bir utility fonksiyon için plan-subagent-review kurmak anlamsız. Ama birden fazla dosyayı etkileyen, test edilebilir kabul kriterleri olan görevlerde bu yapı, ajanı "sihirli kod üretici" olmaktan çıkarıp "kontrol edilebilir asistan" haline getiriyor. Fark, sonuçta harcanan toplam saat ve dolarda ortaya çıkıyor.