Integrations

Admina integrates with the most popular AI agent frameworks and orchestration tools, in two shapes: sidecar integrations that route traffic through the Admina proxy over HTTP, and in-process integrations that call the governance engines directly inside your own Python process.

Sidecar or in-process. OpenClaw, n8n and Cheshire Cat AI talk to a running Admina proxy and are pointed at it with a URL. The LangChain and CrewAI callbacks run the governance engines in-process: there is no proxy to start and no URL to configure. The model adapters are likewise in-process β€” GovernedModel runs the pipeline around them β€” while the GuardrailsAI guard is a plugin loaded by the proxy itself.

OpenAI-compatible gateway

Since v0.11.0 the proxy exposes an OpenAI-compatible HTTP surface β€” POST /v1/chat/completions (streaming and non-streaming) and GET /v1/models β€” on the same port 8080 as /mcp. Any client, library or front-end that speaks the OpenAI chat-completions API can be pointed at Admina with no code change: set the base URL to http://localhost:8080/v1 and the API key to your ADMINA_API_KEY. Port 8080 is the proxy port under admina dev --stack; in the default zero-Docker local mode (admina dev) the same surface is on :3000. See Quick Start.

from openai import OpenAI

client = OpenAI(
    base_url="http://localhost:8080/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…"}],
)

The prompt runs through the injection firewall, PII redaction and any pluggable governance guards before it leaves Admina; the response is PII-redacted on the way back β€” streamed deltas through a 64-character windowed buffer β€” and every request writes one forensic record. Loop detection does not run on this surface.

Two things to know. A blocked request returns HTTP 200 with a synthetic completion whose finish_reason is content_filter. That is deliberate, so OpenAI-compatible UIs render the refusal rather than erroring β€” which means clients must detect blocks by finish_reason, not by status code. And Admina forwards no credentials upstream, so ADMINA_GATEWAY_UPSTREAM (default http://localhost:11434/v1, a local Ollama) must point at a keyless upstream such as Ollama, a local vLLM server, or LM Studio.

See API Reference β†’ OpenAI-compatible gateway for the full request/response contract, and Configuration for the three ADMINA_GATEWAY_* variables.

OpenClaw

Admina ships with a ready-to-use OpenClaw skill that routes all MCP tool calls from your agent through the Admina governance proxy. The setup steps below install the skill and route the agent's traffic via Admina.

How it works

The admina-governance SKILL.md routes all agent actions through /api/v1/validate and /api/v1/audit. Governed action types include:

  • llm_call β€” LLM prompt and completion governance
  • shell_exec β€” Shell command execution
  • file_write β€” File system write operations
  • http_request β€” Outbound HTTP requests
  • message_send β€” Agent-to-agent or agent-to-user messages
v0.11.0 MODIFY β†’ REDACT: no change needed here. The /api/v1/validate action value returned when a request is allowed but PII was masked changed from "MODIFY" to "REDACT" in v0.11.0. The in-repo admina-governance SKILL.md contains no occurrence of either string: it instructs the agent to branch on other response fields, not on action. A pinned pre-0.11 copy of the skill is therefore not broken by this particular rename. Any custom OpenClaw tooling you wrote around /api/v1/validate that branches on action == "MODIFY" does have to change β€” the current response fields are action, risk_level, checks, redacted_content and latency_ms; see the API Reference.

Skill install

The skill lives at admina/integrations/openclaw/admina-governance/ and ships exactly three files: SKILL.md (the skill itself), admina.yaml (the sidecar's config) and setup.sh. Copy the directory into your OpenClaw skills directory, then run setup.sh: it starts the Admina proxy as a Docker container named admina-openclaw-sidecar on port 18790 (override with ADMINA_PORT), mounts the adjacent admina.yaml read-only at /app/admina.yaml, and waits for /health. The script then prints the ADMINA_PROXY_URL to export β€” the skill reads that variable for every /api/v1/validate and /api/v1/audit call, and refuses to act if the sidecar is unreachable.

# Start the Admina governance sidecar
cd admina/integrations/openclaw/admina-governance
chmod +x setup.sh
./setup.sh

# Point the skill at the sidecar
export ADMINA_PROXY_URL=http://127.0.0.1:18790

# Health check and teardown
./setup.sh --status
./setup.sh --uninstall

LangChain

The AdminaCallbackHandler is a drop-in callback for ChatOpenAI and any other LangChain model. It governs all LLM and tool events without changing your existing chain logic. It runs in-process β€” the firewall, PII redactor and loop breaker are called directly, so there is no proxy to start and no URL or API key to configure.

Governed events

  • on_llm_start β€” Validates prompts before they reach the model
  • on_llm_end β€” Scans completions for PII
  • on_tool_start β€” Firewall, loop and PII check before tool execution
  • on_tool_end β€” PII scan and audit trail of tool results

Configuration

The handler accepts the following parameters:

  • session_id β€” Session identifier for loop detection across calls (default: an auto-generated langchain-… id)
  • pii_redaction β€” Enable PII redaction, Data Sovereignty domain (default True)
  • firewall β€” Enable the injection firewall, Agent Security domain (default True)
  • loop_detection β€” Enable the loop breaker, Agent Security domain (default True)
  • on_block β€” Behaviour when a request is blocked: "raise" (default) raises GovernanceBlockedError, "warn" logs and continues
  • audit β€” Emit events to the governance event bus (default True)

Example

pip install "admina-framework[nlp]" langchain langchain-openai

from admina.integrations.langchain.callbacks import AdminaCallbackHandler
from langchain_openai import ChatOpenAI

handler = AdminaCallbackHandler(
    session_id="my-session",
    pii_redaction=True,
    firewall=True,
)
llm = ChatOpenAI(callbacks=[handler])

# Every call to llm.invoke() is now governed
response = llm.invoke("Summarize this document")

# handler.last_result β†’ GovernanceResult for the most recent check
# handler.get_stats() β†’ session_id, call_count, block_count, redact_count, features

CrewAI

Admina provides two callbacks for CrewAI: AdminaStepCallback fires after each agent step, and AdminaTaskCallback fires after task completion. Both are callables you instantiate per crew, and both run in-process β€” no proxy URL, no API key.

Callbacks

  • AdminaStepCallback β€” Governs each agent step (tool calls, LLM calls, reasoning) with the firewall, PII redactor and loop breaker. Parameters: session_id, pii_redaction, firewall, loop_detection, on_block ("raise" or "warn") and audit.
  • AdminaTaskCallback β€” Redacts and audits completed task outputs. Parameters: session_id, pii_redaction and audit only β€” it does not run the firewall or the loop breaker.

Example

pip install "admina-framework[nlp]" crewai

from admina.integrations.crewai.callbacks import AdminaStepCallback, AdminaTaskCallback
from crewai import Crew, Agent, Task

researcher = Agent(
    role="Researcher",
    goal="Analyze market trends",
    backstory="Senior market analyst",
)
task = Task(
    description="Research Q4 revenue for ACME Corp",
    expected_output="Revenue summary",
    agent=researcher,
)

crew = Crew(
    agents=[researcher],
    tasks=[task],
    step_callback=AdminaStepCallback(session_id="my-crew"),
    task_callback=AdminaTaskCallback(session_id="my-crew"),
)

# Kick off β€” every step and task is governed
result = crew.kickoff()

The module also exports two ready-made instances with default settings, admina_step_callback and admina_task_callback, if you do not need to configure anything: Agent(step_callback=admina_step_callback) and Crew(task_callback=admina_task_callback).

n8n

The n8n-nodes-admina package adds three governance nodes to your n8n workflows:

Install

npm install n8n-nodes-admina

Nodes

  • AdminaGovern β€” Inline governance node. Place it before any AI node in your workflow. Validates inputs, blocks non-compliant requests, and adds governance metadata to the output.
  • AdminaAudit β€” Passive logging node. Place it after any AI node to log all requests and responses to the Admina audit trail without blocking.
  • AdminaDashboard β€” WebSocket trigger node. Streams real-time governance events from Admina into n8n, enabling reactive workflows (alerts, Slack notifications, compliance reports).
Breaking in v0.11.0 β€” MODIFY is now REDACT. The /api/v1/validate response action value returned when a request is allowed but PII was masked changed from "MODIFY" to "REDACT", with no compatibility shim. The in-repo AdminaGovern node and the Cheshire Cat AI plugin were updated in the same release, so update your installed copies. Neither package version was bumped (both are still 1.0.0), so there is no manifest signal β€” any downstream n8n expression comparing json._admina.action to 'MODIFY' must be changed to 'REDACT'. Node parameters (contentField, onBlock, logToForensic) and the Cheshire Cat plugin settings are unchanged; see the API Reference. Admina is pre-1.0 β€” the public API is feature-complete and production-ready, but the stability commitment is deferred to 1.0, which is what permits a rename in a minor release.
n8n community nodes: After installing, restart your n8n instance. There is no dedicated Admina category β€” the nodes use standard n8n groups, so search the palette for "Admina": Admina Govern is a transform node, Admina Audit an output node, and Admina Dashboard a trigger.

Cheshire Cat AI

Admina integrates with Cheshire Cat AI as a Python plugin. The plugin hooks into the Cat's event system to govern agent actions at three critical points.

Governed hooks

  • agent_fast_reply β€” Intercepts the agent's reply before it is sent to the user. Enables PII redaction, toxicity filtering, and compliance checks on outputs.
  • before_cat_sends_message β€” Final checkpoint before the message leaves the Cat. Applied after all other plugins have processed the response.
  • before_cat_recalls_memories β€” Governs memory recall to prevent leaking sensitive data from the vector store.
Breaking in v0.11.0 β€” MODIFY is now REDACT. All three hooks in admina_governance.py branch on result.get("action") == "REDACT" before substituting redacted_content; before v0.11.0 the same three branches compared against "MODIFY". A pinned pre-0.11 copy of the plugin therefore fails open against a v0.11 proxy: the redacted branch never fires, so the un-redacted user message is left in working memory, the un-redacted reply is sent to the user, and the un-redacted text is used as the RAG query. Blocking is unaffected β€” the BLOCK branches are untouched, and only the PII-redaction path silently stops applying. The plugin version was not bumped (still 1.0.0 in plugin.json), so there is no manifest signal: copy the v0.11.0 plugin over your installed one. Plugin settings are unchanged.

Install

The plugin lives at admina/integrations/cheshirecat/admina-plugin/. Its setup.sh starts the Admina proxy as a Docker sidecar on port 18790; copy the plugin folder into your Cat, point it at the sidecar with the ADMINA_PROXY_URL environment variable, and enable it from the admin panel. The plugin reads its configuration from the environment β€” ADMINA_PROXY_URL (default http://localhost:18790) and ADMINA_TIMEOUT in seconds (default 5) β€” not from a settings file.

# Start the Admina governance sidecar
cd admina/integrations/cheshirecat
./admina-plugin/setup.sh

# Copy the plugin into your Cat's plugins directory
cp -r admina-plugin/ <your-cheshire-cat>/plugins/admina-plugin/

# In the Cat's .env or docker-compose.yml
ADMINA_PROXY_URL=http://host.docker.internal:18790

GuardrailsAI

Admina ships GuardrailsAIGuard, a built-in governance guard (admina.plugins.builtin.guards.guardrailsai_guard, registered under the name guardrailsai) that wraps GuardrailsAI validators as a stage of Admina's own pipeline. You do not construct it yourself β€” the proxy discovers every built-in guard at startup and builds it with the matching plugin_config block from admina.yaml. All inference runs locally β€” no data leaves your infrastructure.

Packaging note. The PyPI distribution guardrails-ai is currently in quarantine, so the [guardrailsai] extra is still not published with admina-framework as of v0.11.0. The plugin itself remains in the codebase and is auto-detected at runtime if you install the guardrails-ai distribution (imported as guardrails) separately, e.g. from a local wheel or mirror.

Install

# From a local wheel or mirror β€” PyPI itself will not serve it while
# the distribution is quarantined. Admina auto-detects it on startup.
pip install ./guardrails_ai-<version>-py3-none-any.whl

Wrapped validators

Four validator names are mapped to guardrails.hub classes; any other name raises ValueError. Extra keys on a validator entry are passed straight to the GuardrailsAI validator constructor, and on_fail defaults to "noop" so Admina β€” not GuardrailsAI β€” decides the action. Set on_fail explicitly on a validator entry and your value wins, which hands the action back to GuardrailsAI.

  • toxic_language β€” guardrails.hub.ToxicLanguage: toxic or harmful language
  • detect_pii β€” guardrails.hub.DetectPII: personally identifiable information
  • detect_jailbreak β€” guardrails.hub.DetectJailbreak: prompt injection and jailbreak attempts
  • bias_check β€” guardrails.hub.BiasCheck: biased content

Configuration

The guard is wired in the top-level plugin_config map of admina.yaml, keyed by the plugin name:

plugin_config:
  guardrailsai:
    inference_mode: local     # local only β€” see below
    validators:
      - name: toxic_language
        threshold: 0.5
      - name: detect_pii
        entities: ["EMAIL_ADDRESS", "PHONE_NUMBER", "IBAN"]
      - name: detect_jailbreak
        threshold: 0.8

There is no enabled switch: the guard is instantiated whenever the guardrails package imports successfully, and skipped with a warning when it does not. Note that admina.yaml.example also carries a domains.agent_security.domains.guardrailsai block β€” the config loader does not read it, so put your settings under plugin_config.

At request time the guard runs every configured validator over the payload's content field and returns action: "ALLOW" with risk_level: "LOW" when validation passes, or action: "BLOCK" with risk_level: "HIGH", the validator error string in details and the class names of GuardrailsAI's failed-validation entries in metadata.failed when it does not. Empty content is allowed without running anything. The guard blocks or allows β€” it never redacts.

Local-only inference, enforced. inference_mode accepts local only, and it is the default. Anything else β€” including remote β€” makes the constructor raise ValueError at startup rather than sending prompts outside the deployment perimeter. There is no environment variable that relaxes this.

Model provider adapters

Where the framework integrations above govern agent traffic, model adapters let the SDK govern direct calls to an LLM provider: wrap a provider in GovernedModel and every prompt and completion runs the full pipeline (loop β†’ firewall β†’ PII β†’ guards). Each adapter is a built-in plugin that lazy-imports its provider SDK, so the dependency is only required when the adapter is actually used. Since v0.10.0 there are seven, installed via per-provider extras:

ProviderInstallKey env varsStreaming
OpenAIadmina-framework[openai]ADMINA_OPENAI_API_KEY / ADMINA_OPENAI_MODELreal
Ollama (local)admina-framework[ollama]ADMINA_OLLAMA_HOST / ADMINA_OLLAMA_MODELreal
Anthropicadmina-framework[anthropic]ADMINA_ANTHROPIC_API_KEY / ADMINA_ANTHROPIC_MODELreal
Mistraladmina-framework[mistral]ADMINA_MISTRAL_API_KEY / ADMINA_MISTRAL_MODELfallback
AWS Bedrockadmina-framework[bedrock]ADMINA_BEDROCK_REGION / ADMINA_BEDROCK_MODEL (standard AWS credential chain)fallback
Google Geminiadmina-framework[gemini]ADMINA_GEMINI_API_KEY / ADMINA_GEMINI_MODELfallback
vLLM (local, OpenAI-compatible)admina-framework[adapters]ADMINA_VLLM_BASE_URL / ADMINA_VLLM_MODEL (model required)real

New in v0.11.0: real means the adapter implements BaseModelAdapter.send_stream() against the provider's own streaming API, so you get incremental deltas. fallback means it inherits the base implementation, which awaits the full response and yields it as a single chunk β€” functionally correct, but with no latency benefit. The fallback is a documented interim state, not the intended end design: the v0.11.0 release notes record the three fallback adapters as pending a follow-on, and the 0.11.0 design note targets native send_stream() implementations for them in the 0.11.x line. Governance is identical either way β€” a single-chunk response still runs through the same redactor β€” so only the delta granularity changes when they land.

Two roll-up extras bundle them: [adapters] pulls every provider, and [all] is [proxy,nlp,telemetry,adapters,presidio] for a complete install. The older [full] extra is [proxy,nlp,telemetry] and does not include Presidio.

pip install "admina-framework[anthropic]"

from admina.plugins.builtin.adapters.anthropic import AnthropicAdapter
from admina.sdk import GovernedModel

# The Anthropic call now runs through the full governance pipeline
model = GovernedModel(model_name="claude-sonnet-4-5", adapter=AnthropicAdapter())
resp = await model.ask("Summarise this contract clause…")
# resp.action β†’ "ALLOW" | "REDACT" | "BLOCK"   (firewall + guards run by default since v0.10.0)

Streaming (v0.11.0)

GovernedModel.stream(prompt, **kwargs) is an async generator consumed with async for. It yields plain str deltas, already PII-redacted, and is async-only β€” there is no stream_sync(). Three kwargs are intercepted by stream() itself: context (the system prompt, handed to the adapter as its own context argument), stream_window_chars (default 64) and session_id (enables loop detection) β€” the last two are never passed on; everything else is forwarded to the provider. Once the iterator is fully exhausted, model.last_stream_result is a plain dict β€” not a dataclass, so access it with ["action"], never .action β€” with exactly eight keys: action, pii_count, model, input_tokens, output_tokens, finish_reason, time_to_first_token_ms and duration_ms. input_tokens and output_tokens are always None: there is no token accounting on the streaming path. action is "ALLOW", "REDACT" (PII was found and masked) or "BLOCK". A blocked prompt yields zero deltas and returns normally β€” no exception is raised β€” with last_stream_result["action"] == "BLOCK" and finish_reason of content_filter.

from admina.plugins.builtin.adapters.ollama import OllamaAdapter
from admina.sdk import GovernedModel

model = GovernedModel(model_name="llama3.1:8b", adapter=OllamaAdapter())

async for delta in model.stream("Summarise this contract clause…"):
    print(delta, end="", flush=True)

# Read the outcome AFTER the iterator is exhausted
print(model.last_stream_result)
# β†’ {'action': 'REDACT', 'pii_count': 1, 'model': 'llama3.1:8b',
#    'input_tokens': None, 'output_tokens': None, 'finish_reason': 'stop',
#    'time_to_first_token_ms': 41.2, 'duration_ms': 812.5}
How the streaming redactor works. StreamRedactor holds back a trailing window of stream_window_chars characters (default 64) so a PII entity split across two deltas is still masked. Two consequences: emission lags by about one window, and a response shorter than the window arrives as a single delta at the end. The window must also exceed the longest expected entity β€” an entity longer than stream_window_chars can be emitted before it is fully visible, and therefore un-redacted. Note too that RetryPolicy is not applied to .stream() (it only wraps ask()), that last_stream_result is only written when the iterator runs to completion β€” breaking out of the loop early leaves the previous value β€” and that there is no mid-stream block: once deltas start flowing, content is redacted, never halted.

StreamRedactor on your own stream

StreamRedactor is a public export β€” from admina.sdk import StreamRedactor β€” and is pure text-in/text-out with no I/O, so you can drive it over any stream you already own (a provider SDK you call directly, an SSE relay, a websocket) without going through GovernedModel. Its constructor is StreamRedactor(pii_redactor, window_chars=64): the first argument is any object implementing the PIIBridge protocol β€” i.e. exposing redact(text) returning a dict with redacted_text and count β€” and admina.engines.get_pii_engine() returns the configured one. window_chars below 1 raises ValueError.

Mind the parameter name. The constructor kwarg is window_chars; the GovernedModel.stream() kwarg for the same setting is stream_window_chars. They are not interchangeable, and both mistakes are quiet: StreamRedactor(engine, stream_window_chars=128) raises TypeError, while model.stream(prompt, window_chars=128) never reaches the redactor at all β€” stream() pops only stream_window_chars, so window_chars falls through into the kwargs handed to the adapter's send_stream() as an ordinary provider request parameter and the window stays at 64. What happens next is the provider SDK's business: it may reject the unknown parameter or ignore it, but either way the window was never changed.

Two methods drive it. feed(delta) takes one raw delta and returns a list[str] of 0 or more redacted, safe-to-emit deltas β€” the list is empty while the buffer is still inside the window, or while an entity straddles the emit boundary. finish() flushes the held window and returns a (final_redacted_tail, summary) tuple, where summary is {"pii_count": N} for the whole stream. Calling finish() is mandatory: skip it and the last window_chars characters of the response are never emitted.

from admina.engines import get_pii_engine
from admina.sdk import StreamRedactor

redactor = StreamRedactor(get_pii_engine(), window_chars=64)

for raw in my_provider_stream:          # any iterable of raw str deltas
    for safe in redactor.feed(raw):    # 0+ redacted deltas per call
        print(safe, end="", flush=True)

tail, summary = redactor.finish()        # always flush at end of stream
print(tail, end="")
print(summary["pii_count"])            # entities masked across the stream
Full governance by default (v0.10.0). GovernedModel.ask() now runs the injection firewall and any pluggable guards on the prompt β€” not just PII redaction β€” and can return action="BLOCK" with empty text. Opt out per stage with GovernedModel(firewall_enabled=False, …). Loop detection runs when you pass a session_id per call.