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 gets the same answer.
- Record
Every decision is stored with a signed receipt, and what it used from the card.
KIFF never runs the action. It authorizes; your system executes.
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.