Measured, Not Modelled

Every published number has a harness behind it.

Aegis publishes two latency figures: 2.9 ms p95 for the prompt and response rails, and 14 ms p95 for full tool & MCP inspection against a 50 ms budget. This page documents exactly how those numbers are produced — the workload, the percentile arithmetic, the hardware, and what is deliberately excluded — so a claim you cannot check becomes one you can.

aegis / benchmark suite baseline 2026-08-29
Prompt & response rails 2.9 ms p95 3000-iteration hermetic run
Tool & MCP inspection 14 ms p95 pessimistic policy, mixed payloads
Published budget < 50 ms p95 holds with 2.5× margin
Not included LLM & network your model's latency is your model's
What Each Number Means

Two figures, two harnesses, one rule.

Both loads are defined once and used twice. On every commit the suite runs them and asserts each against its budget — the published 50 ms gate for tool inspection, and for the rails an internal bound well inside the 300 ms budget they answer to. That is a regression guard, and deliberately a coarse one: it catches a rail that has turned pathological, not a figure that has drifted a few percent. The numbers published on this page come from somewhere stricter. They are produced on demand by make benchmark-baseline as the median of 5 repetitions on an idle machine, frozen into a committed artifact, and substituted into this page at build time — so a published figure moves when someone regenerates the evidence, and cannot drift quietly in between.

2.9 ms — the prompt & response rails

Measures one full pass of the deterministic input and output rails: 3000 iterations over a rotating mix of a benign request, a prompt-injection attempt, and a large (multi-kilobyte) input. The timer wraps only the engine calls — no HTTP, no serialization, no upstream model. This is the overhead added to a turn that carries no tool calls.

14 ms — full tool & MCP inspection

Measures the execution rail — name policy, least-privilege constraints, dangerous-action detection, argument scanning, and tool-result validation — under a deliberately pessimistic setup: a 55-glob enterprise policy with 8 constraint groups, a payload mix that exercises every branch of the precedence chain, and a 32 KiB nested-JSON argument in one sample out of seven.

50 ms — the number that is a promise

Absolute milliseconds vary with hardware; the budget does not. The published gate — tool inspection under 50 ms p95 — is asserted by the test suite on every commit, at the worst-case payload size. That payload measures 19.29 ms p95, a 2.5× margin against the 50 ms budget; the run it is read from is in the next section. If your hardware is slower than ours, that margin is what absorbs the difference.

The Run Itself

The output the two figures above are read from.

The harness writes a JSON artifact and the site is built from it. Every number in this table is substituted at build time, so a figure on this page cannot disagree with the measurement it came from — and the site does not build at all if the artifact is missing, unparseable, or short a figure.

Workload Iterations p50 p95 p99
Prompt & response rails 3000 0.06 ms 2.85 ms 2.96 ms
Tool & MCP inspection 2000 0.02 ms 14.36 ms 14.47 ms
Tool inspection, worst-case payload 200 18.92 ms 19.29 ms 19.98 ms

Run 2026-08-29 on Apple M4 Pro (14-core, macOS 26.5), CPython 3.11.16, coverage instrumentation off — instrumentation inflates the same workload measurably, so a run carrying it is not publishable. Each figure is the median of 5 independent repetitions rather than one run: the first artifact this harness produced was a single reading that happened to be the lowest of nine taken that afternoon, and it sat just under a rounding boundary, so one run decided what this page said. Repetitions are taken on an otherwise idle machine, and the harness refuses to measure a busy one — the gate margin is 50 divided by a tail figure, so load enters a published number as a divisor rather than as noise. At the precision each figure is published to, every repetition read 2.8–2.9 ms for the rails and 14 ms for the tool guard; each one is recorded individually in the artifact, at full precision, rather than summarised away.

The rails p50 and p95 sit far apart on purpose. That workload rotates three inputs and one of them is a 5416-character body, so the p95 is a statement about the large sample and the p50 about the two small ones. A rails figure quoted without the payload it was measured on cannot be checked by anyone, and an uncheckable figure is the kind that goes stale quietly — which is what the last row of the history table below records.

Reproducing this table shell
make benchmark

# Runs these three workloads in-process on your own machine: no gateway, no
# model, no network, no database. Prints p50/p95/p99 per workload and diffs
# them against the committed baseline this page is built from.
Method

The details that make a percentile honest.

A p95 can hide a lot: what was sampled, how the rank was computed, what the machine was doing, and whether the slow tail was trimmed. Here is each choice, stated.

Timing

A monotonic high-resolution timer (time.perf_counter()) wraps the engine calls and nothing else. Every iteration is recorded — no warm-up discards, no outlier trimming. The slow tail is part of the number.

Percentile definition

Nearest-rank on the sorted sample: p95 is the value at rank ⌈0.95 · n⌉. At small n this makes p95 equal the maximum, which is correct — a percentile that reads below an observed value is the misleading kind.

Hardware & environment

One machine: Apple M4 Pro (14-core, macOS 26.5), running CPython 3.11.16 — a single process, standard library, no accelerator. Every figure on this page was measured there and nowhere else, and the artifact names it, so each number reads as "on this hardware" rather than as a universal; the second environment is your own, which is what make benchmark is for. Published figures come from uninstrumented runs: the suite normally runs under coverage tooling, which inflates the same workload measurably, so gate assertions leave headroom for it.

Pessimistic by construction

The tool-inspection workload is worse than production on purpose: every allow-glob is a potential match attempt, constraints add a path pass over every argument leaf, and the oversized sample is an order of magnitude past a realistic call. Payloads beyond the scan bound are refused, not truncated — truncation would be a bypass, so it is not available as a performance optimization either.

End-to-end, separately

A separate concurrent load harness fires thousands of requests at a running gateway over HTTP and reports throughput and latency percentiles with the upstream model stubbed out. It answers a different question — gateway overhead including transport — and is deliberately not the source of the per-rail figures.

What is excluded, and why

Your model's inference time, the network hop to your gateway, and your tools' execution time. Aegis runs in-process in your own infrastructure, so there is no vendor round-trip to include — which is the point of comparison with network-hop guardrail APIs, and the reason the honest comparison is budget against budget.

When the Number Moves, We Move It

A latency figure is a liability the moment it goes stale.

Detectors are code, and code grows. Every time added coverage has cost measurable latency, the published number changed with it — in public, with the reason. This table is the complete history.

Date What changed Measured effect Published figure
Aug 2026 Thirteen non-English detector language packs added to the input rails 1.19× on the rails benchmark, same machine, with and without Rails 1.4 → 1.7 (superseded)
Aug 2026 Detector coverage for reverse shells, download-and-execute, credential stores and destructive cloud-CLI actions — the cost of nine closed bypasses from the internal red team Mixed-workload tool inspection roughly 7.6 → 13 on the same harness; the 50 ms gate re-measured and still held Tool guard 7.6 → 13 (superseded)
Aug 2026 A defect in our own percentile arithmetic: p95 was computed one rank low whenever the rank fell on a fraction, so the p95 of two samples returned the lower one Found by exercising the deployed artifact, not by the suite; fixed at the definition above and regression-locked Definition corrected, published
Aug 2026 The rails figure re-measured for the first time since commit 7a62038 added nine detectors to the input rails. That commit re-measured tool inspection only, so the rails base — 1.4 — carried forward untouched; the 1.19× multilingual ratio in the row above was then applied to it, and 1.4 × 1.19 was published as 1.7. Neither step was wrong on its own. Applying a correctly measured ratio to a stale base is the defect, and closing it is why the harness behind the table above exists. Rails 2.85 ms p95 against 1.7 published — roughly two thirds higher, and the ratio is written that way because a decimal typed here would go stale at the next regeneration; tool inspection 14.36 ms against 13. Every latency figure on this site is now substituted at build time from the committed artifact, so a hand-typed one cannot exist. Separately, in the same pass, four hand-rolled copies of the percentile arithmetic were reduced to one shared definition: its isolated effect on the rails p95 was −0.009%, and downward, so it accounts for none of the gap in either direction. That is a different expression in a different file from the defect two rows above, and it erred the other way: the benchmark copies read one rank high wherever the rank fell exactly on a whole number, so the old figures were marginally pessimistic rather than flattering. The gate margin moves with them: the superseded ≥ 3 named no measurement a reader could check, and it is now 50 divided by the worst-case p95 in the table above, which is itself measured on a tool name the least-privilege stage actually matches — the previous worst case named one no constraint covered, so it returned before the constraint pass and measured less work than the mixed workload above it. Rails 1.7 → 2.9 ms p95, tool guard 13 → 14 ms p95, gate margin ≥ 3 → 2.5×

The build now refuses to ship a page carrying a retired figure: the site's audit step scans the built HTML for every number this table has ever replaced and fails on a match. A stale benchmark is treated as a build error, not a copywriting oversight. The stronger half is upstream of that guard — a live figure is no longer typed into a page at all, so the failure in the last row now needs a regenerated measurement to happen, not a missed find-and-replace.

Check It Yourself

Run the same probes against your own policy.

Design partners get the benchmark harness with the product — the same make benchmark target that produced the table above, run against your own policy on your own hardware. The operator console ships a red-team runner as well, evaluating our adversarial corpus against your policy in-process, generating no traffic.