Accounting for Agents

Every piece of accounting software I've used was built in a world where the person writing the ledger was also the person who would notice if something went wrong.

That world is ending.

For the past year I've been using AI assistants to help run the books for my consulting business—categorizing expenses, reconciling imports, flagging anomalies. The AI part works. What doesn't work is the software underneath it. Not because the software is bad, but because it was designed around an assumption that no longer holds: that a human is always in the loop, watching, catching the mistakes that slip through.

hledger, beancount, ledger-cli—these are good tools. They were built to be used by someone sitting at a terminal, editing a text file, running a balance check and noticing when something looks off. The design encodes that workflow at every level. Errors produce human-readable messages. State is stored in files a person can open and read. Idempotency is your problem—if you import the same statement twice, you deal with the duplicates.

None of that is a flaw. It's just a set of assumptions about who's doing the work.

The past five years have cracked those assumptions open. We now have AI that can reason about financial data, categorize transactions, write and execute queries, and generate reports in plain language. The capability is real. What's lagging is the infrastructure—the backends, the APIs, the contracts—that these agents need to operate reliably. Most of what exists was built before the question "what happens when the user is an AI?" was worth asking.

This is the gap I kept running into. The AI could do the work. The tools couldn't support it safely.

What Breaks

The failure modes are predictable once you see them.

An agent imports a bank statement and the connection drops halfway through. It retries. Now some transactions are in the ledger twice. The plain-text tools don't have a concept of idempotent writes—they append. The agent has no way to know whether retrying is safe without reading the entire ledger and doing its own deduplication, which means building that logic yourself or accepting that duplicates will happen.

A confused agent—bad context, ambiguous instructions, a model that hallucinated an account code—posts a debit without a matching credit. The books are now wrong. Some tools will catch this on a balance check, but only if you run one. An agent operating autonomously might not.

Something fails and the tool emits a human-readable error message. The agent tries to parse it. This works until the message format changes, or the error is new, or the wording differs slightly between versions. Brittle.

These aren't edge cases. They're the normal failure surface of any automated system running against software designed for interactive use.

Building for the New World

I built beankeeper to see what accounting infrastructure looks like if you start from the other assumption: the primary user is an agent, not a human.

Three things follow from that.

Retries have to be safe by design. Every transaction takes an optional --reference—a string the agent derives from the source data, typically a bank's own transaction ID. The tool hashes it and stores the result. Same reference, same company, same transaction—subsequent posts return the existing ID rather than creating a duplicate. The agent can retry freely. This isn't a feature bolted on; it's the default behavior when you assume retries will happen.

Incorrect state has to be impossible, not just detectable. The Rust library enforces double-entry at the type level. A Transaction can only be constructed through JournalEntry::post(), which checks that debits equal credits before the type exists. There's no runtime exception for an unbalanced entry—the type doesn't exist. An agent can't post bad data any more than it can return a string from a function typed to return an integer. I wrote about this pattern in the rusty_paseto post: constraints that live in the type system are stronger than constraints that live in documentation.

Output has to be a contract, not prose. Every command in JSON mode returns the same envelope: ok, meta (command name, company, timestamp), and either data or error with a named code. Six error codes cover everything the tool can produce. Exit codes are semantic. An agent can branch on ok and switch on error.code without parsing anything. Most CLI tools don't do this—they mix errors into stdout, use exit code 1 for everything, and emit different shapes depending on the command. That's fine when a human is reading the output. It's a problem when code is.

The backing store is SQLite (optionally encrypted with SQLCipher) rather than a plain-text file. That's less about features and more about what's natural for a database-first workflow: atomic writes, concurrent reads, a query layer the agent can use directly. The ledger is also append-only—posted transactions are immutable. If an agent makes a mistake, you correct it with a reversing entry, the standard accounting practice. The audit trail stays intact.

The UI Question

One objection I expected: without a dashboard or a GUI, how do you actually review what the agent did?

The agent is the UI. I ask questions in plain English and get back summaries and insights faster than I could build a report. "What did I spend on contractors last quarter?" is a query; I don't need a date picker. "Anything look unusual this month?" is not a button that exists in any accounting dashboard I've used. The CLI is the agent's interface; natural language is mine. That relationship feels backwards until you use it, and then it feels obvious.

Where This Goes

beankeeper is early. OFX/QFX imports are there; direct bank connections aren't. Currency support covers ten ISO 4217 currencies—enough for most use cases, and it's open source, so adding more is a PR. There's no ecosystem of scripts and importers yet, nothing like what's built up around the plain-text tools.

But I think the direction is right. The software stack we have was built for a world where humans did the work and tools assisted. We're moving into a world where agents do the work and humans supervise. Most of the stack will need to catch up to that shift—not by adding AI features to existing tools, but by rethinking what the tools should guarantee when the user on the other end isn't a person watching the screen.

Accounting was a good place to start because the failure modes are concrete and the correctness requirements are well-defined. But the same gap exists everywhere: task management, file systems, communication tools, deployment pipelines. The infrastructure lags the capability.


beankeeper is on GitHub — if it looks useful, a star helps more people find it. The library is on crates.io. The CLI binary (bk) installs via a one-liner:

curl -sL https://raw.githubusercontent.com/Govcraft/beankeeper/main/install.sh | sh

Run bk init --demo to spin up a populated database with three companies, intercompany transactions, and a payroll split across employer and employee books.

Accounting for Agents - Roland Rodriguez