# Asynchronous API jobs: traceable agent workflows

लंबे समय तक चलने वाली API request भेजने वाले agent को workflow चाहिए, ऐसा loop नहीं जो endpoint को तब तक call करता रहे जब तक कुछ पूरा हुआ न दिखे। Creation, observation, cancellation और result retrieval, इन सभी की failure modes अलग होती हैं। इन्हें एक prompt और कुछ retries में समेट देंगे, तो कभी न कभी काम दो बार submit होगा, पूरा हुआ result खो जाएगा या ऐसी cancellation का दावा होगा जो लागू ही नहीं हुई।

मुश्किल हिस्सा HTTP request भेजना नहीं है। असली चुनौती network timeout, agent restart, provider outage और उस इंसान के बीच intent को सुरक्षित रखना है जो remote service के काम शुरू कर देने के बाद कहता है, «रोक दो»। एक स्थायी local job record के इर्द-गिर्द workflow बनाएं और हर external call का बाद में जवाब दिया जा सके: हमने क्या मांगा था, कौन-सा remote job उसका मालिक है, हमने कौन-सी state देखी और उसके बाद क्या किया?

## Create request को ownership स्थापित करनी चाहिए

Create endpoint asynchronous operation शुरू करता है और operation के terminal state तक पहुंचने से पहले response लौटा देता है। पहली response में agent को इतना data मिलना चाहिए कि वह request दोहराए बिना आगे बढ़ सके। अच्छी तरह डिजाइन की गई API में इसका अर्थ है job ID, initial state और status URL या job retrieve करने के endpoint का convention।

यह न मानें कि request स्वीकार हो गई क्योंकि socket connect हो गया या bytes भेजने के बाद client timeout हो गया। उपयोगी प्रमाण केवल server response या बाद की ऐसी lookup है जो मूल logical request को किसी job से जोड़ती हो। Client को सरल उत्तर की सबसे ज्यादा जरूरत ठीक उसी समय होती है जब network delivery अनिश्चित होती है।

Agent request भेजने से पहले हर logical operation को local operation ID दें। यह आपका identifier है, provider का नहीं। इसे request body या उसके normalized fingerprint, intended target, idempotency token, timestamps और काम की अनुमति देने वाले caller के साथ store करें। Request भेजने से पहले इस record को durable तरीके से लिखें।

एक न्यूनतम record ऐसा दिख सकता है:

```json
{
  "operation_id": "op_01J7Q5X4D4PA3D",
  "request_fingerprint": "sha256:4f8b...",
  "idempotency_token": "idem_5b5c76c7",
  "remote_job_id": null,
  "state": "create_pending",
  "created_at": "2025-03-08T14:22:11Z",
  "create_deadline": "2025-03-08T14:24:11Z",
  "result_deadline": "2025-03-08T15:22:11Z"
}
```

Fingerprint एक सूक्ष्म लेकिन आम गलती पकड़ता है: agent parameter बदलने के बाद create call retry करता है। यह नया operation है, भले ही कोई इंसान उसे «वही task» कहे। एक request shape से जुड़ा token चुपचाप किसी दूसरी request को authorize नहीं कर सकता।

Creation response ऐसा हो सकता है:

```http
HTTP/1.1 202 Accepted
Location: /v1/jobs/job_7ad2
Content-Type: application/json

{
  "job_id": "job_7ad2",
  "state": "queued",
  "status_url": "/v1/jobs/job_7ad2"
}
```

Response मिलते ही durable record में `remote_job_id`, observed state और response metadata update करें। इसके बाद ही agent observation में जा सकता है। API synchronous success लौटाए तो उसी operation के तहत result दर्ज करें। Workflow को दोनों रास्तों का समर्थन करना चाहिए, लेकिन उन्हें एक ही अर्थ वाला नहीं मानना चाहिए।

## Idempotency अनिश्चित delivery के लिए है, हर retry के लिए नहीं

Idempotency token server को बताता है कि एक ही logical create request की repeated delivery से काम दो बार नहीं बनना चाहिए। यह हर request को safe नहीं बनाता और उस API को ठीक नहीं करता जिसने create endpoint पर idempotency लागू ही नहीं की।

Provider के contract के अनुसार token create request में रखें। कुछ APIs `Idempotency-Key` header स्वीकार करती हैं, दूसरी request field मांगती हैं। Documented form इस्तेमाल करें और इतना entropy वाला token बनाएं कि अलग operations आपस में टकरा न सकें। मूल operation resolve होने तक वही token सुरक्षित रखें।

```http
POST /v1/reports HTTP/1.1
Content-Type: application/json
Idempotency-Key: idem_5b5c76c7
X-Trace-ID: tr_0830d3

{
  "account": "acct_218",
  "range": {"start": "2025-02-01", "end": "2025-02-28"},
  "format": "csv"
}
```

सुरक्षित retry sequence सीमित है:

1. Token बनाएं और operation record persist करें।
2. उस token के साथ create request भेजें।
3. Response खो जाए या client timeout हो जाए, तो उसी request को उसी token के साथ retry करें।
4. Server मूल job लौटाए, तो उसका ID save करें और आगे बढ़ें।
5. अलग input चाहिए, तो संभव हो तो पुराने operation को close या cancel करें, फिर नया record और token बनाएं।

यह फर्क महत्वपूर्ण है क्योंकि agent reasoning के दौरान request को स्वाभाविक रूप से फिर लिखते हैं। बदली हुई date range, destination, account या output format से प्रभाव बदल जाता है। ऐसे बदलाव के बाद token reuse करने पर client और server के बीच विवाद पैदा होता है: सावधान server mismatch को reject करेगा, जबकि कम सावधान server पुरानी response लौटा सकता है जो agent के मौजूदा intent से मेल नहीं खाती।

HTTP standard यह फर्क साफ करता है। RFC 9110 idempotent methods को ऐसे methods के रूप में परिभाषित करता है जिनकी repeated identical requests का intended effect एक request के समान होता है। POST default रूप से idempotent नहीं है। Provider POST endpoint में idempotency behavior जोड़ सकता है, लेकिन client को इसे HTTP का नियम नहीं, स्पष्ट application contract मानना चाहिए।

एक आम खराब सलाह है, «POST को केवल एक बार retry करें।» Retry की संख्या मुख्य बात नहीं है। एक duplicate submission payment भेज सकती है, environment provision कर सकती है या महंगा batch शुरू कर सकती है। Deadline और provider guidance अनुमति दें तो create request retry करें, लेकिन केवल ऐसे token के साथ जिससे server मूल operation पहचान सके।

## Timeout के बाद job state अज्ञात रहती है

Create timeout का अर्थ यह नहीं कि service ने request reject कर दी। इसका अर्थ है कि agent को अपनी deadline से पहले निश्चित उत्तर नहीं मिला। Server ने request स्वीकार की हो सकती है, अभी process कर रहा हो सकता है या उसे request मिली ही न हो।

यही failure कमजोर agent workflows को उजागर करती है। Agent create request भेजता है, तीस सेकंड इंतजार करता है, response नहीं मिलता और नए token के साथ नई request भेज देता है। अब दो reports चल रही हैं। दूसरी पहले पूरी हो सकती है, इसलिए incident तब तक दिखाई नहीं देता जब तक कोई charges, exports या downstream changes की तुलना न करे।

स्पष्ट `create_pending` state रखें। Call अस्पष्ट रूप से fail हो तो error class, timestamp और attempt count दर्ज करें, लेकिन operation को हटाएं नहीं। फिर API के reconciliation path का इस्तेमाल करें। Providers इसे अलग-अलग तरीकों से उपलब्ध कराते हैं:

- उसी idempotency token से retry करने पर मूल acceptance response मिल सकती है।
- List या search endpoint client request reference से filter कर सकता है।
- Status lookup client द्वारा दिए गए operation ID को स्वीकार कर सकती है।
- Provider request identifier से हाल की creations खोजने की documented query दे सकता है।

यदि इनमें से कोई रास्ता नहीं है, तो खोई हुई response के बाद API client को reliable at-most-once creation semantics नहीं दे सकती। अपने design में यह बात साफ लिखें। Local outbox और सीमित retries से duplicates कम किए जा सकते हैं, लेकिन यह साबित नहीं किया जा सकता कि retry से अतिरिक्त काम नहीं बना।

उसी token के साथ unfamiliar job ID वाली response को contract violation मानें और incident की तरह लें। पुराने ID को overwrite न करें। दोनों response records सुरक्षित रखें, उस operation की automatic activity रोकें और human decision आवश्यक बनाएं। किसी एक को चुपचाप चुनना audit trail को काल्पनिक बना देता है।

Communication और work के लिए अलग deadlines रखें। Create deadline तय करती है कि agent remote job ID स्थापित करने की कितनी देर कोशिश करेगा। Result deadline तय करती है कि business process completion के लिए कितनी देर इंतजार करेगा। Create response का छोटा timeout होने के बाद भी job कई घंटे चल सकती है। दोनों को एक timer में मिलाने से agent recoverable work छोड़ सकता है या गलत समय पर उसे retry कर सकता है।

## Polling में backoff, ownership और stop time चाहिए

Polling तब safe है जब एक durable workflow job का मालिक हो और हर poll एक observation दर्ज करे। कई agent runs वही job फिर खोज लें और सभी स्वतंत्र रूप से poll करने लगें, तो polling abusive बन जाती है।

Remote job ID को एक record में रखें और observation के वर्तमान मालिक process को lease दें। Lease database row में expiration timestamp, visibility rules वाला queue message या कोई अन्य durable concurrency control हो सकता है। Worker crash होने पर lease expire होने के बाद अगला worker काम संभाल सकता है। Ownership के बिना retries और restarts status calls की संख्या बढ़ाते जाते हैं।

API `Retry-After` भेजे तो उसका पालन करें। Guidance न मिले तो jitter वाला capped exponential backoff इस्तेमाल करें। Exact ceiling इस बात पर निर्भर करती है कि business को उत्तर कितनी जल्दी चाहिए और provider की rate limits क्या हैं, लेकिन pattern synchronized bursts से बचना चाहिए।

```text
attempt 1: wait a randomized interval near 2 seconds
attempt 2: wait a randomized interval near 4 seconds
attempt 3: wait a randomized interval near 8 seconds
later attempts: keep increasing until the configured cap
```

अगला delay agent के prose summary से calculate न करें। Job record में next poll time store करें। इससे restarted worker schedule से आगे बढ़ सकता है और operator को साफ समझ आता है कि agent क्यों इंतजार कर रहा है।

Status response में केवल observed facts update होने चाहिए। उदाहरण के लिए:

```json
{
  "job_id": "job_7ad2",
  "state": "running",
  "updated_at": "2025-03-08T14:26:40Z",
  "progress": {"completed": 146, "total": 500}
}
```

`state`, provider timestamp यदि दिया गया हो, retrieval time, raw response reference और next action दर्ज करें। अस्पष्ट progress field को job के पूरा होने के वादे में न बदलें। Providers अक्सर progress देर से या batches में report करते हैं। Progress operators की मदद करता है, workflow को terminal state नियंत्रित करनी चाहिए।

Result deadline तय करें और उसके expire होने को एक state बनाएं, job भूल जाने का बहाना नहीं। `result_timed_out` का अर्थ है कि agreement expire होने के कारण agent ने automatic polling रोक दी। इसका अर्थ यह नहीं कि remote job रुक गई। Action में वास्तविक cost या side effects हों, तो बाद में reconcile करने और cancellation उचित है या नहीं, यह तय करने के लिए पर्याप्त जानकारी रखें।

Webhooks latency कम कर सकते हैं, लेकिन status loop हटाते नहीं। Providers callbacks retry कर सकते हैं, उन्हें क्रम से बाहर deliver कर सकते हैं या deliver ही नहीं कर सकते। Provider documentation के अनुसार callback verify करें, उपलब्ध होने पर event ID से duplicate हटाएं, उसी job record को update करें और success घोषित करने से पहले final status read करें।

## State transitions को wishful thinking रोकनी चाहिए

Job state machine agent को शब्दों का जरूरत से ज्यादा उदार अर्थ निकालने से बचाती है। Tools को API से जोड़ने से पहले local states और permitted transitions तय करें। Remote services अलग नाम इस्तेमाल करती हैं, लेकिन आपके record में uncertainty साफ दिखनी चाहिए।

एक व्यावहारिक local model है:

```text
create_pending -\u003e accepted -\u003e observing -\u003e result_collecting -\u003e succeeded
create_pending -\u003e create_unknown -\u003e reconciliation
accepted or observing -\u003e cancel_requested -\u003e cancelling -\u003e cancelled
accepted or observing -\u003e failed
observing -\u003e result_timed_out
```

ये arrows documentation का diagram नहीं, rules हैं। Evidence के बिना worker को transition reject करना चाहिए। केवल progress value 100 देखकर `succeeded` न करें। `DELETE /jobs/job_7ad2` भेजने भर से `cancelled` न करें। `failed` से `observing` पर तभी लौटें जब remote API retry या resume operation को स्पष्ट रूप से support करती हो और नया action अलग से दर्ज किया गया हो।

Remote state और local state अलग रखें। `cancel_requested` local fact है: agent ने cancellation request भेजी और confirmation की प्रतीक्षा कर रहा है। `cancelled` remote fact है: service ने terminal cancelled state report की है। यह छोटा फर्क incident के दौरान बहुत भ्रम बचाता है।

Transitions के लिए append-only history रखें। हर entry में operation ID, actor, time, पिछली local state, अगली local state, triggering request या response और reason होना चाहिए। संक्षिप्त history पर्याप्त है:

```json
{
  "at": "2025-03-08T14:29:02Z",
  "actor": "worker-3",
  "from": "observing",
  "to": "cancel_requested",
  "cause": "human_request:req_91af",
  "remote_job_id": "job_7ad2"
}
```

केवल एक mutable `status` field पर निर्भर न रहें। वह बताती है कि workflow अभी क्या मानता है, लेकिन यह नहीं बताती कि पांच मिनट पहले वह ऐसा क्यों मानता था। Remote API बाद में surprising state लौटाए, तो history बताएगी कि provider बदला, agent ने call दोहराई या operator ने हस्तक्षेप किया।

## Cancellation को confirmation और damage boundary चाहिए

Cancellation भविष्य का काम रोकने का request है। Provider जिस काम को पहले ही commit कर चुका है उसे यह undo नहीं कर सकती। कुछ providers में ऐसी race भी होती है जिसमें cancel request पहुंचने के समय job पूरी हो जाती है। Workflow इसी वास्तविकता पर बनाएं।

जब कोई इंसान या policy job रोकने का निर्णय ले, पहले cancellation intent दर्ज करें। किसने request की, क्यों की और अपेक्षित प्रभाव क्या है, यह लिखें। फिर stored remote job ID का इस्तेमाल करते हुए documented cancel endpoint call करें। Response को तब भी persist करें जब उसमें केवल यह लिखा हो कि server ने request स्वीकार कर ली।

Cancellation के बाद भी polling जारी रखें। स्वीकार्य terminal outcomes में आम तौर पर `cancelled`, `succeeded` और `failed` शामिल होते हैं। Cancel request के बाद completed result मिलना अपने-आप error नहीं है। हो सकता है job कुछ सेकंड पहले commit point पार कर चुकी हो। Workflow को desired outcome के अनुसार history बदलने के बजाय यह sequence ठीक-ठीक report करनी चाहिए।

कुछ operations में cancellation से अलग damage boundary चाहिए। Export job file लिखती हो तो cancellation partial file छोड़ सकती है। Provisioning job resources बनाती हो तो cancellation के बाद कुछ resources बने रह सकते हैं। API contract में cleanup, rollback या partial-result details की उपलब्धता स्पष्ट होनी चाहिए। ऐसा न हो तो cancellation को transaction नहीं, operational control मानें।

हर polling worker से बार-बार cancel calls न करवाएं। `cancel_requested` store करें, provider अनुमति दे तो cancel operation को idempotent बनाएं और follow-up का ownership lease holder को दें। Harmless request दोहराने से capacity व्यर्थ जाती है, जबकि side effects वाली cancellation दोहराने से remote audit log अस्पष्ट हो सकता है।

Cancellation deadline भी उपयोगी है। उचित और documented wait के बाद success का दावा करने के बजाय `cancellation_unconfirmed` पर जाएं। Remote job ID, trace ID, request history और provider request IDs के साथ escalation करें। इस package से human या provider support team chat messages से sequence दोबारा बनाए बिना वास्तविक घटनाक्रम देख सकती है।

## Result collection अलग action है

Terminal success state का अर्थ है कि remote work पूरा हो गया। इसका अर्थ यह नहीं कि result fetch, validate, store या अगले system तक deliver भी हो चुका है। Collection को अपने recorded action की तरह लें।

पहले API द्वारा दिए गए job ID या result reference से result fetch करें। जहां provider content type, schema, checksum, size या record count देता हो, अपेक्षित value को validate करें। Job record में result reference और validation outcome store करें। Result बड़ा हो तो opaque content को event log में copy करने के बजाय durable location और integrity data रखें।

फिर तय करें कि collection को खुद idempotency चाहिए या नहीं। कई result endpoints safe reads होते हैं। कुछ temporary download बनाते हैं, one-time artifact consume करते हैं या job को delivered mark करते हैं। Contract पढ़ें। हर `GET` को harmless मानने वाला agent भी provider-specific state change trigger कर सकता है।

Successful HTTP status को अकेला validation न मानें। Report endpoint valid file लौटा सकता है जिसमें error row हो। Image batch failed items वाली manifest लौटा सकता है। Data export पूरा हो सकता है, लेकिन ऐसे records छोड़ सकता है जिन्हें API के अनुसार caller access नहीं कर सकता। उस business expectation के आधार पर validate करें जिसके कारण job बनाई गई थी।

Batch work में provider support करे तो item-level outcomes दर्ज करें। Terminal job में 498 successes और दो failures हो सकती हैं। उसे केवल «succeeded» कहने से अगला agent result payload से partial failure फिर खोजने को मजबूर होगा। Local final state successful रह सकती है, जबकि result summary में counts और failed item references की list हो।

Operation तभी close करें जब collection अपने contract को पूरा करे। `succeeded` का अर्थ होना चाहिए कि intended result उपलब्ध है और आपके rules के अनुसार verified है। Provider complete हो गया हो लेकिन collection fail हो, तो `result_unavailable` या `result_validation_failed` जैसी अलग local state रखें। Remote job पूरी हो सकती है, workflow नहीं।

## Trace IDs actions को जोड़ते हैं, audit records facts स्थापित करते हैं

हर operation के लिए trace ID रखें और API custom headers स्वीकार करे तो creation, status reads, cancellation और collection में उसे भेजें। Remote job ID मिलते ही उसे trace ID के साथ जोड़ें। Trace ID आपके systems के events को जोड़ता है, job ID provider के work item को खोजने देता है।

किसी identifier पर जरूरत से ज्यादा जिम्मेदारी न डालें। Trace ID को idempotency token न बनाएं, क्योंकि एक operation में अलग retry rules वाली कई requests हो सकती हैं। Job ID को authorization record न बनाएं, क्योंकि local action का निर्णय लेने के बाद provider ने उसे generate किया है।

Audit record को उन सवालों का उत्तर देना चाहिए जिनका जवाब logs अक्सर नहीं दे पाते: operation किस agent process ने शुरू किया, कौन-सी human approval उस पर लागू थी, कौन-सा credentialed action हुआ और क्या किसी ने बाद में history बदली। Create call से पहले संक्षिप्त intent record लिखें और उसके बाद observations append करें। Request IDs और sanitized response metadata सुरक्षित रखें। Bearer tokens, passwords, private keys या पूरे sensitive payloads को general log में कभी न रखें।

Sallyport stored credentials को agent के सामने उजागर किए बिना HTTP API calls चला सकता है। इसकी sessions और individual calls tamper-evident trail देती हैं, जिसे `sp audit verify` से verify किया जा सकता है। इससे credential custody और action evidence सुरक्षित होते हैं, लेकिन workflow को अपना operation record फिर भी चाहिए, क्योंकि केवल वही जानता है कि `job_7ad2` मांगे गए business task से जुड़ा है या नहीं।

किसी खराब दिन में trace तभी उपयोगी होगा जब हर component उसे एक ही तरह record करे। उसे local job record, agent execution context, जहां अनुमति हो वहां request headers, जहां अनुमति हो वहां audit annotations और operator tickets में रखें। हर poll के लिए नया trace न बनाएं। वे उसी operation के child events हैं।

## Reference loop सामान्य failures को संभालता है

नीचे दिया workflow कठिन decisions को स्पष्ट रखता है। इसमें ऐसा provider माना गया है जिसके पास idempotent create contract, status endpoint और cancellation endpoint हैं। Endpoint names बदलें, लेकिन persistent state transitions न हटाएं।

```text
load operation by local operation ID

if no operation exists:
    create and persist record with fingerprint and idempotency token

if remote job ID is absent:
    send create with the stored token
    if response confirms job ID:
        persist job ID and move to observing
    if response is ambiguous:
        move to create_unknown and reconcile using the stored token
    if response rejects request definitively:
        move to failed

while local state requires observation and result deadline has not passed:
    acquire lease for the operation
    read remote status
    append the observation
    if cancellation was requested and remote state is nonterminal:
        send cancellation once and record the attempt
    if remote state is terminal:
        collect and validate result if appropriate
        persist final local state
    otherwise:
        persist next poll time and release lease

if the deadline expires before a terminal observation:
    move to result_timed_out and preserve the reconciliation record
```

इस loop में कोई जादुई retry count नहीं है, क्योंकि retry limits provider, operation की cost और caller की deadline पर निर्भर करती हैं। लेकिन इसमें एक अधिक महत्वपूर्ण rule है: हर retry एक stored operation से जुड़ी है और हर externally visible action उस operation की history बदलता है।

Autonomous agents को देने से पहले failure injection के साथ testing करें। Server के request स्वीकार करने के बाद create response drop करें। Job ID save करने और पहला poll schedule करने के बीच worker को बंद करें। Cancellation race के दौरान terminal result लौटाएं। वही webhook दो बार deliver करें। Stale lease के साथ restart करें। यदि workflow इनमें से हर स्थिति को समझा और recover नहीं कर सकता, तो महंगे या महत्वपूर्ण jobs शुरू करने के लिए तैयार नहीं है।

पहला implementation task आकर्षक नहीं है: durable operation record बनाएं और उसके बिना create request भेजने से इनकार करें। यह एक constraint agent को intent बनाए रखने के लिए मजबूर करती है, duplicate prevention संभव बनाती है और remote system के imperfect व्यवहार करने पर सभी को factual record देती है।
