# एजेंट टूल फेल्योर टेस्टिंग: असुरक्षित रीट्राई को जल्दी पकड़ें

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

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

## फेल्योर रिस्पॉन्स एजेंट के प्लानर के लिए इनपुट है

एजेंट टूल के परिणाम को प्रमाण मानता है। अगर परिणाम बताता है कि कोई बदलाव नहीं हुआ, तो एजेंट दोबारा कोशिश कर सकता है। अगर वह बताता है कि क्रेडेंशियल एक्सपायर हो गया, तो एजेंट अधिकृत रिकवरी रास्ता खोज सकता है। अगर रिमोट परिणाम अज्ञात होने पर भी सफलता का संकेत मिले, तो एजेंट बाद की कई कार्रवाइयां गलत धारणा पर आधारित कर सकता है।

विफलताओं को इस आधार पर अलग करें कि कॉलर क्या जान सकता है। यह अंतर अक्सर धुंधला हो जाता है:

- पक्के इनकार का मतलब है कि रिमोट सर्विस को अनुरोध मिला और उसने उसे अस्वीकार कर दिया।
- पक्की विफलता का मतलब है कि रिमोट सर्विस ने ऐसा परिणाम लौटाया जिसमें बताया गया कि मांगा गया काम नहीं हुआ।
- अनिश्चित परिणाम का मतलब है कि कॉलर यह तय नहीं कर सकता कि रिमोट सिस्टम ने काम किया या नहीं।
- स्थानीय विफलता का मतलब है कि टूल ने सार्थक रिमोट कोशिश करने से पहले ही काम करना बंद कर दिया।

TCP सेशन शुरू होने से पहले कनेक्शन रिजेक्ट होना आम तौर पर स्थानीय विफलता है। HTTP 403 पक्का इनकार है। `POST` भेजने के बाद रीड टाइमआउट अनिश्चित परिणाम है, जब तक रिमोट सर्विस आपको ऑपरेशन की स्थिति देखने का तरीका न दे। ये लेबल आपके टेस्ट केस और टूल के परिणाम स्कीमा में दिखने चाहिए। उन्हें ऐसे वाक्य में न छिपाएं जिसका अर्थ एजेंट को खुद निकालना पड़े।

एक छोटा परिणाम रूप कॉन्ट्रैक्ट को टेस्ट करने योग्य बनाता है:

```json
{
  "ok": false,
  "category": "outcome_unknown",
  "operation": "create_deployment",
  "retry": "reconcile_first",
  "correlation_id": "case-ssh-017",
  "message": "Connection closed after the remote command started; remote completion is unknown."
}
```

नाम बहुत महत्वपूर्ण नहीं हैं। अलगाव महत्वपूर्ण है। अनुमति से इनकार पर `retry: "never"` और टाइमआउट हुए राइट पर `retry: "reconcile_first"` एजेंट को अलग-अलग निर्देश देते हैं, बिना पूरे एरर मैसेज को प्रॉम्प्ट में बदले।

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

## ऑपरेशन और प्रमाण के आधार पर मैट्रिक्स बनाएं

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

इस मैट्रिक्स को शुरुआती दस्तावेज़ की तरह इस्तेमाल करें। ऑपरेशन के नाम और अपेक्षित रिकॉर्ड अपने अनुसार बदलें, लेकिन «रिमोट असर ज्ञात है?» कॉलम न हटाएं। यही कॉलम कठिन मामलों को सामने लाता है।

| केस | ऑपरेशन | डाली गई स्थिति | रिमोट असर ज्ञात है? | अपेक्षित श्रेणी | एजेंट के लिए निर्देश |
| --- | --- | --- | --- | --- | --- |
| C01 | इश्यू पढ़ना | DNS lookup विफल | हां, अनुरोध भेजा ही नहीं गया | local_failure | सीमित बजट के भीतर रीट्राई करें |
| C02 | इश्यू बनाना | टोकन एक्सपायर | हां, अस्वीकार हुआ | authentication_failed | रुकें और अधिकृत क्रेडेंशियल रिकवरी मांगें |
| C03 | रिलीज़ हटाना | अनुमति नहीं मिली | हां, अस्वीकार हुआ | authorization_denied | रीट्राई न करें |
| C04 | बिल्ड पढ़ना | JSON में `status: 7` है | हां, रिस्पॉन्स मिला | malformed_response | रुकें और स्कीमा mismatch बताएं |
| C05 | डिप्लॉयमेंट बनाना | क्लाइंट डेडलाइन से आगे रिस्पॉन्स में देरी | नहीं | outcome_unknown | रीट्राई से पहले मिलान करें |
| C06 | SSH रीस्टार्ट चलाना | रिमोट शुरू होने के बाद स्थानीय हेल्पर बंद | नहीं | outcome_unknown | दूसरी कमांड से पहले रिमोट स्थिति जांचें |
| C07 | रिकॉर्ड अपडेट करना | सर्विस 429 लौटाती है | हां, अस्वीकार हुआ | rate_limited | निर्देश के अनुसार प्रतीक्षा करें, फिर सुरक्षित हो तो रीट्राई करें |

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

हर पंक्ति में चार दावे होने चाहिए:

1. टूल की परिणाम श्रेणी और रीट्राई निर्देश की जांच करें।
2. एजेंट की अगली कार्रवाई की जांच करें, जिसमें यह भी शामिल हो कि वह अपने-आप कोई विनाशकारी रीट्राई न करे।
3. रिमोट स्थिति या उसके अज्ञात रहने का दर्ज कारण जांचें।
4. इवेंट ट्रेल में कॉरिलेशन ID और देखा गया परिणाम मौजूद होने की जांच करें।

यह केवल `ok == false` जांचने से ज्यादा काम है। लेकिन इससे वे विफलताएं भी पकड़ में आती हैं जो एजेंट के कई कदम उठा लेने के बाद महत्वपूर्ण बनती हैं।

## एक्सपायर्ड क्रेडेंशियल और अस्वीकार की गई कार्रवाइयों की रिकवरी अलग होती है

एक्सपायर्ड या रद्द किया गया क्रेडेंशियल साबित करता है कि ऑथेंटिकेशन विफल हुआ। अस्वीकार की गई कार्रवाई साबित करती है कि कॉलर ने ऑथेंटिकेट किया, लेकिन उसके पास उस ऑपरेशन की अनुमति नहीं है, जब तक प्रोवाइडर जानबूझकर यह अंतर छिपाता न हो। दोनों को «एक्सेस विफल» मानने से एजेंट गलत व्यवहार करेगा।

RFC 9110 में 401 का मतलब अनऑथेंटिकेटेड अनुरोध है और सर्वर को `WWW-Authenticate` चैलेंज भेजना जरूरी है। इसमें 403 का मतलब अनुरोध पूरा करने से इनकार है, भले ही सर्वर कारण न बताए। प्रोवाइडर हमेशा इस अंतर का साफ पालन नहीं करते, इसलिए उनके वास्तविक रिस्पॉन्स को टेस्ट करें। फिर भी, जहां ईमानदारी से संभव हो, आपका टूल देखे गए प्रमाण को अलग-अलग श्रेणियों में मैप करे।

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

एक सरल fixture दोनों मामलों को सीक्रेट उजागर किए बिना दिखा सकता है:

```yaml
cases:
  - id: expired-token
    request:
      method: POST
      path: /v1/releases
    fixture_response:
      status: 401
      headers:
        www-authenticate: Bearer error="invalid_token"
      body: {"error":"token_expired"}
    expect:
      category: authentication_failed
      retry: never
      secret_in_result: false

  - id: denied-release
    request:
      method: POST
      path: /v1/releases
    fixture_response:
      status: 403
      body: {"error":"insufficient_scope"}
    expect:
      category: authorization_denied
      retry: never
      secret_in_result: false
```

`secret_in_result` assertion उस गलती को पकड़ती है जो घबराहट में डीबग करते समय सामने आती है: कोड बाहर भेजे गए authorization header या configuration object को exception में जोड़ देता है। सीरियलाइज़ किए गए टूल आउटपुट, trace आउटपुट और एजेंट तक पहुंचने वाले transcript को टेस्ट करें। एक logger में redaction दूसरे logger की सुरक्षा नहीं करता।

जब तक सिस्टम एजेंट को इस्तेमाल करने के लिए स्पष्ट रूप से अलग और अधिकृत पहचान न देता हो, उसे «दूसरा क्रेडेंशियल आज़माने» को न कहें। बिना सोचे क्रेडेंशियल चुनना उपलब्धता की समस्या हल करने जैसा दिखते हुए privilege boundary पार कर सकता है। टेस्ट को साबित करना चाहिए कि auth failure रन रोकता है या उसे स्वीकृत मानवीय रिकवरी पथ पर भेजता है।

## गलत डेटा के लिए कॉन्ट्रैक्ट टेस्ट चाहिए, केवल JSON पार्सर टेस्ट नहीं

गलत डेटा में ऐसा मान्य JSON भी शामिल है जिसे आपका कोड सुरक्षित रूप से इस्तेमाल नहीं कर सकता। अमान्य सिंटैक्स आसान मामला है। प्रोडक्शन विफलताएं अक्सर तब आती हैं जब किसी फ़ील्ड का प्रकार बदल जाता है, ज़रूरी पहचान गायब हो जाती है, success envelope की जगह error envelope आ जाता है या proxy कनेक्शन बंद करने के बाद रिस्पॉन्स कट जाता है।

JSON-RPC 2.0 स्पेसिफिकेशन parse errors (`-32700`) और invalid requests (`-32600`) को अलग रखता है। यह विभाजन उपयोगी है क्योंकि इससे न पढ़े जा सकने वाले बाइट्स और प्रोटोकॉल तोड़ने वाले पढ़ने योग्य संदेश का अंतर पता चलता है। अपने डोमेन रिस्पॉन्स में भी यही अनुशासन रखें: parser का सफल होना यह साबित नहीं करता कि रिस्पॉन्स टूल कॉन्ट्रैक्ट पूरा करता है।

हर प्रोवाइडर रिस्पॉन्स के लिए ऐसी fixtures लिखें जो एक बार में एक धारणा तोड़ें:

- string ID को `null`, number और object से बदलें।
- ऐसा फ़ील्ड हटाएं जिसकी बाद के टूल कॉल को reconciliation के लिए जरूरत है।
- success status के साथ error-shaped body लौटाएं।
- error status के साथ HTML body या कटा हुआ JSON document लौटाएं।
- किसी item को दोहराएं या क्रम बदलें, जब आपका कोड पहला item चुनता हो।

फिर सटीक व्यवहार की जांच करें। टूल को सुरक्षित diagnostics में विफल फ़ील्ड या contract condition का नाम देना चाहिए, एजेंट को `malformed_response` लौटाना चाहिए और अनुमानित मानों के आधार पर आगे कोई mutation नहीं करना चाहिए।

एक आम खराब पैटर्न देखने में harmless लगता है: `response.id || request.id`. जब प्रोवाइडर `id` छोड़ देता है तो यह workflow को आगे बढ़ाता है, लेकिन अगर request identity और response identity अलग हों तो इससे किसी असंबंधित object पर update या delete हो सकता है। टेस्ट करें कि response identity गायब होने पर ऑपरेशन रुक जाए। गलत write से असफल workflow सस्ता है।

MCP tool clients को भी यही सावधानी चाहिए। Model Context Protocol tool result format टूल-स्तर की विफलता के लिए `isError` signal देता है। जब टूल अपना वादा किया हुआ काम पूरा न कर सके, तब इसका इस्तेमाल करें, लेकिन result content इतना स्पष्ट रखें कि एजेंट सुरक्षित शाखा चुन सके। गलत upstream response को «Error:» से शुरू होने वाले सामान्य text result के रूप में न छिपाएं। कई clients इसे सफल tool execution मानेंगे और बाकी अर्थ एजेंट पर छोड़ देंगे।

## राइट शुरू होने के बाद टाइमआउट अस्पष्ट होता है

टाइमआउट बताता है कि आपकी डेडलाइन खत्म हो गई। यह रिमोट ऑपरेशन की स्थिति नहीं बताता। बात स्पष्ट लगती है, लेकिन retry loop अक्सर खोए हुए रिस्पॉन्स को duplicate invoice, दो deployments या दूसरे restart में बदल देता है।

उस सीमा पर टाइमआउट व्यवहार टेस्ट करें जहां निश्चितता बदलती है। आपका fault injector या fake service हर चरण दर्ज करे:

```text
case=C05 request_id=case-http-005 received=true
case=C05 request_id=case-http-005 mutation_committed=true
case=C05 response_write=delayed
client case=C05 deadline_exceeded=true
```

अपेक्षित assertion «क्लाइंट को टाइमआउट मिला» नहीं है। सही assertion यह है कि क्लाइंट `outcome_unknown` लौटाए, दूसरा `POST` न भेजे और आगे बढ़ने से पहले status lookup या idempotency mechanism इस्तेमाल करे।

Idempotency token तभी मदद करते हैं जब रिमोट API संबंधित ऑपरेशन के लिए उनका दस्तावेज़ीकरण करे और उन्हें माने। इन्हें पूरे sequence के रूप में टेस्ट करें: unique token के साथ अनुरोध भेजें, पहली response तब तक रोकें जब तक caller हार न मान ले, recovery path से वही token भेजें और फिर जांचें कि service एक logical operation ही बताती है। केवल इसलिए idempotency का दावा न करें कि आपने ऐसा header जोड़ा है जिसे provider अनदेखा करता है।

जिन ऑपरेशन के लिए reconciliation endpoint या idempotency support नहीं है, उसे tool result में साफ बताएं। सुरक्षित कार्रवाई रुककर किसी व्यक्ति से रिमोट सिस्टम जांचने को कहना हो सकती है। यह engineering failure नहीं है। Workflow आगे बढ़ाना चाहता है इसलिए निश्चितता का दिखावा करना failure है।

जहां आपका client support करता हो, phase के अनुसार timeout तय करें: connection, request write, first response byte और total operation duration। एक बड़ी deadline यह छिपा देती है कि peer ने कनेक्शन कभी स्वीकार नहीं किया या write स्वीकार करने के बाद रुक गया। आपके टेस्ट को हर phase एजेंट के सामने दिखाने की जरूरत नहीं, लेकिन diagnostics में ऑपरेटर के लिए घटना दोहराने लायक जानकारी होनी चाहिए।

## बाधित रिमोट कमांड में अनिश्चितता बनाए रखें

SSH कमांड में failure window होती है जिसे HTTP डेवलपर अक्सर कम आंकते हैं। client कमांड भेज सकता है, remote shell उसे शुरू कर सकती है और फिर caller को exit status मिलने से पहले connection बंद हो सकता है। स्थानीय process crash या network route खो जाना उस काम को वापस नहीं करता जो remote host पहले ही शुरू कर चुका है।

OpenSSH दस्तावेज़ बताता है कि client remote command का exit status तभी लौटाता है जब वह उसे प्राप्त कर सके। Transport पहले टूट जाए तो caller के पास वह status नहीं होता। इस स्थिति को जानबूझकर टेस्ट करें, न कि nonzero local process exit को remote command failure का प्रमाण मानें।

ऐसी remote test command बनाएं जो start marker दर्ज करे, प्रतीक्षा करे, completion marker दर्ज करे और पहचानने योग्य result लिखे। फिर प्रतीक्षा के दौरान local transport बंद करें। इसे केवल अपने नियंत्रण वाले अलग-थलग host या container तक सीमित रखें।

```sh
# remote command used only in an isolated test environment
id="case-ssh-017"
printf '%s start\n' "$id" >> /tmp/agent-tool-test.log
sleep 20
printf '%s complete\n' "$id" >> /tmp/agent-tool-test.log
```

कमांड को उसी SSH path से चलाएं जिसे tool इस्तेमाल करता है, start marker दिखने तक प्रतीक्षा करें और फिर local helper बंद करें। Remote wait पूरा होने के बाद log जांचें। टेस्ट दो बार चलाएं: एक बार remote process को पूरा होने दें और दूसरी बार start marker के बाद remote side से उसे बंद कर दें। दोनों में local interruption होगा, लेकिन recovery अलग होनी चाहिए।

जो कमांड स्थिति बदलती हैं, उनके लिए retry बनाने से पहले reconciliation command बनाएं। Service restart में process uptime या deployment revision पूछा जा सकता है। Package installation में installed version पूछा जा सकता है। जिस command का reconciliation संभव नहीं, interruption के बाद उसमें स्पष्ट human handling जरूरी होनी चाहिए।

ऐसे shell snippets से बचें जो `&&` chains और अस्पष्ट output के पीछे partial completion छिपाते हैं। Mutating part शुरू होने से पहले durable operation ID लिखें और बाद की जांच में उसी ID का इस्तेमाल करें। अगर remote environment कोई marker सुरक्षित नहीं रख सकता, तो tool के पास agent को यह बताने का आधार नहीं है कि retry सुरक्षित है।

## मानव इनकार सामान्य परिणाम है, टूटा हुआ टेस्ट नहीं

किसी कार्रवाई को अस्वीकार करने वाले इंसान के लिए अलग परिणाम होना चाहिए, जो उस action branch को साफ तरीके से समाप्त करे। टीमें अक्सर यह टेस्ट करती हैं कि approval screen दिखती है और decline path को भूल जाती हैं। इससे agent बार-बार retry करता है, वही अनुरोध नए शब्दों में दोहराता है या approval failure को network issue बताता है।

आपके सामने आने वाली हर authorization boundary पर denial टेस्ट करें। जांचें कि denial के बाद tool remote service से connect न करे। जांचें कि वह बाद के process या ऐसी अगली कार्रवाई के लिए approval बचाकर न रखे जिसे नए फैसले की जरूरत है। यह भी जांचें कि agent को ऐसी भाषा मिले जिसे वह refusal को workaround खोजने का निमंत्रण समझे बिना इस्तेमाल कर सके।

Sallyport का vault gate lock रहने तक हर action को अस्वीकार करता है, और उसके session तथा per-call approvals agent process में credentials रखे बिना इन फैसलों को testable बनाते हैं। यह विभाजन उपयोगी है क्योंकि locked vault, declined session और denied per-call use अलग कारणों से operation रोक सकते हैं।

Approval fatigue खुद एक test failure है। अगर harmless read सामान्य run के दौरान बार-बार prompt दिखाता है, तो लोग पढ़े बिना approve करेंगे। अगर destructive call गलती से broad approval पा लेता है, तो लोगों को वह decision point कभी नहीं मिलेगा जिसकी उन्हें उम्मीद थी। Prompt की मौजूदगी के साथ उनकी संख्या, timing और scope भी टेस्ट करें।

ऐसे test agent का इस्तेमाल करें जो एक approved action, एक denied action और process समाप्त होने के बाद एक action आज़माए। अंतिम call उस approval state को पकड़ती है जो intended session के बाद भी बची हुई है। इसे केवल in-memory Boolean बदलकर simulate न करें। नया process शुरू करें, ताकि test वही lifecycle साझा करे जिसे आपके user वास्तव में चलाते हैं।

## लॉग में एक्सेस उजागर किए बिना घटना स्पष्ट होनी चाहिए

एक उपयोगी failure record से causality दोबारा बनाई जा सकती है: किस agent run ने कौन-सा operation आज़माया, कौन-सा request identifier इस्तेमाल किया, remote system ने क्या देखा, tool ने क्या लौटाया और agent ने अगला कदम क्या उठाया। इसमें call को authorize करने वाला credential नहीं होना चाहिए।

हर उस point पर event log करें जहां उत्तर बदल सकता है। Timed write के लिए request construction, dispatch start, remote acceptance, deadline expiry, reconciliation attempt और final classification दर्ज करें। पहला network action शुरू होने से पहले correlation ID बनाएं। इसे secret से न निकालें और operations के बीच reuse न करें।

Local test harness के लिए यह record format काम कर सकता है:

```json
{"time":"2025-04-12T10:18:03Z","case":"C05","id":"case-http-005","event":"dispatch_started"}
{"time":"2025-04-12T10:18:03Z","case":"C05","id":"case-http-005","event":"remote_committed"}
{"time":"2025-04-12T10:18:08Z","case":"C05","id":"case-http-005","event":"client_timeout"}
{"time":"2025-04-12T10:18:08Z","case":"C05","id":"case-http-005","event":"result","category":"outcome_unknown"}
```

अब आपका assertion `id` के आधार पर client और fixture records की तुलना कर सकता है। अगर fixture `remote_committed` कहती है और tool `confirmed_failure`, तो test विफल होना चाहिए। यह असहमति unsafe claim को उजागर करती है, भले ही सभी code paths ने साफ error object लौटाया हो।

Tamper-evident trail के लिए verification भी टेस्ट करें। Sallyport अपने session और activity records को encrypted hash-chained audit log से project करता है, और `sp audit verify` vault key के बिना offline chain जांचता है। Failure test में known event sequence जोड़ें, उसे verify करें, copied record बदलें और जांचें कि बदली हुई copy पर verification विफल हो।

सामान्य logs में पूरी request bodies को डिफॉल्ट रूप से न रखें। Request data में personal data, source code या लापरवाह caller द्वारा जोड़े गए tokens हो सकते हैं। Operation name, target classification, correlation ID, result category और protected diagnostic reference log करें। Collection तभी बढ़ाएं जब controlled test environment में आपको पता हो कि fixtures में क्या है।

## केवल adapter नहीं, agent का recovery behavior भी टेस्ट करें

Unit tests साबित करते हैं कि adapter 403 को `authorization_denied` में बदलता है। वे यह साबित नहीं करते कि agent इसे पाने के बाद रुकता है। Deterministic agent instruction और injected event log दिखाने वाली fake remote service के साथ छोटा end-to-end suite चलाएं।

हर run को छोटा task और स्पष्ट सीमा दें। उदाहरण के लिए: एक record बनाएं, उसे वापस पढ़ें और फिर note जोड़ें। Record commit होने के बाद create response में देरी करें। सही agent behavior यह है कि वह दूसरा create करने से पहले correlation ID या idempotency token से जांच करे। अगर वह दूसरा record बनाता है, तो test fail होना चाहिए, भले ही अंत में task पूरा हो जाए।

इस suite के लिए agent prompt स्थिर रखें। Prompt, tool contract, fixture behavior और model version को एक साथ बदलने पर failure से बहुत कम पता चलता है। Tool transcript और agent की अगली call दर्ज करें, फिर उसे अनुमत transitions से मिलाएं:

```text
create -> outcome_unknown -> lookup_by_request_id -> found -> attach_note
create -> outcome_unknown -> create
```

पहला transition तभी अनुमत है जब lookup original create की पुष्टि करे। दूसरा failure है। यह state-machine check इस बात को आंकने से ज्यादा उपयोगी है कि अंतिम prose response समझदार लगा या नहीं।

Tool code, result schemas, authorization handling या retry logic में हर बदलाव पर deterministic matrix cases चलाएं। Interruption cases को isolated infrastructure में बार-बार चलाएं, क्योंकि scheduling का असर पड़ता है। जब नई incident सामने आए, उसे ठीक करने से पहले उसका छोटा reproduction matrix में जोड़ें। वरना वही आकर्षक लेकिन गलत recovery path अगले refactor में फिर लौट आएगा।

किसी tool का मानक यह नहीं है कि वह हर fault के बाद चलता रहे। मानक यह है कि वह जो जानता है उसके बारे में सच बताए, प्रमाण सुरक्षित रखे और अनिश्चितता को दूसरी विनाशकारी कार्रवाई में बदलने से इनकार करे।
