# ⚙️ AIR
### The Language of Accounting
**LLVM for the ledger** — LLMs understand your documents, a deterministic compiler keeps your books.
[](https://github.com/spboucher-ai/air/actions/workflows/ci.yml)
[](pyproject.toml)
[](LICENSE)
[](tests/)
[](tests/golden/cases/)
[](docs/adr/0003-monetary-amounts.md)
[](core/invariant.py)
[](kernel/ledger.py)
[](docs/cli-reference.md)
*by [Simon-Pierre Boucher](mailto:contact@spboucher.ai)*
---
## 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:
```mermaid
flowchart LR
A[📄 Invoice / Email
Bank / POS / API] --> B["🧠 LLM
understanding"]
B -->|"AIR events
(never journal entries)"| C["⚙️ AIC Compiler
deterministic rules"]
P["📜 ALSL Policies
taxes · thresholds · rounding
versioned & cited"] --> C
C -->|"balanced entries
+ full provenance"| D["📗 Native Ledger
hash-chained, standalone"]
C --> E["📤 CSV · QuickBooks
Xero · Odoo · SAP*"]
D --> F["📊 Trial balance · Balance sheet
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 + Equity` verified 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
```bash
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 books
```
Full command reference: **[docs/cli-reference.md](docs/cli-reference.md)**
## Sixty seconds of AIR
An economic event — *what happened*, nothing else:
```yaml
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 here
```
The 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_up
```
Same input + same policies = **byte-identical output, always** (property-tested,
and CI compiles the demo twice and `diff`s 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: true
```
## Agents keep books through syscalls — and only syscalls
```python
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 VALID
```
Refused actions are audited like successful ones. Editing any record breaks the chain.
## The seven guarantees
1. **Determinism** — no LLM, network, or clock inside the compiler.
2. **Double entry** — the invariant is verified after *every* pass; violations abort compilation.
3. **Traceability** — provenance graph from every posted line to its source (accounting SSA).
4. **No floats** — exact decimals end to end; floats rejected at every boundary.
5. **No rules in code** — every rate/threshold lives in cited, versioned ALSL policies.
6. **Append-only** — reversals, never edits; ledger and audit log are hash-chained.
7. **Human in the loop** — low-confidence or schema-invalid extractions always route to a person.
## Repository map
| Path | Role | LLVM analogy |
|---|---|---|
| [`schemas/`](schemas/) | AIR JSON Schema | the IR definition |
| [`core/`](core/) | events, Money, provenance, invariants | IR + verifier |
| [`aic/`](aic/) | pass manager, passes, diagnostics, incremental | opt/llc |
| [`alsl/`](alsl/) | rule language + cited policy sets | TableGen |
| [`backends/`](backends/) | native, CSV, QuickBooks (offline-tested) | targets |
| [`kernel/`](kernel/) | ledger, reporting, workspace, audit, reconciliation | runtime |
| [`sdk/`](sdk/) | CLI, agent syscalls, demo agent | libclang |
| [`ingestion/`](ingestion/) | extractors, confidence routing, approval queue | frontend |
| [`docs/spec/`](docs/spec/) | **the AIR/ALSL specification** | LangRef |
| [`docs/adr/`](docs/adr/) | architecture decision records | — |
| [`docs/research/`](docs/research/) | 9 cited research reports (tax law, formats, APIs) | — |
| [`tests/`](tests/) | golden cases, property tests, unit + live tests | lit |
## Documentation
- 📖 **[The AIR Specification](docs/spec/air-spec-v0.1.md)** — the language, the compiler, diagnostics, syscalls, reconciliation
- 🧭 **[CLI Reference](docs/cli-reference.md)** — every command with examples
- 🏛️ **[ADRs](docs/adr/)** — why Python, why REA events, why rounding is policy
- 🔬 **[Research](docs/research/)** — GST/QST from official sources, camt.053/MT940 specs, ERP API surveys, LLVM architecture lessons
- 🤝 **[Contributing](CONTRIBUTING.md)** — the non-negotiable rules CI enforces
## Development
```bash
.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 -v
```
Everything — 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
- [x] 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](mailto:contact@spboucher.ai)