Seri · The Quiet Machine · Bölüm 8
Claude Code Geliyor, Memory Framework de Bir Temizlikten Geçiyor
Claude Code’u Windows box’a kurmak tek satırdı:
npm install -g @anthropic-ai/claude-code
Beklenenden sönük. Claude Code’un yöneteceği local-first memory framework’üm, haftalardır her okumadan sağ çıkmış bir bug taşıyordu: bir environment variable resolver, her şeyi box’ın GPU’suna devrettim diye kendimi kutlarken, sessizce bir aracı localhost‘a sabitliyordu.
Framework’ün kendisi, bu projede en çok gurur duyduğum parça.
$0, Tamamen Local, ve Mekanik Yolda Generative LLM Yok
Framework, kişisel bir RAG, grounding ve persistent-memory stack’i: bir SQLite dosyası ve local bir Ollama instance’ı üzerinde on yedi küçük Python aracı. Hiçbir şey makineden çıkmıyor, hiçbir API key hot path’e değmiyor. Bütün bu iş ayda sıfır dolara dönüyor; onu güvenilir kılan tasarım kuralıysa geçmeyi reddettiğim tek bir satır:
Mekanik yolda generative bir LLM olmayacak.
Bu ayrım belirleyici. Embedder’lar ve BERT cross-encoder’lar discriminative skorlayıcılar (sıralarlar, uydurmazlar), o yüzden onlara izin var.
Yasak olan, bir chat modelinin indexing’e, verification’a ya da state-writing’e herhangi bir şekilde yaklaşması — çünkü gerçeklerini “yardımseverce” paraphrase eden generative bir model, kendini bozan ve bunu sana hiç söylemeyen bir hafızaya dönüşür. Hiç unutmayan, aptal ama deterministik bir yazmayı yeğlerim.
- Tier 0: deterministik, $0, hiç model yok. Doc↔code drift verification, near-duplicate detection, verbatim-faithfulness QC, SQLite store’un kendisi, health doctor. İşin büyük kısmı bu.
- Tier 1: local embedding, neredeyse bedava GPU.
nomic-embed-text, doc vault’u ve codebase’i semantic search için vektörlere çeviriyor. - Tier 2: local generation, neredeyse bedava GPU. 14B’lik bir model, structured extraction, git-diff özetleri ve toplu çeviri yapıyor.
- Tier 3: cloud Claude, minimize edilmiş. Pahalı model yalnızca küçük bir review queue‘ya hakemlik ediyor — deterministik katmanların tek başına çözemediği o karar anlarına.
Aynalarken Temizle
Bu framework’ü zaten box’a aynalıyordum: synthesize → clean → improve → match. Baştan sona oku, yazdığımdan beri ne değişmiş araştır, yalnızca iyileştirilmiş versiyonu taşı — her adım hislere değil gerçek koda dayanıyor. O gece üç upgrade çıktı.
A: Her Şeyi Açıklayan env Bug’ı
Haftalardır kendime stack’in box’ta çalıştığını söyleyip durdum. Beş Tier-1/2 aracının dördü OLLAMA_HOST‘u okuyup URL’sini ondan kuruyordu; o tek değişkeni box’a çevirmek de onları oraya taşıyordu.
Ama indexer farklı bir değişkeni, OLLAMA_URL‘i, tam bir URL olarak okuyor; OLLAMA_HOST‘u ise büsbütün görmezden geliyordu. Yani diğer araçlar box’a giderken her re-index, embedding’leri hiç hata vermeden Mac‘in kendisinde çıkarmaya devam ediyordu. “Sadece box’a bağla”nın bir türlü bitmemesinin gerçek sebebi işte o tek tutarsızlıktı.
Çözüm: beş aracın hepsini tek bir resolver’da birleştirmek, ki tek bir değişken bütün stack’i taşısın. Bug utandırıcıydı çünkü görünmezdi — hiçbir şey hata vermiyor, sadece yavaş ve yanlış olanı yapıyordu.
B: Tier-2, qwen3:14b‘e, Non-Thinking
Üç generation aracı, aynı ~9GB Q4 ayak izinin içinde, eski 14B’den Qwen3-14B’ye geçti. Mekanik bir pipeline için hile, non-thinking modunu zorlamak:
# in the /api/generate body
{"model": "qwen3:14b", "think": False, "temperature": 0.1}
Verbatim extraction’da, modelin çıktının içine bir <think> monoloğu döktürmesini istemezsin; Tier-0 faithfulness check’inden sağ çıkan temiz, deterministik bir metin istersin. Öyle de oldu: tertemiz bir “OK”, reasoning sızıntısı yok, full suite 27/27 yeşil.
C: Yanlış Kalibre Edilmiş Bir Blend’i Öldürmek
Retrieval’imde bir şeyler kokuyordu: sonuçlar 0.75 * cosine + 0.25 * lexical diye birleştiriliyordu. Cosine [0,1] ile sınırlı; “lexical” ayaksa sınırsız bir ölçekte, ham bir term-overlap sayısıydı.
Sınırlı bir sinyalle sınırsız bir sinyali sabit ağırlıklarla karıştır, ham büyüklüğü daha büyük olan hangisiyse sessizce o kazanır — ders kitabı seviyesinde bir calibration bug’ı. Çözüm, Reciprocal Rank Fusion: ölçeksiz, tuning gerektirmiyor — yalnızca her doc’un her listedeki rank‘ini önemsiyor.
score = sum(1.0 / (60 + rank) for rank in (vec_rank, bm25_rank))
Bu, gerçek bir sparse ayak gerektiriyordu; o yüzden vektörlerin yanına bir FTS5 bm25() index’i ekledim ve neredeyse anında bir gotcha’ya tosladım. İlk denememde bir external-content tablo kullandım — metni çoğaltmadan ana tabloyu aynalayan o zarif seçenek. Silme anında trigger şunu fırlattı:
sqlite3.OperationalError: database disk image is malformed
External-content kontratı trigger sıralaması konusunda titizdir; benimki de ona takıldı. Onunla boğuşmak yerine standalone FTS5‘e geçtim — açıkça sync’te tuttuğum düz bir ayna tablo. Daha az akıllıca, ama hiç hata fırlatmıyor.
Rebuild’den sonra index’ler tam olarak eşleşti (4309 = 4309 doc, 24217 = 24217 code chunk); bir code query de aradığım tam dosyayı en üstte geri getirdi.
Bu serinin başındaki BIOS ayarlamasıyla aynı ölçü: tam bir brute-force cosine loop, ANN index yok. Bu corpus büyüklüğünde (4309 doc) brute force tam sonucu veriyor: %100 recall, sıfır tuning — en azından corpus onu aşana kadar.
Temizlikten Sonra Geriye Kalan
O temizliğin bir yerinde, artık “tek bir projenin aracı” gibi hissettirmez oldu. Projeye özel vault yolunu çıkarınca geriye kalan, genel ve tekrar kullanılabilir bir local-AI kurulumu: kademeli, $0, özel bir memory-and-retrieval beyni — hangi işim olsa takılabileceği, eskiden boşta duran donanımın üstünde.
Cloud model, bilerek uzandığım bir neştere dönüştü — günde en fazla birkaç kez.
Box’ın sıradaki görevinin memory’yle hiçbir ilgisi yoktu: onu self-hosted bir GitHub Actions runner’a çevirmek — böylece embedding servis edip duran aynı makine, gerçek CI işini de görmeye başlayacaktı.