CAIN-42 CAIN Studio

Recipe · LangChain

LangChain: guard email with CAIN

Step by step, for an agent that sends email: 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[langchain] @ 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 your agent's tool list

protect() wraps each tool and keeps its name, description and argument schema, so the model sees exactly the same tools. Forgetting one tool is the realistic failure mode, so pass the whole list.

from langchain.agents import create_agent
from langchain_core.tools import tool
from cainstudio.langchain import protect

@tool
def send_email(to: str, subject: str, body: str) -> str:
    """Email: an agent that sends email."""
    ...  # your existing code, unchanged

tools = protect([send_email], agent_id='email-agent')   # pass the whole tool list
agent = create_agent(model, tools)

On a refusal the agent gets the refusal as the tool's error, so the agent can explain or take another route. 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 send_email 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

Mail inside your company runs; mail to anyone outside waits for a person. Recipients are also destinations: until you allow a destination under egress rules, the egress stage refuses it on its own.

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: {"to": "ops@yourcompany.com", "subject": "Invoice", "body": "..."}

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": "email-guard", "effect": "require_approval", "priority": 10, "match_path": "/tools/send_email", "conditions": [{"field": "to", "op": "not_contains", "value": "@yourcompany.com"}]}'

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