⚙️ AIR
The Language of Accounting
LLVM for the ledger — LLMs understand your documents, a deterministic compiler keeps your books.
The idea
Every ERP reinvents the same accounting model. LLMs are brilliant at understanding ("this invoice = 3 chairs paid by Visa") and unreliable at applying hundreds of tax and accounting rules. AIR separates the two — exactly like LLVM separated language frontends from machine backends:
flowchart LR
A[📄 Invoice / Email<br/>Bank / POS / API] --> B["🧠 LLM<br/><i>understanding</i>"]
B -->|"AIR events<br/>(never journal entries)"| C["⚙️ AIC Compiler<br/><i>deterministic rules</i>"]
P["📜 ALSL Policies<br/>taxes · thresholds · rounding<br/><i>versioned & cited</i>"] --> C
C -->|"balanced entries<br/>+ full provenance"| D["📗 Native Ledger<br/>hash-chained, standalone"]
C --> E["📤 CSV · QuickBooks<br/>Xero · Odoo · SAP*"]
D --> F["📊 Trial balance · Balance sheet<br/>Income statement · GL"]
style B fill:#f9e79f,stroke:#b7950b
style C fill:#aed6f1,stroke:#2471a3
style D fill:#a9dfbf,stroke:#1e8449
style P fill:#f5b7b1,stroke:#c0392b
The one rule that never bends: an LLM (or any frontend) only ever produces AIR — a description of what happened economically. It never writes a journal entry, an account code, or a debit. The compiler does that, deterministically, with the double-entry invariant
Assets = Liabilities + Equityverified after every pass and clang-style diagnostics when anything is wrong.
What you get
| 🗣️ A universal language | EconomicEvent (REA-based): sales, purchases, refunds, payments, FX — perspective-neutral, schema-validated, no debits anywhere |
| ⚙️ A real compiler | Pass pipeline (validation → classification → tax → FX → posting), pass manager with verify-after-every-pass, diagnostics with location + cause + help: fix |
| 📜 Rules as data (ALSL) | GST 5%, QST 9.975%, HST, capitalization thresholds, rounding modes — all in versioned YAML policy sets with mandatory source citations (the loader rejects uncited rates) |
| 🏠 Standalone books | No ERP required: managed AIR home with an append-only, tamper-evident ledger, content-addressed document archive, and all financial statements in text/markdown/CSV/JSON |
| 🔁 Git-style corrections | Changed invoice? air recompile diffs by content fingerprint and posts reversal + replacement entries — history is never edited |
| 🤖 An agent SDK | AI agents get syscalls (CreateEconomicEvent, Post, Reverse, ClosePeriod, Merge, Reconcile…) — never the ledger. Every call, including refusals, lands in a hash-chained audit log |
| 🧾 LLM ingestion | Invoice text → Claude (structured outputs) → schema-validated AIR → confidence routing → human approval inbox. Fully offline mock for tests |
| 🚀 Optimizations | 50 identical payments → 1 batch entry (fusion), refund↔sale netting, duplicate detection — provenance preserved through every transformation |
| 🏦 Bank reconciliation | camt.053 + MT940 (with statement integrity check) + CSV → matched against the books, differences reported on both sides |
| 🔍 Total traceability | Every posted cent walks back through the provenance graph (accounting SSA) to its source event, document, OCR/LLM confidence, policy version, and rounding mode |
Quickstart
git clone https://github.com/spboucher-ai/air && cd air
python3 -m venv .venv && .venv/bin/pip install -e ".[dev]"
source .venv/bin/activate
# 1) Your books live in a managed AIR home — one command
air init --home books --policies alsl/policies/ca-qc-2026.yaml
# 2) Compile economic events into the books (document archived, ledger chained)
air compile tests/fixtures/demo_document.yaml --home books \
--report trial-balance --report balance-sheet
# 3) A source invoice was corrected? Post only the delta (reversal + replacement)
air recompile old.yaml corrected.yaml --home books
# 4) Statements any time, any format
air report income-statement --home books --format markdown
# 5) Ingest a real document (offline extractor; add --llm for Claude)
air ingest tests/fixtures/invoice_high_confidence.txt --home books
air inbox --home books
# 6) Month end
air reconcile statement.mt940 --home books
air audit --home booksFull command reference: docs/cli-reference.md
Sixty seconds of AIR
An economic event — what happened, nothing else:
events:
- id: evt_01H8XGJWBW
type: Sale
date: 2026-07-20
parties: {seller: "company:acme", buyer: "customer:cust_123"}
items:
- {sku: chair-std, qty: "3", unit_price: {amount: "333.33", currency: CAD}}
payment: {method: card.visa, immediate: true}
tax: {jurisdiction: CA-QC} # ← where. Rates live in policies, never hereThe compiler applies the cited CA-QC policy set (GST 5%, QST 9.975% on the pre-GST price, half-up per Excise Tax Act s.165.2(2)) and emits a balanced entry:
DR 1000 Cash 1149.74
CR 4000 Sales revenue 999.99
CR 2310 GST payable 50.00 tax:GST@0.05~half_up
CR 2320 QST payable 99.75 tax:QST@0.09975~half_upSame input + same policies = byte-identical output, always (property-tested,
and CI compiles the demo twice and diffs the results). When something is wrong,
you get a compiler error, not a wrong number:
error[AIR-E400]: no tax policy in set 'ca-qc' matches jurisdiction 'CA-BC'
--> event evt_x, field tax.jurisdiction
pass: tax
help: add an ALSL tax policy for this jurisdiction or mark the event tax.exempt: trueAgents keep books through syscalls — and only syscalls
from sdk.syscalls import AirKernel
k = AirKernel("books", actor="agent:alice")
k.create_economic_event({...}) # schema-validated draft
k.post() # deterministic compile → books (idempotent)
k.reverse("je_evt_x") # corrections are contra entries, never edits
k.close_period("2026-01") # posting into it now fails with AIR-E700
k.merge() # post with netting + payment fusion
k.reconcile("statement.mt940") # bank matching$ air audit --home books
#0000 OK agent:alice CreateEconomicEvent {"event_id": "evt_x", "type": "Sale"}
#0001 OK agent:alice Post {"drafts": 1}
#0008 ERR agent:alice Post {"drafts": 1} <- error[AIR-E700]: period 2026-01 is closed
10 syscalls, hash chain VALIDRefused actions are audited like successful ones. Editing any record breaks the chain.
The seven guarantees
- Determinism — no LLM, network, or clock inside the compiler.
- Double entry — the invariant is verified after every pass; violations abort compilation.
- Traceability — provenance graph from every posted line to its source (accounting SSA).
- No floats — exact decimals end to end; floats rejected at every boundary.
- No rules in code — every rate/threshold lives in cited, versioned ALSL policies.
- Append-only — reversals, never edits; ledger and audit log are hash-chained.
- Human in the loop — low-confidence or schema-invalid extractions always route to a person.
Repository map
| Path | Role | LLVM analogy |
|---|---|---|
schemas/ |
AIR JSON Schema | the IR definition |
core/ |
events, Money, provenance, invariants | IR + verifier |
aic/ |
pass manager, passes, diagnostics, incremental | opt/llc |
alsl/ |
rule language + cited policy sets | TableGen |
backends/ |
native, CSV, QuickBooks (offline-tested) | targets |
kernel/ |
ledger, reporting, workspace, audit, reconciliation | runtime |
sdk/ |
CLI, agent syscalls, demo agent | libclang |
ingestion/ |
extractors, confidence routing, approval queue | frontend |
docs/spec/ |
the AIR/ALSL specification | LangRef |
docs/adr/ |
architecture decision records | — |
docs/research/ |
9 cited research reports (tax law, formats, APIs) | — |
tests/ |
golden cases, property tests, unit + live tests | lit |
Documentation
- 📖 The AIR Specification — the language, the compiler, diagnostics, syscalls, reconciliation
- 🧭 CLI Reference — every command with examples
- 🏛️ ADRs — why Python, why REA events, why rounding is policy
- 🔬 Research — GST/QST from official sources, camt.053/MT940 specs, ERP API surveys, LLVM architecture lessons
- 🤝 Contributing — the non-negotiable rules CI enforces
Development
.venv/bin/python -m pytest tests/ -q --ignore=tests/test_llm_live.py # 99 tests, fully offline
python3 scripts/check_headers.py # author-header gate
# opt-in live LLM tests (cost a few cents)
ANTHROPIC_API_KEY=sk-ant-... .venv/bin/python -m pytest tests/test_llm_live.py -vEverything — including the QuickBooks backend and the LLM ingestion — develops and tests fully offline: mock transports simulate the real APIs' documented behaviors (idempotency replay, duplicate errors), so no account or key is ever required to work on AIR.
Roadmap
- Phases 0–6: research → core → compiler → backends → ingestion → agent SDK → optimizations & reconciliation (complete)
- beancount/hledger export backend
- Xero & Odoo backends (offline mock pattern)
- Reference-first bank matching (
EndToEndId) - More jurisdictions as cited ALSL policy sets (US sales tax, EU VAT)
- AIR v0.2: REA commitments (pending deliveries, IFRS 15 performance obligations)
AIR — because your books deserve a compiler.
MIT © 2026 Simon-Pierre Boucher