Framework adapters — overview

Prismor Warden intercepts tool calls in production framework agents — not just local coding agents. The integration is designed to be a single function call on your existing agent or controller object, with no changes to your tool logic.

UX at a glance

FrameworkLanguageInstallGuardMulti-tenant
OpenAI Agents SDKPythonpip install "prismor[openai-agents]"guard_agent(agent)use_subject("user:alice")
LangChain / LangGraphPythonpip install "prismor[langchain]"guard_tools([...])use_subject("user:alice")
CrewAIPythonpip install "prismor[crewai]"guard_tools([...])use_subject("user:alice")
browser-usePythonpip install "prismor[browser-use]"guard_controller(controller)use_subject("user:alice")
Vercel AI SDKTypeScriptnpm install prismor-wardenwardenTools(tools, opts){ subject: "user:alice" }

The Python adapters ship inside the prismor package (needs >= 1.14.2) — the extra just pulls the framework itself. prismor[frameworks] installs all four. On npm it is prismor-warden (the registry blocks bare prismor as too similar to prisma). | Any language | Any | — (HTTP client only) | POST /v1/evaluate | X-Warden-Subject header |

The Python multi-tenant pattern: guard once at startup with no bound subject, then wrap each request with use_subject. A context var threads the subject through the evaluation pipeline — thread-safe and async-safe.

For non-Python languages: pass subject per call in the request body or X-Warden-Subject header. The eval-server resolves it identically.

What "guard" does

Regardless of framework, every adapter does the same three things:

  1. Intercept — wraps the framework's tool execution surface (see table below) so the original callable is never reached on a denied call.
  2. Evaluate — calls warden.runtime.evaluate_tool_call() with a canonical event and the resolved subject. Same pipeline as coding-agent hooks.
  3. Block or allow — in enforce mode a denied call returns a denial string to the model (the run recovers gracefully) or raises WardenBlocked. In observe mode findings are recorded but the call always proceeds.

Hook points by framework

FrameworkWhat gets wrappedWhen it fires
OpenAI Agents SDKFunctionTool.on_invoke_tool (async)after the LLM decides to call a tool, before the function runs
LangChain / LangGraphtool.func + tool.coroutinebefore tool.invoke() / tool.ainvoke() executes
CrewAItool.functool._runtool.run (first found)before the tool implementation runs
browser-useRegistry.execute_actionbefore Playwright executes any browser action
Vercel AI SDKtool.executebefore the tool body runs, after the LLM emits the tool call
HTTP (any language)caller-side POST /v1/evaluatebefore calling the tool implementation

Eval-server (non-Python languages)

For TypeScript, Go, Ruby, Java, Rust — any language that can make an HTTP request — run the eval-server as a sidecar and call it before executing each tool:

python3 -m prismor.runtime.eval_server --port 7071 --workspace /path/to/project
POST /v1/evaluate
{
  "tool_name": "run_shell",
  "arguments": { "command": "rm -rf /" },
  "event_type": "shell",
  "mode": "enforce",
  "subject": "user:alice"
}

→ { "allow": false, "reason": "[CRITICAL] ...", "subject": { "user_id": "alice" } }

The Python policy engine, IAM, and telemetry run inside the sidecar. Adapters in other languages are ~25 lines of HTTP client code with no Python dependency. The adapter fails open if the eval-server is down — agents are never broken by infrastructure issues.

Validated live on an Ubuntu EC2 instance with real OpenAI function calls:

LanguageAdapter sizeDependencies
TypeScript (Vercel AI SDK)~80 linesnpm install prismor-warden
Node.js (raw)~25 linesbuilt-in fetch
Ruby~20 linesstdlib Net::HTTP
Java 21~25 linesstdlib java.net.http
Rust~25 linesureq crate

See examples/multilang/ for runnable examples in all four languages.

Naming agents

Every guard entry point takes an optional name= — an instance label distinct from the framework id. Multiple agents on the same framework ("checkout-bot" and "support-bot", both on the OpenAI Agents SDK) become individually visible and controllable:

guard_agent(agent, name="checkout-bot")            # OpenAI Agents SDK
tools = guard_tools(tools, name="support-bot")     # LangChain / CrewAI
guard_controller(controller, name="browser-bot")   # browser-use
const tools = wardenTools(myTools, { agentName: 'checkout-bot' });  // Vercel AI SDK

What a name unlocks:

  • Its own row in the org dashboard's Connections view (and a per-agent activity drill-in), instead of blending into the framework's aggregate.
  • Per-agent runtime control via prismor agents set <name> — kill-switch (--disabled hard-blocks every call), a mode override, and a forced IAM profile. Config lives in agents.yaml (global ~/.prismor/ or per-project .prismor/); agents self-register on first sight.
prismor agents list                                # every named instance seen
prismor agents set checkout-bot --mode enforce     # this bot only
prismor agents set support-bot --disabled          # kill switch

Unnamed agents keep working unchanged — they report under the framework id.

Modes

guard_agent(agent, mode="observe")   # log findings, never block — safe rollout
guard_agent(agent, mode="enforce")   # block denied calls before execution

Start in observe to understand blast radius, switch to enforce once confident. Policy is YAML — change it without redeploying agents.

Per-user IAM

Add user:<id> or team:<id> keys to .prismor/iam.yaml:

agents:
  user:bob:
    deny_tools: [Bash]
    deny_network: true
    allowed_paths: ["**"]

When a request runs under use_subject("user:bob"), bob's profile is selected automatically — no env var, no code change. Users without a profile get the org-wide defaults.

Per-framework guides