Rust Engine
Admina ships a Rust core engine (admina_core) compiled via PyO3
as a Python extension. The engine provides up to 434× faster governance processing
compared to the pure Python implementation for the full 4-domain pipeline.
Admina auto-detects which engine to use at startup.
Performance
2.08µs 2.33µs 2.50µs 0.62µs 0.67µs 0.71µs 2.38µs 2.67µs 2.75µs 1.00µs 1.12µs 1.25µs 6.25µs 7.04µs 7.29µs
Verified Docker run: Linux aarch64, Python 3.11, single-threaded,
10,000 iterations after 1,000 warmup. Reproduce with
docker build -f Dockerfile.benchmark -t admina-bench . && docker run --rm admina-bench.
7.79µs 2.08µs 3.7× 8.21µs 0.62µs 13.2× 1,992µs 0.62µs 3,213× 505µs 2.38µs 212× 2,261µs 5.21µs 434× Python column includes spaCy NER and sklearn TF-IDF — the production-equivalent path. The large speedup in PII and Loop Breaker reflects replacing ML libraries with compiled Rust.
Selecting the engine
Since v0.10.0 a single switch — ADMINA_ENGINE — selects the
governance-engine backend uniformly across the proxy, the SDK, and the
LangChain/CrewAI callbacks (engines are acquired through one
admina.engines package). This means Admina works without
Rust — the Python engines are slower, but carry the broader detection coverage
(see below).
ADMINA_ENGINE=auto— default. Use Rust for the firewall + loop breaker whenadmina-coreis installed, else Python. PII redaction stays on the Python engine for full recall, and the firewall stays on Python whenadmina.yamlsets a Python-only firewall key (below).ADMINA_ENGINE=python— force the pure-Python engines everywhere (broadest detection coverage).ADMINA_ENGINE=rust— force Rust everywhere, including PII (faster, narrower coverage). An unrecognized value raises at startup.
rust fails instead of falling back ADMINA_ENGINE=rust without admina-core installed raises
EngineSelectionError (a ValueError) from every engine factory —
the SDK included — and the proxy does not start; v0.12 warned and ran the Python engines.
The same error is raised when ADMINA_ENGINE=rust meets a non-empty
agent_security.firewall key that only the Python firewall applies —
custom_patterns, disabled_categories,
disabled_patterns or pattern_packs
(admina.engines.PYTHON_ONLY_FIREWALL_KEYS). Under auto those keys
keep working: the Python firewall runs with them and a warning names the keys. To migrate,
install the [rust] extra or remove the keys, or set
ADMINA_ENGINE=auto or python.
# Check which engines are running curl http://localhost:8080/health # → {"status": "healthy", ..., "engine": {"engine": "rust", "rust_available": true, # "selection": "auto", "active": "rust", "pii_active": "python", # "firewall": "rust", "loop_breaker": "rust", "pii": "python", ...}, ...}
New in v0.13.0: engine.firewall, engine.loop_breaker and
engine.pii (also on /api/stats) name the engines the proxy actually
built, while active and pii_active still report what the selection
resolves to. They can differ: with Python-only firewall keys in admina.yaml under
auto, firewall is python while active is
rust. loop_breaker is null when no enabled surface needs
one (a gateway-only proxy), and pii can also be presidio or a plugin
engine's name. The Prometheus gauge admina_engine_info carries the same split in
its labels (engine, firewall, loop_breaker,
pii, pii_redaction, rust_available,
rust_version, selection, version) — before v0.13.0 its
engine label read rust whenever admina-core was
installed. See the API Reference.
Install — prebuilt wheel
Since v0.9.4 the Rust engine is opt-in via the [rust]
extra. The extra pulls the admina-core wheel from PyPI
and the engine bridge auto-detects it at startup. Since v0.13.0 the extra requires
admina-core>=0.13.0,<0.14: admina-core and
admina-framework are released in lockstep from the same tag, so upgrade the two
together (earlier releases accepted any core from 0.9.3 on).
pip install "admina-framework[rust]" python -c "import admina_core; print(admina_core.version())" # → 0.13.0
The project ships as a single Stable-ABI (abi3-py311)
wheel — one artefact works on Python 3.11+ without per-interpreter wheels.
The Rust and Python firewall/PII engines are not equivalent — this is
measured, not assumed. The Rust firewall uses per-pattern severity (matching the Python
InjectionFirewall reporting model) but its pattern coverage is still narrower.
The Python firewall carries 44 builtin patterns (with evasion
normalisation, Italian baseline patterns since v0.13.0, and pattern packs) against
15 on Rust. Per the bundled red-team efficacy suite: on the 64-sample
injection corpus recall is 62% Python (23/37) vs 35% Rust (13/37), with
zero false positives on the 27 negative samples for both engines — the Python figure rose
from 21/37 in v0.13.0, when two Italian attacks of the corpus started being detected. On the 42-sample PII corpus, type-level micro-averaged recall is
100% Python (29/29 expected types) vs 66% Rust (19/29),
with 6 false positives for Python and 0 for Rust across the 16 negative samples. The loop
breaker inverts the picture — 82% Python vs 91% Rust on its 22-sample
corpus, zero false positives on the 11 negatives. The Rust PII scanner also does not cover
EU national IDs or NER person/org names. So under ADMINA_ENGINE=auto
the firewall and loop breaker run on Rust while PII redaction stays on Python for full
recall; ADMINA_ENGINE=rust opts the PII scanner into Rust too. The
pure-Python install has the broadest detection coverage, and the Rust engine wins on raw
throughput (≈6.25µs full-pipeline median).
The coverage gap above is measured, not asserted. Admina ships a red-team
efficacy suite as part of the package (admina/redteam/) that scores three
detectors — injection, PII and loop breaker — against committed corpora (64, 42 and 22
samples) on every engine available in the environment. Since v0.13.0 it is a command of
the package, admina redteam (scripts/redteam.py now runs the
same command):
# Markdown scorecard on stdout + JSON scorecard in redteam-scorecard.json admina redteam # CI gate: exit 1 on a recall drop or a new false positive vs the packaged baseline admina redteam --gate # Your own corpora (each listed in the directory's SHA256SUMS) and firewall settings admina redteam --corpora-dir ./my-corpora --config ./admina.yaml --gate
Options: --engine both|python|rust, --corpus NAME,
--format md|json|both, --out FILE, --corpora-dir DIR
(external <name>.jsonl corpora in the packaged format, verified against
the directory's SHA256SUMS before the run), --config FILE
(default $ADMINA_CONFIG: builds the injection firewall from that file's
agent_security.firewall settings, as the proxy does),
--baseline FILE, --gate and
--write-baseline [FILE]. Exit status is 0, 1 when --gate finds a
regression, and 2 when the run cannot happen — including --engine rust without
admina-core, or with a --config that sets a Python-only firewall
key. The pinned baseline lives at admina/redteam/baselines/baseline.json, and a
CI test (tests/test_redteam_efficacy.py) fails the build on a recall drop or a
new false positive. Measurements are pinned to the engine mode they were taken in, so metrics
from different engine versions are never silently compared. Since v0.11.0 the suite also
scores the optional Microsoft Presidio PII engine as a third column, when
presidio-analyzer and a supported spaCy model are installed. One caveat: the
PII corpus contains only structured identifiers (email, credit card, IBAN, IP, SSN, phone,
Italian codice fiscale, Spanish DNI/NIE) and carries no PERSON/ORG/place expectations —
the regex engine's home ground — so the PII recall column should not be read as a general
verdict on NER-based engines. See
Data Sovereignty for the engine-selection
details.
Note (v0.9.1 hotfix). admina-core 0.9.0 was yanked
from PyPI due to a PyO3 framework-linking bug that aborted at import on Python
versions other than the build host. The [rust] extra's lower bound
(now 0.13.0) keeps a fresh install well away from the yanked wheel.
Mac Intel (x86_64-apple-darwin) wheels are not published;
Intel users build from sdist via the steps below.
Building the Rust engine from source
Prerequisites
- A stable Rust toolchain (rustup.rs) — CI builds with the current stable
- Python 3.11+ with development headers
- maturin (
pip install maturin)
Build
# Full build: Rust engine + Python install make all # Or manually cd core-rust maturin develop --release # builds + installs into current venv # Python-only mode (no Rust required) make python # uv users: since v0.13.0 uv.lock resolves admina-core from ./core-rust, so this # builds the engine of the same checkout (Rust toolchain required) uv sync --extra rust
The [tool.uv.sources] entry only affects uv sync / uv.lock
in a checkout: a sync without the rust extra needs no toolchain, and the
published package metadata keeps the admina-core>=0.13.0,<0.14 range for
pip install "admina-framework[rust]".
Verify the build
python -c "import admina_core; print(admina_core.version())" # → 0.13.0 python -c "from admina_core import RustFirewall; f = RustFirewall(); print(f.check('test').to_dict())" # → {'is_injection': False, 'risk_level': 'low', 'matched_patterns': [], 'heuristic_score': 0.0, 'heuristic_signals': []}
Rust modules
core-rust/src/firewall.rsRegexSet single-pass injection pattern matching. All 15 patterns compile once into a shared OnceLock RegexSet on first use, so nothing is recompiled per request. Since v0.12.2 the role_hijacking pattern matches whole words only, so text such as "impact assessment" or "the AI Act asks" is no longer flagged.
core-rust/src/pii.rsCompiled PII regex scanner. Email, phone, credit card, SSN, IBAN, IP — all patterns pre-compiled into a single pass.
core-rust/src/loop_breaker.rsNormalised term-frequency vectors and cosine similarity over a sliding window for loop detection. Plain HashMap arithmetic — no linear-algebra crate.
core-rust/src/forensic.rsSHA-256 hash chain for the forensic black box. Uses the sha2 crate for zero-dependency hashing.
Running benchmarks
# Engine microbenchmark (the figures at the top of this page), in Docker docker build -f Dockerfile.benchmark -t admina-bench . && docker run --rm admina-bench # Load test against a running proxy (PROXY_URL, default http://localhost:8080) make bench # --quick: 100 requests, 10 concurrent python scripts/benchmark.py # 500 requests, 20 concurrent python scripts/benchmark.py --requests 10000 --concurrency 50
The load test measures the proxy end to end with whatever engine it runs — it does not
compare engines — and writes terminal, JSON and HTML reports (latency percentiles,
throughput over time) to benchmark-reports/. Since v0.13.0,
scripts/bench_gateway.py measures the time to first chunk the gateway adds and
the event-loop lag, per firewall engine.
Dockerfile notes
The included admina/proxy/Dockerfile compiles the Rust engine during the Docker
build. Since v0.13.0 a failed Rust build fails the image build — earlier
images silently fell back to the Python engines — and the base images are pinned by digest.
The default target is the full image (proxy, NLP and telemetry extras, the Rust engine, the
bundled dashboard); docker build --target slim builds the slim image (the
[proxy] extra and the Rust engine only), published as
ghcr.io/admina-org/admina-proxy:<version>-slim. Release images are labelled
org.admina.engine=rust and signed with cosign — see
Quick Start for cosign verify.
The Dockerfile.benchmark is a separate image for running benchmarks in isolation
without polluting the proxy image.