Put an agent you already run under a KIFF Card. kiff-guard is the client
SDK and framework adapters that check each of your agent's tool calls with
KIFF before the tool runs.
A KIFF Card is the authority a business gives one agent: which actions it may take, how much per action and in total over a window, and what happens to a call outside it (it waits for the owner, or it is refused). The owner changes or revokes the Card without changing the agent. kiff-guard is how an agent in your own code reaches that check.
One guard, two modes:
- observe runs every tool, records an audit trail, and learns which tools the agent calls. No KIFF account, no domain, no API call. It shows you what the agent actually does, which is what you need to write its Card.
- enforce asks KIFF to decide before each tool runs, against the domain
and the agent's Card.
allowedproceeds; anything else (approval_required,blocked,invalid, or any future outcome) withholds the call. Fail-safe by construction.
The same one-line integration also derives a starter KIFF domain from real traffic, so you never start from a blank policy file.
Part of KIFF. This repo is the client SDK and framework adapters, MIT-licensed. The decision engine is the open-source framework at
kiff/kiff. Cards, owner approvals and signed receipts are part of KIFF Cloud.Agent calls MCP tools? You may not need this SDK: point the agent at the KIFF MCP gateway (
mcp.kiff.dev) and KIFF checks each call against its Card with no code change. See kiff.dev/docs/connect-an-agent.
Connect starts from the agent you already run:
- Attach an adapter to the framework's pre-tool-execution seam, or wrap a
plain function call with the core
Guard. - Observe real tool calls without a KIFF account or domain. The agent still runs; the guard records what tools are being called and learns the action catalog.
- Derive starter KIFF domain material from that traffic: candidate actions, entity hints, required parameters, and the runtime/source evidence they came from.
- Enforce by pointing the same guard at a KIFF runtime. Every tool call asks
for a decision first:
allowedruns, anything else withholds. With an API key bound to the agent, the guard proposes as that agent, so the decision draws on the agent's Card: a call over its limit is held for the owner or refused before the tool runs.
The framework repo (kiff/kiff) owns the domain/runtime kernel. This repo owns
the connection layer: SDK primitives, framework adapters, observe/enforce
wrappers, runtime registration, and source attribution.
Tracking: cloud Connect epic
kiff/kiff-cloud#542;
source-attribution follow-up #42.
packages/
python/kiff-guard/ # the Python SDK (shipped): core + 10 adapters
js/ # the TypeScript SDK (shipped): core + OpenClaw adapter
The guard is a framework-agnostic core plus thin adapters, one per agent framework, each translating that framework's pre-tool-execution seam into a single call to the core. The guard logic lives once; an adapter adds no governance logic of its own.
Custom or no framework? You don't need an adapter. The core (
Guard
HTTPClient) governs any tool call directly over plain HTTP — the adapters are just convenience glue. See the "Custom agent? No adapter required" quickstart inpackages/pythonorpackages/js, and the raw-HTTP recipe incookbook/custom-agent-httpfor stacks the SDKs don't cover (Ruby, Go, shell).
See packages/python/kiff-guard/README.md
for install, quickstart, and per-framework usage.
pip install kiff-guard # core, zero deps
pip install "kiff-guard[agno]" # + a framework adapter's depsfrom kiff_guard import Guard
from kiff_guard.adapters.agno import agno_hook
guard = Guard(mode="observe") # zero-config audit; no KIFF account
agent = Agent(model=..., tools=[...], tool_hooks=[agno_hook(guard)])| Framework | Lang | Shape | Status |
|---|---|---|---|
| Agno | py | middleware (tool_hooks) |
shipped |
| LangGraph / LangChain | py | middleware (wrap_tool_call) |
shipped |
| Hermes (Nous) | py | vote (pre_tool_call plugin hook) |
shipped |
| OpenAI Agents SDK | py | vote (tool input guardrail) | shipped |
| Google ADK | py | vote (before_tool_callback) |
shipped |
| Pydantic AI | py | vote (before_tool_execute hook) |
shipped |
| Strands Agents | py | vote (BeforeToolCallEvent) |
shipped |
| Haystack Agents | py | vote (ConfirmationStrategy) |
shipped |
| Microsoft Agent Framework | py | middleware (FunctionMiddleware, async) |
shipped |
| OpenClaw | ts | vote (before_tool_call) |
shipped (@kiff/kiff-guard/adapters/openclaw; seam + contract verified) |
| LlamaIndex | py | middleware (GuardedAgentWorkflow subclass, async) |
shipped |
| Custom / no framework | any | core Guard + HTTPClient / raw HTTP |
shipped |
Two integration shapes: middleware (the guard runs the tool via a handler continuation) and vote / inverted-control (the framework runs the tool; the hook only votes allow/block). Each adapter documents its verified pre-tool-execution seam and block contract in its module docstring.
ToolMap is the list of tools KIFF is asked about. A tool that is not bound
has no action to propose, so the runtime is never consulted — and since 1.1.0
the guard withholds rather than clearing it:
guard = Guard(mode="enforce", client=HTTPClient(api_key=..., tool_map=tool_map))
# refund_order is bound -> KIFF decides
# wire_transfer is not -> invalid: "not bound in the ToolMap; KIFF was not asked"This is deliberate and it is the safe direction. Before 1.1.0 an unbound tool
was cleared with the reason "unmapped; cleared and audited", which meant a
binding you forgot became an ungoverned tool with a receipt claiming otherwise.
For a staged rollout where the map is still being filled in, opt back in explicitly — but understand that such a deployment governs only what you remembered to map:
HTTPClient(api_key=..., tool_map=tool_map, unmapped="allow")Use kiff-scan or kiff scan to find consequential calls you have not bound.
Both SDKs share a version number and are released from the same tag.
Semantic versioning applies to the core: Guard, HTTPClient,
ToolMap, and the decision shape. Those are what your code imports and
what a major version protects.
Adapters are scoped out of that promise. An adapter is glue over another project's interception seam, and agent frameworks change those seams on their own schedule — sometimes in a patch. When an upstream framework moves its hook, the adapter follows it in a minor release rather than forcing a major version for a break we did not introduce. Each adapter pins the framework range it is tested against, and CI runs it against that framework's latest so drift surfaces as a red badge.
In practice: pin the SDK normally, and treat an adapter's framework range as the real compatibility contract for that framework.
Every adapter must pass the conformance suite
(kiff_guard.conformance) — a contract that pins the invariants all
adapters share (observe is decide-independent and one-receipt; enforce is
one-receipt; unknown outcomes fail safe; the trust boundary holds). Add a
small drive shim in tests/test_conformance.py and pass it; that's the
bar, not a line-by-line audit.
Support tiers: a small set of adapters are maintained tier-1; the rest are community/best-effort. Each adapter pins the framework version range it's tested against, and CI runs against each framework's latest so breakage shows as a red badge, not a silent rot.
See packages/js/README.md for install,
quickstart, and usage.
npm install @kiff/kiff-guardimport { Guard } from "@kiff/kiff-guard";
import { registerKiffGuard } from "@kiff/kiff-guard/adapters/openclaw";
const guard = new Guard({ mode: "observe" }); // zero-config audit
// register on OpenClaw plugin api (see package README for full example)The TypeScript SDK is a faithful port of the Python SDK: same architecture
(Guard, Decision, Catalog, Client), same primitives (observe/decideOnly/
recordExecuted/recordWithheld/evaluate), same invariants. Both SDKs speak
the same versioned decide contract (/v1, additive-only).
See cookbook/README.md for runnable recipes
proving KIFF stops risky agent actions before they execute.
Each recipe is a complete, runnable proof: real models, real agent frameworks, real side effects, real KIFF runtime. Deploy locally or on your own infra, run the scenario, see the verdict.
| # | Recipe | Adapter | Verdict |
|---|---|---|---|
| 1 | duplicate-payment-guard | OpenClaw (TS) | $100K → $10K, 9 blocked |
| 2 | refund-ceiling-guard | LangGraph | $250 → $100, 3 blocked |
| 3 | collections-promise-guard | Agno | 5 contacts → 1, 4 blocked |
| 4 | chargeback-dispute-guard | Strands | $125 → $25, 4 blocked |
| 5 | vulnerability-escalation-guard | Agno Teams | 6 actions → 1, whole team halted by one event |
| 6 | kyb-verification-guard | Agno Workflows | $60 → $12, 4 blocked (once-and-done) |
Recipes 5 and 6 also show framework guardrails PLUS KIFF: Agno's
PIIDetectionGuardrail (a pre_hook, input safety) and KIFF (a
tool_hook, action authority) running side by side — different layers,
not competitors.
A second family with the opposite framing: instead of leading with the block,
the agent does the work — a real Agno agent performs the legitimate
revenue/ops action (KIFF allows it, state advances), then is declined on every
repeat once state moves on. The boundary is what makes putting the agent on the
task shippable. E2–E5 run on kiff/kiff v0.6.0 (policy-owned roles) + Agno v2.
| # | Recipe | Adapter | Verdict |
|---|---|---|---|
| E1 | refund-enablement-guard | Agno | refund issued, 4 repeats declined |
| E2 | deal-close-enablement-guard | Agno | 1 discount, 4 declined (no stacking) |
| E3 | payment-recovery-enablement-guard | Agno | 1 charge recovered, 4 declined |
| E4 | instant-payout-enablement-guard | Agno | 1 payout, 4 declined (one-per-escrow) |
| E5 | prompt-injection-refund-guard | Agno | adversarial: 0 extra payouts, 2/2 money paths declined |
MIT. See LICENSE.