The .agi tree
The hardest decision in any system-of-record is the wire format. Pick wrong and you spend years migrating. Accelerando's bet is that the wire format should be human-readable text that mirrors the schema declaration syntax, because the schema declaration syntax already exists.
The schema is .agi
The Accelerando ERP module declares its entities in .agi, the Agicore DSL:
ENTITY Invoice {
tenant_id: string REQUIRED
customer_id: string REQUIRED
total: float REQUIRED
status: string = "draft"
due_date: datetime
paid_date: datetime
notes: string
TIMESTAMPS
CRUD create, read, list, edit
}
So the on-disk record format mirrors it exactly — RECORD instead of ENTITY, plus an id:
RECORD Invoice 8f3b2e1a-... {
tenant_id : "acme"
customer_id : "cus_001"
total : 1500.00
status : "draft"
due_date : "2026-07-25T00:00:00Z"
notes : "Q3 consulting"
created_at : "2026-06-25T10:00:00Z"
updated_at : "2026-06-25T10:00:00Z"
}
That's the entire format. No JSON. No protobuf. No schema versioning header. The first line tells you which ENTITY it conforms to; the parser reads the ENTITY declaration from the same vendored .agi file the schema is generated from.
The on-disk layout
data/
<tenant_id>/
Invoice/
8f3b2e1a-....agi
ad9320de-....agi
InvoiceLineItem/
c91d7e5f-....agi
...
Customer/
30cf071b-....agi
ServiceTicket/
...
Tenant isolation is structural — there is no tenant_id query parameter that could be forgotten. The path data/acme/Invoice/ literally cannot return globex's invoices.
The ENTITY parser
src/agi-parser.ts reads ENTITY blocks straight out of .agi text and materializes them as runtime EntitySpec objects:
const ents = parseEntities(`
ENTITY Customer {
tenant_id: string REQUIRED
name: string REQUIRED
active: bool = true
TIMESTAMPS
}
`);
// → [{ name: "Customer", fields: [...] }]
The parser handles:
field : type [REQUIRED] [= default]— the basic shapeTIMESTAMPSmacro — expands tocreated_at+updated_at, both requireddatetimeCRUDallowed-ops — captured but not enforced at this layerboolas an alias forboolean(the canonical.agiusesbool)// line comments
It deliberately does NOT understand the full .agi grammar (no AI_SERVICE, STAGES, VIEW, WORKFLOW). Accelerando only needs the ENTITY subset.
The record codec
src/agi-record.ts round-trips a single record through serialize/parse:
const text = serializeRecord(INVOICE_ENTITY, record);
const back = parseRecord(INVOICE_ENTITY, text);
assert.deepEqual(back, record);
It uses the EntitySpec to know which fields exist, their types, and required-ness. The output is canonically formatted (field names aligned, types respected, null values omitted) so two semantically-equal records produce byte-identical files — git diff shows real changes, not whitespace noise.
Why this format
catworks. Operators read records without a tool.git diffshows business-meaningful changes. Status moved from draft to paid? That's the diff.- AI reads it natively. A BabyAI model trained on code reads an
.agirecord without a special parser pass. - The schema and the data share a notation. No "the schema says
floatbut the JSON saysnumber" disagreements.
Read The SQL projection for how this gets fast to query.