Skip to content

OpenClaw Plugin: How It Works

The OpenClaw plugin is the second way to generate Agent Receipts — the first being the MCP Proxy. Both produce the same W3C Verifiable Credentials with Ed25519 signatures and hash-chained sequences. They differ in where they sit and what context they have access to.

This post covers the plugin’s internal mechanics, using real receipts from the first end-to-end trial.


Plugin vs. Proxy: two instrumentation approaches

Section titled “Plugin vs. Proxy: two instrumentation approaches”
MCP ProxyOpenClaw Plugin
Where it sitsBetween MCP client and MCP serverInside OpenClaw, as a plugin
What it seesFull JSON-RPC request/responseTool call name, parameters, outcome
Intent fieldAvailable (full context)Not available (call boundary only)
Agent frameworkAny MCP-compatible agentOpenClaw agents
SetupWrap your MCP server in the proxyopenclaw plugins install @agnt-rcpt/openclaw

The proxy approach gives you more context — it has the full request including the reasoning context that triggered the call. The plugin approach requires zero changes to your agent or tool implementations. You get receipts for every tool call automatically, across any tool your agent uses, by virtue of OpenClaw’s hook system.

OpenClaw Plugin approach

Agent

OpenClaw gateway

agent-receipts hooks

Tool implementations

MCP Proxy approach

stdio

stdio

MCP Client

mcp-proxy

MCP Server


The plugin registers two hooks with the OpenClaw gateway:

agent-receipts-daemonSQLiteToolagent-receipts pluginOpenClawAgentagent-receipts-daemonSQLiteToolagent-receipts pluginOpenClawAgentstash { toolName, params,paramsHash, startedAt }classify action + riskbuild receiptsign Ed25519 in-processalt[daemonForwarding: false (default)][daemonForwarding: true]tool call1before_tool_call (priority 100)2execute3result / error4after_tool_call5insert receipt6forward event [fire-and-forget]7tool result8

before_tool_call runs at priority 100 — earlier than most hooks — to capture timing accurately before the tool call starts. It stashes the parameters hash (SHA-256 of the RFC 8785 canonical JSON of the arguments) and the start timestamp.

Receipt creation happens in after_tool_call. The default path signs in-process and writes to the local SQLite database. With daemonForwarding: true, the plugin forwards the event to agent-receipts-daemon instead — the daemon signs and stores the receipt; in-process signing is skipped. In the default in-process path, signing or DB write failures are caught by the plugin, which logs a warning and returns cleanly. In the daemon-forwarding path, forwarding is fire-and-forget — daemon-side failures are not reported back to the plugin. In both cases the agent session continues; the receipt may be lost but the tool call is not blocked.


Classification: from tool call to action type

Section titled “Classification: from tool call to action type”

Every tool call is mapped to an action type and risk level before the receipt is built. The taxonomy is open — the full table is in the Action Taxonomy reference. A representative sample:

Tool nameAction typeRisk
exec, bash, run_commandsystem.command.executehigh
read_file, readfilesystem.file.readlow
write_file, writefilesystem.file.createlow
delete_filefilesystem.file.deletehigh
browser_navigate, web_fetchsystem.browser.navigatelow
settings_modifysystem.settings.modifyhigh

Tool names that don’t match any taxonomy entry are classified as unknown with risk level medium. This is intentional — an unrecognised tool call still gets a receipt, it just can’t be classified precisely.


Every receipt is a W3C Verifiable Credential. Here’s a full receipt from the v0 trial — the first system.command.execute in the session. The principal ID and chain ID below have been anonymized (123456789 is a placeholder for the real Telegram user ID); everything else, including the signature, is the actual receipt as written to disk.

{
"@context": [
"https://www.w3.org/ns/credentials/v2",
"https://agentreceipts.ai/context/v1"
],
"id": "urn:receipt:a7ee4f79-883b-4ef1-8c9b-25011cdd975e",
"type": ["VerifiableCredential", "AgentReceipt"],
"version": "0.2.0",
"issuer": {
"id": "did:openclaw:openclaw-agent-receipts"
},
"issuanceDate": "2026-04-26T21:09:38.311Z",
"credentialSubject": {
"principal": {
"id": "did:session:agent:main:telegram:direct:123456789"
},
"action": {
"type": "system.command.execute",
"risk_level": "high",
"target": { "system": "openclaw", "resource": "exec" },
"parameters_hash": "sha256:ed512c9f8d0ee83b2b398620189396f8845b0082db1cd8e3a6dbf32e23cfd79f",
"id": "act_6c3e085e-96f6-47f8-8e48-4f49cfab01bf",
"timestamp": "2026-04-26T21:09:27.881Z"
},
"outcome": { "status": "success", "error": null },
"chain": {
"sequence": 1,
"previous_receipt_hash": null,
"chain_id": "chain_openclaw_agent:main:telegram:direct:123456789_52de0419-1d76-4d15-9fb4-ffe5463e5cd2"
}
},
"proof": {
"type": "Ed25519Signature2020",
"created": "2026-04-26T21:09:38.313Z",
"verificationMethod": "did:openclaw:agent#key-1",
"proofPurpose": "assertionMethod",
"proofValue": "usxX8cHlq_6D9k8fOE1qmraW6L47K_0eorYJgjM5zwdQNpblpQEhEiiBdJvbySEKGGq5QLoD2bRA_zoD_ckLHCg"
}
}

Key fields:

  • parameters_hash — SHA-256 of the RFC 8785 canonical JSON of the tool call arguments. Proves the parameters at signing time without storing them in plaintext.
  • chain.sequence — position within this chain. Sequence 1 means this is the first receipt; previous_receipt_hash is null.
  • chain.chain_id — encodes the session context: chain_openclaw_<agent-id>_<session-id>. Each new session starts a new chain. The same agent across multiple sessions produces multiple chains.
  • proof.proofValue — Ed25519 signature over the canonical receipt. Verifiable by anyone with the corresponding public key.

The signing key is generated on first run and stored at ~/.openclaw/agent-receipts/keys.json. Keys are persistent across sessions — receipts from different sessions can all be verified against the same public key.


Each receipt links to the previous one by hash:

Receipt 1: sequence=1, previous_receipt_hash=null
Receipt 2: sequence=2, previous_receipt_hash=sha256:<hash of receipt 1>
Receipt 3: sequence=3, previous_receipt_hash=sha256:<hash of receipt 2>

To tamper with the chain, you’d need to modify a receipt, recompute all subsequent hashes, and re-sign every receipt with the private key. Without the key, the chain is tamper-evident: any modification breaks verification at the point of change, and any gap in sequence numbers is immediately visible. In the default configuration the signing key lives on disk at ~/.openclaw/agent-receipts/keys.json — accessible to the agent process itself. Set daemonForwarding: true to move key material into agent-receipts-daemon: the agent can no longer read the key or rewrite already-signed receipts. The daemon listens on a local Unix socket, so a compromised agent running as the same OS user can still connect and emit new events for signing; preventing that requires the daemon to run as a dedicated OS user.

The export command produces the full credential for independent verification:

Terminal window
npx @agnt-rcpt/openclaw export --id <receipt-id>

The receipts command produces a human-readable summary table for auditing.


parameterDisclosure: operator-controlled privacy

Section titled “parameterDisclosure: operator-controlled privacy”

By default, the plugin stores only the parameters hash. You cannot reconstruct what arguments were passed to exec. This is deliberate — agents frequently receive secrets, tokens, or sensitive data in their tool call parameters.

If you need more forensic detail, set parameterDisclosure in openclaw.json:

{
"plugins": {
"entries": {
"openclaw-agent-receipts": {
"config": {
"parameterDisclosure": "high"
}
}
}
}
}

With parameterDisclosure: "high", high-risk and critical actions include a parameters_disclosure field alongside the hash:

{
"action": {
"type": "system.command.execute",
"risk_level": "high",
"parameters_hash": "sha256:9c84a8c9e89a07ff323b0ad52972f148b7f2f5240817f2d9f9892ca514b4522c",
"parameters_disclosure": {
"command": "echo \"Testing agent-receipts plugin fix\""
}
}
}

The hash is still computed and stored — integrity proof is not traded away for readability.

SettingBehaviour
false (default)Hash only, no plaintext
"high"Plaintext for high and critical risk actions
truePlaintext for all actions
["system.command.execute"]Plaintext for specific action types

Self-verification: an agent querying its own audit trail

Section titled “Self-verification: an agent querying its own audit trail”

The first end-to-end trial produced this sequence:

8 unknown medium success ar_query_receipts 2026-04-26T21:10:40Z
9 filesystem.file.read low success read 2026-04-26T21:10:46Z
10 unknown medium success ar_query_receipts 2026-04-26T21:11:01Z

Rows 8 and 10 are the agent — asked to verify the plugin worked — choosing to do so by calling ar_query_receipts against its own audit trail. Both calls have receipts. The receipt for the audit query is in the audit trail.

The unknown classification is openclaw#98 — the plugin’s own tools aren’t in the taxonomy yet. That’s a taxonomy gap, not a protocol gap: the receipts exist, they’re signed, they’re correctly positioned in the chain. The self-verification loop works.

This matters because it demonstrates receipts as a first-class input to the agent itself, not just an external observer mechanism. An agent can retrieve its own receipts, verify the chain, and act on the results — with that verification attempt recorded in the same trail it’s inspecting.


Two things the plugin cannot provide that the MCP Proxy can:

Intent field. The MCP Proxy sits between the client and server and has access to the full JSON-RPC request, including the reasoning context that prompted the call. The plugin intercepts at the tool boundary — it sees the call, not what caused it. The intent field in the receipt schema is populated by the proxy, not the plugin.

Proxy-side argument retention. The proxy still records action.parameters_hash in receipts by default, but it can additionally retain a redacted or encrypted copy of the full arguments in its audit database, depending on configuration. The plugin’s privacy-preserving default is hash-only. Even with parameterDisclosure: true, what’s stored is a best-effort plaintext representation, not the full canonical arguments object.

The tradeoff: the plugin requires no changes to your agent implementation, no wrapping of your MCP servers, and no proxy process in your stack. You get receipts for every tool call across every tool your agent uses. The depth of each receipt is narrower.


  • Subagent chain linking — when an agent fans out into parallel subagents, linking their receipt chains to the parent. (ar#14)
  • ClawHub submission — listing @agnt-rcpt/openclaw in the OpenClaw plugin registry. (openclaw#5)
  • Taxonomy coverage — adding ar_query_receipts and ar_verify_chain to the action taxonomy. (openclaw#98)

See OpenClaw Installation to get started.