Vaulteq
The double-entry ledger you
pip install— not one you sign up for.
Finance is supposed to be boring. Most "AI-native" financial tooling isn't — it lets an LLM touch the arithmetic, which means it can be confidently wrong. Vaulteq is the boring part on purpose: a deterministic, rule-enforcing ledger that an agent calls instead of calculates. Debits and credits match, or it refuses the post. No exceptions, no vibes.
Status: Alpha. Core engine and concurrency safety are tested (see below) — including a bug the tests themselves caught and fixed. Not independently audited. See Disclaimer before using this for anything involving real money.
Install
pip install vaulteq # core library — zero dependencies
pip install "vaulteq[mcp]" # + MCP server, for agent tool-calling
Why this and not TigerBeetle / Modern Treasury / Formance?
They're real, capable systems — for a different buyer. TigerBeetle is a standalone database server built for massive throughput; Modern Treasury and Formance are hosted platforms with contracts and onboarding. Vaulteq is for the developer who wants correctness now, in a script or an agent, with no server to run and nothing to sign up for. Smaller scope, on purpose.
Use it as a library
from vaulteq import LedgerEngine, PostRequest, JournalLineInput, Direction, AccountType
engine = LedgerEngine("mybook.db") # or LedgerEngine() for in-memory
org = engine.create_organization("Acme Corp", base_currency="USD")
engine.create_account(org, "1001", "Cash", AccountType.ASSET, Direction.DEBIT)
engine.create_account(org, "4000", "Revenue", AccountType.REVENUE, Direction.CREDIT)
resp = engine.post(PostRequest(
organization_id=org, idempotency_key="order_123",
lines=[
JournalLineInput("1001", Direction.DEBIT, 5000, "USD"),
JournalLineInput("4000", Direction.CREDIT, 5000, "USD"),
]
))
Use it as an MCP server
vaulteq-mcp # in-memory ledger
VAULTEQ_DB_PATH=./mybook.db vaulteq-mcp # persistent ledger
Exposes vaulteq_create_organization, vaulteq_create_account, vaulteq_post, vaulteq_trial_balance, vaulteq_get_audit_trail, and vaulteq_verify_audit_chain as tools any MCP-compatible agent can call directly — point Claude or another agent at it and it can post a balanced journal entry without ever touching the math.
What's inside
| File | Purpose |
|---|---|
vaulteq/schema.sql |
SQL schema, bundled as package data. Integer minor units. Hash-chained audit trail. payload_hash + UNIQUE(org, idempotency_key) for real idempotency. |
vaulteq/engine.py |
Core engine. Zero dependencies. Explicit transaction control (isolation_level=None). Atomic idempotency with safe-retry and conflict paths. Full error taxonomy. |
vaulteq/mcp_server.py |
Optional MCP server wrapping the engine as agent-callable tools. Only import path that needs the mcp extra. |
tests/test_race.py |
Concurrent stress test. Two threads, two connections, same DB. Verifies exactly one journal entry lands and no raw IntegrityError leaks. |
Design decisions
- Amounts are integer minor units (
BIGINT, cents) — never floats, never unconstrained decimals in storage. - Audit events are hash-chained (
prev_event_hash→ SHA-256 of previous event). This makes deleting or altering a mid-chain event detectable viaverify_audit_chain()— it is not a substitute for append-only storage or an external anchor, and does not protect against someone with full write access wiping the entire audit table or truncating the most recent event. See Disclaimer. - Idempotency is real, not cosmetic:
- Same key + same payload → returns cached
PostResponse(safe retry) - Same key + different payload →
DUPLICATE_IDEMPOTENCY_KEYconflict - Check is atomic under
BEGIN IMMEDIATEwith explicit transaction control (isolation_level=None) - Belt-and-suspenders:
IntegrityErrorfrom the UNIQUE constraint is caught and resolved into proper retry or conflict
- Same key + same payload → returns cached
- Error taxonomy is explicit and complete:
| Code | Meaning |
|---|---|
ORGANIZATION_NOT_FOUND |
Referenced org doesn't exist |
INVALID_JOURNAL |
Fewer than 2 lines, or other structural violation |
UNBALANCED_JOURNAL |
Debits ≠ credits |
ACCOUNT_NOT_FOUND |
Referenced account_code doesn't exist for this org |
ACCOUNT_INACTIVE |
Account exists but is closed/inactive |
DUPLICATE_IDEMPOTENCY_KEY |
Key already used with a different payload |
CURRENCY_MISMATCH |
Line currency has no registered fx_rate to base_currency |
PERIOD_CLOSED |
Attempted post to a closed accounting period (deferred) |
Run the tests
python -m pytest tests/test_engine.py -v # 16 behavioral tests — balance rules,
# idempotency, audit tamper-detection
python tests/test_race.py # concurrent idempotency + race safety
Zero dependencies for the core library. Uses Python stdlib + SQLite.
Why the race test proves what it proves
race_test.py uses separate sqlite3 connections per thread (not a shared connection, which would serialize through Python's GIL and mask real cross-connection races) and a threading.Barrier to force both threads into post() at the same instant rather than hoping for a scheduling accident. It asserts the invariant that matters: exactly one journal entry in the database, both threads returning the same journal ID, and zero raw IntegrityError exceptions leaking to the caller. This is a legitimate concurrency test, not a token one.
What works now
- Organization & Chart of Accounts management
- Double-entry journal posting with strict balance validation
- Real idempotency — safe retries return cached responses, conflicts are explicit
- Atomic idempotency under SQLite reserved lock with explicit transaction control
- Race-safety verified — concurrent threads with same key produce exactly one journal entry
- Tamper-evident, hash-chained audit trail (deletion of a mid-chain event is detectable — not the same guarantee as append-only/immutable storage; see Disclaimer)
- Trial balance query
- Audit chain integrity verification
- Complete error taxonomy with structured JSON responses
What's intentionally deferred
- Multi-currency FX rates (MVP enforces base-currency only)
- Period close / lock
- Journal reversals
- HTTP API layer (FastAPI wrapper)
- Concurrent post safety at scale (SQLite serializes; prod needs row-level locking in Postgres)
- Postgres migration (swap connection string, schema is compatible)
The one thing to remember
This is infrastructure, not a fintech. Your first customer is a developer building an AI agent that needs to post a journal without hallucinating the math.
License
MIT — see LICENSE.
Disclaimer
Vaulteq is alpha software. It has not been independently audited or reviewed by a security or accounting professional. The double-entry and concurrency guarantees described in this README have been verified with the tests in this repo, under the specific conditions those tests exercise (SQLite, single-process, the scenarios in tests/) — they have not been verified in production or at scale.
If you use Vaulteq for anything involving real money, you are responsible for your own testing, review, and risk assessment. It is provided "as is," without warranty of any kind, as stated in the LICENSE. In particular:
- The audit trail is tamper-evident, not immutable — see the note under Design Decisions above.
- Multi-currency, period close, and journal reversals are not implemented.
- Concurrency safety has been tested against SQLite specifically; it has not been tested under Postgres or at production scale.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file vaulteq-1.1.1.tar.gz.
File metadata
- Download URL: vaulteq-1.1.1.tar.gz
- Upload date:
- Size: 19.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/7.0.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7632c3b5657d2deb798b25f3e6e926fa98442128e6ceb90026b4a684c5f9d161
|
|
| MD5 |
13e72fe11b27c9651db3c01c3f931b0c
|
|
| BLAKE2b-256 |
885dc98cf5f4dfc53baaf887d3646a064013c338199e5532036a0529ecf72ec8
|
File details
Details for the file vaulteq-1.1.1-py3-none-any.whl.
File metadata
- Download URL: vaulteq-1.1.1-py3-none-any.whl
- Upload date:
- Size: 14.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/7.0.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f5c8aaea1a898ff4d54c576d594d070dc0f3f2e4d8a167941d9b43c9646aac64
|
|
| MD5 |
bc201f3b71ce99e3e3265fd5568a254a
|
|
| BLAKE2b-256 |
c33b8fddaa89d3b3dbaedf27e1278f141afed4785334a7d7bf696c0830e18635
|