# KIFF > KIFF is the open-source operational foundation for agentic systems: the layer that lets you put an AI agent, human, or service to work on consequential actions while you keep control of what each is allowed to do. You define an operational domain once (its entities, states, actions, authority, approvals, and evidence), and any actor proposes actions against that contract. The runtime checks state, permissions, parameters, and approvals against the system's real state, returns a decision before execution, and records a tamper-evident receipt. It is what lets you hand an agent the routes that move money or change state and stay in control. KIFF is a framework first (MIT, Go) with hosted and authoring layers around it. Build the operational domain once and connect any agent; replace the agent without rewiring the system. The framework is the kernel you can embed with zero cloud; KIFF Cloud runs the same runtime as a hosted HTTP contract; KIFF Studio authors the domain; and kiff-guard is the on-ramp for agents you already run. ## Key facts - Open source: the KIFF Framework is MIT-licensed, written in Go. - What it is: a coordination and runtime layer — events, state, decisions, action contracts, approvals, and a replayable, tamper-evident audit trail. It is not a workflow engine, an agent platform, a model gateway, or an app builder; it composes with those. - Core property: a decision is made before a consequential action executes, and callers cannot self-approve — the runtime is the sole authority that grants execution. - Product layers: Framework (the kernel) · Studio (author the domain) · Cloud (the hosted runtime over HTTP) · kiff-guard (observe agents you already run, then migrate the risky actions in). - The domain is the reusable unit; agents are meant to be replaceable. ## Core - [KIFF](https://kiff.dev/): The operational foundation for agentic systems — define the domain once, connect any agent, decide before execution. - [Why KIFF](https://kiff.dev/why): The conviction behind KIFF in plain language — the trust ceiling under every agent that's allowed to act, and the principles (open source, verifiable proof, humans keep authority) the product is built on. - [Whitepaper](https://kiff.dev/whitepaper): The framework's design and principles. - [Proof — the boundary governing a real action](https://kiff.dev/proof): A state-aware runtime stopping a duplicate $10,000 payment, with a replayable decision trail. - [Pricing](https://kiff.dev/pricing): KIFF Cloud pricing. ## The framework (open source, MIT, Go) - [KIFF Framework on GitHub](https://github.com/kiff/kiff): The protocol kernel — events, state, decisions, actions, approvals, audit. Embed it in your backend; useful with zero cloud. Ships a CLI to scaffold and verify domains. - [Quickstart — ship one governed action](https://kiff.dev/docs/quickstart): Scaffold a runnable refund domain with `kiff new -scenario refund`, run it, and watch the boundary allow the good refund, hold the risky one for a human, and refuse the duplicate. Local, MIT, no account. - [What KIFF is](https://kiff.dev/docs/what-kiff-is): Scope and non-goals. - [Concepts](https://kiff.dev/docs/concepts): How the coordination loop works. - [Domains](https://kiff.dev/docs/domains): The reusable operational contract. - [Decisions](https://kiff.dev/docs/decisions): The proposal-to-decision model. - [Receipts and proof](https://kiff.dev/docs/receipts-and-proof): Tamper-evident evidence that the boundary ran. - [Protected controls](https://kiff.dev/docs/protected-controls): The self-approval trust boundary. - [What's real today](https://kiff.dev/docs/whats-real-today): An honest account of what ships now. ## kiff-guard (the on-ramp for existing agents) - [kiff-guard on GitHub](https://github.com/kiff/kiff-guard): Attach to your agent's tool calls, observe what it does, then migrate the risky actions onto the framework. Python and TypeScript, MIT, with adapters for common agent frameworks. - [Connect an existing agent](https://kiff.dev/docs/connect-an-agent): Attach the guard in observe mode, watch what your agent does, derive a starter domain from real traffic, then enforce — no rewrite. - [Domain skill for coding agents](https://kiff.dev/skills/kiff-domains.md): A portable SKILL.md a coding assistant (Cursor, Kiro, Codex, Claude Code, and more) installs so it authors and extends your kiff.yaml domains against the real grammar — states, actions, approvals, permissions, executors. Fetch with one curl. - Adapter guides: [Agno](https://kiff.dev/docs/agno), [LangGraph](https://kiff.dev/docs/langgraph), [Hermes](https://kiff.dev/docs/hermes), [OpenAI Agents](https://kiff.dev/docs/openai-agents), [Google ADK](https://kiff.dev/docs/google-adk), [Pydantic AI](https://kiff.dev/docs/pydantic-ai), [Strands](https://kiff.dev/docs/strands), [Haystack](https://kiff.dev/docs/haystack), [Microsoft Agent Framework](https://kiff.dev/docs/microsoft-agent-framework), [LlamaIndex](https://kiff.dev/docs/llamaindex). ## KIFF Cloud (hosted runtime) and Studio (authoring) - [KIFF Cloud](https://app.kiff.dev): The KIFF runtime, hosted — agents and services call it over HTTP to propose an action and get a decision before execution, with approvals, signed receipts, and a governance evidence view. - [KIFF API](https://api.kiff.dev): The headless runtime contract — POST a proposal, get a decision before execution. - [Cloud quickstart](https://kiff.dev/docs/cloud-quickstart): Govern one action over HTTP — seed a starter domain, POST one proposal, watch KIFF refuse a refund on an unpaid order, then see the domain behind the decision. No Go, no framework, no domain to author by hand. - [Five-line integration](https://kiff.dev/docs/five-line-integration): Put one proposal call in front of an existing consequential function; your code stays the executor. - [Govern](https://kiff.dev/docs/govern): Decisions, receipts, approvals, and verifiability. ## Legal and trust - [Security](https://kiff.dev/security) - [Privacy](https://kiff.dev/privacy) - [Terms](https://kiff.dev/terms) - [Data Processing Addendum](https://kiff.dev/legal/dpa) - [EU AI Act](https://kiff.dev/ai-act) ## Optional - [llms-full.txt](https://kiff.dev/llms-full.txt): The full text of the docs and whitepaper concatenated into one file, for single-fetch ingestion. _Last updated: 2026-07-16._ --- # Full text The complete text of the KIFF docs and whitepaper follows, for single-fetch ingestion. Source of truth: https://kiff.dev/docs and https://kiff.dev/whitepaper. --- # Documentation ## Quickstart — ship one governed action in 5 minutes Source: https://kiff.dev/docs/quickstart # Ship one governed action in 5 minutes Pick the action you'd never let an agent take unsupervised — a refund — and ship a governed version of it. You scaffold a runnable refund domain, run it, and watch the boundary let the good refund through, hold the risky one for a human, and refuse the duplicate — **before any money moves**. No account, no cloud, all local. This is the fast path: install → scaffold → run → read the decisions. KIFF is an MIT Go framework; this quickstart runs entirely on your machine. [KIFF Cloud](/docs/cloud-quickstart) is the hosted way to run the same boundary later — optional, not required here. ## 1. Install and scaffold ```bash go install github.com/kiff/kiff/cmd/kiff@latest kiff new -scenario refund github.com/acme/refunds cd refunds go mod tidy make demo ``` `kiff new -scenario refund` generates a runnable refund domain: an `Order` entity, a `MARK_PAID` action, an approval-gated `REFUND_ORDER` action, a headless HTTP API, and a demo script. `make demo` runs it end to end. > Building against a local checkout of the framework? Add > `-replace-local /path/to/kiff`. Want a smaller starter without the > refund scenario? `kiff new github.com/acme/thing`. ## 2. The useful action runs `make demo` marks an order paid, then has the agent propose a refund on it. The refund is a real, consequential action — and it runs: ```text order-2 PAID agent → REFUND_ORDER $12.00 KIFF ✓ ALLOWED — the contract is satisfied → your executor refunds → REFUNDED ``` This is the point of KIFF: the agent does the work you'd otherwise keep it away from, because the boundary checked the action against the order's state and your policy first. KIFF returns the decision; **your code executes** the refund on `allowed`. ## 3. The risky path waits; the repeat is refused The same domain makes the dangerous cases safe without you writing defensive glue: ```text agent → REFUND_ORDER $999.00, high-risk KIFF ⏸ HELD — approval required (the executor did NOT run) human → approves KIFF ✓ ALLOWED — refund executes → REFUNDED agent → REFUND_ORDER (same order, retried) KIFF ✗ BLOCKED — order is already REFUNDED (refused before money moves again) ``` A high-risk refund holds for a human; a duplicate refund on an already-refunded order is refused. The agent proposing the wrong thing is normal — the boundary is what makes proposing it safe. ## 4. It replays, and it's on the record Every decision and execution is protocol data, not an optional log. The entity's state rebuilds from the stored events alone: ```text replay from events → REFUNDED materialized == replayed ``` That replay — plus the recorded decisions and approvals — is the proof of what happened and why. You can verify it yourself; it isn't a number on someone's dashboard. ## What you just shipped, and what's next You put an agent on a refund and let it run, with the risky and duplicate paths handled by a boundary outside the agent — in five minutes, on your machine. - **Understand the boundary** → [How the boundary works](/docs/concepts): events, state, typed decisions, approvals, and the no-self-approval rule. - **Already have an agent?** [Connect an existing agent](/docs/connect-an-agent): attach the guard in observe mode, watch what it does, derive a starter domain from real traffic, then enforce — no rewrite. - **Run it hosted** → [Govern one action over HTTP](/docs/cloud-quickstart): route a consequential action through KIFF Cloud over plain HTTP, no Go required. - **Go deeper** → [Reusable operational domains](/docs/domains) is the thing you build once and connect any agent to. The framework and every scaffold live in the public MIT repo, [`kiff/kiff`](https://github.com/kiff/kiff). docs.kiff.dev owns the guided journey; the repo owns the code. ## Connect an existing agent Source: https://kiff.dev/docs/connect-an-agent # Connect an existing agent Already have an agent making tool calls? You don't need the scaffold or a rewrite. Attach the guard, watch what the agent actually does, let KIFF derive a starter domain from that real traffic, then turn on enforcement so the consequential calls are governed — and the agent keeps doing the work. This is the on-ramp for the agent you already run. The [quickstart](/docs/quickstart) starts a governed action from scratch; this page meets you where your agent already is. ## 1. Install the guard The guard is an open-source (MIT) Python package, [`kiff/kiff-guard`](https://github.com/kiff/kiff-guard). Install the core plus the adapter for your framework — here, Agno: ```bash pip install "git+https://github.com/kiff/kiff-guard.git#subdirectory=packages/python/kiff-guard" ``` The core has zero required dependencies; each framework adapter pulls its own framework in as an optional extra. > The guard isn't on PyPI yet, so the install is from git for now. A > `pip install kiff-guard` line lands when the package is published — > this page updates the moment it does, never before. ## 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. ```python 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](/docs/agno) shows the same attach with that framework's one-liner, and [How the guard works](/docs/concepts) 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. ```python 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`: ```python from kiff_guard import export_yaml print(export_yaml("my-domain", guard.catalog)) ``` ```yaml # 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. Turn on enforcement When you're ready, switch the guard to `enforce`, point it at your tenant, and KIFF decides **before** each tool runs — `allowed` proceeds, `approval_required` holds for a human, `blocked` refuses. Same one-line attach, same receipts; now the risky calls are governed. ```python guard = Guard(mode="enforce", client=client) # client carries the key ``` ### Provision a runtime key Enforcement needs a **runtime API key** for your tenant. Today you mint it in the [dashboard](/dashboard) — *Keys → create* — scoped to a role, and put it in the agent process's environment: ```bash export KIFF_CLOUD_URL=https://api.kiff.dev export KIFF_CLOUD_API_KEY='kiff_live_...' ``` Keep two credential types separate: - the **KIFF runtime key** authorizes the agent to *ask KIFF for a decision* — it can't grant itself authority; the key's role governs what's permitted server-side; - your **provider credentials** (the model API, your payments API) stay where they already live. KIFF never holds or proxies them. > A `kiff auth login` / `kiff keys create` CLI to mint and write keys in > one command is on the way (it isn't shipped yet — until it is, mint in > the dashboard). This page will show it once it lands. ## Where to go next - **The model behind observe/enforce** → [How the guard works](/docs/concepts). - **Your framework's one-liner** → pick it from the sidebar; ten adapters are shipped and verified. - **Start one from scratch instead** → the [quickstart](/docs/quickstart) scaffolds a governed refund with `kiff new`. - **No Go, just HTTP?** → [Govern one action over HTTP](/docs/cloud-quickstart), or drop in the [five-line proposal call](/docs/five-line-integration). The guard, every adapter, and the conformance suite live in the public MIT repo [`kiff/kiff-guard`](https://github.com/kiff/kiff-guard). docs.kiff.dev owns the guided journey; the repo owns the code. ## Connect your code to KIFF in 5 lines Source: https://kiff.dev/docs/five-line-integration # Connect your code to KIFF in 5 lines Keep the agent, framework, and function you already run. Immediately before a consequential call, send the proposed action to KIFF and branch on the decision. KIFF returns the decision; your application remains the executor. ## Before Today the action runs as soon as the agent or application asks for it: ```python result = process_refund(order_id, amount, reason) ``` There is no independent state check between the proposal and the consequence. ## Add the five-line proposal ```python decision = ask_kiff( entity_id=order_id, entity_type="Order", action="ISSUE_REFUND", parameters={"amount": amount, "reason": reason}, ) ``` Then let the explicit outcome decide whether your existing function runs: ```python if decision["outcome"] == "allowed": result = process_refund(order_id, amount, reason) elif decision["outcome"] == "approval_required": raise NeedsApproval(decision["reasons"]) elif decision["outcome"] == "blocked": raise Blocked(decision["reasons"]) else: raise BadProposal(decision["reasons"]) ``` That boundary is the integration. KIFF does not run `process_refund`; it evaluates the proposal against the entity's current state and domain contract. Your code runs only after an `allowed` decision. ## The helper The helper is a normal HTTP call. It requires no model SDK and no change to your agent runtime: ```python import os import requests def ask_kiff(*, entity_id, entity_type, action, parameters, actor_id="agent"): 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() ``` Configure the hosted endpoint and a key minted in your workspace: ```bash export KIFF_CLOUD_URL=https://api.kiff.dev export KIFF_CLOUD_API_KEY='kiff_live_...' ``` ## Outcomes | Outcome | Meaning | What your code does | |---|---|---| | `allowed` | The current state and contract permit the action. | Run the function. | | `approval_required` | A human authority must decide first. | Hold the action. | | `blocked` | State, permissions, or parameters forbid it. | Do not run it. | | `invalid` | The proposal or domain configuration is incomplete. | Fix the request or domain. | | `limit_exceeded` | The current Cloud allowance is exhausted. | Do not run; surface the billing state. | Branch on `outcome`, not only the HTTP status. Allowed, blocked, and approval-required decisions are all valid answers from the runtime. ## What this does—and does not—guarantee KIFF governs calls routed through this boundary. It does not intercept another code path that bypasses the helper, invent missing domain events, or prove that your downstream function completed successfully. It proves what was proposed, which state and contract were evaluated, and what KIFF authorized before the call. When you are ready to integrate an existing agent through a framework-native hook instead, use [kiff-guard](https://github.com/kiff/kiff-guard) and the [framework guides](/docs/concepts). ## Govern one action over HTTP Source: https://kiff.dev/docs/cloud-quickstart # Govern one action over HTTP Route one consequential action through KIFF Cloud and get a decision *before* it runs — over plain HTTP. No Go, no framework, no domain to author by hand. You'll watch KIFF refuse a refund on an order that was never paid, then see the domain that decision came from. KIFF decides; your code stays the executor. KIFF only governs the actions you route through it. > Base URL: `https://api.kiff.dev`. Every call is authenticated with a > Bearer API key. ## 1. Get an API key Sign in at [app.kiff.dev](https://app.kiff.dev), open **Settings → API keys**, and mint one. It's shown once. Export it: ```bash export KIFF_KEY="kiff_live_…" ``` ## 2. Seed a starter domain (no authoring) You don't start from a blank file. Apply the `refund-agno` starter — a minimal order-refund domain — in one call: ```bash curl -s https://api.kiff.dev/v1/me/domain/template \ -H "Authorization: Bearer $KIFF_KEY" \ -H "Content-Type: application/json" \ -d '{"slug":"refund-agno"}' ``` That domain says, in plain terms: an `Order` moves `CREATED → PAID → REFUNDED`, and `AUTO_REFUND` is only allowed from **PAID**. ## 3. Ask KIFF to decide one action Your refund agent is about to auto-refund `order-001` — an order KIFF has no record of being paid. Ask first: ```bash curl -s https://api.kiff.dev/v1/proposals/decide \ -H "Authorization: Bearer $KIFF_KEY" \ -H "Content-Type: application/json" \ -d '{ "id": "demo-001", "entity_id": "order-001", "entity_type": "Order", "action_name": "AUTO_REFUND", "actor_id": "refund-agent", "parameters": {"amount": 4900, "reason": "customer request"} }' ``` KIFF refuses it: ```json { "proposal_id": "prop_…", "outcome": "blocked", "reasons": ["state_not_allowed"], "message": "entity is not in a state that permits this action", "mode": "enforce" } ``` That's the point. The refund was blocked not because you told KIFF the order was unpaid, but because **KIFF reconstructed the order's state from its own events and the order is not `PAID`**. A retry won't change it. Your agent never sends the money. Once the order is actually paid, the identical call returns: ```json { "outcome": "allowed", "message": "action contract satisfied; safe to execute", "mode": "enforce" } ``` — and your code runs the refund. ## 4. See the proof Every decision is recorded as a signed receipt — proof KIFF decided before execution. Because you threaded an `id` (`demo-001`) on the proposal, its decision receipt has a deterministic trace, `decision-demo-001`: ```bash curl -s https://api.kiff.dev/v1/receipts/decision-demo-001 \ -H "Authorization: Bearer $KIFF_KEY" ``` The receipt is signed and tamper-evident: what was proposed, the state it was decided against, and the outcome. > Receipts are signed by a background sweep, so the signed receipt > appears within about a minute of the decision — if the fetch 404s or > the list is empty, give it a moment and re-run. You can also list > recent receipts with `curl -s https://api.kiff.dev/v1/receipts`. ## 5. The domain behind the decision That decision didn't come from a generic API — it came from a **governed domain**. Open it at [app.kiff.dev/dashboard/domains](https://app.kiff.dev/dashboard/domains): the entity, its lifecycle, the action contract (`AUTO_REFUND` allowed only from `PAID`), and the evidence are all there. From here you **shape the domain in your codebase** — Build scaffolds it to your repo, you finish it with your agent and `kiff verify`, then re-activate — and your agent keeps routing through the same boundary. That's the whole arc: **start with one action, discover the domain behind it, grow into a governed backend.** KIFF Cloud is paid for the governed operations flowing through that domain — not for the domains, actions, agents, or integrations themselves. ## What this is and isn't - KIFF returns a decision; **your system executes**. It does not move money, hold credentials, or run your agent. - It governs the actions you route through it. It can't stop an action on a path that never asks. - The unit is the **governed operation**: one authenticated proposal evaluated against current state and the active domain contract, with a terminal outcome. Idempotent retries don't count twice. ## Next - Attach an existing agent and let KIFF derive the domain from real traffic → [Quickstart](/docs/quickstart). - The proposal/decision model in depth → [Decisions](/docs/decisions). - What a receipt proves → [Receipts & proof packs](/docs/receipts-and-proof). ## How the guard works Source: https://kiff.dev/docs/concepts # How the guard works Every framework page on this site follows the same spine: **install → observe → activate → enforce**. This page explains the model behind that spine once, so the per-framework pages can stay short. ## One guard, two modes The guard is a thin wrapper you attach to your agent's tool calls. It has exactly two modes. **`observe`** runs every tool, records a receipt, and learns your action catalog. It never calls KIFF and never blocks a tool. No client, no tenant, no API key — it's the zero-config way to [connect an agent you already run](/docs/connect-an-agent): see exactly what it does and derive a starter domain from real traffic instead of a blank file. (Every call is also recorded, so you get an audit trail for free — but the point is understanding the agent well enough to govern it.) **`enforce`** asks KIFF to decide **before** each tool runs, and acts on the answer. There are three outcomes: - `allowed` — the tool runs. - `approval_required` — the call is held for a human. - `blocked` / `invalid` — the call is refused; the tool never runs. Enforce needs a client (an API key) and an active domain for your tenant. You mint the key and activate the domain in the [dashboard](/dashboard). ## The one-receipt rule Every tool call produces **exactly one** receipt, in both modes and across every adapter: - `observe` → one `observed` receipt; the tool always ran. - `enforce`, allowed → one governed receipt; the tool ran. - `enforce`, withheld → one governed receipt; the tool did **not** run. This invariant is part of the guard's conformance suite — a contract every adapter must pass. It's what makes the audit trail trustworthy: one tool call, one record, never zero and never two. ## Two adapter shapes Frameworks expose their pre-tool-execution seam in one of two shapes. This is the only thing that differs between adapters; the guard logic lives in the core, once. **Middleware shape** — the framework hands the guard a continuation that runs the tool, so the guard runs it itself: ```python guard.evaluate(tool, args, run=lambda: handler()) ``` The guard runs the tool on `allowed`, and raises `kiff_guard.Hold` (skipping the tool) on a withheld decision. Agno and LangGraph are this shape. **Inverted-control / vote shape** — the framework runs the tool after the hook returns; the hook only votes. The guard never gets a `run` callback: ```python if guard.mode == "observe": guard.observe(tool, args) # record, never block else: decision = guard.decide_only(tool, args) if decision.withheld: ... # tell the framework to skip the tool ``` Hermes and the OpenAI Agents SDK are this shape. The adapter records the receipt explicitly (`record_executed` / `record_withheld`) to keep the one-receipt rule. Which shape a framework uses is a property of that framework's API, not a choice — each framework page states its shape and why. ## The trust boundary The guard never asserts roles or authority. An API key's roles govern what it's allowed to do **server-side**; the guard only carries the call. This means a compromised or buggy adapter cannot widen its own authority — it can only ask, and KIFF decides. The conformance suite pins this too: the guard never injects a `roles` field. ## Fail-safe posture In `enforce`, a transport or guard error **fails closed** by default: a control tower that waves traffic through when its radar is down is the wrong default, so a withheld-on-error is safer than a silent allow. `observe` always allows (it never blocks anyway). Adapters expose a `fail_closed=False` override for hosts that prefer availability over the gate. ## Where the code lives The narrative is here on docs.kiff.dev. The runnable code — the core, every adapter, and the conformance suite — lives in the public MIT repo [`kiff/kiff-guard`](https://github.com/kiff/kiff-guard). Each framework page links out to the adapter and its example; this site never duplicates the code. ## Reusable operational domains Source: https://kiff.dev/docs/domains # Reusable operational domains A **domain** is the thing you build once with KIFF: the state, the valid actions, the authority and approvals, the outcomes, and the evidence that an agentic system would otherwise rebuild around every new agent. A domain is the operational contract your software runs against — and the unit you reuse. That is the apex idea. The decision boundary, the controls, the receipts, and Govern are all things a domain *has*. Start here, and the rest of these pages are detail. ## What a domain holds A domain gathers, in one place, the operational facts about a slice of your business: - **State** — what counts as the current status of an entity (an invoice is `PENDING` or `PAID`; a refund is `REQUESTED` or `SETTLED`), derived from the events KIFF has seen. - **Actions** — the consequential operations that can be taken (`ISSUE_REFUND`, `CREATE_PAYOUT`, `PAY_INVOICE`) and the states each is allowed in. - **Authority and approvals** — who may propose what, what needs a human sign-off, and the thresholds that trigger it. - **Outcomes and evidence** — the terminal result of each proposal and the signed receipt that proves what was decided, before execution. A [Protected Control](/docs/protected-controls) is how you express one consequential action *inside* a domain. It is the natural starting point — pick the one action you'd least like taken unsupervised — but it is a part of the domain, not the foundation everything sits on. The domain is. ## Reuse is the point The reason a domain is worth building is that it outlives any single agent. Once the operational contract exists, you can: - connect a new agent, or replace the one you have, without rewriting what "a legitimate refund" means; - put a human, a scheduled job, or a service through the same boundary the agent uses; - change the framework underneath without changing the contract above. You build the refund domain, the payout domain, the access-change domain once. The agents that act through them are interchangeable. That is the difference between governing an agent and owning an operational domain. ## How you build one You don't start from a blank file. Attach the guard to an agent you already run, in observe mode, and it derives a **starter domain** from real traffic — the actions your agent actually calls, ready to review. You activate the domain in the [dashboard](/dashboard), then point the guard at it in enforce mode. See the [quickstart](/docs/quickstart) and [how the guard works](/docs/concepts). ## The Cloud unit: a governed operation When you run a domain on KIFF Cloud, the thing that's counted is a **governed operation**: one unique authenticated proposal that KIFF evaluates against current state and the active domain contract and records with a terminal outcome. Idempotent retries of the same proposal do not create another governed operation — the duplicate that state refuses is not a second billed event. Domains, actions, agents, integrations, and development environments are not metered. They expand what the platform is worth; only governed operations are counted. See the [pricing page](/pricing) for how Cloud operation is bought. ## Honest boundaries - A domain is **yours**. KIFF decides whether a proposed action conforms to the contract you declared; it does not invent authority you didn't set, and it never becomes your executor. - Reuse is a property you build toward, not a marketplace claim. KIFF does not yet offer a public exchange of domains authored by other customers; reuse here means reuse across your own agents, humans, and services. ## Next - The mechanism a domain runs on → [What KIFF is](/docs/what-kiff-is). - Express your first action → [Protected Controls](/docs/protected-controls). - See what a decision proves → [Receipts & proof packs](/docs/receipts-and-proof). ## What KIFF is Source: https://kiff.dev/docs/what-kiff-is # What KIFF is KIFF lets you put an agent on the consequential actions you've kept it away from — refunds, payouts, payments, role changes — the work that moves money or can't be cleanly undone. It does that by putting a **boundary** around the action: KIFF decides whether this specific action, on this specific entity, is allowed right now — **before it runs** — so the useful action goes through, the out-of-policy one is refused, and the duplicate can't fire twice. The thing you build once is a [reusable operational domain](/docs/domains) — the state, valid actions, authority, approvals, and evidence your agents share. This page is about the mechanism that makes the actions against it safe to hand over. ## What this does KIFF sits in front of an action and answers one question: *is this specific action, on this specific entity, allowed right now, given the entity's state and your policy?* The answer is one of four outcomes — `allowed`, `blocked`, `approval_required`, or `invalid` — and it is produced before the action executes, not after. The refund runs when the order is in a state that permits it. The same refund is refused once the order is already refunded. A high-value one waits for a human. You get to say yes to the agent doing real work because the cases you were afraid of are handled by the boundary, not by hoping the model behaves. Every answer leaves a record: a signed receipt of what was decided, on what basis, at that moment — support for the claim, not the headline. ## What KIFF is not KIFF is the **decision boundary, not the runner.** This distinction is load-bearing, so it's worth being blunt about it: - KIFF does **not** execute your action. It does not move the money, send the email, or write to your database. - KIFF does **not** hold your credentials or proxy your API calls. - KIFF does **not** run your agent or manage your workflow. Your system stays the executor. KIFF returns a decision; your code, having asked, then runs the action when the decision is `allowed` and withholds it when it isn't. This is the same cooperative model every policy engine uses — the value is that the useful action can finally run unattended, with a provable record, not physical interception. ## What you own vs what KIFF decides A clean way to hold the split: **You own:** - the policy — which actions need approval, what the thresholds are, who may authorize what; - the state — what counts as the current state of an entity (an invoice is `PENDING` or `PAID`, a refund is `REQUESTED`); - the thresholds and approval rules; - execution — your runtime runs the action when KIFF says `allowed`. **KIFF decides:** - whether a proposed action is permitted against that state and policy, before it runs; - and produces the receipt that proves what was decided. KIFF derives the *observed action surface* for you — when you attach the guard in observe mode, it learns which tools your agent actually calls and hands you a starter domain from real traffic (see [Connect an existing agent](/docs/connect-an-agent)). But the policy, state model, and approval rules in that domain are yours to set. KIFF never invents authority you didn't declare. ## The shortest path through the product 1. **Ship one governed action** — scaffold a runnable refund domain and watch the boundary let the good refund run, hold the risky one, and refuse the duplicate. See the [quickstart](/docs/quickstart). 2. **Connect the agent you already have** — attach the guard in observe mode, learn its action surface, and derive a starter domain from real traffic. See [Connect an existing agent](/docs/connect-an-agent). 3. **Activate a domain** — start with the one action you'd least like taken unsupervised. See [Domains](/docs/domains) and [Protected Controls](/docs/protected-controls). 4. **Inspect the evidence** — the receipts and the [Govern](/docs/govern) view, exportable as a [proof pack](/docs/receipts-and-proof). If you only read one more page, read [Domains](/docs/domains) — the reusable operational domain is what everything else is built on. ## Protected Controls Source: https://kiff.dev/docs/protected-controls # Protected Controls A **Protected Control** is how you express one consequential action inside a [domain](/docs/domains): one production action that KIFF decides on before it runs, scoped to the thing it acts on. It's the natural place to start — pick the one action you'd least like an agent to take unsupervised — but it's a part of the domain, not the foundation. The [domain](/docs/domains) is what you build once and reuse; a control is how a single action within it is governed. ## What this does A Protected Control wraps a single consequential action — `PAY_INVOICE`, `ISSUE_REFUND`, `CREATE_PAYOUT` — and says: *before this action runs, ask KIFF, and only proceed on the answer.* You start with the one action you'd least like an agent to take unsupervised, and add more controls over time. ## How it works A control is built from a few pieces you define: - **Action** — the operation being governed (e.g. `ISSUE_REFUND`). - **Entity** — the thing the action acts on (an order, an invoice, a customer account), identified so KIFF can reason about *this* one. - **State** — the entity's current status, derived from the events KIFF has seen. Whether an action is allowed usually depends on state: a refund on an order that is already `FULLY_REFUNDED` is not the same request as a refund on a `PAID` order, even if the call looks identical. - **Policy** — your rules: which states permit the action, what parameters are required, what thresholds apply, and whether a human must approve. When the action is proposed, KIFF checks it against the entity's state and your policy and returns one of: - **`allowed`** — the action satisfies the policy for the current state; your runtime runs it. - **`blocked`** — the action is not permitted in this state, or violates a rule; your runtime does not run it. - **`approval_required`** — the action needs a human sign-off before it can proceed; it is held, not run, until resolved. - **`invalid`** — the proposal is malformed or missing required data. ## What you configure You own the policy, the state model, the thresholds, and the approval rules. A control is *your* declaration of when an action is acceptable — KIFF enforces what you declared and proves it did. KIFF does not decide your business rules for you; it decides whether a given action conforms to the rules you set. KIFF helps you start: attached in observe mode, the guard derives the action surface from real traffic so your first control isn't a blank file (see the [quickstart](/docs/quickstart)). But the risk levels, thresholds, and approval requirements are yours to fill in. ## State is what makes a control more than a log The reason a control is worth more than an `if` statement in your app is state held *outside* the acting system. The classic case: an agent tries to pay an invoice, the response drops, and the call retries. Each retry is individually valid — same invoice, same amount. Only something that knows the invoice was `PENDING` on the first call and `PAID` on the retry can refuse the duplicate. A control is that independent, state-aware authority. ## What KIFF still does not do A control decides; it does not execute. On `allowed`, **your runtime** runs the action. On `blocked` or `approval_required`, your runtime withholds it. KIFF is the boundary and the record, not the hand that moves the money. See [What KIFF is](/docs/what-kiff-is) for the full boundary. ![A Protected Control in Govern: control identity (action, entity type, environment), current enforce mode, and the decisions-by-outcome summary.](/static/img/docs/protected-control-overview.png) *A Protected Control in Govern — control identity, current mode, and decisions grouped by outcome, from a clean demo tenant.* ## Next - See a control in action → [Decisions](/docs/decisions). - See the evidence a control produces → [Receipts & proof packs](/docs/receipts-and-proof) and [Govern](/docs/govern). ## Decisions Source: https://kiff.dev/docs/decisions # Decisions A decision is KIFF's answer to "is this action allowed, right now?" — produced **before** the action runs. This page covers what's evaluated, the four outcomes, and the lifecycle from proposal to recorded decision. ## What this does Your runtime proposes an action. KIFF evaluates that proposal against the entity's current state and the control's policy, and returns a decision. Your runtime then acts on the answer: run the action on `allowed`, withhold it otherwise. The decision happens at the gate, not after the fact. ## The decision flow ``` your runtime: about to run an action │ ▼ propose to KIFF ──► KIFF evaluates: │ • entity's current state (from events) │ • the control's policy + thresholds │ • required parameters present? │ • does the actor's identity hold authority? ▼ decision returned: allowed | blocked | approval_required | invalid │ ▼ your runtime acts on it: allowed → run the action blocked / invalid → do not run approval_required → hold until a human resolves it │ ▼ one signed receipt recorded, either way ``` ## The four outcomes - **`allowed`** — the proposal satisfies the policy for the current state. Your runtime runs the action. - **`blocked`** — a governance answer: the proposal is well-formed, but the action is not permitted in this state or breaks a rule (over a threshold, wrong state, missing authority). KIFF evaluated it and said no. Your runtime does not run it. - **`approval_required`** — the action needs a human sign-off first. It is held; your runtime does not run it until the approval is resolved. - **`invalid`** — the proposal is a **broken request**, not a governance answer: it is malformed or misconfigured against the contract — a required parameter is missing, a parameter fails the contract's constraints, the contract is invalid, or it has no executor. There was nothing to decide; the request itself needs fixing. A useful invariant: anything that is not exactly `allowed` means the action should not run. When KIFF runs in enforce mode through the guard, that withholding is the default behavior, and an error fails closed (withheld) rather than silently allowing. See [How the guard works](/docs/concepts) for the fail-safe posture. ### `blocked` vs `invalid` — a decision vs a broken request The two are distinct on purpose, and your integration should treat them differently: - **`blocked` is a governance decision.** KIFF looked at a valid request and refused it. It returns successfully (the decision *is* the result), counts as a governed operation, and surfaces in your evidence and alerts — it's a real thing that happened. - **`invalid` is not a governance decision.** The request never got as far as one — either it was malformed/misconfigured, or the data plane couldn't evaluate it right then. Either way it comes back as an error (not a success), is **not** counted as a governed operation, and does not raise a governance alert. Two sub-cases, and they call for different handling: - **Malformed or misconfigured** (missing/invalid parameter, invalid contract, no executor) → a client/config error (an HTTP `4xx`) that is yours to **fix** — the same way a bad request to any API is the caller's to correct, not a policy event. - **Transient data-plane error** (the runtime, state store, or action registry was briefly unavailable) → returned as an HTTP `5xx` you should **retry**, not fix; nothing was wrong with your request. So a missing or invalid parameter is `invalid` (fix your request), not `blocked` (a policy said no). Branch on this: on `blocked`, respect the decision; on `invalid`, **fix** a `4xx` or **retry** a `5xx`. ## What data is evaluated A decision is made against: - **the entity's state** at the moment of the proposal, derived from the events KIFF has recorded — not a value the caller asserts; - **the control's policy** — permitted states, required parameters, thresholds, approval rules — which you defined; - **the actor's identity**, resolved server-side. The roles a request carries are descriptive; authority is resolved from policy keyed to an authenticated identity, never from what the caller claims about itself. ## The proposal / decision lifecycle 1. **Proposal** — your runtime submits the action it intends to take, with the entity and parameters. 2. **Evaluation** — KIFF reads the current state and applies the policy. 3. **Decision** — one of the four outcomes is returned, synchronously, before execution. 4. **Hold (if `approval_required`)** — the proposal waits for a human resolution rather than running. 5. **Record** — exactly one signed receipt captures what was decided and on what basis. See [Receipts & proof packs](/docs/receipts-and-proof). ### Reading an entity's lifecycle The proposals, validations, approvals, and executions KIFF has recorded for a single entity can be read back as one chronological, read-only view — the entity's governed history in order, assembled from records KIFF already keeps. It's a projection, not a new decision path: it changes nothing and simply surfaces what happened. Like every read surface it is scoped to your tenant — you only ever see your own entities — and a valid entity with no history yet returns an empty lifecycle rather than an error. ## Example shape The exact request/response contract for the decide endpoint and the guard's `enforce` mode lives with the runnable code in the public guard repo, [`kiff/kiff-guard`](https://github.com/kiff/kiff-guard), and on each [framework page](/docs/agno). Conceptually, your runtime sends the action and entity, and receives a decision with an outcome and a trace reference for the receipt: ```text proposal: action=ISSUE_REFUND entity=order/ord_4821 amount_cents=2500 decision: outcome=blocked reason=state_not_allowed (order already FULLY_REFUNDED) → one receipt recorded; your runtime does not issue the refund ``` ![A blocked decision in the dashboard: the REFUND_ORDER action on an order that was not in a refundable state, with the reason state_not_allowed.](/static/img/docs/decision-blocked-detail.png) *A blocked decision from a clean demo tenant — the action, the entity state at decision time, and the reason (`state_not_allowed`).* ## What KIFF does not do here KIFF returns the decision. It does not execute the action on `allowed` — **your runtime does** — and it does not undo anything downstream. The decision proves what was authorized before execution; it does not prove what your system did afterward. That distinction is the heart of [Receipts & proof packs](/docs/receipts-and-proof). ## Receipts & proof packs Source: https://kiff.dev/docs/receipts-and-proof # Receipts & proof packs Every decision KIFF makes leaves a **receipt**: a signed record of what was authorized, on what basis, before the action ran. A **proof pack** is the exportable bundle of that evidence for one control over a window. This page is precise about what a receipt proves — and, just as important, what it does not. ## What this does When KIFF decides on a proposed action, it records one receipt capturing the decision: the action, the entity and its state at that moment, the outcome, and the policy that was checked. The receipt is signed and tamper-evident, so the record of the decision can't be quietly altered after the fact. ## What a receipt proves A decision receipt proves **pre-execution authorization**: that, at a specific moment, this action on this entity was evaluated against the recorded state and your declared policy, and a specific decision — `allowed`, `blocked`, `approval_required`, or `invalid` — was reached *before* the action ran. It is verifiable and tamper-evident. In plain terms: the receipt is proof of *what was authorized and on what basis*, made at the gate. ## What a receipt does not prove This boundary matters, and overstating it would be dishonest: - A receipt does **not** prove the action executed. KIFF is the decision boundary, not the runner — your system executes `allowed` actions, and what happened downstream is outside the receipt. - A receipt does **not** prove the outcome of the action (that the money actually moved, that the email was delivered). - A receipt does **not** assert reviewer attribution beyond what the product records; approval-to-control linkage is limited today (see [What's real today](/docs/whats-real-today)). So a receipt answers "was this authorized, before it ran, against what state and policy?" — not "what did the system do after?" Those are different questions, and conflating them is exactly the trap honest evidence avoids. ## Why "before" is the whole point Most systems log what happened after the fact, in a store the acting system controls. That's an account, not proof — the actor wrote it, and it can be edited. A decision receipt is different on all three counts: it's made **before** the action, by the decision boundary rather than the actor, in a **tamper-evident** form. That's what makes it hold up when someone later asks you to show what was authorized. ## Proof packs A proof pack is the exportable form of this evidence for one Protected Control over a time window. The on-screen [Govern](/docs/govern) view and the exported proof pack are built from the **same evidence object**, so what you see and what you hand someone else cannot diverge. A proof pack covers what the evidence object can truthfully show for the window: the control's identity, the decisions by outcome, the signed receipts and their verifiability, and the posture (shadow vs enforce). It carries an honesty disclaimer about scope — a sampled "all verified" never reads as a complete-window guarantee. ## Verifiability and tamper-evidence Receipts are signed, which is what makes them tamper-evident: a change to the recorded decision breaks the signature. Receipts can optionally be anchored to an external trail for independent verifiability; the production form of that anchoring is not shipped, so the honest claim today is **signed, tamper-evident, verifiable** — not more. See [What's real today](/docs/whats-real-today) for the exact line. ![A receipt detail in the dashboard: the decision it records and its signature status, shown as Signed.](/static/img/docs/receipt-detail.png) *A receipt detail from a clean demo tenant — the decision it records and its signature status (Signed.).* ![A proof-pack export: the signed evidence bundle for one control over a window, with the scope/honesty disclaimer.](/static/img/docs/proof-pack-export.png) *The proof-pack export for one control — the signed decisions and receipts in scope, led by the honesty disclaimer about what it does and does not assert.* ## Next - See receipts in context → [Govern](/docs/govern). - Understand what produced them → [Decisions](/docs/decisions). ## Govern Source: https://kiff.dev/docs/govern # Govern Govern is where you watch the boundary do its job on real traffic — the decisions it made, the receipts it produced, and the posture it's in. It's the evidence that lets you keep an agent on a consequential action and still answer "what happened, and why?" — the on-screen view of the same records a [proof pack](/docs/receipts-and-proof) exports. ## What this does For one Protected Control over a time window, Govern shows, summary-first: - **Control identity** — the action, entity type, environment, and current mode. - **Posture** — whether the control is in `shadow` or `enforce`, and when that last changed. - **Decisions by outcome** — counts of `allowed`, `blocked`, `approval_required`, `invalid`, with a drill-down into individual decisions. - **Receipts** — the signed records, with their signature and verifiability status. - **Completeness** — whether the window shown is complete or truncated, so a partial view never reads as the whole story. It reads top-down: a one-line summary of the control's state, then detail on demand. ## Shadow vs enforce A control runs in one of two postures, and Govern is honest about which: - **`shadow`** (observe-only) — KIFF evaluates every action and records what it *would* have decided, but does not withhold anything; your runtime proceeds as usual. This is the on-ramp: watch real decisions on real traffic before you turn the gate on. - **`enforce`** — KIFF's decision is acted on: `allowed` runs, everything else is withheld by your runtime. Shadow always reads as *would-have-decided*, never as production enforcement. The point of shadow is to build trust before you flip to enforce, and the moment you flipped is itself recorded. ## Honest by design Govern is built to state what it can and cannot show. Every section renders a distinct **recorded**, **empty**, or **not-yet-recorded** state rather than implying completeness: - A control with no traffic yet shows an honest empty state, not a staged success. - A section whose data isn't recorded yet says so plainly. - Verifiability is scope-aware — a sampled result never presents itself as a complete-window guarantee. This matters because most dashboards imply more than they know. Govern's value depends on the opposite: if it says something happened, it happened, and if it can't show something, it says so. ## What it does not show - It does not show that an action executed downstream — only what KIFF decided. KIFF is the decision boundary, not the runner. - It does not present demo or synthetic data as real customer evidence. - It does not claim reviewer attribution the product doesn't record yet (see [What's real today](/docs/whats-real-today)). ![The Govern evidence timeline: the summary line and decisions-by-outcome for a control with real windowed traffic.](/static/img/docs/control-room-timeline.png) *The Govern view for a control with real traffic — the summary line and decisions by outcome, from a clean demo tenant.* ![Govern for a freshly installed control with no traffic yet, showing the honest waiting/not-yet-recorded state.](/static/img/docs/control-room-empty.png) *The honest empty state — a control installed and waiting, before any real proposal has run. KIFF never stages evidence that does not exist.* ## Next - What the receipts here prove → [Receipts & proof packs](/docs/receipts-and-proof). - The full honesty line → [What's real today](/docs/whats-real-today). ## What's real today vs planned Source: https://kiff.dev/docs/whats-real-today # What's real today vs planned These docs describe what KIFF does, not what it might do. This page is the explicit line between the two, so you never build on a claim the product can't back yet. If something isn't here as shipped, treat it as not shipped. ## Real today - **The decision boundary.** KIFF evaluates a proposed action against an entity's state and your policy and returns `allowed`, `blocked`, or `approval_required` — before the action runs. A malformed or misconfigured request is `invalid` (a client error to fix, not a governance decision, and not a governed operation). See [Decisions](/docs/decisions). - **Read an entity's lifecycle.** The recorded proposals, approvals, and executions for an entity can be read back as one chronological, tenant-scoped, read-only view. See [Decisions](/docs/decisions). - **Observe mode, zero config.** Attach the guard to an agent you already run, with no account and no config, and get a real audit trail plus a derived starter policy. See the [quickstart](/docs/quickstart). - **Enforce mode through the guard.** Switch the guard to enforce, point it at a tenant, and KIFF's decision is acted on before each tool runs, failing closed on error. See [How the guard works](/docs/concepts). - **Shipped framework adapters.** Several adapters are verified against the real framework and pass the conformance suite; others are clearly labelled **preview**. The sidebar badges tell you which is which. - **Signed, tamper-evident receipts.** Every decision records one signed receipt of what was authorized, before execution. See [Receipts & proof packs](/docs/receipts-and-proof). - **Govern + proof packs.** A per-control evidence view on your real traffic, exportable as a proof pack from the same evidence object, with honest empty / not-yet-recorded states. See [Govern](/docs/govern). - **Shadow → enforce posture**, with the moment of the flip recorded. ## Not shipped — do not assume - **Production anchoring.** Receipts are signed and tamper-evident, and can be anchored to an external trail, but the production form of public anchoring is not shipped. The honest claim is *signed, tamper-evident, verifiable* — nothing stronger. - **Reviewer attribution as a guarantee.** The full linkage between an approval and the specific control decision is limited; where it isn't recorded, the evidence says "not yet recorded" rather than inventing it. Don't rely on reviewer-attribution claims. - **Compliance certification.** KIFF produces evidence that's useful in a compliance conversation, but it is not a certification and does not assert legal sufficiency for any regime. - **KIFF as the executor.** KIFF never runs your action, moves money, or holds your credentials. Your runtime is always the executor. ## How we keep these docs honest Two rules govern this page and the rest of the docs: 1. **Copy never outruns the artifact.** A capability is described as real only if it's shipped and verifiable. If it isn't, it lives in the "not shipped" list above until it is. 2. **No demo data presented as real.** Screenshots and examples in these docs are either clearly labelled as examples or drawn from a clean, safe tenant — never synthetic data dressed up as production evidence. If you find a claim in these docs that contradicts this page, this page wins, and it's a bug in the other page. Tell us. ## Next - Start using it → [Quickstart](/docs/quickstart). - Understand the model → [What KIFF is](/docs/what-kiff-is). ## Agno Source: https://kiff.dev/docs/agno # Agno > **New to KIFF?** Start with the [quickstart](/docs/quickstart) or [connect an existing agent](/docs/connect-an-agent) — this page is the framework-specific attach for that same journey. The model behind it: [how the guard works](/docs/concepts). **Status: verified.** Agno was the first adapter, and its seam was exercised live against the cloud decide endpoint ([kiff-cloud#242](https://github.com/kiff/kiff-cloud/issues/242)). **Shape: middleware.** Agno's `tool_hooks` hand the guard a continuation that runs the tool, so the guard runs it via `Guard.evaluate(run=...)`. See [how the guard works](/docs/concepts) for what that means. ## Install ```bash pip install "git+https://github.com/kiff/kiff-guard.git#subdirectory=packages/python/kiff-guard" # plus Agno itself, the framework you're guarding: pip install agno ``` ## Observe — audit with zero config ```python from agno.agent import Agent 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)], ) ``` Run the agent as usual, then read the trail: ```python for r in guard.receipts: print(r.state, r.tool, r.outcome) ``` `agno_hook` is one line of real logic — it matches Agno's hook signature and calls the guard: ```python def hook(function_name, func, args): return guard.evaluate(function_name, args, run=lambda: func(**args)) ``` ## Activate Derive a starter domain from what you observed, review it, and activate it in the [dashboard](/dashboard): ```python from kiff_guard import export_yaml print(export_yaml("my-domain", guard.catalog)) ``` ## Enforce — govern at runtime Once you have a tenant and an active domain, point the guard at KIFF and switch to `enforce`. Nothing about the agent wiring changes — only the guard: ```python from kiff_guard import Guard, HTTPClient, ToolMap from kiff_guard.adapters.agno import agno_hook client = HTTPClient( api_key="kiff_live_...", # mint in the dashboard tool_map=ToolMap().bind( "refund_order", action="REFUND_ORDER", entity_type="Order", entity_arg="order_id"), ) guard = Guard(client=client, tenant="", agent="support", mode="enforce") agent = Agent(model=..., tools=[refund_order], tool_hooks=[agno_hook(guard)]) ``` In enforce mode a withheld decision raises `kiff_guard.Hold`; the tool never runs. The host app maps `Hold` onto Agno's human-in-the-loop / approval flow if it wants the agent to see why. ## The seam (verified) Agno invokes each tool hook as middleware: `hook(function_name, func, args)`. The hook calls `func(**args)` to let the tool proceed, or skips it to block. That maps directly onto the guard's `run` continuation. ## Links - Adapter code: [`kiff_guard/adapters/agno.py`](https://github.com/kiff/kiff-guard/blob/main/packages/python/kiff-guard/src/kiff_guard/adapters/agno.py) - Seam research: [agno.md](https://github.com/kiff/kiff-cloud/blob/main/docs/integration/frameworks/agno.md) - Agno docs: [docs.agno.com](https://docs.agno.com/) (Hooks → Tool Hooks) ## LangGraph / LangChain Source: https://kiff.dev/docs/langgraph # LangGraph / LangChain > **New to KIFF?** Start with the [quickstart](/docs/quickstart) or [connect an existing agent](/docs/connect-an-agent) — this page is the framework-specific attach for that same journey. The model behind it: [how the guard works](/docs/concepts). **Status: verified.** The adapter ships in [`kiff-guard`](https://github.com/kiff/kiff-guard) and passes the conformance suite. The seam was checked against the current LangChain middleware API, including the built-in `ShellAllowListMiddleware` that uses the same block pattern. **Shape: middleware.** The seam is `wrap_tool_call` — the guard runs the tool via the `handler` continuation, so `Guard.evaluate(run=...)` fits directly. (Not `interrupt()`; see the note below.) ## Install ```bash pip install "git+https://github.com/kiff/kiff-guard.git#subdirectory=packages/python/kiff-guard" pip install langgraph langchain ``` ## Observe — audit with zero config ```python from langchain.agents import create_agent from langchain.agents.middleware import wrap_tool_call from kiff_guard import Guard from kiff_guard.adapters.langgraph import kiff_wrap_tool_call guard = Guard(mode="observe") # zero-config audit kiff_mw = wrap_tool_call(kiff_wrap_tool_call(guard)) agent = create_agent(model="...", tools=[...], middleware=[kiff_mw]) ``` Run the agent, then read the trail from `guard.receipts`. ## Activate ```python from kiff_guard import export_yaml print(export_yaml("my-domain", guard.catalog)) ``` Review the draft and activate it in the [dashboard](/dashboard). ## Enforce — govern at runtime ```python from kiff_guard import Guard, HTTPClient, ToolMap client = HTTPClient(api_key="kiff_live_...", tool_map=ToolMap().bind(...)) guard = Guard(client=client, tenant="", agent="support", mode="enforce") kiff_mw = wrap_tool_call(kiff_wrap_tool_call(guard)) agent = create_agent(model="...", tools=[...], middleware=[kiff_mw]) ``` On a withheld decision the middleware returns a `ToolMessage` (`status="error"`) carrying the reason **without** calling the handler — the tool never runs, and the model sees the message as the tool result. This is exactly the short-circuit LangChain's own `ShellAllowListMiddleware` uses to reject disallowed shell commands. ## The seam (verified) `wrap_tool_call(request, handler) -> ToolMessage | Command`. `request.tool_call["name"] / ["args"] / ["id"]` carry the call. Calling `handler(request)` runs the tool; returning a `ToolMessage` without calling it blocks. `langchain` is imported lazily, so `import kiff_guard` never requires it. ## Why not `interrupt()` `interrupt()` pauses the graph and persists state for a **human** to resume — the right tool for a human approval pause, but heavyweight and asynchronous, not a synchronous "is this tool allowed right now" gate. KIFF's gate is a machine decision that returns in milliseconds, so it belongs in `wrap_tool_call`. When a withheld decision *is* an `approval_required`, the host app can bridge it into an `interrupt()` on top of the gate. ## Links - Adapter code: [`kiff_guard/adapters/langgraph.py`](https://github.com/kiff/kiff-guard/blob/main/packages/python/kiff-guard/src/kiff_guard/adapters/langgraph.py) - Seam research: [langgraph.md](https://github.com/kiff/kiff-cloud/blob/main/docs/integration/frameworks/langgraph.md) - LangChain middleware: [docs.langchain.com](https://docs.langchain.com/oss/python/langchain/middleware/custom) ## Hermes Agent (Nous) Source: https://kiff.dev/docs/hermes # Hermes Agent (Nous Research) > **New to KIFF?** Start with the [quickstart](/docs/quickstart) or [connect an existing agent](/docs/connect-an-agent) — this page is the framework-specific attach for that same journey. The model behind it: [how the guard works](/docs/concepts). **Status: verified.** The adapter ships in [`kiff-guard`](https://github.com/kiff/kiff-guard) and passes the conformance suite. The seam was source-verified against the `NousResearch/hermes-agent` source on `main`, not just the docs — and a doc-vs-source discrepancy was resolved in favor of the source (see below). **Shape: inverted-control.** Hermes runs the tool after the hook returns; the hook only votes. So the adapter uses `Guard.observe()` / `Guard.decide_only()`, not a `run` callback. See [how the guard works](/docs/concepts). Hermes is a CLI-first agent that takes real, irreversible actions (terminal, code execution, file writes, messaging gateways) — exactly the "actor takes consequential action" profile KIFF governs. ## Install The guard ships to Hermes as a **plugin** — a directory with `plugin.yaml` + `__init__.py` exposing `register(ctx)`. No fork, no monkeypatch. ```bash pip install "git+https://github.com/kiff/kiff-guard.git#subdirectory=packages/python/kiff-guard" ``` Then create the plugin at `~/.hermes/plugins/kiff-guard/`. ## Observe — audit with zero config `__init__.py`: ```python from kiff_guard import Guard from kiff_guard.adapters.hermes import register_kiff_guard _GUARD = Guard(mode="observe") # zero-config audit; no KIFF account def register(ctx): register_kiff_guard(ctx, _GUARD) ``` The hook fires before every tool (terminal, browser, code-exec, file writes, MCP) in both the CLI and the gateway, records a receipt, and never blocks. ## Activate ```python from kiff_guard import export_yaml print(export_yaml("my-domain", _GUARD.catalog)) ``` Review and activate in the [dashboard](/dashboard). ## Enforce — govern at runtime Build the guard with a client and `mode="enforce"`: ```python from kiff_guard import Guard, HTTPClient, ToolMap client = HTTPClient(api_key="kiff_live_...", tool_map=ToolMap().bind(...)) _GUARD = Guard(client=client, tenant="", agent="hermes", mode="enforce") ``` On a withheld decision the adapter returns Hermes' `{"action": "block", "message": ...}` directive — Hermes short-circuits the tool and hands `message` to the model as the tool's error result. The tool never runs. Enforce **fails closed** on a guard error by default; pass `fail_closed=False` to override. ## The seam (verified) The pre-execution seam is the **`pre_tool_call`** plugin hook. In source (`model_tools.py` → `handle_function_call`), Hermes calls `get_pre_tool_call_block_message(...)` *before* dispatch; a non-None block result short-circuits the tool. The hook fires exactly once per tool execution and the first block wins. > **Doc-vs-source discrepancy (resolved).** Hermes' plugin-build guide > lists `pre_tool_call`'s return as "ignored" (observer-only), but the > hooks reference says it can block — and the **source confirms the > hooks reference is correct**. The block directive is supported and > stable. KIFF complements Hermes' own `DANGEROUS_PATTERNS` local check > by adding the tenant-wide policy + audit. ## Links - Adapter code: [`kiff_guard/adapters/hermes.py`](https://github.com/kiff/kiff-guard/blob/main/packages/python/kiff-guard/src/kiff_guard/adapters/hermes.py) - Seam research (full, source-verified): [hermes.md](https://github.com/kiff/kiff-cloud/blob/main/docs/integration/frameworks/hermes.md) - Hermes docs: [hermes-agent.nousresearch.com](https://hermes-agent.nousresearch.com/docs) ## OpenAI Agents SDK Source: https://kiff.dev/docs/openai-agents # OpenAI Agents SDK > **New to KIFF?** Start with the [quickstart](/docs/quickstart) or [connect an existing agent](/docs/connect-an-agent) — this page is the framework-specific attach for that same journey. The model behind it: [how the guard works](/docs/concepts). **Status: verified.** The adapter ships in [`kiff-guard`](https://github.com/kiff/kiff-guard), passes the conformance suite, and was checked against the real `openai-agents` v0.17.4 SDK plus a live model call — confirming the guardrail is accepted and `reject_content` genuinely skips the tool. **Shape: inverted-control.** The SDK runs the tool after the guardrail returns a verdict, so the adapter uses `Guard.observe()` / `Guard.decide_only()`, not a `run` callback. See [how the guard works](/docs/concepts). ## Install ```bash pip install "git+https://github.com/kiff/kiff-guard.git#subdirectory=packages/python/kiff-guard" pip install openai-agents # the framework you're guarding ``` (The `openai` extra maps to the `openai-agents` package.) ## Observe — audit with zero config Attach the guard as a **tool input guardrail** on a `function_tool`: ```python from agents import Agent, function_tool from kiff_guard import Guard from kiff_guard.adapters.openai_agents import kiff_tool_input_guardrail guard = Guard(mode="observe") # zero-config audit; no KIFF account kiff_gd = kiff_tool_input_guardrail(guard) @function_tool(tool_input_guardrails=[kiff_gd]) def refund_order(order_id: str, amount_cents: int) -> str: ... agent = Agent(name="support", tools=[refund_order]) ``` In observe mode the guardrail records + learns and always allows the tool to run. Read the trail from `guard.receipts`. ## Activate ```python from kiff_guard import export_yaml print(export_yaml("my-domain", guard.catalog)) ``` Review and activate in the [dashboard](/dashboard). ## Enforce — govern at runtime Build the guard with a client and `mode="enforce"`; the `@function_tool` wiring is unchanged: ```python from kiff_guard import Guard, HTTPClient, ToolMap client = HTTPClient(api_key="kiff_live_...", tool_map=ToolMap().bind(...)) guard = Guard(client=client, tenant="", agent="support", mode="enforce") kiff_gd = kiff_tool_input_guardrail(guard) ``` On a withheld decision the guardrail returns `ToolGuardrailFunctionOutput.reject_content(reason)` — the SDK skips the tool and hands the reason to the model instead of the tool result. Enforce **fails closed** on a guard error by default. ## The seam (verified, v0.17.4) `@tool_input_guardrail` runs before the tool executes. The callback gets `data.context.tool_name`, `.tool_arguments` (raw JSON **string** — the adapter parses it to a dict), and `.tool_call_id`. It returns `allow()` to run or `reject_content(message)` to skip. > **Not `needs_approval`.** `needs_approval` is the heavyweight *human* > pause: it pauses the run, surfaces `interruptions`, and resumes via > `RunState`. KIFF's gate is a machine decision, so it belongs in the > synchronous tool input guardrail. A withheld `approval_required` can > still be bridged into the SDK's native `needs_approval` flow on top of > the gate. > **Boundary:** tool input guardrails apply to `function_tool`s only. > Hosted/built-in tools (web search, code interpreter, computer, shell, > apply-patch) and handoffs bypass this pipeline; governing those needs > a different seam. ## Links - Adapter code: [`kiff_guard/adapters/openai_agents.py`](https://github.com/kiff/kiff-guard/blob/main/packages/python/kiff-guard/src/kiff_guard/adapters/openai_agents.py) - Live check: [`live_openai_check.py`](https://github.com/kiff/kiff-guard/blob/main/packages/python/kiff-guard/live_openai_check.py) - Seam research: [openai-agents.md](https://github.com/kiff/kiff-cloud/blob/main/docs/integration/frameworks/openai-agents.md) - SDK guardrails: [openai.github.io/openai-agents-python](https://openai.github.io/openai-agents-python/guardrails/) ## Google ADK Source: https://kiff.dev/docs/google-adk # Google ADK > **New to KIFF?** Start with the [quickstart](/docs/quickstart) or [connect an existing agent](/docs/connect-an-agent) — this page is the framework-specific attach for that same journey. The model behind it: [how the guard works](/docs/concepts). **Status: verified.** The adapter ships in [`kiff-guard`](https://github.com/kiff/kiff-guard) and passes the conformance suite. The seam was verified against the real `google-adk` package. **Shape: vote (inverted-control).** ADK runs the tool itself; its `before_tool_callback` only votes — return `None` to allow, return a `dict` to block (the dict becomes the tool result). So the guard uses `observe()` / `decide_only()`, not a `run` callback. See [how the guard works](/docs/concepts). ## Install ```bash pip install "git+https://github.com/kiff/kiff-guard.git#subdirectory=packages/python/kiff-guard" # plus Google ADK, the framework you're guarding: pip install google-adk ``` ## Observe — audit with zero config ```python from google.adk.agents import LlmAgent from kiff_guard import Guard from kiff_guard.adapters.google_adk import kiff_before_tool_callback guard = Guard(mode="observe") # no client, no tenant needed agent = LlmAgent( model=..., tools=[refund_order, send_email], before_tool_callback=kiff_before_tool_callback(guard), ) ``` Run the agent as usual, then read the trail: ```python for r in guard.receipts: print(r.state, r.tool, r.outcome) ``` ## Activate Derive a starter domain from what you observed, review it, and activate it in the [dashboard](/dashboard): ```python from kiff_guard import export_yaml print(export_yaml("my-domain", guard.catalog)) ``` ## Enforce — govern at runtime Once you have a tenant and an active domain, point the guard at KIFF and switch to `enforce`. Only the guard changes: ```python from kiff_guard import Guard, HTTPClient, ToolMap from kiff_guard.adapters.google_adk import kiff_before_tool_callback client = HTTPClient( api_key="kiff_live_...", # mint in the dashboard tool_map=ToolMap().bind( "refund_order", action="REFUND_ORDER", entity_type="Order", entity_arg="order_id"), ) guard = Guard(client=client, tenant="", agent="support", mode="enforce") agent = LlmAgent(model=..., tools=[refund_order], before_tool_callback=kiff_before_tool_callback(guard)) ``` In enforce mode a withheld decision returns a `dict` to ADK, so the tool is skipped and the reason is handed back to the model as the result. An allowed decision returns `None` and the tool runs. On a transport error the guard fails closed (blocks) by default. ## The seam (verified) ADK calls `before_tool_callback(tool, args, tool_context)` before a tool's `run_async`. Returning `None` runs the tool; returning a `dict` skips it and uses that dict as the result — the block path. ## Links - Adapter code: [`kiff_guard/adapters/google_adk.py`](https://github.com/kiff/kiff-guard/blob/main/packages/python/kiff-guard/src/kiff_guard/adapters/google_adk.py) - Google ADK docs: [google.github.io/adk-docs](https://google.github.io/adk-docs/) (Callbacks → Before Tool Callback) ## Pydantic AI Source: https://kiff.dev/docs/pydantic-ai # Pydantic AI > **New to KIFF?** Start with the [quickstart](/docs/quickstart) or [connect an existing agent](/docs/connect-an-agent) — this page is the framework-specific attach for that same journey. The model behind it: [how the guard works](/docs/concepts). **Status: verified.** The adapter ships in [`kiff-guard`](https://github.com/kiff/kiff-guard) and passes the conformance suite. The seam was verified against the real `pydantic-ai` package. **Shape: vote (inverted-control).** Pydantic AI runs the tool itself; the `before_tool_execute` hook only votes — return `args` to allow, raise `SkipToolExecution(result)` to block. So the guard uses `observe()` / `decide_only()`, not a `run` callback. See [how the guard works](/docs/concepts). ## Install ```bash pip install "git+https://github.com/kiff/kiff-guard.git#subdirectory=packages/python/kiff-guard" # plus Pydantic AI, the framework you're guarding: pip install pydantic-ai ``` ## Observe — audit with zero config ```python from pydantic_ai import Agent from pydantic_ai.capabilities import Hooks from kiff_guard import Guard from kiff_guard.adapters.pydantic_ai import kiff_before_tool_execute guard = Guard(mode="observe") # no client, no tenant needed agent = Agent( "...", tools=[refund_order, send_email], capabilities=[Hooks(before_tool_execute=kiff_before_tool_execute(guard))], ) ``` Run the agent as usual, then read the trail: ```python for r in guard.receipts: print(r.state, r.tool, r.outcome) ``` ## Activate Derive a starter domain from what you observed, review it, and activate it in the [dashboard](/dashboard): ```python from kiff_guard import export_yaml print(export_yaml("my-domain", guard.catalog)) ``` ## Enforce — govern at runtime Once you have a tenant and an active domain, point the guard at KIFF and switch to `enforce`. Only the guard changes: ```python from kiff_guard import Guard, HTTPClient, ToolMap from pydantic_ai import Agent from pydantic_ai.capabilities import Hooks from kiff_guard.adapters.pydantic_ai import kiff_before_tool_execute client = HTTPClient( api_key="kiff_live_...", # mint in the dashboard tool_map=ToolMap().bind( "refund_order", action="REFUND_ORDER", entity_type="Order", entity_arg="order_id"), ) guard = Guard(client=client, tenant="", agent="support", mode="enforce") agent = Agent("...", tools=[refund_order], capabilities=[Hooks(before_tool_execute=kiff_before_tool_execute(guard))]) ``` In enforce mode a withheld decision raises `SkipToolExecution`, so the tool never runs and the reason is fed back to the model. An allowed decision returns the args and the tool runs. On a transport error the guard fails closed (skips) by default. ## The seam (verified) Pydantic AI's `before_tool_execute` hook (on a `Hooks` capability) fires before the tool runs, with the validated `args` and a `call` carrying `tool_name`. Returning `args` runs the tool; raising `SkipToolExecution(result)` skips it — the block path. ## Links - Adapter code: [`kiff_guard/adapters/pydantic_ai.py`](https://github.com/kiff/kiff-guard/blob/main/packages/python/kiff-guard/src/kiff_guard/adapters/pydantic_ai.py) - Pydantic AI docs: [ai.pydantic.dev/hooks](https://ai.pydantic.dev/hooks/) (tool execution hooks) ## Strands Agents Source: https://kiff.dev/docs/strands # Strands Agents > **New to KIFF?** Start with the [quickstart](/docs/quickstart) or [connect an existing agent](/docs/connect-an-agent) — this page is the framework-specific attach for that same journey. The model behind it: [how the guard works](/docs/concepts). **Status: verified.** The adapter ships in [`kiff-guard`](https://github.com/kiff/kiff-guard) and passes the conformance suite. The seam was verified against the real `strands-agents` package. **Shape: vote (inverted-control).** Strands runs the tool itself; its `BeforeToolCallEvent` hook only votes — set `event.cancel_tool` to a message to block, leave it unset to allow. So the guard uses `observe()` / `decide_only()`, not a `run` callback. See [how the guard works](/docs/concepts). ## Install ```bash pip install "git+https://github.com/kiff/kiff-guard.git#subdirectory=packages/python/kiff-guard" # plus Strands, the framework you're guarding: pip install strands-agents ``` ## Observe — audit with zero config ```python from strands import Agent from kiff_guard import Guard from kiff_guard.adapters.strands import kiff_hook_provider guard = Guard(mode="observe") # no client, no tenant needed agent = Agent( model=..., tools=[refund_order, send_email], hooks=[kiff_hook_provider(guard)], ) ``` Run the agent as usual, then read the trail: ```python for r in guard.receipts: print(r.state, r.tool, r.outcome) ``` ## Activate Derive a starter domain from what you observed, review it, and activate it in the [dashboard](/dashboard): ```python from kiff_guard import export_yaml print(export_yaml("my-domain", guard.catalog)) ``` ## Enforce — govern at runtime Once you have a tenant and an active domain, point the guard at KIFF and switch to `enforce`. Only the guard changes: ```python from kiff_guard import Guard, HTTPClient, ToolMap from kiff_guard.adapters.strands import kiff_hook_provider client = HTTPClient( api_key="kiff_live_...", # mint in the dashboard tool_map=ToolMap().bind( "refund_order", action="REFUND_ORDER", entity_type="Order", entity_arg="order_id"), ) guard = Guard(client=client, tenant="", agent="support", mode="enforce") agent = Agent(model=..., tools=[refund_order], hooks=[kiff_hook_provider(guard)]) ``` In enforce mode a withheld decision sets `event.cancel_tool` to the reason; Strands cancels the call and surfaces the message as a tool result with an error status. An allowed decision leaves the event untouched and the tool runs. On a transport error the guard fails closed (cancels) by default. ## The seam (verified) Strands fires `BeforeToolCallEvent` before executing a tool; `event.tool_use` carries the tool name and input, and `event.cancel_tool: bool | str` blocks the call. The guard registers via a `HookProvider` attached with `Agent(hooks=[...])`. ## Links - Adapter code: [`kiff_guard/adapters/strands.py`](https://github.com/kiff/kiff-guard/blob/main/packages/python/kiff-guard/src/kiff_guard/adapters/strands.py) - Strands docs: [strandsagents.com](https://strandsagents.com/docs/) (Hooks → BeforeToolCallEvent) ## Haystack Agents Source: https://kiff.dev/docs/haystack # Haystack Agents > **New to KIFF?** Start with the [quickstart](/docs/quickstart) or [connect an existing agent](/docs/connect-an-agent) — this page is the framework-specific attach for that same journey. The model behind it: [how the guard works](/docs/concepts). **Status: verified.** The adapter ships in [`kiff-guard`](https://github.com/kiff/kiff-guard) and passes the conformance suite. The seam was verified against the real `haystack-ai` package. **Shape: vote (inverted-control).** Haystack runs the tool itself; a `ConfirmationStrategy` only votes — return a decision with `execute=True` to allow, `execute=False` to block. So the guard uses `observe()` / `decide_only()`, not a `run` callback. See [how the guard works](/docs/concepts). ## Install ```bash pip install "git+https://github.com/kiff/kiff-guard.git#subdirectory=packages/python/kiff-guard" # plus Haystack, the framework you're guarding: pip install haystack-ai ``` ## Observe — audit with zero config ```python from haystack.components.agents import Agent from kiff_guard import Guard from kiff_guard.adapters.haystack import kiff_confirmation_strategy guard = Guard(mode="observe") # no client, no tenant needed agent = Agent( chat_generator=..., tools=[refund_order, send_email], confirmation_strategies={ "refund_order": kiff_confirmation_strategy(guard), "send_email": kiff_confirmation_strategy(guard), }, ) ``` Run the agent as usual, then read the trail: ```python for r in guard.receipts: print(r.state, r.tool, r.outcome) ``` ## Activate Derive a starter domain from what you observed, review it, and activate it in the [dashboard](/dashboard): ```python from kiff_guard import export_yaml print(export_yaml("my-domain", guard.catalog)) ``` ## Enforce — govern at runtime Once you have a tenant and an active domain, point the guard at KIFF and switch to `enforce`. Only the guard changes: ```python from kiff_guard import Guard, HTTPClient, ToolMap from haystack.components.agents import Agent from kiff_guard.adapters.haystack import kiff_confirmation_strategy client = HTTPClient( api_key="kiff_live_...", # mint in the dashboard tool_map=ToolMap().bind( "refund_order", action="REFUND_ORDER", entity_type="Order", entity_arg="order_id"), ) guard = Guard(client=client, tenant="", agent="support", mode="enforce") agent = Agent(chat_generator=..., tools=[refund_order], confirmation_strategies={"refund_order": kiff_confirmation_strategy(guard)}) ``` In enforce mode a withheld decision returns `execute=False` with the reason as feedback, so the tool never runs. An allowed decision returns `execute=True` and the tool runs. On a transport error the guard fails closed (blocks) by default. ## The seam (verified) Haystack calls a `ConfirmationStrategy.run(*, tool_name, tool_description, tool_params, tool_call_id, ...)` before executing a tool; it returns a `ToolExecutionDecision(execute=bool, feedback=...)`. The guard's strategy is passed via `Agent(confirmation_strategies=...)`. ## Links - Adapter code: [`kiff_guard/adapters/haystack.py`](https://github.com/kiff/kiff-guard/blob/main/packages/python/kiff-guard/src/kiff_guard/adapters/haystack.py) - Haystack docs: [docs.haystack.deepset.ai](https://docs.haystack.deepset.ai/docs/human-in-the-loop) (Agent → human-in-the-loop) ## Microsoft Agent Framework Source: https://kiff.dev/docs/microsoft-agent-framework # Microsoft Agent Framework > **New to KIFF?** Start with the [quickstart](/docs/quickstart) or [connect an existing agent](/docs/connect-an-agent) — this page is the framework-specific attach for that same journey. The model behind it: [how the guard works](/docs/concepts). **Status: verified.** The adapter ships in [`kiff-guard`](https://github.com/kiff/kiff-guard) and passes the conformance suite. The seam was verified against the real `agent-framework-core` package. **Shape: middleware (async).** A `FunctionMiddleware` wraps the tool call: `await call_next()` runs the tool, or set `context.result` and skip `call_next` to block. The guard decides first and only runs the tool when allowed. See [how the guard works](/docs/concepts). ## Install ```bash pip install "git+https://github.com/kiff/kiff-guard.git#subdirectory=packages/python/kiff-guard" # plus Microsoft Agent Framework, the framework you're guarding: pip install agent-framework-core ``` ## Observe — audit with zero config ```python from agent_framework import Agent from kiff_guard import Guard from kiff_guard.adapters.microsoft_agent_framework import kiff_guard_middleware guard = Guard(mode="observe") # no client, no tenant needed agent = Agent( client=..., name="assistant", tools=[refund_order, send_email], middleware=[kiff_guard_middleware(guard)], ) ``` Run the agent as usual, then read the trail: ```python for r in guard.receipts: print(r.state, r.tool, r.outcome) ``` ## Activate Derive a starter domain from what you observed, review it, and activate it in the [dashboard](/dashboard): ```python from kiff_guard import export_yaml print(export_yaml("my-domain", guard.catalog)) ``` ## Enforce — govern at runtime Once you have a tenant and an active domain, point the guard at KIFF and switch to `enforce`. Only the guard changes: ```python from kiff_guard import Guard, HTTPClient, ToolMap from agent_framework import Agent from kiff_guard.adapters.microsoft_agent_framework import kiff_guard_middleware client = HTTPClient( api_key="kiff_live_...", # mint in the dashboard tool_map=ToolMap().bind( "refund_order", action="REFUND_ORDER", entity_type="Order", entity_arg="order_id"), ) guard = Guard(client=client, tenant="", agent="support", mode="enforce") agent = Agent(client=..., name="assistant", tools=[refund_order], middleware=[kiff_guard_middleware(guard)]) ``` In enforce mode a withheld decision sets `context.result` to the reason and skips `call_next`, so the tool never runs. An allowed decision awaits `call_next` and the tool runs. On a transport error the guard fails closed (blocks) by default. ## The seam (verified) A `FunctionMiddleware.process(self, context, call_next)` fires before a tool runs; `context.function.name` + `context.arguments` carry the call. Awaiting `call_next()` runs the tool; setting `context.result` and skipping `call_next` blocks it. Attached via `Agent(middleware=[...])`. ## Links - Adapter code: [`kiff_guard/adapters/microsoft_agent_framework.py`](https://github.com/kiff/kiff-guard/blob/main/packages/python/kiff-guard/src/kiff_guard/adapters/microsoft_agent_framework.py) - Microsoft Agent Framework docs: [learn.microsoft.com/agent-framework](https://learn.microsoft.com/en-us/agent-framework/) (Middleware → Function middleware) ## LlamaIndex Agents Source: https://kiff.dev/docs/llamaindex # LlamaIndex Agents > **New to KIFF?** Start with the [quickstart](/docs/quickstart) or [connect an existing agent](/docs/connect-an-agent) — this page is the framework-specific attach for that same journey. The model behind it: [how the guard works](/docs/concepts). **Status: verified.** The adapter ships in [`kiff-guard`](https://github.com/kiff/kiff-guard) (`kiff_guard.adapters.llama_index`) and was source-verified against llama-index-core and passes the conformance suite. **Shape: middleware.** LlamaIndex's `AgentWorkflow` runs each tool in its `call_tool` step. The guard sits *inside* that step — you subclass `AgentWorkflow` as `GuardedAgentWorkflow` and the guard runs the tool via its continuation, the same middleware shape as Agno. See [how the guard works](/docs/concepts) for what that means. ## Install ```bash pip install "git+https://github.com/kiff/kiff-guard.git#subdirectory=packages/python/kiff-guard" # plus LlamaIndex itself, the framework you're guarding: pip install "kiff-guard[llama-index]" ``` ## Observe — audit with zero config ```python from llama_index.core.agent.workflow import FunctionAgent from llama_index.llms.openai import OpenAI from kiff_guard import Guard from kiff_guard.adapters.llama_index import GuardedAgentWorkflow guard = Guard(mode="observe") # no client, no tenant needed workflow = GuardedAgentWorkflow( agents=[FunctionAgent(tools=[refund_order], llm=OpenAI(model="gpt-4o-mini"))], guard=guard, ) ``` Run the workflow as usual, then read the trail: ```python for r in guard.receipts: print(r.state, r.tool, r.outcome) ``` ## Activate Derive a starter domain from what you observed, review it, and activate it in the [dashboard](/dashboard): ```python from kiff_guard import export_yaml print(export_yaml("my-domain", guard.catalog)) ``` ## Enforce — govern at runtime Once you have a tenant and an active domain, point the guard at KIFF and switch to `enforce`. Nothing about the workflow wiring changes — only the guard: ```python from kiff_guard import Guard, HTTPClient, ToolMap from kiff_guard.adapters.llama_index import GuardedAgentWorkflow client = HTTPClient( api_key="kiff_live_...", # mint in the dashboard tool_map=ToolMap().bind( "refund_order", action="REFUND_ORDER", entity_type="Order", entity_arg="order_id"), ) guard = Guard(client=client, tenant="", agent="support", mode="enforce") workflow = GuardedAgentWorkflow( agents=[FunctionAgent(tools=[refund_order], llm=OpenAI(model="gpt-4o-mini"))], guard=guard, ) ``` In enforce mode a withheld decision raises `kiff_guard.Hold` from inside the `call_tool` step; the tool body never runs and the exception propagates up through the workflow handler (the fail-safe path — an unknown outcome withholds rather than proceeds). ## The seam (verified) LlamaIndex's `AgentWorkflow` exposes a `call_tool` workflow step that resolves and runs each tool. `GuardedAgentWorkflow` overrides that step and runs the tool body through the guard's continuation — block by raising `Hold`, proceed by calling through. The adapter is fully async to match the workflow, and lazily imports LlamaIndex so `import kiff_guard` works without `llama-index-core` installed. ## Links - Adapter code: [`kiff_guard/adapters/llama_index.py`](https://github.com/kiff/kiff-guard/blob/main/packages/python/kiff-guard/src/kiff_guard/adapters/llama_index.py) - LlamaIndex docs: [developers.llamaindex.ai](https://developers.llamaindex.ai/python/framework/use_cases/agents/) ## OpenClaw Source: https://kiff.dev/docs/openclaw # OpenClaw > **New to KIFF?** Start with the [quickstart](/docs/quickstart) or [connect an existing agent](/docs/connect-an-agent) — this page is the framework-specific attach for that same journey. The model behind it: [how the guard works](/docs/concepts). **Status: verified.** The adapter ships in the JS guard SDK ([`@kiff/kiff-guard`](https://github.com/kiff/kiff-guard), `packages/js/src/adapters/openclaw.ts`) and passes the conformance suite ported from the Python guard. It was verified against the OpenClaw plugin SDK's `before_tool_call` hook. This is KIFF's first TypeScript adapter — the Python `kiff_guard` package can't host it, so it lives in the JS package instead. **Shape: vote / approval-native.** The `before_tool_call` hook runs the tool itself; the guard only votes via its return value — one governed receipt per call, same as the Python vote adapters. OpenClaw is the first framework where KIFF's middle outcome, `approval_required`, maps onto a **native** human-in-the-loop primitive (OpenClaw pauses the run and routes a real `/approve` flow) rather than collapsing to a block. ## Why OpenClaw matters OpenClaw isn't a quick fifth adapter — it widens the whole picture: 1. **It forces the TypeScript guard SDK.** Every shipped adapter is Python; OpenClaw is Node/TS. A TS port unlocks the entire JS agent ecosystem (LangGraph.js, Vercel AI SDK, Mastra) — the vibecode crowd KIFF Cloud targets first. 2. **First native `approval_required`.** OpenClaw has a real approval primitive (title, severity, timeout, resolution) plus an `/approve` UX. This is the first integration where "clearance held for a human" renders as the experience the whitepaper describes, not just a refusal. 3. **Control-plane to control-plane.** OpenClaw is itself a Gateway with channels, approvals, and audit. KIFF governing OpenClaw is two control planes composing — KIFF as the shared clearance authority above OpenClaw's runtime. ## The seam Register the guard on your OpenClaw plugin's `api` via the shipped `registerKiffGuard` helper. It wires KIFF's outcomes onto the `before_tool_call` return contract for you: ```ts import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry"; import { Guard } from "@kiff/kiff-guard"; import { registerKiffGuard } from "@kiff/kiff-guard/adapters/openclaw"; const guard = new Guard({ mode: "observe" }); // switch to enforce when ready export default definePluginEntry({ id: "kiff-guard", name: "KIFF Guard", register(api) { registerKiffGuard(api, guard); // before_tool_call, priority 50 }, }); ``` Under the hood the adapter votes on each call: `allowed` proceeds and records that the tool ran; `approval_required` returns a `requireApproval` directive (title, severity, a `timeoutMs` that fails closed) so OpenClaw routes a real human approval; anything else returns `{ block: true }`. Enforce mode **fails closed** — if the guard can't reach KIFF, the tool is blocked. One governed receipt is recorded per call either way. ## Status and tracking Shipped. OpenClaw was the forcing function for the TypeScript guard SDK ([RFC 015](https://github.com/kiff/kiff-cloud/blob/main/docs/design/015-open-core-and-guard-sdk-repo.md)), which now lives in `packages/js` of the public guard repo. The adapter and its conformance test ship there ([`adapters/openclaw.ts`](https://github.com/kiff/kiff-guard/blob/main/packages/js/src/adapters/openclaw.ts)), which is what moved this page from preview to verified. ## Links - Seam research (full): [openclaw.md](https://github.com/kiff/kiff-cloud/blob/main/docs/integration/frameworks/openclaw.md) - OpenClaw plugin hooks: [docs.openclaw.ai](https://docs.openclaw.ai/plugins/hooks) --- # Whitepaper Source: https://kiff.dev/whitepaper # KIFF ## A coordination protocol for governed action **Working notes, May 2026** **Gabriel Sarmiento** Released under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). Code referenced is part of the open-source MIT framework at [github.com/kiff/kiff](https://github.com/kiff/kiff). --- ## Abstract Operational software increasingly involves multiple actors — humans, services, integrations, AI agents — writing to the same state. The default shape of this software is brittle: each actor mutates state through its own path, governance is enforced by convention, and the audit trail is whatever log the engineer remembered to add. KIFF is a small Go framework that puts a runtime between actors and shared state. Actors propose actions. The runtime validates state, parameters, permissions, and approvals before any executor runs. Every step — proposal, validation, approval, execution, failure — is appended to an immutable audit trail with trace correlation. State can be replayed from events. This document describes the protocol, the trust boundary it enforces, and the artifacts that prove the design is real: a working v0.1 framework, two end-to-end demos with real LLM proposals, a Postgres backend, a shared conformance test suite, and a CLI that inspects any KIFF server. The argument is small. Operational governance belongs in the runtime, not in the prompt or in convention. The runtime is small enough to read in a weekend. --- ## Contents 1. [The problem](#1-the-problem) 2. [The coordination loop](#2-the-coordination-loop) 3. [The trust boundary](#3-the-trust-boundary) 4. [Mechanics, not semantics](#4-mechanics-not-semantics) 5. [Evidence: what we built](#5-evidence-what-we-built) 6. [Honest limits](#6-honest-limits) 7. [Where this goes](#7-where-this-goes) 8. [Appendix A: the action contract](#appendix-a-the-action-contract) 9. [Appendix B: what KIFF is not](#appendix-b-what-kiff-is-not) --- ## 1. The problem Coordination failures around shared state look the same across very different industries. The vocabulary changes; the operational shape does not. Five examples, in five sectors, with the same underlying story: **E-commerce.** A support agent (human or AI) issues a $999 refund on an order whose payment never cleared. Nothing in the path between the agent and the payment processor checked the order's state. The refund executes. The customer is delighted, then confused, then files a second dispute. **Insurance.** A claims-handling service auto-approves a $50,000 payout because the claim text matched a low-friction template. The state of the claim — under fraud review by a different team — was visible in another system but not consulted. Funds move; the fraud team finds out the next morning. **Healthcare.** A clinical workflow tool ingests a lab result and writes an order that conflicts with an active prescription. There is no shared state both modules read from before writing. Two clinicians have to reconcile what happened by reading two audit logs that don't share a trace ID. **Fintech.** An automated treasury-management service moves funds between two corporate accounts to optimize float. The transfer exceeds the threshold that should have triggered dual control, but dual control was implemented in a spreadsheet by the operations team, not in the service. The transfer settles; the CFO gets a flagged email three hours later. **Internal DevOps.** An on-call engineer's AI assistant restarts a production service to clear what looked like a memory leak. The deploy freeze that was active for the next two hours was a calendar event, not a runtime check. The restart triggers a cascade. Twelve minutes of downtime. These are not AI failures. The AI assistant in the last example is incidental; the human engineer makes the same mistake. The common pattern is that *the actor and the executor were the same component*, with no runtime between them that could refuse the action. Every team eventually builds something to address this: a state-machine table, a webhook that emits to an audit log, a Slack approval bot, a review queue, a "do not run during freeze" check. The components are real and they work. But they are built once per company, in different languages, with different ergonomics, and the cost of wiring them together is large enough that most teams build the minimum viable version and stop. The result is what the old vendor-built systems looked like: governance that lives in convention, not in code; audit trails that explain part of what happened; replay capability that exists in theory because the events were stored, but no software can actually reconstruct the entity. KIFF is the same components, written once, in idiomatic Go, with the contracts visible to every actor. --- ## 2. The coordination loop The framework has six primitives. They are not invented; they are what operational systems already have, named consistently and connected by a small runtime. ``` event → state → decision → action → approval → audit ``` **Events** are normalized records of what happened. They are append-only, timestamped, attributed to a source. They carry domain-specific payloads inside a stable structural envelope. **State** is the maintained, shared, auditable condition of an entity. It is updated by events through deterministic transitions. State is the single source of truth all actors read from before deciding anything. **Decisions** are auditable records of intent. An agent that wants to issue a refund records a decision with its reasoning, its evidence, its confidence. The decision exists whether or not the action ever runs. This is what gives the system the answer to "why did you try this?" — not just "what did you do?". **Actions** are explicit contracts, not free-form tool calls. A contract declares the action's name, the states in which it is allowed, the parameters it requires, the permissions it requires, its risk level, and whether it requires human approval. The full type is reproduced in [Appendix A](#appendix-a-the-action-contract). **Approvals** are first-class records, not booleans. An approval has an identity, an entity, an action, a requester, a reviewer, a status, a reason, and timestamps. The runtime, and only the runtime, can mark an action context as approved — and only after looking up a granted approval record from the store. **Audit** is part of the protocol, not a logging concern. Every event ingested, every state transition, every decision recorded, every action validated, every approval requested or reviewed, every execution result, and every failure produces an audit record. Records carry `trace_id`, `correlation_id`, and `causation_id`. One filter call returns the full chain that started from any inbound request. The loop runs left to right. An event arrives, state advances, a decision is recorded (by an agent or a human), an action is validated, approval is requested if needed, the executor runs, the result is audited. Any step can fail; the failure is also audited. Six months later, the entity can be replayed from events alone and the materialized state can be checked against the replayed state. This is not a framework that asks you to learn a new programming model. It is the model you would have built yourself, written once. --- ## 3. The trust boundary The single technical claim KIFF stands on: > Callers cannot self-approve. A naive system would let any caller pass `approved: true` along with the action they want to run. KIFF refuses to expose that field. Inside the runtime, an `ActionContext` has an unexported `approved` boolean. The Go compiler enforces that fields with lower-case names are not visible outside the package. Code in any other package — including the agent, including the HTTP handler, including a malicious caller — cannot construct an `ActionContext` with `approved: true`. They can ask for approval. They cannot grant it to themselves. ```go // pkg/kiff/action/action.go (excerpt) type ActionContext struct { ActionName string EntityID string EntityType string CurrentState string Actor actor.Actor Parameters map[string]any ApprovalID string approved bool // unexported. Only the runtime can set this. } ``` The runtime sets the field through a single method, `applyApproval`, which runs only after looking up an approval record by ID, verifying the record matches the entity and action, and confirming its status is `granted`. If any check fails, the bit stays false. If the approval store returns an error, the runtime propagates it; it does not silently treat a missing approval as not-required. This is testable. The conformance suite confirms that constructing an action context with an `approved: true` literal is a compile-time error. A separate test confirms that calling `ExecuteAction` with an invented approval ID returns `action.ErrApprovalRequired` rather than running the executor. The test exists because the boundary is the framework's most important property. The same shape applies to other guarantees: actions require explicit executor functions (a missing executor returns `ErrExecutorMissing` rather than a silent no-op), audit IDs combine an atomic counter with random bytes (collision-resistant under concurrent writes), and the runtime validates a domain definition when one is supplied (an invalid state machine fails fast, not after the first event). These are small properties. Each one is one decision. The framework's value is that the decisions are made consistently across packages and backed by tests, not that any one of them is novel. --- ## 4. Mechanics, not semantics The most common protocol-design mistake is normalizing too much. The instinct is reasonable: if KIFF defines a coordination layer, why not also define what an order, a claim, or a clinical encounter is? The answer is that business semantics do not portably abstract. A refund and a fraud hold and a prescription order share none of their business meaning. Forcing them into a common ontology produces a specification that has to change every time a new domain is added — and that fails the moment a domain has a concept the ontology did not anticipate. KIFF normalizes the *operational structure*, not the meaning. Every domain that uses the framework defines its own: - entity types (`Order`, `Claim`, `Encounter`, `Account`, `Service`) - event types (`ORDER_PAID`, `CLAIM_FILED`, `LAB_RESULT_RECEIVED`) - states (`PAID`, `UNDER_REVIEW`, `RESOLVED`) - action contracts (`REFUND_ORDER`, `APPROVE_CLAIM`, `RESTART_SERVICE`) - permissions (dotted lowercase identifiers like `orders.refund`) KIFF only defines how those are *structured*: events have a stable envelope (id, type, entity, source, actor, timestamp, metadata, payload), states are values updated by deterministic transitions, actions are contracts with the seven fields above, approvals are upsert records, audit is append-only with trace correlation. The analogy is TCP/IP. The protocol does not know whether the bytes are a transaction confirmation or a video frame. It defines the structure inside which those things travel. KIFF is the same kind of layer for operational coordination. This boundary is what makes the framework's coordination story work across the five sectors above without becoming a 200-page specification. The mechanics generalize. The vocabulary does not. --- ## 5. Evidence: what we built The framework is at v0.1. The artifacts in the repository are the evidence the design is real, not aspirational. ### 5.1 The framework About 6,000 lines of Go under `pkg/kiff/`. Seventeen packages, each with one job, each with tests. The entire core protocol — events, state, decisions, actions, approvals, audit, runtime, store interfaces, HTTP API, observability wrapper, test helpers — runs against `go 1.23` with one external dependency (`pgx/v5` for the Postgres backend, optional). Anything implementing the four `Store` interfaces (`event.Store`, `decision.Store`, `approval.Store`, `audit.Store`) is a valid backend. Three implementations exist: in-memory (default), file-backed JSONL (local persistence), Postgres (production). All three pass the same shared conformance suite (`pkg/kiff/store/storetest`), which has 21 cases covering ordering, filtering, payload round-trips, upsert semantics, validation rejection, and context cancellation. ### 5.2 The refund demo Located at `examples/refund-agno/`. Two runs of the same Agno-shaped agent against the same prompts, same model, same fixture. **Run A — without KIFF.** The agent's `refund_order` tool mutates a mock database directly. A $999 refund on an unpaid order succeeds because nothing checks the order's state. **Run B — through KIFF.** The agent's tool POSTs to a small HTTP server that wraps `pkg/kiff/runtime`. Small refunds (≤ $100) hit `AUTO_REFUND` and execute immediately. Refunds above the ceiling hit `REFUND_ORDER`, which has `ApprovalRequirement: ApprovalRequired`. The runtime returns `approval_required` to the agent. A human grants the approval. The same call from the agent now executes. The audit timeline shows the proposal, the validation gate, the approval cycle, the execution, and the rebuild check — `materialized = REFUNDED`, `replayed = REFUNDED`, `events = 3 ✓`. The fixture is deterministic (`agent.OfflineProvider`) so the demo runs without an LLM API key. The same code runs against AWS Bedrock when credentials are set. The point is that the *governance behavior* is identical regardless of where the proposal came from. The demo is a single `make demo` command. Output reproduced in Appendix C of the demo's own README; the canonical 90-second screencast on the landing page is a recording of this command. ### 5.3 The breadth demo Located at `examples/support-ops/`. One agent with five tools running on a five-ticket batch produces five distinct outcomes: | Ticket | Tool | Outcome | Reason | |---|---|---|---| | 1 | `issue_refund` | executed | small amount, no approval needed | | 2 | `issue_refund` | approval_required → granted → executed | over the cap, granted on review | | 3 | `send_outreach` | blocked_consent_missing | structural rejection before any approval is opened | | 4 | `escalate_to_human` | executed | escalation never needs approval | | 5 | `close_ticket` | executed | only legal in `RESOLVED` | Ticket 3 is the most interesting case. The `SEND_OUTREACH` action has a custom validator that rejects the action when `consent_verified` is missing or false — *before* an approval is ever opened. Approvals are for authority decisions; eligibility checks happen earlier. The breadth demo demonstrates that a heterogeneous tool surface can flow through the same runtime cleanly, including domain-specific validators. ### 5.4 The conformance suite `pkg/kiff/store/storetest/` defines the contract every persistence backend must satisfy. Adding a new backend is a known-cost exercise: implement the four `Store` interfaces, write a test factory that creates a clean instance of each, register the factories with the suite. The suite confirms the new backend behaves identically to the in-memory and file-backed reference implementations. This matters because backends accumulate. A small contract that holds across implementations is the difference between a real boundary and a "plug your own here" hand-wave. ### 5.5 The Postgres backend `pkg/kiff/store/postgres/`. About 600 lines plus `schema.sql`. Four tables (`kiff_events`, `kiff_decisions`, `kiff_approvals`, `kiff_audit`) with `JSONB` payloads, indexes only on the columns the suite filters on. Connection pooling via `pgx/v5/pgxpool`. Conformance tests gated by `KIFF_POSTGRES_TEST_URL` so the default `go test ./...` does not need a running database. Verified against `postgres:16-alpine`. Every conformance subtest passes. Schema is idempotent (`CREATE TABLE IF NOT EXISTS`). Production migrations are the operator's tool of choice (`golang-migrate`, `goose`, Atlas); KIFF does not bundle one. ### 5.6 The operator surface Two pieces, intentionally minimal: **`/admin` and `/admin/entities/{id}`**, served by `pkg/kiff/httpapi`. Read-only HTML rendered with `html/template`. Lists pending approvals, shows entity timelines, color-codes denials and failures. Production deployments put auth in front of this; KIFF does not pretend to be a multi-tenant UI. **`kiff timeline -base -entity `**, the inspection CLI. Hits the `/entities/{id}/timeline` and `/demo/rebuild` endpoints on any running KIFF server. Renders a compact terminal table with the audit trail and the rebuild check. Twenty seconds from typing the command to having the answer to "why is this entity in this state?". ### 5.7 What this evidence does and does not prove It proves the protocol can be implemented small (six primitives, seventeen packages, one external dependency). It proves the trust boundary is testable (the unexported field plus the conformance suite). It proves the persistence interface is real (three backends pass the same suite). It proves the agent integration story works in two demos with real LLM proposals. It does not yet prove the protocol generalizes to all five sectors in §1. Three of those sectors — insurance, healthcare, fintech — are hypothetical for KIFF today. The evidence in §5 is concrete for e-commerce shapes; the rest is reasoned from the structural argument in §4. The cross-sector examples in §1 are structural analogies, not proof of sector readiness or compliance with sector-specific regulations. The next twelve months will close this gap or surface where the protocol breaks down. We expect both. --- ## 6. Honest limits The framework v0.1 does not handle the following. These are design boundaries, not missing features — each one has a composition story with a tool that already solves it. **Durable execution.** KIFF is not a workflow engine. If your domain needs steps that survive crashes, replay on retry, or coordinate across multiple processes over hours or days, KIFF should sit *underneath* a workflow engine (Temporal, Inngest, Restate), not replace it. The workflow handles durability; KIFF handles whether each step is allowed. **Multi-tenant identity.** KIFF has actors and permissions but no notion of organizations, projects, or scopes. Production deployments add their own auth layer in front of the HTTP API. The framework does not impose a tenant model because the model varies too much across adopters. **Distributed state.** State today is centralized. The runtime assumes a single source of truth per entity. Distributed state (replicated, eventually consistent, or sharded across regions) is not part of v0.1. Most adopters do not need it; the ones who do should compose KIFF with a state backend that handles the replication. **Event ordering across producers.** KIFF preserves insertion order within a single store. Cross-producer ordering (clock skew across sources, exactly-once delivery, idempotency keys) is the producer's responsibility. The framework refuses to pretend it has solved distributed-systems problems it has not solved. **A complete observability story.** `pkg/kiff/observability` wraps the audit store with `slog` and a counter registry. There are no traces, no spans, no metrics histograms. Production deployments instrument their own observability layer; KIFF gives them the audit records to instrument from. **A managed runtime.** KIFF Cloud is mentioned in the project's vision as a future commercial layer. It does not exist yet. Adopting KIFF today means embedding it as a library and running it yourself. These are not bugs. They are explicit non-goals for v0.1. Each has a real design reason; each has a composition story with the tools that already solve it. The protocol's value depends on staying small. --- ## 7. Where this goes The next six months, ordered by likelihood: **Get to twenty-five real adopters.** The framework is in good shape. The bottleneck now is not code; it is people writing domains on top of it. The launch sequence — public push, hosted demo, screencast, Show HN — is the next major effort. **A second persistence backend.** SQLite is the most likely candidate, because it covers single-binary deployments and edge use cases that Postgres does not. DynamoDB for AWS-native deployments lands behind SQLite if the adoption signal supports it. **One more sector demo.** A second worked example outside e-commerce. The two strongest candidates are a simple fintech-ops domain (a small treasury rule that gates large transfers behind dual control) and a clinical-workflow domain (a tiny EHR ingest path with active-state checks). The choice depends on which adopter pulls hardest in the launch period. **A managed runtime.** "KIFF Cloud" — hosted runtime, multi-tenant admin UI, audit retention, compliance exports — is the eventual commercial product. It is not a v0.1 deliverable. It exists in this document only as a footnote so the reader knows it is planned. **Specification work.** The current type signatures are the spec. A proper protocol document, language-independent, that other implementations can read against, is on the roadmap once the v0.1 API is stable. The Markdown for that document already exists in fragmented form across `docs/architecture.md`, `docs/conventions.md`, and this whitepaper. What is *not* on the roadmap: an LLM SDK in the core, a workflow engine, a managed agent platform, a model gateway, an app builder. KIFF is one thing. The next six months are about doing that thing publicly. --- ## Appendix A: the action contract Reproduced verbatim from `pkg/kiff/action/action.go`: ```go // ActionContract describes when and how an action is allowed to run. type ActionContract struct { Name string AllowedStates []string RequiredParameters []string RequiredPermissions []permission.Permission Risk RiskLevel ApprovalRequirement ApprovalRequirement Executor func(context.Context, ActionContext) (ActionResult, error) } ``` Field by field: - **`Name`** — the operational identifier, stable across versions. Shows up in audit records, HTTP routes, and proposal payloads. - **`AllowedStates`** — the only states from which this action is meaningful. The runtime returns `action.ErrStateNotAllowed` if the current state is not in this list. State is checked before parameters, permissions, or approvals; an action that does not belong in the current state never reaches the rest of the gate. - **`RequiredParameters`** — the parameters that must be present and non-nil. Missing parameters fail validation with `action.ErrMissingParameter`. The executor never has to write defensive nil-checks. - **`RequiredPermissions`** — permissions the actor must hold. The runtime queries the configured `permission.Policy` interface. A missing permission returns `action.ErrPermissionDenied`. - **`Risk`** — operational metadata. Drives reporting, dashboards, and downstream tooling. Does not affect the runtime path. - **`ApprovalRequirement`** — `ApprovalNever` or `ApprovalRequired`. When set to `ApprovalRequired`, execution returns `action.ErrApprovalRequired` until a granted approval record is resolved by the runtime. - **`Executor`** — the side-effect function. Takes a validated `ActionContext`, returns an `ActionResult`. The executor never re-validates state, parameters, permissions, or approval; the runtime did. Its only job is to do the thing and describe the outcome, including any follow-up events that drive the next state transition. The contract is the operational document for the action. A new engineer joining the team can read down a contract and answer every governance question for that action in under thirty seconds. --- ## Appendix B: what KIFF is not Five categories KIFF deliberately does not occupy: **Not an agent framework.** No prompt builder, no model SDK, no tool registry, no harness. Agents are clients of KIFF; they are not what KIFF is. Use LangGraph, Agno, OpenAI Agents SDK, or your own harness. **Not a workflow engine.** No durable execution, no retries, no timers, no saga support. KIFF composes underneath workflow engines that own those concerns. **Not a managed agent platform.** No deploy story for the agent's code, no session durability, no harness orchestration. Use Modal, Lambda, Dari, your own infrastructure. **Not a model gateway.** No routing between providers, no request shaping, no caching. Talk to your model provider directly. **Not an app builder.** No UI scaffolding, no admin SDK beyond the read-only `/admin` page. KIFF's HTTP API is JSON; build whatever frontend you want. The reason this list matters: every operational team eventually encounters pressure to add one of these capabilities to the governance layer. The discipline is to refuse, because the moment KIFF tries to replace any of these tools it stops being small enough to read in a weekend, and the small-enough-to-read property is what makes it trustworthy. KIFF composes. It is not a platform. --- *This document is part of the KIFF Framework, MIT-licensed, at [github.com/kiff/kiff](https://github.com/kiff/kiff). The whitepaper itself is released under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/).* *Comments, corrections, and counterexamples welcome at hello@kiff.dev or as issues on the repository.* --- # Why KIFF Source: https://kiff.dev/why # Why I'm building KIFF Every company I talk to is racing to put AI agents into production. And almost every one has the agent doing the same thing: drafting, suggesting, summarizing. Watching. The moment the conversation turns to letting it *actually do something* (issue the refund, move the money, change the record), everyone goes quiet. Because nobody trusts it. So the agent gets caged: read-only, one narrow task, a human in every loop. We bought something that can reason and act, and we use it as an intern who isn't allowed to touch anything. I didn't start KIFF to ship a governance feature. I started it because there's a bigger thing on the other side of this, and one fear standing in front of it. The bigger thing: organizations being able to give machines real responsibility, and still be accountable for what happens. Not automation that someone has to babysit. Not autonomy that someone has to pray about. Responsibility you can hand over and still stand behind. That's the actual product. Domains, contracts, receipts: those are the frames and the lenses. What we're really selling is the ability to see what your agents are doing and trust it enough to let them act. ## What I keep seeing The same three things, over and over. **The ceiling is trust, not intelligence.** Teams keep reaching for a smarter model, a better prompt, another eval. But the thing keeping the agent out of production was never its IQ. A wrong action on real state (a double refund, a payout to the wrong account, a status flipped twice) is expensive and hard to undo. No model upgrade fixes that. The fear is rational, and it's structural. **Everything underneath was built for a human in the loop.** Our software assumes a person is the one clicking: someone who knows the order was already settled, someone who won't approve their own request. Take the human out, drop an agent in, and all those quiet assumptions vanish at once. **The popular fixes don't fix it.** More guardrails in the prompt, which is string-matching hope. More monitoring, which tells you after it already happened. A sandbox, which stops the agent doing the one thing you deployed it for. Each one manages the fear without removing it. ## The reframe We've been asking the wrong question. Not *"can I trust this agent?"* That one is unanswerable. You can't audit a vibe. The question is *"can I trust the system it's acting in?"* That one has an answer. A system can be deterministic. It can know what state something is actually in. It can hold the rule that an action is only allowed from certain states, that a large refund needs a second signature, that no one approves their own request. It can decide *before* anything executes, and record what it decided and why. You stop trusting the actor and start trusting the structure around it. So you define your operational domain once: the entities, their states, the allowed actions, who's permitted, what needs approval. Any agent, in any framework, in any language, proposes against that contract. KIFF checks it against the real state and decides (allowed, refused, or held for a human) before it touches production, and signs a receipt you can verify yourself. The agent stops being the thing you have to trust. It becomes a thing that asks, and gets a real answer. And the domain is the part that lasts. Models change, frameworks churn, this year's agent gets replaced by next year's. The contract for how your business is allowed to operate shouldn't churn with them. You write it once and it outlives every agent you point at it. The agent is replaceable. The domain is the asset. ## What I believe A category is a thing you build. *How* you build it is a choice, and I care more about that part than the org chart usually allows. **If software decides about you, you should be able to read it.** A system that sits between an agent and your money or your customers cannot be a black box you're asked to trust on faith. That's why the core is open source. Not as a growth tactic, but as a condition of the thing being trustworthy at all. You can run the kernel yourself and read every line that makes a decision. **Proof beats promises.** Anyone can claim their agent did the right thing. We make every decision verifiable: evidence you can check yourself, not a number on our dashboard you have to take our word for. Accountability shouldn't depend on trusting the vendor, including us. **Show, don't assert.** Our site says what's built and what isn't. If a thing is on the roadmap, it says roadmap. In a market this full of demos, I'd rather earn trust the way the product does, by showing the decision instead of asserting it. **Humans keep the authority.** The goal was never to remove people. It's to let them hand off the work while keeping the call. The system holds the line. A person still owns the consequential ones. Autonomy with someone accountable, on purpose. None of these are decorations on the product. They *are* the product. A trust layer built by a company you can't see into would be a contradiction. ## Why now Agents crossed the line from *suggesting* to *acting* faster than anyone planned for. The intelligence arrived. The safe place to put it didn't. That gap is the whole game right now, and it's an infrastructure problem, not a model problem. Build the layer beneath the agents, honestly and in the open, and organizations stop having to choose between control and autonomy. They get both. That's worth building right. If you're trying to get an agent past the read-only ceiling and into real work, that's the entire reason KIFF exists. I'd love to talk.