AgentFGA · Report · October 2026

Benchmarks.

Agents ask for permission before every tool call and on every document they retrieve, so each check has to be cheap. Here is how quickly AgentFGA answers, how much traffic it handles, and how it compares with OpenFGA. The tests ran against a deployed instance from a test machine in Australia.

Measured 2026-10-18Re-run 2026-10-26Test machine in Australia

§ 01 · Summary

The short version.

6ms

to answer a check on the server when the store is in the same data center as the request. About 21 ms from one region away.

1.1Mrelationships

in the largest store tested. Checks were as fast as in a store ten times smaller.

~540req/s

from one store. Past that, extra requests wait in line instead of failing.

6,799req/s

across 16 stores, with zero errors. Capacity grows as you add customers.

These numbers are from 2026-10-18. A re-run a week later was slower: 12–30 ms per check and about 256 requests a second for one store. The stores landed in different data centers that time, and the older version measured the same day was slower too, so the code itself didn't regress. See the re-run.

§ 02 · Store size

Bigger stores aren't slower.

We filled three stores with a Google-Drive-like dataset of users, nested groups, folders and documents, then timed the same checks on each.

StoreRelationshipsWhere it ranTypical check
Small1,366Sydney, the same data center as the test traffic6 ms
Medium112,472Melbourne21 ms
Large1,124,085Melbourne21 ms

Typical means the median, timed on the server. The trip from the test machine to the nearest data center added about 11 ms on top.

The medium and large stores differ tenfold in size and took the same time. The small store was quicker only because of where it was placed: the hop from Sydney to Melbourne adds about 15 ms. Every kind of check took the same time, from a direct grant to a walk through nested folders and groups.

Queries that return lists take longer. On the largest store, finding the 1,000 documents a user can view took 69 ms, and finding the 225 users who can view a document took 40 ms. Writing one relationship took 20–40 ms.

§ 03 · Throughput

How much traffic one store handles.

Each store runs as a single instance that works through requests one at a time. We sent a mix of checks to the largest store from more and more clients at once.

Requests answered per second

Largest store. No errors at any level.

Throughput levelled off at about 540 requests a second. More clients didn't cause errors; requests just waited longer, and the typical response time rose from 22 ms to 462 ms.

Batching raises the limit. BatchCheck sends up to 50 checks in one request. With 25 per request, one store answered about 3,600 checks a second.

§ 04 · Scaling

More stores, more capacity.

Stores never query each other, so each one runs on its own. We spread the same mix of checks across 1 to 16 small stores.

Requests answered per second

No errors in any run.

256 clients at once 1,024 clients at once

16 stores answered 6,799 requests a second. The limit was the single machine generating the traffic, not AgentFGA. In an agentic product, give each customer or workspace its own store.

§ 05 · Comparison

Compared with OpenFGA.

We ran AgentFGA's engine and OpenFGA v1.22.0 on the same PC, loaded the same 112,472 relationships into each, and timed the same queries.

Typical time (median)AgentFGAOpenFGA
Check: direct owner0.01 ms1.9 ms
Check: owner counts as editor counts as viewer0.02 ms7.0 ms
Check: through 3 levels of groups0.04 ms31 ms
Check: through nested folders and groups0.15 ms38 ms
Check: denied, after searching everything0.12 ms37 ms
Check: user is blocked0.20 ms1.7 ms
Check: time-limited access0.02 ms1.1 ms
BatchCheck: 25 checks18 ms¹683 ms
ListObjects: 1,000 documents9.7 ms360 ms
ListUsers: 186 users17 ms25 ms

OpenFGA on its SQLite database with default settings. ¹ Timed inside the local development server (wrangler dev).

Not a perfect like-for-like test. AgentFGA was timed inside its own process, while OpenFGA was timed over a local HTTP connection, which adds about 0.5 ms. OpenFGA's recommended production database, Postgres, wasn't tested, so OpenFGA may be faster in production than shown here.

OpenFGA does better in two places. With its optional caches turned on, it answered repeated ListObjects queries faster (about 6 ms against 10 ms), though cached answers can be up to 10 seconds old. And one OpenFGA process uses every CPU core, while one AgentFGA store handles one request at a time; AgentFGA gets more capacity by adding stores instead.

§ 06 · Data

Full results.

Every number comes from the test harness in bench/. The complete write-up, including the performance fixes these tests led to, is in BENCHMARKS.md.

Every query, by store size

Client is the full round trip from the test machine in Australia. Server is measured from the moment the request reached the nearest data center. Median of 40 requests each, measured 2026-10-18.

Median, mssmall · Sydneymedium · Melbournelarge · Melbourne
clientserverclientserverclientserver
Check: direct owner17633213221
Check: owner → editor → viewer17632213120
Check: 3 levels of groups17631213121
Check: nested folders and groups17630213221
Check: denied17631203121
Check: user is blocked17631213221
Check: time-limited access17632213221
BatchCheck: 25 checks221045315139
ListObjects: a user's documents 300 · 1,000 · 1,000 results321981638769
ListObjects: public documents 0 · 1,000 · 1,000 results18676618469
ListUsers: a shared document 151 · 186 · 225 users331950365540
Expand: a document's viewers17632203120
Read: one document's relationships18632213121
Write: 1 relationship312049384837
One store under load

The largest store, with clients sending requests back to back for 8–10 seconds. Half the checks are for a document's owner and half for a random user, which forces a full search.

Clients at onceRequests/sClient median / slowest 1%, msServer median / slowest 1%, msErrors
12733 / 18322 / 350
821037 / 6424 / 400
3240679 / 12164 / 980
64441138 / 331110 / 2100
128540239 / 334214 / 3130
256512491 / 734462 / 7130

Even a trivial request that reads nothing topped out at about 680–700 per second, so the limit is the single store instance itself, not the permission logic. With BatchCheck (25 checks per request):

Clients at onceBatches/sChecks/sServer median, ms
4751,87538
8952,37569
161213,025117
641453,625429
Many stores
StoresClients at onceRequests/sClient median, msServer median, msErrors
12567053633350
22561,2292111800
42562,326108910
82563,43670530
162564,38154370
161,0246,7991411140
Every comparison with OpenFGA

Same PC (AMD Ryzen 7 7700, Windows 11), 40 timed requests per query after warm-up, measured 2026-10-26. OpenFGA's caches answer repeated queries for up to 10 seconds, so cached results can be out of date.

Median, msAgentFGAOpenFGA SQLiteOpenFGA in-memoryOpenFGA SQLite + caches
Check: direct owner0.011.92.63.0
Check: owner → editor → viewer0.027.09.05.7
Check: 3 levels of groups0.04313710
Check: nested folders and groups0.1538869.2
Check: denied0.12374226
Check: user is blocked0.201.75.71.6
Check: time-limited access0.021.14.41.3
BatchCheck: 25 checks18¹683665890
ListObjects: a user's documents 1,000 results9.7360915.9
ListObjects: public documents 1,000 results10198585.3
ListUsers: a shared document 186 results17253731
Write: 1 relationship–5.8425.7

Bold marks the fastest in each row. ¹ Timed inside wrangler dev.

Under the same mix of checks used in the deployed tests, one OpenFGA process on this PC handled 10–47 requests a second, and from 32 clients up some requests ran past its 3-second deadline. It managed 600–820 a second when every check was a simple direct one.

The re-run on 2026-10-26

After the compatibility work, the final version was deployed as a new instance and measured again. This time the small store landed in Brisbane, about 25 ms from the Sydney data center the traffic entered, and the large one in Sydney.

Server mediansmall · Brisbanemediumlarge · Sydney
Checks25–30 ms23–24 ms12–15 ms
BatchCheck: 25373560
ListObjects: 1,000 results50²51–5264–70
ListUsers905789
Write: 1 relationship453828

² 300 results on the small store.

The Sydney hop itself was slower that day for both versions: the old version's Sydney store took 10–13 ms per request, against 6 ms a week earlier.

One store (large, Sydney)2026-10-182026-10-26
Mix of checks, requests/s540256
BatchCheck of 25, checks/s3,6251,400
Trivial request, requests/s680–700394–511

A same-day comparison on fresh stores shows the new version isn't the cause:

2026-10-26old versionnew version
Trivial request, requests/s405–410503–511
Bulk loading, relationships/s4,2066,791
Cold starts

The first request after a deploy, or after 4 minutes idle, compared with a warm request.

Client / server, mssmallmediumlarge
First request after a deploy91 / 4780 / 6676 / 63
First request after 4 minutes idle100 / 3579 / 6383 / 65
Warm request21 / 731 / 2132 / 21
Test data and how to reproduce

A Google-Drive-like model (bench/dataset.ts), generated from a fixed seed at three sizes:

  • Groups nested 3 levels deep.
  • Folders nested 4 levels deep, whose viewers and editors carry down to what's inside.
  • Documents with owners, and access granted directly or through groups.
  • Blocked users, and time-limited access checked with a condition.
StoreRelationshipsUsersGroupsFoldersDocuments
small1,3662002050300
medium112,47210k1k4k30k
large1,124,085100k10k40k300k

.bench.env holds AGENTFGA_URL and AGENTFGA_KEY and is git-ignored.

shell
set -a && . ./.bench.env && set +a

npx tsx bench/load.ts small|medium|large [concurrency] [copies]   # create + bulk-load stores
npx tsx bench/run.ts latency small medium large                    # per-query latency
npx tsx bench/run.ts throughput large 1,8,32,64,128,256 10         # one store under load
BENCH_WORKLOAD=batch25 npx tsx bench/run.ts throughput large 4,8,16,64 8
npx tsx bench/run.ts horizontal small 1,2,4,8,16 256 8             # needs: bench/load.ts small 4 16
npx tsx bench/run.ts cold 240                                      # first request after idle
npx tsx bench/engine.ts                                            # engine only, local node:sqlite

# The same suites against another server, e.g. OpenFGA on :18090:
BENCH_URL=http://127.0.0.1:18090 BENCH_STORES=bench/results/stores-upstream.json npx tsx bench/load.ts medium 8
BENCH_URL=http://127.0.0.1:18090 BENCH_STORES=bench/results/stores-upstream.json npx tsx bench/run.ts latency medium