# Production accounts पर एक साथ चलने वाले AI agents कैसे टकराते हैं

एक ही production account पर चलने वाले दो autonomous runs केवल इसलिए सुरक्षित नहीं हो जाते कि हर run का task description अलग है। दोनों एक बदलती हुई system को साझा करते हैं, और उनके बनाए हर plan के बाद अगली API call तक वह पुराना हो सकता है। अगर दोनों एक ही resource बदल सकते हैं, तो आपको ऐसी ownership boundary चाहिए जिसे service लागू करे।

आम failure किसी बड़े outage जितना नाटकीय नहीं होता। एक agent group में member जोड़ता है, जबकि दूसरा पुराने read के आधार पर group की पूरी membership बदल देता है। दोनों requests success लौटाती हैं। बाद वाली request पहले agent द्वारा जोड़े गए member को हटा देती है। हर run ने अपने instructions का पालन किया। आपकी API ने एक गलत sequence स्वीकार कर लिया।

Agent run को वास्तविक credentials वाले, लेकिन अविश्वसनीय और imperfect timing वाले concurrent client की तरह मानें। उसे ownership का छोटा और स्पष्ट क्षेत्र दें, write requests को उस version पर निर्भर बनाएँ जिसे उसने पढ़ा था, और इतना context रिकॉर्ड करें कि बाद में किसी rejected या accepted change को समझाया जा सके। Human review उपयोगी है, लेकिन वह ऐसे receiver की जगह नहीं ले सकता जो stale state पहचानता हो।

## Task boundary और write boundary को अलग रखें

Task boundary बताती है कि agent से क्या पूरा करने को कहा गया था। Write boundary बताती है कि वह किन बदलने योग्य objects को बदल सकता है। ये दोनों अलग बातें हैं। जब teams इन्हें एक जैसा मान लेती हैं, तब नुकसान होता है।

«Staging deployment update करो» सुनने में सीमित काम लगता है। फिर भी इसमें shared image tag, release pointer, traffic rule, DNS record, database migration ledger और notification channel बदल सकते हैं। Agent task के शब्दों का पालन करते हुए भी उस release run से टकरा सकता है जिसके पास इनमें से किसी object का ownership है।

Ownership को उन terms में परिभाषित करें जिन्हें receiving service जाँच सके। अच्छी boundaries काम की ढीली category नहीं, बल्कि स्थायी resource identifiers बताती हैं:

- एक environment और एक deployment record
- एक tenant या customer account
- एक pull request और उसकी branch
- एक incident ticket और उसके change set में बताए गए resources
- एक maintenance window और targets की स्पष्ट सूची

«Backend work» या «production cleanup» जैसी boundaries से बचें। ये लोगों के लिए labels हैं। वे API को नहीं बताते कि कौन-सा write fail होना चाहिए।

एक उपयोगी ownership record में run ID, resource ID, allowed operation और expiry होते हैं। इसे resource की owner service के पास रखें। अगर deployment controller release pointer का owner है, तो वही controller जाँचे कि उसे आगे बढ़ाने की अनुमति किसे है। Spreadsheet, chat message या prompt instruction उस request को नहीं रोक सकते जो उसे लिखने वाला व्यक्ति घर जा चुका होने के बाद पहुँचे।

### Write access देने से पहले छोटा conflict map बनाएँ

हर automated job के लिए लिखें कि वह किन resources को पढ़ता, लिखता और हटाता है, और किन्हें shared default की तरह इस्तेमाल करता है। फिर हर उस job pair को चिन्हित करें जो एक ही identifier लिख सकता है, या जहाँ एक job के input को दूसरा job बदल सकता है। यह bureaucracy नहीं है। इससे वे collisions दिखते हैं जिन्हें role-based permissions छिपा देती हैं।

मिसाल के लिए, service token बदलने वाला agent और integration configuration update करने वाला agent शायद कभी एक ही endpoint call न करें। Configuration writer current token reference पढ़ सकता है, फिर rotation के बाद अपना पूरा configuration document publish कर सकता है। Conflict एक जैसे command में नहीं, document के version में छिपा है।

अगर आप किसी run का write set नहीं बता सकते, तो उसे broad production write permission न दें। उससे proposal तैयार करवाएँ, या boundary स्पष्ट होने तक उसे किसी resource namespace तक सीमित रखें।

## Successful response भी सही change मिटा सकता है

जब clients पूरी representations भेजते हैं, तब last-write-wins behavior data-loss policy बन जाता है। Demos में यह harmless लगता है, क्योंकि हर client तुरंत पढ़कर लिखता है। Agents logs देखने, plan बनाने, approval माँगने और timeout के बाद call दोबारा आज़माने में कई मिनट लगा सकते हैं।

मान लें कि service में `notification-policy` resource है। वह Agent A को यह representation लौटाती है:

```json
{
  "id": "prod-alerts",
  "version": 41,
  "destinations": ["oncall@example.test"],
  "severity": "high"
}
```

Agent A backup destination जोड़ने की योजना बनाता है। Review के दौरान Agent B `severity` को `high` से `critical` करता है और version 42 सफलतापूर्वक लिख देता है। इसके बाद Agent A version 41 के आधार पर full replacement भेजता है:

```json
{
  "destinations": ["oncall@example.test", "backup@example.test"],
  "severity": "high"
}
```

अगर endpoint यह request स्वीकार कर लेता है, तो वह चुपचाप B का change वापस कर देता है। किसी agent में bug होना ज़रूरी नहीं है। API ने पुराने observation को नए fact पर overwrite करने दिया।

Partial updates surface area घटाते हैं, लेकिन समस्या खत्म नहीं करते। Destination जोड़ने वाला patch भी नई quota, बदली हुई routing policy या read के बाद हुए deletion का उल्लंघन कर सकता है। Service को current state के आधार पर तय करना होगा कि patch अभी भी valid है या नहीं।

इसीलिए «हम agents को केवल PATCH इस्तेमाल करने देते हैं» concurrency design नहीं है। यह केवल write का छोटा रूप है। आपको अब भी ऐसी condition चाहिए जो write को agent द्वारा देखी गई state से जोड़े।

## हर state-changing request को conditional बनाएँ

Agent writes के लिए optimistic concurrency control आम तौर पर पहली अच्छी सुरक्षा है। Client कोई version, ETag, generation number या revision token पढ़ता है। Intended update के साथ उसे वापस भेजता है। Service तभी write स्वीकार करती है, जब current value अब भी वही हो।

RFC 9110 में `If-Match` इसी तरह की request के लिए है। Server method लागू करने से पहले condition जाँचता है। अगर entity tag match नहीं करता, तो server method को `412 Precondition Failed` के साथ अस्वीकार करता है। यह API की असुविधा नहीं है। Server यह मानने से इनकार कर रहा है कि पुराना plan अब भी सही है।

Conditional HTTP update इस तरह दिख सकता है:

```http
GET /v1/notification-policies/prod-alerts

HTTP/1.1 200 OK
ETag: "41"
Content-Type: application/json

{"destinations":["oncall@example.test"],"severity":"high"}
```

Agent उस ETag को अपने write में भेजता है:

```http
PATCH /v1/notification-policies/prod-alerts
If-Match: "41"
Content-Type: application/json
Idempotency-Key: run-7f3c-add-backup

{"destinations":["oncall@example.test","backup@example.test"]}
```

अगर किसी दूसरे writer ने ETag `"42"` बना दिया है, तो स्पष्ट rejection लौटाएँ:

```http
HTTP/1.1 412 Precondition Failed
Content-Type: application/json

{
  "error": "stale_version",
  "resource": "notification-policy/prod-alerts",
  "expected_etag": "41",
  "current_etag": "42",
  "retryable": false
}
```

अगर agent उसी body को बिना सोचे दोबारा भेज सकता है, तो इस error को retryable न कहें। Retry हमेशा fresh read और नए decision से शुरू होना चाहिए। इससे पता चल सकता है कि desired result पहले ही मौजूद है, latest policy change को invalid बनाती है, या दो competing outcomes में से किसी एक को चुनने के लिए व्यक्ति की ज़रूरत है।

Databases में mutation के अंदर equivalent predicate लगाएँ। आम update `WHERE` clause में version जाँचता है और zero affected rows को conflict मानता है:

```sql
UPDATE notification_policy
SET destinations = :destinations,
    version = version + 1
WHERE id = :id
  AND version = :observed_version;
```

Version पढ़कर दूसरे operation में unconditional update कभी न करें। Check और state change, दोनों उस authority पर साथ होने चाहिए जो state store करती है।

## Idempotency duplicates रोकती है, असहमति नहीं

Teams अक्सर endpoint पर idempotency key लगाकर मान लेती हैं कि concurrent writes संभाल लिए गए। Idempotency key एक ही logical request को अपना effect दो बार पैदा करने से रोकती है। यह service को नहीं बताती कि दो अलग requests compatible हैं या नहीं।

Network timeout इस फर्क को साफ दिखाता है। Agent deployment बनाने की request भेजता है, लेकिन response खो जाता है। उसी idempotency key के साथ retry करने पर दूसरी deployment बनाने के बजाय original result मिलना चाहिए। यह duplicate suppression है।

अब दो agents को लें, जो उसी production environment के लिए अलग release candidate चुनते हैं। वे अलग bodies और अलग idempotency keys भेजते हैं। दोनों requests पूरी तरह idempotent हो सकती हैं, फिर भी एक को हारना चाहिए क्योंकि release pointer बदल चुका है।

महत्वपूर्ण write endpoints पर दोनों controls लगाएँ:

- Idempotency key retries और duplicate deliveries को एक completed operation से बाँधती है।
- Version precondition उस write को अस्वीकार करती है जिसका decision stale resource state पर आधारित है।
- Server-side invariant यह जाँचता है कि current write में भी वे rules बने रहें जो हमेशा लागू होने चाहिए, जैसे active credentials की maximum संख्या।

Idempotency key को request fingerprint और completed response के साथ store करें। अगर caller उसी key को अलग body के साथ दोबारा इस्तेमाल करे, तो उसे reject करें। अलग operation के लिए पहला response लौटाने से debugging मुश्किल होती है और client bug छिप सकता है।

Expiry को लेकर सख्त रहें। Service को key इतनी देर रखनी चाहिए कि उसका वास्तविक retry behavior समा सके, लेकिन idempotency store permanent command history नहीं है। History audit log में रखें।

## केवल उस work के लिए leases इस्तेमाल करें जो overlap नहीं कर सकता

कुछ actions इतने लंबे होते हैं कि केवल optimistic checks user experience को खराब बना देते हैं। Database migration, destructive reconciliation job या cutover में कई dependent writes हो सकते हैं। ऐसे मामलों में एक run को resource पर short-lived lease दें।

Lease में owner, expiry और fencing value होना चाहिए। Fencing value इसलिए ज़रूरी है कि expired worker जागकर तब भी आगे बढ़ सकता है, जब दूसरा worker नया lease ले चुका हो। हर protected write में lease का monotonically increasing token होना चाहिए, और service को पिछले accepted token से पुराना token reject करना चाहिए।

Fencing के बिना lock service Agent A को बता सकती है कि उसका lease expire हो गया, लेकिन वह A की delayed request को database तक पहुँचने से नहीं रोक सकती। Destination service को उसे reject करना होगा। Distributed lock होने की बात कहते समय teams इसी हिस्से को छोड़ देती हैं।

Leases को सीमित और छोटा रखें। पूरे autonomous investigation के लिए «production» को lock न करें। `migration/customer-1842` या `release/prod-eu` को lock करें और run से lease तभी renew करवाएँ, जब वह अभी भी progress कर रहा हो। Agent process, उसका laptop या network गायब हो जाए, तो lease सुरक्षित रूप से expire हो जाना चाहिए।

Version checks वाली सामान्य configuration edits को lease से न ढकें। Long locks routine changes को queues में बदल देते हैं, फिर लोग lock bypass करना सीख जाते हैं। Fresh plan के बाद मिलने वाला `412`, stale lock holder की वजह से हुए outage से सस्ता है।

## Agent identity gateway के पार भी बनी रहनी चाहिए

Shared production token service के सामने हर run को एक ही नाम देता है। Collision के बाद आपको दिखता है कि token ने action किया, लेकिन यह पता नहीं चलता कि change किस process ने plan किया, किस approval ने उसे cover किया या किस run को रोकना है। इससे cleanup धीमा और revocation बहुत व्यापक हो जाता है।

हर agent process को अलग session identity दें और हर destination request में stable correlation identifier भेजें। Destination service को identity, run ID, request ID, target resource, observed version, outcome और अपना resulting version log करना चाहिए। यह जानकारी commit message के prose में न छिपाएँ।

Sallyport action चलाते समय API और SSH credentials को agent process से बाहर रखता है। इससे agent के planning context और secret के बीच boundary बनाए रखने में मदद मिलती है। इसकी per-session authorization किसी नए agent process की पहचान run शुरू होने से पहले कर सकती है। यह authorization इस बात का control है कि कौन action कर सकता है, destination-side preconditions का विकल्प नहीं।

Agent को arbitrary header में अपनी effective identity चुनने न दें। Gateway या destination service को authenticated session से identity bind करनी चाहिए। वरना run बाद में खुद को deployment coordinator बता सकता है और आपके logs केवल दिखावा बन जाते हैं।

SSH में भी यही सिद्धांत लागू होता है, भले ही wire protocol अलग हो। अलग classes of work के लिए अलग principals या restricted accounts इस्तेमाल करें। Remote command logs में run identifier रखें और ऐसा single shared shell account न रखें जो हर application directory edit कर सके।

## Approval timing, transaction timing नहीं है

कोई व्यक्ति agent की request approve कर सकता है, फिर भी दस seconds बाद किसी दूसरे writer के state बदलने से वह write गलत हो सकती है। Concurrent system में यह सामान्य है। Approval review के समय authority और intent से जुड़ी होती है। इससे resource freeze नहीं होता।

खतरनाक design में व्यक्ति «production configuration update करो» जैसे broad sentence को approve करता है, फिर agent जब चाहे reads और writes की sequence चलाता है। सुरक्षित design target और intended effect दिखाता है, फिर write पहुँचने के समय service version या lease लागू करती है।

Approval के बाद precondition fail हो जाए, तो बदले हुए plan के लिए उस approval का अपने-आप फिर इस्तेमाल न करें। Agent को conflict साफ शब्दों में बताना चाहिए: कौन-सा resource बदला, उसने कौन-सा version देखा, service बता सके तो कौन-सा field बदला, और उसका proposed result अब भी ज़रूरी है या नहीं। इसके बाद व्यक्ति नई action approve कर सकता है, या agent reread के बाद safe no-op कर सकता है।

हर call पर approval उन operations के लिए सही है जिनका हर इस्तेमाल काफी जोखिम भरा है, जैसे production credential हटाना या externally visible routing rule बदलना। Narrow, conditional writes के सामान्य batches में per-run approval अक्सर अधिक उपयोगी होती है। इससे operator run की identity और scope देख सकता है, बिना हर बार आदतन click किए।

Approvals के ढेर को control न समझें। अगर operators resource ID, operation और current conflict result नहीं देख सकते, तो वे एक sentence approve कर रहे हैं जबकि असली काम service कहीं और कर रही है।

## Conflict response का owner पहले से तय करें

Rejected stale write safety का सफल परिणाम है, लेकिन तभी जब run को पता हो कि आगे क्या करना है। «Error पर retry करो» गलत default है। इससे असहमति automated race में बदल जाती है।

Autonomous execution की अनुमति देने से पहले हर write path को classify करें। Class से तय होगा कि conflict कौन सुलझाएगा:

| Change type | On version conflict | Owner |
| --- | --- | --- |
| अलग और unique नाम वाला independent resource जोड़ना | Reread करें और नाम unused हो तो retry करें | Agent |
| Current source data से computed field update करना | Reread, recompute और फिर retry करें | Agent |
| Shared release pointer आगे बढ़ाना | रुकें और दोनों candidates दिखाएँ | Release owner |
| Access membership या permissions बदलना | रुकें और review माँगें | Account owner |
| Shared configuration document हटाना या replace करना | जब तक explicit lease न हो, रुकें | Named operator |

उद्देश्य agents को डरपोक बनाना नहीं है। उद्देश्य recomputation और judgment के बीच अंतर करना है। Agent current inputs से बना report सुरक्षित रूप से दोबारा तैयार कर सकता है। उसे केवल `412` देखकर दो approved production versions, दो access decisions या दो rollback plans में से किसी एक को नहीं चुनना चाहिए।

Conflict responses machine-readable बनाएँ। उनमें resource identity, current version, conflict category और fresh automatic attempt की अनुमति है या नहीं, यह शामिल करें। HTML error page वाला अस्पष्ट `409` agent को अनुमान लगाने पर मजबूर करता है।

### जिस collision की आशंका है, उसका test करें

अपने checks को सही साबित करने के लिए production traffic का इंतज़ार न करें। ऐसा test बनाएँ जो read और write के बीच एक run को रोक दे, दूसरे run को उसी resource में change करने दे, फिर पहले run को आगे बढ़ाए। चार परिणाम जाँचें:

1. पहला write resource बदले बिना fail हो।
2. Response generic server error के बजाय stale version बताए।
3. Agent अपना पुराना body अपने-आप दोबारा न भेजे।
4. आपके journals दोनों attempts को उनकी run identities और approvals से जोड़ सकें।

Timeout और retry के साथ भी यही test चलाएँ, ताकि idempotency behavior अलग साबित हो। ये अलग failure paths हैं और इनके expected results भी अलग होने चाहिए।

## Attempted action और resulting state, दोनों का audit करें

Agent collision के बाद production account में दो records चाहिए: command path और authoritative resource history। Gateway logs बताते हैं कि किस approved session से किसने action माँगा। Service logs बताते हैं कि state बदली या नहीं, कौन-सा version जीता और request क्यों fail हुई।

«PATCH succeeded» कहने वाले activity record से संतुष्ट न हों। Resource identifier, method, request correlation ID, client द्वारा भेजी गई precondition, idempotency key या उसका सुरक्षित reference, response status और resulting ETag रिकॉर्ड करें। अगर service field-level history रखती है, तो changed fields वहीं capture करें, agent transcript से उनका अनुमान न लगाएँ।

Sallyport अपने Sessions और Activity journals को encrypted hash-chained audit log से तैयार करता है। Agent actions के लिए इसका इस्तेमाल करते हैं, तो disputed sequence की जाँच के दौरान यह command चलाएँ:

```sh
sp audit verify
```

यह command ciphertext पर offline chain verify करता है और vault key की ज़रूरत नहीं होती। इससे पता चल सकता है कि वह local journal intact रहा या नहीं। क्या हुआ, यह तय करने से पहले इसकी तुलना destination service के request logs से करें।

यहाँ retention और access rules भी महत्वपूर्ण हैं। Agent transcript में flawed reasoning या copied operational details हो सकते हैं, जबकि request journal एक संक्षिप्त factual record होना चाहिए। Authority और state transitions को फिर से समझने के लिए ज़रूरी evidence रखें और उसे browse करने वालों की संख्या सीमित करें।

## Parallelism को independent resource sets में रखें

हर autonomous run के लिए एक single global queue की ज़रूरत नहीं है। आपको ऐसा rule चाहिए जो independent work को आगे बढ़ने दे और shared mutation को स्पष्ट बनाए। Tenant, environment, repository branch, service या किसी ऐसे resource namespace के आधार पर partition करें जिसे service verify कर सके।

एक व्यावहारिक production design में coordinator हर run को write set देता है और credentials या gateway access केवल उसी set के लिए देता है। Coordinator यह तय नहीं करता कि हर change समझदारी भरा है या नहीं। वह दो workers को गलती से overlapping authority के साथ पहुँचने से रोकता है। Receiving services फिर भी versions और invariants लागू करती रहें, क्योंकि coordinators fail हो सकते हैं, assignments बदल सकते हैं और लोग normal path से बाहर emergency work शुरू कर सकते हैं।

जब कोई action कई resources तक फैला हो, तो उसे one atomic change कहने की जल्दबाज़ी न करें, अगर services वास्तव में साथ transact नहीं कर सकतीं। Intended state रिकॉर्ड करें, writes को इस क्रम में रखें कि बाद के steps पहले वाले results validate कर सकें, और execution से पहले compensation तय करें। Compensation action को भी current-state check चाहिए। पुराने snapshot पर rollback करने से original run के बाद हुआ legitimate change मिट सकता है।

पहला production test जानबूझकर साधारण रखें: एक shared configuration resource चुनें, उसी version से दो agent runs शुरू करें और उनसे incompatible edits propose करवाएँ। अगर service दोनों स्वीकार कर ले, तो किसी भी run को बड़ा remit देने से पहले उस endpoint को ठीक करें। Collision के बाद autonomy कम दिलचस्प लगती है, और इसी कारण controlled test में collision पहले कराना चाहिए।
