agent-firstcrash-safeone static binarypay-as-you-go

grange — a document database
your agent can pay for

Agents don't want a database console — they want JSON in, JSON out, and a bill they can settle themselves.

French for "barn" — a place where documents live.

grange is a document DB written in machin: crash-safe WAL, indexes with O(1) aggregates, range queries. Embed it, run it, or use this hosted instance — signup is one curl with a peage wallet.

# get an isolated, metered namespace (the wallet IS the signup)
curl -s -X POST https://grange.intrane.fr/tenants -H 'X-Peage-Wallet: pw_...' -d '{"name":"my agent"}'
# -> {"tenant":"t...","token":"gt_..."}

curl -s -X POST https://grange.intrane.fr/put -H 'Authorization: Bearer gt_...' \
  -d '{"coll":"leads","doc":{"co":"acme","score":9}}'
curl -s 'https://grange.intrane.fr/agg?coll=leads&group-by=co&sum=score' -H 'Authorization: Bearer gt_...'

faster than SQLite where it matters

100k docs, both engines indexed on the same fields, same box (bench ships in the repo — make bench):

workloadgrangeSQLite
bulk insert, 2 indexes maintained278k docs/s25k rows/s
indexed count × 1000<1 ms1.5 s
group-by count/sum/avg × 1000<1 ms (write-time registers)49 s
range count × 1000<1 ms257 ms (indexed)

Crash-safe by construction

Every commit is one immutable, checksummed WAL chunk. kill -9 leaves exactly the committed prefix — the repo ships the harness that proves it.

Race-free, provably

The server is a single actor with zero goroutines; machin's inferred data-race analysis verifies the whole engine on every build. No Send/Sync, no annotations.

RBAC

POST /tokens issues read-only (ro) or read-write (rw) tokens per tenant. Existing tokens default to rw.

Concurrent reads

GRANGE_CONCURRENT_READS=1 auto-spawns a read replica on port+1 and proxies GETs to it — real read concurrency without touching the write path.

Automated failover

POST /promote (admin) flips a --follow follower to primary in one request. The data is already local.

Memory introspection

GET /memory reports RSS, limit, arena resets, loaded collections, and watchdog config. GRANGE_RESET_EVERY reclaims the arena between requests.

Pay-as-you-go

Storage: €0.15/GB/month, first 50 MB free. Queries free. Accrued continuously, charged via peage — no subscription, no card on file.

No lock-in

The engine is MIT OSS. Export with one curl; self-host the same binary when you outgrow the hosted rail.

first 5 minutes

Hosted — signup to first query in 5 curls (get a wallet at peage.intrane.fr):

# 1. signup — the wallet IS the credential, no card, no form
curl -s -X POST https://grange.intrane.fr/tenants \
  -H 'X-Peage-Wallet: pw_...' -d '{"name":"my agent"}'
# > {"tenant":"t...","token":"gt_..."}

# 2. put — store JSON documents, auto-id or your own
curl -s -X POST https://grange.intrane.fr/put \
  -H 'Authorization: Bearer gt_...' \
  -d '{"coll":"leads","doc":{"co":"acme","score":9}}'
# 3. index — equality for agg O(1), range for ordering
curl -s -X POST https://grange.intrane.fr/index \
  -H 'Authorization: Bearer gt_...' \
  -d '{"coll":"leads","field":"co","sums":"score"}'
curl -s -X POST https://grange.intrane.fr/index \
  -H 'Authorization: Bearer gt_...' \
  -d '{"coll":"leads","field":"score","kind":"range"}'
# 4. find — equality, range, ordered, paginated
curl -s 'https://grange.intrane.fr/find?coll=leads&where=score>=5&order=score&desc=true' \
  -H 'Authorization: Bearer gt_...'
# 5. aggregate — group-by count/sum/avg, O(1) at write time
curl -s 'https://grange.intrane.fr/agg?coll=leads&group-by=co&sum=score' \
  -H 'Authorization: Bearer gt_...'

Self-hosted — same engine, no signup:

curl -sSL -o grange https://github.com/javimosch/grange/releases/latest/download/grange-linux-x86_64
chmod +x grange
./grange put   --db ./data --coll leads --doc '{"co":"acme","score":9}'
./grange index --db ./data --coll leads --field co --sums score
./grange index --db ./data --coll leads --field score --range
./grange find  --db ./data --coll leads --where 'score>=5' --order score --desc
./grange agg   --db ./data --coll leads --group-by co --sum score

dogfooding: this page runs on grange

The subscribe form below is not a demo — it is a paying tenant of this hosted instance. The flow: landing form → grange-subscribe (a 130-line machin app) → machin client SDK → hosted grange. Every subscriber email is a document in the subscribers collection, stored and queried like any customer's data. The count is live:

curl -s https://subscribe.grange.intrane.fr/count
# > {"ok":true,"data":{"subscribers":4}}

The subscribe service is a separate process (a grange actor calling its own HTTP API would deadlock), which is also the honest shape of a customer. Source: apps/subscribe/subscribe.src

dogfooding: vigie-sync — a live analytics mirror

vigie is a real analytics product. vigie-sync mirrors its SQLite database into grange as a cold collection — disk-resident, not held in memory. It runs continuously on dk1: a systemd timer syncs every 10 minutes, a daily one re-checks every aggregate against SQLite as the oracle.

# sync new rows from vigie's SQLite (read-only sidecar)
vigie-sync sync --sqlite /opt/vigie/vigie.db --token gt_...

# compare: every aggregate must match SQLite exactly
vigie-sync compare --sqlite /opt/vigie/vigie.db --token gt_...
# > {"match":true,"checks":14,"last_24h_query_ms":3}

14/14 checks match SQLite: total count, per-site counts, per-kind counts, and a 24-hour window. The 24h query — the one every analytics dashboard runs — was a full scan before the cold range index; it now answers in 3 ms on 604 events. The sync cursor itself lives in grange (one more dogfood). SQLite stays the source of truth, so a live product carries none of the risk of the experiment.

get launch + milestone notes

One mail when something ships (new SDKs, replication, pricing changes). Stored — of course — in grange itself: this form's backend is a machin app using the machin client SDK against the hosted service, as a paying tenant.

the interface is the docs

curl -s https://grange.intrane.fr/llms.txt   # the full contract, written for agents