# Expired API tokens: long agent jobs के लिए recovery rules

Long agent jobs तब खास तौर पर बेवकूफी भरे तरीके से fail होते हैं, जब token expiry की जिम्मेदारी किसी की न हो। Agent 401 देखता है, वही call दोहराता है, rate-limit budget खर्च करता है और कभी-कभी एक अनिश्चित write को कई writes में बदल देता है। यह authentication glitch नहीं, recovery design की विफलता है।

Expiry को expected state change मानकर योजना बनाएं। Renewal की जिम्मेदारी एक component को दें, action लेने से पहले failures को classify करें, retries को operation के अर्थ से बांधें और ऐसा record छोड़ें जिससे operator देख सके कि remote side ने काम पहले ही स्वीकार किया था या नहीं। Agent को कभी अनुमान नहीं लगाना चाहिए कि credentials mint करने की अनुमति है या write दोबारा भेजना सुरक्षित है।

## Expiration एक state change है, असाधारण outage नहीं

Expired API token बताता है कि short-lived credential के लिए authorization समाप्त हो गया है। यह नहीं बताता कि task fail हो गया। Long-running job expiry वाली अगली request से पहले दस remote operations पूरी कर चुकी हो सकती है। Recovery code को यह फर्क बनाए रखना चाहिए।

Teams अक्सर चार अलग घटनाओं को `auth_failed` नाम की एक branch में मिला देती हैं। इससे गलत व्यवहार होता है, क्योंकि हर घटना का response अलग होना चाहिए:

- Expiry का अर्थ है कि token का lifetime खत्म हुआ और authorized renewal owner नया access token मांग सकता है।
- Revocation का अर्थ है कि user, administrator या provider ने grant वापस ले लिया। Renewal जानबूझकर fail हो सकता है।
- Invalid request authentication का अर्थ malformed header, गलत credential type, issuer mismatch या गलत audience हो सकता है।
- Insufficient permission का अर्थ है कि identity valid है, लेकिन यह operation करने की अनुमति नहीं है।

OAuth इन भेदों को कारण से नाम देता है। RFC 6750 `invalid_token` bearer-token error को specify करता है और कहता है कि token expired, revoked, malformed या अन्यथा invalid होने पर resource server 401 response के साथ `WWW-Authenticate` challenge भेजता है। यह उपयोगी protocol guidance है, लेकिन client को blind refresh की अनुमति नहीं देता। Resource server केवल इतना बताता है कि उसने यह request reject की।

Job की दो timelines होती हैं। **Work timeline** वह दर्ज करती है जो job ने discover, calculate, create और confirm किया। **Authorization timeline** वह credential generation दर्ज करती है जिसने उसे action करने दिया। Token expire होने पर work timeline बचाएं और authorization timeline को `renewing` या `blocked` में ले जाएं। पूरी job reset करके उसे recovery न कहें।

यह फर्क agents के लिए सबसे महत्वपूर्ण है, क्योंकि वे dependent calls की श्रृंखला बनाते हैं। मान लें agent change request बनाता है, artifact upload करता है और artifact attach करने से पहले access खो देता है। पहली instruction से restart करने पर दूसरा change request बन सकता है। हर confirmed remote effect के बाद durable checkpoint agent को पूरी योजना दोहराने के बजाय missing attachment से आगे बढ़ने देता है।

`authenticated` जैसे boolean के बजाय स्पष्ट states इस्तेमाल करें:

```text
ready -> executing -> authorization_expired -> renewal_in_progress
renewal_in_progress -> executing
renewal_in_progress -> authorization_blocked
executing -> outcome_unknown
outcome_unknown -> reconciled -> executing
```

`outcome_unknown` की अपनी state होनी चाहिए। Service write commit करने के बाद, लेकिन caller को response मिलने से पहले connection टूट सकती है। Token renewal इस अनिश्चितता को हल नहीं कर सकता। Write दोबारा भेजने से पहले job को operation identifier, idempotency token या provider-specific lookup से पूछना होगा कि पहले क्या हुआ।

## Renewal की जिम्मेदारी एक component की हो

जिस client के पास refresh credential है, access-token renewal उसी के पास होना चाहिए। Agent को renewable credentials देने के बजाय उससे action का अनुरोध कराएं। यह नियम तब और स्पष्ट हो जाता है जब दो agents एक साथ expiry का सामना करते हैं।

यदि हर worker के पास refresh token की copy हो, तो सभी workers स्वतंत्र रूप से renew कर सकते हैं। वे आपस में race करेंगे, credential events का बिखरा हुआ trail बनाएंगे और refresh-token rotation किसी दूसरे worker के token को invalid कर सकती है। इससे भी बड़ी समस्या यह है कि task पढ़ सकने वाली हर process को long-lived identity holder बना दिया जाता है।

Agents और provider के बीच credential broker या action gateway रखें। Broker refresh credential या service credential store करता है, short-lived access tokens प्राप्त करता है, request चलाते समय ही एक token जोड़ता है और response लौटाता है। Agent को access token या refresh token में से कोई नहीं मिलता।

इस विभाजन से हर actor का काम स्पष्ट रहता है:

- Agent तय करता है कि कौन सा allowed operation चलाना है और operation inputs देता है।
- Gateway देखता है कि session request कर सकता है या नहीं, credential reference चुनता है और call चलाता है।
- Renewal owner provider की classified expiration failure मिलने पर एक बार refresh करता है।
- Operator revoked grant, नए consent requirement या human presence मांगने वाले credential को संभालता है।

सिर्फ इसलिए agent को renewal owner न बनाएं कि वह OAuth endpoint call कर सकता है। Capability और authority अलग बातें हैं। Calendar event मांगने या artifact deploy करने वाला agent किसी व्यक्ति या service identity को extend करने की अनुमति अपने-आप नहीं पाता।

RFC 6749 refresh tokens को client को दिए गए credentials बताता है, जिनका उपयोग नए access tokens पाने के लिए होता है। Architecture में “client” शब्द को शाब्दिक रूप से लें। यदि आपका agent registered client नहीं है, तो API request बनाने के कारण उसे client का refresh token नहीं मिलना चाहिए।

कुछ मामलों में job स्वयं renewal की owner हो सकती है। अपनी registered client, अपनी storage boundary और बिना human delegation वाला सीमित machine workload ऐसा कर सकता है। तब भी उस identity के सभी concurrent operations के लिए एक renewal coordinator होना चाहिए। Credential reference पर mutex या single-flight mechanism लगाएं। पहली failing call renew करे, बाकी calls token endpoint पर stampede करने के बजाय परिणाम की प्रतीक्षा करें।

Ownership record सरल और स्पष्ट रखें:

```json
{
  "credential_ref": "billing-write-prod",
  "renewal_owner": "action-gateway",
  "access_token_lifetime": "provider-defined",
  "refresh_allowed": true,
  "reauthorization_owner": "on-call-operator",
  "concurrent_refresh": "single-flight"
}
```

Record में reference रखें, credential स्वयं नहीं। यह भी लिखें कि refresh काम न करने पर किसे action लेना है। Deployment से पहले कोई इसका उत्तर न दे सके तो रात में job इसका जवाब गलत तरीके से देगी।

## Renewal शुरू करने से पहले 401 के प्रमाण देखें

Renewal तभी करें जब response और credential record expiry diagnosis का समर्थन करें। हर 401 को expiry मानने से configuration defects छिपते हैं और बेकार refresh attempts की लंबी श्रृंखला बन सकती है।

Provider के documented error body, headers और token-format expectations से शुरुआत करें। कुछ APIs OAuth-compatible `WWW-Authenticate` values देती हैं, कुछ JSON error codes। कुछ APIs authentication failures को ऐसे gateway के पीछे रखती हैं जो अलग status code देता है। Classifier को provider के documented signals इस्तेमाल करने चाहिए और फिर सुरक्षित terminal error पर जाना चाहिए।

एक उपयोगी classification contract ऐसा हो सकता है:

```json
{
  "http_status": 401,
  "provider_code": "invalid_token",
  "www_authenticate": "Bearer error=\"invalid_token\"",
  "credential_ref": "reports-read",
  "token_generation": 17,
  "decision": "renew_once"
}
```

Response तभी `renew_once` के योग्य है जब सभी बातें सही हों: request में gateway द्वारा जारी या चुना गया credential लगा हो, उसके लिए renewable path हो, provider signal documented expiration या invalid-token condition से मेल खाता हो और job generation 17 को पहले renew न कर चुकी हो।

हर failure class का अलग परिणाम रखें। Malformed header `configuration_error` है, ताकि developer request builder देख सके। Audience mismatch `credential_binding_error` है, जहां token request या resource configuration ठीक करनी होगी। Revoked grant `reauthorization_required` है, जहां system external actions रोककर सही operator को बताए कि किस identity को consent चाहिए। 403 `permission_denied` है, उसे refresh करना cargo-cult behavior है।

Clock problems false diagnoses का बड़ा कारण हैं। Local expiry निकालने वाला client usable token को जल्दी reject कर सकता है, जबकि गलत clock वाला client expired token भेज सकता है। Token मिलने पर issuer-provided expiry record करें, छोटा safety margin रखें और trusted system clock इस्तेमाल करें। हर agent को decoded token claim से अपनी expiry निकालने न दें। इससे protocol logic दोहरती है और agents के निष्कर्ष अलग हो सकते हैं।

केवल यह तय करने के लिए token payload inspect न करें कि उस पर भरोसा किया जा सकता है या नहीं। JSON Web Token में `exp` claim हो सकता है, लेकिन उसके base64url payload को decode करने से signature, issuer, audience या revocation verify नहीं होते। इसे केवल hint मानें, वह भी तब जब token प्राप्त करने वाले component ने provider का documented validation पूरा कर लिया हो। Opaque access tokens में inspect करने के लिए payload होता ही नहीं, इसलिए response handling पर निर्भर रहना बेहतर है।

## Retry limits remote system और आपके evidence की रक्षा करते हैं

Classified expiry के बाद एक coordinated renewal और एक controlled replay की अनुमति दें। अधिक retries authorization को बेहतर नहीं बनातीं। वे टूटे renewal path को छिपाती हैं और audit trail पढ़ना कठिन करती हैं।

Replay का नियम operation पर निर्भर करता है। Read request आमतौर पर renewal के बाद replay सह लेती है। Write request को अधिक मजबूत evidence चाहिए, क्योंकि expiration response, timeout या connection loss caller तक पहुंचने से पहले remote service उसे process कर चुकी हो सकती है।

Action interface बनाते समय operations को classify करें:

| Operation class | उदाहरण | Renewal के बाद recovery |
| --- | --- | --- |
| Read | Record fetch करना | यदि external side effect नहीं है तो एक बार replay करें |
| Idempotent write | ज्ञात version पर document replace करना | Provider उस method और condition के लिए idempotency guarantee दे तो एक बार replay करें |
| Idempotency token वाला write | Invoice draft बनाना | ठीक वही token और payload एक बार फिर इस्तेमाल करें |
| Non-idempotent write | Message भेजना या payment trigger करना | पहले reconcile करें, फिर तभी action लें जब remote system पहले effect न होने की पुष्टि करे |

HTTP method names इसका फैसला नहीं करते। `PUT` का intent अक्सर idempotent होता है, लेकिन provider उससे email notification या asynchronous downstream action जोड़ सकता है। `POST` सुरक्षित हो सकता है यदि provider idempotency field support करता हो। Endpoint contract पढ़ें और वास्तविक behavior test करें।

Idempotency token वाले operations के लिए पहला network call करने से पहले token बनाएं और canonical request fingerprint के साथ persist करें। हर retry में ठीक वही token और logically identical payload भेजें। 401 के बाद नया token न बनाएं। नया token provider को बताता है कि यह नया operation है और idempotency का उद्देश्य खत्म हो जाता है।

```json
{
  "operation_id": "job-84f3/create-draft",
  "idempotency_token": "a stable random value stored before send",
  "request_fingerprint": "method, path, normalized body hash",
  "attempt": 1,
  "authorization_generation": 17
}
```

“Normalized body hash” महत्वपूर्ण है। Retry builder timestamp, array ordering या generated label बदल दे तो वही idempotency token किसी incompatible request में बदल सकता है। कुछ providers ऐसी mismatch reject करते हैं, कुछ अलग तरह से handle करते हैं। पहला serialized request body बचाकर रखें या उसे एक बार canonicalize करके reuse करें।

Rate-limit और network retry logic को एक ही budget में रखें। Agent तीन network retries, फिर renewal retry और फिर तीन और network retries करे तो operation duplicate या overload होने के सात अवसर बनते हैं। एक operation-level attempt budget तय करें। सुरक्षित read के लिए initial call, एक renewal path और एक replay पर्याप्त हो सकते हैं। Payment जैसे action में initial call के बाद केवल reconciliation की अनुमति हो सकती है।

Suppressed retry को भी log करें। Operator को दिखना चाहिए कि system `renewal_attempted=true` के बाद जानबूझकर रुका, agent crash नहीं हुआ। यह record तब भी साफ signal देता है जब provider error format बदल दे और classifier पहले अनुमत recovery को रोकने लगे।

## Checkpoints job को अपना अतीत गढ़े बिना resume करने देते हैं

Long agent task को completed remote effects और pending intent अलग-अलग persist करने चाहिए, क्योंकि renewed token यह नहीं बताता कि failure से पहले क्या हुआ। आम खराब pattern केवल chat transcript या agent की final plan बचाता है और interruption के बाद agent से state reconstruct करने को कहता है।

ऐसा task journal रखें जिसे program reconcile कर सके। हर intended external call का stable operation ID हो। हर confirmed result में provider का resource ID, उपलब्ध हो तो version या ETag, और request fingerprint हो। हर uncertain result में उसे resolve करने का lookup rule हो।

Compact checkpoint ऐसा दिख सकता है:

```json
{
  "task_id": "release-2025-04-17-42",
  "completed": [
    {"operation_id": "create-change", "remote_id": "CR-819", "version": "6"},
    {"operation_id": "upload-bundle", "remote_id": "asset-552"}
  ],
  "pending": {
    "operation_id": "attach-bundle",
    "request_fingerprint": "POST /changes/CR-819/assets body-sha256:...",
    "reconcile": "list assets for CR-819 and match asset-552"
  },
  "authorization_state": "authorization_expired"
}
```

Job को हर intermediate thought बचाने की जरूरत नहीं है। अगला सुरक्षित remote action तय करने के लिए पर्याप्त facts बचाएं। Secrets, bearer headers और refresh responses journal में न रखें। ये credential owner के पास होने चाहिए, general task storage में नहीं।

Pause के दौरान version conditions महत्वपूर्ण हैं। यदि task ने expiry से पहले document version 6 पढ़ा और एक घंटे बाद resume किया, तो कोई दूसरा actor उसे बदल चुका हो सकता है। उपलब्ध होने पर ETags, revision numbers, conditional headers या provider-specific concurrency fields इस्तेमाल करें। Condition fail हो तो agent को बताएं कि पुरानी plan अब लागू नहीं है। Token renew करके newer state को overwrite न करें।

यहीं autonomous work के judgment की सीमा तय करनी होती है। जिस upload का destination और hash पहले record हो चुका है, job उसे सुरक्षित रूप से resume कर सकती है। लेकिन deployment फिर से plan करना, approval record बदलना या पुराना context समाप्त होने के बाद अलग target चुनना उचित नहीं है। ऐसे operations को recovery के बाद fresh confirmation की जरूरत वाली category में रखें।

## Recovery response agent को उसकी अनुमति की सीमा बताए

Action gateway को अस्पष्ट authentication sentence के बजाय structured recovery result लौटाना चाहिए, वरना agent improvisation कर सकता है। Result में बताया जाए कि call चली या नहीं, gateway ने authorization renew किया या नहीं, replay की अनुमति है या नहीं और human intervention चाहिए या नहीं।

Execution status और credential status को अलग रखें:

```json
{
  "operation_id": "attach-bundle",
  "execution_state": "not_sent",
  "authorization_state": "reauthorization_required",
  "retry_allowed": false,
  "credential_ref": "release-api",
  "operator_action": "Reauthorize the release-api connection, then resume task release-2025-04-17-42",
  "safe_resume_from": "attach-bundle"
}
```

`not_sent` का अर्थ है कि gateway ने request network client को देने से पहले रोक दी। `outcome_unknown` का अर्थ है कि gateway यह दावा नहीं कर सकता। दोनों को `failed` में न मिलाएं। पहला reauthorization की प्रतीक्षा कर सकता है। दूसरे को किसी retry से पहले remote service के विरुद्ध reconcile करना होगा।

Agents को recovery के लिए सीमित vocabulary दें: `completed`, `renewed_and_replayed`, `needs_reconciliation`, `reauthorization_required`, `permission_denied` और `configuration_error`। हर outcome एक permitted behavior से जुड़ा हो। उदाहरण के लिए `renewed_and_replayed` के बाद agent आगे बढ़ सकता है, `needs_reconciliation` पर documented read-only reconciliation चला सकता है और `reauthorization_required` पर external writes रोकना होगा।

Raw provider response को एकमात्र signal न बनाएं। Diagnostics के लिए raw details उपयोगी हैं, लेकिन providers की असंगत भाषा agent गलत समझ सकता है। Raw response को protected diagnostic record में रखें और caller को stable machine-readable decision दें।

अच्छा error message identity reference और blocked operation बताता है, secret नहीं। “Credential `release-api` को `attach-bundle` चलाने से पहले reauthorization चाहिए” operator को कार्रवाई की जगह बताता है। “Unauthorized” किसी की मदद नहीं करता।

## Refresh credentials को access tokens से ज्यादा सुरक्षा चाहिए

Refresh credential अधिक सुरक्षा का हकदार है, क्योंकि वह आमतौर पर जिस access token को replace करता है उससे अधिक समय तक चल सकता है। Expiry हल करने के लिए इसे हर agent workspace, build directory, environment variable या transcript में न बांटें।

OAuth 2.0 Security Best Current Practice, RFC 9700, public clients के लिए refresh-token rotation या sender-constrained refresh tokens की सिफारिश करता है। Provider support अलग-अलग हो सकता है, लेकिन सुरक्षा lesson वही है: चोरी हुआ renewable credential सामान्य short-lived bearer token से कहीं लंबे समय तक misuse हो सकता है।

Renewal material को action gateway के encrypted credential store में रखें। तय करें कि कौन से action definitions उसे चुन सकते हैं, उपयुक्त actions के लिए explicit human approval मांगें और reauthorization को अलग operator action बनाएं। Locked credential store को copied secrets वाली configuration files पर fallback देने के बजाय काम रोक देना चाहिए।

Sallyport supported HTTP और SSH actions के लिए यही model अपनाता है: उसका encrypted vault credential रखता है, agent अपने MCP shim के माध्यम से action मांगता है और केवल result प्राप्त करता है। यह विभाजन उपयोगी है, क्योंकि recovery गलत होने पर agent refresh credential को अपने context में print नहीं कर सकता।

Refresh responses के साथ सावधानी रखें। कुछ providers refresh credential rotate करते हैं और use होते ही पुरानी value invalid कर देते हैं। Renewal owner waiting calls छोड़ने से पहले stored value को atomically replace करे। नया access token लिखकर replacement refresh credential खो देने पर job थोड़ी देर काम करेगी और अगली expiry पर स्थायी रूप से fail हो सकती है।

इन fields को कभी log न करें: `Authorization`, access token, refresh token, client secret, signed assertion या पूरा token endpoint body। Masking पर्याप्त नहीं है, क्योंकि masker चलने से पहले systems raw request objects copy कर सकते हैं। Logger को secret-bearing structures के बजाय credential references और token generation numbers लेने के लिए design करें।

## Human approval authority resume करे, retry storm न बनाए

Human approval तभी उपयोगी है जब वह स्पष्ट निर्णय से जुड़ी हो: इस agent run को अनुमति देना, इस sensitive call को अनुमति देना या revoked authorization बहाल करना। Expiry के बाद generic “retry” button operator को अस्पष्ट action पर rubber stamp बना देता है।

Approvals अलग रखें। Reauthorization credential owner को नया grant या usable renewal path देती है। Session authorization तय करती है कि यह particular agent process actions मांग सकता है या नहीं। Per-call approval तय करती है कि sensitive operation अभी हो सकती है या नहीं। ये अलग निर्णय हैं। इन्हें मिला देने पर noisy prompts या excessive standing permission मिलती है।

Credential को reauthorization चाहिए तो affected credential reference, identity या connection label, blocked operation और task checkpoint दिखाएं। Token स्वयं न दिखाएं। Operator authorization बहाल करे तो gateway केवल checkpoint में दर्ज pending operation resume करे। Agent transcript से हर failed call को चुपचाप replay न करे।

Sallyport की fixed decision ladder इस boundary के लिए उपयोगी है: locked vault हर action deny करता है, नए agent processes को default रूप से session approval चाहिए और चुने हुए credentials के लिए हर use पर approval लगाया जा सकता है। Controls rules के ढेर से intent अनुमान लगाने की कोशिश नहीं करते, इसलिए expired credential किसी वैध run को बीच में रोक दे तो भी behavior स्पष्ट रहता है।

Approval fatigue आमतौर पर design error है। दस parallel calls एक ही expiry देखें और operator को दर्जनों prompts मिलें तो renewal coordinator ने event को collapse नहीं किया। एक reauthorization request दिखाएं, बाकी calls को प्रतीक्षा कराएं और वही decision हर task को report करें।

Unknown write outcome को approval से ढकने की कोशिश न करें। सही prompt पूछे कि operator remote state reconcile करवाना चाहता है या नहीं, retry करवाना नहीं। इंसान भी agent की तरह गलती से duplicate कर सकता है।

## Expiry tests में uncertain writes और concurrent workers शामिल हों

एक read से पहले synthetic 401 लौटाने वाला token-renewal test बहुत कम साबित करता है। नुकसानदायक failures boundaries पर आते हैं: parallel work के दौरान, write commit होने के बाद, refresh rotation के समय या grant revoke होने पर।

ऐसा test provider या controllable HTTP fixture बनाएं जो received operation IDs record करे और तय points पर failure inject कर सके। Assertions में remote-effect record और local audit record दोनों देखें। केवल successful final response duplicate create छिपा सकता है।

Long-running agent पर भरोसा करने से पहले ये cases चलाएं:

1. Read से पहले access token expire करें। Confirm करें कि केवल एक worker renew करता है और सभी waiting reads replacement generation इस्तेमाल करते हैं।
2. Idempotent write से पहले token expire करें। Confirm करें कि gateway एक बार renew करके original operation ID और request body के साथ replay करता है।
3. Provider द्वारा non-idempotent write record करने के बाद response गिरा दें। Confirm करें कि job `outcome_unknown` में जाती है, lookup चलाती है और दूसरा effect नहीं बनाती।
4. Renewal से पहले grant revoke करें। Confirm करें कि हर dependent action `reauthorization_required` के साथ रुकती है और कोई loop token endpoint को बार-बार call नहीं करता।
5. Rotated refresh credential लौटाकर storage interrupt करें। Confirm करें कि gateway incomplete replacement पहचानता है और stale copy इस्तेमाल करने के बजाय future renewal रोक देता है।

Time को भी स्पष्ट रूप से test करें। Credential component में clock inject करें, ताकि expiry को request construction से ठीक पहले, header construction के ठीक बाद और queue में request के इंतज़ार के दौरान रख सकें। Real token expire होने तक sleep करने से tests धीमे होते हैं और महत्वपूर्ण timing cases छूट जाते हैं।

अंत में vault access के बिना audit path test करें। आपको यह verify कर पाना चाहिए कि attempted calls और renewal decisions का sequence नहीं बदला, भले ही हर record decrypt न कर सकें। Sallyport का `sp audit verify` vault key के बिना encrypted audit data की hash chain check करता है। Credential access locked रहने वाले incident में verification का यही सही रूप है।

Expiry को अच्छी तरह संभालने वाली job authorization fail होने के बाद कम काम करती है। वह रुकती है, failure classify करती है, एक owner को renew करने देती है, uncertainty reconcile करती है और केवल recorded operation resume करती है। यह restraint duplicate side effects रोकता है और operator को retries के ढेर के बजाय evidence देता है।
