Skip to main content
ClaudeWave
Skill682 repo starsupdated 3d ago

integrate-arcjet-guard-agents

Integrate Arcjet security into a Vercel AI SDK (v7) application using @arcjet/guard — wrap agent tools with guard checks, enforce rules on risky app actions, and emit audit events joined by one correlation ID. Use when asked to add Arcjet to an AI SDK app, protect or rate limit agent tool calls, guard AI agent actions, or audit what an agent did.

Install in Claude Code
Copy
git clone --depth 1 https://github.com/arcjet/arcjet-js /tmp/integrate-arcjet-guard-agents && cp -r /tmp/integrate-arcjet-guard-agents/arcjet-guard/skills/integrate-arcjet-guard-agents ~/.claude/skills/integrate-arcjet-guard-agents
Then start a new Claude Code session; the skill loads automatically.

SKILL.md

# Integrate Arcjet Guard into a Vercel AI SDK app

`@arcjet/guard`'s Vercel AI v7 namespace wraps the app's existing Arcjet
client. It never talks to the Arcjet API itself. Three surfaces, one decision
rule:

- **Model-invoked** (the LLM decides to call a tool) → `guardTool()`
- **App-invoked** (your code performs a risky action) → `guardAction()`
- **Observe-only** (record that something happened) → `captureAction()`

All three attach the same correlation ID so the Arcjet Console reconstructs
the whole run as one Sequence.

## Questions to ask the human first

Ask only what you cannot infer from the code; suggest defaults.

1. Which tool calls / actions are **risky** (external side effects,
   irreversible, spends money, sends messages)? Those get rules. Purely
   informational ones can be wrapped with no `rules` (recorded, nothing
   enforced locally) or left to `captureAction()`.
2. What **limits**? (e.g. "10 lookups/min per user" → `tokenBucket`;
   "5 posts/min" → `slidingWindow`.)
3. Who is the **user** for metadata — an opaque user/tenant/installation ID
   (never PII)?
4. Is there an existing **run identifier** (request ID, job ID, review ID)
   to use as the correlation ID? Default: auto-generated ULID.

## Step 1: Install and find the guard client

Install `@arcjet/guard` (required), plus `ai` and `@ai-sdk/provider-utils`
(optional peers, needed only for `@arcjet/guard/vercel-ai/v7`). Every agent
helper lives on that one path. Always use explicit versions:
`@arcjet/guard/vercel-ai/v7` resolves, but `@arcjet/guard/vercel-ai` does not —
omitting the version is deliberate (it prevents silent API breaking changes
when a new major version is supported). Attempting to import from an
unversioned path throws `ERR_PACKAGE_PATH_NOT_EXPORTED`.

```sh
npm install @arcjet/guard ai @ai-sdk/provider-utils
```

If the app has no guard client yet, launch one **once at module scope**:

```ts
import { launchArcjet } from "@arcjet/guard";
export const arcjet = launchArcjet({ key: process.env.ARCJET_KEY! });
```

## Step 2: Create the context at the run's entry point

In the HTTP route / job handler / webhook that starts the run:

```ts
import { createAgentContext, securityMetadata } from "@arcjet/guard/vercel-ai/v7";

const ctx = createAgentContext({
  correlationId: existingRunId, // omit to auto-generate a ULID
  metadata: securityMetadata({ agent: "support-agent", workflow: "support-request", user: userId }),
});
```

Constraints: correlation IDs are 1–256 characters of printable ASCII;
invalid values throw at creation.

The `correlationId` joins every guard decision and capture event from one
logical run **or session** into a single sequence in the Arcjet console, so
the best value is an ID the app already has and can search by (request ID,
job ID, ticket ID, review ID). Omit it and a ULID is generated.

## Step 3: Thread the context explicitly

The context is a plain JSON-serializable object. Pass it hand to hand — as a
field on queue payloads and workflow inputs (it survives serialization).
Never stash it in module state or AsyncLocalStorage.

## Step 4: Wrap model-invoked tools

```ts
import { guardTool, securityMetadata } from "@arcjet/guard/vercel-ai/v7";
import { tokenBucket } from "@arcjet/guard";

const lookupLimit = tokenBucket({ bucket: "lookups", refillRate: 5, intervalSeconds: 60, maxTokens: 10 });

const tools = {
  lookupOrder: guardTool(arcjet, lookupOrderTool, {
    action: "order.looked-up", // "resource.verb", past tense
    rules: ({ orderNumber }) => [lookupLimit({ key: `order:${orderNumber}`, requested: 1 })],
    // securityMetadata() maps the flat vocabulary to wire keys, so its fields
    // are strings. Nested values go alongside it in the raw metadata object.
    metadata: (input) => ({
      ...securityMetadata({ resource: `order:${input.orderNumber}`, user: userId }),
      caller: { id: userId, role: "customer" },
    }),
  }),
};
```

- Omit `rules` to submit none. The guard call still happens, so the decision is
  correlatable and the call site stays reachable by policy configured outside
  the code — but it costs a round trip. Use `captureAction()` instead when you
  want a record and no decision.
- `rules` may be a callback over the tool's parsed input, computed from the
  data being acted on — here, keying the rate limit on the specific order
  being looked up.
- On DENY the tool's `execute` never runs; the model receives a structured
  denial result carrying the deciding rule's own `reason` — for the
  `tokenBucket` above that is `reason: "RATE_LIMIT"`, `retryable: true`, and a
  computed `retryAfterSeconds`. Only rate-limit denials are retryable; every
  other reason reports `retryable: false` and no backoff hint. Reshape it with
  `onDeny`.
- Guard policy unavailability: if the guard cannot be evaluated (e.g. Arcjet
  API unreachable), the default is `onGuardError: "deny"` — the tool is blocked
  and the model receives `reason: "ERROR"` with `retryable: true` and a fixed
  `retryAfterSeconds: 5` backoff hint. For read-only operations like lookups,
  set `onGuardError: "allow"` if availability matters more than enforcement: the
  tool executes normally and the model receives its ordinary output.
- Pilot limitation: `guardTool` throws if the tool already declares its
  own `contextSchema`.
- **Alternative form:** calling `guardAction` directly inside the tool's
  `execute` block is also supported and keeps control flow visible, but
  requires threading the context in by hand. `guardTool` wrapping extracts it
  automatically via the injected `contextSchema`.

## Step 5: Deliver the context to the tools

```ts
import { aiToolsContext } from "@arcjet/guard/vercel-ai/v7";

const result = await generateText({
  model,
  instructions:
    systemPrompt +
    " If a tool call is denied by security policy, do not retry it; explain the denial to the user or try a different approach.",
  prompt,
  tools,
  toolsContext: aiToolsContext(ctx, tools),
  stopWhen: stepCount
integrate-arcjet-guard-claude-agent-sdkSkill

Integrate Arcjet security into a Claude Agent SDK agent using @arcjet/guard — wrap tool() handlers, screen inbound prompts with UserPromptSubmit, and deny unwrapped built-in/MCP tools with PreToolUse. Use when asked to add Arcjet to a Claude Agent SDK or Claude Code agent, rate limit its tools, screen inbound messages, or block prompt injection / PII.

integrate-arcjet-guard-eveSkill

Integrate Arcjet security into a Vercel Eve agent using @arcjet/guard — add guard gates to tools and connections, screen inbound messages, and record agent lifecycle events correlated to the session. Use when asked to add Arcjet to an Eve agent, rate limit its tools, guard connection access, or screen inbound messages.

integrate-arcjet-guard-genkitSkill

Integrate Arcjet security into a Genkit JS agent using @arcjet/guard — wrap ai.defineTool, put guardMiddleware on generate({ use }) for unwrapped / MCP / filesystem tools, and read a caller-owned id from generate({ context }). Use when asked to add Arcjet to genkit, rate limit its tools, screen inbound messages, or block prompt injection / PII.

integrate-arcjet-guard-langchainSkill

Integrate Arcjet security into a LangChain JS createAgent using @arcjet/guard — wrap tool() / StructuredTool, put guardMiddleware on createAgent({ middleware }) for MCP / unwrapped tools, and read configurable.thread_id for correlation. Use when asked to add Arcjet to langchain createAgent, rate limit its tools, screen inbound messages, or block prompt injection / PII. This is LangChain JS, not the Python page.

integrate-arcjet-guard-langgraphSkill

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.

integrate-arcjet-guard-mastraSkill

Integrate Arcjet security into a Mastra agent using @arcjet/guard — wrap createTool execute, screen input/output with a Processor tripwire, and gate unwrapped MCP/workspace tools with hooks. Use when asked to add Arcjet to a Mastra agent, rate limit its tools, screen inbound messages, or block prompt injection / PII.

integrate-arcjet-guard-openai-agentsSkill

Integrate Arcjet security into an OpenAI Agents text Agent using @arcjet/guard — wrap tool({ execute }), screen inbound before run(), and read a caller-owned id from runContext.context. Use when asked to add Arcjet to @openai/agents, rate limit its tools, screen inbound messages, or block prompt injection / PII.

integrate-arcjet-guard-strands-agentsSkill

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.