Docs Connect
Connect an agent
An agent gets a Card the same way however it is built. What differs is how its actions reach KIFF. Pick the path that matches the agent:
| Your agent | Path | What changes in the agent |
|---|---|---|
| Calls tools over MCP (Claude Code, Codex, other MCP clients) | The KIFF MCP gateway | Its MCP server: a URL and a key, or a link it signs in with. No code. |
| Runs in your own code, on a framework | kiff-guard, below | One hook in the agent’s code. |
| Any code that can make an HTTP call | The decision API, below | One call before the action. |
In every path KIFF decides before the action runs, and only actions that go through KIFF are checked. Give each agent its own key, from its page in KIFF Cloud: that is how KIFF knows which agent is asking, and whose Card to draw on.
Using Claude Code or Codex? Install the KIFF skill and ask it to connect your agent. It walks you through the right path below, drafts the Card, and knows what to do when a call is held.
Connect with kiff-guard
kiff-guard is an open-source (MIT) library that sits in the agent’s own tool-calling loop. Attach it in observe mode, watch what the agent actually does, then switch to enforce so each tool call is checked against the agent’s Card first. The agent keeps doing the work; no rewrite.
1. Install the guard
The guard is an open-source (MIT) package,
kiff/kiff-guard, published on PyPI
and npm. Install the core plus the adapter for your framework, here, Agno:
pip install "kiff-guard[agno]"
The core has zero required dependencies; each framework adapter pulls its
own framework in as an optional extra. For TypeScript (the OpenClaw
adapter), npm install @kiff/kiff-guard.
2. Attach it in observe mode
One guard, one hook. observe runs every tool, records a receipt, and
learns your action catalog. It needs no client, no tenant, no API key -
it never calls KIFF and never blocks a tool.
from kiff_guard import Guard
from kiff_guard.adapters.agno import agno_hook
guard = Guard(mode="observe") # no client, no tenant needed
agent = Agent(
model=...,
tools=[refund_order, send_email],
tool_hooks=[agno_hook(guard)],
)
On a different stack? The hook is the only line that changes. Every framework page shows the same attach with that framework’s one-liner, and How the guard works explains the model behind all of them.
3. Run as usual, and see what it did
Nothing about your agent changes, run it the way you already do. The guard rides along on each tool call and turns each into a receipt.
agent.run("Refund order ord_4821 and email the customer")
for r in guard.receipts:
print(r.state, r.tool, r.outcome)
# observed refund_order observed
# observed send_email observed
4. Derive a domain from real traffic
Because the guard learned the catalog while it watched, it hands you a
starter KIFF domain drafted from what the agent actually does, so you
never face a blank kiff.yaml:
from kiff_guard import export_yaml
print(export_yaml("my-domain", guard.catalog))
# KIFF domain draft for 'my-domain'
# Auto-derived from observed agent traffic (instrument-first).
domain: my-domain
actions:
- name: refund_order
parameters: [amount_cents, order_id]
risk: low # TODO(human): low | medium | high
requires_approval: false # TODO(human)
- name: send_email
parameters: [body, to]
risk: low # TODO(human): low | medium | high
requires_approval: false # TODO(human)
The draft is a starting point, you set the risk, thresholds, and approval rules. KIFF derives the observed action surface; it never invents authority you didn’t declare.
5. Give the agent a key and a Card, then enforce
- In KIFF Cloud, open the agent on My agents (or Agents), go to its Keys tab, choose New key, then Code that runs kiff-guard and Create the key. The key is bound to this agent and shown once. Put it in the agent process’s environment:
export KIFF_CLOUD_URL=https://api.kiff.dev
export KIFF_CLOUD_API_KEY='kiff_live_...'
- Give the agent a Card for the actions it should take, from Cards in KIFF Cloud.
- Switch the guard to
enforceand give it the client that carries the key. KIFF now decides before each tool runs:allowedproceeds,approval_requiredwaits for the owner (see Held calls and approvals),blockedrefuses. If KIFF can’t be reached, the guard fails closed.
guard = Guard(mode="enforce", client=client) # client carries the key
Keep two kinds of credential apart:
- the agent’s KIFF key only asks KIFF for decisions. It cannot grant itself authority or change its own Card;
- your provider credentials (the model API, your payments API) stay where they already live. kiff-guard never sends them to KIFF.
To cut the agent off, revoke its key on its Keys tab. Its next call with that key is refused; other agents are untouched.
Call the decision API directly
No framework, or code you’d rather not hook? Immediately before a consequential call, send the proposed action to KIFF and branch on the answer. KIFF returns the decision; your code still runs the action.
import os
import requests
def ask_kiff(*, entity_id, entity_type, action, parameters, actor_id):
response = requests.post(
f"{os.environ['KIFF_CLOUD_URL']}/v1/proposals/decide",
headers={"Authorization": f"Bearer {os.environ['KIFF_CLOUD_API_KEY']}"},
json={
"entity_id": entity_id,
"entity_type": entity_type,
"action_name": action,
"actor_id": actor_id,
"parameters": parameters,
},
timeout=5,
)
return response.json()
decision = ask_kiff(entity_id=order_id, entity_type="Order", action="ISSUE_REFUND",
parameters={"amount": amount}, actor_id="refund-agent")
if decision["outcome"] == "allowed":
process_refund(order_id, amount)
| Outcome | Meaning | What your code does |
|---|---|---|
allowed |
State, contract and Card permit it. | Run the action. |
approval_required |
The owner must decide first. | Hold the action. |
blocked |
State, permissions, parameters or the Card forbid it. | Do not run it. |
invalid |
The request or domain configuration is incomplete. | Fix the request or domain. |
limit_exceeded |
The account’s monthly allowance of checks is used up (1,000 on the free open-beta plan). | Do not run; see Settings → Usage, or write to gabriel@kiff.dev. |
Branch on outcome, not only the HTTP status. Where the action draws on a
Card, actor_id must be the agent the Card was issued to, and the key must
be that agent’s own key: a key issued for another agent is refused, so one
agent cannot spend another’s Card.
This governs only the calls you route through it. It proves what KIFF authorized before the call, not that your function completed.
Where to go next
- Agents that call MCP tools → Use the KIFF MCP gateway.
- The model behind observe and enforce → How kiff-guard works.
- Your framework’s hook → pick it from the sidebar; each adapter ships in kiff-guard and passes its conformance suite.
- What a Card can say → KIFF Cards.
The guard, every adapter, and the conformance suite live in the public
MIT repo kiff/kiff-guard.