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.
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.3.5 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/frameworksA conformance claim names the corpus it was made against, because the corpus grows. For this page that is v0.3.5 (b4d5c9e). Passing says nothing about an authority's decisions or a runtime's enforcement; the boundary test covers enforcement.
Framework coverage
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.
| Capability | Binding property | OpenAI Responses API / Agents SDK | Vercel AI SDK | LangChain | MCP (tools/call) |
|---|---|---|---|---|---|
action_identityaction.typeIs the action named by a stable identifier the policy can refer to? | action.type | native | native | native | native |
parametersaction.parametersAre the arguments a JSON object that canonicalizes as they are, or text to be parsed? | action.parameters | adapter | native | native | native |
target_identityaction.resourceIs what is acted on named apart from the parameters? | action.resource | adapter | adapter | adapter | adapter |
principalactorIs the acting identity carried with the action, by the runtime and not by the agent? | actor | adapter | adapter | adapter | adapter |
expiryexpires_atIs the intent bounded in time? | expires_at | adapter | adapter | adapter | adapter |
idempotencycontext.idempotency_keyDoes a retry present the same intent and a new attempt a new one, by a runtime-chosen key? | context.idempotency_key | adapter | adapter | adapter | adapter |
intent_digestthe hashIs there one digest over the whole of the above that authority can be bound to? | the hash | adapter | adapter | adapter | adapter |
effect_correlationthe resultCan the effect that followed be tied to exactly this call? | the result | native | native | native | native |
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/.
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.
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
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.
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
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.
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
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 corpus
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.
| Vector | What it pins | Intent hash |
|---|---|---|
| agent-safe-intent-v1 | Cross-implementation hash vector for agent-safe.intent/1 | sha256:aff2fd3aa1a7557a77f20afa5583f56eb0c39529304b31f69ee78e1e7b53e316 |
| composed-vs-decomposed | NFC (café) and NFD (café) are intentionally DISTINCT — no implicit Unicode normalization | sha256:4783c9bb9e92da817ff30c7f0e10dac952b0b1893e654cb006a067aa4f8d7595 |
| fractional-exponent | Fractional and exponent-form numbers use JS JSON encoding (1e+21) | sha256:2bf2414e38ed0efe498fa735f624fd758adf2a647b1a14b1208e0734976849f8 |
| negative-zero | Negative zero canonicalizes to 0 per JS JSON encoding | sha256:e6a312b1d452aa705e11d045f19ba9f7097fbbc3a7ef956573c98e6d81203b6a |
| nested-arrays | Nested arrays canonicalize depth-first | sha256:7e576e1ac9e486dccda1d68df4baf27b2ed22f8d23a39e63748bafe2e4e76e26 |
| unicode-astral | Unicode escapes and astral characters (emoji) round-trip verbatim — no normalization applied | sha256:59910e9010a014d9723172c03d890d7527fb7797110d8efa1772a5657437a5e6 |
| utf16-sort-order | Keys sort by UTF-16 code units: 'Z'(0x5A) < 'za'(0x7A) < 'zé'(0x7A,0xE9) < 'Éclair'(0xC9) | sha256:87fb08703bfcfc2249c4aa2ed144c53b900de74f7bedb432ea690e0b5388f3fa |
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.
sha256:c1124c3c6fa4ace58edb2e053017d4d0413df0816c254d3ef3902a8104480826action.parameters.replicas the replica count, raised after authorization: the mutation the demo is named forsha256:75cc1a9ae4444e54c565fc3e7f93007154c94329d7d4618882b6d72545285b27action.parameters.service the service: a deployment outside the principal's remitsha256:5b00674723b6a6235f690340ad05a160dd1f166792998496f15324743baa4127action.parameters.cluster the cluster: another team's, with the resource left as it wassha256:8022f9b0ff144e469a3245732d15710d6c7c2305c4a7f89fc7926fdc209e79f0action.resource the resource the action names, with the parameters left as they weresha256:e137482d371ae3cbb5ab7796a639872ea8a17ca83d85428e5a851132bc9616c6actor.id the principal: the same request from a stronger identity is another intent, not a stronger claim to this onesha256:295c59bd21addea4e25f603bf8fd2625ff2c64ffdf7340d566703aae5fbe0e44expires_at the expiry: an hour more is another intentsha256:be36e7144f85751721c554084dd12b524703eb60b3cef89c56722853d290f8f3context.idempotency_key the idempotency key the trusted runtime chose: a retry is not a re-authorizationsha256:e43d6e5fcb2e4cfb73cf6c0490fab02a76b6989eafb17d8ea435114faed36cf7downstream_target.environment the environment the runtime binds: production and staging are different intents with the same parameterssha256:6a73fb0f7071c6d104e58b9283c9fbaae9a4d6f4c41299eaa17c11f99519d0159 hashes, all different: a grant for the authorized binding covers none of the mutations.
Beyond the binding
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 same governed requests run against each way AgentSafe is deployed, to show that every packaging enforces the boundary the same way.