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.

your_app.py
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.

  1. Ask

    The agent’s code sends the action, the entity and the parameters. One request.

  2. 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?

  3. Answer

    Allowed, refused, or needs approval, with the reason. A retry of the same request gets the same answer.

  4. 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.

pip install kiff-guard # or: npm i @kiff/kiff-guard
agent.py
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 runs
agent.py
from 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)])
agent.py
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): ...
agent.py
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))
agent.py
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))
agent.py
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)])
agent.py
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)])
agent.py
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()
agent.ts
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
agent.py
# 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
shell
# 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 receipt

Connect an existing agent →   Call the decision API →

under 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.

same pattern · different work
  • 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

Read the domain grammar →

what your function sees

Same call. Three answers.

issue_refund · €42 · paid order

allowed

Right state, on the card, room left. Runs, and nobody has to read it.

issue_refund · €88 · paid order

refused

Refused on the day’s authority. Your function never reaches the payment line.

the agent asks for more room

refused

A limit the agent can raise is not a limit. Only an owner or admin changes a card.

Run it live, against a real agent →

Connect one action today.