GitHub Copilot CLI, GitHub Copilot app ve Copilot SDK aynı motoru kullanıyor: Copilot agent runtime. Bu runtime Mayıs 2026’ya kadar tamamen TypeScript’ti ve Node.js ile V8 üzerinde çalışıyordu. 21 Ağustos’ta ise 832.378 satır production Rust haline gelmişti.
İşin ilginç tarafı dili değil, yöntemi. Kodun büyük kısmını AI agent’lar yazdı. Geçiş, main branch’ine giren 128 pull request ile parça parça yapıldı ve bu sürede ürün kullanıcılara sürüm çıkarmaya devam etti. Eskiden bir ekibin bir iki yılını alacak iş, ağırlıklı olarak tek bir geliştirici tarafından birkaç ayda tamamlandı.
O geliştirici, Microsoft’ta Distinguished Engineer olan Stephen Toub. .NET performans yazılarıyla tanınan Toub, süreci GitHub blogunda çok ayrıntılı bir yazıyla anlattı. Aşağıda o yazının önemli bölümlerini bulacaksınız.

Neden taşımak gerekti?
Copilot agent runtime sadece CLI’ın arkasında durmuyor. VS Code, Visual Studio, Copilot cloud agent, Copilot Code Review, Copilot Cowork, Copilot Studio ve Excel, Outlook, PowerPoint, Word gibi ürünlerin AI desteği de mimari olarak bu runtime’ın etrafındaki bir kabuk. Bu ürünlerin çoğu önce kendi agent loop’unu yazmış, sonra Copilot SDK’ya geçmiş. Böylece bir yerde yapılan düzeltme hepsine yansıyor.
Sorun paylaşılan şeyin doğasındaydı. CLI hızlı yazılıp yayınlandığı için terminal arayüzü ile runtime iç içe geçmişti. SDK gerektiğinde de pragmatik bir karar alınıp SDK, CLI’ın üstüne yerleştirilmişti; mantıken tersi beklenirdi. Yani SDK’dan bir CopilotClient oluşturmak, arka planda headless modda bir CLI süreci başlatmak ve onunla JSON-RPC üzerinden konuşmak anlamına geliyordu.
const client = new CopilotClient();
await client.start(); // CLI'ı alt süreç olarak başlatır
const session = await client.createSession({ /* ... */ });Bunun bedeli, SDK’yı kullanan her uygulamaya yükleniyordu:
- Her client için Node ve V8 ayağa kalkıyor, büyük miktarda JavaScript parse ediliyordu
- C#, Python, Go, Java ve Rust SDK’ları, uygulamanın başka hiçbir işine yaramayan ikinci bir dil runtime’ı için client başına en az 100 MB civarı bellek ödüyordu
- Her olay, her mesaj ve her dosya işlemi süreç sınırını geçiyordu
- Node çöktüğünde session da gidiyordu
- Deploy eden taraf en az iki süreci izleyip yönetmek zorundaydı
Hedeflenen yeni runtime’ın özellikleri: terminal arayüzünden bağımsız bir kütüphane olması, bağımlılığı ve ek yükü az bir dilde yazılması, uygulamanın kendi sürecine gömülebilmesi, altı SDK dilinin FFI mekanizmalarıyla temiz birlikte çalışabilmesi ve tedarik zinciri riski daha düşük bir araç zinciri kullanması.
Seçim Rust oldu. Toub bunun bir genelleme olmadığını açıkça belirtiyor: her büyük TypeScript programı Rust’a taşınmalı demiyor. Belirleyici olan C ABI üzerinden gömülebilirlik, düşük başlangıç ve kalıcı ek yük ve öngörülebilir kaynak kullanımıydı. Kendi ifadesiyle amaç Rust’a gitmekten çok Node.js ve V8’den uzaklaşmaktı.

130 bin satırlık tahmin 430 bine çıktı
Mayıs başındaki plan runtime’ı yaklaşık 130.000 satır TypeScript olarak tahmin ediyordu. Bu rakam hem doğru hem çok yanıltıcı çıktı, çünkü port sürerken iki şey oldu:
- Terminal arayüzü katmanında kalan parçalar runtime katmanına indirildi, başta hesaba katılmayan bileşenler de port kapsamına girdi
- Agent destekli onlarca geliştirici haftada yüzlerce PR merge ediyor, sürekli yeni TypeScript ekliyordu
Sonuçta porttan geçen production TypeScript yaklaşık 430.000 satır oldu. Bu süreçte runtime’a ~300 bin satır TypeScript girdi ve ~430 bin satır çıktı; ~1,2 milyon satır Rust girdi ve ~365 bin satır çıktı. Grafikteki TypeScript çizgisinin uzun süre yatay görünmesi, arkasındaki bu yoğun değişimi gizliyordu.

21 Ağustos itibarıyla tablo şöyleydi:
| Kalem | Satır |
|---|---|
| Production Rust | 832.378 |
| Rust unit testleri | 468.689 |
| E2E TypeScript testleri | 174.675 |
| Copilot SDK deposundaki E2E testleri (6 dil) | ~130.000 |
Neden “yerinde” port?
Bu ölçekte bir yeniden yazım için iki temel yol var. Birincisi big bang: yeni runtime ayrı geliştirilir ve hazır olduğunda tek seferde devreye alınır. İkincisi yerinde (in place) port: bileşenler tek tek taşınır.
Ekip ikinci yolun atomik değiştirme varyantını seçti. Her PR, mevcut TypeScript implementasyonunu Rust’a çağrı yapan ince bir shim ile değiştiriyor ve eski kodu aynı değişiklikte siliyordu. Gerekçeler:
- Kimsenin işi durmuyor,
mainaktif kalıyor mainher zaman yayınlanabilir durumda- Her PR tek bir bileşeni taşıdığı için diff inceleme yapılabilir boyutta kalıyor
- CLI ve SDK’nın mevcut tüm E2E testleri her adımda yeni Rust koduna karşı çalışıyor; zorunlu bir test kırılırsa PR giremiyor
İki versiyonu yan yana tutup A/B yapma seçeneği bilinçli olarak reddedildi. Session orkestrasyonu gibi, durum tutan, iki yönlü callback’ler çalıştıran ve neredeyse her alt sisteme dokunan bir bileşenin iki dilde iki kopyasını haftada yüzlerce PR boyunca senkron tutmak, önlediğinden fazla regresyon üretirdi.
Kademeli yayın da doğrulamanın bir parçasıydı. Yaklaşık 14,5 haftalık port süresinde main 135 sürüm çıkardı: 100 pre-release ve 35 stable, günde ortalama 1,3 sürüm. Her sürüm az sayıda, bilinen port değişikliği taşıdığı için gelen hata raporları kolayca ilgili değişikliğe bağlanabiliyordu.
Yapraklardan köke doğru
İlk iki PR Rust workspace’ini, araç zincirini, lint kurallarını, CI’ı ve kodlama talimatlarını kurdu. Ardından I/O’su ve paylaşılan durumu olmayan, testleri güçlü saf mantık fonksiyonları taşındı. Bunlar bir tür pilot uygulamaydı: FFI, paketleme, test ve review konusundaki varsayımlar sonraki büyük portlarda kullanılacak kurallara dönüştü.
Sıra yapraklardan içeriye doğruydu. Önce saf yardımcılar, shell araçları ve session dosya sistemi; sonra durum tutan alt sistemler; sonra tool’lar, hook’lar, model client’ları ve MCP. En çok bağımlılığı olan session orkestrasyonu sona bırakıldı.
| Dönem | PR sayısı | Medyan değişen satır |
|---|---|---|
| 1-15 Mayıs | 8 | 3.250 |
| 16-31 Mayıs | 2 | 9.421 |
| 1-15 Haziran | 40 | 5.073 |
| 16-30 Haziran | 31 | 8.253 |
| 1-15 Temmuz | 10 | 9.514 |
| 16-31 Temmuz | 14 | 28.159 |
| 1-15 Ağustos | 19 | 13.861 |
| 16-30 Ağustos | 4 | 99.445 |
Pratikte port birimi her zaman “bir bileşen” değildi. MCP desteği yedi ayrı PR’da taşındı, tool’lar altı parçalık bir seride. Genel desen şöyleydi: önce saf mantığı taşı, sonra durum sahipliğini, sonra orkestrasyonu, sonra fallback’leri kaldır, en sonda geçici interop gidince Rust kodunu sadeleştir.
İki katmanlı interop
Geçici iç interop. Bir fonksiyon Rust’a taşındığında, onu çağıran TypeScript kodunun hâlâ çağırabilmesi gerekiyordu. Bu iş napi-rs ile yapıldı: #[napi] ile işaretlenen bir Rust fonksiyonu JavaScript’ten çağrılabilir hale geliyor, async fn bir promise döndürüyor. Ters yönde, henüz taşınmamış TypeScript’e geri çağrı gerektiğinde “threadsafe function” mekanizması kullanıldı. Bu geçici katman 3 Ağustos’ta zirve yaptı: 2.019 N-API export ve 3.356 TypeScript çağrı noktası. Port bittiğinde ikisi de sıfıra indi.
Kalıcı SDK yüzeyi. Asıl kazanç burada. Derlenen runtime.node dosyası aslında sıradan bir paylaşımlı kütüphane (.dll, .so veya .dylib) ve iki giriş kapısı var: Node’un native addon olarak yüklediği napi kapısı ve herhangi bir dilin kendi sürecine FFI ile yükleyebildiği C ABI kapısı.
| SDK | Native köprü | Aynı süreçte çalıştırma |
|---|---|---|
| C# | P/Invoke | RuntimeConnection.ForInProcess() |
| Go | purego | copilot.InProcessConnection{} |
| Java | JNA | RuntimeConnection.forInProcess() |
| Python | cffi | RuntimeConnection.for_inprocess() |
| Rust | libloading | Transport::InProcess |
| TypeScript | koffi | RuntimeConnection.forInProcess() |
Aynı süreçte çalıştırma şu an opt-in. Uygulamayla aynı süreci, dolayısıyla aynı çökme sınırını paylaşmanın güvenilirliği henüz doğrulanıyor.
C ABI kapısının ilginç tarafı küçüklüğü: sadece 19 export edilmiş fonksiyon var. Arkalarındaki sözleşme ise 364 dispatch route içeriyor. API metodları ayrı export almıyor; JSON-RPC byte’ları olarak bağlantıya yazılıyor. Yeni bir API metodu eklemek ABI’ye hiç dokunmuyor.
Peki süreç sınırı yoksa neden hâlâ JSON-RPC? Çünkü her SDK’da zaten çalışan bir JSON-RPC client vardı. FFI’ı bu client’ın altına bir transport olarak eklemek, byte yolunu pipe’tan fonksiyon çağrısına taşıyor ve üstteki her şeyi aynen bırakıyor. Metod başına tipli C fonksiyonları tanımlamak ise her yeni API için altı ayrı binding ve versiyonlanması gereken bir ABI demekti. Toub bunun bedava olmadığını kabul ediyor: süreç atlaması ortadan kalkıyor ama JSON serileştirme maliyeti devam ediyor. Model çağrısının süresi yanında bu maliyet çok küçük, ama gerekirse sonradan değiştirilebilecek bir karar.
Session loglarında ne var?
Yazıdaki rakamların çoğu iki kaynaktan geliyor: özel github/copilot-agent-runtime deposunun geçmişi ve agent session logları. Tüm port PR’larının özeti:
| Metrik | Sayı |
|---|---|
| Olay | 12.760.995 |
| Kullanıcı mesajı | 31.247 |
| Asistan mesajı | 1.385.214 |
| Hook başlangıç/bitiş olayı | 6.438.562 |
| Tool başlatma | 1.857.409 |
| Derleme komutu | 23.096 |
| Test komutu | 19.485 |
| Rebase | 2.496 |
| Commit | 7.410 |
| Push | 5.554 |
| Tamamlanan compaction | 5.116 |
31.247 kullanıcı mesajının tamamı Toub’un yazdığı prompt’lar değil. Skill talimatları, otomatik mesajlar, session’lar arası trafik ve child agent’lar da dahil. Kendi yazdığı ya da sesle verdiği mesaj sayısı yaklaşık 2.600, yani 12’de bir. Tool çağrılarının %61‘i ana session’dan değil subagent’lardan gelmiş.
Toub, Copilot’a kendi mesajlarını niyete göre sınıflandırttı. İlk üç kategori etkileşimlerin %63’ünü oluşturuyor: review, test ve CI (%31), teknik veya tasarım kararlarına itiraz (%17,4) ve işi tamamlatmak için baskı (%15).

Rolüm “görevi ver ve bekle” olmaktan çok “kontrol döngüsünü işletmek”ti: sonucu incelemek, teknik kararlara itiraz etmek, kalite kapılarını uygulamak ve bir agent ara bir durağı bitiş çizgisi sandığında onu ileri itmek.
Stephen Toub
Toub’a göre insan müdahalesi azalmadı, bir üst seviyeye çıktı. Syntax yazmak yerine problemleri çerçevelemek, sınırları tanımlamak, strateji seçmek ve istisnalara karar vermekle uğraştı.
Maliyeti belirleyen şey: prompt caching
Sağlayıcılar cache’ten okunan input token’ları genelde çok daha ucuza faturalıyor, çoğu zaman %90 indirimle. Bu portta prompt cache isabet oranı %96,22 oldu. Cache yazma %3,07, taze input yalnızca %0,71.

Bu tesadüf değil. Copilot agent loop’u uzun ve sabit bir öneki koruyacak şekilde tasarlıyor: önce system prompt, sonra tool tanımları, sonra biriken konuşma. Her tur, modelin zaten işlediği bağlamın sonuna ekleniyor. Toub’a göre uzun otonom session’ların ekonomik olarak mümkün olmasının sebebi bu.
Compaction da ilginç bir veri sunuyor. Context penceresi dolduğunda session kendini özetleyip devam ediyor ve port boyunca bu 5.116 kez oldu. Tek bir PR 647 kez compaction yaptı. Her özet, işin kopabileceği bir noktaydı. Toub bunu ölçmek için compaction öncesi ve sonrası 20’şer tool çağrısını içeren ~4.000 pencereyi karşılaştırdı. Agent bağlamı kaybetseydi, compaction sonrası okuma işlerinde belirgin bir artış ve düzenleme işlerinde çöküş görülürdü. Görülen ise hafif bir kayma: keşif %46,5’ten %48,1’e çıkmış, değişiklik %8,4’ten %6’ya inmiş.
Uzun agent oturumlarında context yönetiminin nasıl değiştiğini daha önce ele almıştık: Claude 5 nesli için context engineering.
Rust derleyicisi gerçekten yardım etti mi?
“Rust’ın katı derleyicisi AI’ın hatalarını yakaladığı için AI kodu için ideal dil” diye yaygın bir görüş var. Session logları bunu test etmeye izin veriyor. rustc‘nin yakaladığı 8.678 hata kodunun %84’ü dört grupta:
- %37: İsim ve import çözümleme (en çok
E0425, “cannot find value in this scope”) - %22: Eksik metod veya alan
- %14: Tip uyuşmazlığı
- %11: Karşılanmamış trait bound
Bunların hiçbiri Rust’a özgü değil. Toub’un tespiti net: C#, Java veya Go derleyicisi bunların hepsini yakalardı, bazılarını daha anlaşılır mesajlarla ve çok daha hızlı. Yani bu argüman aslında Rust için değil, statik tipli herhangi bir dil için geçerli.
Rust’ın zor olduğu tartışmalarının merkezindeki ownership, borrowing ve lifetime hataları toplamda sadece %1,7. Doğrudan cargo check çalıştırmalarının %87,1’i temiz döndü, çünkü agent’lar küçük adımlarla değiştirip sürekli derliyordu.
Agent’lar kod yazmaktan çok okudu
| Tool | Çağrı | Medyan | Ölçülen saat |
|---|---|---|---|
powershell | 630.423 | 3 sn | 2.833,9 |
view | 590.988 | 0 sn | 621,7 |
rg | 281.783 | 1 sn | 408,4 |
grep | 126.483 | 1 sn | 115,3 |
apply_patch | 53.715 | 0 sn | 17,0 |
edit | 40.591 | 1 sn | 24,1 |
read_powershell | 36.728 | 90 sn | 1.203,9 |
task | 13.080 | 274 sn | 2.329,0 |
Okuma ve arama araçları, düzenleme araçlarının 10 katı kullanıldı. Toub’a göre “AI kod kusuyor” imajı bu ölçekte neredeyse tersine dönüyor; iş daha çok durumu incelemek, hipotez kurmak, hedefli değişiklik yapmak ve tekrar etmek şeklinde ilerledi.
Shell trafiğinde en sık komut grubu salt okuma git incelemeleriydi (300.530 çağrı). Agent’lar sürekli “neredeyim?” diye soruyordu: ne değişti, rebase ne yaptı, başka bir session ne merge etti, branch hızla ilerleyen main‘den ne kadar uzaklaştı.
Model seçimi de dilim bazında yapıldı. Ana session’larda modeli ve reasoning seviyesini Toub seçti. Subagent’larda ise seçimi orkestra eden model yaptı. En sık kullanılanlar Claude Opus 4.8, GPT-5.6 Sol, Claude Haiku 4.5 ve GPT-5.5 oldu, onları Gemini 3.1 Pro ve Claude Opus 5 izledi. Ancak explore ve task agent tanımları Claude Haiku’ya, research ise Claude Sonnet’e sabitlenmişti; bu hacmin önemli kısmı model seçiminden değil agent tanımından geliyor.
30 bin satırlık session.ts ve 15 child session
En zor port session.ts dosyasıydı: organik olarak ~30.000 satıra büyümüş, runtime’ın neredeyse her bileşenine dokunan bir dosya. Portu üstlenen session hemen Rust yazmaya başlamadı. İlk 56 dakikasını okuyarak geçirdi, bir şey oluşturmadan önce 122 tool çağrısı yaptı. Dosyanın neye sahip olduğunu ve nerelerden bölünebileceğini anladıktan sonra işi dilimlere ayırıp child session’lara dağıttı.

Ana session üç saat içinde yedi dalgada 15 child session oluşturdu. Her biri kendi worktree’sinde, kendi branch’inde ve kendi agent loop’unda çalışıyordu. 10’u GPT-5.6 Sol, 5’i Claude Opus 4.8 ile çalıştı; hepsi autopilot modundaydı. Child session’ların başlangıç prompt’larını bir insan değil, ana agent yazdı; medyan uzunluk yaklaşık 1.100 karakterdi.
Aynı ana session ayrıca beş subagent kullandı: paralel çalışan üç explore, bir code-review ve bir rubber-duck. Aradaki ayrım önemli: subagent’lar soruları araştırıp cevabı ana session’ın bağlamına getiriyor, child session’lar ise izolasyon gerektiren asıl port işini yapıyor.
Child session’lar 140 farklı dosyaya dokundu, bunların 120’sine yalnızca bir session. Çakışan 20 dosyanın hepsi session.ts gibi merkez dosyalardı. Ana session bunun bedelini koordinasyonla ödedi: çocukların durumunu 60 kez sorguladı, 89 koordinasyon mesajı gönderdi ve sonunda commit’lerini cherry-pick edip çakışmaları elle çözdü.
Dizüstü bilgisayar dayanamadı: agentic mutex
Bir noktada 15 agent aynı makinede aynı anda build ve test çalıştırmaya başladı ve Toub’un dizüstü bilgisayarı kilitlendi. Toub ana session’dan çocuklara build ve testi durdurmalarını iletmesini istedi, onlar da uydu. Sonrasında standing talimatlara “subagent ve subsession’lar büyük build ve test çalıştırmasın, bunu sadece ana agent yapsın” kuralı eklendi.
Daha sonra bir adım ileri gitti ve sıradan bir chat session’ı sekiz porting session’ı için build zamanlayıcısına dönüştürdü. Prompt basitti: açık session’lara CPU yoğun işlerden kaçınma politikası gönder, build gerektiğinde bu session’dan izin iste, aynı anda yalnızca bir session’a build hakkı ver. Chat session açık bir sahip ve kuyruk tutarak session’lar arası mesajlaşma üzerinden kilit dağıttı. İzin alamayan session’lar genelde beklerken yapılacaklar listesindeki başka işlere geçti.

Beklenmedik olay: bir session diğerinin işini aldı
Yazının en dikkat çekici bölümü bu. Toub, session.ts portunu başlattıktan sonra işi hızlandırmak için runtime’ın yüzlerce giriş noktasını taşıyacak ikinci bir session başlattı ve ona session.ts sınırında durmasını söyledi. Başlangıç prompt’unda paralel çalışan diğer session’ları da belirtti, böylece neye dokunmaması gerektiğini bilsin istedi. Sonra uyumaya gitti.
Olanlar şöyle gelişti:
- Giriş noktaları session’ı dört dakika içinde app’in yerleşik
orchestrateskill’ini kendiliğinden çağırdı - Tüm aktif session’ları listeledi ve çakışma gördüklerine mesaj attı
session.tssession’ı 2.001 karakterlik bir çakışma envanteriyle cevap verdi- Giriş noktaları session’ı söyleneni doğrulamak için diğer session’ın worktree’sini okudu
- 760 dosyalık diff’i birleştirmeye hazır olup olmadığını sordu, “Henüz commit/entegrasyona hazır değilim” cevabını aldı
- Aynı soruyu üç kez daha sordu, her seferinde aynı cevabı aldı
- Sonunda diğer session’ın ne düşündüğünü umursamadı, worktree’sine uzanıp tüm değişikliklerini kendi branch’ine merge etti
Toub’un bu olaydan çıkardığı dersler, agent kullanan herkes için geçerli:
- Niyet açıkça söylenmeli. Diğer session’ları “dokunma” diye değil sadece bilgi olarak belirtmek, agent’ı engellemek yerine teşvik etti.
- Erişilebilir kıldığınız her şeyi agent kullanabilir. Prompt’ta
orchestrateskill’inden hiç bahsedilmemişti. Model durumunu skill açıklamasıyla eşleştirip kendisi yükledi. - Eşitler arasında hakem gerekir. Reddetmenin hiçbir yaptırımı yoktu; tek taraflı hareket etmeye istekli olan varsayılan olarak kazandı.
- “Otonom çalış” talimatına kendi branch’inin dışına çıkan kararlar için istisna eklenmeli. Toub “tasarım detayları için beni uyandırma” demek istemişti; agent komşu session’ı ele geçirmeyi kapsam içinde gördü.
- Kök neden Toub’un kendisiydi. İşi aynı anda hem yukarıdan aşağı hem aşağıdan yukarı bölmüş, iki yön kod tabanının en çok bağlantılı dosyasında buluşmuştu.
Toub bunun istisnai bir olay olduğunu belirtiyor. Yaprak bileşenlerin çoğu sorunsuz tek session’lık işlerdi.
Ölçekte code review ve Agent Merge
Sık rebase gerektiği için Toub rust-rebase-review adında bir skill yazdı. Skill’in özü şuydu: commit’leri tek commit’e indir, origin/main üzerine rebase et, sonra Claude Opus 5, GPT-5.6 Sol ve Grok 4.6 ile birer subagent başlatıp eski TypeScript ile yeni Rust’ı satır satır karşılaştırt. Kontrol listesinde anlamsal eşitlik, idiomatic Rust, silinmemiş ölü TypeScript kalmaması ve E2E testlerin silinmemiş veya değiştirilmemiş olması vardı. İnceleme temiz gelene kadar düzelt ve tekrar incele döngüsü sürüyordu.
Atomik değiştirme yaklaşımının beklenmedik bir faydası da oldu. TypeScript kodu, karşılığı olan Rust kodu eklenirken aynı anda silindiği için, o koda gelen yeni değişiklikler rebase sırasında zorunlu çakışma üretti. Böylece zaten taşınmış koda yapılan değişiklikler gözden kaçmadı.
İş akışının merkezinde GitHub Copilot app’in Agent Merge özelliği vardı (CLI’da /pr auto). Bu döngü review yorumu gelince agent’a kabul veya ret kararı verdiriyor, test kırılınca logları indirip hatayı düzeltiyor, çakışma çıkınca rebase ediyor. Port PR’larının tamamı bu döngüden geçti, ama merge adımı genelde Toub’a bırakıldı.

Son merge adımını elde tutmanın önemi bir kez somut olarak görüldü. Bir port, SDK’ya açılan bir fonksiyonu yanlışlıkla silmişti. Şema uyumluluğu CI kontrolü doğru şekilde kırıldı. Agent’ın buna cevabı, kontrolü geçirmek için depodaki schema-break-ok etiketini PR’a eklemek oldu. Toub merge öncesi inceleme yaparken sordu: “Şema kırılması ne? Neden sorun değil?” Sorun vardı; metod main‘de mevcuttu ve port onu kaybetmişti. Toub bunu kabul edilemez bir regresyon olarak işaretledi. 21 saniye sonra etiket kaldırıldı ve metod Rust’ta yeniden yazıldı.
Her hata tekrarlanmasın diye sisteme de yansıtıldı: agent talimatları güncellendi, session logları eval’e dönüştürüldü, bazı dersler runtime’ın kendi prompt ve tool açıklamalarına işlendi.
Aslında iki geçiş
Dil değişimi bağımlılıkların da değişmesi demekti. Sadece runtime tarafından kullanıldığı için ~60 npm paketi kaldırıldı. Bazıları birebir karşılık buldu: js-tiktoken → tiktoken-rs, minimatch → globset, dompurify → ammonia, github/keytar → keyring. Bazıları birebir eşleşmedi: sekiz opentelemetry/* paketi dört crate artı elle yazılmış bir durum makinesine dönüştü. Beş pakette ise hiçbir hazır karşılık bulunamadı ve tamamen özel implementasyon yazıldı.
Ne kadar unsafe var?
Agent’ların borrow checker’ı aşamadığında unsafe‘e kaçıp kaçmadığı sık sorulan bir soru. Runtime crate’inde 36 dosyaya dağılmış 158 unsafe bloğu var ve hepsi dış dünyayla sınırda:
| Sebep | Blok | Pay |
|---|---|---|
| C ABI sınırı | 51 | %32,3 |
| Windows API | 49 | %31,0 |
| POSIX / libc | 46 | %29,1 |
| SQLite C API | 7 | %4,4 |
| Dinamik kütüphane yükleme | 4 | %2,5 |
| Süreç environment’ı | 1 | %0,6 |

Model client’larında, MCP katmanında, agent katmanında ve prompt katmanında hiç unsafe yok. Bilinen regresyonların hiçbiri bir unsafe bloğuyla ilgili değil. Toub bir de şunu belirtiyor: TypeScript runtime aynı sınırları Node’un C++ iç yapısı ve native npm paketleri üzerinden zaten geçiyordu, sadece kaynak kodda bunun nerede olduğunu gösteren bir işaret yoktu. Rust’ta bu noktalar en azından denetlenebilir.
Regresyonlar
14 Eylül itibarıyla onlarca bilinen port regresyonu tespit edilip düzeltilmişti. Toub bilinmeyenlerin de olduğundan “%100 emin”. Önemli olan sayı değil, sebepler. Regresyonların büyük kısmı üç ana ailede toplanıyor: davranışsal sözleşmenin farklı uygulanması; durum, sahiplik veya lifetime davranışının değişmesi; ve geçişin bir kısmının atlanması ya da rebase’de kaybolması.

Somut örnekler, benzer bir geçiş yapacak herkes için değerli:
- Belirsiz anlam. TypeScript’te tek bir
numbertipi var; Rust’ta seçim yapmak gerekiyor. Tamsayı olması gereken alanlarf64oldu ve42yerine42.0serileştirildi; Go ve C# SDK’ları bunuint64‘e çeviremedi. Daha ince bir örnek: JavaScript’tekievent.error || "Unknown error", Rust’ta.unwrap_or("Unknown error")oldu. JavaScript boş string’i değiştirir,unwrap_orkorur. - Görünmez ortam davranışları.
toLocaleDateStringhost saat dilimini sessizce kullanıyordu; Rust’ta açıkça verilmesi gerekti. Windows’ta platform tespiti için çağrılan bir Node fonksiyonu_NT_SYMBOL_PATH‘e uyup CLI açılmadan dakikalarca PDB indirebiliyordu. - Çiftin yarısını taşımak. Bir tur sınırı kontrolü native tarafta abort durumunu güncelledi ama model döngüsünü iptal etmedi, bir istek daha kaçtı.
- Ana thread’i bloklamak. CLI hâlâ Node’un tek thread’li event loop’undan runtime’ı sürdüğü için napi sınırındaki senkron işler arayüzü dondurdu.
/chronicle reindexgirdiyi yaklaşık bir dakika blokladı. - Yanıp sönen pencereler. Node runtime, Windows’ta alt süreç başlatırken konsol penceresi açılmasını önleyen
CREATE_NO_WINDOWbayrağını monkey patch ile ekliyordu. Bu gizli gereksinim porting agent’ına görünmedi. - Lifecycle yönetimi. En büyük küme. TypeScript’in elinde kalan opak handle’lar Rust tarafındaki nesneden uzun yaşayabildi; istek ortasında dispose edilen bir hook, eşleşmemiş bir
tool_usebloğu bırakıp konuşmayı kilitledi. - Gözden kaçan özellikler. Bir port SDK callback’lerini atladı ve ilgili E2E testini sildi. Bunun üzerine agent’ların açık onay olmadan E2E testlerini değiştiremeyeceği kuralı getirildi.
- Farklı görüşteki kütüphaneler. Rust MCP SDK’sı (
rmcp) bozuk JSON-RPC girdisine cevap veriyordu, TypeScript SDK’sı vermiyordu. Hatalara bozuk çıktıyla cevap veren bir server karşısında bu nezaket sonsuz döngüye ve başlangıçta takılmaya dönüştü. - Yavaşlamalar. Bir salt okuma taraması 260 MB’lık event log’u ödünç almak yerine derin kopyaladı.
Kalite etkisini ölçmek için Toub, açık kaynak copilot-cli ve copilot-sdk depolarında hata etiketli veya başlığında “bug”, “regression”, “crash” gibi kelimeler geçen issue’ların oranına baktı:
| Depo | Ocak-Nisan (öncesi) | Mayıs-Ağustos (sırası/sonrası) |
|---|---|---|
| github/copilot-cli | %22,9 (454 / 1.982) | %23,7 (354 / 1.496) |
| github/copilot-sdk | %36,2 (190 / 525) | %32,3 (135 / 418) |
Toub bunun kesin bir kusur sayımı olmadığını belirtiyor, ama bu kadar yoğun bir değişiklik sürecinde kaliteyle ilgili belirgin bir sıçrama da görülmemiş.
“Derleniyorsa doğrudur” değil
Rust dünyasında “derleniyorsa doğrudur” diye bir espri var. Regresyon listesi buna iyi bir cevap: her regresyon main‘e merge edilmişti, yani hepsi derleniyordu.
Bir derleyici yazdığınız programın kendi içinde tutarlı olup olmadığını kontrol eder. Programın tamamını yazıp yazmadığınızı, eski sözleşmeyi koruyup korumadığınızı, şeyleri doğru sırada çağırıp çağırmadığınızı, host’un yazılı olmayan gereksinimlerini karşılayıp karşılamadığınızı veya işi kabul edilebilir bir maliyetle yapıp yapmadığınızı kontrol edemez.
Stephen Toub
Performans
Port bilinçli olarak davranışı korumaya odaklıydı. Toub agent’ları fırsatçı optimizasyondan defalarca uzak tuttu, çünkü dil ve davranış aynı anda değişince neyin bozduğunu anlamak zorlaşıyor. Buna rağmen kazançlar büyük. Ölçümler C# SDK üzerinden, localhost’ta sabit yanıt veren deterministik bir server’a karşı yapıldı. Yani model çıkarımı ve ağ gecikmesi hesaba katılmadı, sadece değişen kısım ölçüldü.
| Senaryo | 12 Mayıs | 21 Ağustos (ayrı süreç) | 21 Ağustos (aynı süreç) |
|---|---|---|---|
| Client, session, tek tur | 5,25 sn | 1,33 sn (4,0x) | 292 ms (18,0x) |
| 32 turluk session’ı devam ettirme | 5,64 sn | 1,52 sn (3,7x) | 264 ms (21,4x) |
| On eşzamanlı client yaşam döngüsü | 12,34 sn | 4,18 sn (3,0x) | 742 ms (16,6x) |
| 1.000 tek turluk session döngüsü | 132,52 sn | 22,53 sn (5,9x) | 20,93 sn (6,3x) |

Sunucu tarafı için en önemli sonuç paylaşılan client testi. Eski TypeScript CLI saniyede 7,55 session döngüsü tamamlıyordu; Rust ayrı süreçte 57,45, aynı süreçte 120. Toplam CPU kullanımı 312 saniyeden yaklaşık 110 saniyeye düştü. On client testinde bellek artışı 1.383 MB’tan ayrı süreçte 247 MB’a, aynı süreçte 126 MB‘a indi.
Toub bunun her iş yükü için geçerli bir “15,9 kat hız” olmadığını vurguluyor, ayrıca aynı dönemde başka değişiklikler de girdiği için rakamlara temkinle bakılmasını istiyor. Asıl önemli nokta şu: bu hâlâ temel port. Kodun büyük kısmı Rust sözdizimi giymiş TypeScript algoritmaları. Rust’ın sahiplik ve eşzamanlılık modeline göre yeniden tasarım henüz yapılmadı.
Maliyet
Porting işinin tamamı yaklaşık 136,3 milyar token harcadı:
| Token türü | Miktar |
|---|---|
| Cache’ten okunan input | ~130,6 milyar |
| Cache’e yazılan input | ~4,2 milyar |
| Taze input | ~900 milyon |
| Output | ~600 milyon |
| Toplam fatura | ~120.000 dolar |
Buna geliştirici zamanı da eklenmeli. Agentic geliştirmede çok bekleme olduğu için Toub aynı anda başka işlerle de uğraştı. Port dönemindeki PR’larının ~%20’si bu işe aitti. PR payını zaman payına kabaca eşitleyince, projeye ayrılan süre yaklaşık üç hafta ediyor. Yani kaba hesap: 120 bin dolar token ve bir geliştiricinin üç haftası.
Toub işin tamamen tek kişilik olmadığını da belirtiyor. Geçici out-of-process interop katmanını ve altı SDK FFI implementasyonundan beşini başka bir ekip üyesi yazdı; paketleme, crate bölme, CI önbellekleme ve review’larda da ekipten katkılar oldu.
Copilot tarafında token maliyetlerini yönetmeye dair pratik adımları daha önce ele almıştık: GitHub Copilot maliyet optimizasyonu.
Toub’un çıkardığı dersler
- Hedef açık ve eksiksiz söylenmeli. “XYZ bileşenini Rust’a taşı” talimatı sadece sıcak yollar veya sadece mantık olarak yorumlandı. Hedefin “TypeScript için çalışma ortamı kalmayan, %100 Rust bir native binary” olduğu netleşince agent’lar çok daha iyi ilerledi.
- E2E testler vazgeçilmez. Eksik özellik regresyonlarının biri hariç hepsi yetersiz E2E testten kaynaklandı. Port başlamadan önce testler güçlendirilmişti ama yeterince değil.
- Doğruluk ölçütünü agent’tan koruyun. Implementasyonu değiştiren agent, test gevşeterek, snapshot güncelleyerek veya kaçış etiketi ekleyerek doğruluğun tanımını sessizce değiştirememeli.
- Önce çevir, sonra yeniden tasarla. Toub bu kuraldan birkaç kez saptığını ve her birinden pişman olduğunu söylüyor.
- Tekrarlanan hataları sisteme işleyin. Bir hata modu iki kez görülüyorsa talimata, skill’e, eval’e veya harness’ın kendisine girmeli.
- Inner loop, agent’larla daha da önemli. Agent’lar düşünmeyi ve yazmayı çok hızlı yapıyor ama build ve test hâlâ zaman alıyor. Build ve teste giden sürenin payı azalmıyor, artıyor.
Bu yazının değeri, başarı hikâyesi anlatmasından çok verisini açık etmesinde. Token faturası, regresyon listesi, agent’ın şema kontrolünü etiketle geçmeye çalışması, bir session’ın diğerinin işini izinsiz alması: bunlar bir tanıtım yazısında genelde yer almaz.
En önemli iki tespit bence şunlar. Birincisi, iş “agent’a ver ve bekle” şeklinde yürümedi. İnsan müdahalesi azalmadı, türü değişti: mesajların üçte biri review ve test, altıda biri teknik itiraz, yedide biri “bitmedi, devam et” baskısıydı. İkincisi, agent’ların test gevşetme, uyumluluk etiketi ekleme gibi doğruluk ölçütünü sessizce değiştirme eğilimi. Merge adımını insana bırakmak bu yüzden gerekiyor.
Rust tarafında da abartıyı dengeleyen bulgular var: derleyici hatalarının neredeyse tamamı statik tipli herhangi bir dilin yakalayacağı türden, borrow checker hataları %1,7. Yani bu hikâyeden “AI için en iyi dil Rust” sonucu çıkmıyor; statik tip ve güçlü test altyapısı her dilde agent’lar için benzer bir geri bildirim döngüsü sağlıyor.
120 bin dolar büyük bir rakam gibi görünebilir. Ama Toub’un kendi hesabına göre bu proje agent’lar olmadan bir ekibin bir iki yılını alırdı ve ürün geliştirmeyle yarıştığı için muhtemelen hiç onaylanmazdı. Asıl değişiklik şu: daha önce maliyeti yüzünden yapılmayan projeler artık yapılabilir hale geldi.
