how it works · for the tech lead
One call before the action. Your code still runs it.
Your agent asks KIFF before it acts. KIFF answers against the agent’s card and the live state. If the answer is no, your function returns before anything happens.
def issue_refund(order, amount): d = kiff.decide("issue_refund", order=order, amount=amount) if not d.allowed: return d payments.refund(order, amount) # your code, unchanged
An illustrated customer-support scenario. Watch or share the short videos →
what happens on every call
Ask. Check. Answer. Record.
- Ask
The agent’s code sends the action, the entity and the parameters. One request.
- Check
Is the entity in a state that allows it? Does the agent hold a card for it, with room left today? Does it need a person?
- Answer
Allowed, refused, or needs approval, with the reason. A retry of the same request counts once. After a person approves a held request, the retry uses that approval, and KIFF checks the Card's current limits again before allowing it.
- Record
Every decision is stored with a signed receipt, and what it used from the card.
With Guard, KIFF never runs the action. It authorizes; your system executes. An agent whose code you can't change connects over MCP instead, and KIFF sends each allowed call to the tool. How the gateway works →
why connect it
Trust you can check. Not take on faith.
- It never runs your action.
With Guard, your code still calls the payment or the email. KIFF only answers yes, no or ask, and never holds the keys to your systems.
How Guard connects → - It can't be talked past a limit.
The Card is checked outside the model. The agent's own key can't change its Card or approve its own request.
✓ TestAgentKeyCannotManageCards - When in doubt, it says no.
If KIFF can't read a limit, the answer is no. It never guesses in the agent's favour.
✓ TestMandateFailsClosedUnlikeTheUsageLimiter - Proof, not just logs.
Every decision has a signed receipt. A receipt changed afterwards fails the check, so your auditor can verify it.
✓ TestAnchorAttester_TamperedReceiptFailsOnchainVerify - One switch stops it.
A halt refuses every call until you resume, and nothing held from before comes back on its own.
✓ TestAHaltRefusesEveryCallAndNothingFromBeforeComesBack - Read it before you trust it.
The Guard SDK and the framework are open source (MIT). KIFF Cloud runs in Ireland, in the EU, under our DPA.
works with your stack
Keep your framework. Add the Guard.
Pick your stack. The KIFF side is the same everywhere: entity, action and parameters in, one answer out.
from kiff_guard import Guard, HTTPClient, ToolMap
from kiff_guard.adapters.agno import agno_hook
tm = ToolMap().bind("refund_order", action="REFUND_ORDER",
entity_type="Order", entity_arg="order_id")
guard = Guard(client=HTTPClient(api_key=KEY, tool_map=tm),
tenant="acme", agent="refunds", mode="enforce")
guard.connect(adapter="agno")
agent = Agent(model=..., tools=[refund_order],
tool_hooks=[agno_hook(guard)]) # decides before the tool runsfrom kiff_guard import Guard, HTTPClient, ToolMap
from kiff_guard.adapters.langgraph import kiff_wrap_tool_call
tm = ToolMap().bind("refund_order", action="REFUND_ORDER",
entity_type="Order", entity_arg="order_id")
guard = Guard(client=HTTPClient(api_key=KEY, tool_map=tm),
tenant="acme", agent="refunds", mode="enforce")
guard.connect(adapter="langgraph")
agent = create_agent(model=..., tools=[refund_order],
middleware=[kiff_wrap_tool_call(guard)])from kiff_guard import Guard, HTTPClient, ToolMap
from kiff_guard.adapters.openai_agents import kiff_tool_input_guardrail
tm = ToolMap().bind("refund_order", action="REFUND_ORDER",
entity_type="Order", entity_arg="order_id")
guard = Guard(client=HTTPClient(api_key=KEY, tool_map=tm),
tenant="acme", agent="refunds", mode="enforce")
guard.connect(adapter="openai-agents")
@function_tool(tool_input_guardrails=[kiff_tool_input_guardrail(guard)])
def refund_order(order_id: str, amount: int, reason: str): ...from kiff_guard import Guard, HTTPClient, ToolMap
from kiff_guard.adapters.google_adk import kiff_before_tool_callback
tm = ToolMap().bind("refund_order", action="REFUND_ORDER",
entity_type="Order", entity_arg="order_id")
guard = Guard(client=HTTPClient(api_key=KEY, tool_map=tm),
tenant="acme", agent="refunds", mode="enforce")
guard.connect(adapter="google-adk")
agent = Agent(tools=[refund_order],
before_tool_callback=kiff_before_tool_callback(guard))from kiff_guard import Guard, HTTPClient, ToolMap
from kiff_guard.adapters.pydantic_ai import kiff_before_tool_execute
tm = ToolMap().bind("refund_order", action="REFUND_ORDER",
entity_type="Order", entity_arg="order_id")
guard = Guard(client=HTTPClient(api_key=KEY, tool_map=tm),
tenant="acme", agent="refunds", mode="enforce")
guard.connect(adapter="pydantic-ai")
agent = Agent(model=...,
before_tool_execute=kiff_before_tool_execute(guard))from kiff_guard import Guard, HTTPClient, ToolMap
from kiff_guard.adapters.strands import kiff_hook_provider
tm = ToolMap().bind("refund_order", action="REFUND_ORDER",
entity_type="Order", entity_arg="order_id")
guard = Guard(client=HTTPClient(api_key=KEY, tool_map=tm),
tenant="acme", agent="refunds", mode="enforce")
guard.connect(adapter="strands")
agent = Agent(model=..., tools=[refund_order],
hooks=[kiff_hook_provider(guard)])from kiff_guard import Guard, HTTPClient, ToolMap
from kiff_guard.adapters.microsoft_agent_framework import kiff_guard_middleware
tm = ToolMap().bind("refund_order", action="REFUND_ORDER",
entity_type="Order", entity_arg="order_id")
guard = Guard(client=HTTPClient(api_key=KEY, tool_map=tm),
tenant="acme", agent="refunds", mode="enforce")
guard.connect(adapter="ms-agent-framework")
agent = Agent(tools=[refund_order],
middleware=[kiff_guard_middleware(guard)])from kiff_guard import Guard, HTTPClient, ToolMap
from kiff_guard.adapters.hermes import register_kiff_guard
tm = ToolMap().bind("refund_order", action="REFUND_ORDER",
entity_type="Order", entity_arg="order_id")
guard = Guard(client=HTTPClient(api_key=KEY, tool_map=tm),
tenant="acme", agent="refunds", mode="enforce")
guard.connect(adapter="hermes")
register_kiff_guard(ctx, guard) # in your Hermes plugin's register()import { Guard, HTTPClient, ToolMap } from "@kiff/kiff-guard";
import { registerKiffGuard } from "@kiff/kiff-guard/adapters/openclaw";
const tm = new ToolMap().bind("refund_order", {
action: "REFUND_ORDER", entityType: "Order", entityArg: "order_id" });
const client = new HTTPClient({ apiKey: KEY, toolMap: tm });
const guard = new Guard({ client, tenant: "acme", agent: "refunds", mode: "enforce" });
registerKiffGuard(ctx, guard); // in your OpenClaw plugin# No adapter needed. Wrap the one function that moves money.
def issue_refund(order, amount):
d = kiff.decide("REFUND_ORDER", entity=order, amount=amount)
if not d.allowed:
return d # blocked or held, never execute
payments.refund(order, amount) # your code, unchanged# No SDK. Any language. POST the proposed action; act only on "allowed".
curl -s https://api.kiff.dev/v1/proposals/decide \
-H "Authorization: Bearer $KIFF_KEY" -H "Content-Type: application/json" \
-d '{"id":"rd-4471","entity_id":"order-4471","entity_type":"Order",
"action_name":"REFUND_ORDER","actor_id":"refunds",
"parameters":{"amount":8400,"reason":"damaged"}}'
# -> {"outcome":"allowed"} then POST .../execute for a signed receiptunder every card
Describe your business once. Every agent uses it.
A card narrows a domain: the states an order or invoice moves through, the actions allowed in each, and who may take them. It lives in a kiff.yaml in your repository, and your coding agent can write it.
- orderCART → PAID → FULFILLED → RETURNED
- invoiceISSUED → APPROVED → PAID → RECONCILED
- claimOPENED → REVIEWED → APPROVED → SETTLED
install the domain skill in your coding agent
curl -fsSL https://kiff.dev/skills/kiff-domains.md \ -o .cursor/rules/kiff-domains.mdc
mkdir -p .kiro/skills/kiff-domains && curl -fsSL https://kiff.dev/skills/kiff-domains.md \ -o .kiro/skills/kiff-domains/SKILL.md
curl -fsSL https://kiff.dev/skills/kiff-domains.md >> AGENTS.md
mkdir -p ~/.claude/skills/kiff-domains && curl -fsSL https://kiff.dev/skills/kiff-domains.md \ -o ~/.claude/skills/kiff-domains/SKILL.md
mkdir -p ~/.copilot/skills/kiff-domains && curl -fsSL https://kiff.dev/skills/kiff-domains.md \ -o ~/.copilot/skills/kiff-domains/SKILL.md
curl -fsSL https://kiff.dev/skills/kiff-domains.md >> GEMINI.md
curl -fsSL https://kiff.dev/skills/kiff-domains.md >> AGENTS.md
curl -fsSL https://kiff.dev/skills/kiff-domains.md >> AGENTS.md
curl -fsSL https://kiff.dev/skills/kiff-domains.md >> AGENTS.md
mkdir -p .windsurf/rules && curl -fsSL https://kiff.dev/skills/kiff-domains.md \ -o .windsurf/rules/kiff-domains.md
what your function sees
Same call. Three answers.
allowed
Right state, on the card, room left. Runs, and nobody has to read it.
refused
Refused on the day’s authority. Your function never reaches the payment line.
refused
A limit the agent can raise is not a limit. Only an owner or admin changes a card.