This tutorial shows how to create tamper-evident audit trails for agent sessions using the ProofChain.
An agent runs for 30 minutes, makes 50 tool calls, and writes to shared memory 20 times. Afterwards, you need to verify: did the agent actually do what it claims? Was the log tampered with? Can you trace every memory write back to a specific tool call?
The ProofChain solves this by producing hash-chained, HMAC-signed envelopes for every event.
import { createProofChain } from '@claude-flow/guidance/proof';
// The HMAC key should be a secret known only to the auditing system
const chain = createProofChain('audit-hmac-secret-key');After each run event (a task execution), append it to the chain with its tool calls and memory operations:
import type { RunEvent } from '@claude-flow/guidance';
// Record tool calls that happened during this event
const toolCalls = [
{
callId: 'call-1',
toolName: 'Read',
params: { file_path: '/src/auth.ts' },
result: { content: '...' },
timestamp: Date.now(),
durationMs: 45,
},
{
callId: 'call-2',
toolName: 'Edit',
params: { file_path: '/src/auth.ts', old_string: 'foo', new_string: 'bar' },
result: { success: true },
timestamp: Date.now(),
durationMs: 12,
},
];
// Record memory operations
const memOps = [
{
key: 'auth-status',
namespace: 'task-results',
operation: 'write' as const,
valueHash: chain.hashContent('fix applied'),
timestamp: Date.now(),
},
];
// Append to chain
chain.appendEvent(runEvent, toolCalls, memOps);Each envelope contains:
const envelope = chain.getEnvelope(0);
envelope.id; // Unique envelope UUID
envelope.contentHash; // SHA-256 of the run event
envelope.previousHash; // Hash of the previous envelope (genesis = '0' x 64)
envelope.chainIndex; // Position in chain (0, 1, 2, ...)
envelope.timestamp; // When the envelope was created
envelope.signature; // HMAC-SHA256 over the entire envelope body
envelope.toolCallHashes; // SHA-256 of each individual tool call
envelope.memoryLineage; // Read/write trail with value hashes
envelope.metadata; // Task ID, intent, violation count, etc.// Verify the entire chain — checks hash links and HMAC signatures
const valid = chain.verify();
if (!valid) {
console.error('Chain has been tampered with!');
// Find which envelope was modified:
for (let i = 0; i < chain.length; i++) {
const env = chain.getEnvelope(i);
if (!chain.verifyEnvelope(i)) {
console.error(`Envelope ${i} (${env.id}) is invalid`);
}
}
}- Genesis envelope has
previousHash='0'.repeat(64) - Each envelope's
previousHashmatches the computed hash of the prior envelope - Each envelope's
signatureis a valid HMAC-SHA256 using the chain's key - The
contentHashmatches a recomputation from the envelope's data
Breaking any single envelope invalidates the chain from that point forward.
// Export the chain for persistence
const serialized = chain.serialize();
// serialized is a JSON-serializable object
// Later, restore it
import { ProofChain } from '@claude-flow/guidance/proof';
const restored = ProofChain.deserialize(serialized, 'audit-hmac-secret-key');
const stillValid = restored.verify(); // true if nothing was modifiedTrack exactly which agent wrote what, and when:
// Each envelope records memory operations
const envelope = chain.getEnvelope(5);
for (const entry of envelope.memoryLineage) {
console.log(`${entry.operation}: ${entry.namespace}/${entry.key}`);
console.log(` Value hash: ${entry.valueHash}`);
console.log(` At: ${new Date(entry.timestamp)}`);
}For large chains, use the WASM kernel for faster hash computation:
import { getKernel } from '@claude-flow/guidance/wasm-kernel';
const k = getKernel();
// Use WASM for individual hashes
const hash = k.sha256(JSON.stringify(runEvent));
const mac = k.hmacSha256('key', envelopeBody);
// Verify a serialized chain via WASM
const valid = k.verifyChain(JSON.stringify(serializedChain), 'audit-hmac-secret-key');At 10,000 events, WASM completes chain verification in ~61ms vs ~76ms for JS.
import { createProofChain } from '@claude-flow/guidance/proof';
import { createGuidanceControlPlane } from '@claude-flow/guidance';
const plane = createGuidanceControlPlane();
await plane.initialize();
const chain = createProofChain('audit-key');
// Task 1
const event1 = plane.startRun('task-1', 'bug-fix');
// ... agent works ...
const evals1 = await plane.finalizeRun(event1);
chain.appendEvent(event1, toolCallsFromTask1, memOpsFromTask1);
// Task 2
const event2 = plane.startRun('task-2', 'feature');
// ... agent works ...
const evals2 = await plane.finalizeRun(event2);
chain.appendEvent(event2, toolCallsFromTask2, memOpsFromTask2);
// End of session — verify and persist
console.log(`Chain length: ${chain.length}`);
console.log(`Valid: ${chain.verify()}`);
const serialized = chain.serialize();
// Store serialized to disk, database, or external audit systemPin critical audit facts so they can't be overridden:
import { createTruthAnchorStore } from '@claude-flow/guidance/truth-anchors';
const anchors = createTruthAnchorStore('anchor-signing-key');
// After verifying a chain, create a truth anchor for the root hash
anchors.create({
kind: 'system',
claim: `Proof chain root hash: ${chain.getRootHash()}`,
attester: 'audit-system',
tags: ['audit', 'proof-chain', sessionId],
});