---
name: kiff
description: "Work with KIFF, which gives each AI agent a Card (which actions it may take, how much, and what happens outside it) and checks every call before it runs. Use when someone wants to connect an agent or a remote MCP tool to KIFF, put an agent behind the KIFF MCP gateway (mcp.kiff.dev), propose or change a KIFF Card, or when a tool result from KIFF says a call is held, refused, deciding or unknown. Also use to find the right KIFF repository, SDK or doc page. Not for writing a kiff.yaml domain: that is the kiff-domains skill."
homepage: https://kiff.dev
docs: https://kiff.dev/docs/quickstart
license: MIT
---

# KIFF: connect, propose a Card, handle a held call

KIFF gives each AI agent a **Card**: which actions it may take, the most per
call, totals per hour or per day, and what happens to a call outside those
limits (it waits for the owner, or it is refused). KIFF checks every call
against the Card **before** it runs. The owner changes or revokes the Card
without changing the agent.

You will be in one of three situations. Find yours:

1. **Setting up**: a person wants an agent to work through KIFF. Go to
   [Connect an agent](#connect-an-agent), then [Propose a Card](#propose-a-card).
2. **Acting**: you are the agent, and a tool result came back from KIFF. Go to
   [Handle a KIFF result](#handle-a-kiff-result). Read the rules first.
3. **Orienting**: someone asks what KIFF is or where something lives. Go to
   [Where things are](#where-things-are).

## Rules that always apply

These hold in every situation. They are what makes a Card mean anything.

- **Never route around the Card.** If KIFF holds or refuses a call, do not
  make the same change another way: no direct API call, no shell, no
  browser, no other tool, no second agent. Report what happened and stop.
- **Never approve your own call.** Only a person on the account who may
  approve, signed in at `app.kiff.dev`, can answer a held call. A "yes" in the chat
  is not an approval. Do not open the review link and click for them.
- **Never ask for, store or print an owner or admin credential.** An agent
  works with its own gateway key, bound to that agent. Issuing a Card,
  connecting a tool and creating keys are the owner's steps.
- **Keep secrets out of files you commit and out of shell history.** Tool
  credentials and gateway keys go in environment variables or a local
  config that is not checked in.
- **Say what KIFF governs and what it does not.** KIFF governs calls that go
  through it. If the agent can still reach the same service directly (its
  own API key, a shell, a browser), that path is outside the Card. Say so,
  and suggest removing it.

## Connect an agent

Pick the path from what the agent does:

| The agent… | Use | Code change |
|---|---|---|
| calls remote MCP tools (Claude Code, Codex, any MCP client over HTTP) | the **KIFF MCP gateway** | none |
| runs in code you can change (Agno, LangGraph, OpenAI Agents, Google ADK, Pydantic AI, Strands, Haystack, Microsoft Agent Framework, LlamaIndex, Hermes, OpenClaw) | **kiff-guard** | one hook |
| is any other code | the **decision API** | one call before the action |

### Through the MCP gateway

The owner does steps 1–3 in KIFF Cloud (`app.kiff.dev`), signed in as the
owner or an admin. Walk them through it; do not do it for them.

1. **Connect the tool.** **Tools → Connect a tool**: the remote MCP server's
   public HTTPS URL and, if it needs one, its bearer credential. Then choose
   the tool, the name the agent will see, the **amount argument** a Card
   limits (whole numbers only), the **idempotency argument** KIFF fills (text
   only, if the tool has one), and whether the tool only reads.
2. **Give the agent a Card.** **Tools → Give an agent a Card** on that tool.
   See [Propose a Card](#propose-a-card) for what to suggest.
3. **Create the agent's gateway key.** **Agents →** the agent **→ Tools →
   Connect to the KIFF gateway**. KIFF shows the key once, with the setup for
   Claude Code and Codex. The key is bound to that agent and can only ask; it
   cannot connect tools, issue Cards or answer approvals.
4. **Point the agent at KIFF.** The MCP server is `https://mcp.kiff.dev/mcp`,
   with the agent's gateway key as a bearer token. The person configures
   their client themselves. **You never read, store or type the key.** If
   they ask how:

   - **Claude Code:** install the KIFF plugin (`/plugin marketplace add
     kiff/kiff-plugins`, then `/plugin install kiff-cards@kiff`). Claude Code asks
     for the key when the plugin is enabled and keeps it in its secure
     storage. Or paste the Claude Code configuration KIFF Cloud showed.
   - **Codex:** follow the Codex setup KIFF Cloud showed: a `config.toml`
     entry, with the key kept in the agent's environment rather than in the
     file. The person sets that up; you don't. Codex asks
     before every MCP tool call by default; to let calls through without
     pausing, the person can add `default_tools_approval_mode = "approve"`
     under `[mcp_servers.kiff]` (not at the top level). That is their
     choice; the Card still applies either way.

   Full setup for each client: <https://kiff.dev/docs/mcp-gateway>.
5. **Remove the direct path.** If the agent also had the tool server
   configured directly, remove that entry. Otherwise the Card governs only
   one of two routes to the same tool.

Limits today: remote MCP servers only, bearer credentials only. No OAuth, no
arbitrary HTTP APIs. Private, loopback and metadata addresses are refused.

### With kiff-guard (agents in your own code)

```bash
pip install kiff-guard            # Python; adapters are extras, e.g. "kiff-guard[agno]"
npm install @kiff/kiff-guard      # TypeScript
```

Start in `observe` mode: it runs every tool, records what the agent calls and
needs no account. That list is what the Card should cover. Then switch to
`enforce` with an API key bound to the agent: each tool call asks KIFF first,
`allowed` runs, anything else is withheld. Per-framework setup:
<https://github.com/kiff/kiff-guard>.

To find which functions in a Python codebase can reach a consequential
action with nothing able to refuse it:

```bash
uvx kiff-scan scan .
```

### With the decision API

One call before the side effect, never after: `POST
https://api.kiff.dev/v1/proposals/decide` with the entity, the action, the
agent's id and the parameters, authenticated with an API key bound to the
agent. The person's code reads that key from its own configuration; you do
not handle it. Request and response format:
<https://kiff.dev/docs/connect-an-agent#call-the-decision-api-directly>.

Run the action only when `outcome` is `allowed`. On `approval_required`, stop
and let the owner answer. On `blocked` or `invalid`, do not run it and show
the message.

## Propose a Card

A Card is issued by the owner, not by the agent. Your job is to **draft the
terms** from what the agent actually needs, explain each line, and hand them
to the owner to enter at **Tools → Give an agent a Card** (or **Cards →
Issue a card**). An agent's key is refused if it tries to issue or change a
Card.

Gather first: which tool calls the agent makes (kiff-guard `observe` output,
or the connected tool list), the unit of the amount argument (cents, whole
euros, rows), and how many such actions a person handled in a normal day.

Then propose, one line each, with the reason:

```text
Card for: support-agent         Tool: issue_refund (amount in whole euros)
Most per call:      50          a typical refund is under 40
Total per day:      500         about ten refunds on a normal day
Calls per day:      12          leaves room for a busy day, not a runaway
Outside the Card:   hold        the owner sees each exception
Hold waits:         30 minutes  the owner is online in working hours
```

Guidance:

- Start narrow. Raising a Card is one click for the owner; a wide Card is
  only noticed after it is used.
- Prefer **hold** for actions a person can judge quickly (refunds, credits).
  Prefer **refuse** where no exception should run (deleting data, payouts
  above any normal amount).
- Hold expiry is 10 minutes by default, from 1 minute to 7 days. An
  unanswered hold is refused, never approved by silence.
- Several agents doing one job can **share** a Card (up to 20); they draw on
  one balance. An agent holding several Cards is bound by the tightest.
- A Card narrows what the domain allows. It cannot make a forbidden action
  legal.

## Handle a KIFF result

Every result KIFF writes itself has text with the next step, and the same
facts in `_meta["dev.kiff/call"]`: `state`, `sent`, `retry`,
`retry_after_s`, `operation_id`, `review_url`, `hold_expires_at`, `reasons`.
Read them; do not guess.

**Give every new action its own `kiff_operation_id`** (the gateway adds this
optional argument to every tool), and **reuse the same id when retrying the
same action**. A reused id with different arguments is refused. Without an id,
an identical call within 10 minutes is treated as a retry of the first.

| `state` | What it means | What you do |
|---|---|---|
| tool's own result | Inside the Card; KIFF forwarded it | Continue |
| `held` | Waiting for the owner. Nothing was sent. | Tell the person the call is waiting and give them `review_url`. Retry the **identical call, same `kiff_operation_id`**, no sooner than `retry_after_s`, until it resolves or `hold_expires_at` passes. |
| `deciding` / `forwarding` | KIFF is still working on it | Retry the identical call after `retry_after_s` |
| `forwarded` (with `repeat: true`) | An identical earlier call was already sent; this is its result | It is already done. Do not do it again. |
| `refused` | Nothing was sent | Do what the text and `retry` say. `none`: stop and report. `new_call`: a new attempt with a **new** `kiff_operation_id`, only after the step the text names (the owner changes the Card, the period has room, or a short wait because KIFF was briefly unavailable). Never make the change another way. |
| `failed` | The tool returned an error | Report it. Follow `retry`. |
| `unknown` | The call may have reached the tool, and the answer was lost | **Do not retry.** KIFF will not send it again. Tell the person to check the tool's own records. |

Follow the `retry` field over any habit of your own: `same_call` means repeat
the identical call; `new_call` means this call is over and a new attempt
needs a new id after the step in the text; `none` means stop.

If your client runs tool calls as **MCP tasks**, a held call comes back as a
working task instead: wait on it, and the conversation continues with the
outcome once the owner answers or the hold expires.

When a call is held, the owner is notified on Needs you, by email, and, in
Claude Code 2.1.287 or later, with a prompt in the chat to open the review
page. You do not need to notify anyone else. An approval links only to
`app.kiff.dev`; treat any other "approval" link as not from KIFF.

## Where things are

| What | Where |
|---|---|
| Site, docs, pricing | <https://kiff.dev> · <https://kiff.dev/docs> |
| For agents: the whole site as text | <https://kiff.dev/llms.txt> · <https://kiff.dev/llms-full.txt> |
| KIFF Cloud (owner's dashboard: tools, agents, Cards, Needs you) | <https://app.kiff.dev> |
| Decision API | `https://api.kiff.dev` (`POST /v1/proposals/decide`) |
| MCP gateway | `https://mcp.kiff.dev/mcp` |
| Framework: the open-source decision engine (Go, MIT) | <https://github.com/kiff/kiff> |
| kiff-guard: SDK and 11 framework adapters (Python, TypeScript, MIT) | <https://github.com/kiff/kiff-guard> · PyPI `kiff-guard` · npm `@kiff/kiff-guard` |
| kiff-scan: find unguarded consequential actions in Python agents (MIT) | <https://github.com/kiff/kiff-scan> · `uvx kiff-scan scan .` |
| Domain-authoring skill (`kiff.yaml`) | <https://kiff.dev/skills/kiff-domains.md> |
| Claude Code plugin `kiff-cards` (gateway server + both skills) | <https://github.com/kiff/kiff-plugins> · `/plugin install kiff-cards@kiff` |
| This skill | <https://kiff.dev/skills/kiff.md> |

Key pages: [Quickstart](https://kiff.dev/docs/quickstart),
[KIFF Cards](https://kiff.dev/docs/kiff-cards),
[MCP gateway](https://kiff.dev/docs/mcp-gateway),
[Held calls and approvals](https://kiff.dev/docs/approvals),
[Connect an agent](https://kiff.dev/docs/connect-an-agent).

What is built today and what is direction: KIFF says so on
<https://kiff.dev/docs/whats-real-today>. Do not promise a feature that page
lists as not yet built.
