Architecture
Four layers, two runtimes, one canonical artifact.
┌──────────────────────────────────────────────┐
│ THE .AGI TREE │
│ data/<tenant>/<Entity>/<id>.agi │
│ one text file per record, RECORD blocks │
└──────────────────────────────────────────────┘
▲ ▲
writes │ │ reads
│ │
┌─────────────────┴─────────┐ ┌────────┴───────────────┐
│ GitStore (TS) │ │ Projection (TS) │
│ create / read / edit / │ │ SQL read-model │
│ transaction(message, fn) │ │ rebuilds from tree │
└─────────┬─────────────────┘ └────────┬───────────────┘
│ │
StorageBackend interface ProjectionBackend interface
│ │
┌─────────┴─────────┐ ┌─────────┴─────────┐
│ NodeFsBackend │ │ SqliteBackend │ ← local dev
│ fs + isogit │ │ better-sqlite3 │
└───────────────────┘ └───────────────────┘
┌───────────────────┐ ┌───────────────────┐
│ R2Backend │ │ D1Backend │ ← production
│ bucket + ETags │ │ cloudflare D1 │
└─────────┬─────────┘ └───────────────────┘
│
│ every */15 min
▼
┌───────────────────┐
│ GitHubSyncer │ ← reconciles R2 → real git
│ Git Data API │ commits on github.com
└───────────────────┘
The layers
- The
.agitree is the canonical system of record. Every record is a text file. The directory structure encodes tenant and entity type. - GitStore owns the create/read/edit/transaction API. It does not know whether it's running on Node or Workers.
- Backends plug into GitStore. The Node pair (
NodeFsBackend+SqliteBackend) runs in tests and local dev. The Workers pair (R2Backend+D1Backend) runs in production. - GitHubSyncer is the bridge from R2's commit-log entries to actual git history on GitHub, batched into one real commit every fifteen minutes via the Git Data API.
The runtime split
The same code compiles to both runtimes. The only thing that changes is which backend you construct:
// Node — used in tests and CLI scripts
const store = new GitStore({ backend: new NodeFsBackend({ root: "./data" }) });
const proj = new Projection(new SqliteBackend("./projection.db"));
// Workers — wired inside the fetch handler
const store = new GitStore({ backend: new R2Backend({ bucket: env.AGI_TREE }) });
const proj = new Projection(new D1Backend(env.PROJECTION));
The interface is small enough that adding a third runtime (browser? Deno? Bun?) is a hundred lines of TypeScript — see Two runtimes, one core.
The HTTP surface
The Cloudflare Worker exposes:
GET /— the navy + gold SPA (this whole wiki is a sibling route)GET /tools— the BabyAI tool catalogPOST /tools/:name— execute a tool call (tenant Bearer token required)GET /whoami— what tenant am I acting as?GET /activity— the audit feed of git writes, tenant-scopedGET /audit-log— the SPC telemetry stream (tool-call audit_log, content-free)GET /audit-log/stats— per-tool aggregates: success rate, p50/p95 latency, Western Electric drift flagsGET /audit-log/workflow-stats— per-workflow rollup joined to Workflow namesPOST /audit-log/:id/verdict— reviewer stamps accept/reject (signal-4)POST /invoices/:id/email— Resend the invoice to the customer's addressGET /digest/config/POST /digest/config— OIE digest config + brandPOST /digest/send-now— fire the nightly digest on demandGET /bookkeeping/config/POST /bookkeeping/config— auto-post + multi-currency + tax-account settingsPOST /admin/*— admin-key-only: mint tenant keys, force sync, rebuild projection
Every non-OPTIONS request that touches data goes through the auth check first. See Per-tenant API keys.
Why this shape
Three decisions are doing most of the work:
- The canonical artifact is text. I can
cata record. I cangit difftwo versions. AI agents can read it with the same skills they read code with. - The query layer is derived. Querying speed lives in a SQL projection that's recomputed, never authored. The canonical truth is always the tree.
- The audit trail is unavoidable. Writes ARE commits. You can't skip the audit log because the audit log is the operation.
Each of those gets its own page in this wiki.