अपना खुद का एजेंट हार्नेस (agent harness) कैसे बनाएं?

@mfpiccolo
अंग्रेज़ी2 माह पहले · 28 मई 2026
339K
1.9K
242
35
4.9K

TL;DR

लेखक का तर्क है कि पारंपरिक एजेंट फ्रेमवर्क बहुत कठोर हैं, और वे एक मॉड्यूलर 'iii' इंजन दृष्टिकोण का प्रस्ताव देते हैं जहाँ हार्नेस का हर घटक—क्रेडेंशियल्स से लेकर पॉलिसी इंजन तक—एक स्वतंत्र और बदलने योग्य वर्कर है।

ज़्यादातर एजेंट टीमें हार्नेस नहीं बनातीं। वे अपनाती हैं। LangChain, LangGraph, OpenAI Agents SDK, Anthropic SDK, CrewAI, AutoGen, लूप, टूल्स, मेमोरी और ऑर्केस्ट्रेशन—ये सब एक ही फैसले के तहत शेल्फ से उठा लिए जाते हैं। हार्नेस एक ऐसा फ्रेमवर्क है जिसे आप इम्पोर्ट करते हैं। अगर उसके अंदर कुछ फिट नहीं बैठता, तो आप उसे फोर्क करते हैं, उससे लड़ते हैं, या उसके आसपास का रास्ता निकालते हैं।

Mike Piccolo - inline image

मेरे ख़्याल से यह आकार ग़लत है, और यही वजह है कि हर लंबे समय तक चलने वाली एजेंट टीम अंततः अपने हार्नेस को स्क्रैच से फिर से लिखती है। हार्नेस एक चीज़ नहीं है। यह दस या बारह अलग-अलग चीज़ों का एक बंडल है, क्योंकि आसपास का इकोसिस्टम आपको उन्हें कंपोज़ करने का कोई तरीका नहीं देता। Pi agent पैकेज सही दिशा में हैं, लेकिन वे अभी भी "एक और सेवा जोड़ो और उसे बाकी सभी के साथ इंटीग्रेट करो" वाले प्रतिमान में हैं। iii इंजन सभी वर्कर्स को एक समान मानता है और इंटीग्रेशन लॉजिक को पूरी तरह हटा देता है। प्रोवाइडर राउटर, क्रेडेंशियल वॉल्ट, पॉलिसी इंजन, अप्रूवल गेट, मॉडल कैटलॉग, सेशन स्टोरेज, बजट ट्रैकर, आफ्टर-कॉल हुक फ़ैनआउट, और ड्यूरेबल टर्न लूप—ये सब स्वतंत्र चिंताएँ हैं। ये सभी आपकी क्यू, HTTP/API सर्वर, स्ट्रीमिंग, यहाँ तक कि ब्राउज़र वर्कर्स के साथ भी इंटरऑपरेबल हैं। एक फ्रेमवर्क जो उन्हें एक ब्लॉक के रूप में भेजता है, वह आपको एक ऐसा समझौता बेच रहा है जो आपको करना ही नहीं था।

iii के पीछे का दांव यह है कि उन्हें एक ब्लॉक नहीं होना चाहिए। एक साझा इंजन पर वर्कर्स का एक सेट होना चाहिए, प्रत्येक बदली जाने योग्य, प्रत्येक स्वतंत्र रूप से वर्जनित, और प्रत्येक एक ही प्रिमिटिव से जुड़ा हो: एक ट्रिगर (iii.trigger()) जिसका उपयोग हर दूसरा वर्कर भी करता है। हार्नेस इंस्टॉल करने योग्य वर्कर्स का एक स्टैक बन जाता है, और "अपना खुद का बनाएँ" का मतलब "एक फ्रेमवर्क फोर्क करना" नहीं रह जाता। इसका मतलब है "कुछ वर्कर्स बदल देना।"

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

15 काम जो एक एजेंट हार्नेस को करने होते हैं

यदि आप प्रोडक्शन एजेंट हार्नेस को उसकी जिम्मेदारियों तक छीलते हैं, तो आपको एक सूची मिलती है जो मोटे तौर पर इस तरह दिखती है:

<code-segment id="0" lang="text">

  1. एक क्लाइंट से टर्न अनुरोध स्वीकार करें और उसे स्थायी रूप से सहेजें
  2. जिस भी मॉडल प्रोवाइडर को कॉल किया जाए, उसके लिए क्रेडेंशियल हल करें
  3. पता लगाएँ कि चुना गया मॉडल वास्तव में क्या कर सकता है (विज़न, टूल्स, स्ट्रीमिंग, कॉन्टेक्स्ट विंडो)
  4. प्रति-टर्न स्टेट मशीन चलाएँ: प्रोविज़न करें, असिस्टेंट स्ट्रीम करें, टूल्स चलाएँ, स्टीयर करें, टियर डाउन करें
  5. स्किल बॉडीज़ लोड करें और परोसें जो प्रत्येक फंक्शन के रिक्वेस्ट शेप, एरर कोड और उपयोग नोट्स का वर्णन करती हैं
  6. सिस्टम प्रॉम्प्ट, मोड पैराग्राफ, आइडेंटिटी प्रीएम्बल, वर्किंग डायरेक्टरी और डिफ़ॉल्ट स्किल्स अपेंडिक्स को असेंबल करें
  7. मॉडल द्वारा उत्पन्न होते ही टोकन वापस क्लाइंट को स्ट्रीम करें
  8. किसी भी टूल कॉल (जो सिर्फ एक फंक्शन है) को चलाने से पहले एक पॉलिसी के विरुद्ध जाँचें
  9. टूल कॉल्स को रोकें जिनमें मानवीय निर्णय की आवश्यकता है और उत्तर को सही टर्न पर वापस रूट करें
  10. प्रति-वर्कस्पेस या प्रति-एजेंट बजट के विरुद्ध LLM खर्च को ट्रैक करें
  11. टूल कॉल्स से पहले और बाद में हुक चलाएँ (लॉगिंग, रिडक्शन, कस्टम साइड इफेक्ट्स)
  12. सेशन को एक ब्रांचिंग ट्री के रूप में स्थायी रूप से सहेजें ताकि फोर्क और रिज़्यूम काम कर सकें
  13. जब कॉन्टेक्स्ट विंडो भर जाए, तो सेशन हिस्ट्री को कॉम्पैक्ट करें
  14. एक इवेंट स्ट्रीम उत्सर्जित करें जिसकी UI सदस्यता लेता है
  15. वह गुम हिस्सा जो हर एजेंट कंपनी बनाते हुए देखती है। हर चरण पर एक OpenTelemetry ट्रेस ले जाएँ ताकि आप डीबग कर सकें </code-segment>

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

iii हार्नेस उन तेरहों कामों में से हर एक को workers.iii.dev रजिस्ट्री पर एक अलग वर्कर के रूप में भेजता है। प्रत्येक एक ही WebSocket प्रोटोकॉल बोलता है। प्रत्येक एक ही इंजन बस पर फंक्शन और ट्रिगर रजिस्टर करता है। प्रत्येक को iii worker add किया जा सकता है, बदला जा सकता है, और किसी भी भाषा में SDK के साथ लिखा जा सकता है।

स्टैक, वर्कर के अनुसार

यहाँ iii-hq/workers मोनोरेपो का वास्तविक प्रोडक्शन स्टैक है, जिसमें प्रत्येक वर्कर का काम एक पंक्ति में है। पूरा बंडल github.com/iii-hq/workers/harness पर उपलब्ध है:

Mike Piccolo - inline image

ग्यारह वर्कर। एक इंजन। प्रत्येक एक प्रकाशित संस्करण पर है। प्रत्येक एक स्वतंत्र प्रक्रिया के रूप में स्वतंत्र रूप से चलने योग्य है (डेव में pnpm dev:<worker>, रिलीज़ बाइनरी के रूप में iii worker add <specific-worker>) या कम्पोजिट एंट्री पॉइंट के हिस्से के रूप में जो उन्हें एक साथ स्पिन करता है।

यह मायने रखता है क्योंकि: उस तालिका में हर बॉक्स एक ऐसी जगह है जहाँ कोई आपको एक अलग वर्कर दे सकता है, और आप बाकी को रख सकते हैं। स्टैटिक मॉडल कैटलॉग पसंद नहीं? एक वर्कर प्लग इन करें जो models::list रजिस्टर करता है और लाइव API से पढ़ता है। फ़ाइल-आधारित क्रेडेंशियल पसंद नहीं? एक वर्कर प्लग इन करें जो auth::get_token रजिस्टर करता है और सीक्रेट्स मैनेजर से पढ़ता है। ऐसे वर्कफ़्लो के लिए एक अलग टर्न FSM चाहिए जो अलग तरह से ब्रांच करता है? turn-orchestrator बदलें, हर आश्रित उसी बस के माध्यम से run::start कॉल करता है और turn_state पढ़ता है, इसलिए बाकी स्टैक नहीं बदलता।

लूप वास्तव में कैसे चलता है

एक टर्न का आकार कुछ इस तरह दिखता है, जिसमें वर्कर उस क्रम में चलते हैं जिसमें वे फायर करते हैं।

एक ब्राउज़र/CLI/चैट harness::trigger के माध्यम से {session_id, message_id, payload} के साथ एक टर्न POST करता है। हार्नेस मेटा-वर्कर पेलोड को run::start पर फॉरवर्ड करता है। यह हॉप इसलिए मौजूद है ताकि OpenTelemetry स्पैन रैपर सेशन और मैसेज आईडी को बैगेज के रूप में सीड कर सके, जो स्टैक में हर वर्कर पर हर नेस्टेड iii.trigger कॉल में प्रचारित होता है। दूसरी तरफ ट्रेस ट्री एक जुड़ा हुआ ग्राफ होता है।

run::start turn-orchestrator पर आता है। यह रन अनुरोध को स्थायी रूप से सहेजता है, iii स्टेट में session/<sid>/turn_state पर प्रारंभिक TurnStateRecord सीड करता है, और तुरंत वापस आ जाता है। वास्तविक काम ड्यूरेबल प्रति-स्टेट मशीन के अंदर होता है, जो turn-step FIFO पर प्रकाशनों द्वारा जागृत होता है।

दो टर्मिनल अवस्थाएँ हैं: stopped (finishSession() के माध्यम से स्वच्छ निकास) और failed (एक अप्रत्याशित हैंडलर थ्रो यहाँ रूट करता है, क्यू को स्वीकार करता है ताकि वह पुनः प्रयास करना बंद करे, और message_complete{stop_reason:'error'} के साथ-साथ agent_end सतह पर लाता है ताकि UI कारण दिखाए)। टियरडाउन एक इनलाइन finishSession() पोर्ट है जिसे किसी भी टर्न-एंड पथ से कॉल किया जाता है, न कि एक अलग एनक्यूड चरण।

provisioning तीन काम करता है। यह एक iii-sandbox माइक्रोवर्चुअल मशीन बूट करता है यदि रन को पृथक निष्पादन की आवश्यकता है। यह system_default_skills में प्रत्येक नेमस्पेस के लिए directory::skills::download कॉल करता है (डिफ़ॉल्ट ["iii://iii-directory/index"]) ताकि iii-directory उन स्किल बॉडीज़ को प्री-कैश कर सके जिनके साथ रन शुरू होता है। और यह सिस्टम प्रॉम्प्ट को तीन परतों में असेंबल करता है: एक मोड पैराग्राफ जो run_request.mode (plan, ask, या agent) से चुना जाता है, iii आइडेंटिटी प्रीएम्बल जो मॉडल को agent_trigger कन्वेंशन और directory::skills::get के ऑन-डिमांड डिस्कवरी पैटर्न सिखाता है, और डिफ़ॉल्ट स्किल्स का एक अपेंडेड इंडेक्स जिनके साथ एजेंट बूट होता है। कॉल करने वाला run::start पर system_prompt पास करके पूरे प्रॉम्प्ट को ओवरराइड कर सकता है; अन्यथा ऑर्केस्ट्रेटर इसे बनाता है। फंक्शन स्कीमा लाइव इंजन कैटलॉग से आते हैं।

assistant_streaming run के प्रोवाइडर फील्ड से मेल खाने वाले प्रोवाइडर वर्कर पर provider::<name>::stream कॉल करता है। प्रोवाइडर वर्कर auth::get_token के माध्यम से क्रेडेंशियल खींचता है (auth-credentials), मॉडल की SSE प्रतिक्रिया को एक iii चैनल में स्ट्रीम करता है, और ऑर्केस्ट्रेटर उस चैनल को खत्म करता है जो UI फ़ैनआउट के लिए agent::events पर message_update इवेंट उत्सर्जित करता है। चैनल निर्माण और रीड लूप provider-stream.ts में एक पुल-आधारित MessagePump के पीछे रहता है, इसलिए स्ट्रीमिंग स्टेट ट्रांज़िशन पर केंद्रित रहता है।

जब असिस्टेंट टूल कॉल लौटाता है, तो FSM function_execute में प्रवेश करता है। हर टूल कॉल dispatchWithHook से गुज़रता है, जो ऑर्केस्ट्रेटर में एकमात्र चोकपॉइंट है। consultBefore 5-सेकंड टाइमआउट के साथ सीधे policy::check_permissions कॉल करता है। पॉलिसी वर्कर (डिफ़ॉल्ट स्टैक में हार्नेस मेटा-वर्कर) iii-permissions.yaml पढ़ता है, कॉल के function_id को नियम सेट से मिलाता है, और तीन परिणामों में से एक लौटाता है:

<code-segment id="1" lang="text">

  • allow: डिस्पैच आगे बढ़ता है; ऑर्केस्ट्रेटर लक्ष्य फंक्शन को ट्रिगर करता है और परिणाम लिखता है
  • deny: डिस्पैच DenialEnvelope के साथ शॉर्ट-सर्किट होता है, परिणाम एक अस्वीकृति रिकॉर्ड बन जाता है
  • needs_approval: व्यक्तिगत कॉल टर्न की awaiting_approval सूची में पार्क हो जाती है। बाकी बैच डिस्पैच करता रहता है। टर्न function_awaiting_approval में तभी ट्रांज़िशन करता है जब एक या अधिक प्रविष्टियाँ लंबित हों। </code-segment>

अप्रूवल वेक रिएक्टिव और साझा है। ऑर्केस्ट्रेटर scope approvals पर बिल्कुल एक turn::on_approval स्टेट ट्रिगर रजिस्टर करता है। जब कंसोल approval::resolve कॉल करता है, तो approval-gate वर्कर approvals/<sid>/<cid> = {decision, reason} को iii स्टेट में लिखता है। वह लेखन turn::on_approval को फायर करता है, जो प्रभावित सेशन को आगे बढ़ाता है। function_awaiting_approval केवल उन निर्णयों को पढ़ता है जो अभी-अभी आए हैं, प्रत्येक को आते ही डिस्पैच करता है (allow पूर्व-अनुमोदित डिस्पैच बन जाता है, deny या aborted एक कृत्रिम अस्वीकृति बन जाता है), और जब awaiting_approval[] खाली होता है तो आगे बढ़ता है। रजिस्टर करने के लिए कोई प्रति-कॉल रिज़्यूम फंक्शन नहीं। पेंडिंग अप्रूवल को पुनर्प्राप्त करने के लिए कोई स्टार्टअप री-स्कैन नहीं। एक ट्रिगर हर सेशन को कवर करता है।

निर्माण के अनुसार फेल-क्लोज्ड: यदि पॉलिसी वर्कर अप्राप्य है या 5-सेकंड टाइमआउट फायर करता है, तो consultBefore gate_unavailable एनवेलप के साथ कॉल को अस्वीकार कर देता है। यदि iii::durable::publish ने स्वयं त्रुटि दी है, तो हुक फ़ैनआउट publish_failed: true लौटाता है और ऑर्केस्ट्रेटर इसे एक अस्वीकृति के रूप में मानता है।

इस आकार से कुछ लेटेंसी लाभ निकलते हैं। आफ्टर-फंक्शन-कॉल हुक एक सब्सक्राइबर-उपस्थिति कैश के माध्यम से publish_collect को शॉर्ट-सर्किट करता है जब विषय के लिए कोई ड्यूरेबल सब्सक्राइबर पंजीकृत नहीं होता है, जो प्रति निष्पादित फंक्शन कॉल में लगभग 500ms हटाता है। tearing_down finishSession() में इनलाइन है, जो प्रति टर्न एक ड्यूरेबल क्यू हॉप हटाता है। context-compaction एक समर्पित agent::turn_end स्ट्रीम की सदस्यता लेता है जिसे ऑर्केस्ट्रेटर टर्न सीमाओं पर उत्सर्जित करता है, इसलिए कॉम्पेक्टर वेकअप प्रति-इवेंट के बजाय प्रति-टर्न होते हैं। सेशन-क्रिएट फ़ैनआउट स्टेट ट्रिगर केवल स्कोप द्वारा गेट करता है और इन-प्रोसेस मेल खाता है, इसलिए पिछला प्रति-लेखन harness::session::is_create_event RPC खत्म हो गया है।

बैच के पूरा होने के बाद, steering_check यह निर्धारित करता है कि जारी रखना है, रोकना है, या max_turns मारा है या नहीं। यदि जारी रखना है, तो assistant_streaming पर वापस लूप करें। यदि रोकना है या max_turns है, तो finishSession() इनलाइन चलता है: agent_end उत्सर्जित करें, सैंडबॉक्स मुक्त करें, stopped पर ट्रांज़िशन करें।

पूरे रन के दौरान, भाग लेने वाला हर वर्कर OTel स्पैन उत्सर्जित करता है जो iii.session.id, iii.message.id, और iii.function.id के साथ टैग किए जाते हैं। वे टैग वही हैं जो इंजन का engine::traces::group_by ट्रेस UI में "Group by Session" / "Group by Message" / "Group by Function" को पॉप्युलेट करने के लिए पढ़ता है। इंस्ट्रुमेंटेशन स्वचालित है: src/runtime/worker.ts हर registerFunction को एक Proxy में लपेटता है, इसलिए किसी भी प्रति-वर्कर कोड को स्पैन जोड़ना याद नहीं रखना पड़ता।

अपना खुद का बनाएँ

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

जिसका अर्थ है "अपना खुद का हार्नेस बनाएँ" उसी ऑपरेशन में विघटित हो जाता है जैसे "कोई भी वर्कर लिखें।" आप उस लेयर को चुनते हैं जिसे आप बदलना चाहते हैं, आप एक वर्कर लिखते हैं जो बस पर समान फंक्शन रजिस्टर करता है, आप इसे iii worker add करते हैं, और बाकी स्टैक आपके वर्कर का उपयोग करना शुरू कर देता है।

दो लेयर ऊपर दी गई वर्कर तालिका में नहीं दिखती हैं, लेकिन हार्नेस के व्यवहार के लिए मायने रखती हैं। स्किल्स वह तरीका है जिससे प्रत्येक वर्कर विज्ञापित करता है कि उसके फंक्शन क्या करते हैं। प्रत्येक वर्कर iii://<worker>/<function> पर एक स्किल प्रकाशित कर सकता है जिसे एजेंट उस फंक्शन को पहली बार कॉल करने से पहले directory::skills::get के माध्यम से प्राप्त करता है। सिस्टम प्रॉम्प्ट प्रति टर्न एक मोड पैराग्राफ, iii आइडेंटिटी प्रीएम्बल, और डिफ़ॉल्ट स्किल बॉडीज़ से इकट्ठा किया जाता है जिनके साथ रन कॉन्फ़िगर किया गया था। दोनों बस-संचालित हैं: स्किल्स iii-directory वर्कर द्वारा परोसी जाती हैं, सिस्टम प्रॉम्प्ट turn-orchestrator द्वारा इकट्ठा किया जाता है। दोनों बदली जा सकती हैं।

पाँच ठोस उदाहरण।

मॉडल कैटलॉग को लाइव API से बदलें। एक वर्कर लिखें जो models::list, models::get, models::supports रजिस्टर करता है। इसे हर N मिनट में अपने प्रोवाइडर के कैटलॉग एंडपॉइंट से लाएँ और कैश करें। इसे प्रकाशित करें। iii worker add your-org/dynamic-models-catalog। स्टैटिक models-catalog वर्कर को रोकें। ऑर्केस्ट्रेटर को कभी फ़रक नहीं पड़ता। वह iii.trigger('models::list') कॉल करता है और इंजन उस वर्कर को रूट करता है जिसने उस फंक्शन आईडी को सबसे हाल ही में पंजीकृत किया था।

एक नया प्रोवाइडर जोड़ें। provider-kimi और provider-lmstudio का आकार पहले से ही साबित करता है। प्रत्येक एक वर्कर है जो provider::<name>::stream और provider::<name>::complete रजिस्टर करता है, ऊपरी API से SSE स्ट्रीम को एक iii चैनल में खत्म करता है, और अपने मॉडल उपयोग को budget::record के माध्यम से llm-budget पर लिखता है। पाँचवाँ प्रोवाइडर जोड़ना एक फ़ोल्डर को एक iii.worker.yaml और एक register.ts के साथ लिखना है। रजिस्ट्री पर प्रकाशित करें, या इसे स्थानीय रखें। turn-orchestrator रन के प्रोवाइडर फील्ड द्वारा प्रोवाइडर चुनता है; वर्कर के कनेक्ट होते ही नए प्रोवाइडर उपलब्ध हो जाते हैं।

निजी आर्टिफैक्ट स्टोर से स्किल्स परोसें। एक वर्कर लिखें जो directory::skills::get और directory::skills::list रजिस्टर करता है, जो आपकी आंतरिक डॉक्स प्रणाली या निजी S3 बकेट द्वारा समर्थित है। डिफ़ॉल्ट iii-directory वर्कर को डिस्कनेक्ट या नाम बदलें। ऑर्केस्ट्रेटर का बूटस्ट्रैप प्रति नेमस्पेस directory::skills::download कॉल करता है; आपका वर्कर जवाब देता है। एजेंट का "नया फंक्शन कॉल करने से पहले प्रति-फंक्शन स्किल प्राप्त करें" पैटर्न अपरिवर्तित काम करता रहता है क्योंकि वायर शेप समान है।

सिस्टम प्रॉम्प्ट को पूरी तरह से ओवरराइड करें। run::start एक वैकल्पिक system_prompt फील्ड स्वीकार करता है। इसे पास करें और ऑर्केस्ट्रेटर आपके स्ट्रिंग का शब्दशः उपयोग करता है, मोड पैराग्राफ + आइडेंटिटी प्रीएम्बल + स्किल्स अपेंडिक्स असेंबली को छोड़ देता है। उपयोगी जब आपके पास एक मौजूदा प्रॉम्प्ट एसेट है जिसे आप हार्नेस बिना संशोधन के सम्मानित करना चाहते हैं। स्किल डाउनलोड बूटस्ट्रैप में चलता रहता है, इसलिए एजेंट कस्टम प्रॉम्प्ट के साथ भी directory::skills::get ऑन-डिमांड डिस्कवरी रखता है।

अप्रूवल गेट UI सतह को बदलें। डिफ़ॉल्ट approval-gate वर्कर approval::resolve रजिस्टर करता है। वायर स्कीमा एक फंक्शन कॉल है:

<code-segment id="2" lang="text">

हैंडलर approvals/<sid>/<cid> = {decision, reason} को iii स्टेट में सहेजता है। ऑर्केस्ट्रेटर का एकल turn::on_approval स्टेट ट्रिगर उस लेखन को उठाता है और सही सेशन को जगाता है। यदि आप कंसोल के बजाय Slack से अप्रूवल चलाना चाहते हैं, तो एक Slack वर्कर लिखें जो /approve <id> और /deny <id> स्लैश कमांड सुनता है, फिर सही पेलोड के साथ approval::resolve कॉल करता है। ऑर्केस्ट्रेटर को कभी फ़रक नहीं पड़ता। पूरा approval-gate वर्कर अछूता रहता है। आपने एक नया वर्कर जोड़ा; आपने मौजूदा को नहीं बदला।

</code-segment>

यदि आप एक अलग पॉलिसी इंजन चाहते हैं (OPA, Cedar, आपकी अपनी DSL), तो एक वर्कर लिखें जो policy::check_permissions रजिस्टर करता है और { decision, rule_id?, matched_constraint? } लौटाता है। डिफ़ॉल्ट पॉलिसी वर्कर को डिस्कनेक्ट करें (जो हार्नेस मेटा-वर्कर के अंदर लिपटा है, इसलिए आप उस हैंडलर को अक्षम करेंगे या एक स्ट्रिप-डाउन मेटा-वर्कर चलाएंगे)। turn-orchestrator का consultBefore अंतर नहीं जानता। वही 5-सेकंड टाइमआउट, वही फेल-क्लोज्ड सिमैंटिक्स, वही वायर शेप।

इन उदाहरणों का उद्देश्य विशिष्ट प्रतिस्थापन नहीं है। यह ऑपरेशन का आकार है। iii स्टैक में हर हार्नेस लेयर बस पर एक या दो फंक्शन आईडी के माध्यम से पहुँच योग्य है। एक लेयर को बदलना उन आईडी को रजिस्टर करने वाला एक वर्कर लिखना है। बाकी सिस्टम स्थिर रहता है।

हार्नेस एक स्लाइडर है, चौराहा नहीं

क्लासिक हार्नेस बहस खुद को पतला बनाम मोटा के रूप में प्रस्तुत करती है। Anthropic का पतला लूप बनाम LangGraph का स्पष्ट DAG। यह फ्रेमिंग मानती है कि आप एक पक्ष चुनते हैं और उसके साथ रहते हैं।

जब हार्नेस एक ही बस पर वर्कर्स से बना होता है, तो पतला बनाम मोटा सिर्फ इस बात का एक हिसाब है कि आप कितने वर्कर इंस्टॉल करते हैं। एक पतला हार्नेस turn-orchestrator + provider-anthropic + auth-credentials + एक न्यूनतम हार्नेस मेटा-वर्कर है। बस इतना ही। कोई अप्रूवल नहीं, कोई बजट नहीं, कोई पॉलिसी इंजन नहीं, कोई हुक फ़ैनआउट नहीं। कुछ भी चलाएँ। मॉडल पर भरोसा करें। स्वायत्त अनुसंधान एजेंटों, प्रयोगात्मक लूप, किसी भी आंतरिक चीज़ के लिए उपयोगी।

एक मोटा हार्नेस सभी तेरह वर्कर + context-compaction + एक कस्टम पॉलिसी वर्कर + एक कस्टम approval-gate + एक Slack-एकीकृत अप्रूवल सतह + बजट वर्कर जो प्रति-वर्कस्पेस कैप लागू करता है, है। एक एजेंट के लिए उपयोगी जो ग्राहक वर्कफ़्लो चलाता है जहाँ हर टूल कॉल ऑडिट करने योग्य होनी चाहिए और हर मॉडल खर्च एक वित्त डैशबोर्ड तक जाना चाहिए।

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

यह एक ही वर्कर के अंदर भी लागू होता है। turn-orchestrator ने अभी-अभी एक रिफैक्टर भेजा है जिसने अपने FSM को ग्यारह अवस्थाओं से घटाकर सात कर दिया, प्रति-कॉल turn::approval_resume::<sid>/<cid> तंत्र को scope approvals पर एक रिएक्टिव turn::on_approval स्टेट ट्रिगर के पक्ष में हटा दिया, और tearing_down को finishSession() पोर्ट में इनलाइन कर दिया। स्टैक में हर दूसरा वर्कर (approval-gate, session, llm-budget, providers, models-catalog, auth-credentials, hook-fanout, context-compaction) अपरिवर्तित रहा। approval::resolve का वायर शेप नहीं हिला। अनुबंध स्थिर रहे। यह वह गुण है जो कंपोज़िशन आपको देता है: एक वर्कर का एक बड़ा आंतरिक पुनर्लेखन एक आत्म-निहित परिवर्तन है क्योंकि प्रत्येक पड़ोसी बस-स्तरीय फंक्शन आईडी के माध्यम से उससे बात करता है।

यह वह हिस्सा है जो फ्रेमवर्क मॉडल आपको नहीं दे सकता। एक फ्रेमवर्क आपके लिए स्लाइडर पर एक स्थान चुनता है और आपको बंद कर देता है। वर्कर मॉडल स्लाइडर को आपके हाथ में छोड़ देता है।

व्यवहार में इसका क्या मतलब है

यदि आप एक फ्रेमवर्क के ऊपर एक एजेंट चला रहे हैं और वही सीमा समस्याएँ महसूस कर रहे हैं जो अधिकांश टीमें स्केल पर हिट करती हैं, तो उत्तर शायद "हार्नेस को अपने फ्रेमवर्क में फिर से लिखना" नहीं है। पॉलिसी इंजन उस तरह से विस्तार नहीं करता जैसा आपको चाहिए। अप्रूवल UI फ्रेमवर्क के चैट सरफेस में वायर्ड है। क्रेडेंशियल स्टोर आपके सीक्रेट्स मैनेजर से बात नहीं कर सकता। बजट ट्रैकर एक साइडकार डेटाबेस में है जिसे ट्रेस नहीं देख सकता। उत्तर एक ऐसे सब्सट्रेट पर स्विच करना है जहाँ हार्नेस पहले स्थान पर विघटित है।

तर्क को महसूस करने का सबसे तेज़ तरीका github.com/iii-hq/workers को क्लोन करना, pnpm install, pnpm build, और कम्पोजिट एंट्री पॉइंट चलाना है। आपको एक iii इंजन की ओर इशारा करते हुए पूरा चौदह-वर्कर हार्नेस मिलेगा। आप बूट सूची से इसकी प्रविष्टि हटाकर किसी भी वर्कर को अक्षम कर सकते हैं। आप समान फंक्शन आईडी रजिस्टर करने वाला एक प्रतिस्थापन लिखकर किसी भी वर्कर को बदल सकते हैं। आप इसके हुक विषयों पर एक सब्सक्राइबर जोड़कर किसी भी वर्कर का विस्तार कर सकते हैं। hook-fanout::publish_collect वह सामान्य है जिस पर हर iii हुक बनाया गया है।

दस्तावेज़ iii.dev/docs पर रहते हैं। इंजन github.com/iii-hq/iii पर है। वर्कर रजिस्ट्री workers.iii.dev पर है। हार्नेस बंडल github.com/iii-hq/workers/harness पर है।

दांव

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

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

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

"अपना खुद का एजेंट हार्नेस बनाएँ" ऐसा दिखता है जब सब्सट्रेट सही आकार का हो। वर्कर चुनें। गुम हुए लिखें। कंपोज़ करें। हार्नेस कंपोज़िशन है।

आधुनिक दुनिया को जिस परफेक्ट एजेंट हार्नेस की ज़रूरत है, उसे बनाने में हमारे साथ शामिल हों: discord.gg/iiidev

iii ओपन सोर्स है। शुरुआत करें iii.dev/docs पर। हार्नेस वर्कर github.com/iii-hq/workers पर और इंजन github.com/iii-hq/iii पर है।

— माइक पिकोलो, Founder & CEO @iiidevs

एक क्लिक में सहेजें

YouMind में वायरल लेखों की AI गहन पढ़ाई

स्रोत सहेजें, केंद्रित सवाल पूछें, तर्क का सारांश बनाएँ और एक वायरल लेख को एक ही AI वर्कस्पेस में दोबारा इस्तेमाल करने लायक नोट्स में बदलें।

YouMind देखें
क्रिएटर्स के लिए

अपने Markdown को एक साफ़-सुथरे 𝕏 आर्टिकल में बदलें

जब आप अपना लंबा कंटेंट पब्लिश करते हैं, तो इमेज, टेबल और कोड ब्लॉक को 𝕏 के लिए फ़ॉर्मेट करना मुश्किल होता है। YouMind पूरे Markdown ड्राफ़्ट को एक साफ़-सुथरे, पोस्ट के लिए तैयार 𝕏 आर्टिकल में बदल देता है।

Markdown से 𝕏 आज़माएँ

समझने के लिए और पैटर्न

हाल के वायरल लेख

और वायरल लेख देखें