Our ledger balanced perfectly and had no audit trail A developer released MnemoPay 1.15, an open-source Apache 2.0 TypeScript SDK that adds policy checks, human approvals, a double-entry ledger and tamper-evident audit chains for AI agents taking consequential actions. The release fixes a bug in 1.14.2 where calling Ledger.compact(n) with n <= 100 permanently disabled entry recording, leaving ledgers that balanced and passed hash-chain verification but had no audit trail; 1.15 makes compact() trim-only with an archiveSink, reports audit detail via verify(), and widens entry hashes to cover every field. The SDK also introduces an OnChainAnchorSink interface for anchoring Merkle roots externally, though the developer notes the chain proves internal consistency, not that a record existed at a given time. If an AI agent moves money or changes infrastructure, someone will eventually ask: what exactly happened, and can you prove it? Not "do the totals add up", but "show me this transaction, and show me nobody edited the record afterwards". MnemoPay is an open-source Apache 2.0 TypeScript SDK for that layer: policy checks, human approvals, a double-entry ledger, and tamper-evident audit chains for agents that take consequential actions. Version 1.15 is out current: 1.15.2 . The most interesting part of it is a bug in our own code. Ledger.compact n trims old entries so memory stays bounded. In 1.14.2, calling it with n <= 100 also switched entry recording off permanently. The SDK's own MnemoPayLite calls compact 50 , so after roughly ten settlements per the changelog the ledger kept correct running totals but stopped recording entries at all. Nothing looked wrong. The totals balanced. The hash chain "verified". There was just nothing left to audit. Here is the same loop against both versions I used Ledger directly and called compact 50 the way MnemoPayLite does : js import { Ledger } from "@mnemopay/sdk"; const ledger = new Ledger ; ledger.recordFunding "agent-1", 1000, "stripe-topup" ; for let i = 0; i < 20; i++ { ledger.recordCharge "agent-1", 10, tx-${i} ; ledger.recordSettlement "agent-1", tx-${i} , 10, 0.19, 9.81, "merchant-1" ; if i % 5 === 4 ledger.compact 50 ; } const s = ledger.verify ; console.log { balanced: s.balanced, entryCount: s.entryCount, entriesForLastTx: ledger.getEntriesForTransaction "tx-19" .length, auditTrail: s.auditTrail ?? " field not present ", chainValid: s.chainValid } ; sdk 1.14.2 { balanced: true, entryCount: 162, entriesForLastTx: 0, auditTrail: ' field not present ', chainValid: true } sdk 1.15.0 { balanced: true, entryCount: 162, entriesForLastTx: 8, auditTrail: 'compacted', chainValid: true } On 1.14.2 the last transaction has zero entries behind it, while every check we had said "fine". In 1.15.0, compact only trims, keeps the hash-chain link, and hands dropped entries to an archiveSink you can point at durable storage. verify now says how much detail backs the totals complete , compacted or disabled , and turning the trail off is an explicit setHighThroughput true . We also widened the entry hash to cover every field, so editing a description or timestamp is now detected, and recordSettlement rejects fee plus net that does not equal gross before it writes any leg. If you are on 1.14.x, upgrade. 1.15.1 and 1.15.2 are small follow-ups: require for subpath imports, the right version in the MCP server, and scripts that now exit on their own. Ledgers saved by earlier versions still verify. The audit chain turns events into a Merkle root. Change one event afterwards and the root no longer matches: js import { AuditChain, verifyBundle } from "@mnemopay/sdk/governance"; const chain = new AuditChain ; chain.emit "action.proposed", { agent: "refund-bot", action: "refund", amountUsd: 40 } ; chain.emit "approval.granted", { approver: "ops@example.com" } ; chain.emit "action.executed", { txId: "tx-1001", amountUsd: 40 } ; const bundle = chain.toBundle { system: "demo" } ; console.log "untouched:", verifyBundle bundle ; const forged = structuredClone bundle ; forged.events 2 .payload.amountUsd = 4; console.log "edited: ", verifyBundle forged ; untouched: { ok: true } edited: { ok: false, reason: 'root mismatch' } The limit is worth saying plainly: whoever holds the bundle can recompute a root, so this proves internal consistency, not that the record existed at a given time. That gap is what the new OnChainAnchorSink interface is for. A provider implements anchor and status in their own package; the SDK hands over only the Merkle root and the metadata you choose, never the events, and stores the returned reference next to the bundle. The SDK ships the interface, helpers and a MockAnchorSink , with no chain code, ABIs or gas handling: js import { AuditChain, verifyBundle, anchorChainBundle, pollAnchor, attachAnchorReference, MockAnchorSink } from "@mnemopay/sdk/governance"; const chain = new AuditChain ; chain.emit "action.executed", { txId: "tx-1001", amountUsd: 40 } ; const bundle = chain.toBundle { system: "demo" } ; const sink = new MockAnchorSink { pendingTicks: 2 } ; const { anchorId } = await anchorChainBundle bundle, sink, { producerId: "demo-producer" } ; const status = await pollAnchor sink, anchorId, { delayMs: 0 } ; const { state, ...ref } = status; console.log status.chain, "| fields sent:", Object.keys sink.requests 0 .join ", " ; console.log "still verifies:", verifyBundle attachAnchorReference bundle, ref ; mock | fields sent: producerId, merkleRoot, bundleMeta, sealedAt still verifies: { ok: true } Note chain is "mock" . That is deliberate, so a mock reference can never pass for a real attestation. What a real anchor proves depends on the provider: the changelog notes that one candidate contract does not authenticate the submitter, so it would show a root existed at block T, not who committed it. The SDK can export an "Article 12 bundle" for a finished mission: mission metadata, every event, a CSV, a hash chain and a manifest with checksums, with a default retention field of 6 months. That is evidence in a regulator-readable shape. It is not legal compliance, and the repo's own EU AI Act technical file says it is self-prepared with no auditor engaged. Whether your system is high-risk, and whether this satisfies you, is for you and your counsel. 0xdb8b69be…e213 , block 53374272, verifyBundle recomputes for the fixture. It shows that root existed at that block, not who committed it. It is a test fixture, not a customer. mnemopay on PyPI, 1.3.0 is a separate, smaller port. npm install @mnemopay/sdk Repo and changelog: https://github.com/mnemopay/mnemopay-sdk https://github.com/mnemopay/mnemopay-sdk . Issues welcome, especially from anyone who audits agent systems for a living.