Skip to main content
ClaudeWave
Skill682 repo starsupdated 3d ago

integrate-arcjet-guard-openai-agents

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.

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

SKILL.md

# Integrate Arcjet Guard into an OpenAI Agents app

`@arcjet/guard`'s OpenAI Agents v0 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({ execute })`) → `guardTool()`. After
  `tool()` the object is a `FunctionTool`; the runner calls `invoke`.
  DENY returns a structured `ArcjetDenialResult`. Do not throw.
- **Correlation** → `openaiAgentsContext()` reads a field the integrator
  put on `runContext.context` (then documented copies: `conversationId`,
  `groupId`, already-resolved `sessionId`). It never mints a new id.
  It never calls `session.getSessionId()`.

This namespace is text **`Agent` + `run()` / `Runner`**. Not Realtime,
not Sandbox, not hosted tools, not computer / shell / apply_patch, not
MCP, not `agent.asTool()`.

## Screen inbound before `run()` (SDK `inputGuardrails` are not Arcjet)

There is no first-class inbound channel, so there is no `guardInbound`.
Put prompt-injection (and other inbound rules) in the application before
`run()`. SDK `inputGuardrails` / `outputGuardrails` /
`defineToolInputGuardrail` / `defineToolOutputGuardrail` are the SDK's
own tripwires, not this policy gate. Do not wrap them as Guard.

## `needsApproval` is not a policy gate

`needsApproval` / `requireApproval` / `onApproval` is human-in-the-loop.
The run pauses; `result.state.approve` / `reject`. Same trap as Mastra
`requireApproval`, Claude `canUseTool`, and LangGraph `interrupt()`.
There is no `guardApproval`. Do not wrap them as Guard.

## `tool()` execute is the deny point; hosted, MCP, and handoffs are not

The runner executes authored function tools in `toolExecution.ts` via
`invoke`. Hosted tools, handoffs, computer / shell / apply_patch, and
MCP (`mcpServers` → `mcpToFunctionTool`) skip that authored-`execute`
path. `agent_tool_start` / `agent_tool_end` are void observe-only
hooks; they are not a deny. There is no `guardHooks` and no
`guardToolNode` (there is no ToolNode).

## Questions to ask the human first

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

1. Which tools are **risky** (external side effects, irreversible, spends
   money, sends messages)? Those get `guardTool`. Hosted / MCP / handoffs
   are out of v0 scope.
2. What **limits**? (e.g. "10 lookups/min per order" → `tokenBucket`.)
3. 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
   `run(..., { context: { sessionId } })`. That id is the correlation id,
   not the user.
4. Is an Arcjet outage unacceptable? Every helper defaults to
   `onGuardError: "deny"`. Ask explicitly about inbound screening before
   `run()`: 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

1. **There is no `guardInbound`.** Screen prompt injection before
   `run()`. SDK input/output guardrails are not Arcjet.
2. **`needsApproval` is not a policy gate.** It is HITL. Use `guardTool`.
3. **The import path is versioned and there is no alias.**
   `@arcjet/guard/openai-agents/v0`. `@arcjet/guard/openai-agents` does
   not resolve.
4. **Correlation is read, never minted.** Do not call `createAgentContext`
   inside a run callback — that generates a second id and splits the
   Sequence. `RunContext` has no session / conversation id of its own.
   Put the id you already chose on `run(..., { context })`. Do not call
   `session.getSessionId()` from the helper: `MemorySession` mints a UUID
   when constructed without `sessionId`. Do not use `traceId` (the SDK
   mints one when omitted).
5. **Do not double-wrap with `@arcjet/guard/vercel-ai/v7`.** `guardTool`
   throws if the tool already carries the Arcjet protection brand.
6. **A denial from `guardTool` is a structured object, not a throw.**
   Throwing would hit the SDK `errorFunction` (a generic string, or
   `ToolCallError` when `outputSchema` / `errorFunction: null`). The
   runner stringifies the object onto a `function_call_result` with
   `status: "completed"` — the denial is in the payload
   (`arcjetDenied: true`). 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 `@openai/agents` (optional peer,
needed for `@arcjet/guard/openai-agents/v0`). Always use the versioned
path: `@arcjet/guard/openai-agents/v0` resolves;
`@arcjet/guard/openai-agents` throws `ERR_PACKAGE_PATH_NOT_EXPORTED`.
Zod is the OpenAI Agents peer, not ours — install `zod` only if the app
already uses it for `tool({ parameters })`.

```sh
npm install @arcjet/guard @openai/agents
```

If the agent 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: Gate authored tools

```ts
import { tool } from "@openai/agents";
import { z } from "zod";
import { guardTool } from "@arcjet/guard/openai-agents/v0";
import { tokenBucket, localDetectSensitiveInfo } from "@arcjet/guard";

import { arcjet } from "./arcjet.js";

const lookupLimit = tokenBucket({
  bucket: "lookups",
  refillRate: 10,
  intervalSeconds: 60,
  maxTokens: 10,
});
// Factory then text — same shape as `detectPromptInjection()(text)`.
// Scan free-text args (a note, reason, body). An opaque `orderId` will
// not trip EMAIL / phone / card / IP, so do not pass it here.
const detectPii = localDetectSensitiveInfo();

export const lookupOrder = guardTool(
  arcjet,
  tool({
    name: "lookup_order",
    description: "Look up an order by ID",
    parameters: z.object({
      orderId: z.string(),
      note: z.string(),
    }),
    execute: async ({ orderId, note }) => ({ orderId, note, status: "shipped
integrate-arcjet-guard-agentsSkill

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.

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-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.