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 Proxy | OpenClaw Plugin | |
|---|---|---|
| Where it sits | Between MCP client and MCP server | Inside OpenClaw, as a plugin |
| What it sees | Full JSON-RPC request/response | Tool call name, parameters, outcome |
| Intent field | Available (full context) | Not available (call boundary only) |
| Agent framework | Any MCP-compatible agent | OpenClaw agents |
| Setup | Wrap your MCP server in the proxy | openclaw 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.
The hook pipeline
Section titled “The hook pipeline”The plugin registers two hooks with the OpenClaw gateway:
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 name | Action type | Risk |
|---|---|---|
exec, bash, run_command | system.command.execute | high |
read_file, read | filesystem.file.read | low |
write_file, write | filesystem.file.create | low |
delete_file | filesystem.file.delete | high |
browser_navigate, web_fetch | system.browser.navigate | low |
settings_modify | system.settings.modify | high |
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.
What a receipt contains
Section titled “What a receipt contains”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_hashis 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.
The chain
Section titled “The chain”Each receipt links to the previous one by hash:
Receipt 1: sequence=1, previous_receipt_hash=nullReceipt 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:
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.
| Setting | Behaviour |
|---|---|
false (default) | Hash only, no plaintext |
"high" | Plaintext for high and critical risk actions |
true | Plaintext 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:01ZRows 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.
What’s absent vs. the MCP Proxy
Section titled “What’s absent vs. the MCP Proxy”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.
What’s next
Section titled “What’s next”- 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/openclawin the OpenClaw plugin registry. (openclaw#5) - Taxonomy coverage — adding
ar_query_receiptsandar_verify_chainto the action taxonomy. (openclaw#98)
See OpenClaw Installation to get started.