- k10s Claude के साथ vibe-coding से तेज़ी से बनाया गया एक GPU-aware Kubernetes TUI था, लेकिन fleet view जोड़ने के बाद कई स्क्रीन states टूट गईं
model.go बढ़कर 1690 lines के एक single Model और 500-line Update() तक पहुँच गया, जो UI, client, cache, navigation और view state सब कुछ संभालने लगा
- AI ने features जल्दी जोड़ दिए, लेकिन उसने god object और global key handler को भी बढ़ाया, और हर नए view के साथ पुराने handler में branch बढ़ती गई
- position-based
[]string data और background tea.Cmd की direct mutation से column errors और साफ़ data race पैदा हो सकते थे
- नया k10s Rust में दोबारा लिखा जा रहा है, और पहले prompt से पहले interface, message type, ownership rule और scope को CLAUDE.md में fix किया जाएगा
k10s को फिर से लिखने की पृष्ठभूमि
- k10s की शुरुआत एक GPU-aware Kubernetes dashboard के रूप में हुई थी, जिसे NVIDIA cluster operators के लिए बनाया गया था ताकि वे GPU utilization, DCGM metrics, idle nodes और
$32/hr जैसी लागत की जानकारी तुरंत देख सकें
- यह Go और Bubble Tea से लिखा गया था, और लगभग 7 months, 234 commits और लगभग 30 weekends के दौरान Claude के साथ vibe-coding sessions में बना
- शुरुआत में pods, nodes, deployments, services, command palette, watch-based live updates और Vim keybindings जैसे बुनियादी k9s clone features लगभग 3 weekends में चलने लगे
- core feature GPU fleet view ऐसा स्क्रीन था जो हर node की GPU allocation, utilization, DCGM-based metrics, temperature, power, memory और color-based status दिखाता था, और Claude ने एक ही बार में
FleetView struct, GPU/CPU/All tab filtering और allocation bars rendering तक generate कर दिया
- fleet view जोड़ने के बाद
:rs pods से pods view में लौटने पर table खाली हो जाती थी, live updates रुक जाते थे, nodes view में fleet view filter का stale data दिखता था, और fleet tab count भी गलत हो जाता था
- समस्या को trace करते हुए लेखक ने Claude द्वारा बनाए गए पूरे 1690-line
model.go को पहली बार पढ़ा, और पाया कि एक ही Model struct UI widgets, Kubernetes client, logs/describe/fleet state, navigation history, cache और mouse handling सब कुछ पकड़े हुए था
Update() method लगभग 500 lines का msg.(type) dispatch function था, जिसमें 110 switch/case branches थीं
- AI features बहुत तेज़ बना सकता है, लेकिन अगर उसे बिना constraints के लगातार काम करने दिया जाए तो architecture टूटने लगता है, और उसकी speed तब तक सफलता जैसी लगती है जब तक सब कुछ एक साथ ढह नहीं जाता
मलबे से निकले पाँच सिद्धांत
-
सिद्धांत 1: AI features बनाता है, architecture नहीं
- Claude ने fleet view, log streaming और mouse support जैसे individual features अच्छी तरह बनाए, लेकिन हर feature “अभी इसे काम कराओ” वाले संदर्भ में implement हुआ और वह उसी state को share करने वाले दूसरे features के साथ उसके संबंध नहीं समझ पाया
resourcesLoadedMsg handler में msg.gvr.Resource == k8s.ResourceNodes && m.fleetView != nil जैसी conditions आ गईं, और generic resource loading path के भीतर fleet view-specific logic मिल गई
- हर नए view के लिए custom behavior चाहिए होता तो उसी handler में नई branch जुड़ जाती, और पुराने view का data नए view में leak न हो इसके लिए कई fields को हाथ से साफ़ करना पड़ता था
model.go में m.logLines = nil, m.allResources = nil, m.resources = nil जैसी manual cleanup की 9 जगहें बिखरी हुई थीं, और इनमें से एक भी छूट जाए तो पिछले view का ghost data बचा रहता था
- विकल्प यह है कि code लिखने से पहले concrete interface, message type और ownership rule खुद लिखे जाएँ और उन्हें
CLAUDE.md में architecture invariants के रूप में रखा जाए
- उदाहरण के लिए नियम ऐसे हो सकते हैं: हर view
View trait/interface implement करे, कोई view दूसरे view की state को access न करे, async data केवल AppMsg variants के रूप में आए, और App struct केवल navigation और message dispatch संभाले
-
सिद्धांत 2: god object वह output है जिसे AI default रूप से बनाता है
- AI immediate prompt को सबसे कम ceremony के साथ पूरा करने के लिए single struct में सब कुछ ठूँस देने वाली दिशा में झुक गया
- key handling भी view के हिसाब से अलग नहीं थी, और एक ही
s key logs view में autoscroll, pods view में shell, और containers view में container shell का काम करती थी
- “pods में shell support जोड़ो” जैसी request को existing global key handler के पास एक branch ठूँसकर implement किया गया
Enter key भी contexts view, namespaces view, logs view और generic drill-down logic के बीच एक flat dispatch में m.currentGVR.Resource string comparison से branch करती थी
- सिर्फ
model.go फ़ाइल के भीतर m.currentGVR.Resource == का इस्तेमाल 20 से ज़्यादा बार type discriminator की तरह हुआ, और हर नया view जोड़ने पर कई handlers को छूना पड़ता था
- विकल्प यह है कि
App/Model में view-specific state fields न जोड़ी जाएँ, हर view को अलग struct बनाया जाए, और key binding को active view के keymap में रखा जाए; इस नियम को CLAUDE.md में लिखना चाहिए
- “view जोड़ना मतलब फ़ाइल जोड़ना होना चाहिए, और अगर existing view में बदलाव चाहिए तो रुककर पूछो” जैसे guardrails होने चाहिए ताकि AI सबसे छोटे रास्ते के रूप में नई branches न जोड़ता जाए
-
सिद्धांत 3: गति का भ्रम scope को फैलाता है
- k10s मूल रूप से GPU training cluster चलाने वाली एक narrow audience के लिए tool था, लेकिन vibe-coding ने pods, deployments, services, command palette, mouse support, contexts और namespaces जैसी चीज़ों को “मुफ़्त” जैसा महसूस कराया
- नतीजतन यह GPU-focused tool न रहकर सभी Kubernetes users के लिए एक general-purpose TUI, यानी लगभग k9s को फिर से बनाने वाली दिशा में फैल गया
- flat
keyMap में Fullscreen, Autoscroll, ToggleTime, WrapText, CopyLogs, ToggleLineNums, Describe, YamlView, Edit, Shell, FilterLogs, FleetTabNext, FleetTabPrev जैसे अलग-अलग view-specific bindings एक ही struct में मिले हुए थे
Autoscroll और Shell दोनों s पर थे, और dispatch current resource देखकर “काम” तो कर रहा था, लेकिन इससे keybinding को local रूप से समझना असंभव हो गया
- code लिखने की speed “shipping” जैसी दिखी, लेकिन हर feature ने god object के भीतर एक और branch जोड़ने की लागत पैदा की
- विकल्प यह है कि
CLAUDE.md में scope boundary साफ़ लिखी जाए कि k10s GPU cluster operators के लिए है, और supported views केवल fleet, node-detail, gpu-detail और workload तक सीमित हैं; generic resource views या k9s-duplicate features नहीं जोड़े जाएँगे
- AI भले ही लगभग असीम line budget दे सकता है, लेकिन complexity budget अभी भी सीमित है, इसलिए scope को पहले से reject करना ज़रूरी है
-
सिद्धांत 4: position-based data एक time bomb है
- k10s Kubernetes API से मिले resource को सीधे
type OrderedResourceFields []string के रूप में flatten कर देता था
- fleet view का sort function
ra[3] को Alloc, ra[2] को Compute, और ra[0] को Name मानता था, और column identity सिर्फ comments और resource.views.json की column order पर निर्भर थी
- अगर
resource.views.json में Instance और Compute के बीच एक column जोड़ दिया जाए तो ra[2], ra[3] पर निर्भर sort, conditional render और drill target चुपचाप गलत हो सकते थे
- compiler को
[]string का अर्थ पता नहीं होता, और JSON config भी sort behavior, conditional rendering या custom drill target को व्यक्त नहीं कर सकता, इसलिए Go code positional assumptions को hardcode कर देता था
- AI अक्सर
[]string या Vec<String> चुन लेता है क्योंकि उसे सीधे table widget में डालना आसान होता है, जबकि typed struct में upfront ceremony अधिक होती है, इसलिए तेज़ रास्ते में वह पीछे छूट जाती है
- विकल्प यह है कि structured data को render से ठीक पहले तक
FleetNode, PodInfo जैसे typed structs में रखा जाए, और sort row[3] जैसे positional access की जगह named fields पर किया जाए
- उदाहरण के लिए
FleetNode { name, instance_type, compute_class, alloc } जैसी structure column identity को type से व्यक्त करती है, जिससे गलत column sort जैसी impossible states बनती ही नहीं
- “Making impossible states impossible” Elm/Rust community का एक वाक्यांश है, जिसका मतलब है कि runtime checks पर निर्भर रहने की बजाय types को इस तरह design किया जाए कि invalid state बन ही न सके
-
सिद्धांत 5: AI state transition का मालिक नहीं होता
- Bubble Tea की architecture का मूल यह है that state केवल message-driven
Update() में बदले, लेकिन k10s ने यह नियम तोड़ा
updateTableMsg handler एक tea.Cmd closure return करता था, और इसी closure के भीतर m.updateColumns(m.viewWidth), m.updateTableData(), m.table.SetCursor(savedCursor) जैसी calls से Model fields बदले जाते थे
- Bubble Tea
tea.Cmd को अलग goroutine में चलाता है, इसलिए closure जब m.resources, m.table, m.viewWidth को पढ़-लिख रहा होता था, उसी समय main goroutine का View() भी वही fields पढ़ सकता था
- कोई lock या mutex नहीं था, और
<-m.updateTableChan सिर्फ update signal का इंतज़ार करता था; वह View() को half-written state पढ़ने से नहीं रोकता था
- यह structure साफ़ data race थी, और ज़्यादातर समय सब कुछ चलता रहता था, लेकिन कभी-कभी display टूट जाती थी
- विकल्प यह है कि background worker सीधे UI state mutate न करे, बल्कि typed messages को channel से भेजे, और main event loop उन messages को लेकर state mutation लागू करे
- concurrency rule यह होना चाहिए कि background task सीधे UI state न बदलें, अपने results typed messages के रूप में भेजें, और
render()/view() side effect, I/O या channel operation के बिना pure function हो
CLAUDE.md और agents.md में डालने योग्य guardrails
-
architecture invariants
- हर view को
View trait/interface implement करना चाहिए और दूसरे view की state को access नहीं करना चाहिए
- सारा async data
AppMsg variants के रूप में आना चाहिए, और background task को fields सीधे mutate नहीं करने चाहिए
- नया view जोड़ने के लिए existing views में बदलाव ज़रूरी नहीं होना चाहिए
App struct navigation और message dispatch संभालने वाला एक thin router होना चाहिए
-
state ownership rules
App/Model struct में view-specific state fields नहीं जोड़नी चाहिए
- हर view अलग struct के रूप में होना चाहिए और अपने key bindings खुद declare करने चाहिए
- app को keys active view तक dispatch करनी चाहिए, और नया keybinding global handler की बजाय उसी view के keymap में जोड़ा जाना चाहिए
- अगर view जोड़ने के लिए existing views बदलने पड़ें तो रुककर पुष्टि करनी चाहिए
-
scope
- k10s सभी Kubernetes users के लिए नहीं, बल्कि GPU cluster operators के लिए tool होना चाहिए
- supported views को fleet, node-detail, gpu-detail और workload तक सीमित रखना चाहिए
- pods, deployments, services जैसे generic resource views नहीं जोड़े जाने चाहिए
- k9s features की नकल करने वाली functionality नहीं जोड़नी चाहिए
- जो feature request GPU training jobs चलाने वाले operators के काम की न हो, उसे reject कर देना चाहिए
-
data representation
- structured data को
[]string, Vec<String> या positional arrays में flatten नहीं करना चाहिए
- data को render call से ठीक पहले तक typed structs में बहना चाहिए
- column identity array index से नहीं, struct field name से आनी चाहिए
- sort functions को
row[3] जैसे positional access पर नहीं, typed fields पर काम करना चाहिए
- display के लिए string बनाना केवल
render()/view() functions के भीतर होना चाहिए
-
concurrency rules
- watcher, scraper, API call जैसे background tasks को UI state सीधे mutate नहीं करनी चाहिए
- background tasks को results typed messages के रूप में channel पर भेजने चाहिए
- केवल main event loop को received messages से state mutation apply करनी चाहिए
render()/view() side effect, I/O या channel operations के बिना pure function होना चाहिए
- अगर async work के result से state बदलनी है तो नया
AppMsg variant define करना चाहिए
दोबारा बनाने का तरीका
- k10s को Rust में फिर से लिखा जाएगा; वजह यह नहीं कि Rust “बेहतर” है, बल्कि यह कि लेखक को वह ऐसी language लगती है जिसे वह सीधे steer कर सकता है
- जिस भाषा का पर्याप्त अनुभव होता है, उसमें अक्सर यह पहले महसूस हो जाता है कि क्या गलत है, भले ही उसे शब्दों में समझाने में समय लगे; vibe-coding इस संवेदना की जगह नहीं ले सकता
- जब AI plausible code देता है, तब यह पहचानने की क्षमता चाहिए कि वह वास्तव में कब खराब है
- नए version में code लिखने से पहले concrete interface, message type और ownership rule जैसे design work इंसान हाथ से पहले करेगा
- पहले जिन architecture decisions को AI गलत तरीके से ले रहा था, अब उन्हें पहले prompt से पहले documents में तय किया जाएगा
- मौजूदा TUI और project links यहाँ हैं: k10s Github और K10S.DEV
अतिरिक्त टिप्पणी
- Bubble Tea The Elm Architecture पर आधारित Go TUI framework है, और k10s की architecture problems Bubble Tea की नहीं बल्कि k10s implementation की थीं
- “Making impossible states impossible” Elm/Rust community का वह expression है जिसमें invalid state को runtime पर जाँचने के बजाय type design के जरिए बनने ही नहीं दिया जाता
- जैसे AI writing में “em-dash” एक smell हो सकता है, वैसे ही AI coding में “god-object” एक smell हो सकता है, और vibe-coding implementation को सस्ता महसूस कराकर focus loss और bloat की ओर ले जा सकता है
1 टिप्पणियां
Hacker News टिप्पणियाँ
जो लोग कहते थे कि generated code ठीक-ठाक है, वे ज़्यादातर उस कोड को पढ़ते ही नहीं थे
लेख में सुझाए गए mitigation भी ज़्यादा समय तक टिकना मुश्किल है। सिस्टम या component design करते समय “एक view दूसरे view की state को access नहीं करता” जैसी invariant conditions बन जाती हैं, और कभी न कभी ऐसा feature जोड़ना पड़ता है जो उनसे टकराता है
तब आमतौर पर या तो feature छोड़ना पड़ता है, या उसे invariant conditions के ऊपर अटपटा और inefficient तरीके से चढ़ाना पड़ता है, या फिर invariant conditions को ही बदलना पड़ता है। यह सिर्फ context का नहीं बल्कि judgment का सवाल है, और मौजूदा model यह judgment बहुत बार गलत करता है
अगर architectural constraints को साफ़-साफ़ लिख भी दें, तो agent उन्हें बदलने की ज़रूरत होने पर भी उसी में मरोड़कर फिट करने की कोशिश में जटिल और maintain न हो सकने वाला code बना देता है। अगर आप इसे इंसानी code से भी ज़्यादा ध्यान से नहीं पढ़ते, तो आखिर में “खुद को खा जाने वाला code” बन जाता है और बहुत देर से पता चलता है
असली बात यह है कि AI किन जगहों पर कठिनाई महसूस करता है, उसे पहचानकर काम को उसके लिए आसान बनाया जाए। जैसे बेहद छोटा context, साफ़ boundaries वाला modularization, input/output से अलग pure modules, interface के पीछे चीज़ों को छिपाना, 1 सेकंड के अंदर चलने वाले 100 tests, benchmarks वगैरह
AI boundaries और छोटे context में अच्छा काम करता है। अगर आप उसे यह नहीं देते, तो performance गिरती है, और इसकी ज़िम्मेदारी tool इस्तेमाल करने वाले की है
कोई भी spec वास्तविक दुनिया के सामने टिक नहीं पाती, और चाहे कितनी भी जांच-पड़ताल व design कर लो, spec के भीतर कुछ invariant conditions अंत में गलत साबित हो जाती हैं
इंसान development के दौरान ऐसी स्थिति में आए तो एक कदम पीछे हटकर सोच सकता है कि invariant condition गलत थी या नहीं, और उसे बदलने का असर क्या होगा। दूसरी तरफ AI अक्सर गलत assumptions या design के नीचे भी किसी तरह hacked solution निकालने की कोशिश करता है, और पूरे मामले को दोबारा evaluate करने वाली insight उसमें कम होती है
अच्छे workflow और verification से इसमें सुधार हो सकता है, लेकिन Claude Code जैसे tools का default strong area यह नहीं है, और इसकी सीमाएँ हैं
शुरुआत में मज़बूत principles तय किए और कुछ usages को हाथ से migrate करके confidence लिया। पूरा migration इतना बड़ा और महँगा था कि लगभग 10 साल टलता रहा, इसलिए cost कम करने के लिए AI से speed-up करने की कोशिश की
AI mechanical और simple 80% cases में ठीक था। बाकी 20% में framework changes चाहिए थे, जिनमें ज़्यादातर API field जोड़ने जैसे छोटे बदलाव थे, लेकिन एक-दो जगह conceptual redesign चाहिए था
एक system का backend 99% cases में कुछ खास data बना सकता था, लेकिन कुछ महत्वपूर्ण cases में वह logically बना ही नहीं सकता था, इसलिए उसे बाहर से report करना पड़ता था। लेकिन एक अहम optimization इस assumption पर बना था कि “वह असंभव है”
AI tool यह स्थिति पकड़ नहीं पाया और migration logic ऐसा जोड़ दिया जैसे वह ठीक से काम करेगा। deployment तरीका ऐसा था कि अभी production bug नहीं बना, लेकिन partner team से सही सवाल पूछते-पुछते पता चला कि ऐसी ज़रूरत और जगहों पर भी है
आखिरकार एक इंसान गहराई से इसमें लगा हुआ था, इसलिए मामला बड़ी समस्या नहीं बना। बेहतर verification tools और ज़्यादा smart models भविष्य में ऐसे migrations को आसान बना सकते हैं, लेकिन अभी generated code दिखने में सुंदर होते हुए भी अंदर से टूटा हो सकता है, इसलिए नज़दीक से देखते रहना पड़ता है
करीब दो महीने से एक अजीब architecture pattern इस्तेमाल कर रहा था, और हर बार थोड़ा असहज लगता था, लेकिन कल रात ही समझ आया कि यह अच्छा abstraction नहीं है और इसे बेहतर तरीके से बाँटा जा सकता है
अगर LLM से code generate करवाते, तो वह असहजता इतनी साफ़ महसूस नहीं होती, इसलिए समस्या पकड़ने और समाधान खोजने में और ज़्यादा समय लगता। peripheral चीज़ें generate कर लो तो ठीक है, लेकिन core functionality अभी भी ज़्यादातर खुद लिखनी चाहिए
मान लो उन्हें किसी precise formal language में लिखा भी जाए, तब भी agent के नीचे का LLM यह समझने में सक्षम नहीं है कि वे invariant conditions क्यों ज़रूरी हैं और क्यों महत्वपूर्ण हैं। tokens और formal specs को जोड़कर proof तक लिखने वाला LLM आ सकता है, लेकिन prompt के अनौपचारिक हिस्से से पैदा हुआ अजीब code फिर भी आता रहेगा
constraints और prompts को सिर्फ requirements list या spec में जोड़ देने भर से यह नहीं रुकेगा। आप बेहतर जाल बना लें, जीव फिर भी निकल जाएगा
समस्या है prompt या task को satisfy करने के लिए code पर code चढ़ाते जाना, यानी code bloat। कई बार कम code बेहतर होता है, और ऐसा इंसान चाहिए जो अनुमान लगा सके कि दूसरे लोग क्या चाहेंगे और क्या expect करेंगे। generator अच्छे हैं, लेकिन fire hose की तरह नहीं, थोड़ा संयम से इस्तेमाल होने चाहिए
जब Copilot एक लाइन auto-complete करता था, तब कहा गया “फिर भी पूरा function तुम्हें लिखना होगा”; जब function पूरा करने लगा, तो कहा गया “function के आसपास का logic तुम्हें लिखना होगा”; जब वह logic भी पूरा होने लगा, तो कहा गया “feature तुम्हें लिखना होगा”
अब feature भी पूरा कर देता है तो कहा जा रहा है “फिर भी architecture तुम्हें लिखना होगा।” ये models architecture हल कर पाएँगे या नहीं, पता नहीं, लेकिन expectations का लगातार खिसकना दिलचस्प है
AI एक लाइन पूरी करे, पूरा function पूरी करे, feature और ticket तक पूरी कर दे, फिर भी आपको code पढ़कर समझना ही होगा
मैं AI हमेशा इस्तेमाल करता हूँ और यह लगातार बेहतर हो रहा है, लेकिन अभी भी हर लाइन review करता हूँ। individual line level पर भी आज यह पिछले साल की tab autocomplete से बहुत बेहतर है, ऐसा कहना मुश्किल है; कभी बहुत अच्छा होता है, कभी सच में बहुत खराब
LLM software development में शानदार हैं, लेकिन जब आप उन्हें architecture लिखने नहीं देते। modules, structs, enums आपको खुद बनाने चाहिए, और जहाँ तक संभव हो fields और variants भी खुद जोड़ने चाहिए
हर struct, enum, field, module पर doc comments लिखें, फिर LLM को उन modules और data structures की ओर point करके ज़रूरी function bodies वगैरह भरवाएँ — यह तरीका अच्छा है
“important path में कभी blocking मत डालना” कई बार कहने पर भी LLM important path में blocking डाल देता है, और “अगर X करो तो Y type tests चाहिए” कहने पर X कर देता है लेकिन tests छोड़ देता है
इंसान भी निर्देश 100% नहीं मानते, लेकिन LLM ज़्यादा random हैं। इंसानी गलती में आम तौर पर यह कम होता है कि वह ठीक वही करे जो आप नहीं चाहते
LLM code के अंदर की महत्वपूर्ण invariants देखकर भी bypass बना देता है, ऐसे tests लिख देता है जो failure को success जैसा दिखाएँ, और फिर कह देता है कि उसने आपकी माँग पूरी कर दी, जबकि 5,000 lines के commit में बात दब जाती है
मुझे भरोसा है कि LLM शानदार हैं और भविष्य हैं, और इसी वजह से मैं इनके लिए https://GitHub.com/Cuzzo/clear नाम की language बना रहा हूँ। जहाँ global context नहीं चाहिए वहाँ भी global context माँगने वाली language problem को पार करना होगा, तभी इनके साथ काम करना आसान होगा
कुछ सफलताएँ मिली हैं, लेकिन कई बार इतना frustration हुआ कि लगता है पता नहीं यह मानसिक ऊर्जा लगाने लायक था भी या नहीं
मतलब यह नहीं कि architecture महत्वपूर्ण नहीं है, बल्कि यह कि जो architecture कल फिट बैठता था, वह ज़रूरी नहीं कि आज भी फिट बैठे
coding agents इस्तेमाल करते समय मैंने कुछ नियम बनाए थे
पहला, अगर agent से code generate करवा रहा हूँ, तो वह ऐसा होना चाहिए जिसे समय मिलने पर मैं खुद सही तरीके से लिख सकूँ — इस पर मुझे पूरा भरोसा हो
दूसरा, अगर ऐसा नहीं है, तो generated चीज़ को पूरी तरह समझकर खुद दोबारा बना सकूँ, तब तक आगे नहीं बढ़ता
तीसरा, अगर दूसरे नियम को तोड़ा, तो cognitive debt बन सकता है, लेकिन project complete घोषित करने से पहले उसका पूरा भुगतान करना होगा
debt जितना बढ़ता है, बाद में generated code की quality गिरने की संभावना उतनी बढ़ती है, और यह compound interest की तरह बढ़ता महसूस होता है। personal projects में यह तरीका मज़ेदार है, बहुत कुछ सीखने को मिलता है, और अंत में ऐसा codebase बचता है जिसे आराम से समझा जा सके
codebase से जुड़े रहते हुए भी team का bottleneck न बनें — इसका कोई balance point चाहिए
Claude PhD-level mathematician है, मैं नहीं; लेकिन मुझे solution में कौन-सी properties चाहिए और उसे सही कैसे test करना है, यह ठीक-ठीक पता था। इसलिए अपने simple और naive solution के बजाय मैंने Claude का solution रखा, pull request में यह बात लिखी, और सबको यह सही निर्णय लगा
ऐसे cases में exception होना चाहिए या नहीं, यह दिलचस्प सवाल है। अगर advanced math ही नहीं, coding में भी AI मुझसे बहुत बेहतर हो जाए, तो क्या मैं hand-coding पूरी तरह छोड़ दूँगा — इस assumption पर कि भले code को खुद judge करने की क्षमता खो दूँ, tests को judge कर सकता हूँ? यह और भी रोचक सवाल है
क्योंकि जो debt जमा हो रहा है, वह ठीक-ठीक code की समझ की कमी है, इसलिए यह ज़्यादा सटीक शब्द है
फिर AI के साथ अचानक अलग व्यवहार क्यों किया जाए, समझ नहीं आता
आखिरकार फैसला risk और reward से होना चाहिए। गलती होने पर नुकसान क्या है, test और review में पकड़े जाने की संभावना कितनी है, और सही होने पर लाभ क्या है — यह सब देखना चाहिए। libraries और external services पर भी यही लागू होता है
tests के बिना update न हो सकने वाले crypto contract के complex financial rules और internal log data को visualize करने वाला viewer — दोनों का risk बिल्कुल एक जैसा नहीं है
theory में यह अच्छा लगता है, लेकिन असल में इंसान हमेशा कुछ न कुछ mental shortcuts ले ही लेता है
किसी अनजान codebase में problem fix करते समय, जो काम मैंने खुद किया हो और जो agent ने किया हो लेकिन जिसे मैंने “पूरी तरह समझ लिया” माना हो — एक हफ़्ते बाद दिमाग़ में बची चीज़ों की मात्रा अलग होती है। खुद करूँ तो वह general knowledge की तरह जमा होती है और महत्वपूर्ण हिस्से अक्सर याद रहते हैं; लेकिन agent के काम को अपना बनाने की कोशिश करूँ, तो उस समय समझा हुआ लगता है पर बहुत जल्दी भूल जाता हूँ
इसलिए मैंने निष्कर्ष निकाला कि ऐसे मामलों में LLM की मदद आम तौर पर मेरे goals के लिए हानिकारक है, और यह बात समय या business pressure जैसी दूसरी चिंताओं को अलग रखकर भी सही लगती है
मेरे साथ भी यही हुआ था
धोखा कुछ इस तरह चलता है। अच्छे codebase में AI बहुत-सी features बना सकता है, और वह तेज़, सुरक्षित और सही भी लगता है। खासकर ऐसे domain में जिसे आप अच्छी तरह नहीं जानते, यह असर और ज़्यादा होता है
समय के साथ codebase बढ़ता है, navigation time लंबा होता है, failure rate बढ़ता है। आप इसे मानना नहीं चाहते, इसलिए और ज़ोर लगाते हैं, और तब जाकर रुकते हैं जब बदलाव लगभग असंभव हो जाते हैं
फिर code को पलटकर देखो तो spaghetti कहना भी कम है — हालत Great Wall of China जैसी होती है
आखिरकार 140k lines में से 75k lines delete करनी पड़ीं, और agent coding में गहराई से डूबे वे 3 महीने बर्बाद लगे। बेकार features बनाए, bugs बढ़ाए, code का mental model खो दिया, ऐसे मुश्किल decisions चूक गए जो सिर्फ code के भीतर होने पर दिखते हैं, और users के लिए भी असफल रहे
तंज नहीं कर रहा, सच में जिज्ञासा है कि शुरुआती expectation क्या थी और वह कहाँ से आई
LLM के लिए expectations कुछ और ही लगती हैं। अगर आपने सिर्फ online मिले किसी random “developer” को feature का summary description दिया होता और बदले में आधा-टूटा implementation dump मिला होता, तो किसी को हैरानी नहीं होती
लेकिन लोग कभी-कभी उस मशीन से चमत्कार की उम्मीद करते हैं जो लम्बी hallucinations करती है — ऐसे चमत्कार जिनकी उम्मीद वे इंसानों से भी नहीं करते। यह भरोसा कहाँ से आता है, समझना चाहता हूँ
जैसे बड़ा शहर, छोटे-छोटे शहरों का समूह होता है। एक नक्शा हो, आप local area तक zoom करें और उसी दायरे में काम करें। एक कप coffee पीने के लिए New York की हर detail जानना ज़रूरी नहीं
maintainable sound architecture बनाना tool इस्तेमाल करने वाले की ज़िम्मेदारी है। AI उसे रोकता नहीं, और tool सही तरह पकड़ा जाए तो उल्टा मदद भी कर सकता है
जैसे generated AI code को तुरंत legacy code मानना, उसके चारों ओर मज़बूत encapsulation boundaries और अच्छी तरह defined interfaces रखना, और फिर उसे ज़्यादा manual flow से integrate करना
one-shot prompts से लेकर inline code generation तक एक spectrum है, और problem तथा codebase की जगह के हिसाब से सही तरीका बदलता है
one-shot generation prototype phase में ज़्यादा फिट बैठती है, जहाँ spec बार-बार दोहराई जाती है; prototype settle हो जाए तो module/file-level generation तक उतरकर ज़्यादा systematic तरीके से काम करना चाहिए, और उस layer पर अच्छा mental model बनाए रखना चाहिए
अगर पढ़ा था लेकिन समझ नहीं आया, तो हर output पर detailed comments माँग सकते थे; और अगर पता है कि codebase बढ़ने पर model struggle करता है, तो complexity बढ़ने के साथ outputs की और सख़्ती से जाँच होनी चाहिए
यानी higher-quality code islands बनाना, AI से developer intent और business rules को reconstruct करने में मदद लेना, और target modules में seam और unit tests बनवाना
AI का उपयोग सिर्फ throughput बढ़ाने के लिए होना ज़रूरी नहीं; वह बाद की hand-coding या agent implementation में मदद करने वाला flexible exploration/refactoring tool भी हो सकता है
ऐसी पोस्ट पढ़ते ही मैं हमेशा सोचता हूँ कि लोग AI से जो speed बताते हैं, उसकी तुलना मैं हाथ से coding करके जो speed पाता हूँ उससे करूँ
संयोग से मैं पिछले 7 महीनों से एक 3D MMO project पर काम कर रहा हूँ। अभी वह playable है, लोगों को मज़ेदार भी लग रहा है, graphics ठीक हैं, और server पर सैकड़ों users को आसानी से रखा जा सकता है। architecture भी काफ़ी अच्छा है, इसलिए features बढ़ाना आसान है, और शायद लगभग 1 साल development के बाद launch किया जा सकेगा
लेकिन मूल पोस्ट का लेखक 7 महीनों की vibe coding के बाद भी basic TUI नहीं बना पाया। feature speed ऊँची महसूस हो सकती है, लेकिन ऐसी basic UI बनाने के मामले में यह यक़ीन न करने लायक धीमा है। अच्छी TUI libraries बहुत हैं, और यह तो ऐसा काम है जहाँ ज़रूरी data से table भरनी होती है — हाथ से कुछ हफ़्तों में बन सकता है
AI इस्तेमाल करने पर तेज़ progress का अहसास बहुत मजबूत होता है, लेकिन असल में कई बार यह manual coding से बहुत धीमा लगता है। productivity data भी शायद यही दिखाता है कि AI users ख़ुद को ज़्यादा तेज़ महसूस करते हैं, पर actual output कम होता है
software development में सबसे बड़ा time sink stakeholders की expectations और solution को align करने वाली meetings होती हैं। उस नज़रिए से AI लगभग मदद नहीं करता, इसलिए proposal से test loop में entry तक का person-hours compare करें तो results निराशाजनक लग सकते हैं
लेकिन problem solving, bug fixing, और approved solution को implement करने में मुझे लगता है कि पहले से कम-से-कम 10x सुधार हुआ है। सिर्फ़ समय ही नहीं, observed behavior को interpret करने और problem investigate करने की क्षमता भी बेहतर हुई है
हालाँकि कुछ लोग AI से valuable और accurate results निकलवा ही नहीं पाते। अगर आपको ठीक-ठीक पता है कि क्या चाहिए और कैसे चाहिए, तो AI बहुत मददगार है। जो मैं वैसे भी करने वाला था, वह उससे ज़्यादा तेज़ी से करवा सकता हूँ। लेकिन अगर मुझे ख़ुद नहीं पता कि क्या चाहिए, तो AI progress के लिए हानिकारक है
लोग जब LLM से बनाई चीज़ें दिखाते हैं, तो वे इसलिए ज़्यादा impressive नहीं लगतीं क्योंकि उनमें से ज़्यादातर चीज़ें हाथ से भी बहुत कम समय में बनाई जा सकती हैं
मैंने यह भी नहीं देखा कि impressive software की संख्या बढ़ रही हो, और यह बात इस अवलोकन से मेल खाती है कि LLM अभी महत्वपूर्ण समस्याओं की तुलना में साधारण समस्याएँ सुलझाने में ज़्यादा इस्तेमाल हो रहे हैं
और एक चीज़ जिसका कम ज़िक्र होता है, वह है code quality
vibe-coded codebase इस बात का शानदार उदाहरण है कि LLM code writing में इतने अच्छे नहीं हैं। वे अपनी ग़लतियाँ सुधारते-सुधारते तुरंत फिर बना देते हैं, और pattern usage भी consistent नहीं होता
हाल के Claude versions कभी-कभी ऐसे “interesting” code style choices कर देते हैं जो मौजूदा codebase style से मेल नहीं खाते
“senior developer” जैसी भाषा में उसे बार-बार रोकना पड़ता है
“code लिखने से पहले specific interfaces, message types, ownership rules खुद design करना” — यही तो coding का मुश्किल हिस्सा है
architecture हो तो code लिखना बहुत आसान हो जाता है। अगर आप खुद code नहीं लिखते, तो यह पकड़ना मुश्किल हो जाता है कि आपने null allow करने वाला API design कर दिया, जबकि database उसे allow नहीं करता; या allow करता भी हो तो कोई और छोटा issue छूट गया
यह समझ नहीं आता कि यह लेख लिखने के बाद भी लेखक को क्यों नहीं समझ आया कि समस्या AI है। सिर्फ इसलिए नहीं कि architecture AI को सौंप दिया गया, बल्कि इसलिए भी कि AI जो कुछ कर रहा था उसे ध्यान से देखा ही नहीं गया
AI एक सजाया-संवारा code generator है, और वह जो भी करे उसकी जाँच करनी ही होगी। software engineering का कठिन हिस्सा code लिखना नहीं, बल्कि उसके अलावा बाकी सब कुछ हमेशा से था
जिन्हें coding मुश्किल लगती है, वे AI coding को सच में पसंद करते हैं, क्योंकि जो पहले कठिन था वह आसान हो गया
दूसरी तरफ जिन्हें coding आसान लगती है, उनके लिए coding abstraction, maintainability, और extensibility की समस्या है। ऐसी sensible foundation रखना कठिन है जिस पर software बढ़ सके; सही abstraction मिल जाए तो बाकी चीज़ें अपेक्षाकृत आसान हो जाती हैं
ऐसे लोगों के लिए AI coding एक उपयोगी tool है, लेकिन कोई magical tool नहीं। मूल लेखक ने AI की सीमाएँ देख लीं, इसलिए वह दूसरी श्रेणी में आता है — उसने वह कठिन हिस्सा देखा जो AI नहीं कर पाता
एक तरफ वे लोग हैं जो strong tab autocomplete या side-window chatbot इस्तेमाल करते हुए भी हर चीज़ को साफ़-साफ़ review करते हैं, और दूसरी तरफ ऐसे नए editors का प्रचार हो रहा है जहाँ Steve Yegge की तरह दर्जनों agents coordinate किए जाते हैं, मानो code का ज़्यादातर हिस्सा पढ़ना ही न पड़े: https://steve-yegge.medium.com/welcome-to-gas-town-4f25ee16d...
पहला समूह design, interfaces, data structures पर अब भी गहराई से सोचता है और कड़ा review करता है। दूसरा समूह यही नहीं करता, इसलिए ज़्यादा चिंता होती है
मैं plan → red/green/refactor approach अपनाता हूँ, और planning अपने-आप में काफ़ी plausible और well-grounded लगती है क्योंकि वह docs और forum discussions सब निगल लेती है
समस्या यह है कि काम शुरू होते ही कोई न कोई ऐसी जगह ज़रूर आती है जहाँ documentation और implementation वास्तव में अलग निकलते हैं। हो सकता है tool combination उस तरह कभी इस्तेमाल ही न हुआ हो, docs पुराने हों, या बस bug हो
फिर भी अगर project या feature goal काफ़ी साफ़ है और चीज़ local में run/test की जा सकती है, तो agent architecture के dead-end में loop करके उससे निकल भी सकता है। वह dependencies और library code तक देखता है, upstream fixes भी suggest करता है — deep debugging session में मैं भी कुछ ऐसा ही करता
इसलिए उबाऊ काम खुद करने के बजाय निर्देश देने और supervise करने के तरीके से मैं काफ़ी संतुष्ट हूँ। लेकिन टीम के कई लोग architecture problems में इतनी गहराई तक जाते ही नहीं; default रूप में वे “architect को escalate” कर देते हैं, और लंबी अवधि में यह अच्छा नहीं लगता
ऐसा लगता है कि वह window, जिसमें आप सब कुछ run करके समझ सकते हैं, तेज़ी से बंद हो रही है। फिर भी जैसे compiler machine code में कैसे बदलता है या modern CPU की branch prediction/caching को पूरी तरह समझे बिना भी हम उनका उपयोग करते हैं, वैसे ही शायद हम नए tools और frameworks बनाकर adapt कर लेंगे
मेरे पास code का बहुत अनुभव नहीं है, लेकिन outputs verify करते हुए, क्या सही है क्या गलत — यह देखते हुए, मैं अब तक सबसे ज़्यादा सीख रहा हूँ
इसलिए मुझे नहीं लगता कि यह जल्दी बहुत बदलने वाला है। जब लोग पूछते हैं “Claude का output इतना अच्छा कैसे निकलवाते हो?”, तो मेरा जवाब हमेशा यही होता है: “मैंने ध्यान से देखा, समस्याएँ ढूँढ़ीं, और Claude से उन्हें ठीक करवाया।” बस यही है, लेकिन यह सुनकर सामने वाले की आँखें अक्सर वहीं मुरझा जाती हैं
यह कुछ वैसा ही है जैसे Google ने information ढूँढ़ना आसान कर दिया, लेकिन अच्छी और बुरी information में फर्क करने वाले human element को खत्म नहीं किया
पहले समस्या पर सोचो, structure और APIs design करो, और उसके बाद ही AI को implementation दो
शीर्षक है “मैं फिर हाथ से code लिखने लगा”, लेकिन असल में जो किया जा रहा है वह है “code लिखे जाने से पहले design work हाथ से करना”
तब भी code शायद Claude ही generate कर रहा है
और भी गंभीर बात यह है कि 7 महीनों तक generated source code देखे बिना यह मान लेना कि vibe-coding project ठीक चल रहा है, और उस पर domain तक खरीद लेना — यह समझना मुश्किल है
अगर यह side project हो और आप diff देखते हुए धीरे-धीरे verify कर रहे हों, तो code को बहुत गहराई से न देखना इतना भी अजीब नहीं है। यह अलग तरह का workflow ज़रूर है, पर पागलपन नहीं
यह सब देखकर लगता है कि developers project management और product management के lessons को speedrun कर रहे हैं
अब वे देख रहे हैं कि specs उपयोगी होती हैं, और बहुत सारा गलत code लिखवा देने से project तेज़ नहीं हो जाता। developers meetings और discussions को code writing में रुकावट मानकर चिढ़ते हैं, लेकिन ऐसे process अक्सर इसलिए होते हैं ताकि सब मिलकर गलत चीज़ को और ज़्यादा न लिखें
अब यह भी समझ आ रहा है कि work management उपयोगी है, और अब जब लोग कह रहे हैं कि सारा design पहले ही कर लेना चाहिए, तो बात waterfall development की ओर जा रही है
आगे चलकर वे prototyping को भी कोई नाम देंगे, फिर पुरानी और नई requirements को साथ लेकर चलने वाली incremental features की बात करेंगे, और आखिर में कहेंगे कि customers को ज़्यादा involve होना चाहिए
project managers और product managers असल में क्या करते हैं, यह देखना चाहिए। वे code नाम के product को lead करते हैं, लेकिन उनसे code पढ़ने की उम्मीद नहीं की जाती; उन्हें यह सब natural language के ज़रिए हासिल करना होता है
क्या इन्हें लगता है कि इंसान कभी टूटे हुए solutions नहीं लिखते? क्या इन्हें लगता है कि teams कभी गलत दिशा में जाकर एक हफ़्ता, या कई महीने, बर्बाद नहीं करतीं? अब vibe coding के साथ वह सब 30 मिनट में अनुभव किया जा सकता है। एक पूर्व technical product manager के रूप में कहूँ तो यह ठीक वैसा ही लगता है
असल में हाथ से code लिखने जैसा कुछ हो ही नहीं रहा, इसलिए शीर्षक और निष्कर्ष के बीच का फर्क भ्रम पैदा करता है