# AI agents के लिए GraphQL mutations को destructive calls से कैसे बचाएं

Generated GraphQL requests एक अनुमानित तरीके से fail होती हैं: model के पास valid syntax बनाने के लिए पर्याप्त जानकारी होती है, लेकिन खतरनाक action को कठिन बनाने के लिए पर्याप्त रोक नहीं होती। `updateProject` नाम का mutation, जिसमें optional `archived` flag हो, इंसान को flexible लग सकता है। Partial context से request बनाने वाले agent के लिए यह routine edit के side effect के रूप में project state बदलने का निमंत्रण है।

रोक schema और resolver में रखें, उस prompt में नहीं जिसमें agent से सावधान रहने को कहा गया हो। Typed inputs, सीमित action authority, उपयोगी previews, conditional writes और recovery path वाली errors सही requests बनाना damaging requests से आसान करते हैं। इससे सामान्य client developers को भी मदद मिलती है। Agents बस उन shortcuts को उजागर करते हैं जिन्हें loose APIs ने वर्षों से सहन किया है।

## Valid request फिर भी गलत action कर सकती है

GraphQL यह जांचता है कि request schema से मेल खाती है या नहीं। यह तय नहीं करता कि caller ने सही customer चुना है, record state समझी है या कुछ हटाने का इरादा था। Teams अक्सर type safety को action safety समझ लेते हैं और फिर broad update mutation में `delete`, `archive` या `status` नाम का field रख देते हैं।

यह एक आम schema है:

```graphql
input ProjectPatchInput {
  name: String
  description: String
  archived: Boolean
  ownerId: ID
}

type Mutation {
  updateProject(id: ID!, input: ProjectPatchInput!): Project!
}
```

इसमें harmless edits के साथ ownership transfer और lifecycle transition भी मिले हुए हैं। अगर agent से «पुराने projects को साफ़ करने» को कहा जाए, तो वह उचित रूप से मान सकता है कि `archived: true` सही है। Project name ठीक करने को कहा गया agent पहले बनाए गए object से `archived` field गलती से रख सकता है। Type system दोनों requests स्वीकार करता है, क्योंकि दोनों अच्छी तरह बनी हुई हैं।

Flexible patch के भीतर destructive behavior न छिपाएं। हर action को ऐसा नाम दें जो उसके परिणाम को स्पष्ट करे और ऐसा input दें जिसमें केवल उसी परिणाम के लिए जरूरी evidence हो:

```graphql
type Mutation {
  renameProject(input: RenameProjectInput!): RenameProjectPayload!
  archiveProject(input: ArchiveProjectInput!): ArchiveProjectPayload!
  transferProjectOwnership(input: TransferProjectOwnershipInput!): TransferProjectOwnershipPayload!
}

input RenameProjectInput {
  projectId: ID!
  expectedVersion: Int!
  name: String!
}

input ArchiveProjectInput {
  projectId: ID!
  expectedVersion: Int!
  reason: ArchiveReason!
  confirmation: String!
  idempotencyKey: String!
}
```

यह दिखावटी औपचारिकता नहीं है। Narrow mutation यह सीमित करता है कि generated request क्या कह सकती है। इससे «label edit करना» और «इसे सामान्य उपयोग से हटाना» अलग actions बनते हैं। Tool descriptions यह अंतर समझा सकती हैं, लेकिन schema को इसे लागू करना चाहिए।

GraphQL specification यहां सीमित, लेकिन उपयोगी मदद करती है। Input object validation उन input fields को अस्वीकार करती है जिन्हें schema define नहीं करता। अगर `ArchiveProjectInput` में `ownerId` नहीं है, तो client archive call में ownership change छिपाकर नहीं भेज सकता। इसे guardrail समझें, security boundary नहीं। यह अभी भी resolver तय करता है कि actor इस खास project को archive कर सकता है या नहीं।

`mutateProject(action: "ARCHIVE")` जैसे generic `action: String!` field से बचें। जब हर action को अलग fields, validation, authorization, preview data और error handling की जरूरत पड़ेगी, तब यह compact नहीं रहेगा। नतीजा input object के भीतर फंसा private RPC protocol होगा, जिसे GraphQL tooling से भी कम मदद मिलेगी।

## Inputs को target और boundary स्पष्ट करनी चाहिए

Destructive input में ठीक-ठीक बताना चाहिए कि क्या बदलेगा, caller ने कौन-सा version देखा था और कौन-सी सीमा selection को फैलने से रोकती है। जब resolver child records, external systems या पूरे tenant की query तक फैल सकता हो, तब अकेले IDs पर्याप्त intent नहीं बताते।

ऐसे object से शुरू करें जो caller के tenant में एक ही resource की पहचान करे। Deletion mutation में arbitrary filter स्वीकार न करें, जब तक product को सचमुच bulk work की जरूरत न हो। `where: { status: INACTIVE }` जैसा filter अस्पष्टता पैदा करता है: inactive किस timestamp, किस tenant और किस hidden default के अनुसार? Model इसे इसलिए भेज सकता है क्योंकि field मौजूद है, इसलिए नहीं कि उसने resulting set की समीक्षा की है।

One-record operations के लिए input में version token रखें। Integer देखना आसान है, लेकिन opaque revision string भी काम करती है। Resolver उसी transaction में stored version से इसकी तुलना करे जिसमें change लिखा जा रहा है। दोनों अलग हों तो conflict लौटाएं और कुछ भी न बदलें।

```graphql
input ArchiveProjectInput {
  projectId: ID!
  expectedVersion: Int!
  reason: ArchiveReason!
  confirmation: String!
  idempotencyKey: String!
}

enum ArchiveReason {
  CUSTOMER_REQUEST
  DUPLICATE
  END_OF_LIFE
}
```

`reason` enum reporting को बेहतर बनाने से ज्यादा काम करता है। यह request को ऐसी free-form justification गढ़ने से रोकता है जिसे बाद का automation meaningful समझ ले। जहां लोगों को note की जरूरत हो वहां free text रखें, लेकिन operational categories को enumerable रखें।

Confirmation field को वास्तविक target से जोड़ना चाहिए। केवल literal string `ARCHIVE` मांगने से careless construction पकड़ी जाती है। `archive acme-project-42` मांगने पर client को resource identifier resolve करके दोहराना पड़ता है। यह malicious client को नहीं रोकता और authorization का विकल्प कभी नहीं हो सकता। लेकिन इससे generated requests का बड़ा वर्ग पकड़ा जाता है, जिसमें सही action गलत ID से जुड़ गया था।

Display name बदलने जैसे routine mutations में confirmation न मांगें। बहुत ज्यादा confirmation agents और लोगों को हर field mechanically भरने की आदत डाल देती है। इसे deletion, publication, financial movement, credential revocation और दूसरे users को प्रभावित करने वाले कठिन या महंगे परिणामों वाले actions के लिए बचाकर रखें।

Bulk operation में upper bound स्पष्ट करें और exact selection से जुड़ा preview token लौटाएं। यह input raw filter से कहीं अधिक जानकारी देता है:

```graphql
input DeleteDormantProjectsInput {
  previewToken: ID!
  expectedCount: Int!
  confirmation: String!
  idempotencyKey: String!
}
```

Execution resolver को ऐसा token अस्वीकार करना चाहिए जो expire हो चुका हो, किसी दूसरे actor का हो, दूसरे tenant का हो या `expectedCount` से अलग count देता हो। वरना agent पांच records का preview लेकर ऐसी moving query execute कर सकता है जो अब पचास records से match करती है।

## Authority noun के नहीं, mutation के साथ चलनी चाहिए

`projects:write` जैसी scope autonomous work के लिए आम तौर पर बहुत व्यापक होती है। एक ही permission के तहत caller rename, archive, transfer, delete और शायद billing से जुड़ी project settings भी बदल सकता है, क्योंकि सभी actions project को छूते हैं। यह grouping database noun पर आधारित है, operation के risk पर नहीं।

ऐसी authority दें जो action को स्पष्ट करे। उदाहरण के लिए release automation के service token में `project:rename` और `project:archive` हो सकते हैं, जबकि support workflow में दोनों न हों। अलग `project:delete` scope दुर्लभ रहनी चाहिए। अगर identity system इतनी narrow scopes जारी नहीं कर सकता, तो mutation name से जुड़ा server-side capability check जोड़ें और authorization decision में उसे दर्ज करें।

Scope अकेले access तय नहीं करती। हर resolver को कई checks एक निश्चित क्रम में करने चाहिए:

1. Caller को authenticate करें और उसका tenant तथा principal पहचानें।
2. देखें कि principal के पास इस mutation की authority है या नहीं।
3. Target को tenant boundary के भीतर load करें, global रूप से load करके बाद में check न करें।
4. Record state और business rule के लिए जरूरी role relationship जांचें।
5. Conditional write करें और उसी transaction में audit event जोड़ें।

Tenant boundary के भीतर loading महत्वपूर्ण है। अगर resolver tenancy verify करने से पहले `findProjectById(id)` call करता है, तो timing या error wording से existence leak हो सकती है। वह globally loaded object ऐसे helper को भी दे सकता है जो मानता है कि authorization पहले ही हो चुकी है। Tenant membership को lookup predicate का हिस्सा बनाएं।

Permission का अनुमान agent के claimed task से न लगाएं। `X-Agent-Goal: cleanup` बताने वाला request header audit trail के लिए evidence है, authority देने का साधन नहीं। Prompts, task labels और model identity human review में मदद कर सकते हैं, लेकिन कोई भी client इन्हें forge कर सकता है।

यही अंतर tool access पर भी लागू होता है। Agent को GraphQL endpoint call करने की permission हो सकती है, लेकिन किसी खास mutation की नहीं। जहां agent runtime अनुमति दे, read tools और action tools का अलग वर्णन करें। अंतिम निर्णय API में रखें, क्योंकि client tool metadata को bypass करके HTTP request खुद भेज सकता है।

## Dry run को execution जैसी ही plan बनानी चाहिए

Dry run तभी उपयोगी है जब वह बताए, «यह exact request अभी क्या करेगी?» अगर fake preview simplified query से rows गिनता है, तो agent को सुरक्षा का झूठा भरोसा मिलेगा। बाद का mutation अलग eligibility rules, अलग authorization branch या ऐसा external action अपना सकता है जिसे preview ने देखा ही नहीं।

Shared planning function बनाएं। यह authenticated actor और input ले, हर condition validate करे, targets resolve करे, side effects निकाले और immutable plan बनाए। Preview उस plan का sanitized रूप लौटाए। Execution path caller के short-lived token और confirmation देने के बाद ही plan का इस्तेमाल करे।

```graphql
type Mutation {
  previewArchiveProject(input: PreviewArchiveProjectInput!): ArchivePreviewPayload!
  archiveProject(input: ArchiveProjectInput!): ArchiveProjectPayload!
}

input PreviewArchiveProjectInput {
  projectId: ID!
  expectedVersion: Int!
  reason: ArchiveReason!
}

type ArchivePreviewPayload {
  previewToken: ID!
  project: Project!
  affectedMemberCount: Int!
  plannedEffects: [ArchiveEffect!]!
  expiresAt: DateTime!
}

enum ArchiveEffect {
  PROJECT_HIDDEN_FROM_DEFAULT_LISTS
  PENDING_INVITATIONS_CANCELLED
}
```

Plan में target IDs, उनके versions, actor identity, tenant, input digest, planned effects और expiry time होना चाहिए। इसे server-side store करें या ऐसा opaque token sign करें जो stored state का reference हो। पूरा plan client-controlled JSON blob में रखकर execution के समय उस पर भरोसा न करें।

Execution input को loose selector दोहराने के बजाय preview token का reference देना चाहिए:

```graphql
input ArchiveProjectInput {
  previewToken: ID!
  confirmation: String!
  idempotencyKey: String!
}
```

यह दो-call flow अतिरिक्त friction पैदा करता है। Consequential actions के लिए यही उद्देश्य है। हर mutation पर इसे लागू न करें। सरल नियम है: जब operation एक से अधिक records को प्रभावित करे, irreversible external effect रखता हो या resource को अन्य users के लिए unavailable बनाता हो, तब preview जरूरी करें।

Preview responses को भी access control चाहिए। Affected records की list लौटाना data leak कर सकता है, ठीक वैसे ही जैसे mutation execute करना। Planning के दौरान वही tenant और role rules लागू करें। Preview में actor की पहुंच से बाहर fields हटाई जा सकती हैं, लेकिन निर्णय के लिए जरूरी count और effect categories रखी जा सकती हैं।

## Idempotency और versions अलग failures हल करते हैं

Idempotency एक ही request को दोबारा लागू होने से रोकती है। Version check उस state पर action होने से रोकता है जो caller के देखने के बाद बदल गई हो। Teams अक्सर एक जोड़कर मान लेती हैं कि दोनों समस्याएं हल हो गईं।

HTTP connection server के mutation commit करने के बाद बंद हो जाए तो agent retry कर सकता है। Idempotency के बिना दूसरा request दूसरा refund बना सकता है, message दोहरा सकता है या उसी external API को दो बार call कर सकता है। हर effectful mutation में caller से मिला `idempotencyKey` रखें। Server को इसे authenticated actor, mutation name, normalized input digest और completed payload या stable error के साथ store करना चाहिए।

जब server को वही actor, mutation, key और input digest दोबारा दिखे, तो original result लौटाएं। वही key अलग digest के साथ दिखे तो `IDEMPOTENCY_KEY_REUSED` लौटाएं और कुछ न करें। Reused key पर बदला हुआ input स्वीकार करने से retries के दौरान clients जिस property पर निर्भर करते हैं, वह खत्म हो जाती है।

Version check अलग sequence को संभालता है। Agent project version 7 पढ़ता है, archive preview बनाता है और कोई human project rename कर देता है या invitation restore कर देता है। Archive execute होने पर resolver version 7 की तुलना current stored version से करता है। अगर अब value 8 है, तो conflict लौटता है। Agent को current state फिर से पढ़कर intent पर विचार करना होगा और जरूरत हो तो नया preview बनाना होगा।

GraphQL specification एक mutation operation में top-level fields को serially execute करती है। इससे अलग HTTP requests serial नहीं होतीं। दो agents लगभग एक ही समय पर दो mutation operations भेज सकते हैं। Database conditional update, row lock या transactional constraint का इस्तेमाल करें। Application memory में check करके अलग write करने से race window बनी रहती है।

SQL जैसी conditional update intended invariant को स्पष्ट करती है:

```sql
UPDATE projects
SET archived_at = CURRENT_TIMESTAMP,
    version = version + 1
WHERE id = :project_id
  AND tenant_id = :tenant_id
  AND version = :expected_version
  AND archived_at IS NULL;
```

अगर इससे zero rows प्रभावित हों, तो tenant boundary के भीतर current record देखें और specific outcome लौटाएं: missing, forbidden, already archived या version conflict। हर zero-row result को generic server error न बताएं। Agent को पता होना चाहिए कि retry करना नुकसानदेह, उपयोगी या बेकार है।

## Error payload agent को अगला कदम बताए

Parse errors, validation failures और resolver failures के लिए GraphQL का top-level `errors` array सही है। लेकिन business outcomes के लिए clients को English messages scrape करने पर मजबूर करना ठीक नहीं। Expected mutation outcomes को stable code और structured details वाले typed payload में रखें।

```graphql
type ArchiveProjectPayload {
  outcome: ArchiveProjectOutcome!
  project: Project
  error: MutationError
}

enum ArchiveProjectOutcome {
  ARCHIVED
  VERSION_CONFLICT
  CONFIRMATION_REQUIRED
  PREVIEW_EXPIRED
  FORBIDDEN
  IDEMPOTENCY_KEY_REUSED
}

type MutationError {
  code: String!
  message: String!
  currentVersion: Int
  requiredConfirmation: String
}
```

Transport और GraphQL execution errors का इस्तेमाल उन स्थितियों के लिए करें जहां client operation को सही तरीके से execute ही नहीं कर सका। जिस request ने सामान्य रूप से execute होकर भी business rule के कारण state नहीं बदली, उसके लिए typed outcome रखें। एक convention चुनें और उसे document करें। कुछ conflicts के लिए `errors.extensions.code` और कुछ के लिए payload enums मिलाने से agent behavior fragile हो जाता है।

Agent को outcome को safe action से जोड़ पाना चाहिए। `VERSION_CONFLICT` का अर्थ है object फिर से पढ़कर intent पर विचार करना। `PREVIEW_EXPIRED` का अर्थ है नया preview बनाना। `CONFIRMATION_REQUIRED` में required phrase human को दिखाएं या उससे मांगें, अनुमान न लगाएं। `FORBIDDEN` का अर्थ है रुक जाना। `IDEMPOTENCY_KEY_REUSED` में नया key तभी बनाएं जब caller स्पष्ट रूप से अलग operation चाहता हो।

Internal policy names, SQL fragments या authorization graph details न लौटाएं। Stable external codes implementation internals उजागर किए बिना precise हो सकते हैं। Response extensions में correlation ID रखें और server पर matching audit record बनाएं। Agent failure report करे तो operator के पास जांच के लिए ठोस जानकारी होनी चाहिए।

Success payload में uncertainty खत्म करने के लिए पर्याप्त जानकारी दें। Resulting record state, नया version, operation ID और वास्तव में हुए effects लौटाएं। Bare Boolean client को फिर query करने पर मजबूर करता है और stale read की गुंजाइश छोड़ देता है। इससे human review भी अनावश्यक रूप से कठिन होता है।

## Deletion को Boolean switch नहीं, lifecycle चाहिए

Hard deletion लोकप्रिय है क्योंकि इससे table साफ-सुथरी दिखती है। लेकिन agent request गलत समझ ले तो यही action unrecoverable support problem पैदा करने की सबसे ज्यादा संभावना रखता है। कई products में पहले archive करना, server के नियंत्रण में undo period रखना और permanent purge को अलग restricted workflow से करना बेहतर है।

अगर archive operation केवल record छिपाता है, तो उसे `deleteProject` न कहें। Names clients को बताते हैं कि वे किस state की अपेक्षा कर सकते हैं। `archiveProject` को `ARCHIVED` लौटाना चाहिए; `purgeProject` का अर्थ होना चाहिए कि data आगे उपलब्ध नहीं रहेगा। जब API हर lifecycle stage के लिए delete इस्तेमाल करती है, agent reversible cleanup और permanent removal में भरोसेमंद अंतर नहीं कर सकता।

Permanent purge के input और authorization archive से अधिक सख्त होने चाहिए। Resource को retention period तक archived रहना पड़ सकता है, कोई legal या billing hold नहीं होना चाहिए और अलग scope वाले operator की मंज़ूरी जरूरी हो सकती है। Resolver को हर condition लागू करनी होगी। Client-side countdown या tool instruction की कोई authority नहीं है।

External side effects पर भी यही नियम लागू होता है। अगर archiving invitations cancel करती है, remote environment हटाती है या webhook trigger करती है, तो preview और final payload में इन effects को लौटाएं। इन्हें generic update resolver से चुपचाप न जोड़ें। Agent request की समीक्षा करने वाले व्यक्ति को approval से पहले consequences दिखने चाहिए, और execution के बाद agent के पास report करने के लिए facts होने चाहिए।

Financial या credential actions के लिए ऐसा pretend dry run न दें जो provider के live endpoint को call करे और बिना effect की उम्मीद रखे। जहां provider का documented preview या authorization mechanism हो, उसका इस्तेमाल करें। अन्यथा result को local estimate बताएं और लिखें कि server क्या verify नहीं कर सका। झूठी certainty, human decision मांगने से बदतर है।

## Resolver checks schema के promises को वास्तविक बनाते हैं

Schema malformed intent को सीमित करता है। Resolver authorized दिखने वाली request को वास्तविक boundary पार करने से रोकता है। इन layers को code में अलग रखें, ताकि future refactor permission check को tool definition की comment से replace न कर दे।

Destructive action के resolver का क्रम ऐसा होना चाहिए जिसमें denial सस्ता और writes देर से हों। पहले request authenticate करें, फिर actor का tenant resolve करें, input validate करें, target को उसी tenant में load करें, scope और state verify करें, preview token और confirmation validate करें, idempotency record claim करें और conditional transaction execute करें। Storage model के अनुसार क्रम बदल सकता है, लेकिन transaction commit कर सकने का भरोसा होने से पहले external effect न करें।

जब काम database और external provider दोनों में फैला हो, idempotency record को सावधानी से संभालें। External call से पहले key को complete mark करने पर call fail होने के बावजूद success claim हो सकती है। Provider को पहले call करने पर completion store करने से पहले process मर जाए तो duplication हो सकती है। जहां उपलब्ध हो, outbox pattern या provider-side idempotency support अपनाएं। Durable pending operation record करें, local decision commit करें और ऐसे operation ID से external effect dispatch करें जो retries में भी बना रहे।

Audit records में authenticated principal, जरूरत होने पर agent run, mutation name, normalized target, input digest, authorization result, preview reference, idempotency key, outcome और resulting version दर्ज करें। Retention rules के अनुसार sensitive content वाले notes और fields redact करें। केवल «mutation succeeded» लिखने वाला audit event incident के दौरान लगभग बेकार होता है।

Credentialed HTTP या SSH calls से काम करने वाले agents के credentials को जहां संभव हो model process से बाहर रखें। Sallyport supported actions को अपने local vault से route करता है और individual calls दर्ज करता है। यह तब उपयोगी है जब GraphQL mutation को bearer token से मिलने वाली authority से अलग human-visible authorization चाहिए।

## केवल resolver नहीं, generated request को test करें

Carefully constructed objects के साथ resolver call करने वाले unit tests उस failure mode को नहीं पकड़ते जिसकी आपको चिंता है। Generated clients omitted fields, nulls, stale IDs, aliases, repeated requests और पिछले tool output से बनाए गए variables भेजते हैं। Public GraphQL boundary को इन्हीं shapes के साथ test करें।

Code branches के बजाय behavior के आधार पर mutation test matrix बनाएं। कम से कम दूसरे tenant का caller, read access लेकिन action scope न रखने वाला caller, expired preview, बदला हुआ target version, गलत confirmation string, replayed idempotency key और same expected version वाले दो concurrent calls test करें। हर test में response और उसके बाद durable state दोनों assert करें।

यह request GraphQL validation में fail होनी चाहिए, क्योंकि input `ownerId` define नहीं करता:

```graphql
mutation BadArchive($input: ArchiveProjectInput!) {
  archiveProject(input: $input) {
    outcome
  }
}
```

```json
{
  "input": {
    "projectId": "prj_42",
    "expectedVersion": 7,
    "reason": "DUPLICATE",
    "confirmation": "archive prj_42",
    "idempotencyKey": "run-18-archive-42",
    "ownerId": "usr_9"
  }
}
```

Expected response top-level GraphQL error format में होना चाहिए, क्योंकि document ने invalid input object दिया है। यह test साबित करता है कि schema unrelated capability को mutation से बाहर रखता है। अलग test से यह भी साबित करें कि well-formed archive request दूसरे tenant के actor के लिए फिर भी fail होती है।

Concurrency tests real transaction behavior पर चलाएं, in-memory fake पर नहीं। Same ID और expected version के साथ दो archive requests भेजें, फिर assert करें कि एक `ARCHIVED` लौटाती है और दूसरी conflict या idempotent replay outcome। अगर दोनों calls अलग operation IDs के साथ success बताती हैं, तो conditional write अपना काम नहीं कर रही।

Audit verification को भी test करें। अगर आपका action gateway tamper-evident encrypted audit trail बनाता है, तो verification को incident drills का हिस्सा बनाएं, ऐसा command नहीं जिसे किसी ने कभी इस्तेमाल न किया हो। Sallyport vault key के बिना hash chain की offline verification के लिए `sp audit verify` देता है। Copied journal पर इसे चलाएं और सुनिश्चित करें कि operators failed check का अर्थ समझते हैं।

## Generated tools को कम choices चाहिए, लंबी warnings नहीं

जब tool schema task के अनुरूप सबसे छोटा safe action दिखाता है, agents बेहतर काम करते हैं। Generic filters, flags और optional side effects वाली बड़ी mutation catalog model को field names से policy का अनुमान लगाने पर मजबूर करती है। Explicit read, preview, execute और recovery operations की compact catalog उसे अनुसरण करने योग्य रास्ता देती है।

ऐसे read operations दें जो agent को mutation प्रस्तावित करने से पहले जरूरी identifiers, versions, status और names लौटाएं। अगर agent को human label से ID का अनुमान लगाना पड़े, तो mutation design आपको नहीं बचा सकता। Stable IDs स्पष्ट रूप से लौटाएं और ambiguous search results को साफ़ दिखाएं, चुपचाप कोई एक result न चुनें।

Tool descriptions में precondition और consequence बताएं, लेकिन enforcement server पर रखें। उदाहरण के लिए: «सफल preview के बाद एक project archive करता है। Preview में सूचीबद्ध pending invitations cancel करता है।» यह «सावधानी से इस्तेमाल करें» से बेहतर है, क्योंकि वह agent को operational जानकारी नहीं देता।

हर risk का समाधान human approval dialog जोड़ना नहीं है। जहां decision व्यक्ति का हो, वहां approval उचित है, लेकिन repetitive prompts background noise बन जाते हैं। Routine protection को scopes, tenant checks, versions और idempotency में रखें। व्यक्ति से उन थोड़े actions की समीक्षा कराएं जिनका intent data से समझना संभव नहीं, जिनका परिणाम permanent है या जो organizational boundary पार करते हैं।

उस mutation से शुरुआत करें जिसे agent दो बार call करे, stale state पर call करे या गलत tenant पर call करे तो सबसे ज्यादा नुकसान होगा। उसके input को अलग करें, जहां उचित हो real preview जोड़ें, write को conditional बनाएं और replay test लिखें। इससे पता चल जाएगा कि आपका API action को स्पष्ट रूप से model करता है या केवल database fields expose करता है।
