Cross-Provider Policy
Govern model-selected calls with one policy. Run two providers, tighten a write allowance, and inspect the resulting effects and signed receipts.
On this page
Run two models against the same application
A model can propose three writes. Chio decides how many reach your application. Both OpenAI Chat Completions and Anthropic Messages use the same notes host, tool schemas and role policy. At a limit of two, two notes are saved and the third call is refused. Tighten the limit to one and the host dispatches only one write.
Download the complete application. Extract it anywhere, install Rust and uv, and configure your funded provider accounts as described in the included README. Then run:
python3 compare.py --openai-model "$OPENAI_MODEL" --anthropic-model "$ANTHROPIC_MODEL"The command makes four real model runs. It builds and starts the included Chio host, requests three unique notes per run, and verifies the saved results. Your original policy file and existing notes remain intact. Complete model responses, policy snapshots, MCP exchanges and signed receipts stay in the project.
See the policy change the result
These captures used OpenAI and Anthropic models through a funded OpenRouter account. All 16 tool calls have distinct signed receipts. Select a provider and allowance, then inspect any call. The saved note list was checked independently against each write result.
2 saved1 refused4 signed receipts verified
These controls select actual captured runs through a funded provider endpoint. They do not make a model request. The model is openai/gpt-4.1-mini.
createNote Completed
{
"body": {
"text": "openai-2-c0c36d57 alpha"
}
}- Request
- mcp-edge-req-7f6384cca6834111afb65a2e68c00f21
- Receipt
- 03a3f98130bfde5578d26e5a129db8e31b4b1be77ee221332ade54693238f177
- Operator-selected signer
- 0fde22e00547adb7a8efda9ea2b0289192dc38a86e65019c4d08f683c5816efa
Signature, content hashes and trusted signer verified. Request and tool arguments matched independently.
Complete signed receipt
{
"id": "03a3f98130bfde5578d26e5a129db8e31b4b1be77ee221332ade54693238f177",
"timestamp": 1789081255,
"capability_id": "cap-01a08d8d-746a-7270-ac83-c932783a9c1f",
"tool_server": "notes",
"tool_name": "createNote",
"action": {
"parameters": {
"body": {
"text": "openai-2-c0c36d57 alpha"
}
},
"parameter_hash": "6032f7a85f5ba975da92cb2e5add3cfe1fb35743a3e7f860fdd9d0112d55226e"
},
"decision": {
"verdict": "allow"
},
"receipt_kind": "mediated_decision",
"boundary_class": "prevent",
"tool_origin": "caller_executed",
"redaction_mode": "none",
"content_hash": "9ece9592bf1b1c962b608d66ccc28b07842c5ca0ebc5852ed617dd9d1a8d5a02",
"policy_hash": "6b65b1da0151c482eddbbd24233e860312fe0510910a49143402f569b6f9b5d8",
"metadata": {
"admission_operation": {
"compensation_status": "not_compensated",
"coordinator_lease_epoch": 1,
"coordinator_lease_id": "01a08d8d-7393-71ea-a4ee-9f00ddf63c48",
"operation_id": "3d1e55a6fa3dd3c289631980d8f45e09b73457eeceb05ab8af22a56efd7ba116",
"projected_dispatch_state": "terminal",
"projected_operation_version": 8,
"projected_state": "completed",
"request_binding_hash": "3dee093d47c5efaa714d4fc4f315f1251979ddaa601ad0ff7b95ce7d19c98954",
"request_id": "mcp-edge-req-7f6384cca6834111afb65a2e68c00f21",
"request_namespace_digest": "9612780366ad05794d723e9324957370a6b14f67d933da155c49e321b233e90d",
"retained_dispatch_commit": {
"committed_version": 6,
"coordinator_lease_epoch": 1,
"coordinator_lease_id": "01a08d8d-7393-71ea-a4ee-9f00ddf63c48",
"provider_attempt": {
"attempt_id": "attempt:3d1e55a6fa3dd3c289631980d8f45e09b73457eeceb05ab8af22a56efd7ba116",
"operation_id": "3d1e55a6fa3dd3c289631980d8f45e09b73457eeceb05ab8af22a56efd7ba116",
"transport_id": "kernel-tool-server:notes",
"transport_key_epoch": 1
},
"store_fence": {
"lease_id": "01a08d8d-7393-71ea-a4ee-9f00ddf63c48",
"owner_epoch": 1,
"store_uuid": "01a08d8d-71a7-7108-bd50-f86722738054"
}
},
"schema": "chio.admission-receipt.v1",
"store_fence": {
"lease_id": "01a08d8d-7393-71ea-a4ee-9f00ddf63c48",
"owner_epoch": 1,
"store_uuid": "01a08d8d-71a7-7108-bd50-f86722738054"
},
"tool_outcome_id": "c6c4f396c3c63dca1806972e4613fe511655c365e93e797f753170dcca70cd83",
"tool_outcome_version": 2,
"trusted_time_unix_ms": 1789081255924
},
"attribution": {
"delegation_depth": 0,
"grant_index": 2,
"issuer_key": "0fde22e00547adb7a8efda9ea2b0289192dc38a86e65019c4d08f683c5816efa",
"subject_key": "1be559c30de81b4a9b22fb0b8a63ffd1637cf7b7e79dd913da0aea2118c5212d"
},
"chio_receipt_signing_nonce": "rcpt-01a08d8d-8032-717d-97d5-0e430c9a841a",
"receipt_context": {
"request_id": "mcp-edge-req-7f6384cca6834111afb65a2e68c00f21"
}
},
"trust_level": "mediated",
"kernel_key": "0fde22e00547adb7a8efda9ea2b0289192dc38a86e65019c4d08f683c5816efa",
"signature": "dc01d221d31ec6d11744597111bec93b31c518270c9cba7dd9a1960e9f43cc85a358f3d1037988eaea83b750d76091b83aa50349f5369463cbfd472cbd29730a"
}Make the rule yours
For your own run, edit the writer in notes-api/authority.json, then start either provider client. The host issues the new session capability from this configuration and binds the complete policy snapshot into its policy hash.
{
"writer": {
"allow_read": true,
"allow_write": true,
"max_writes": 1
},
"auditor": {
"allow_read": true,
"allow_write": false,
"max_writes": 0
}
}python3 run.py openai --model "$OPENAI_MODEL" "Attempt to save alpha, beta and gamma once each, then list the actual saved notes."The provider loop forwards full tool arguments to its existing MCP session. It does not decide admission, replenish the grant after a refusal, or treat the model's final text as proof of a write. The shared boundary is small:
result = session.call(call.function.name, arguments)
messages.append({
"role": "tool",
"tool_call_id": call.id,
"content": json.dumps(result),
})Receipts from different executions have different identities, timestamps and signatures. Compare their authorized operation, policy, verdict and actual effect. Byte equality would be the wrong acceptance test. Each original receipt remains available for independent verification.
Continue with the complete provider clients or a writer and auditor with separate framework authority.
Provider format conformance
The older eight-provider capture comparison is useful for translation regression checks. Its full source, commands and original anchors remain below. It checks recorded formats; the executable policy application above performs new kernel decisions and application effects.
Eight-provider conformance reference and complete records
One operation, eight provider formats
Chio separates a model provider’s tool-call syntax from the authority to execute an operation. That separation lets an agentic operating system govern tools across model runtimes. This project examines the translation layer: eight saved exchanges request get_weather for San Francisco in Celsius and carry an allow verdict.
Run it to compare those recorded fields and catch a fixture-contract mismatch. It is an offline verifier: the command makes no model request, performs no weather lookup, and signs no new kernel receipt. For an application you can connect to a model, start with the provider and LangChain project or the complete Rust host.
Run the comparison
Fetch the project and run it. You need Rust; rust-toolchain.toml selects the compiler and Cargo fetches the conformance crate with its recorded fixtures. You can also download the project.
chio new cross-provider-policy --example cross-provider-policy
cd cross-provider-policy
cargo run --locked --quiet -- --dry-runThe command prints eight comparison records, then this captured summary. A successful exit means their selected fields match the configured fixture contract and each other.
cross-provider verdict equality: 8 receipts validated for policy cross-provider-policy-demoDownload the complete captured stdout or all eight comparison records as JSON. The required --dry-run flag makes the command’s offline role explicit.
Follow the weather request
Read the example’s contract
The file below is the project's complete configuration. Its fixture_contract names the expected operation, exact arguments and recorded verdict. The example’s own parser reads this subset; it does not load the full HushSpec guard engine. Keep this verifier configuration separate from an application’s runtime policy.
hushspec: "0.1.0"
name: cross-provider-policy-demo
description: Dry-run policy proving equivalent tool verdicts across native provider adapters.
rules:
tool_access:
enabled: true
default: block
allow:
- get_weather
fixture_contract:
scenario_id: weather_lookup_allow
required_tool: get_weather
required_arguments:
location: "San Francisco, CA"
unit: celsius
expected_verdict: allowDistinguish the two input paths
| Provider captures | Work performed |
|---|---|
OpenAI, Anthropic, Bedrock | Replay the provider adapter: lift recorded requests, compare normalized invocations, and check lowered tool responses against the capture. The verdict comes from the saved record. |
Gemini, Mistral, Groq, Ollama, Cohere | Read the invocation and verdict directly from the saved NDJSON record. This example does not run their deep adapter replay paths. |
Inspect a complete comparison record
This is the first record from the executed command. Its receipt_id identifies a fixture record. The object has no kernel signature, signer key or content hash; use it for comparison, rather than as a signed execution receipt.
{
"receipt_id": "rcpt_openai_basic_single_tool_call_allow",
"body": {
"policy_id": "cross-provider-policy-demo",
"scenario_id": "weather_lookup_allow",
"tool_name": "get_weather",
"arguments": {
"location": "San Francisco, CA",
"unit": "celsius"
},
"verdict": {
"verdict": "allow",
"reason": null,
"redactions": []
},
"provenance": {
"provider": "open_ai",
"request_id": "call_weather_1",
"api_version": "responses.2026-04-25",
"principal": {
"kind": "open_ai_org",
"org_id": "org_chio_demo"
},
"received_at": "2026-04-25T00:00:01.150Z"
}
}
}Make a mismatch visible
Copy the contract so the original stays available:
cp policy.yaml comparison.yamlIn comparison.yaml, change only rules.fixture_contract.expected_verdict:
expected_verdict: denycargo run --locked --quiet -- \
--dry-run --policy comparison.yamlfixture rcpt_openai_basic_single_tool_call_allow verdict Allow did not match policy verdict DenyThe captured request still says allow. Changing the expected verdict makes the verifier reject that mismatch; it does not reevaluate the request and turn it into a denied invocation. Restore expected_verdict: allow to pass again.
Compare the fields that should agree
The comparison removes provider provenance and the outer receipt identifier, then serializes the remaining body as canonical JSON. Every selected record reduces to these fields:
{
"policy_id": "cross-provider-policy-demo",
"scenario_id": "weather_lookup_allow",
"tool_name": "get_weather",
"arguments": {
"location": "San Francisco, CA",
"unit": "celsius"
},
"verdict": {
"verdict": "allow",
"reason": null,
"redactions": []
}
}Connect provider syntax to the recorded invocation
The OpenAI fixture contains four NDJSON records. This is the complete provider response payload from its second record; arguments arrive as an encoded JSON string.
{
"id": "resp_openai_basic_single_tool_call",
"object": "response",
"output": [
{
"type": "message",
"content": [
{
"type": "output_text",
"text": "Checking the forecast."
}
]
},
{
"type": "function_call",
"call_id": "call_weather_1",
"name": "get_weather",
"arguments": "{\"location\":\"San Francisco, CA\",\"unit\":\"celsius\"}"
}
]
}The third record stores the normalized invocation and the captured verdict. Adapter replay checks that the provider request lifts into this invocation. It does not independently authorize the weather lookup.
{
"ts": "2026-04-25T00:00:01.150Z",
"schema": "chio-provider-conformance.capture.v1",
"fixture_id": "openai_basic_single_tool_call",
"family": "tool_use_basic",
"api_snapshot": "2026-04-25",
"direction": "kernel_verdict",
"provider": "openai",
"invocation_id": "call_weather_1",
"verdict": "allow",
"receipt_id": "rcpt_openai_basic_single_tool_call_allow",
"payload": {
"invocation": {
"provider": "open_ai",
"tool_name": "get_weather",
"arguments": {
"location": "San Francisco, CA",
"unit": "celsius"
},
"provenance": {
"provider": "open_ai",
"request_id": "call_weather_1",
"api_version": "responses.2026-04-25",
"principal": {
"kind": "open_ai_org",
"org_id": "org_chio_demo"
},
"received_at": "2026-04-25T00:00:01.150Z"
}
}
}
}Interpret a passing result
| Observation | Conclusion |
|---|---|
Tool, arguments and verdict agree | The eight selected captures contain the same normalized request and recorded decision. |
Policy and scenario identifiers agree | The comparison attached the same configured labels to each output record. These labels are not independently recovered from signed policy evidence. |
Provider provenance differs | The records retain their provider, request, principal, API version and capture time; those fields are excluded from equality. |
Comparison exits successfully | The configured checks passed. This does not establish live provider compatibility, arbitrary HushSpec enforcement, tool execution or signed-receipt authenticity. |
See where the paths converge
Extend the check you actually need
Add captured cases to examine different argument shapes or provider translations. A denial case needs a captured denial and the matching expected contract. To test a new runtime guard, send the request through a configured kernel in your application and inspect its actual decision.
Adding an egress rule to this file does not test egress enforcement. A control run with an extra blocking egress rule still passes because the example’s parser ignores that section. Use Write a Policy for an executed policy edit and receipt verification for signed evidence.
Keep the comparison output
cargo run --locked --quiet -- \
--dry-run > comparison.out
cat comparison.outEach record includes provider provenance, followed by a provenance.provider line. The complete output is useful when a change needs inspection; its fixture identifiers should not be passed to a kernel receipt verifier.
Inspect the verifier implementation
The source checks each record against the expected tool, arguments and verdict, then compares canonical bytes. These details support maintenance of the verifier after the complete run above works.
Contract checks and canonical comparison source
fn enforce_policy(policy: &DemoPolicy, receipt: &DemoReceipt) -> Result<(), DemoError> {
let contract = &policy.rules.fixture_contract;
if receipt.body.tool_name != contract.required_tool {
return Err(DemoError::ToolMismatch {
fixture_id: receipt.receipt_id.clone(),
expected: contract.required_tool.clone(),
actual: receipt.body.tool_name.clone(),
});
}
if receipt.body.arguments != contract.required_arguments {
return Err(DemoError::ArgumentsMismatch {
fixture_id: receipt.receipt_id.clone(),
});
}
if receipt.body.verdict.verdict != contract.expected_verdict {
return Err(DemoError::VerdictMismatch {
fixture_id: receipt.receipt_id.clone(),
expected: contract.expected_verdict,
actual: receipt.body.verdict.verdict,
});
}
Ok(())
}fn body_without_provenance(body: &ReceiptBody) -> ReceiptBodyWithoutProvenance {
ReceiptBodyWithoutProvenance {
policy_id: body.policy_id.clone(),
scenario_id: body.scenario_id.clone(),
tool_name: body.tool_name.clone(),
arguments: body.arguments.clone(),
verdict: body.verdict.clone(),
}
}fn assert_receipt_equivalence(receipts: &[DemoReceipt]) -> Result<(), DemoError> {
let Some(first) = receipts.first() else {
return Ok(());
};
let first_body = body_without_provenance(&first.body);
let first_body_bytes = canonical_json_bytes_for("first receipt body", &first_body)?;
let first_verdict_bytes =
canonical_json_bytes_for("first normalized verdict", &first.body.verdict)?;
for receipt in receipts.iter().skip(1) {
let body = body_without_provenance(&receipt.body);
let body_bytes = canonical_json_bytes_for("normalized receipt body", &body)?;
assert_canonical_bytes_eq(
"receipt body excluding provenance",
&first_body_bytes,
&body_bytes,
)?;
let verdict_bytes = canonical_json_bytes_for("normalized verdict", &receipt.body.verdict)?;
assert_canonical_bytes_eq("normalized verdict", &first_verdict_bytes, &verdict_bytes)?;
}
Ok(())
}Check the required flag and the output shape
Without --dry-run the command refuses to start, so a mistyped invocation can never reach a provider:
cargo run --locked --quiet--dry-run is required; this example never calls live provider APIs
The complete run prints one record and one provenance line per provider, then the summary:
cargo run --locked --quiet -- --dry-run > out.txt
wc -l < out.txt
grep -c '^provenance.provider' out.txt
tail -1 out.txt227 8 cross-provider verdict equality: 8 receipts validated for policy cross-provider-policy-demo
The first record and its provenance line, the first 29 lines of out.txt:
{
"receipt_id": "rcpt_openai_basic_single_tool_call_allow",
"body": {
"policy_id": "cross-provider-policy-demo",
"scenario_id": "weather_lookup_allow",
"tool_name": "get_weather",
"arguments": {
"location": "San Francisco, CA",
"unit": "celsius"
},
"verdict": {
"verdict": "allow",
"reason": null,
"redactions": []
},
"provenance": {
"provider": "open_ai",
"request_id": "call_weather_1",
"api_version": "responses.2026-04-25",
"principal": {
"kind": "open_ai_org",
"org_id": "org_chio_demo"
},
"received_at": "2026-04-25T00:00:01.150Z"
}
}
}
provenance.provider: "open_ai"
{Continue with your governed application
Use the notes application with a provider SDK to put real tool dispatch behind Chio. For Rust, the OpenAI integration includes kernel boot, registration and capability issuance. Keep the provider loop and the governed operation connected when changing models.