5-minute @governed walkthrough
The shortest path from pip install to a real allow/block decision on a tool you wrote yourself. No agent framework, no LLM API key, no adapter. Just a Python function, a Rego rule, and the policy gate.
What you'll have at the end
A refund.py script that calls approve_refund(...) twice — once inside policy, once outside. The inside call returns its result; the outside call raises GovernanceError with the policy decision attached.
Prerequisites
- Python 3.11+
- Docker for the OPA policy engine
- ~5 minutes
Step 1 — Install
pip install kitelogikStep 2 — Scaffold OPA + a starter project (optional but easiest)
kitelogik init governed-tutorial
cd governed-tutorial
docker compose up -d # starts OPA on http://localhost:8181The init command writes a policies/policy.yaml, compiles it to policies/policy.rego, generates an agent.py, and a docker-compose.yml. We'll ignore the generated agent.py and write our own tiny script.
You can also skip init and run OPA directly:
mkdir policies && cd policies # for the rego we'll write below
docker run -d --name opa -p 8181:8181 \
-v "$(pwd):/policies:ro" \
openpolicyagent/opa:latest run --server --addr :8181 /policiesStep 3 — Write a tiny policy
Replace the contents of policies/policy.yaml with:
version: 1
rules:
- name: allow_small_refund
when:
action: approve_refund
role: support_agent
scope: approve_refund
args:
amount: { lte: 100 }
then: allow
- name: block_large_refund
when:
action: approve_refund
args:
amount: { gt: 100 }
then: deny
reason: "Refunds over $100 require manager approval"Compile it to Rego:
kitelogik compile policies/policy.yamlOPA was started with --watch (the compose file does this), so the new Rego loads automatically — no restart needed.
Step 4 — Write the script
Create refund.py next to policies/:
# refund.py
import asyncio
from kitelogik import (
GovernanceError,
OPAClient,
PolicyGate,
SessionContext,
governed,
)
gate = PolicyGate(opa_client=OPAClient()) # default: http://localhost:8181
context = SessionContext(
session_id="tutorial_001",
user_role="support_agent",
session_scopes=["approve_refund"],
)
# Your business function — no governance code in the body.
@governed(gate=gate, context=context)
async def approve_refund(customer_id: str, amount: float) -> str:
return f"Refunded ${amount:.2f} to {customer_id}"
async def main():
# Within scope — runs normally
print(await approve_refund(customer_id="cust_001", amount=42.0))
# Out of scope — policy denies, GovernanceError is raised
try:
await approve_refund(customer_id="cust_001", amount=5000.0)
except GovernanceError as exc:
print(f"BLOCKED: {exc}")
print(f" risk_tier = {exc.decision.risk_tier}")
print(f" rule_matched = {exc.decision.rule_matched}")
asyncio.run(main())Step 5 — Run it
python refund.pyExpected output:
Refunded $42.00 to cust_001
BLOCKED: Tool 'approve_refund' denied by policy: Refunds over $100 require manager approval
risk_tier = OPERATIONAL
rule_matched = data.kitelogik.main.deny[_]The first call ran the function body normally. The second never reached it — GovernanceError was raised before the function body executed, with the full PolicyDecision attached on exc.decision.
Iterating
Edit policies/policy.yaml (e.g. raise the threshold to lte: 200), recompile:
kitelogik compile policies/policy.yamlOPA hot-reloads. Re-run python refund.py to see the new threshold take effect — no restart anywhere.
What you just built
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ refund.py │ call │ @governed │ HTTP │ OPA :8181 │
│ approve_refund │ ──────▶ │ evaluates input │ ──────▶ │ policy.rego │
└──────────────────┘ └────────┬─────────┘ └──────────────────┘
│
allow / deny / hitl
│
▼
fn body runs (or
GovernanceError)Three pieces, ~20 lines of code, no framework.
Where to go next
- GovernedToolbox — when you have many tools, register them all on one toolbox and call by name
- Your first policy — cover the full YAML DSL, hand-written Rego, and
opa test - Adapter overview — wire
@governed's underlying gate into OpenAI, Agents SDK, LangChain, LangGraph, etc. - Risk tiers & HITL — when policy should escalate to a human reviewer instead of denying outright