arcjet/arcjet-js/integrate-arcjet-guard-langgraph
Claude Code and agent skill by arcjet/arcjet-js
- community
Checking that the SKILL.md file is still in the repository.
Integrate Arcjet security into a LangGraph Graph API agent using @arcjet/guard — wrap tool() / StructuredTool, wrap ToolNode for unwrapped MCP tools, and read thread_id for correlation. Use when asked to add Arcjet to a LangGraph StateGraph / ToolNode agent, rate limit its tools, screen inbound messages, or block prompt injection / PII.
Install
npx vskill@latest install arcjet/arcjet-js/integrate-arcjet-guard-langgraphSKILL.md
Integrate Arcjet Guard into a LangGraph agent
@arcjet/guard's LangGraph v1 namespace wraps the agent's existing Arcjet client. It never talks to the Arcjet API itself. Two surfaces, one decision rule:
- An authored tool (
tool()/StructuredTool) →guardTool(). DENY
returns a structured ArcjetDenialResult. Do not throw.
- MCP / runtime-discovered / unwrapped tools →
guardToolNode().
Guards the tools a ToolNode from @langchain/langgraph/prebuilt executes, in place, so execute still hits Guard. Already-branded tools are skipped (no double-call).
- Correlation →
langgraphAgentContext()reads
configurable.thread_id, then the run id, then checkpoint_ns. It never mints a new id.
This namespace is LangGraph Graph API (StateGraph + ToolNode). createReactAgent is deprecated in LangGraph JS v1 in favor of LangChain createAgent / wrapToolCall. Do not build on createReactAgent. Do not use this path for a LangChain createAgent app — that is @arcjet/guard/langchain/v1.
Screen inbound before invoke (or at the first graph node)
There is no first-class LangGraph channel for inbound screening, so there is no guardInbound. Put prompt-injection (and other inbound rules) in the application before graph.invoke, or in the graph's first node.
interrupt() is not a policy gate
interrupt() / interrupt_before=["tools"] is human-in-the-loop, not policy. Same trap as Mastra requireApproval, Claude canUseTool, Google ADK requireConfirmation, and Cloudflare Think needsApproval. There is no guardInterrupt and no guardApproval. Do not wrap them as Guard.
ToolNode is the deny point for tools; hooks / HITL cannot enforce
Unwrapped and MCP tools run inside ToolNode. Graph hooks and HITL pauses cannot stop tool.invoke. Use guardToolNode (or guardTool for authored tools you invoke yourself).
guardToolNode guards the node's tools in place and returns the same node. ToolNode's constructor captures func: (input, config) => this.run(input, config) and run reads this.tools, so guarding a copy would leave the original node running unguarded tools. This also means a caller still holding the pre-wrap node cannot bypass Guard.
Questions to ask the human first
Ask only what you cannot infer from the code; suggest defaults.
- Which tools are risky (external side effects, irreversible, spends
money, sends messages)? Those get guardTool. MCP / tools you did not author get guardToolNode.
- What limits? (e.g. "10 lookups/min per order" →
tokenBucket.) - Who is the user for metadata — an opaque user/tenant ID (never PII)?
Default: none. Pass it via metadata on the policy. thread_id is the correlation id, not the user.
- Is an Arcjet outage unacceptable? Every helper defaults to
onGuardError: "deny". Ask explicitly about inbound screening before invoke: failing closed there means the graph does not run for the duration of the outage, so "allow" is a routine and legitimate choice at that one call site.
The six things readers get wrong
- There is no `guardInbound`. Screen prompt injection before
graph.invoke or in the first graph node.
- `interrupt()` is not a policy gate. It is HITL. Use
guardToolor
guardToolNode.
- The import path is versioned and there is no alias.
@arcjet/guard/langgraph/v1. @arcjet/guard/langgraph does not resolve.
- Correlation is read, never minted. Do not call
createAgentContext
inside a LangGraph callback — that generates a second id and splits the Sequence. langgraphAgentContext reads thread_id / checkpoint_ns / run id and omits correlationId when none of those is a valid id.
- **Do not double-wrap with
@arcjet/guard/vercel-ai/v7or
@arcjet/guard/claude-managed-agents/v0.** LangGraph tools are LangChain tool(), but this namespace brands them. guardTool throws if the tool already carries the Arcjet protection brand. guardToolNode skips already-branded tools so Guard is not double-called.
- A denial from `guardTool` is a structured object, not a throw.
ToolNode turns it into a real ToolMessage. Because the tool did not throw, that message's status is success — the denial is in the payload (arcjetDenied: true). Do not fabricate a ToolMessage yourself to force status: "error": an object that only looks like a message reaches the graph's message reducer and crashes it. If onDeny throws, the tool still does not run and the model still receives the default denial.
Step 1: Install and find the guard client
Install @arcjet/guard (required), plus @langchain/langgraph and @langchain/core (optional peers, needed for @arcjet/guard/langgraph/v1). Always use the versioned path: @arcjet/guard/langgraph/v1 resolves; @arcjet/guard/langgraph throws ERR_PACKAGE_PATH_NOT_EXPORTED.
If the agent has no guard client yet, launch one once at module scope:
Step 2: Gate authored tools
- Omit
rulesto submit none. The guard call still happens. - Optional
actorandinputs(static, or a resolver over this adapter's native call — parsed input plus trusted runtime/context) are forwarded on the
guard call so a remote policy that declares those names can evaluate. Build each input with policyInput. A resolver that throws or returns a value Guard cannot use is left out of the guard call, and onGuardError decides.
- On DENY the tool's
func/invokenever runs. The model receives
{ arcjetDenied: true, reason, message, retryable } as the tool result content. If you invoke a guarded tool outside ToolNode, read that object and build your own ToolMessage rather than pushing it into messages.
- Default
onGuardError: "deny"blocks the tool if Arcjet is unreachable,
or if an action, rules or metadata callback throws or returns a value Guard cannot use. A failed callback never skips the guard call: Guard is called without its value (a failed action falls back to "tool.invoked"), so remote policy still evaluates the call and a DENY stands.
Step 3: Screen inbound before invoke
Or put the same screen in the graph's first node. There is no guardInbound.
Step 4: Gate tools you did not wrap
Pass the wrapped node to StateGraph.addNode("tools", tools). It is the same node object you passed in, with its tools guarded in place. Use this for tools you did not pass through guardTool. guardToolNode skips already-branded tools, so applying both to the same authored tool does not double-call the guard. Tools discovered after wrapping are guarded on the next invoke.
Step 5: Correlation
Pass the checkpointer thread_id you already have on graph.invoke(input, { configurable: { thread_id } }). langgraphAgentContext reads it; it never calls createAgentContext.
Preference order: configurable.thread_id, then the run id, then configurable.checkpoint_ns. The namespace is last because it names one subgraph ("" for the parent), so preferring it would split sibling subgraphs of a single run across correlation ids. If none is a valid 1–256 printable-ASCII string, the call is uncorrelated rather than joined to a generated id nobody has.
Verify the integration
npm run typecheckpasses.- Exercise inbound PI (before invoke), a tool deny, PII on args, a rate
limit, an unwrapped ToolNode deny, and fail-closed (an unreachable guard).
- Confirm in the Arcjet dashboard that decisions share the thread id as
their correlation id.
- Manual E2E with a real
ARCJET_KEYis still-to-verify until you run it.
A full working demo will land in `arcjet/examples` `langgraph-agent` with arcjet/examples#193. Do not add an example under examples/ in the JS SDK repo.
Note: capture events are fire-and-forget and batched, so events can lag the decisions they accompany by a few seconds. A dropped event is diagnosed, never thrown.
Security
- Security scanPassed, 100 of 100Pattern checks on the skill's files.
- Model reviewNot runA model review is scheduled for the next rescan.
Quality evaluation
- Task evaluationNot runNo evaluation yet. Run one in Skill Studio.
Source
- SKILL.md in the repositoryCheckingarcjet-guard/skills/integrate-arcjet-guard-langgraph/SKILL.md
Popularity
- 7-day trend score
- 5
Not refreshed yet.
Works with
Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.
- Claude Code
- Cursor
- GitHub Copilot
- Windsurf
- Codex
- Gemini CLI
- Cline
- Amp
- Roo Code
- Goose
- Continue
- Kilo Code
- Junie
- Trae
- OpenHands
- Qwen Code
- AI Agent
- Tabnine Chat
- Sourcegraph Cody
- Amazon Q
- Firebase Genkit
- Sweep
- Devin
- Phind
- Mentat
- Claude Desktop
- Aider
- GPT Pilot
- Plandex
- bolt.new
- v0
- Replit Agent
- Anse
- TypingMind
- BetterChatGPT
- LibreChat
- Warp AI
- Pieces
- Fig AI
Embed badge
[](https://verified-skill.com/skills/arcjet/arcjet-js/integrate-arcjet-guard-langgraph)