-
Notifications
You must be signed in to change notification settings - Fork 0
Sync vendored skills with skills main (677f177 / #55–#58) #21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
411 changes: 388 additions & 23 deletions
411
plugins/arcjet/skills/arcjet/references/guards_javascript.md
Large diffs are not rendered by default.
Oops, something went wrong.
216 changes: 27 additions & 189 deletions
216
plugins/arcjet/skills/arcjet/references/guards_python.md
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
228 changes: 228 additions & 0 deletions
228
plugins/arcjet/skills/integrate-arcjet-guard-claude-agent-sdk-py/SKILL.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,228 @@ | ||
| --- | ||
| name: integrate-arcjet-guard-claude-agent-sdk-py | ||
| description: Integrate Arcjet Guard into the Python Claude Agent SDK — wrap authored @tool with guard_tool, and use guard_hooks for UserPromptSubmit inbound plus PreToolUse on unwrapped built-ins / MCP. Use when asked to add Arcjet to claude-agent-sdk, rate limit those tools, screen inbound prompts, or block prompt injection / PII. This is Python claude-agent-sdk, not the JS adapter and not Claude Managed Agents hosted sessions. | ||
| license: Apache-2.0 | ||
| compatibility: Requires Python >= 3.10 and official claude-agent-sdk>=0.2.127,<1 via arcjet[claude-agent-sdk] (safe extra, no chromadb). Until-published — pin arcjet to git SHA 9ea0b06a87bcee77b8df0664338c712c4668b87b; not in PyPI 0.9.0. | ||
| metadata: | ||
| author: arcjet | ||
| type: core | ||
| library: arcjet | ||
| --- | ||
|
|
||
| # Integrate Arcjet Guard into the Python Claude Agent SDK | ||
|
|
||
| `arcjet.guard.claude_agent_sdk` wraps the agent's existing Arcjet client. | ||
| It never talks to the Arcjet API itself. Shared Guard fundamentals | ||
| (client, rules, labels, decisions, capture, registration) live in | ||
| [../arcjet/references/guards_python.md](../arcjet/references/guards_python.md). | ||
| Load that reference for anything that is not Claude Agent SDK-specific. | ||
|
|
||
| Official `claude-agent-sdk>=0.2.127,<1` only — not the JS | ||
| `@anthropic-ai/claude-agent-sdk` adapter | ||
| (`@arcjet/guard/claude-agent-sdk/v0`, docs | ||
| https://docs.arcjet.com/guards/claude-agent-sdk/), not Vercel AI, not | ||
| community forks, and not Claude Managed Agents hosted | ||
| `client.beta.sessions` (`arcjet.guard.claude_managed_agents`). Importing | ||
| `arcjet.guard.claude_agent_sdk` does not load LangChain, CrewAI, or the | ||
| JS adapter. The extra is safe (no chromadb). | ||
|
|
||
| Exports: `guard_tool`, `guard_hooks`, `claude_agent_context`. Authored | ||
| `@tool` + `create_sdk_mcp_server` for tools you own; `guard_hooks` for | ||
| inbound `UserPromptSubmit` and unwrapped built-ins / MCP `PreToolUse`. | ||
| There is no inbound helper and no `guard_can_use_tool`. | ||
|
|
||
| Three surfaces, one decision rule: | ||
|
|
||
| - **An authored `@tool`** → `guard_tool`. Denial is JSON-in-content + | ||
| `is_error: True`. Python does **not** forward `structuredContent` | ||
| (that is the JS adapter). Do **not** raise. | ||
| - **Inbound text** → `guard_hooks` `UserPromptSubmit` (`decision: | ||
| "block"`). That is the only place a turn can be declined before the | ||
| model reads the prompt. | ||
| - **Built-ins / unwrapped MCP** → `guard_hooks` `PreToolUse` | ||
| (`permissionDecision: "deny"`). `PostToolUse` is capture only. | ||
| - **Correlation** → `claude_agent_context` reads a caller-owned UUID | ||
| `session_id`. It never mints. | ||
|
|
||
| Docs: https://docs.arcjet.com/guards/claude-agent-sdk-py/. | ||
|
|
||
| ## Unwrapped tools deny on `PreToolUse` via `guard_hooks` | ||
|
|
||
| Built-ins (Bash, Write, …) and MCP tools you did not pass through | ||
| `guard_tool` have no authored handler. `PreToolUse` is the only deny for | ||
| those. `PostToolUse` cannot undo a tool that already ran. List every | ||
| `guard_tool` wrapper in `exclude` or each authored tool is guarded twice | ||
| (two round trips, two quota units). Entries match the reported name: | ||
| pass `{"server": "support", "name": "lookup_order"}` for an authored MCP | ||
| tool (it resolves to `mcp__support__lookup_order`) and a bare string for | ||
| a built-in such as `"Bash"`. A bare authored name deliberately does not | ||
| match every server's tool of that name. | ||
|
|
||
| ## Authored `@tool` denial is JSON-in-content + `is_error: True` | ||
|
|
||
| `guard_tool` wraps the `@tool` definition so the handler never runs on | ||
| `DENY` (or unevaluated Guard under the default `on_guard_error="deny"`). | ||
| The model receives the `ArcjetDenialResult` as JSON text on `content` | ||
| with `is_error: True`. Do **not** set `structuredContent` (JS only). Do | ||
| **not** raise: a throw is a raw exception; omitting `is_error` looks | ||
| like success. Same fail-closed default as | ||
| [#196](https://github.com/arcjet/arcjet-py/pull/196): only `"allow"` | ||
| fails open; a `DENY` always blocks. Core `guard()` still fails open. | ||
|
|
||
| ## `can_use_tool` is not a policy gate | ||
|
|
||
| `can_use_tool` is human-in-the-loop. `allowed_tools`, allow rules, and | ||
| `bypassPermissions` / `acceptEdits` skip it. Same trap as CrewAI | ||
| `human_input`, JS `canUseTool`, LangGraph `interrupt()`, and OpenAI | ||
| Agents `needs_approval`. There is no inbound helper — screen prompt | ||
| injection on `guard_hooks` `UserPromptSubmit`. | ||
|
|
||
| ## 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 `guard_tool`. Built-ins and | ||
| unwrapped MCP get `guard_hooks` `PreToolUse`. | ||
| 2. What **limits**? (e.g. "10 lookups/min per user" → `TokenBucket`.) | ||
| 3. Who is the **user** for metadata — an opaque user/tenant ID (never PII)? | ||
| Default: none. Session id is the correlation id, not the user. The | ||
| Claude SDK requires `session_id` to be a UUID and allows a given id | ||
| to be created only once — later turns use `resume`. | ||
| 4. Is an Arcjet outage unacceptable? Every helper defaults to | ||
| `on_guard_error="deny"`. Ask explicitly about inbound | ||
| `UserPromptSubmit`: failing closed there means the agent stops | ||
| answering, so `"allow"` is a routine and legitimate choice at that | ||
| one call site. | ||
|
|
||
| ## The things readers get wrong | ||
|
|
||
| 1. **This is not the JS adapter and not Claude Managed Agents.** Do not | ||
| import `@arcjet/guard/claude-agent-sdk/v0` or | ||
| `arcjet.guard.claude_managed_agents`. | ||
| 2. **There is no inbound helper.** Screen on `UserPromptSubmit` via | ||
| `guard_hooks`. | ||
| 3. **`can_use_tool` is HITL, not policy.** | ||
| 4. **Python does not forward `structuredContent`.** Authored deny is | ||
| JSON-in-content + `is_error: True`. | ||
| 5. **`session_id` is a UUID, and only once.** Later turns: `resume`. | ||
| `claude_agent_context` never mints. | ||
| 6. **List every `guard_tool` wrapper in `exclude`.** Missing exclude | ||
| double-calls Guard. | ||
| 7. **Key rate limits on the authenticated caller**, not a model-supplied | ||
| order id. Hand `query` / `create_sdk_mcp_server` the copy | ||
| `guard_tool` returns. | ||
| 8. **Do not hand-wrap every tool with raw `guard()`.** | ||
|
|
||
| ## Step 1: Install and find the guard client | ||
|
|
||
| Until-published: PyPI `arcjet` 0.9.0 does not include this module. Pin | ||
| `arcjet` to git SHA `9ea0b06a87bcee77b8df0664338c712c4668b87b`: | ||
|
|
||
| ```bash | ||
| pip install "arcjet[claude-agent-sdk] @ git+https://github.com/arcjet/arcjet-py.git@9ea0b06a87bcee77b8df0664338c712c4668b87b" | ||
| ``` | ||
|
|
||
| If the agent has no guard client yet, launch one **once at module scope**: | ||
|
|
||
| ```python | ||
| import os | ||
| from arcjet.guard import launch_arcjet | ||
|
|
||
| aj = launch_arcjet(key=os.environ["ARCJET_KEY"]) | ||
| ``` | ||
|
|
||
| ## Step 2: Gate authored tools — `guard_tool` | ||
|
|
||
| ```python | ||
| from claude_agent_sdk import ClaudeAgentOptions, create_sdk_mcp_server, query, tool | ||
| from arcjet.guard import DetectPromptInjection, TokenBucket, launch_arcjet | ||
| from arcjet.guard.claude_agent_sdk import guard_hooks, guard_tool | ||
|
|
||
| aj = launch_arcjet(key=os.environ["ARCJET_KEY"]) | ||
| lookup_limit = TokenBucket( | ||
| label="order.looked-up", | ||
| bucket="lookups", | ||
| refill_rate=10, | ||
| interval_seconds=60, | ||
| max_tokens=10, | ||
| ) | ||
| mcp_limit = TokenBucket( | ||
| label="mcp.invoked", | ||
| bucket="mcp-access", | ||
| refill_rate=20, | ||
| interval_seconds=60, | ||
| max_tokens=20, | ||
| ) | ||
| inbound = DetectPromptInjection() | ||
| user_id = authenticated_user_id | ||
| # Caller-owned UUID for this conversation. Later turns: resume=session_id. | ||
| session_id = conversation_id | ||
|
|
||
| @tool("lookup_order", "Look up an order by number", {"order_id": str}) | ||
| async def lookup_order(args: dict) -> dict: | ||
| return { | ||
| "content": [{"type": "text", "text": f"{args['order_id']}: shipped"}], | ||
| } | ||
|
|
||
| lookup_order = guard_tool( | ||
| guard=aj, | ||
| tool=lookup_order, | ||
| action="order.looked-up", | ||
| rules=[lookup_limit(key=user_id, requested=1)], | ||
| on_guard_error="deny", | ||
| ) | ||
| # can_use_tool=... is HITL — not this policy gate | ||
| ``` | ||
|
|
||
| ## Step 3: Screen inbound and gate unwrapped tools — `guard_hooks` | ||
|
|
||
| ```python | ||
| async for message in query( | ||
| prompt=user_text, | ||
| options=ClaudeAgentOptions( | ||
| session_id=session_id, # later turns: resume=session_id instead | ||
| mcp_servers={ | ||
| "support": create_sdk_mcp_server(name="support", tools=[lookup_order]), | ||
| }, | ||
| hooks=guard_hooks( | ||
| guard=aj, | ||
| session_id=session_id, | ||
| exclude=[{"server": "support", "name": "lookup_order"}], | ||
| inbound={ | ||
| "action": "message.received", | ||
| "rules": lambda ctx: [inbound(ctx["prompt"])], | ||
| }, | ||
| action="mcp.invoked", | ||
| rules=lambda ctx: [mcp_limit(key=user_id, requested=1)], | ||
| on_guard_error="deny", | ||
| ), | ||
| ), | ||
| ): | ||
| pass | ||
| ``` | ||
|
|
||
| ## Step 4: Correlation | ||
|
|
||
| `claude_agent_context(session_id=session_id)` reads that same | ||
| caller-owned UUID (hook `session_id` first, then `options.session_id`). | ||
| It never mints. Pass the id you already have into `guard_hooks` and | ||
| `ClaudeAgentOptions` — do not derive a new one per turn. The Claude SDK | ||
| also requires `session_id` to be a UUID and allows a given id to be | ||
| created only once — later turns use `resume`. | ||
|
|
||
| ## Verify the integration | ||
|
|
||
| 1. `python -m py_compile` (or the project's type-check) passes. | ||
| 2. Exercise inbound PI (`UserPromptSubmit` block), an authored-tool deny | ||
| (JSON-in-content + `is_error: True`, no `structuredContent`), a | ||
| built-in / unwrapped MCP deny (`PreToolUse`), a rate limit, and | ||
| fail-closed (an unreachable guard). Confirm a wrapped tool produces | ||
| **one** guard decision per invocation — a second decision under the | ||
| `PreToolUse` action means a missing `exclude` entry. | ||
| 3. Confirm in the Arcjet Console / CLI that decisions share the | ||
| caller-owned UUID `session_id`. | ||
| 4. Manual E2E with a real `ARCJET_KEY` is still-to-verify until you run it. | ||
|
|
||
| The example `fastapi-claude-agent-sdk-guard` stays with Runtime — do not | ||
| invent a new example name. Do not add an example in this skills repo. |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.