Conformance

Agent-Safe Intent conformance

An implementation conforms when it reproduces the canonical bytes and the intent hash of every vector in the corpus, and hashes every mutation of a binding differently from the binding and from each other. Run the corpus offline with the AgentSafe runtime.

Corpus
v0.5.0 (e1e702f)
Checked
2026-10-29

Run it

With AgentSafe installed, from a clone of the repository at the corpus release. Exit 0 means every pinned hash was reproduced; 1 names the vector that was not, with the expected and the computed value.

git clone --depth 1 --branch v0.5.0 https://github.com/decionis/agent-safe-pipeline
cd agent-safe-pipeline
agentsafe verify intent conformance/vectors conformance/agent-safe-intent-v1.json
agentsafe verify intent conformance/frameworks

Or with the independent verifier, no AgentSafe install

npx @decionis/verify intent conformance/vectors conformance/agent-safe-intent-v1.json conformance/frameworks

@decionis/verify implements the specification with no code shared with the runtime, so the two agreeing is what shows the corpus pins the specification rather than one implementation of it.

A conformance claim names the corpus it was made against, because the corpus grows. For this page that is v0.5.0 (e1e702f). Passing says nothing about an authority's decisions or a runtime's enforcement; the boundary test covers enforcement.

Framework coverage

What each framework's tool call already carries

A description, not a grade: none of these frameworks was designed to carry execution authority, and none has to change for a producer built on it to conform. Each column is one vector, the same kind of consequential call captured from that framework's own record.

CapabilityOpenAI Responses API / Agents SDKVercel AI SDKLangChainMCP (tools/call)
action_identityaction.typeIs the action named by a stable identifier the policy can refer to?nativenativenativenative
parametersaction.parametersAre the arguments a JSON object that canonicalizes as they are, or text to be parsed?adapternativenativenative
target_identityaction.resourceIs what is acted on named apart from the parameters?adapteradapteradapteradapter
principalactorIs the acting identity carried with the action, by the runtime and not by the agent?adapteradapteradapteradapter
expiryexpires_atIs the intent bounded in time?adapteradapteradapteradapter
idempotencycontext.idempotency_keyDoes a retry present the same intent and a new attempt a new one, by a runtime-chosen key?adapteradapteradapteradapter
intent_digestthe hashIs there one digest over the whole of the above that authority can be bound to?adapteradapteradapteradapter
effect_correlationthe resultCan the effect that followed be tied to exactly this call?nativenativenativenative
native
the framework's own tool-call record carries it
adapter
the trusted runtime adds it when it captures the call
not represented
neither the record nor the adapter carries it
not tested
no vector covers it yet

The capabilities and their questions are §8 of the specification. CrewAI, AutoGen, Mastra and others have no vector yet; adding one is a pull request under conformance/frameworks/.

OpenAI Responses API / Agents SDK

An OpenAI Responses API function_call item, the record a function tool produces (the OpenAI Agents SDK's function tools produce the same item). The arguments arrive as a JSON-encoded string; everything but the action name and the arguments is the trusted runtime's to add.

parameters
arguments is a JSON string; the adapter parses it into the object that is hashed
effect_correlation
function_call_output echoes call_id
idempotency
call_id identifies the model's call, not a retry boundary; the runtime chooses the key

The framework's record

{
  "id": "fc_synthetic_1",
  "call_id": "call_synthetic_1",
  "type": "function_call",
  "name": "refund_order",
  "arguments": "{\"orderId\":\"synthetic-1\",\"amountMinor\":35000,\"currency\":\"USD\"}"
}

Intent hash

sha256:fbb961ed4a8e5a8b39ff92328c6573b9e688a4aaa9069ab5aaf5bd6ea5b08fea

The vector

Vercel AI SDK

A Vercel AI SDK tool-call part, as generateText and streamText return it. The input is already an object; everything but the tool name and the input is the trusted runtime's to add.

effect_correlation
the tool-result part carries the same toolCallId
idempotency
toolCallId identifies the model's call, not a retry boundary; the runtime chooses the key

The framework's record

{
  "type": "tool-call",
  "toolCallId": "call_synthetic_2",
  "toolName": "refund_order",
  "input": {
    "orderId": "synthetic-1",
    "amountMinor": 35000,
    "currency": "USD"
  }
}

Intent hash

sha256:147eb95792bdc1be431e3512c607c8def3dcdec1055c4c8e817c49540de7228f

The vector

LangChain

A LangChain ToolCall, as an AIMessage carries it in tool_calls (LangGraph agents pass the same object to a ToolNode). The args are already an object and the id is optional; everything but the name and the args is the trusted runtime's to add.

effect_correlation
the ToolMessage carries the same id as tool_call_id; the id is optional on the call
idempotency
id identifies the model's call, not a retry boundary; the runtime chooses the key

The framework's record

{
  "type": "tool_call",
  "id": "call_synthetic_3",
  "name": "refund_order",
  "args": {
    "orderId": "synthetic-1",
    "amountMinor": 35000,
    "currency": "USD"
  }
}

Intent hash

sha256:72e34c9754c82b3cfa820410a470874f7a364dd113cf3be82ed829aebccb8673

The vector

MCP (tools/call)

An MCP tools/call request, at the moment the model has chosen a tool and constructed its arguments. Unlike the other three frameworks, the record-to-proposal mapping is not written by the integration: @decionis/agentsafe ships it as bindMcpInvocation, and the tool binding below is what an operator declares.

MCP names the tool and carries its arguments as an object, so the action and the parameters are native and the JSON-RPC id is the correlation the result echoes. What MCP has no place for is what makes an invocation authorizable: which resource the action lands on, who proposed it, when the proposal stops being current, and the key an effect is bound to. The shipped binder derives the target from the arguments rather than accepting one, and the trusted runtime supplies the rest.

The framework's record

{
  "jsonrpc": "2.0",
  "id": "call_synthetic_1",
  "method": "tools/call",
  "params": {
    "name": "delete_customer",
    "arguments": {
      "customerId": "synthetic-1"
    }
  }
}

Intent hash

sha256:b275810adef3e8f9192430270f15e294f1fe41dbea789c916e89d6726b521b30

The vector

The corpus

Hash vectors

Each vector pins a JSON value, its canonical bytes and its hash. The first is a whole binding; the canonicalization cases each isolate one rule of the canonical form (§4); the adversarial vector holds §6 to account.

agent-safe-intent-v1Cross-implementation hash vector for agent-safe.intent/1sha256:aff2fd3aa1a7557a77f20afa5583f56eb0c39529304b31f69ee78e1e7b53e316
composed-vs-decomposedNFC (café) and NFD (café) are intentionally DISTINCT — no implicit Unicode normalizationsha256:4783c9bb9e92da817ff30c7f0e10dac952b0b1893e654cb006a067aa4f8d7595
fractional-exponentFractional and exponent-form numbers use JS JSON encoding (1e+21)sha256:2bf2414e38ed0efe498fa735f624fd758adf2a647b1a14b1208e0734976849f8
negative-zeroNegative zero canonicalizes to 0 per JS JSON encodingsha256:e6a312b1d452aa705e11d045f19ba9f7097fbbc3a7ef956573c98e6d81203b6a
nested-arraysNested arrays canonicalize depth-firstsha256:7e576e1ac9e486dccda1d68df4baf27b2ed22f8d23a39e63748bafe2e4e76e26
policy-projection-absentThe same intent as policy-projection.json with no policy projection: the binding, bytes and hash an intent without one has always produced (it is byte-identical to @decionis/agent-safe-pipeline 0.4.0's), and the authority evaluates its action type as the decision type, transaction type and workflow key.sha256:bbd2b45a06920fe1a0caf82a51bd2d70394114ed8f687bec1277784537e21717
unicode-astralUnicode escapes and astral characters (emoji) round-trip verbatim — no normalization appliedsha256:59910e9010a014d9723172c03d890d7527fb7797110d8efa1772a5657437a5e6
utf16-sort-orderKeys sort by UTF-16 code units: 'Z'(0x5A) < 'za'(0x7A) < 'zé'(0x7A,0xE9) < 'Éclair'(0xC9)sha256:87fb08703bfcfc2249c4aa2ed144c53b900de74f7bedb432ea690e0b5388f3fa

compromised-principal

The Compromised Principal Test as a hash vector: an infrastructure agent's deployment.scale intent, and eight single-field mutations a valid principal could make after authorization. Every mutation MUST hash differently from the base and from every other, so that a grant for the base authorises none of them.

  1. The authorized bindingsha256:c1124c3c6fa4ace58edb2e053017d4d0413df0816c254d3ef3902a8104480826
  2. action.parameters.replicas the replica count, raised after authorization: the mutation the demo is named forsha256:75cc1a9ae4444e54c565fc3e7f93007154c94329d7d4618882b6d72545285b27
  3. action.parameters.service the service: a deployment outside the principal's remitsha256:5b00674723b6a6235f690340ad05a160dd1f166792998496f15324743baa4127
  4. action.parameters.cluster the cluster: another team's, with the resource left as it wassha256:8022f9b0ff144e469a3245732d15710d6c7c2305c4a7f89fc7926fdc209e79f0
  5. action.resource the resource the action names, with the parameters left as they weresha256:e137482d371ae3cbb5ab7796a639872ea8a17ca83d85428e5a851132bc9616c6
  6. actor.id the principal: the same request from a stronger identity is another intent, not a stronger claim to this onesha256:295c59bd21addea4e25f603bf8fd2625ff2c64ffdf7340d566703aae5fbe0e44
  7. expires_at the expiry: an hour more is another intentsha256:be36e7144f85751721c554084dd12b524703eb60b3cef89c56722853d290f8f3
  8. context.idempotency_key the idempotency key the trusted runtime chose: a retry is not a re-authorizationsha256:e43d6e5fcb2e4cfb73cf6c0490fab02a76b6989eafb17d8ea435114faed36cf7
  9. downstream_target.environment the environment the runtime binds: production and staging are different intents with the same parameterssha256:6a73fb0f7071c6d104e58b9283c9fbaae9a4d6f4c41299eaa17c11f99519d015

9 hashes, all different: a grant for the authorized binding covers none of the mutations.

policy-projection

A binding that states its policy projection: a Walmart order acknowledgement the authority decides as an ORDER_ACCEPTANCE, not under its action type. The projection is inside the hash, so a grant decided on one policy path authorises no other. Every mutation MUST hash differently from the base and from every other; the last, the projection removed, is exactly policy-projection-absent.json.

  1. The authorized bindingsha256:adda2a7a9483e4b357579ee06f415e2bb73e3c35492084ea7f2adee1f0568706
  2. policy_projection.decision_type the decision type: the release of a held order, which policy sends to a person to decidesha256:9da2d7ad7e97299f2407bf7e98dfd463dd755c0fe6f62f8f89723f9be7c9608d
  3. policy_projection.transaction_type the transaction type left out of the evaluation, where the base names onesha256:eba00ff4e76705a832807ec25574789681af41d3769ebb9158e499618a716068
  4. policy_projection.workflow_key the workflow key: a broader workflow than the one the grant was decided undersha256:bfbfe472c032e066d20d8413b9f8a33805da5359da893fa01b0bc99a6fb2f44a
  5. policy_projection.channel the channel left out of the evaluation, where the base names onesha256:e8e82e73a4587e6292ca396d991e92d526b59e7ba2ea569d1e87214a74dc9729
  6. policy_projection the projection removed: the authority would evaluate the action type insteadsha256:bbd2b45a06920fe1a0caf82a51bd2d70394114ed8f687bec1277784537e21717

6 hashes, all different: a grant for the authorized binding covers none of the mutations.

target-precondition

A binding that states the target's state as the runtime read it before asking: a Business Central customer about to be blocked, read at its @odata.etag with blocked empty. The precondition is inside the hash, under the reserved context key agent_safe, so a grant decided against one version of the target authorises a write against no other. Every mutation MUST hash differently from the base and from every other; the last, the precondition removed, is the same intent captured with none, which carries no agent_safe key at all.

  1. The authorized bindingsha256:b0284a98f9a0dfebbc6dfb6db6cdf1d1744f6994e24dd61b568ffec68dbce67e
  2. context.agent_safe.precondition.version another version of the customer: someone else wrote to the record after it was readsha256:e1da0941b50bae23ebdbc436752a6630503de6304e1fdca153718196989c249a
  3. context.agent_safe.precondition.state another state read: the customer was already blocked for invoicing, a different transitionsha256:876f6744d8b9915ba3e8833452d5c8c07365478ca8238799e76064d7b31a11cd
  4. context.agent_safe.precondition.read_at another read of the same version: when it was read is inside the hash toosha256:53e2547110aecfeda0ea2f7734083613d89b20b04e51f67d62316582d9b7d628
  5. context.agent_safe the precondition removed: the intent an executor that binds no target state capturessha256:d0856e043f28eda20dda0a8fd985cf4f0658fabcb4f440c2ff7c9791f6609fdf

5 hashes, all different: a grant for the authorized binding covers none of the mutations.

Beyond the binding

The other suites in the repository

Verifying providers

Vectors for a downstream service that checks, on its own side, that a request carries a valid execution grant for exactly that request before it acts.

The profileconformance/provider

Runtime

The same governed requests run against each way AgentSafe is deployed, to show that every packaging enforces the boundary the same way.

conformance/runtime