A money ledger and an escalation gate for autonomous agents. Zero
dependencies — stdlib only (sqlite3), runs anywhere Python 3.9+ runs.
Every "AI agent that runs a business" needs the same two primitives, and almost none of the demos actually have them:
- A ledger it can't lie to itself in. If an agent tracks money in a
variable, a prompt, or its own memory, nothing stops drift between what it
believes it earned and what actually happened.
Ledgerstores every dollar as one row in SQLite; the balance is aSUM, never a running total the agent maintains by hand. - A gate it can't open itself. An agent that can both propose a risky
action and approve it in the same breath doesn't have a safety
mechanism, it has a formality.
gate()splits proposing from approving across two separate calls — the agent queues aDecision, a human answers it out of band, and only a later run can see the approval. One process cannot ask a question and grant its own answer.
This is the pattern underneath a real autonomous agent that has taken real money and spent real money against a real budget, extracted and genericized so it isn't tied to any one business, product, or codebase.
from ledger import Ledger, gate
book = Ledger("agent.db", budget_cents=5000_00) # $5,000 lifetime cap
book.revenue(1999, "sold a license", source="stripe")
book.cost(300, "domain renewal") # raises BudgetExceeded if over cap
if gate(book, "email this list of 200 leads?", context="draft in outbox/leads.md"):
send_the_emails()
else:
print("waiting on a human to approve decision", book.pending_decisions())Or from the shell, so an agent's tool-use surface is a CLI instead of raw SQL:
python3 cli.py revenue 19.99 "first sale" --source stripe
python3 cli.py cost 5 "domain renewal"
python3 cli.py status
python3 cli.py ask "post this on the company Twitter?"
python3 cli.py decisions
python3 cli.py answer 1 yes # the human's command, not the agent's- Integer cents, always. Floats drift; the one number that defines whether the agent is working is not where you want rounding error.
- A cost above budget raises, it doesn't clamp or warn. Silent degradation ("spent $5,001 of a $5,000 budget, logged a warning") is worse than a hard stop an agent has to explicitly catch and escalate around.
gate()keys on the exact question string, not a category. Approving "email this list of 200 leads?" once doesn't silently authorize "email this other list of 500 leads" — scope creep in an autonomous agent should require a new, explicit ask.- One SQLite file per agent. No server, no ORM, no migration framework —
something you can
sqlite3 agent.db "select * from entries"from a phone over SSH at 2am.
python3 -m unittest discover -s tests -v12 tests, no network, no mocks — a temp SQLite file per test.
Not a framework, not a scheduler, not a model wrapper. It has no opinion on which LLM you use, how you loop, or what the agent is actually for. It is the two primitives every "give an LLM agent money and autonomy" project needs and almost always builds badly, or not at all, on the first pass.
MIT. Use it, fork it, ship it inside something you charge for.