अनुसंधान

स्व-चालित कोडबेस की ओर

Wilson Lin24 मिनट में पढ़ें
स्व-चालित कोडबेस की ओर

लंबे समय तक चलने वाली स्वायत्त कोडिंग को स्केल करने पर हमारे अनुसंधान को मिली प्रतिक्रिया से हम उत्साहित हैं।

यह काम मौजूदा मॉडल्स की सीमाओं को आगे बढ़ाने के लिए आंतरिक अनुसंधान के रूप में शुरू हुआ था। इस अनुसंधान के हिस्से के रूप में, हमने हज़ारों एजेंट्स का समन्वय करने और उनके व्यवहार का अवलोकन करने के लिए एक नया एजेंट उपयोग में लाने की व्यवस्था बनाई। पिछले महीने तक, हमारा सिस्टम इतना स्थिर हो गया था कि वह लगातार एक सप्ताह तक चल सकता था, और हमारे अनुसंधान प्रोजेक्ट (एक वेब ब्राउज़र) में ज़्यादातर कमिट वही कर रहा था। इस ब्राउज़र को बाहरी उपयोग के लिए नहीं बनाया गया था, और हमें उम्मीद थी कि कोड में कुछ कमियाँ होंगी।

हालाँकि, विचित्रताओं के बावजूद, यह तथ्य कि हज़ारों एजेंट्स मिलकर ऐसा काम कर सकते थे जो लगभग पूरी तरह मानव हस्तक्षेप के बिना चलने योग्य था, हमें साझा करने लायक एक मील का पत्थर लगा। तब से, हमने अपना अनुसंधान जारी रखा है, और हम इस पर अधिक गहराई से बताना चाहते थे कि यह उपयोग में लाने की व्यवस्था कैसे बनाई गई।

हम इस अनुसंधान का एक हिस्सा कुछ उपयोगकर्ताओं के लिए आज़माने हेतु भी उपलब्ध करा रहे हैं।

पृष्ठभूमि

हमारा अनुसंधान प्रोजेक्ट शुरुआत में मेरा एक व्यक्तिगत साइड प्रोजेक्ट था।

एक ब्राउज़र एक दिलचस्प बेंचमार्क लगा। वह इतना जटिल था कि अत्याधुनिक मॉडल्स की सीमाएँ सामने आ सकें, और उसमें कई अलग-अलग उपप्रणालियाँ थीं जिन्हें मिलकर काम करना था।

मेरी शुरुआती योजना JavaScript समर्थन के बिना वेब पेजों की रेंडरिंग को सपोर्ट करने की थी। मैंने Opus 4.5 को प्रॉम्प्ट करके शुरुआत की और उससे ब्राउज़र इंजन बनाने के लिए एक विस्तृत योजना लिखने को कहा। मैं उसे बार-बार "आगे बढ़ते रहो" कहकर उकसाता था, ताकि देख सकूँ कि वह उस योजना को कितनी दूर तक ले जा सकता है।

यह जल्दी ही विफल हो गया। मॉडल अक्सर भूल जाता था कि वह क्या कर रहा है, लक्ष्य से बहुत दूर होने के बावजूद बार-बार सफलता घोषित कर देता था, और जटिल इम्प्लीमेंटेशन विवरणों में फँस जाता था। लेकिन उसमें गहरे ज्ञान और इंटेलिजेंस के संकेत दिखे। वह छोटे-छोटे हिस्सों में अच्छा कोड लिख सकता था।

मुख्य समस्या यह थी कि ब्राउज़र का काम बहुत भारी था और उसे उप-कार्यों में बाँटना ज़रूरी था। इसके बाद, मैंने एजेंट से मुख्य कार्यों का एक डिपेंडेंसी ग्राफ तैयार कराया, जिन्हें एजेंट समानांतर में उठा सकें। कार्यों के लिए एजेंट्स को मैन्युअल रूप से स्पॉन किया जाता था, और जब वे रुक जाते थे तो उन्हें फिर से आगे बढ़ाया जाता था। इससे थ्रूपुट बढ़ा, लेकिन नतीजे बहुत बेहतर नहीं हुए। एजेंट एक-दूसरे से संवाद नहीं कर सकते थे और न ही पूरे प्रोजेक्ट पर फ़ीडबैक दे सकते थे। सिस्टम को और अधिक गतिशील होने की ज़रूरत थी।

इसी दौरान, GPT-5.1 (और बाद में GPT-5.2) ने निर्देशों का सटीक रूप से पालन करने की क्षमता में बेहतर परिणाम दिखाने शुरू किए। यह लंबे समय तक चलने वाले एजेंट्स के लिए अच्छा मेल लगा, इसलिए हमने इन प्रयोगों के आधार पर OpenAI मॉडल्स का उपयोग करने के लिए अपनी उपयोग में लाने की व्यवस्था अपडेट की।

इस बिंदु तक, उपयोग में लाने की व्यवस्था JavaScript के बिना वेब ब्राउज़र का एक सरल संस्करण बिल्ड कर सकता था, लेकिन एक ही एजेंट के साथ एक पूरा ब्राउज़र इंजन बनाना अत्यधिक धीमा होता।

यहीं से हमारे अनुसंधान का अगला दौर शुरू हुआ। क्या हम 10x अधिक compute खर्च करके 10x अधिक सार्थक थ्रूपुट पा सकते थे?

एकल से बहु-एजेंट तक

हमने एक सरल Rust-आधारित उपयोग में लाने की व्यवस्था के साथ एक नई रिपॉज़िटरी बनाई।

वितरित सिस्टम की जटिलता से निपटने के बजाय, हमने इस उपयोग में लाने की व्यवस्था को भरपूर संसाधनों वाली एक ही बड़ी Linux VM (Virtual Machine) पर चलाया। उपयोग में लाने की व्यवस्था को नियंत्रित करने के लिए, हम SSH के ज़रिए VM में लॉग इन करते थे और एक सरल टर्मिनल इंटरफ़ेस का उपयोग करते थे।

हमने शुरुआत में ही सिस्टम की उचित ऑब्ज़र्वेबिलिटी पर अधिक समय दिया। हमने सभी एजेंट संदेशों, सिस्टम कार्रवाइयों और कमांड आउटपुट को टाइमस्टैम्प के साथ लॉग किया, ताकि हम सत्रों का विश्लेषण कर सकें और उन्हें दोबारा चला सकें। इससे n केवल हमें मैन्युअल समीक्षा में मदद मिली, बल्कि बड़ी मात्रा में डेटा को छांटने और पैटर्न जल्दी खोजने के लिए इसे वापस Cursor में भेजना भी उपयोगी रहा।

स्व-समन्वय

हमारा पहला बहु-एजेंट विचार सबसे सरल था: समान भूमिकाओं वाले एजेंट्स एक साझा स्टेट फ़ाइल का उपयोग करें, ताकि वे देख सकें कि दूसरे किस पर काम कर रहे हैं, तय कर सकें कि उन्हें किस पर काम करना है, और फ़ाइल को अपडेट कर सकें।

स्व-समन्वय आरेख, जिसमें एजेंट्स एक साझा समन्वय फ़ाइल से कनेक्ट होते हुए दिख रहे हैंस्व-समन्वय आरेख, जिसमें एजेंट्स एक साझा समन्वय फ़ाइल से कनेक्ट होते हुए दिख रहे हैं

हम क्या करना है, इस बारे में कम से कम निर्देश देना चाहते थे और इसके बजाय एजेंट्स को खुद यह तय करने देना चाहते थे कि वे आपस में समन्वय कैसे करें। यह तरीका जल्दी ही विफल हो गया।

समन्वय फ़ाइल ने जल्दी ही और समस्याएँ खड़ी कर दीं। एजेंट्स बहुत देर तक लॉक पकड़े रहते थे, उन्हें छोड़ना भूल जाते थे, जब इसकी अनुमति नहीं होती थी तब लॉक या अनलॉक करने की कोशिश करते थे, और कुल मिलाकर समन्वय फ़ाइल पर लॉक रखने के महत्व को ठीक से समझ नहीं पाते थे। लॉकिंग में गलती करना आसान है और इसे सही ढंग से लागू करना मुश्किल, और अधिक प्रॉम्प्टिंग से भी कोई सहायता नहीं मिली।

लॉकिंग ने बहुत ज़्यादा टकराव भी पैदा किया। 20 एजेंट्स की गति घटकर 1-3 के थ्रूपुट तक आ जाती थी, और ज़्यादातर समय लॉक के इंतज़ार में बीतता था। हमने एजेंट्स को दूसरे एजेंट के कार्य का स्पष्ट रूप से इंतज़ार करने के लिए एक टूल देने की भी कोशिश की, लेकिन उन्होंने शायद ही कभी उसका उपयोग किया। हमने लॉक-रहित आशावादी समवर्ती Control तरीके को भी आज़माया, जिससे ओवरहेड घटा, लेकिन भ्रम खत्म नहीं हुआ।

एजेंट्स के बीच संरचना की कमी का मतलब था कि कोई एक एजेंट बड़े, जटिल कार्य अपने हाथ में नहीं लेता था। वे टकराव और संघर्ष से बचते थे, और पूरे प्रोजेक्ट की ज़िम्मेदारी लेने के बजाय छोटे और ज़्यादा सुरक्षित परिवर्तनों को चुनते थे।

संरचना और भूमिकाएँ जोड़ना

इसके बाद, हमने भूमिकाओं को अलग-अलग किया ताकि एजेंटों को स्वामित्व और जवाबदेही मिल सके:

संरचित भूमिकाओं का आरेख, जिसमें पाइपलाइन में योजनाकार, निष्पादक, वर्कर और निर्णायक दिखाए गए हैंसंरचित भूमिकाओं का आरेख, जिसमें पाइपलाइन में योजनाकार, निष्पादक, वर्कर और निर्णायक दिखाए गए हैं

सबसे पहले, एक योजनाकार उपयोगकर्ता के निर्देशों के अनुसार आगे बढ़ने के लिए सटीक तरीका और अपेक्षित परिणाम तय करता था। यह फिर एक निष्पादक को सौंप दिया जाता था, जो यह सुनिश्चित करने वाला एकमात्र प्रमुख एजेंट होता था कि योजना पूरी तरह पूरी हो। निष्पादक वर्कर के लिए टास्क शुरू कर सकता था, जिससे रैखिक स्केलिंग और थ्रूपुट मिलता था।

निरंतर प्रगति और जवाबदेही बनाए रखने के लिए, निष्पादक के काम पूरा करने के बाद एक स्वतंत्र निर्णायक चलता था, जो यह तय करता था कि काम पूरा हुआ या नहीं और क्या एक और पुनरावृत्ति चलनी चाहिए। इससे समन्वय से जुड़ी कई समस्याएँ सुलझ गईं। निष्पादन का स्वामित्व लेने और उसकी निगरानी करने के लिए एक समर्पित भूमिका होने से वर्कर अपने-अपने कार्य पर केंद्रित रह सके, जबकि पूरी प्रणाली फिर also प्रभावी ढंग से परिणाम देती रही।

अवलोकन और क्रमिक सुधार

इस डिज़ाइन तक पहुँचने के लिए सिस्टम का बहुत बारीकी से निरीक्षण करना पड़ा।

अगर कोई बड़ी समस्या होती, तो वह बार-बार और कई एजेंट्स व टूल कॉल्स में दिखती। उदाहरण के लिए, हमने देखा कि बहुत ज़्यादा टकराव हो रहा था, क्योंकि कई एजेंट्स एक साथ git restore चला रहे थे। हमने लॉग्स का विश्लेषण करने और उन्हें अपने प्रॉम्प्ट्स से मिलाकर देखने के लिए Cursor का उपयोग किया, ताकि समझ सकें कि व्यवहार अपेक्षाओं से मेल क्यों नहीं खा रहा था।

आखिरकार, हमने पाया कि यह सिस्टम सबसे धीमे वर्कर की वजह से अटक रहा था। यह बहुत कठोर था।

सारी योजना पहले से बना लेने से, नई समस्याएँ सामने आने पर सिस्टम के लिए खुद को गतिशील रूप से फिर से समायोजित करना भी मुश्किल हो जाता था। कुछ एजेंट्स आखिरकार उलटी दिशा में बढ़ने लगते थे और लूप के अगले पुनरावृत्ति तक खुद को सुधार नहीं पाते थे।

निरंतर एक्ज़ीक्यूटर

अगले संस्करण में स्वतंत्र योजनाकार को हटा दिया गया।

निष्पादक अब कार्य स्पॉन करने के साथ-साथ यह भी योजना बना सकता था कि लक्ष्य तक कैसे पहुँचना है। चूँकि वही एकमात्र एजेंट था, इसलिए उसे कहीं भी योजना लिखने, किसी एक स्थिर और अपरिवर्तित योजना पर अड़े रहने, या सभी वर्कर का सख्ती से इंतज़ार करने की आवश्यकता नहीं थी।

ताज़गी सुनिश्चित करना

यह सुनिश्चित करने के लिए कि सभी भूमिकाओं में एजेंट लंबे समय तक भटकें नहीं, हमने ताज़गी बनाए रखने के लिए कुछ तंत्र पेश किए:

  1. scratchpad.md में बार-बार नया लिखना चाहिए, उसमें बस जोड़ते नहीं रहना चाहिए।
  2. व्यक्तिगत एजेंटों को संदर्भ सीमा तक पहुँचने पर स्वचालित रूप से सारांश बना लेना चाहिए।
  3. हमने सिस्टम प्रॉम्प्ट्स में आत्म-चिंतन और संरेखण से जुड़े रिमाइंडर जोड़े।
  4. एजेंटों को किसी भी समय दिशा बदलने और मान्यताओं पर सवाल उठाने के लिए प्रोत्साहित किया गया।

सिस्टम अब बेहद गतिशील और लचीला था: यह सक्रिय रूप से कोड का अन्वेषण कर सकता था, फैसलों पर पुनर्विचार कर सकता था, वर्कर प्रबंधित कर सकता था, कार्यों को आपस में मिला सकता था, और लगातार नवीनतम जानकारी पर विचार कर सकता था। हमें मिला कि एजेंट निर्देशों का पालन करते हुए कार्यों को उचित रूप से पूरा करने में काफ़ी अच्छे थे, इसलिए सिस्टम को सरल बनाए रखने के लिए निर्णायक को हटा दिया गया।

वर्कर के साथ एक अनंत एक्ज़ीक्यूटर लूप दिखाने वाला निरंतर एक्ज़ीक्यूटर आरेखवर्कर के साथ एक अनंत एक्ज़ीक्यूटर लूप दिखाने वाला निरंतर एक्ज़ीक्यूटर आरेख

असामान्य व्यवहार

इन सुधारों के बावजूद, निरंतर एक्ज़ीक्यूटर ने असामान्य व्यवहार दिखाना शुरू कर दिया। वह कभी भी अचानक सो जाता था, एजेंट्स चलाना बंद कर देता था, खुद ही काम करने लगता था, योजना बनाने और बहुत सीमित दायरे वाले कुछ ही कार्यों से अधिक शुरू करने से इंकार कर देता था, वर्करों के परिवर्तनों को ठीक से मर्ज नहीं करता था, और समय से पहले काम पूरा होने का दावा करता था।

हमें पता चला कि उसे एक साथ बहुत अधिक भूमिकाएँ और उद्देश्य दिए जा रहे थे, जिसमें शामिल थे: योजना बनाना, खोजबीन करना, अनुसंधान करना, कार्य शुरू करना, वर्करों की जाँच करना, कोड की समीक्षा करना, संपादन करना, आउटपुट मर्ज करना, और यह तय करना कि लूप पूरा हुआ है या नहीं। बाद में सोचने पर, यह समझ में आता है कि वह अभिभूत हो गया था।

अंतिम सिस्टम डिज़ाइन

अंतिम डिज़ाइन हमारी सभी सीखों को समाहित करता है:

  1. एक रूट योजनाकार उपयोगकर्ता के निर्देशों के पूरे दायरे का स्वामित्व रखता है। यह मौजूदा स्थिति को समझने और लक्ष्य की ओर आगे बढ़ाने वाले विशिष्ट, लक्षित कार्य सौंपने के लिए ज़िम्मेदार होता है। यह खुद कोई कोडिंग नहीं करता। इसे यह भी पता नहीं होता कि इसके कार्य उठाए जा रहे हैं या नहीं, या उन्हें कौन उठा रहा है।
  2. जब किसी योजनाकार को लगता है कि उसके दायरे को और छोटे हिस्सों में बाँटा जा सकता है, तो वह उप-योजनाकार स्पॉन करता है, जो सौंपे गए उस सीमित हिस्से का पूरा स्वामित्व ले लेते हैं। वे भी उसी तरह पूरी ज़िम्मेदारी निभाते हैं, लेकिन केवल उसी हिस्से के लिए। यह प्रक्रिया पुनरावर्ती है।
  3. वर्कर कार्य उठाते हैं और उन्हें पूरा करने की पूरी ज़िम्मेदारी उन्हीं की होती है। वे बड़े सिस्टम से अनभिज्ञ रहते हैं। वे किसी अन्य योजनाकार या वर्कर से संवाद नहीं करते। वे रेपो की अपनी कॉपी पर काम करते हैं, और काम पूरा होने पर एक ही हैंडऑफ़ लिखते हैं, जिसे सिस्टम उस योजनाकार को सबमिट करता है जिसने वह कार्य अनुरोध किया था।

रोचक बात यह है कि आज कुछ सॉफ़्टवेयर टीमें वास्तव में इसी तरह काम करती हैं।

पुनरावर्ती योजनाकारों, उप-योजनाकारों, वर्करों और git को दिखाता अंतिम सिस्टम डिज़ाइनपुनरावर्ती योजनाकारों, उप-योजनाकारों, वर्करों और git को दिखाता अंतिम सिस्टम डिज़ाइन

उप-योजनाकार तेज़ी से वर्करों को फैलाकर थ्रूपुट बढ़ाते हैं, साथ ही यह सुनिश्चित करते हैं कि पूरे सिस्टम का स्वामित्व और ज़िम्मेदारी पूरी तरह किसी एजेंट के पास बनी रहे। इससे बड़े प्रोजेक्ट्स और कार्यों में भी मदद मिली, जहाँ अन्यथा एक अकेला योजनाकार दबाव में आ जाता और संकीर्ण दृष्टि विकसित कर लेता।

हैंडऑफ़ में सिर्फ़ यह नहीं होता कि क्या किया गया, बल्कि महत्वपूर्ण टिप्पणियाँ, चिंताएँ, विचलन, निष्कर्ष, विचार और फ़ीडबैक भी शामिल होते हैं। योजनाकार इसे एक अनुवर्ती संदेश के रूप in प्राप्त करता है। इससे सिस्टम लगातार गतिशील बना रहता है: भले ही कोई योजनाकार "पूरा" कर चुका हो, वह फिर भी अपडेट प्राप्त करता रहता है, नवीनतम रेपो पुल करता है, और आगे की योजना बनाना तथा अगले निर्णय लेना जारी रख सकता है।

सभी एजेंट्स के पास यह तंत्र होता है, जो सिस्टम को बेहद गतिशील और स्वयं-अभिसारी बनाए रखता है, और वैश्विक समन्वयन या आपसी क्रॉस-टॉक के ओवरहेड के बिना, अधिक व्यापक दृश्य रखने वाले स्वामियों तक श्रृंखला में ऊपर की ओर जानकारी पहुँचाता रहता है।

इंटीग्रेटर को हटाना

हमने शुरुआत में एक इंटीग्रेटर जोड़ा था, ताकि केंद्रीकृत, पूरे सिस्टम के संदर्भ से अवगत गुणवत्ता नियंत्रण किया जा सके और एक ही समय में पुश, rebase, कॉन्फ्लिक्ट सुलझाने और मर्ज करने की कोशिश कर रहे बहुत ज़्यादा वर्कर के बीच होने वाली खींचतान को कम किया जा सके।

यह जल्दी ही एक साफ़ bottleneck बन गया। सैकड़ों वर्कर थे और केवल एक ही दरवाज़ा (यानी "लालफीताशाही") था, जिससे होकर सारा काम गुजरना पड़ता था। हमने प्रॉम्प्ट में परिवर्तन आज़माए, लेकिन अंततः हमने तय किया कि यह अनावश्यक था और सिस्टम को सरल बनाने के लिए इसे हटाया जा सकता था।

थ्रूपुट और ट्रेड-ऑफ़्स

एक हफ्ते के दौरान 10M टूल कॉल्स में सिस्टम ने ~1,000 कमिट प्रति घंटे का उच्चतम स्तर छुआ। सिस्टम शुरू होने के बाद, उसे हमारी ओर से किसी भी हस्तक्षेप की ज़रूरत नहीं पड़ी।

यह थ्रूपुट हासिल करने के लिए कुछ जानबूझकर ट्रेड-ऑफ़्स किए गए थे।

कमिट की शुद्धता

जब हमने हर एक कमिट से पहले 100% शुद्धता को आवश्यक बना दिया, तो इससे काफ़ी ज़्यादा क्रमिक निर्भरता बढ़ी और प्रभावी थ्रूपुट धीमा पड़ गया। API में बदलाव या टाइपो जैसी एक छोटी-सी त्रुटि भी पूरे सिस्टम को लगभग ठप कर सकती थी। वर्कर अपने दायरे से बाहर जाने लगते थे और अप्रासंगिक चीज़ें ठीक करने लगते थे। कई एजेंट एक ही समस्या को ठीक करने की कोशिश में एक साथ जुट जाते थे और एक-दूसरे के काम में हस्तक्षेप करने लगते थे।

यह व्यवहार न उपयोगी था, न आवश्यक। थोड़ी गुंजाइश देने का मतलब है कि एजेंट भरोसा कर सकते हैं कि दूसरी समस्याएँ जल्द ही दूसरे एजेंट ठीक कर देंगे। और यह सही भी है, क्योंकि पूरे कोडबेस पर सिस्टम के पास प्रभावी ओनरशिप और डेलीगेशन होता है। त्रुटियाँ आती हैं, फिर जल्दी ठीक भी हो जाती हैं। त्रुटि दर छोटी और स्थिर बनी रहती है—हो सकता है सिस्टम पूरी तरह साफ़ कम ही दिखे, लेकिन वह स्थिर और प्रबंधनीय रहता है, बेकाबू नहीं होता और न ही बिगड़ता जाता है।

यह संकेत हो सकता है that आदर्श रूप से कुशल सिस्टम कुछ हद तक त्रुटि दर को स्वीकार करता है, लेकिन रिलीज़ से पहले एक अंतिम "green" ब्रांच की ज़रूरत होती है, जहाँ कोई एजेंट नियमित रूप से स्नैपशॉट ले और एक तेज़ fixup pass कर दे।

समन्वयन ओवरहेड

कभी-कभी कई एजेंट एक ही फ़ाइल में बदलाव करते हैं या उसी कोड को रीफ़ैक्टर करते हैं। इसे पूरी तरह रोकने की कोशिश करने या किसी समाधान को बेवजह ज़रूरत से ज़्यादा जटिल बनाने के बजाय, हम कुछ समय की अस्थिरता स्वीकार करते हैं और सिस्टम को थोड़ी देर में स्वाभाविक रूप से संतुलित होकर स्थिर होने देते हैं।

इसमें कुछ अतिरिक्त टोकन खर्च होते हैं और स्थानीय स्तर पर टकराव पैदा होता है, लेकिन इससे कुल मिलाकर सिस्टम ज़्यादा सरल रहता है: मॉडल्स को एक-दूसरे के साथ संरेखित रखना और उन पर ज़रूरत से ज़्यादा बोझ न डालना आसान होता है, इसे प्रबंधित करना और इसकी निगरानी करना आसान होता है, रुकावट कम होती है, और समग्र उत्पादकता बेहतर होती है। यह ज़रूरत से ज़्यादा जटिल तरीकों से भी बचाता है।

इन्फ्रास्ट्रक्चर से मिली सीख

वितरित सिस्टम्स से जुड़ी समय से पहले आने वाली जटिलताओं से बचने के लिए, हर बहु-एजेंट रन को पर्याप्त सिस्टम संसाधनों वाली अलग बड़ी मशीन पर चलाया गया। यह तरीका उपयुक्त था, क्योंकि ज़्यादातर रन अपने चरम पर कई सौ एजेंट तक पहुँचते थे, जो आम तौर पर इन मशीनों को काफ़ी हद तक व्यस्त कर देते थे, लेकिन उन पर ज़रूरत से ज़्यादा दबाव नहीं डालते थे। इस आर्किटेक्चर ने सिस्टम मेट्रिक्स पर नज़र रखना और ज़रूरत पड़ने पर स्टेट को साझा करना और कॉपी करना आसान बना दिया।

एजेंट्स के RAM उपयोग को सीमित करने के बाद, डिस्क सबसे बड़ा हॉटस्पॉट बन गई। खासकर मोनोलिथ प्रोजेक्ट में, सैकड़ों एजेंट्स के एक साथ compile करने से बिल्ड आर्टिफैक्ट्स पर कई GB/s की reads और writes होने लगती थीं। इसका harness के कुल थ्रूपुट पर काफ़ी असर पड़ा, और इससे एक दिलचस्प सीख मिली: प्रोजेक्ट की संरचना, आर्किटेक्चरल फ़ैसले, और डेवलपर अनुभव token और कमिट थ्रूपुट को प्रभावित कर सकते हैं, सिर्फ़ इसलिए कि कोडबेस के साथ काम करना (जैसे compilation) आदर्श रूप से सोचने और कोडिंग करने की तुलना में ज़्यादा समय लेता है।

सामान्य विकास परिवेश में भी कुछ सीमाएँ और अक्षमताएँ थीं: जो बातें एक अकेले उपयोगकर्ता के कार्यस्थान में ठीक लगती हैं या बहुत महत्वपूर्ण नहीं होतीं, वही एक मशीन पर सैकड़ों एजेंट्स के एक ही काम करने पर साफ़ नज़र आने लगती हैं। इसका एक सीधा समाधान यह है कि हर एजेंट को उसकी अपनी मशीन दे दी जाए। लेकिन इन आदिम घटकों और उपकरणों में से कुछ पर फिर से विचार करके और उनका पुनः डिज़ाइन करके, दक्षता में बड़े सुधार के कई दिलचस्प और आसानी से हासिल किए जा सकने वाले अवसर मौजूद हैं।

उदाहरण के लिए, Git और Cargo जैसे कई उपकरण shared locks का उपयोग करते हैं, मुख्यतः एक सरल concurrency control mechanism के तौर पर। क्या databases जैसे concurrent systems से अच्छी तरह स्थापित तंत्रों को अपनाकर इन्हें बहु-एजेंट सिस्टम्स में भी उतना ही प्रभावी बनाया जा सकता है? सभी एजेंट्स के पास रेपो की अपनी-अपनी कॉपी होती है, लेकिन ज़्यादातर फ़ाइलें और आर्टिफैक्ट्स एक जैसे होते हैं; क्या अधिक उन्नत production storage systems में मिलने वाली सरल copy-on-write और deduplication सुविधाएँ जोड़कर, अलग अवसंरचना बनाए बिना, आम तौर पर "single-user" सिस्टम में भी ऐसे ही आसान लाभ पाए जा सकते हैं?

एजेंट्स के लिए आशय स्पष्ट करना

इस बहु-एजेंट सिस्टम को दिए गए निर्देश बहुत महत्वपूर्ण थे।

शुरुआत में, हमने उन्हें अपना मुख्य लक्ष्य नहीं बनाया था; इसके बजाय हमारा लक्ष्य एक स्थिर और प्रभावी उपयोग में लाने की व्यवस्था बनाना था। लेकिन निर्देशों का महत्व बहुत जल्दी स्पष्ट हो गया। मूल रूप से हम एक सामान्य कोडिंग एजेंट के साथ ही काम कर रहे थे, बस उसके पास कई गुना अधिक समय और compute था। इससे हर चीज़ बढ़-चढ़कर सामने आती है, जिसमें कमजोर और अस्पष्ट निर्देश भी शामिल हैं।

शुरुआती निर्देशों पर अधिक समय खर्च करना समझदारी है। आखिरकार, एजेंट अब भी एजेंट ही हैं: उन्हें आपके निर्देशों का सख्ती से पालन करने, उन्हीं पाथ पर आगे बढ़ने, और उन्हें न बदलने या ओवरराइड न करने के लिए प्रशिक्षित किया गया है — चाहे वे निर्देश खराब ही क्यों न हों।

हम अपने अनुसंधान प्रोजेक्ट्स में सफलता देखना चाहते थे, इसलिए जैसे-जैसे प्रोजेक्ट और उपयोग में लाने की व्यवस्था विकसित हुए, हमने अपने शुरुआती निर्देशों में बदलाव किया। हम एक ब्राउज़र बनाना सीख रहे थे और साथ ही इस नए बहु-एजेंट सिस्टम को चलाना भी सीख रहे थे, और हम साफ़ देख पा रहे थे कि खराब या अधूरे स्पेसिफिकेशन आउटपुट की गुणवत्ता में झलक रहे थे — और इसका कारण स्वयं उपयोग में लाने की व्यवस्था नहीं था। उपयोग में लाने की व्यवस्था तो बस हमारे निर्देशों का ठीक-ठीक पालन कर रही थी।

ब्राउज़र प्रोजेक्ट के कुछ उदाहरण:

  • शुरुआत में, निर्देश स्पेक्स को कार्यान्वित करने और बग्स ठीक करने पर केंद्रित थे। "spec implementation" जैसे निर्देश इतने अस्पष्ट थे कि एजेंट समझदारी से प्राथमिकताएँ तय करने के बजाय दुर्लभ और कम इस्तेमाल होने वाली सुविधाओं में बहुत गहराई तक चले जाते थे।
  • हमने यह बात अप्रत्यक्ष रूप से मान ली थी कि उपयोगकर्ता-अनुकूल सीमाओं के भीतर प्रदर्शन संबंधी अपेक्षाएँ मौजूद थीं। लेकिन एजेंट्स को अन्य लक्ष्यों के साथ प्रदर्शन का संतुलन बनाने के लिए मजबूर करने हेतु स्पष्ट निर्देशों और लागू किए गए टाइमआउट की ज़रूरत पड़ी।
  • सिस्टम के जटिल हिस्सों में, एजेंट ऐसा कोड लिख सकते हैं जिसमें memory leak हों या deadlock पैदा हों। इंसान इसे पहचान लेते, लेकिन एजेंट्स के लिए यह हमेशा स्पष्ट नहीं था। सिस्टम को सुचारु रूप से रिकवर करने और अधिक रक्षात्मक ढंग से काम करने देने के लिए स्पष्ट प्रक्रिया-आधारित संसाधन प्रबंधन उपकरण आवश्यक थे।

JavaScript के बिना हमारे साधारण ब्राउज़र का पहला संस्करण ऐसी आर्किटेक्चर पर जाकर ठहरा जो आगे चलकर एक पूर्ण ब्राउज़र में विकसित होने के लिए उपयुक्त नहीं थी। यह शुरुआती स्पेसिफिकेशन की विफलता थी।

इसी तरह, जबकि एजेंट्स को बताया गया था कि प्रोजेक्ट शून्य से एक ब्राउज़र बनाना है, फिर भी उन्होंने कुछ निर्भरताएँ जोड़ लीं जिन्हें वे खुद कार्यान्वित कर सकते थे, या उचित कार्यान्वयन पूरा होने तक अस्थायी scaffolding के रूप में उपयोग कर सकते थे। यह निर्देशों में चूक थी। बाद की एक run में निर्भरताओं को लेकर दर्शन स्पष्ट रूप से बताया गया और यह भी कि किन libraries का उपयोग नहीं किया जाना चाहिए, जिससे यह समस्या ठीक हुई।

उस बाद की run में एक बड़ा पुनर्गठन भी किया गया, जिसमें मोनोलिथ से हटकर कई self-contained crates बनाए गए। repo बहुत टूटी हुई अवस्था में था, लेकिन बहु-एजेंट सिस्टम कुछ ही दिनों में काम करने वाले कोड की ओर अभिसरित हो गया। इससे पता चला कि सिस्टम में सहयोगात्मक और समझदारी से काम करने की मजबूत क्षमता है, जो पूरी तरह टूटी हुई अवस्थाओं में भी बनी रहती है — आगे और बिगड़ने या अटक जाने के बजाय। इस run में compilation का इंतज़ार करने में भी बहुत कम समय लगा, और यह पहले की तुलना में कई गुना अधिक थ्रूपुट पर चला।

आर्किटेक्चर और निर्देश मायने रखते हैं। एजेंट्स में अपार इंजीनियरिंग कौशल होता है, लेकिन वे निर्देशों का अंत तक पालन करेंगे, चाहे वे अच्छे हों या बुरे। अत्यधिक संकीर्ण मेट्रिक्स और असंरचित स्वतंत्रता के बीच संतुलन ढूँढना मुश्किल था, और यह समझना भी कि क्या बात स्वतः स्पष्ट है और किसका स्पष्ट उल्लेख करना ज़रूरी है।

यह सब आशय को सामने लाने, स्पष्ट रूप से निर्दिष्ट करने और समझने के महत्व को दर्शाता है, और इस पैमाने पर यह और भी अधिक महत्वपूर्ण हो जाता है। नियंत्रणीयता और ऑब्ज़र्वेबिलिटी आगे भी खोजे जाने वाले रोचक अनुसंधान क्षेत्र होंगे।

प्रॉम्प्ट्स को बेहतर बनाना

प्रॉम्प्टिंग विकास प्रक्रिया का एक अहम हिस्सा थी।

हमने पाया कि जिन कामों के बारे में मॉडल पहले से जानता है, उनके लिए निर्देश न देना बेहतर है; सिर्फ़ उन चीज़ों के लिए निर्देश दें जिन्हें वह नहीं जानता (जैसे, बहु-एजेंट सहयोग) या जो आपके प्रासंगिक डोमेन के लिए विशिष्ट हैं (जैसे, परीक्षण कैसे चलाने हैं, आपकी डिप्लॉय पाइपलाइन)। मॉडल को एक बेहद प्रतिभाशाली नए इंजीनियर की तरह समझें, जो इंजीनियरिंग जानता है, लेकिन आपके खास कोडबेस और प्रक्रियाओं को नहीं।

निर्देशों की तुलना में सीमाएँ ज़्यादा प्रभावी होती हैं। "कोई TODO नहीं, कोई आंशिक इम्प्लीमेंटेशन नहीं" , "इम्प्लीमेंटेशन पूरा करना याद रखें" से बेहतर काम करता है। मॉडल्स आम तौर पर डिफ़ॉल्ट रूप से सही काम करते हैं। सीमाएँ उनकी हदें तय करती हैं।

उच्च-स्तरीय या गहरे कार्यों के लिए चेकबॉक्स वाली मानसिकता से बचें। अपने आशय के बारे में विस्तार से बताएँ, लेकिन याद रखें कि करने के लिए बहुत विशिष्ट चीज़ें बताने से मॉडल का ध्यान व्यापक दायरे के बजाय उन्हीं को पूरा करने पर टिक जाता है। इससे आप अनजाने में असूचीबद्ध चीज़ों की प्राथमिकता भी कम कर देते हैं। आम तौर पर, मॉडल को अपने विवेक और स्वायत्तता का उपयोग करने देना बेहतर होता है।

हमें यह भी उपयोगी लगा कि दायरे की मात्रा पर बात करते समय ठोस संख्याएँ और रेंज दी जाएँ। "कई कार्य जनरेट करें" जैसे निर्देश अक्सर कम संख्या में परिणाम देते हैं: रूढ़िवादी डिफ़ॉल्ट, सुरक्षित रुख, और तकनीकी रूप से फिर भी निर्देशों का पालन। "20-100 कार्य जनरेट करें" यह संकेत देता है कि आशय बड़ा दायरा है, इसे महत्वाकांक्षी होना चाहिए, और हमने व्यवहार में काफ़ी व्यापक अंतर देखा।

सिस्टम डिज़ाइन से मिली सीख

हमने अपने अनुसंधान से कुछ सिद्धांत निकाले:

  1. सिस्टम एंटी-फ्रैजाइल होना चाहिए। जैसे-जैसे हम एक साथ चलने वाले एजेंट्स की संख्या को स्केल करते हैं, विफलता की संभावना भी बढ़ती है। हमारे सिस्टम को अलग-अलग एजेंट्स की विफलता सहने में सक्षम होना चाहिए, ताकि दूसरे एजेंट्स उबर सकें या वैकल्पिक तरीके आज़मा सकें।
  2. मान्यताओं से ज़्यादा अनुभवजन्य निष्कर्षों पर भरोसा। हम समायोजन करने के लिए डेटा और अवलोकन का उपयोग करना चाहते थे, बजाय इसके कि मानव संगठनों या मौजूदा सिस्टम डिज़ाइन के आधार पर पहले से यह मान लें कि इसे कैसे काम करना चाहिए।
  3. थ्रूपुट के लिए स्पष्ट रूप से डिज़ाइन करें। इसका मतलब था कोडिंग के कुछ दूसरे पहलुओं के साथ समझौता करना—जैसे त्रुटियों की एक छोटी लेकिन स्थिर दर को स्वीकार करना, जिसके लिए अंतिम मिलान चरण की ज़रूरत हो—इसके बजाय 100% समय पूरी तरह काम करने वाला कोड पाने की कोशिश करना, जो सिस्टम को बहुत धीमा कर देता।

ऐसे सिस्टम, जब सही ढंग से बनाए जाएँ, तो बेहद सादगीपूर्ण और प्रभावी होते हैं, लेकिन जब तक हमने कई अलग-अलग तरीकों को नहीं खोजा, तब तक यह स्पष्ट नहीं था कि कौन-सा सरल तरीका काम करेगा। मौजूदा सिस्टम डिज़ाइन न्यूनतम ओवरहेड के साथ चल रहा है और उपयोगी तरीके से token थ्रूपुट की रैखिक स्केलिंग देता है। उपयोग में लाने की व्यवस्था पर आगे किसी बड़े पुनरावृत्ति की ज़रूरत नहीं पड़ी है।

निष्कर्ष

हालाँकि रुचि, निर्णय और दिशा मनुष्यों से आए, AI इस अनुसंधान में तेज़ी से पुनरावृत्ति करने और नई दिशाओं की पड़ताल करने के लिए एक महत्वपूर्ण गुणक साबित हुआ।

यह कुछ हद तक "सद्चक्र" AI लूप जैसा है, जहाँ AI का उपयोग AI विकसित करने के लिए किया जाता है, और जैसे-जैसे मॉडल, एजेंट और उपयोग में लाने की व्यवस्थाएँ बेहतर होते जाते हैं, यह खुद को और आगे बढ़ाता है तथा लगातार तेज़ी पकड़ता जाता है। हम उन उपकरणों को आकार देते हैं, जो हमें आकार देते हैं।

इस अनुसंधान में आज कुछ सॉफ़्टवेयर टीमें जिस तरह काम करती हैं, उसकी भी एक काव्यात्मक झलक मिलती है। इन मॉडल्स को इस तरीके से स्पष्ट रूप से प्रशिक्षित नहीं किया गया था, जो यह संकेत देता है कि यह स्वतः उभरने वाला व्यवहार है और संभवतः अंततः सॉफ़्टवेयर प्रोजेक्टों को संरचित करने का सही तरीका भी।

हम अत्यंत लंबे समय तक चलने वाले एजेंटों पर अनुसंधान जारी रखेंगे, और हमारे निष्कर्ष हमारे उत्पाद के भविष्य को दिशा देंगे।