accelerando.wiki ↗ app ↗ github

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:

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

Read The SQL projection for how this gets fast to query.