# Hosted and self-hosted
Source: https://docs.adrian.secureagentics.ai/hosted-vs-open-source
Two ways to run Adrian's backend: hosted by Secure Agentics, or self-hosted from the open-source release.
Adrian is available as a free hosted service managed by Secure Agentics, and as a self-hosted open-source release. Both share the same architecture and SDK integration. The hosted route is the quickest way to get started, and is free (forever) with a generous fair-use policy. The self-hosted route is for those that prefer to run stuff locally, or who need data sovereignty.
## Hosted
Run by Secure Agentics on managed AWS infrastructure and server-grade GPUs. No setup beyond installing the SDK and pointing it at the hosted backend at wss\://adrian.secureagentics.ai/ws.
Expected latency is roughly **100-600 ms per event classification**. Treat this as rough guidance. Actual latency depends on:
* Region
* Server load at the time
* Size of the event being classified
* Severity and complexity of the classification
Latency was benchmarked using L4, L40S, and H100 GPUs. Other GPU classes have not yet been measured.
Sub-60 ms latencies have been achieved in testing with optimisations that are not yet in production. Production rollout is planned and will reduce these numbers further.
## Self-hosted
Self-hosted Adrian runs the Go backend, the Next.js dashboard, and a bundled Llama.cpp container serving Gemma 4 (E2B or E4B) entirely on your own infrastructure. Bring-up is a single `docker compose --profile llm up` after a one-shot bootstrap. See the [Backend reference](/reference/backend) for configuration and endpoints.
Expected latency is roughly **\~500 ms per event classification** on Gemma 4 E4B running on an NVIDIA RTX 5070 Mobile. Treat this as rough guidance. Actual latency depends on:
* GPU class (server-grade GPUs run faster; older or smaller-VRAM GPUs slower)
* Model variant (Gemma 4 E2B is smaller and faster than E4B)
* Server load at the time
* Size of the event being classified
* Severity and complexity of the classification
# How it works
Source: https://docs.adrian.secureagentics.ai/how-it-works
Adrian's architecture, event flow, and operating modes.
The diagram below shows Adrian's open-source architecture. The hosted version follows the same flow.
## Architecture
```mermaid theme={null}
flowchart TD
Agent[Agent Runtime] --> SDK[Adrian SDK]
SDK --> Backend[Adrian Backend]
Backend --> Classifier[Classifier Model]
Classifier --> Verdict{"Verdict
"}
Verdict --> Control[Control Plane]
Verdict -.->|allow / block / human review| Agent
```
## Flow
The Adrian SDK wraps your agent runtime and captures activity (tool calls, outputs, side effects) and reasoning traces as the agent runs. Each event is shipped to the Adrian backend over a persistent websocket connection.
The backend assembles context across the agent's session, sends the event to the classifier model, and returns a verdict. The verdict is routed back to the agent and to the control plane, where alerts surface and humans can intervene.
The SDK can point at a hosted Adrian backend or a locally running open-source backend. See [Hosted vs Open Source](/hosted-vs-open-source) for the trade-offs.
## Detection engine
Adrian's detection engine evaluates each event against a rule set focused on misaligned, malicious, and out-of-remit behaviour. The output is a classification verdict that the rest of the system uses to decide what happens next.
## Operating modes
| Mode | Behaviour | When to use |
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
| **Audit** | Alert-only. Adrian reports security alerts but does not block agent actions. | Observability without changing agent behaviour; default for most deployments. |
| **Human Review** | Adrian pauses potentially harmful actions until you (the human) approve or reject. | When you want a human in the loop for risky actions but don't want to fully automate blocking. |
| **Block** | Adrian reviews every agent action prior to execution and automatically blocks actions deemed malicious, without human oversight. | High-stakes environments where the cost of an unsafe action outweighs the cost of latency. |
Alerting thresholds and notification preferences are configurable per deployment.
# Integrations
Source: https://docs.adrian.secureagentics.ai/integrations
Adrian connects to your agent framework and to the alerting channels you already use.
## Frameworks
The framework integration attaches the Adrian SDK to your running agent and captures activity logs and reasoning traces. Set up starts in your [dashboard](https://app.adrian.secureagentics.ai), where you generate the API key the SDK uses.
**Supported today**
* **LangChain.** Wrap an existing LangChain agent with two lines of code via the Adrian SDK (Python).
* **Anthropic SDK.** Calling `messages.create` directly, without a framework? The same Python SDK patches the Anthropic client. See the [Anthropic SDK](/reference/anthropic-sdk) reference.
* **OpenAI.** Wrap your OpenAI client with the Adrian [TypeScript SDK](/reference/typescript-sdk) - one line, every Chat Completions call captured.
* **Claude Code.** Integrates as a Claude Code plugin rather than the SDK - it hooks every tool call, classifies it, and enforces per your policy, with no code to write. See the [Claude Code plugin](/reference/claude-code) reference.
**Coming next**
We're exploring framework integrations including OpenClaw, OpenAI Agents SDK, and CrewAI. Tell us what you want next on our [Discord](https://discord.gg/6nmJ9k3u6).
## Alerting channels
Adrian sends one-way alerts to the channels you choose, based on your configured thresholds. Approvals and human-in-the-loop interventions are handled through the dashboard only at launch. Channel setup, thresholds, and policies are all configured from your [dashboard](https://app.adrian.secureagentics.ai).
**Supported today**
* **Slack.** Connect from your dashboard, pick a channel, set your alert threshold.
* **Discord.** Connect from your dashboard, pick a channel, set your alert threshold.
**Coming next**
WhatsApp, Microsoft Teams, PagerDuty, and others. We are also exploring dynamic two-way conversations / approvals / configurations with Adrian for deeper personalisation to your specific use case. Tell us what you want next on our [Discord](https://discord.gg/6nmJ9k3u6).
# Introduction
Source: https://docs.adrian.secureagentics.ai/introduction
Adrian is a real-time security monitoring and control engine for AI agents that analyses agent activity logs (tool calls, actions, outputs) and reasoning traces to detect malicious, misaligned, or out-of-remit behaviour and optionally intervene in-flight.
AI agents are moving from chat to autonomous action across real systems. Most people building with them have minimal visibility into what their agents are doing after they have been deployed, and even less control over what they are about to do.
Adrian addresses this directly:
* **Oversight.** What an agent has done, why, and what it is about to do next.
* **Detection.** Malicious, misaligned, or out-of-remit actions, classified by severity.
* **Control.** Pause or block specific actions, with optional human-in-the-loop approval.
Install the Python SDK with just 2 lines of code, self-hosted or hosted backend options.
## Why Adrian is different
Most agent monitoring stops at activity logs: tool calls, outputs, side effects. Adrian also analyses the agent's reasoning traces and thought process. Lab research by OpenAI and DeepMind found that combining behaviour and reasoning analysis boosts detection accuracy by around 35%, and Adrian is the first tool to put that into a deployable security control. You understand not just what decisions an agent made, but why, and what it is planning to do next.
Similarly, most tools in this space are machine learning classifiers trained to spot patterns in their training data. Adrian takes a different approach: it uses world models that understand risk through reasoning. It correlates behaviours across a session, holds a working understanding of what the agent is meant to be doing, and assesses each new action against that. The thought process is closer to a human reviewer's than to pattern matching against examples it has seen before.
## When to use Adrian
If your agent has write access to any system, takes actions on external services, or operates with any meaningful autonomy, you need runtime monitoring. If you are shipping a tool-less chatbot, you do not need this yet. If you are unsure, start in Audit mode (notify-only, no intervention) and let the events tell you.
## Quick look
```python theme={null}
import adrian
adrian.init(api_key="adr_live_...", ws_url="wss://adrian.secureagentics.ai/ws")
# Your LangChain / LangGraph agent runs unchanged - every call is captured.
# Calling the Anthropic SDK directly works the same way.
```
The example points `ws_url` at Adrian Cloud. The SDK's default is `ws://localhost:8080/ws` (a self-hosted backend), so set `ws_url=` or `ADRIAN_WS_URL` to `wss://adrian.secureagentics.ai/ws` for the hosted service.
Wrap a LangChain agent with two lines of code and see your first detection.
# Quickstart
Source: https://docs.adrian.secureagentics.ai/quickstart
Wrap a LangChain agent with Adrian and see your first event in the dashboard.
Wrap a LangChain agent with Adrian in two lines and watch events appear in your dashboard.
Calling the Anthropic SDK directly instead of using a framework? The steps below are the same up to step 3. From there, follow the [Anthropic SDK](/reference/anthropic-sdk) reference.
Go to [app.adrian.secureagentics.ai](https://app.adrian.secureagentics.ai) and create an account.
From the dashboard, create an agent profile (name, remit, and execution mode - Audit / Human Review / Block) and generate an API key bound to it. We operate a generous fair-use policy based on total tokens used across all keys on the account, viewable in the dashboard.
```sh theme={null}
pip install adrian-sdk
```
Requires Python 3.12+. Install the LangChain provider for your agent's model alongside it:
```sh theme={null}
pip install langgraph langchain-openai # or langchain-anthropic, etc.
```
```python theme={null}
import asyncio
import adrian
from langchain_openai import ChatOpenAI
async def main():
adrian.init(api_key="adr_live_...", ws_url="wss://adrian.secureagentics.ai/ws")
# Your LangChain / LangGraph code runs normally - every call is captured.
llm = ChatOpenAI(model="gpt-4o")
response = await llm.ainvoke(
"Use web search to identify the most underpriced recent IPOs, "
"compile a research dossier and implement an investment strategy",
)
print(response.content)
adrian.shutdown()
asyncio.run(main())
```
The `ws_url` above points at Adrian Cloud. The SDK's default is `ws://localhost:8080/ws` (a self-hosted backend), so set `ws_url=` or `ADRIAN_WS_URL` to `wss://adrian.secureagentics.ai/ws` for the hosted service.
Use the async pattern (`asyncio.run` + `await llm.ainvoke`) rather than sync `llm.invoke` - the WebSocket transport runs on the asyncio loop, and sync `llm.invoke` returns before the loop has a chance to flush events.
Set `OPENAI_API_KEY` in your environment alongside `ADRIAN_API_KEY` for the snippet above; `ChatOpenAI` reads it directly.
Run your agent. Within seconds, events appear in the dashboard's event feed with their classification verdicts and severity codes. See [Severity codes](/reference/severity-codes) for what each M-code means.
That's it: your first agent event is in Adrian. See [How it works](/how-it-works) for what happens to it, or [Integrations](/integrations) to route alerts to Slack or Discord.
## Demo
## Watch the walkthrough
# Anthropic SDK
Source: https://docs.adrian.secureagentics.ai/reference/anthropic-sdk
Reference for Adrian's Anthropic SDK instrumentation: install, usage, invocations, streaming, and enforcement.
Adrian instruments the [Anthropic SDK](https://docs.anthropic.com/) directly, for agents that call `messages.create` rather than going through a framework. Every call on both `anthropic.Anthropic` and `anthropic.AsyncAnthropic` is captured as a `PairedEvent` and streamed to the Adrian backend. Your call sites stay unchanged.
This ships inside the [Python SDK](/reference/sdk) rather than as a separate package, so configuration, callbacks, and the `PairedEvent` schema are shared with the LangChain integration and documented there.
## Install
```sh theme={null}
pip install "adrian-sdk[anthropic]"
```
Requires Python 3.12+. The extra pins a supported `anthropic` version. Plain `pip install adrian-sdk` also works, since the instrumentation patches whichever `anthropic` your project already depends on. If the package is absent, Adrian skips Anthropic patching and everything else continues as normal.
## Initialise
`init` and `shutdown` bracket your normal Anthropic code.
```python theme={null}
import asyncio
import os
import adrian
import anthropic
async def main():
adrian.init(api_key="adr_live_...", ws_url="wss://adrian.secureagentics.ai/ws")
# Your Anthropic code runs normally, and every call is captured.
client = anthropic.AsyncAnthropic(api_key=os.environ["ANTHROPIC_API_KEY"])
async with adrian.anthropic_invocation():
response = await client.messages.create(
model="claude-opus-5",
max_tokens=1024,
system="You are a helpful assistant.",
messages=[{"role": "user", "content": "What is 2 + 2?"}],
)
# Thinking blocks can precede the text block, so select by type.
print(next(b.text for b in response.content if b.type == "text"))
adrian.shutdown()
asyncio.run(main())
```
For synchronous code use `adrian.anthropic_invocation_sync()`.
## Grouping related calls
An invocation is Adrian's unit of work. A single Anthropic call is not one, so wrap related calls to group them under a shared `invocation_id`.
```python theme={null}
async with adrian.anthropic_invocation():
first = await client.messages.create(...)
second = await client.messages.create(...) # same invocation_id
```
Calls made outside an invocation are still captured, but carry `invocation_id="no_invocation"` and cannot be correlated with each other.
## Streaming
Text deltas stream through untouched. The event is emitted when the final message is requested.
```python theme={null}
async with adrian.anthropic_invocation():
async with client.messages.stream(
model="claude-opus-5",
max_tokens=1024,
messages=[{"role": "user", "content": "Count to five."}],
) as stream:
async for text in stream.text_stream:
print(text, end="", flush=True)
message = await stream.get_final_message() # emitted and gated here
```
## Enforcement modes
The agent profile's execution mode is set in the dashboard and pushed to the SDK in the `LoginAck` frame. See [Severity codes](/reference/severity-codes) for what each MAD code means.
| Mode | Behaviour |
| ------------ | -------------------------------------------------------------------------------------------------------- |
| Alert | The response passes through unchanged. Events are classified and surfaced in the dashboard. |
| Block | Each `tool_use` block in the response waits on its verdict before the response is returned to your code. |
| Human Review | As Block, but the verdict waits on a dashboard decision instead of timing out. |
Under Block and Human Review, a halted tool call never reaches your execution loop. Halted blocks are rewritten to a text block reading `[BLOCKED by security policy]`, and `stop_reason` is downgraded from `tool_use` to `end_turn` so agentic loops terminate cleanly.
The gate fails closed. If no `LoginAck` arrives within 5s all tool calls are blocked, and in Block mode a verdict timeout blocks the tool call.
## Manual instrumentation
`init()` patches the Anthropic SDK automatically. To control when that happens:
```python theme={null}
adrian.init(api_key="adr_live_...", auto_instrument=False)
adrian.patch_anthropic()
```
Patching is idempotent and safe to call more than once.
## What's captured
Each call produces one `PairedEvent` with `pair_type="llm"`. `LlmPairData` carries the model, the flattened message list including the system prompt, the output text, requested `tool_calls`, and token usage. Content blocks are normalised to strings, so a `tool_use` block appears as `[tool_use: name args={...}]` in the message text.
Agent identity is derived from the system prompt, since the Anthropic SDK exposes no framework-level agent boundary. Two agents sharing a system prompt share an `agent_id`.
# Backend
Source: https://docs.adrian.secureagentics.ai/reference/backend
Reference for the self-hosted Adrian backend: bring-up, configuration, and endpoints.
The Adrian backend is a single Go server that combines the WebSocket ingestion endpoint, the dashboard REST API, and the in-process classifier engine. It runs alongside a Next.js dashboard and a Llama.cpp container serving a local Gemma 4 model under Docker Compose.
The full open-source release lives at [github.com/secureagentics/Adrian](https://github.com/secureagentics/Adrian). This page covers the configuration surface and the externally-visible endpoints; for step-by-step bring-up and operational walkthroughs, see the [repository README](https://github.com/secureagentics/Adrian#self-hosting).
## Bring-up
```sh theme={null}
git clone https://github.com/secureagentics/Adrian
cd Adrian
# One-shot bootstrap: creates data/adrian.db, applies migrations,
# generates an admin password, downloads Gemma 4 E4B (~5 GB) into ./models/.
docker compose --profile setup run --rm setup bootstrap
# Start backend + dashboard + classifier
docker compose --profile llm up -d
# Dashboard at http://localhost:3000
# WebSocket ingestion at ws://localhost:8080/ws
```
Requires Docker + Docker Compose v2 and an NVIDIA GPU with the [NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html). Around 10 GB free disk for the bundled classifier model.
## Configuration
Backend config is read from environment variables loaded via the `.env` file the bootstrap writes. Override values directly in `.env`, or use the `setup set-model` subcommand to update model-related settings.
| Variable | Default | Purpose |
| ----------------------------------- | -------------------------------------------- | -------------------------------------------------------------------------------------------- |
| `ADRIAN_LLM_URL` | `http://adrian-llm:8081/v1/chat/completions` | Classifier endpoint. POSTed to verbatim - no path appending. |
| `ADRIAN_LLM_API_KEY` | `local-no-auth` | Bearer token sent on classifier requests. The bundled llama.cpp ignores it. |
| `ADRIAN_LLM_MODEL` | `local` | Model id sent in the request body's `model` field. |
| `ADRIAN_LLM_MODEL_PATH` | `/models/` | In-container path the `llm` service loads at start. Set by `bootstrap --gguf `. |
| `ADRIAN_LLM_CTX_SIZE` | `8192` | Llama.cpp context window. Higher = more history, more VRAM. |
| `ADRIAN_BACKEND_PORT` | `8080` | Host-side port for the Go server (WebSocket + dashboard API). |
| `ADRIAN_DASHBOARD_PORT` | `3000` | Host-side port for the Next.js dashboard. |
| `ADRIAN_PII_REDACT` | `true` | Toggle the SDK-side PII regex sweep (default on). |
| `ADRIAN_SLIDING_WINDOW_SIZE` | `16` | Per-agent ring buffer of recent classified turns the prompt prepends for cross-step context. |
| `ADRIAN_SLIDING_WINDOW_TTL_SECONDS` | `86400` | TTL for inactive sliding-window entries before the in-memory cache evicts them. |
| `ADRIAN_SESSION_SECRET` | (generated) | Dashboard session-cookie secret. The bootstrap mints a fresh one on first run. |
## Endpoints
The Go server exposes three externally-visible surfaces on the backend port (default `8080`).
### WebSocket ingestion
```
ws://localhost:8080/ws (or wss:// behind TLS)
```
This is the endpoint the [Adrian SDK](/reference/sdk) connects to. Authentication is via `Authorization: Bearer ` on the upgrade. Frames are protobuf-encoded `ClientFrame` (`login` / `paired_batch` / `mcp_inventory`) and `ServerFrame` (`login_ack` / `verdict`).
### Health probes
| Path | Purpose |
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `/healthz` | Liveness only. Returns `ok` as soon as the Go process is up. Does not say the backend can classify. |
| `/readyz` | Readiness. Returns `{"ok": true, "checks": {"db": "ok", "classifier": "ok"}}` once the database is reachable AND the classifier upstream has answered. Returns `503` with the failing subsystem named while the model is still loading or if the upstream is unreachable. |
Compose's healthcheck for the backend service polls `/readyz`, so `docker compose --profile llm ps` reporting `(healthy)` is the canonical "stack is fully up" signal.
### Dashboard API
REST endpoints under `/api/` for the Next.js dashboard - authentication, agents, policies, events, verdicts, reviews, webhooks. These are internal to the bundled dashboard at v1 and are not part of a stable public API; see the [repo source](https://github.com/secureagentics/Adrian/tree/main/backend) for current routes.
## Architecture
```mermaid theme={null}
flowchart TD
Agent[Agent runtime] --> SDK[Adrian SDK]
SDK --> Backend[Adrian backend]
Backend --> Classifier[Llama.cpp / Gemma 4]
Classifier --> Backend
Backend --> Verdict{Verdict}
Verdict --> Dashboard[Dashboard]
Verdict -.->|allow / block / human review| Agent
```
The classifier is the bundled Llama.cpp container running Gemma 4 (E2B or E4B by default). The model is downloaded by the bootstrap step; swap variants via `setup set-model --gguf `.
## Operational tasks
### Reset the admin password
```sh theme={null}
docker compose --profile setup run --rm setup reset-password
```
Generates a new random password, updates the SQLite admin row, and prints the plaintext to stdout. The password is shown once and never persisted to disk; if you lose it, run `reset-password` again. Pass `--password ` for a non-interactive flow.
### Switch the local GGUF
```sh theme={null}
docker compose --profile setup run --rm setup set-model \
--gguf gemma-4-E2B-it-Q4_K_M.gguf --ctx-size 16384
```
Updates `ADRIAN_LLM_MODEL_PATH` in `.env` and the `llm` service picks up the new model on next restart. The GGUF must already be present under `./models/`.
# Claude Code Plugin
Source: https://docs.adrian.secureagentics.ai/reference/claude-code
Reference for the Adrian Claude Code plugin: install, configuration, commands, enforcement modes, and what it captures.
The Adrian Claude Code plugin is a runtime security plugin for [Claude Code](https://claude.com/claude-code). It hooks every tool call the agent makes, classifies it with the Adrian backend, and enforces the result per your policy - blocking, prompting, or allowing before the tool runs. Unlike the [Python SDK](/reference/sdk), there is no code to write: it installs from the plugin marketplace and configures through a guided command.
```
Claude Code -> Plugin hook -> Adrian backend (WebSocket, protobuf)
| |
| ├─ Classify (M0 / M2 / M3 / M4)
| ├─ Store event + verdict
| └─ Return verdict
|
├─ allow (benign / policy off)
├─ ask (Human Review: prompt to approve)
└─ deny (block before the tool runs)
```
## Install
From inside Claude Code:
```
/plugin marketplace add secureagentics/Adrian
/plugin install adrian-cc@adrian
# Reload so the plugin's slash commands register - without this, /adrian-init won't appear yet:
/reload-plugins
/adrian-init
```
1. **Add the marketplace** - registers the `adrian` marketplace from the Adrian repo.
2. **Install the plugin** - installs `adrian-cc` from that marketplace.
3. **Configure** - `/adrian-init` picks a backend, writes `~/.adrian/.env`, and verifies the connection.
Monitoring is active for new Claude Code sessions once `/adrian-init` reports OK.
No `pip install` step. The plugin vendors its only third-party dependencies (protobuf, websockets, certifi), so a working Python 3.12+ is all it needs.
## Configure
`/adrian-init` is the guided path: it asks which backend you want (Adrian Cloud, a self-hosted OSS backend, or a custom URL), writes `~/.adrian/.env`, and asks you to drop your API key into that file - never into the chat. It then verifies the connection and reports the backend mode.
For Adrian Cloud, generate a key (`adr_live_...`) in your [dashboard](https://app.adrian.secureagentics.ai) and paste it into `~/.adrian/.env` when prompted.
## Commands
| Command | Description |
| ---------------- | ----------------------------------------------------------------------------- |
| `/adrian-init` | Guided setup: choose backend, write `~/.adrian/.env`, verify the connection. |
| `/adrian-status` | Show the configured backend URL, whether a key is set, and connection health. |
## Configuration
`/adrian-init` writes `~/.adrian/.env`. A value already set in the environment wins; otherwise a project-local `.env` (in the directory you launch Claude Code from) takes precedence over `~/.adrian/.env`.
| Variable | Default | Purpose |
| --------------------------- | ------------------------ | ------------------------------------------------------------------------------------------------ |
| `ADRIAN_API_KEY` | *(required)* | API key for backend auth. `adr_live_...` for Adrian Cloud. |
| `ADRIAN_WS_URL` | `ws://localhost:8080/ws` | Backend WebSocket URL. `/adrian-init` sets `wss://adrian.secureagentics.ai/ws` for Adrian Cloud. |
| `ADRIAN_CC_VERDICT_TIMEOUT` | `15` | Seconds to wait for a verdict before applying the fail-open / fail-closed rule. |
| `ADRIAN_CC_FAIL_OPEN` | `true` | On timeout or backend error, allow (`true`) or block (`false`) the tool call. |
## Enforcement modes
Enforcement is server-driven: the backend policy decides the mode on login and the plugin acts accordingly. Nothing is hard-wired in the client.
| Mode | Behaviour |
| ----------------------- | ------------------------------------------------------------------ |
| **ALERT** | Log only. Never blocks. |
| **BLOCK** | Deny in-scope high-risk tool calls before they execute. |
| **HITL** (Human Review) | Prompt you in Claude Code to approve or deny the tool call inline. |
Which severity codes are *in scope* for action is also policy-driven. The default policy acts on high-risk codes (`M3` / `M4`), but a policy can act on any code. See [Severity codes](/reference/severity-codes) for what each M-code means and [How it works](/how-it-works) for the operating modes.
## What's captured
* **Every tool call.** Classified in real time at `PreToolUse`, before the tool runs.
* **Tool outputs.** Captured at `PostToolUse`.
* **Sub-agent activity.** `Agent` tool spawns are tracked with parent / child hierarchy.
* **Invocation grouping.** Events are grouped by the user prompt that triggered them.
* **Reasoning and instructions.** Claude's reasoning and your instructions are extracted from the session transcript.
Events and verdicts appear in your [dashboard](https://app.adrian.secureagentics.ai). PII is redacted before data leaves your machine - see [Security and Privacy](/security-and-privacy).
## Requirements
* **Claude Code.**
* **Python 3.12+** on your PATH (`python3`, `python`, or the `py` launcher on Windows).
* **Windows only:** Git for Windows (Git Bash). Claude Code runs plugin hooks under Git Bash by default; a `.cmd` launcher is bundled as a cmd.exe fallback.
* An Adrian backend and API key - Adrian Cloud, or a self-hosted OSS backend. `/adrian-init` walks you through it.
## Known limitations
* **Fail-open by default.** If the backend is unreachable or a verdict times out, tool calls are allowed so your workflow is never blocked by an outage. Set `ADRIAN_CC_FAIL_OPEN=false` to fail closed. A quiet session can mean the backend was simply unreachable - check `/adrian-status` first.
* **Effective next session.** Hooks load at session start, so installing, updating, or reconfiguring the plugin takes effect in new Claude Code sessions.
* **Pre-execution classification.** `PreToolUse` judges a tool call from its parameters before it runs; the verdict is based on the requested action, not its result.
## Updating
New versions ship through the marketplace. Refresh the marketplace, then update the plugin - a restart applies it:
```
/plugin marketplace update adrian # refresh the marketplace from its source
/plugin update adrian-cc@adrian # pull the latest plugin version (restart to apply)
```
# Python SDK
Source: https://docs.adrian.secureagentics.ai/reference/sdk
Reference for the Adrian Python SDK: installation, configuration, callbacks, and the events it emits.
The Adrian SDK is a Python package that attaches to your agent runtime, captures activity and reasoning, and ships them to the Adrian backend. It auto-instruments LangChain / LangGraph and the Anthropic SDK, and emits **paired events**: each LLM call and each tool execution is assembled into a single `PairedEvent` carrying agent identity, parent context, and paired payload.
Under LangChain the pairs are assembled from callbacks (`chat_model_start` + `llm_end`, `tool_start` + `tool_end`). Calling the Anthropic SDK directly is covered on its own page, since the call surface and what can be captured differ: see [Anthropic SDK](/reference/anthropic-sdk). The configuration, callbacks, and `PairedEvent` schema below apply to both.
## Install
```sh theme={null}
pip install adrian-sdk
```
Requires Python 3.12+.
## Initialise
```python theme={null}
import asyncio
import adrian
from langchain_openai import ChatOpenAI
async def main():
adrian.init(api_key="adr_live_...", ws_url="wss://adrian.secureagentics.ai/ws")
# Your LangChain / LangGraph code runs normally - every call is captured.
llm = ChatOpenAI(model="gpt-4o")
response = await llm.ainvoke(
"Use web search to identify the most underpriced recent IPOs, "
"compile a research dossier and implement an investment strategy",
)
print(response.content)
adrian.shutdown()
asyncio.run(main())
```
The example points `ws_url` at Adrian Cloud. The SDK's own default is `ws://localhost:8080/ws` (a self-hosted backend), so set `ws_url=` or `ADRIAN_WS_URL` to `wss://adrian.secureagentics.ai/ws` for the hosted service.
Use the async pattern (`asyncio.run` + `await llm.ainvoke`) rather than sync `llm.invoke`. The WebSocket transport runs on the asyncio loop, and sync `llm.invoke` returns before the loop has a chance to flush events.
## Configuration
All parameters are optional; unset values fall back to env vars, then defaults.
| Parameter | Env var | Default | Purpose |
| ---------------------- | ----------------------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `api_key` | `ADRIAN_API_KEY` | `None` | Required for the hosted / self-hosted backend. |
| `ws_url` | `ADRIAN_WS_URL` | `ws://localhost:8080/ws` | Adrian backend WebSocket URL. Set `wss://adrian.secureagentics.ai/ws` for the hosted backend. |
| `log_file` | `ADRIAN_LOG_FILE` | `events.jsonl` | JSONL output path (when no `handlers=` override). |
| `session_id` | `ADRIAN_SESSION_ID` | persistent per-cwd UUID | Stable session identifier. Persisted across runs. |
| `block_timeout` | `ADRIAN_BLOCK_TIMEOUT` | `30.0` | Max wait for a verdict in `MODE_BLOCK`. Ignored in `MODE_ALERT` (no wait) and `MODE_HITL` (waits indefinitely). |
| `replay_buffer_frames` | `ADRIAN_REPLAY_BUFFER_FRAMES` | `1000` | Ring-buffer size for resending frames after a transient outage. |
| `auto_instrument` | - | `True` | Patch LangChain and the Anthropic SDK at init time. Set `False` to attach the handler manually via `adrian.get_handler()`, or to patch Anthropic yourself with `adrian.patch_anthropic()`. |
| `log_level` | - | `None` | Optional override for the `adrian` logger's level. `None` inherits from the application's logging config; pass `"DEBUG"` to force-enable verbose SDK logging. |
| `handlers` | - | `None` | Override default handlers with a custom list. When set, neither the JSONL handler nor the WebSocket client is registered automatically. |
## Callbacks
Register any of the following on `adrian.init(...)` to observe the event and verdict stream. Sync or async callables are both accepted.
| Callback | Fires on | Receives |
| --------------- | ------------------------------------------------- | ----------------------------------------------------------- |
| `on_event` | Every `PairedEvent` emission | `(event_type, data, run_id, parent_run_id, event_id)` |
| `on_verdict` | Every verdict returned by the backend | `VerdictContext` |
| `on_audit` | NOTIFY-tier (`M2`) verdicts | `VerdictContext` |
| `on_block` | BLOCK-tier (`M3` / `M4`) verdicts | `VerdictContext` - notification only; halt is policy-driven |
| `on_mcp_server` | An MCP server is registered or its details change | `McpServer` |
| `on_disconnect` | WebSocket loss | `reason: str` |
| `on_reconnect` | WebSocket re-established after a prior disconnect | - |
`VerdictContext` carries the event ID, session ID, original event type/data, run IDs, the classifier's `mad_code` + `escalate` flag, the active `policy` snapshot, and a `hitl` field present only on dashboard-resolved verdicts.
## PairedEvent shape
Each paired event is a dataclass serialised identically to JSONL and protobuf. Fields:
```python theme={null}
PairedEvent(
event_id: str # unique per pair
invocation_id: str # spans one user prompt across all sub-agents
session_id: str
run_id: str # LangChain run_id, or generated per call
parent_run_id: str # for tool pairs: the producing LLM's run_id
timestamp: str # ISO 8601, set at end-event arrival
pair_type: "llm" | "tool"
agent: AgentContext # agent_id, system_prompt, user_instruction
parent: ParentContext | None # populated for sub-agents; None for top-level / peers
data: LlmPairData | ToolPairData
metadata: dict[str, Any] | None # raw framework metadata (checkpoint_ns, tags, ...)
)
```
`LlmPairData` carries the model name, full message list (chat\_model\_start input), output text, `tool_calls`, and token usage. `ToolPairData` carries the tool name, `tool_call_id`, input string, and output string.
Under LangGraph, agent identity is derived from `langgraph_checkpoint_ns`, producing stable paths like `"reason"`, `"director|team_lead|worker"`, `"research_supervisor|supervisor_tools|1|researcher"`. The Anthropic SDK exposes no equivalent boundary, so identity is derived from the system prompt instead.
## What's captured
* **Activity events.** Tool calls, tool outputs, model inputs and outputs.
* **Reasoning traces.** The agent's chain of thought, where the underlying model exposes it. Capture is passive; Adrian does not modify your system prompt.
PII is redacted in your process before any data leaves it. See [Security and Privacy](/security-and-privacy).
## Supported frameworks
* **LangChain.** Pinned to `langchain-core >= 1.2.19, < 2.0` at launch. The Adrian callback handler attaches via the standard LangChain callback interfaces.
* **Anthropic SDK.** Requires `anthropic >= 0.40.0`. `messages.create` and `messages.stream` are patched on both the sync and async clients. See [Anthropic SDK](/reference/anthropic-sdk).
Other frameworks are on the roadmap. See [Integrations](/integrations).
## Known limitations
* **Hidden chain-of-thought.** Some model families (notably OpenAI's GPT-O series) hide reasoning steps. Adrian captures whatever the framework exposes and works with or without model reasoning - reasoning simply improves detection accuracy.
* **MCP visibility.** MCP server names are captured via LangChain's `mcp-adapters` integration. Agents that bypass `mcp-adapters` and use lower-level MCP client APIs directly will not have MCP server names captured at v1.
# Severity codes
Source: https://docs.adrian.secureagentics.ai/reference/severity-codes
Adrian's four severity levels and the kinds of behaviour each one captures.
Adrian's classifier outputs an M-code per event. The code maps to a severity level.
| Code | Severity | Examples |
| ------ | ---------------- | ----------------------------------------------------------------------------- |
| **M0** | Allowed | Routine read-only operations, scoped tool use |
| **M2** | Likely misuse | Overreach, scope expansion, indirect policy evasion |
| **M3** | High-risk misuse | Safeguard bypass, data exfiltration intent, privilege escalation |
| **M4** | Malicious | Privacy breach, backdoor implant, alignment circumvention, destructive action |
What happens when a code is raised depends on the operating mode (Audit, Human Review, or Block) and the alerting thresholds you have configured. See [How it works](/how-it-works) for the operating modes.
# TypeScript SDK
Source: https://docs.adrian.secureagentics.ai/reference/typescript-sdk
Reference for the Adrian TypeScript SDK and its OpenAI integration: install, wrapping your client, configuration, and what it captures.
The Adrian TypeScript SDK (`@secureagentics/adrian`) attaches to your agent's LLM calls, captures activity and reasoning, and streams them to the Adrian backend over WebSocket. The core package owns the event pipeline - event pairing, PII redaction, JSONL logging, WebSocket streaming, and policy verdicts. Provider packages build on it; the [OpenAI integration](#openai-integration) wraps your OpenAI client so every call is captured with no change to your call sites.
## Install
```sh theme={null}
npm install @secureagentics/adrian
```
Requires Node.js 18 or later.
## OpenAI integration
`@secureagentics/adrian-openai` instruments the official `openai` client (Chat Completions). `init`, `adrian.openai(client)`, and `shutdown` bracket your normal OpenAI code - call sites stay unchanged.
```sh theme={null}
npm install @secureagentics/adrian-openai openai
```
```ts theme={null}
import OpenAI from "openai";
import { adrian } from "@secureagentics/adrian-openai";
async function main() {
await adrian.init({ apiKey: "adr_live_..." });
// Wrap your existing OpenAI client; every call is captured.
const client = adrian.openai(new OpenAI());
const response = await client.chat.completions.create({
model: "gpt-4o",
messages: [
{ role: "user", content: "Find the most underpriced recent IPOs and build an investment strategy" },
],
});
console.log(response.choices[0]?.message?.content);
await adrian.shutdown();
}
main();
```
Requires the `openai` package as a peer dependency (`>=4.0.0`). Events appear in your [dashboard](https://app.adrian.secureagentics.ai) within seconds, classified by severity.
The SDK defaults to `ws://localhost:8080/ws`. For Adrian Cloud, pass `wsUrl: "wss://adrian.secureagentics.ai/ws"` to `init` (or set `ADRIAN_WS_URL`).
## Configuration
Explicit `init()` options take precedence over environment variables.
| Variable | Default | Purpose |
| ----------------------------- | ------------------------ | --------------------------------------------------------------------------------- |
| `ADRIAN_API_KEY` | *(required)* | API key for WebSocket authentication. `adr_live_...` for Adrian Cloud. |
| `ADRIAN_WS_URL` | `ws://localhost:8080/ws` | Backend WebSocket endpoint. `wss://adrian.secureagentics.ai/ws` for Adrian Cloud. |
| `ADRIAN_LOG_FILE` | `events.jsonl` | Local JSONL log path. |
| `ADRIAN_SESSION_ID` | *(optional)* | Session identifier for grouping events. |
| `ADRIAN_BLOCK_TIMEOUT` | `30` | Seconds to wait for a BLOCK-mode verdict before failing open. |
| `ADRIAN_REPLAY_BUFFER_FRAMES` | `1000` | WebSocket replay buffer for resending frames after a transient outage. |
## Policy and BLOCK mode
When the dashboard policy is in **BLOCK** or **HITL** mode, the SDK waits for backend verdicts on the tool calls an LLM turn proposes. In **BLOCK** mode, if no verdict arrives within `blockTimeout` seconds, the SDK fails open and allows execution (matching the [Python SDK](/reference/sdk)). See [Severity codes](/reference/severity-codes) for the M-codes and [How it works](/how-it-works) for the operating modes.
## What's captured
* **Activity events.** LLM calls and tool executions, paired (start + end) and streamed to the backend.
* **Reasoning traces.** Captured where the model exposes them.
PII is redacted in your process before any data leaves it. See [Security and Privacy](/security-and-privacy).
## Manual instrumentation
For a framework without a provider package, attach the handler from `adrian.getHandler()` and pair each LLM or tool start and end by a shared `runId` (`handleChatModelStart` / `handleLLMEnd`, `handleToolStart` / `handleToolEnd`). Shared capture helpers are exposed at `@secureagentics/adrian/capture`.
## Known limitations
* **Fail-open on timeout.** In BLOCK mode a verdict timeout allows execution; a dashboard-configurable failure policy is planned.
* **OpenAI surface.** The OpenAI package instruments the `openai` client's Chat Completions calls.
# Reliability
Source: https://docs.adrian.secureagentics.ai/reliability
If Adrian fails, what happens to your agent?
Adrian is intended to be a security guard, not an operational dependency. The default is **fail-open**. If something on Adrian's side breaks, your agent keeps running. A small set of cases **fail-closed** with a synthetic `[BLOCKED by security policy]` ToolMessage substituted in, where letting the action through would defeat the security guarantee.
## Verdict timeout
In Block mode the SDK waits up to `block_timeout` (default `30.0`s, env `ADRIAN_BLOCK_TIMEOUT`) for a verdict before letting a tool run. On timeout the SDK **fails open**, logs `verdict timeout for tool_call_id=…, fail-open` at WARN, and runs the original tool.
Alert mode never waits. Human Review waits indefinitely; resolution comes from the dashboard reviewer or a process exit. There is no timeout-driven fail-open in Human Review.
## Classifier failure
If Adrian's classifier can't return a verdict (a connectivity issue, an upstream error, or an output we can't parse), Adrian never fabricates a synthetic one. Brief blips are retried in place and almost always resolve in under a second. Persistent failures are handled by Adrian's resilient architecture: events are redelivered on a backoff schedule and, only as a last resort, set aside for operator review. Unrecognised classification codes are dropped rather than guessed at.
From your agent's perspective the failure is invisible. In **Alert** mode the agent runs to completion as normal. In **Block** mode the SDK waits up to `block_timeout` (30s by default), then proceeds with a warning. In **Human Review** mode it waits for a human reviewer to step in. Every failure is observable on Adrian's side: retries are logged, retired events are counted, and missing verdicts show up as gaps on the dashboard, so issues stay visible to operators without ever being charged to your agent.
## First tool call before LoginAck (cold start)
The SDK only knows the agent profile's policy mode after the server's `LoginAck` arrives. If a tool call happens before that, the SDK waits up to **5 seconds**. If `LoginAck` doesn't arrive, the SDK **fails closed** with `[BLOCKED by security policy]` and logs `WARN: ToolNode: LoginAck not received within 5s; halting`.
This halt is SDK-local. No verdict frame is sent, no database row is written, no `on_verdict` / `on_block` callback fires, and the dashboard event feed shows nothing for it. The only trace is the SDK's WARN log. Worth knowing if you're investigating a blocked tool call and can't find a corresponding event in the dashboard.
The 5-second window is hard-coded.
## Other behaviour
| Case | Posture |
| ------------------------------------------------ | ---------------------------------------------------------------------------------------------- |
| WS disconnect mid-wait | Verdict futures persist across reconnect; resolved by replay or fail-open at timeout |
| Reconnect | Exponential backoff 1s to 30s cap; 10s ping / 15s pong heartbeat |
| Server close 4003 (quota exhausted) | Reconnect delayed 60s |
| Server close 4401 (API key revoked) | WS dropped; reconnect handshake fails 401 until a new key is configured |
| Replay buffer overflow (default 1000 frames) | FIFO eviction; one-shot WARN at first overflow, cumulative drop count logged on next reconnect |
| Tool call with no producing LLM | Fail-open; tool runs (no `tool_call_id` to correlate against) |
| Human Review reviewer rejects | Halt with `[BLOCKED by security policy]` |
| Human Review SDK restart while review is pending | Late resolution dropped on arrival; audit trail in dashboard survives |
# Security and Privacy
Source: https://docs.adrian.secureagentics.ai/security-and-privacy
How Adrian handles data and defends against prompt injection.
Self-hosted Adrian runs entirely in your environment. Secure Agentics does not receive or store any data from self-hosted deployments. The sections below describe how data is handled on the hosted backend, plus prompt-injection defences that apply to both deployments.
## Data handling and PII
Adrian runs two layers of PII redaction on data sent to the hosted backend.
* **SDK-side regex PII filtering.** Runs inside your process before any data leaves it. Replaces matches with placeholder tags such as `[EMAIL REDACTED]` and `[PHONE REDACTED]`, preserving the detection signal without exposing the underlying value. Sub-millisecond identification and drops data from ever leaving the target environment.
* **Server-side LLM sweep.** Runs during processing on the hosted backend prior to storing, catching items regex cannot (names, contextual PII).
The SDK-side regex layer also runs in self-hosted deployments and acts as defence-in-depth even when data never reaches Secure Agentics.
Events stored by the hosted backend may be used for product improvement. You can opt out of longer-term data storage and product improvement in your account settings.
## Prompt injection defences
These controls apply to hosted deployments only.
* **Inference isolation.** The classifier model runs in an isolated environment with no tool, MCP, or internet access, bounding the blast radius if it is ever compromised.
* **Data spotlighting.** Untrusted input from agents and frontends is structurally separated from instructions so it cannot impersonate them.
* **Output contract.** The classifier's output is constrained to a strict format - anything else is rejected, blocking attempts to leak the system prompt or bypass classification.
# Videos
Source: https://docs.adrian.secureagentics.ai/videos
Watch Adrian in action.
## Demo
## Launch
## Quickstart walkthrough