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

Shell
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.

TypeScript
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 proof

How it works

Every call follows the same six-step pipeline:

  1. 1. Normalize input — deterministic JSON representation of the tool input
  2. 2. Hash input — SHA-256 of the canonical input bytes
  3. 3. Execute — run the tool function
  4. 4. Normalize and hash output — same process for the response
  5. 5. Build envelope — canonical JSON with tool name, version, both hashes, timestamp
  6. 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.

TypeScript
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 execution

One-shot execution

For single calls without creating a reusable wrapper:

TypeScript
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.

TypeScript
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 proof

The 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.

TypeScript
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 valid

Execution envelope

The canonical execution record committed to OCC:

JSON
{
  "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.

ParameterTypeDescription
toolToolDefinitionTool with name, version, execute, normalize functions
config.apiUrlstringOCC commit service URL
config.apiKeystring?Optional Bearer token for authenticated endpoints
config.runtimestring?Runtime identifier (default: "agent-skills")

ToolDefinition

FieldTypeDescription
namestringTool identifier (e.g. "fetch_url")
versionstringSemver version string
execute(input) => PromiseThe actual tool logic
normalizeInput(input) => unknownDeterministic input representation for hashing
normalizeOutput(output) => unknownDeterministic 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.

Source

github.com/mikeargento/occ/packages/occ-agent · npm · demo