Skip to content

Agent guard framework integrations

What is Arcjet? Arcjet is the runtime security platform that ships with your code. Enforce budgets, stop prompt injection, detect bots, and protect personal information with Arcjet's AI security building blocks.

AI frameworks normally pass a model’s generated arguments directly to a tool’s execute or invoke function. Arcjet’s framework integrations add a security checkpoint between those two steps, so a remote policy or SDK rule evaluates every tool attempt before the tool can create a side effect.

Use an integration when you want to protect tools without writing decision handling around every function. The wrapper preserves the framework’s normal tool definition and result flow, and selects policy with a stable action. Where the adapter supports it, the wrapper also maps the actor and the relevant tool arguments into policy inputs.

Use protect() on HTTP routes. Use the helpers on each framework page for agent tools and other non-HTTP actions.

Where an adapter maps policy inputs, you choose what the policy receives. Map only the tool arguments that the policy needs, and take the actor from your authenticated application context. This keeps the policy contract explicit and avoids sending unrelated data.

Every Arcjet SDK offers the same two things: a direct guard() call that you place yourself, and helpers that place the same check around a framework’s tool for you. What changes between languages is how a helper is packaged, how it receives the client, and how it reports a denial. One rule accounts for most of the difference:

A helper that answers a model returns a value. A helper that answers your application signals an error.

A denied tool call has to reach the model as something it can read and respond to, so guardTool and its equivalents return a denial payload rather than throwing. A denied application call has to reach code that can handle it, so guardAction throws in JavaScript and raises in Python.

The direct client is available in JavaScript, Python, and Go.

JavaScriptPythonGo
Package@arcjet/guardarcjet.guardgithub.com/arcjet/arcjet-go
Create a clientlaunchArcjet()launch_arcjet() and launch_arcjet_sync()arcjet.NewGuardClient()
Evaluate a policyawait arcjet.guard({ label, rules })await arcjet.guard(label=, rules=)guard.Guard(ctx, arcjet.GuardRequest{Label: …})
Reads ARCJET_KEYNoNoYes, when Key is empty
Process-wide registryregisterArcjet(), then free guard()register_arcjet(), then free guard() or guard_sync()None. Pass the client
Rule modeDefaults to LIVEDefaults to LIVEMode is required. An empty Mode returns ErrInvalidMode
Incomplete checkFails open. Check decision.hasFailedOpen()Fails open. Check decision.has_failed_open()Fails open. Check decision.HasFailedOpen()

Framework helpers are available for JavaScript and Python. Go has the direct client and capture.

JavaScriptPython
Import pathA versioned subpath per vendor major, such as @arcjet/guard/langchain/v1. Unversioned aliases don’t resolveA submodule per vendor, such as arcjet.guard.langchain
Framework dependencyAn optional peer dependencyAn extra, such as arcjet[langchain]. CrewAI has no extra
ClientThe first argumentThe guard= keyword. Several helpers accept a registered client instead
Denial the model readsArcjetDenialResult in that framework’s envelopeThe same fields in that framework’s envelope
Denial your application catchesguardAction throws ArcjetDeniedError or ArcjetGuardUnavailableErrorguard_action raises ArcjetDeniedError or ArcjetUnavailableError
Fail-closed defaultonGuardError: "deny"on_guard_error="deny"
Blocking variantNone. The client is asyncEvery entry point has a _sync pair

For the shape each framework’s denial takes, see Denial responses.

Pick the page that matches the object you hold when the effect runs. Each page covers install, every helper, the denial envelope, and correlation for that framework.

FrameworkLanguageImport pathEnforcement point
Vercel AI SDK v7JavaScript@arcjet/guard/vercel-ai/v7guardTool on an authored tool({ execute })
LangChainJavaScript@arcjet/guard/langchain/v1guardTool on an authored tool, guardMiddleware on wrapToolCall
LangChainPythonarcjet.guard.langchainguard_tool on a BaseTool, ArcjetMiddleware on create_agent
CrewAIPythonarcjet.guard.crewairegister_arcjet_hooks on PRE_TOOL_CALL
LangGraphJavaScript@arcjet/guard/langgraph/v1guardTool on an authored tool, guardToolNode on ToolNode
GenkitJavaScript@arcjet/guard/genkit/v1guardTool on a ToolAction, guardMiddleware on the tool hook
Google ADKJavaScript@arcjet/guard/google-adk/v2guardPlugin on beforeToolCallback
OpenAI AgentsJavaScript@arcjet/guard/openai-agents/v0guardTool on FunctionTool.invoke
OpenAI AgentsPythonarcjet.guard.openai_agentsguard_tool on FunctionTool.tool_input_guardrails
Strands AgentsJavaScript@arcjet/guard/strands-agents/v1guardTool on an authored tool, guardHooks on BeforeToolCallEvent
Strands AgentsPythonarcjet.guard.strands_agentsguard_tool on an authored tool, guard_hooks on cancel_tool
TanStack AIJavaScript@arcjet/guard/tanstack-ai/v0guardMiddleware on onBeforeToolCall
Vercel EveJavaScript@arcjet/guard/vercel-eve/v0guardApproval on a connection, guardTool on execute, guardInbound on a channel
MastraJavaScript@arcjet/guard/mastra/v1guardTool on execute, guardHooks on beforeToolCall, guardProcessor on inbound text
Claude Agent SDKJavaScript@arcjet/guard/claude-agent-sdk/v0guardTool on an authored tool, guardHooks on PreToolUse and UserPromptSubmit
Claude Agent SDKPythonarcjet.guard.claude_agent_sdkguard_tool on an authored @tool, guard_hooks on PreToolUse and UserPromptSubmit
Claude Managed AgentsJavaScript@arcjet/guard/claude-managed-agents/v0guardEvents before events.send, guardCustomTool on agent.custom_tool_use
Claude Managed AgentsPythonarcjet.guard.claude_managed_agentsguard_events before events.send, guard_custom_tool on agent.custom_tool_use

Each JavaScript path is versioned. Unversioned aliases such as @arcjet/guard/vercel-ai do not resolve.

Each adapter wraps the object that its own framework executes, so use the adapter that matches the framework you are calling. Wrapping one framework’s tool with another framework’s adapter either throws or misses the path the framework runs, which leaves the tool unguarded. The Python CrewAI helpers work with official CrewAI rather than an npm port of it.

Most agent frameworks offer a way to pause a run so that a person can approve a tool call:

FrameworkApproval control
Claude Agent SDKA permission callback
Claude Managed Agentsalways_ask with user.tool_confirmation
CrewAIhuman_input
GenkittoolApproval
LangChain, LangGraphAn interrupt
MastrarequireApproval
OpenAI AgentsAn approval flag
Strands AgentsAn interrupt
TanStack AIneedsApproval
Vercel EveA connection’s approval field

None of these is a Guard enforcement point. Each one asks a person instead of evaluating a policy, and a run that nobody is watching stalls. Arcjet does not wrap any of them as a gate, and no adapter has a guardApproval helper except Vercel Eve, where the approval field is the only place the framework lets you intercept an OpenAPI or MCP connection.

Use a framework’s approval control when a person must decide, and a Guard helper when a policy must decide. The two compose, so an approval pause can sit in front of a guarded tool.

Every JavaScript adapter uses one payload, ArcjetDenialResult. The fields, wording, and retry-after rules are the same, so a model sees one shape. The envelope is per-framework, because each SDK reports a tool that did not run in a different way:

AdapterIdiomatic envelopeWhy not the others
AI SDK / MastraReturn { arcjetDenied: true, … } as the tool resultA throw becomes a generic tool error and drops the fields
OpenAI Agents (JavaScript)Return { arcjetDenied: true, … } from invokeA throw hits errorFunction or ToolCallError and can kill the run
OpenAI Agents (Python)reject_content(...) with JSON of ArcjetDenialResultA raise becomes a tripwire halt, or default_tool_error_function swallows it
Strands Agents (JavaScript)guardTool returns { arcjetDenied: true, … }. guardHooks sets event.cancel to a JSON string of that objectA throw drops the fields. event.interrupt() is HITL, not policy
Strands Agents (Python)guard_tool returns JSON of ArcjetDenialResult. guard_hooks sets cancel_tool to True or a stringA raise is a raw exception. event.interrupt() is HITL, not policy
TanStack AIonBeforeToolCall skip with ArcjetDenialResult ({ type: "skip", result }). Optional onDeny: "abort"A throw from execute is swallowed. There is no guardTool. Abort is not the default
LangGraphReturn { arcjetDenied: true, … }. ToolNode wraps a ToolMessage with status: "success"Fabricating a ToolMessage to force status: "error" crashes the graph reducer
LangChain (JavaScript)guardTool returns { arcjetDenied: true, … }. guardMiddleware returns a real ToolMessage (JSON content, default status)A throw drops the fields. wrapToolCall cannot return a bare object. Don’t set status: "error"
GenkitReturn { arcjetDenied: true, … } as completed toolResponse.outputA throw / interrupt() is HITL and the wrong envelope
Google ADKDeny dict from beforeToolCallback. undefined executesA throw is the wrong envelope. undefined on error executes the tool
Claude Agent SDK (JavaScript)MCP CallToolResult with isError: true and the payload on structuredContentA throw is a raw exception. Omitting isError looks like success
Claude Agent SDK (Python)JSON in content + is_error: TruePython does not forward structuredContent. A raise is a raw exception. Omitting is_error looks like success
Claude Managed AgentsDon’t send user.message on inbound DENY. On custom-tool DENY, don’t execute; return user.custom_tool_result with is_errorBuilt-in tools already ran under Anthropic. user.tool_confirmation is opt-in HITL, not policy. A throw is a raw exception. is_error is on the events schema
Vercel EveThrow ArcjetDeniedError. Opt in to a returned payload with onDeny: "result"Eve projects a throw as a failed action.result. A silent return can violate outputSchema
const result: ArcjetDenialResult = {
arcjetDenied: true,
reason: "RATE_LIMIT",
message:
"Arcjet denied this call (RATE_LIMIT). It may be retried after 30 seconds.",
retryable: true,
retryAfterSeconds: 30,
};

You can import ArcjetDenialResult from @arcjet/guard/vercel-ai/v7 and the other JavaScript adapter namespaces.

guardTool must produce an envelope the model can inspect. guardAction throws so application code can catch. These cannot share one handler.

Only rate-limit denials set retryable: true and include retryAfterSeconds. When Guard is unavailable and onGuardError is "deny", the payload uses reason: "ERROR", retryable: true, and retryAfterSeconds: 5.

The direct client and the framework helpers answer an incomplete check differently.

A direct guard() call fails open. It returns ALLOW with an error result, so hasFailedOpen(), has_failed_open(), or HasFailedOpen() reports true. Check it at any call site that must fail closed.

Every framework helper fails closed. The tool does not run, and the model receives the same ArcjetDenialResult it would receive for a policy denial, carrying reason: "ERROR", retryable: true, and retryAfterSeconds: 5, in that framework’s envelope. Set onGuardError: "allow" in JavaScript or on_guard_error="allow" in Python only when running the action without a complete security decision is acceptable.

Inbound screening is the case where "allow" is often the right choice, because failing closed on a user’s first message stops the agent answering at all.

Two helpers report unavailability differently from the preceding table:

  • Python LangChain guard_tool raises ArcjetToolUnavailableError, and a policy denial raises ArcjetToolDeniedError. Both subclass LangChain’s ToolException, so a wrapped tool’s handle_tool_error handles them when you configure one.
  • CrewAI register_arcjet_hooks raises HookAborted(reason=..., source="arcjet") from PRE_TOOL_CALL for both a denial and unavailability, so the agent sees Tool execution blocked by hook. Tool: {name} in each case.

For the direct client and wrapper differences, see Availability and fail behavior.