Agent SDK
Wrap any tool call with a portable, cryptographic execution receipt. The SDK normalizes inputs and outputs, hashes them into a canonical envelope, and commits the digest through OCC. Raw data never leaves your runtime.
Install
npm install occ-agent
Quick start
The built-in fetch_url tool is ready to use. Wrap it, call it, and get back your output with an OCC proof attached.
import { wrapTool, fetchUrlTool } from "occ-agent";
const verifiedFetch = wrapTool(fetchUrlTool, {
apiUrl: "https://nitro.occproof.com",
});
const result = await verifiedFetch({ url: "https://api.example.com/data" });
result.output; // normal fetch response
result.executionEnvelope; // canonical execution record
result.occProof; // portable OCC proofHow it works
Every call follows the same six-step pipeline:
- 1. Normalize input — deterministic JSON representation of the tool input
- 2. Hash input — SHA-256 of the canonical input bytes
- 3. Execute — run the tool function
- 4. Normalize and hash output — same process for the response
- 5. Build envelope — canonical JSON with tool name, version, both hashes, timestamp
- 6. Commit — SHA-256 of the envelope is sent to OCC. The enclave signs it and returns a proof.
Only the 32-byte envelope digest crosses the network. The enclave never sees your input, output, or tool logic.
Define a custom tool
Any async function can become a verified tool. Define the execution logic and normalization functions — the SDK handles the rest.
import { wrapTool } from "occ-agent";
import type { ToolDefinition } from "occ-agent";
const summarizeTool: ToolDefinition<
{ text: string },
{ summary: string }
> = {
name: "summarize",
version: "1.0.0",
execute: async (input) => {
const response = await callLLM(input.text);
return { summary: response };
},
normalizeInput: (input) => ({ text: input.text }),
normalizeOutput: (output) => ({ summary: output.summary }),
};
const verifiedSummarize = wrapTool(summarizeTool, {
apiUrl: "https://nitro.occproof.com",
});
const result = await verifiedSummarize({ text: "..." });
// result.output.summary — the LLM response
// result.occProof — cryptographic proof of executionOne-shot execution
For single calls without creating a reusable wrapper:
import { runVerifiedTool, fetchUrlTool } from "occ-agent";
const result = await runVerifiedTool(
fetchUrlTool,
{ url: "https://httpbin.org/json" },
{ apiUrl: "https://nitro.occproof.com" },
);Export a receipt
Save the execution envelope and OCC proof as a portable JSON document. Raw tool output is intentionally excluded — it stays in your runtime.
import { exportReceipt, loadReceipt } from "occ-agent";
// Export to JSON string
const json = exportReceipt(result);
await fs.writeFile("receipt.json", json);
// Load it back
const receipt = loadReceipt(await fs.readFile("receipt.json", "utf8"));
// receipt.envelope — the execution envelope
// receipt.proof — the OCC proofThe receipt format is occ-agent/receipt/1. It contains everything needed for offline verification — hand it to anyone and they can verify without contacting OCC.
Verify a receipt
Verification is offline. Given an envelope and proof, anyone can check that the execution was committed through OCC. Works with a VerifiedToolResult or a loaded receipt.
import { verifyExecutionReceipt, loadReceipt } from "occ-agent";
// From a VerifiedToolResult
const verification = await verifyExecutionReceipt(
result.executionEnvelope,
result.occProof,
);
// Or from an exported receipt
const receipt = loadReceipt(json);
const v = await verifyExecutionReceipt(receipt.envelope, receipt.proof);
v.valid; // true/false
v.checks.envelopeHashMatch; // digest matches artifact
v.checks.signatureValid; // Ed25519 signature validExecution envelope
The canonical execution record committed to OCC:
{
"type": "tool-execution",
"tool": "fetch_url",
"toolVersion": "1.0.0",
"runtime": "agent-skills",
"adapter": "occ-agent",
"inputHashB64": "65ZIM1fa4oixyj6qdsQe...",
"outputHashB64": "Y+aesdCj8/940fyda2T0...",
"timestamp": 1773464119585
}Fields are sorted alphabetically and serialized without whitespace before hashing. This ensures any implementation produces the same digest for the same execution.
API reference
wrapTool(tool, config)
Returns an async function that executes the tool and returns a VerifiedToolResult.
| Parameter | Type | Description |
|---|---|---|
| tool | ToolDefinition | Tool with name, version, execute, normalize functions |
| config.apiUrl | string | OCC commit service URL |
| config.apiKey | string? | Optional Bearer token for authenticated endpoints |
| config.runtime | string? | Runtime identifier (default: "agent-skills") |
ToolDefinition
| Field | Type | Description |
|---|---|---|
| name | string | Tool identifier (e.g. "fetch_url") |
| version | string | Semver version string |
| execute | (input) => Promise | The actual tool logic |
| normalizeInput | (input) => unknown | Deterministic input representation for hashing |
| normalizeOutput | (output) => unknown | Deterministic output representation for hashing |
Privacy model
- • Hashes only. Only SHA-256 digests are sent to OCC. Raw input and output stay in your runtime.
- • No reverse engineering. SHA-256 is preimage-resistant. The digest reveals nothing about the original data.
- • Metadata is optional. Tool name and runtime are included in the commit metadata, but this is configurable.
- • Verification is offline. Anyone with the envelope and proof can verify without contacting OCC.