यह पृष्ठ चीनी मूल से अनूदित है। दोनों में अंतर होने पर मूल पाठ मान्य है।
Confer — प्रोटोकॉल का डिज़ाइन
Confer इंस्टेंसों के बीच, और उपयोगकर्ता के क्लाइंट तथा सर्वर के बीच के सभी प्रोटोकॉल यहाँ परिभाषित हैं। सब खुले मानकों पर टिके हैं, ताकि आगे फ़ेडरेशन आसान रहे।
Agent की पहचान
DID:web प्रारूप
हर उपयोगकर्ता या कंपनी का इंस्टेंस अपना DID दस्तावेज़ खुद रखता है:
https://acme.com/.well-known/did.json
DID दस्तावेज़ की संरचना (W3C DID v1.0 के अनुकूल):
{
"@context": ["https://www.w3.org/ns/did/v1"],
"id": "did:web:acme.com",
"verificationMethod": [
{
"id": "did:web:acme.com#key-1",
"type": "Ed25519VerificationKey2020",
"controller": "did:web:acme.com",
"publicKeyMultibase": "z6MkpTHR8VNsBxYAAWHut2Geadd9jSrue..."
}
],
"service": [
{
"id": "did:web:acme.com#confer-agent",
"type": "ConferAgent",
"serviceEndpoint": "https://acme.com/a2a/v1"
}
]
}
उपयोगकर्ता के Agent का DID इस रूप में होता है: did:web:acme.com:agents:laowang — मुख्य इंस्टेंस और उसके आगे एक पथ खंड। इसी से एक इंस्टेंस कई उपयोगकर्ताओं को संभाल सकता है।
did:web विनिर्देश के अनुसार, उप-पहचानकर्ता वाला DID (यानी पथ खंडों वाला) उसी पथ के दस्तावेज़ पर हल होता है, इंस्टेंस की जड़ के .well-known पर नहीं:
did:web:acme.com:agents:laowang→https://acme.com/agents/laowang/did.json(कोलन → स्लैश, अंत में/did.json)- नंगा इंस्टेंस DID
did:web:acme.com→https://acme.com/.well-known/did.json - असली पोर्ट
%3Aसे कूटबद्ध होता है:did:web:acme.com%3A3000:agents:laowang→https://acme.com:3000/agents/laowang/did.json(नंगा कोलन जैसे:8080एक पथ खंड है, पोर्ट नहीं)
कुंजी बदलना
- DID दस्तावेज़ में कई सत्यापन विधियाँ घोषित की जा सकती हैं, जिससे बिना रुकावट कुंजी बदली जा सकती है।
- पुरानी कुंजी कम से कम 30 दिन रखी जाती है (ताकि रास्ते में चल रहे अनुरोध विफल न हों)।
- रद्द करना दस्तावेज़ से सत्यापन विधि हटाकर होता है।
- कार्यान्वयन:
POST /api/v1/users/me/keys/rotate#key-<n>बनाता है और तुरंत उससे हस्ताक्षर करता है; पुरानी कुंजी सेवानिवृत्त होती हुई चिह्नित होती है और केवल सत्यापन के लिए 30 दिन DID document में रहती है।POST /api/v1/users/me/keys/revoke {key_id}सेवानिवृत्त हो रही कुंजी को तुरंत हटाता है (उपयोग में चल रही कुंजी सीधे रद्द नहीं हो सकती; पहले बदलें)। दोनों केवल साइन-इन सत्र स्वीकारते हैं और ऑडिट होते हैं। - सत्यापनकर्ता पक्ष: जिस हस्ताक्षर का keyId हमारे पास कैश की गई सामने वाले की DID document प्रति में न हो, उस पर एक बार ताज़ा प्राप्ति होती है (प्रति DID प्रति मिनट अधिकतम एक बार), ताकि कुंजी बदलने के बाद सामने वाले का पहला संदेश हमारे कैश के कारण अस्वीकार न हो।
AgentFacts (NANDA के अनुकूल)
हर Agent अपना विवरण देने वाला एक AgentFacts प्रकाशित करता है। स्थान:
https://acme.com/agents/{slug}/agent.json
या well-known की सामान्य सूची:
https://acme.com/.well-known/agents.json
संरचना का उदाहरण:
{
"@context": "https://nanda.dev/schemas/agent/v1",
"did": "did:web:acme.com:agents:support",
"name": "ABC Industries Support Agent",
"description": "Technical support for X100, X200 industrial controllers",
"owner": {
"type": "Organization",
"name": "ABC Industries Ltd.",
"url": "https://acme.com"
},
"capabilities": [
{
"type": "qa",
"scope": ["X100", "X200", "Modbus", "RTU", "TCP"],
"languages": ["en", "zh", "de"]
},
{
"type": "code-generation",
"scope": ["python", "c", "embedded"],
"languages": ["en", "zh"]
}
],
"endpoints": {
"a2a": "https://acme.com/a2a/v1",
"stream": "https://acme.com/a2a/v1/stream"
},
"trust": {
"verifiedBy": ["did:web:nanda.org"],
"issuedAt": "2024-10-01T00:00:00Z"
},
"publicKey": {
"id": "did:web:acme.com#key-1",
"type": "Ed25519VerificationKey2020"
}
}
क्षेत्रों का अर्थ:
capabilities: यह Agent क्या कर सकता है। Claude Codescopeक्षेत्र से कीवर्ड रूटिंग करता है (X100 से जुड़ा कोड लिखते समय अपने-आप इसी Agent से पूछता है)।languages: समर्थित भाषाएँ। अनुवाद नीति के लिए।trust.verifiedBy: तीसरे पक्ष का भरोसा-समर्थन (वैकल्पिक; आगे NANDA देगा)।publicKey: A2A संचार के लिए हस्ताक्षर की सार्वजनिक कुंजी।
A2A प्रोटोकॉल
प्रोटोकॉल की परत
सारा A2A संचार HTTPS POST/GET से होता है, JSON में कूटबद्ध। /a2a/v1 के नीचे दो बाइंडिंग साथ-साथ रहती हैं:
- Linux Foundation के A2A v1.0 की HTTP+JSON बाइंडिंग (
routes/a2a-rest.ts), जिसके पथ विनिर्देश की §11.3 के ठीक वैसे हैं:POST /message:send,GET /tasks/{id}इत्यादि। Agent Card यही घोषित करता है और मानक क्लाइंट यही बुलाते हैं। देखेंdocs/05-api.md। - Confer की अपनी बोली (
routes/a2a.ts), जो इंस्टेंसों के बीच चलती है और/.well-known/agents.jsonसे खोजी जाती है।
दोनों एक ही फाटकों से गुज़रते हैं — हस्ताक्षर सत्यापन, सहमति फाटक, नीति निर्णय, थ्रेड का स्वामित्व — और वे सब a2a/inbound.ts में हैं; फ़र्क़ सिर्फ़ तार पर के प्रारूप का है। हर बाइंडिंग के लिए ये फाटक अलग से लिखना ही वह कारण है जिससे अंतर-किरायेदार थ्रेड इंजेक्शन का बग चार बार लिखा गया; इन्हें दोबारा मत बाँटिए।
मूल बात: HTTP Message Signatures (RFC 9421) इस्तेमाल होते हैं, bearer टोकन नहीं। कारण:
- bearer टोकन पकड़ में आते ही बेकार हो जाता है।
- HTTP हस्ताक्षर एक विशिष्ट अनुरोध से बँधा होता है (विधि + पथ + क्वेरी + बॉडी का डाइजेस्ट + समय-मुहर)।
- पुनःप्रेषण से बचाव: अनुरोध की
Dateको 5 मिनट की खिड़की में होना चाहिए, और हर सत्यापित हस्ताक्षर एक रिप्ले कैश (nonce) में दर्ज होता है, इसलिए खिड़की के भीतर वही अनुरोध दोबारा भेजने पर अस्वीकार हो जाता है; हस्ताक्षर सत्यापित होते ही भेजने वाले की पहचान तय हो जाती है।
हस्ताक्षर जिन घटकों को ढकता है वे हैं @method, @authority और @path, साथ में @query (जब अनुरोध में क्वेरी स्ट्रिंग हो) और content-digest (जब बॉडी हो), और date। @query कोई वैकल्पिक बारीकी नहीं है: @path ? पर रुक जाता है, और REST बाइंडिंग का GET /tasks पूरी तरह क्वेरी पैरामीटरों से छाँटता और पन्ने पलटता है; उन्हें न ढकना यानी बीच के किसी को उन्हें मनमाने ढंग से बदलने देना, और हस्ताक्षर फिर भी सही निकलेगा। हस्ताक्षर के पैरामीटरों में हर अनुरोध पर एक यादृच्छिक nonce भी होता है — created की सूक्ष्मता सेकंड तक ही है, इसलिए एक ही सेकंड में दो समान अनुरोध एक जैसे बाइट पर हस्ताक्षर करते, और सामने वाले की रिप्ले कैश उन्हें हमला मानकर ठुकरा देती (किसी task की जाँच या कोई भी पुनःप्रयास इसी से टकराता है)। असली पुनःप्रेषण — वही बाइट ज्यों के त्यों दोबारा — अब भी पकड़ा जाता है, क्योंकि तब हस्ताक्षर बाइट-दर-बाइट वही होता है।
इस परत में विनिर्देश के securitySchemes नहीं हैं: वे API कुंजी, HTTP प्रमाणन, OAuth2, OIDC या mTLS हैं, और इनमें से कोई भी अनुरोध-हस्ताक्षर नहीं है। असली अपेक्षा Card पर एक अनिवार्य विस्तार के रूप में घोषित होती है, और REST बाइंडिंग उसे §3.3.4 के अनुसार लागू करती है: जो क्लाइंट वह विस्तार घोषित नहीं करता उसे ExtensionSupportRequiredError मिलता है, न कि ऐसा 401 जो कुछ समझाता ही नहीं।
आने वाले अनुरोध का उदाहरण
POST /a2a/v1/messages HTTP/1.1
Host: acme.com
Content-Type: application/json
Date: Sun, 24 Nov 2024 14:30:00 GMT
Content-Digest: sha-256=:X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=:
Signature-Input: sig1=("@method" "@authority" "@path" "content-digest" "date");keyid="did:web:vendor-x.com#key-1";created=1732458600;alg="ed25519"
Signature: sig1=:aBcDeF...:
Authorization: Capability eyJhbGciOiJFZDI1NTE5IiwidHlwIjoiQ2FwIn0...
{
"from": "did:web:vendor-x.com:agents:engineer-li",
"to": "did:web:acme.com:agents:support",
"thread_id": "thread_8f3a9c",
"message": {
"type": "question",
"content": "RTU मोड में X100 की वोल्टेज सीमा क्या है?",
"language": "hi",
"context": {
"via": "claude-code",
"project_hint": "modbus integration"
}
}
}
सत्यापन का क्रम (प्राप्तकर्ता की ओर)
Signature-InputऔरSignatureहेडर पढ़ें।Signature-Inputकेkeyidपैरामीटर से DID निकालें।- DID दस्तावेज़ लाएँ (कैश सहित: ETag + 60s TTL)।
- सार्वजनिक कुंजी निकालें, RFC 9421 §2.5 के अनुसार हस्ताक्षर की आधार-श्रृंखला दोबारा बनाएँ, और हस्ताक्षर सत्यापित करें।
- जाँचें कि
Content-Digestबॉडी के हैश से मेल खाता है। - जाँचें कि
Date5 मिनट के भीतर है (पुनःप्रेषण से बचाव)। - capability टोकन सत्यापित करें (macaroon शैली, नीचे विस्तार से)।
- संपर्क सहमति फाटक: भेजने वाला प्राप्तकर्ता के संपर्कों में पहले से है? यदि नहीं → LLM नहीं चलता; अनुरोध संपर्क-निवेदन के रूप में लंबित रह जाता है (नीचे देखें)।
- यदि है → नीति इंजन तय करता है कि उत्तर देना है या नहीं।
Capability टोकन
Capability टोकन से भेजने वाला Agent कह पाता है “मैं उपयोगकर्ता X की ओर से Y प्रकार का सवाल पूछने आया हूँ”, और अधिकार बारीकी से सीमित हो जाते हैं।
रूप JWT जैसा, पर विचार macaroon वाला:
{
"iss": "did:web:vendor-x.com",
"sub": "did:web:vendor-x.com:users:engineer-li",
"aud": "did:web:acme.com",
"scope": ["ask:technical", "ask:product:X100"],
"exp": 1737000000,
"ctx": {
"thread_id": "thread_8f3a9c",
"delegation_depth": 1
}
}
scope: किस प्रकार के सवाल पूछे जा सकते हैं।delegation_depth: प्रतिनिधिकरण से कितनी बार आगे भेजा गया (अनंत श्रृंखला रोकने के लिए)।
धारा में उत्तर
LLM उत्तर धारा में बनाता है, और A2A भी SSE समर्थित है:
GET /a2a/v1/stream/{message_id} HTTP/1.1
Host: acme.com
Signature: ...
text/event-stream लौटाता है:
event: token
data: {"text": "X100 "}
event: token
data: {"text": "RTU मोड में "}
event: citation
data: {"source": "X100 इंस्टॉलेशन मैनुअल पृ. 12", "url": "..."}
event: done
data: {"thread_id": "thread_8f3a9c"}
अनुमति मॉडल (Claude Code से प्रेरित)
तीन स्तर:
L1 — स्वतः (पुष्टि की ज़रूरत नहीं)
- मेरा Agent मेरी ही सामग्री पढ़े।
- सामने वाला Agent अपने दस्तावेज़ों का हवाला देकर उत्तर दे।
- Agents के बीच विशुद्ध पूछताछ (कोई दुष्प्रभाव नहीं, कोई डेटा साझा नहीं)।
L2 — एक बार पूछना
- कोई निर्देशिका या फ़ाइल सामने वाले Agent से साझा करना।
- सामने वाले Agent को मेरी बातचीत का संदर्भ दिखाना।
- दूसरे इंस्टेंस को डेटा आगे भेजना।
- कोई औज़ार चालू करना (पहली बार)।
इंटरफ़ेस में: अनुमति कार्ड खुलता है, चार विकल्पों के साथ:
- इस बार अनुमति
- हमेशा अनुमति (उसी peer और दायरे तक सीमित)
- विवरण देखें
- अस्वीकार
L3 — स्पष्ट सहमति (हर बार पूछा जाए)
- मेरा Agent मेरी ओर से निमंत्रण स्वीकार करे, भुगतान करे, अनुबंध पर हस्ताक्षर करे।
- अपरिवर्तनीय कार्य (मिटाना, पैसे भेजना, बाहरी वचन देना)।
- धन या क़ानून से जुड़े वचन।
इंटरफ़ेस में: मोडल विंडो + कार्रवाई की विस्तृत सूची + उलटी गिनती (ग़लती से दबने से बचाव)।
स्थायी नीतियाँ
उपयोगकर्ता पहले से नियम तय कर सकता है जो डिफ़ॉल्ट व्यवहार पर भारी पड़ें:
peer.acme-industries:
allow:
- read: "src/modbus/**"
- ask: "technical:*"
deny:
- read: ".env"
- read: "**/secrets/**"
- ask: "personal:*"
always_consult: true
peer.unknown:
default: ask_user
require_human_in_loop: true
संपर्क सहमति फाटक
किसी A2A संदेश का उत्तर देना प्राप्तकर्ता का LLM बजट खर्च करता है। ताकि कोई अजनबी Agent मालिक की जानकारी के बिना बेतहाशा संदेश भेजकर उसके टोकन न जला दे, इस खर्च की पूर्वशर्त है जुड़ा होना:
- पहले से जुड़ा peer (प्राप्तकर्ता के
peer_contactsमें) → जुड़ाव ही सहमति है; नीति इंजन में जाकर सामान्य रूप से संसाधित होता है। - न जुड़ा peer →
POST /a2a/v1/messages202लौटाता है, बॉडी{ "status": "pending_connection" }; न संवाद बनता है, न संदेश सहेजा जाता है, न LLM चलता है। साथ हीaction='connect'वाला एक लंबित संपर्क-निवेदन प्रतीक्षा-पेटी में रख दिया जाता है (peer के हिसाब से दोहराव हटाकर, ताकि बार-बार के संदेश उसे भर न दें)। - मालिक अपनी अनुमति-पेटी में देखता है «अमुक Agent जुड़ना चाहता है + उसका पहला संदेश»। स्वीकृति पर
peer_contactsमें लिख दिया जाता है (जुड़ाव हो गया) और उसके बाद उस peer के संदेश सामान्य रूप से संसाधित होते हैं; अस्वीकार पर कोई जुड़ाव नहीं बनता।
मॉडल LinkedIn या किसी कंपनी-फ़ेडरेशन जैसा है: खोज की परत खुली (कोई भी agents.json और AgentFacts पढ़ सकता है), मेल-जोल की परत सहमति-सापेक्ष (जुड़ने के बाद ही दूसरे की गणना-शक्ति खर्च होती है)।
«जुड़ा हुआ» होने के दो रास्ते हैं:
- प्राप्तकर्ता खुद
POST /contacts/lookup→POST /contactsसे उस peer को जोड़े; - peer पहल करे और प्राप्तकर्ता पेटी में उसका निवेदन स्वीकार करे।
थ्रेड का बंधन (thread_id का दायरा)
आने वाले संदेश का thread_id peer की एक प्रार्थना है, कोई आधिकारिक आदेश नहीं। gateway उसे मूल रूप में तभी दोबारा इस्तेमाल करता है जब दो शर्तें एक साथ पूरी हों:
- वह peer उस संवाद का भागीदार पहले से हो;
- वह संवाद जिस Agent को संदेश भेजा गया है उसके मालिक का हो (
conversations.created_by)।
दूसरी शर्त छोड़ी नहीं जा सकती: peer_agents DID के हिसाब से विश्व भर में अद्वितीय है, इसलिए एक ही peer कई मालिकों से एक साथ जुड़ा हो सकता है। सिर्फ़ पहली जाँचें तो A और B दोनों से जुड़ा कोई peer, B के Agent को संदेश भेजते समय A का thread_id लगा सकता है और संदेश को A के संवाद में उड़ेल सकता है — B का Agent A के इतिहास को संदर्भ मानकर उत्तर देगा, उत्तर A के थ्रेड में लिखा जाकर A तक प्रसारित होगा, और ऊपर से A के संवाद की सामग्री B की दीर्घकालिक स्मृति में जा बैठेगी।
दोनों पूरी हों तो इसका अर्थ है कि सामने वाला हमारी ओर से भेजे गए संदेश का उत्तर दे रहा है (और तब thread_id हमारे अपने संवाद की id है)। न हों तो वह peer की अपनी गिनती का थ्रेड है — यहाँ किसी चीज़ की ओर इशारा नहीं करता, पर peer के लिए स्थिर है, इसलिए हमारी संवाद-id sha256('a2a-thread:<मालिक की id>:<peer पंक्ति की id>:<peer का thread_id>') से व्युत्पन्न होती है (lib/derived-id.ts, जो 26 अक्षरों का Crockford लौटाता है, ULID जैसा ही रूप)। इस तरह उसी peer थ्रेड के आगे के संदेश हमेशा उसी संवाद में लौटते हैं।
पहले «अनजाने thread_id» को «कोई थ्रेड नहीं» मान लिया जाता था, इसलिए प्राप्तकर्ता हर आने वाले संदेश पर एक नया संवाद बना देता था: आगे का सवाल मूल सवाल के पास कभी नहीं रहता, मालिक की संवाद-सूची एक-एक पंक्ति वाले थ्रेडों से भर जाती, loadA2AHistory को कोई इतिहास नहीं मिलता, और Agent हर दौर ऐसे उत्तर देता मानो पहली बार बात हो रही हो।
«मानचित्र तालिका रखने» के बजाय व्युत्पन्न करने के दो फ़ायदे हैं: कोई माइग्रेशन नहीं चाहिए, और होड़ संभव ही नहीं — एक साथ आए दो संदेश प्राथमिक कुंजी पर टकराते हैं, बजाय इसके कि हर एक अपना संवाद बना ले (इसीलिए संवाद बनाना लेन-देन के भीतर onConflictDoNothing के साथ होता है)। जोड़ी गई श्रृंखला में केवल अंतिम खंड की लंबाई बदल सकती है और वही peer के नियंत्रण में है; बाक़ी खंड 26 अक्षरों की निश्चित id हैं, इसलिए बिना बचाव वाला कोलन कोई द्विअर्थ नहीं बनाता।
संवाद बनाते समय conversation_participants में मालिक और peer दोनों एक साथ लिखे जाते हैं। मालिक की भागीदारी वाली पंक्ति ही संवाद-सूची और प्रति-संवाद पठन फाटक का आधार है; वह न हो तो मालिक को वह थ्रेड दिखेगा ही नहीं जिसका उत्तर उसका अपना Agent दे रहा है।
इसलिए thread_id हर पक्ष की अपनी-अपनी संवाद id है, दोनों ओर एक जैसी नहीं। इससे दो नियम निकलते हैं जो छोड़े नहीं जा सकते:
- उत्तर में वही
thread_idलौटाना होगा जो पूछने वाले ने भेजा था, अपना नहीं। ऊपर की दूसरी शर्त (ठीक ही) ऐसे थ्रेड को ठुकरा देगी जो उसका नहीं है, इसलिए हमारी संवाद id लेकर लौटा उत्तर सामने वाले के यहाँ बिलकुल नए संवाद में जमा हो जाएगा; पूछने वाला अब भी अपने बनाए संवाद की जाँच करता रहेगा, और/api/v1/consult/{id}/replyहमेशाpendingपर अटका रहेगा — जबकि दोनों मशीनों पर एक भला-चंगा उत्तर पड़ा है। messages.thread_rootमें स्थानीय संवाद id लिखी जाती है, peer का कच्चा मान कभी नहीं। वह स्तंभchar(26)है और हमारी अपनी ULID के लिए बना है: बाहरी मान रखना ऐसे संवाद की ओर इशारा करेगा जो शायद हमारा है ही नहीं, और किसी भी peer को यह मौक़ा देगा कि 26 अक्षरों से लंबाthread_idभेजकर इस endpoint को 500 पर गिरा दे। आने वालेthread_idकी लंबाई की अलग जाँच भी है।
उत्तर न दे पाना भी एक उत्तर है
जब जिस Agent को संदेश गया है वह यह दौर चला ही नहीं सकता (मॉडल तय नहीं, प्रदाता अनजाना, प्रदाता तय पर कुंजी नहीं, या मॉडल की कॉल में त्रुटि), तो वह type: 'notification' भेजता है जिसका context.error मशीन-पठनीय कोड लिए होता है (no_model_configured / unknown_provider / no_key_for_provider / agent_error), और जिसका content अंग्रेज़ी में एक व्याख्या-वाक्य है। notification इसलिए कि वह सामने वाले पर दोबारा स्वतः उत्तर नहीं जगाता (यह सिर्फ़ question करता है)।
ऐसा न करने का मतलब «एक सूचना कम» नहीं है: विफलता उत्तर देने वाले के यहाँ बस एक लॉग पंक्ति बनकर रह जाती है, तार पर कुछ नहीं जाता, और पूछने वाले का /api/v1/consult/{id}/reply समय-सीमा तक जाँचता रहकर pending लौटाता है — हर पुनःप्रयास पर वही, और «अभी सोच रहा है» तथा «कभी नहीं आएगा» में फ़र्क़ करने का कोई उपाय ही नहीं बचता।
सामने वाला दूसरा इंस्टेंस है और हमारी भाषा नहीं जानता, इसलिए आधार context.error का कोड है; content सिर्फ़ मनुष्य के पढ़ने लायक़ आख़िरी सहारा है। यह «सर्वर उपयोगकर्ता के लिए वाक्य नहीं बनाता» वाले नियम से नहीं टकराता: वह नियम इसी इंस्टेंस के अपने क्लाइंट को भेजे जाने वाले पाठ पर लागू होता है।
वही विफलता डेटाबेस में भी दर्ज होती है — संवाद के भीतर content_type: 'system_notice' वाले संदेश के रूप में (in_reply_to उसी सवाल की ओर, content_json में वही कोड, और वाक्य क्लाइंट i18n से बनाता है)। भेजकर दर्ज न करने के तीन परिणाम हैं, और तीनों सचमुच हो चुके हैं: मालिक को अपने IM में सामने वाले का सवाल दिखता है और उसके बाद कुछ नहीं, और उसे कभी पता ही नहीं चलता कि मॉडल तो उसी ने तय नहीं किया था; A2A REST बाइंडिंग का task FAILED के बजाय WORKING पर ठहरा रहता है, और क्लाइंट ऐसी चीज़ की जाँच करता रहता है जो कभी पूरी नहीं होगी; और GET /a2a/v1/stream/{id} अनंत काल तक pending लौटाता है। इस नोट के साथ तीनों एक ही बार में निर्णय-योग्य अंतिम अवस्था में आ जाते हैं।
पता: दो DID एक ही Agent पर जाते हैं
to Agent DID (did:web:<host>:agents:<user>:agent, जो सार्वजनिक सूची /.well-known/agents.json में दिखता है) और मालिक DID (did:web:<host>:agents:<user>) — दोनों स्वीकार करता है। बाद वाला ही एकमात्र पहचानकर्ता है जो DID दस्तावेज़ पर हल होता है, और वही क्लाइंट उपयोगकर्ता को नक़ल करने के लिए दिखाता है: सिर्फ़ पहला मानने से «DID चिपकाकर संपर्क जोड़ना» ऐसा संपर्क देता है जिससे जुड़ाव भी होता है, हस्ताक्षर भी सही निकलता है, और जवाब 404 आता है।
इसी तरह, भेजने वाला peer जुड़ा है या नहीं यह तय करते समय from (Agent DID) और हस्ताक्षर सत्यापन से मिला हस्ताक्षरकर्ता DID (मालिक DID) — दोनों मानने होंगे: peer_agents में पंक्ति DID के हिसाब से बनती है, और उनमें से कौन-सा वहाँ दर्ज हुआ यह इस पर निर्भर करता है कि संपर्क उस समय किस तरह जोड़ा गया था; सिर्फ़ from मानने से सामने वाले का उत्तर «किसी अजनबी का संपर्क-निवेदन» बन जाता है।
प्रतीक्षा-पेटी (मालिक की अनुपस्थिति में उत्तर)
जब पहले से जुड़े peer का सवाल आए और मालिक मौजूद न हो, तो नीति इंजन तय करता है (evaluatePolicy, action=ask, L2):
allow(डिफ़ॉल्ट, क्योंकि जुड़ाव ही सहमति है) → Agent सीधे उत्तर देता है (201+ स्वतः उत्तर का चक्र)।ask_user(मालिक ने स्पष्ट रूप सेpolicies_json.default='ask_user'या{action:'ask',decision:'ask_user'}नियम रखा हो) → लागू हो चुका है: आने वाला सवाल फिर भी सहेजा और प्रसारित होता है (मालिक उसे IM में देख लेता है), पर स्वतः उत्तर नहीं जाता; पेटी मेंaction='ask'वाली लंबित अनुमति रख दी जाती है, औरPOST /a2a/v1/messages202 { "status": "pending_approval", "message_id" }लौटाता है। मालिक को सवालGET /permissions/pendingमें दिखता है;POST /permissions/{id}/decideमेंallow_*तय करने पर Agent उसकी ओर से उत्तर देता है (in_reply_toसहित उत्तर + बाहर जाने वाली सुपुर्दगी),denyतय करने पर कोई उत्तर नहीं। peer की ओरGET /a2a/v1/stream/{message_id}स्वीकृति से पहलेstatus:'pending'लौटाता है, बाद में उत्तर।deny(स्पष्ट अस्वीकार नियम) →403 policy_denied।
A2A उत्तर की क्षमता: आने वाला A2A उत्तर और वेब चैट एक ही साझा ऑर्केस्ट्रेशन से गुज़रते हैं (
orchestration/agent-orchestrator.tsकाrunAgentTurn), पर उनकी क्षमताएँ एक जैसी नहीं हैं।runAgentTurnएक अनिवार्यaudienceलेता है ('owner' | 'peer'— अनिवार्य, डिफ़ॉल्ट वाला नहीं, क्योंकि डिफ़ॉल्ट मान वही होता जो ढीला है), और उसी से औज़ारों का समूह तथा पहुँच में आने वाला डेटा-तल दोनों तय होते हैं:
- मालिक का दौर (वेब चैट):
web_search,search_knowledge_base(सभी ज्ञान-कोश),list_knowledge_bases,search_memory,list_contacts, और दीर्घकालिक स्मृति स्वतः जोड़ी जाती है।- peer का दौर (आने वाला A2A उत्तर): केवल
web_search,search_knowledge_baseऔरlist_knowledge_bases, और खोज केवल उन्हीं ज्ञान-कोशों तक सीमित है जिन परshared_with_peersका निशान है; दीर्घकालिक स्मृति नहीं उठाई जाती, और नsearch_memoryदिया जाता है नlist_contacts— पहला मालिक की निजी बातचीत से जमे तथ्य हैं, दूसरा उसका सामाजिक जाल; किसी अजनबी के सवाल का उत्तर देने में इनमें से किसी की ज़रूरत नहीं।सीमा डेटा-तल से बनती है, प्रॉम्प्ट से नहीं: peer का सवाल और मालिक के निर्देश मॉडल तक एक ही तरह के पाठ के रूप में पहुँचते हैं, इसलिए «Agent बताने से इनकार कर देगा» टिकता नहीं; टिकता सिर्फ़ यह है कि खोज भौतिक रूप से वहाँ पहुँच ही न सके। इसी कारण कोई औज़ार न देना पहुँच-नियंत्रण नहीं है: मॉडल ऐसे औज़ार-नाम की कॉल भी कर सकता है जो उसे कभी दिया ही नहीं गया, इसलिए मालिक-मात्र वाली शाखाएँ
executeToolCallके भीतरaudienceदोबारा जाँचती हैं।दोनों तरह के दौर मालिक की कुंजी इस्तेमाल करते हैं, पूछने वाले peer की नहीं। मिले हुए ज्ञान-कोश अंश
messages.citations_jsonमें उद्धरण के रूप में सहेजे जाते हैं, और उत्तर पूरा होने पर इस दौर के तथ्य अतुल्यकालिक रूप से दीर्घकालिक स्मृति में जमा होते हैं (peer के दौर से आई पंक्तियों परa2aका निशान लगता है, और उठाते समय उनका स्रोत बताया जाता है)। मालिक के पास embedding / ज्ञान-कोश / tavily कुंजियाँ न हों तो सहजता से केवल-LLM उत्तर पर उतर आता है (कोई त्रुटि नहीं, कोई उद्धरण नहीं)।allowका और स्वीकृतask_userका उत्तर-मार्ग यही ऑर्केस्ट्रेशन साझा करते हैं।
ask='ask'वाली लंबित अनुमति काscope_jsonइस रूप में होता है:{ kind:'a2a_question', conversation_id, inbound_message_id, sender_did, peer_id, content }— स्वीकृति के समय उत्तर दोबारा बनाकर आगे बढ़ाने के लिए इतना काफ़ी है (agent और peeruser_id/peer_idसे उसी समय पढ़े जाते हैं; निष्क्रिय-दोहराव-सुरक्षित: उत्तर पहले से हो तो छोड़ दिया जाता है)। स्थायी नीति तय करने का UI, «संपादित करके उत्तर देना», और push सूचनाएँ अब भी backlog में हैं।
फ़ेडरेटेड खोज
डोमेन से खोज
डोमेन acme.com मिलने पर क्लाइंट:
- मुख्य DID के लिए
https://acme.com/.well-known/did.jsonलाता है। - उस डोमेन के सभी सार्वजनिक Agents की सूची के लिए
https://acme.com/.well-known/agents.jsonलाता है। - एक चुनकर संपर्क में जोड़ लेता है।
उपयोगकर्ता DID का हल
किसी उपयोगकर्ता के Agent का उप-पहचानकर्ता DID मिल जाने पर उसका DID दस्तावेज़ did:web विनिर्देश के अनुसार हल होता है:
did:web:acme.com:agents:laowang→GET https://acme.com/agents/laowang/did.json- नंगा इंस्टेंस DID
did:web:acme.com→GET https://acme.com/.well-known/did.json
आने वाले A2A का हस्ताक्षर सत्यापन ठीक इसी रास्ते चलता है: Signature-Input के keyid से हस्ताक्षरकर्ता DID निकालें → ऊपर वाले URL पर हल करें → verificationMethod में से keyid से मेल खाती सार्वजनिक कुंजी लेकर सत्यापित करें। वह दस्तावेज़ केवल सार्वजनिक कुंजी सामग्री उजागर करता है, और verificationMethod[*].id वही key_id है जो संग्रहीत है (अनुरोध के Host से दोबारा नहीं गढ़ा जाता), इसलिए दूसरे इंस्टेंस से हल करने पर और स्थानीय रूप से हल करने पर मिली id सदा एक ही रहती है।
सार्वजनिक रजिस्ट्री (v2+)
NANDA Index या वैसी ही किसी सार्वजनिक रजिस्ट्री से जुड़ना, जिससे संभव हो:
- क्षमता से खोज («Modbus जानने वाला Agent ढूँढ़ो»)।
- संगठन से खोज («ABC Industries का Agent»)।
- स्थान से खोज («आसपास के सेवा Agent»)।
भरोसे का जाल (v2+)
- मेरे संपर्कों के Agent ऊपर आते हैं।
- मेरे सहकर्मियों की कंपनी के Agent ऊपर आते हैं।
- तीसरे पक्ष के समर्थन (NANDA से सत्यापित) पर भरोसे का बैज होता है।
स्पैम रोकथाम
- हर peer-डोमेन पर प्रति मिनट दर-सीमा (प्रोसेस-लोकल काउंटर; गेटवे एकल इंस्टेंस है)।
- श्वेतसूची में न होने वाले peer डिफ़ॉल्ट रूप से निम्न प्राथमिकता के।
- उपयोगकर्ता किसी peer-डोमेन को अवरुद्ध कर सकता है।
- प्रतिष्ठा अंक (v2+): कितने दूसरे इंस्टेंसों ने उसे स्पैम बताया।
अनुवाद नीति
- हर Agent अपने AgentFacts में
primary_languageऔरstyleघोषित करता है। - भाषाओं के आर-पार बातचीत में अनुवाद लक्ष्य Agent के भीतर होता है (अपनी शब्दावली और दस्तावेज़ वही सबसे अच्छा जानता है)।
- उद्धृत हिस्सा हमेशा मूल रूप में रहता है: उपयोगकर्ता अनुवाद से पहले वाली प्रामाणिक अभिव्यक्ति देख सकता है।
- डिफ़ॉल्ट व्यवहार
preserve-styleहै (शैली वही, केवल भाषा बदलती है); उपभोग के दृश्य मेंlocalize-styleघोषित किया जा सकता है (स्थानीय रीति के अनुरूप)।
प्रोटोकॉल के विकास की नीति
- हर प्रोटोकॉल में
@contextयाversionक्षेत्र होता है। - क्लाइंट और सर्वर दोनों पिछड़ी संगतता रखते हैं (अनजान क्षेत्र स्वीकारते और अनदेखा करते हैं)।
- तोड़ने वाले बदलाव मुख्य संस्करण बढ़ाकर होते हैं (जैसे
/a2a/v2/)। - NANDA और Google के A2A की स्कीमा-विकास से संगत (हम खुली पारिस्थितिकी पर दाँव लगाते हैं)।