Quick Start
Get Admina running in under 5 minutes. The default workflow is
zero-Docker β a single uvicorn process serves both
the proxy API and the bundled dashboard on :3000 (auto-fallback to
the next free port if it is taken). Docker Compose remains available for the
full stack with ClickHouse, Grafana and OTEL collector.
- Python 3.11+ (pinned in .python-version)
- Platforms β Linux x86_64 (Ubuntu 20.04+, Debian 11+, Fedora 38+, RHEL 9+) and macOS arm64 (12 Monterey+); both tested in CI
- Optional: Docker 24+ with Docker Compose v2 for the full stack
- 4 GB RAM available (full stack runs 8 containers)
Step 1 β Install Admina
# Recommended β proxy + infrastructure deps (what makes `admina dev` work) pip install "admina-framework[proxy]" # Broad install β [full] is [proxy,nlp,telemetry] and does not include Presidio; # the complete roll-up is [all] = [proxy,nlp,telemetry,adapters,presidio] since v0.11.0 pip install "admina-framework[full]" # Optional: Rust-accelerated engine (opt-in since v0.9.4) pip install "admina-framework[rust]" # Optional: Microsoft Presidio as a selectable PII engine (new in v0.11.0 β # the default engine stays spacy-regex until you set ADMINA_PII_ENGINE=presidio) pip install "admina-framework[presidio]" # Advanced β SDK only (embed governance in your own app; no local dev server) pip install admina-framework
The default install runs the pure-Python governance engines. The Rust
accelerator is opt-in via the [rust] extra β
it pulls the admina-core wheel from PyPI, and
ADMINA_ENGINE=auto (the default) uses it for the firewall + loop
breaker while keeping PII on Python for full recall. The two engines are not
equivalent β the Python engines carry broader detection coverage, and the gap
is measured and tracked by the shipped red-team baseline rather than assumed.
See the Rust engine guide.
The [nlp], [full], [presidio] and
[all] extras
require spaCy models, which PyPI does not allow as direct-URL dependencies.
Presidio supports English and Italian, and each language is active only if its
model is installed. After install, run:
python -m spacy download en_core_web_sm it_core_news_sm it_core_news_sm is only needed for the Presidio engine's Italian
support β the default spacy-regex engine uses
en_core_web_sm.
[proxy] boots without spaCy admina dev boots with the [proxy] extra only.
spaCy is imported lazily β without the [nlp] extra, PII redaction
runs in regex-only mode, still covering email, phone, SSN, IBAN,
IP address, credit card and EU national IDs. Add [nlp] when you
also want NER-based entities (person, organisation, location).
Step 2 β Initialize a project
admina init my-project cd my-project # Scaffolds admina.yaml, docker-compose.yml, .env, and a runnable main.py
Step 3 β Start the proxy + dashboard
admina dev has three execution modes:
# 1. Default β zero-Docker local mode (one uvicorn serves API + dashboard) admina dev # 2. Full Compose stack (proxy + dashboard + redis + clickhouse + otel + grafana) admina dev --stack # 3. Stack + local AI infra (ollama + chromadb + open-webui) admina dev --with-llm # Expose on the LAN instead of localhost: admina dev --public # or --host 0.0.0.0
If you cloned the repo directly and prefer Docker Compose without the Python
CLI, bootstrap a .env with random credentials first:
git clone https://github.com/admina-org/admina.git cd admina ./scripts/bootstrap-secrets.sh # writes + prints random API key and dashboard password (.env) docker compose up --build # startup banner masks the key β re-read it with `cat .env`
Step 4 β Verify governance is active
In local mode everything β API, dashboard and Swagger UI β is on
:3000. Under --stack the proxy is a separate container
on :8080, so swap the port in the commands below.
# Health check (always public) curl http://localhost:3000/health # { # "status": "healthy", # "service": "admina-proxy", # "version": "0.11.1", # "engine": { # "engine": "python", # "rust_available": false, # "rust_version": null, # "selection": "auto", # "active": "python", # "pii_active": "python" # }, # "timestamp": "2026-01-01T12:00:00+00:00" # } # Check stats (auth required when ADMINA_API_KEY is set) curl http://localhost:3000/api/stats -H "X-API-Key: $ADMINA_API_KEY"
engine is an object, not a string. With the [rust] extra
installed it reports "active": "rust" while
"pii_active" stays "python" under the default
ADMINA_ENGINE=auto, which keeps full PII recall.
Step 5 β Explore the dashboard
In local mode the dashboard opens without a login β the proxy hands the browser
a signed, expiring session cookie so the SPA can call the API. HTTP Basic auth is
a --stack feature: the dashboard container puts it in front of nginx
when ADMINA_DASHBOARD_PASSWORD is set, using the credentials printed
by admina dev or bootstrap-secrets.sh.
Local mode β admina dev
http://localhost:3000 Governance dashboard β Admina Score, OISG adequacy, live event feed, EU AI Act gaps http://localhost:3000/docs Interactive API documentation (Swagger UI) β same process, same port Full stack β admina dev --stack
http://localhost:3000 Governance dashboard β served by nginx in the dashboard container http://localhost:8080 Proxy container β MCP proxy, admin API, /v1 gateway and /docs http://localhost:3001 Grafana β OTEL metrics and traces http://127.0.0.1:8123 ClickHouse HTTP interface β bound to localhost only http://127.0.0.1:4317 OTEL collector, OTLP gRPC (4318 HTTP, 8888/8889 Prometheus) β localhost only Step 6 β Send your first governed request
Route your AI agent's MCP calls through Admina instead of directly to the MCP server.
In local mode the proxy listens on port 3000 (8080 under --stack).
/mcp forwards to UPSTREAM_MCP_URL, which local mode leaves
empty β point it at your MCP server before this step, or run
admina dev --stack, which wires the bundled mock MCP server for you.
/mcp is also authenticated, unlike /health.
# Send a test MCP call through the governance proxy curl -X POST http://localhost:3000/mcp \ -H "X-API-Key: $ADMINA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", "method": "tools/call", "params": { "name": "read_file", "arguments": {"path": "/etc/hosts"} }, "id": 1 }' # The response includes governance metadata: # X-Admina-Governance-Action: ALLOW # X-Admina-Latency-Us: 6.25 # X-Admina-Event-Id: evt_01J... # X-Admina-Forensic-Hash: a3f8c9...
Step 7 β Point any OpenAI client at Admina
Since v0.11.0 the same proxy port also serves an OpenAI-compatible surface β
POST /v1/chat/completions and GET /v1/models β so any
client that speaks the OpenAI chat-completions API can be governed without a
code change. Point Admina at your model server first β these go in
.env or the shell that launches admina dev, since settings
are read once at start-up:
# Upstream OpenAI-compatible API Admina forwards to β must include the /v1 base path. # Default: http://localhost:11434/v1 (a local Ollama) export ADMINA_GATEWAY_UPSTREAM=http://localhost:11434/v1 # Optional β comma-separated allow-list applied to GET /v1/models (default: empty = passthrough) export ADMINA_GATEWAY_MODELS_ALLOWLIST=llama3.1:8b # Optional β text returned to the caller when governance blocks a request # (default: "This request was blocked by the Admina governance policy.") export ADMINA_GATEWAY_BLOCK_MESSAGE="Blocked by policy."
Then talk to http://localhost:3000/v1 instead of the upstream
(http://localhost:8080/v1 under --stack). The
API key is your ADMINA_API_KEY β the proxy accepts it as
Authorization: Bearer or X-API-Key, so an OpenAI SDK's
api_key parameter works as-is.
curl -X POST http://localhost:3000/v1/chat/completions \ -H "Authorization: Bearer $ADMINA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "llama3.1:8b", "messages": [{"role": "user", "content": "Summarise this clauseβ¦"}] }'
from openai import OpenAI client = OpenAI( base_url="http://localhost:3000/v1", # Admina, not the provider api_key="<your ADMINA_API_KEY>", ) resp = client.chat.completions.create( model="llama3.1:8b", messages=[{"role": "user", "content": "Summarise this clauseβ¦"}], ) print(resp.choices[0].message.content)
Streaming works the same way β Admina re-emits the upstream as Server-Sent
Events, redacting PII across delta boundaries, and always terminates the stream
with data: [DONE]:
for chunk in client.chat.completions.create( model="llama3.1:8b", messages=[{"role": "user", "content": "Summarise this clauseβ¦"}], stream=True, ): print(chunk.choices[0].delta.content or "", end="")
finish_reason
When governance blocks a prompt, the gateway returns HTTP 200
with a synthetic completion whose finish_reason is
content_filter (streaming: one chunk carrying the block message with
the same content_filter, then data: [DONE]). This is
deliberate β OpenAI-compatible UIs render the refusal instead of erroring β so your
client must detect blocks by finish_reason, never by status code.
Admina also forwards no credentials upstream, so
ADMINA_GATEWAY_UPSTREAM must point at a keyless server such as Ollama,
a local vLLM, or LM Studio.
What just happened?
Every call you made was:
- Scanned for PII in both directions (Data Sovereignty)
- Checked for prompt injection patterns (Agent Security β Firewall)
- Checked for infinite loop signatures (Agent Security β Loop Breaker)
- Added to the SHA-256 hash chain audit log (Compliance β Forensic)
- Classified for EU AI Act risk level (Compliance)
- Recorded as an OpenTelemetry span (Compliance β OTEL) β only with the
[telemetry]extra installed andOTEL_ENDPOINTset, which--stackdoes for you; bareadmina devclears it - Counted into the Admina Score at
/api/dashboard/scoreβ the live runtime composite (residency, audit coverage, EU AI Act coverage, recent attacks, forensic chain)
Two exceptions on the /v1 gateway from Step 7: loop detection does not
run on that surface, and a gateway request writes its forensic record without
emitting an OpenTelemetry span. Everything else in the list applies.
Your OISG score at /api/dashboard/oisg is a
static capability assessment β 4 pillars Γ 5 criteria Γ 5 points,
each criterion satisfied when the corresponding subsystem is present and configured.
It does not move with traffic: a stock instance reads the same after one request as
after a million. Nor does it start at the top β a bare admina dev leaves
five criteria unsatisfied (o1 and i3 want AI infra + RAG enabled, o5 and s5 want the
Rust engine, g3 wants a governance-guard plugin), capping it at 75 in the
Good coverage band until you enable them.
See OISG Adequacy for the full paradigm.
Next steps
Troubleshooting
First stop: admina doctor
The CLI ships with a diagnostic command that inspects your environment and reports
which checks pass or fail β Python version, Rust engine availability, plugin discovery,
admina.yaml validity, and infrastructure reachability.
admina doctor Containers fail to start
Check that all required ports are free. The stack binds 3000, 3001 and 8080 on all
interfaces, plus 8123, 4317, 4318, 8888 and 8889 on localhost only.
Run docker compose logs proxy for detailed error output.
Rust engine not detected
Admina automatically falls back to the Python implementation if the Rust binary is not available.
The health endpoint's engine object will show
"rust_available": false and "active": "python". To enable Rust, ensure the
admina_core binary is compiled for your platform (see Rust engine guide).
Need help?
Open an issue on GitHub or start a discussion in GitHub Discussions.