Haberler Microsoft

GitHub, Copilot runtime’ını Copilot ile Rust’a taşıdı: 832 bin satır, 128 PR ve 120 bin dolarlık token

GitHub Copilot runtime Rust geçişi

GitHub, Copilot CLI, app ve SDK’nın arkasındaki agent runtime’ı Mayıs-Ağustos 2026 arasında AI agent’larla TypeScript’ten 832 bin satırlık Rust’a taşıdı. Stephen Toub’un yazısından yöntem, session log verileri, regresyonlar, 120 bin dolarlık token maliyeti ve performans kazançları.

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.

GitHub Copilot runtime Rust geçişi

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ı.

Rust geçişi öncesi ve sonrası Copilot runtime mimarisi
Önce: SDK, ayrı bir süreçte çalışan headless CLI’a bağlanıyordu. Sonra: Rust runtime hem aynı süreçte (FFI) hem ayrı süreçte çalışabiliyor

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.

Runtime satır sayısı: TypeScript sıfıra indi, Rust 830 bine çıktı
TypeScript sıfıra inerken production Rust ~830 bin, Rust unit testleri ~469 bin satıra çıktı

21 Ağustos itibarıyla tablo şöyleydi:

KalemSatır
Production Rust832.378
Rust unit testleri468.689
E2E TypeScript testleri174.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, main aktif kalıyor
  • main her 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önemPR sayısıMedyan değişen satır
1-15 Mayıs83.250
16-31 Mayıs29.421
1-15 Haziran405.073
16-30 Haziran318.253
1-15 Temmuz109.514
16-31 Temmuz1428.159
1-15 Ağustos1913.861
16-30 Ağustos499.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ı.

SDKNative köprüAynı süreçte çalıştırma
C#P/InvokeRuntimeConnection.ForInProcess()
Gopuregocopilot.InProcessConnection{}
JavaJNARuntimeConnection.forInProcess()
PythoncffiRuntimeConnection.for_inprocess()
RustlibloadingTransport::InProcess
TypeScriptkoffiRuntimeConnection.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:

MetrikSayı
Olay12.760.995
Kullanıcı mesajı31.247
Asistan mesajı1.385.214
Hook başlangıç/bitiş olayı6.438.562
Tool başlatma1.857.409
Derleme komutu23.096
Test komutu19.485
Rebase2.496
Commit7.410
Push5.554
Tamamlanan compaction5.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).

İnsan mesajlarının niyet dağılımı
2.639 insan mesajının niyet dağılımı

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.

Prompt cache bileşimi
Prompt cache bileşimi: %96,22 cache okuma, %3,07 cache yazma, %0,71 taze input

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
powershell630.4233 sn2.833,9
view590.9880 sn621,7
rg281.7831 sn408,4
grep126.4831 sn115,3
apply_patch53.7150 sn17,0
edit40.5911 sn24,1
read_powershell36.72890 sn1.203,9
task13.080274 sn2.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ı.

session.ts portu: subagent'lar ve child session'lar
session.ts portu: bir ana session, beş subagent ve yedi dalgada başlatılan 15 child session

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.

Sekiz porting session'ı için build kapısı görevi gören chat session
Sekiz porting session’ı için build kapısı görevi gören tek bir chat session

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:

  1. Giriş noktaları session’ı dört dakika içinde app’in yerleşik orchestrate skill’ini kendiliğinden çağırdı
  2. Tüm aktif session’ları listeledi ve çakışma gördüklerine mesaj attı
  3. session.ts session’ı 2.001 karakterlik bir çakışma envanteriyle cevap verdi
  4. Giriş noktaları session’ı söyleneni doğrulamak için diğer session’ın worktree’sini okudu
  5. 760 dosyalık diff’i birleştirmeye hazır olup olmadığını sordu, “Henüz commit/entegrasyona hazır değilim” cevabını aldı
  6. Aynı soruyu üç kez daha sordu, her seferinde aynı cevabı aldı
  7. 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 orchestrate skill’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ı.

GitHub Copilot app Agent Merge paneli
GitHub Copilot app Agent Merge paneli: review geri bildirimi, CI, çakışmalar ve merge hazırlığı

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-tiktokentiktoken-rs, minimatchglobset, dompurifyammonia, github/keytarkeyring. 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:

SebepBlokPay
C ABI sınırı51%32,3
Windows API49%31,0
POSIX / libc46%29,1
SQLite C API7%4,4
Dinamik kütüphane yükleme4%2,5
Süreç environment’ı1%0,6
unsafe bloklarının dağılımı
158 unsafe bloğunun tamamı dış sınırlarda

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ı.

Tekrarlayan regresyon türleri
Tekrarlayan doğruluk regresyonu türleri

Somut örnekler, benzer bir geçiş yapacak herkes için değerli:

  • Belirsiz anlam. TypeScript’te tek bir number tipi var; Rust’ta seçim yapmak gerekiyor. Tamsayı olması gereken alanlar f64 oldu ve 42 yerine 42.0 serileştirildi; Go ve C# SDK’ları bunu int64‘e çeviremedi. Daha ince bir örnek: JavaScript’teki event.error || "Unknown error", Rust’ta .unwrap_or("Unknown error") oldu. JavaScript boş string’i değiştirir, unwrap_or korur.
  • Görünmez ortam davranışları. toLocaleDateString host 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 reindex girdiyi 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_WINDOW bayrağı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_use bloğ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ı:

DepoOcak-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ü.

Senaryo12 Mayıs21 Ağustos (ayrı süreç)21 Ağustos (aynı süreç)
Client, session, tek tur5,25 sn1,33 sn (4,0x)292 ms (18,0x)
32 turluk session’ı devam ettirme5,64 sn1,52 sn (3,7x)264 ms (21,4x)
On eşzamanlı client yaşam döngüsü12,34 sn4,18 sn (3,0x)742 ms (16,6x)
1.000 tek turluk session döngüsü132,52 sn22,53 sn (5,9x)20,93 sn (6,3x)
Rust geçişi öncesi ve sonrası performans
Isınmış client ile tek tur 5,25 sn’den 55,3 ms’ye, verim saniyede 7,55’ten 120 session’a çıktı, on client belleği 1.383 MB’tan 126 MB’a indi

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.

Kaynak: https://github.blog/ai-and-ml/generative-ai/migrating-the-github-copilot-runtime-to-rust-using-copilot/

Yazar Hakkında

Kerem Şuğle

Solution Architect, VMware vExpert ve Microsoft sertifikalı altyapı uzmanı. VMware vSphere/vSAN/VCF, Azure, AWS, Google Cloud, enterprise sanallaştırma ve yapay zeka konularında 15+ yıl deneyim. AI/cloud dönüşümü, sovereign cloud, enterprise güvenlik ve modern altyapı mimarisi alanlarında yazıyor.

Leave a Comment