arcjet/arcjet-js/integrate-arcjet-guard-strands-agents
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 Strands Agents JS app using @arcjet/guard — wrap tool({ callback }), put guardHooks on Agent({ plugins }) for unwrapped / MCP / vended tools, and read a caller-owned id from invocationState. Use when asked to add Arcjet to strands-agents, rate limit its tools, screen inbound messages, or block prompt injection / PII.
Install
npx vskill@latest install arcjet/arcjet-js/integrate-arcjet-guard-strands-agentsSKILL.md
Integrate Arcjet Guard into a Strands Agents app
@arcjet/guard's Strands Agents v1 namespace wraps the agent's existing Arcjet client. It never talks to the Arcjet API itself. Three surfaces, one decision rule:
- An authored tool (
tool({ callback })) →guardTool(). After
tool() the object is a FunctionTool / ZodTool whose runner path is _callback (stream() / invoke()). DENY returns a plain ArcjetDenialResult. Do not throw. Do not fabricate a ToolResultBlock.
- MCP / unwrapped / vended tools →
guardHooks(). A Plugin whose
initAgent registers BeforeToolCallEvent at HookOrder.SDK_FIRST - 1. On DENY it sets event.cancel to JSON.stringify(ArcjetDenialResult). Already-branded tools are skipped. Do not use BeforeToolsEvent.cancel (that skips per-tool hooks).
- Correlation →
strandsAgentContext()reads a field the
integrator put on invocationState (correlationId, then sessionId, then requestId). It never mints a new id. It never reads traceId. It never uses SessionManager or agent.id.
This namespace is JS `@strands-agents/sdk` `Agent` + `tool({ callback })` + Plugin / `addHook`. Not the Python SDK. Do not also wrap the same tool with @arcjet/guard/vercel-ai/v7, @arcjet/guard/langgraph/v1, or @arcjet/guard/claude-managed-agents/v0. Zod is their peer, not ours.
Screen inbound before invoke() / stream() — there is no inbound hook.
There is no first-class inbound channel, so there is no guardInbound. Put prompt-injection (and other inbound rules) in the application before agent.invoke() / stream(). Middleware / model hooks are not this policy gate.
interrupt() is not a policy gate.
event.interrupt() is human-in-the-loop. Same trap as Mastra requireApproval, Claude canUseTool, LangGraph interrupt(), OpenAI Agents needsApproval, LangChain humanInTheLoopMiddleware, Google ADK requireConfirmation, and Cloudflare Think needsApproval. There is no guardApproval / guardInterrupt. Do not wrap interrupt() as Guard.
Deny with BeforeToolCallEvent.cancel (and guardTool on authored callbacks). BeforeToolsEvent.cancel skips per-tool hooks — do not use it.
The authored callback is the deny point for tools you own. MCP, vended tools, and anything not wrapped with guardTool skip that callback. guardHooks is the invoke-wide gate for those. Official: set event.cancel to a string; tool.stream() does not run; AfterToolCallEvent still fires. It records the call even when an action or metadata callback fails, under the default label and its own metadata.
Do not use BeforeToolsEvent.cancel. A truthy value skips _toolExecutor.execute(), so per-tool hooks never run.
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 / vended / tools you did not author get guardHooks.
- 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. Put the conversation / session id you already have on agent.invoke(..., { invocationState: { sessionId } }) _and_ on guardHooks({ sessionId }). That 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 agent 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
agent.invoke() / stream(). Middleware / model hooks are not Guard.
- `interrupt()` is not a policy gate. It is HITL. Use
guardTool
or guardHooks. A denial is event.cancel = JSON.stringify(...), not an InterruptError.
- The import path is versioned and there is no alias.
@arcjet/guard/strands-agents/v1. @arcjet/guard/strands-agents does not resolve.
- Correlation is read, never minted. Do not call
createAgentContext
inside a hook — that generates a second id and splits the Sequence. Put the id you already chose on invocationState. Do not read traceId. Do not use SessionManager or agent.id.
- Do not use `BeforeToolsEvent.cancel`. It skips the per-tool
hooks that guardHooks registers. Deny on BeforeToolCallEvent.
- A denial from `guardTool` is a structured object, not a throw.
Wrap both _callback and ZodTool's _functionTool._callback. Returning a plain ArcjetDenialResult is correct; FunctionTool wraps objects in a JsonBlock. Do not fabricate a ToolResultBlock. Do not double-wrap with @arcjet/guard/vercel-ai/v7 or @arcjet/guard/langgraph/v1.
Step 1: Install and find the guard client
Install @arcjet/guard (required), plus @strands-agents/sdk (optional peer, needed for @arcjet/guard/strands-agents/v1). Always use the versioned path: @arcjet/guard/strands-agents/v1 resolves; @arcjet/guard/strands-agents throws ERR_PACKAGE_PATH_NOT_EXPORTED. Zod is Strands' peer, not ours — install zod only if the app already uses it. Node 22+.
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 authored callback never runs. The model receives
{ arcjetDenied: true, reason, message, retryable } as the callback return (FunctionTool wraps that object in a JsonBlock).
- Default
onGuardError: "deny"blocks the tool if Arcjet is unreachable,
or if a rules, metadata or sessionId callback throws or returns a value Guard cannot use. A failed callback never skips the guard call: Guard is called without its value, so remote policy still evaluates the call and a DENY stands.
- Prefer omitting
outputSchemaon guarded tools, or verify the schema
accepts ArcjetDenialResult / your onDeny shape. A denial is not schema-checked.
Step 3: Gate unwrapped / MCP / vended tools
Already-branded (guardTool) tools skip the hook gate. Tools that are not branded — MCP, vended tools, anything not wrapped — are still gated.
Put the same id on invoke(..., { invocationState: { sessionId } }) and on guardHooks({ sessionId }) when you need tool-time correlation through the hook.
Step 4: Screen inbound before invoke
There is no guardInbound.
Step 5: Correlation
Put the id you already have on the invocationState bag you pass to invoke() / stream():
Preference order: invocationState.correlationId, then invocationState.sessionId, then invocationState.requestId, then documented copies on the envelope, then init.sessionId. If none is a valid 1–256 printable-ASCII string, the call is uncorrelated rather than joined to a generated id nobody has.
Never read traceId. Never read agent.id. Never call SessionManager. Never call createAgentContext inside a hook.
Verify the integration
npm run typecheckpasses.- Exercise inbound PI (before invoke), a tool deny, PII on args, a
rate limit, a hook deny on an unwrapped tool, and fail-closed (an unreachable guard). Confirm interrupt() is never called.
- Confirm in the Arcjet dashboard that decisions share the session /
request 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` `strands-agent` as a later follow-up. 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-strands-agents/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-strands-agents)