CAIN-42 CAIN Studio

Recipe · Claude Agent SDK

Claude Agent SDK: guard code merges with CAIN

Step by step, for an agent that merges pull requests: install, wrap the tool, see the live decision, write and test a rule, handle approvals.

Last reviewed 2026-10-02

1 Get a key

No key yet? Create a free account (every new account starts with a 7-day trial), then copy the key from the console. Requests go from your browser straight to this site.

Give each agent its own key in production (console → Agent keys): decisions, trust history and revocation are then per agent.

2 Install

pip install "cainstudio[claude-agent-sdk] @ https://cainstudio.online/cainstudio-0.4.1-py3-none-any.whl#sha256=1bea5cf49aeeb2775acec58933fc39b008a1c84bfd7c9690c54723ed707af689"
export CAIN_API_KEY=...

cainstudio 0.4.1, pinned by URL and SHA-256 so pip installs exactly the published wheel (the package is not on PyPI yet). Framework dependencies come from PyPI as usual.

3 Put CAIN in front of PreToolUse hooks

cain_hooks() maps CAIN's decision onto the hook's permission decision: refused is deny, held is ask (the user decides), only an explicit allow proceeds. Tools from an in-process MCP server are named mcp__<server>__<tool>, and that is the name your rules match.

from claude_agent_sdk import ClaudeAgentOptions, ClaudeSDKClient, create_sdk_mcp_server, tool
from cainstudio.integrations.claude_agent_sdk import cain_hooks

@tool('merge_pull_request', "Code merges: an agent that merges pull requests", {"repo": str, "number": int, "base": str})
async def merge_pull_request(args):
    ...  # your existing code
    return {"content": [{"type": "text", "text": "done"}]}

ops = create_sdk_mcp_server(name="ops", tools=[merge_pull_request])
options = ClaudeAgentOptions(mcp_servers={"ops": ops}, allowed_tools=["mcp__ops__merge_pull_request"],
                             hooks=cain_hooks(agent_id='code-merge-agent'))   # PreToolUse: CAIN decides
client = ClaudeSDKClient(options=options)

On a refusal the agent gets a denied tool call with the reason. The tool body never runs: not on a refusal, not on a hold, not when CAIN cannot be reached.

4 See what CAIN decides for this exact call

This is the request the integration sends when the model calls mcp__ops__merge_pull_request with these arguments. record: false evaluates it without writing an evidence record. A new agent has no trust history, so expect REQUIRE_APPROVAL at first; destinations outside your egress allowlist are refused by the egress stage.

5 Write the rule, and test it before you save it

Merges into feature branches run; merges into main wait for a person.

The panel sends the draft rule to the rule tester: it says which rule would decide the call and saves nothing. Swap in the safe arguments below to see it pass through. matches is a case-insensitive glob (*, ?), not a regular expression; conditions on one rule must all hold; the first matching rule by priority wins.

Safe arguments: {"repo": "acme/app", "number": 812, "base": "feature/login"}

When it does what you want, save it from the console or with POST /fabric/tool-rules:

curl -sS -X POST https://cainstudio.online/fabric/tool-rules \
  -H "X-API-Key: $CAIN_API_KEY" -H 'Content-Type: application/json' \
  -d '{"name": "code-merge-guard", "effect": "require_approval", "priority": 10, "match_path": "/tools/mcp__ops__merge_pull_request", "conditions": [{"field": "base", "op": "eq", "value": "main"}]}'

6 Handle the calls that wait for a person

A held call is not executed. It appears in the approval queue with its exact arguments; an approval lets that one call run once. Adapters can wait instead of returning: wait_for_approval=120.

GET /fabric/approvals

7 Check it before you ship

cainstudio redteam --agent code-merge-agent   # dry-runs dangerous calls; exit 2 if any would run
cainstudio decisions                 # what was decided, and why
cainstudio explain <decision_id>

Every recorded decision is signed; Cain().verify(decision) checks its quorum certificate on your machine (Python SDK).

More

Try CAIN-42 on your own agents

Create a free account and every new account starts with a 7-day trial of the full platform. Or try the sandbox first, with no account at all.

Create a free account →  ·  Try the sandbox  ·  See the whole ecosystem