knowledge-runtime is the Python SDK that turns any function into a Policy Enforcement Point (PEP). It handles signature verification, binding checks, JWKS caching, and the /resolve loop.
Install
pip install knowledge-runtime
Depends on httpx, python-jose (ES256), pydantic (v2). Python 3.10+.
Configure
from knowledge_runtime import KnowledgeClient
client = KnowledgeClient(
base_url="https://knowledge.asplenz.com/tnt-acme",
api_key=os.environ["KNOWLEDGE_API_KEY"],
jwks_cache_ttl_seconds=300,
default_verdict_ttl_seconds=60,
)
One client instance per tenant. Thread-safe. Reuse across the process.
The @governed_tool decorator
The primary interface.
from knowledge_runtime import governed_tool
@governed_tool(
action="refund.execute",
resource="tx", # arg name that carries the resource
bind=["amount"], # arg names that become parameter bindings
client=client,
on_behalf_of=lambda: current_user_id(),
)
def refund_customer(tx: str, amount: int, reason: str = ""):
return refund_api.execute(tx, amount, reason=reason)
On each call :
- Extract
(tx, amount)from the call arguments. - Call
client.check(action="refund.execute", resource=tx, parameters={"amount": amount}, on_behalf_of=...). - If verdict is
allowed, verify the signature, verify bindings match, then invoke the wrapped function. - If verdict is
blocked, raiseKnowledgePolicyBlocked(verdict). - If verdict is
approval_required, raiseKnowledgeApprovalRequired(consultation_id, approval_id).
The wrapped function only runs if enforcement authorised it.
Guard-rails
The decorator refuses to start if configuration is incoherent. Fail-loud, not fail-silent :
bind=["amount"]but the function has noamountparameter ->ConfigurationErrorat import time.resource="tx"buttxis not a positional or keyword arg ->ConfigurationError.client=Noneand no default client set ->ConfigurationError.
verify_verdict (low-level)
For custom PEP code that does not fit the decorator :
from knowledge_runtime import verify_verdict
payload = verify_verdict(
jws=signed_verdict_string,
jwks_url="https://knowledge.asplenz.com/tnt-acme/v1/jwks",
expected_actor="agn-rm-copilot",
expected_action="refund.execute",
expected_resource="TX-456",
expected_bindings={"amount": 40},
max_age_seconds=60,
)
Returns the decoded claims dict on success. Raises typed errors :
SignatureInvalidExpiredVerdictBindingMismatch(field, expected, got)KidNotFound(kid)
/resolve loop with fetcher registry
from knowledge_runtime import resolve_with_fetchers
fetchers = {
"client.classification": crm.get_classification,
"client.knowledge_experience_level": crm.get_ke_level,
"solicitation.type": lambda ctx: llm_extract(ctx["conversation"], "solicitation_type"),
}
result = resolve_with_fetchers(
client=client,
action="structured_note.propose",
resource=product_id,
initial_context={"client.id": {"value": client_id, "source": "crm"}},
fetchers=fetchers,
max_rounds=8,
on_behalf_of=user_id,
)
Returns the final complete result (with signed_verdict) or raises TooManyResolveRounds if max_rounds is exhausted.
JWKS cache
Automatic. First verification per tenant fetches the JWKS, subsequent verifications reuse the cached keys until TTL expires. On KidNotFound, the cache force-refreshes once before giving up (handles rotation).
Spent-verdict store (exactly-once)
from knowledge_runtime.spent import RedisSpentStore
store = RedisSpentStore(redis_url="redis://localhost:6379")
@governed_tool(
action="refund.execute",
resource="tx",
bind=["amount"],
client=client,
spent_store=store,
)
def refund_customer(tx: str, amount: int):
...
The store records {iss, consultation_id} on each authorised call. A replay within TTL is rejected with VerdictAlreadySpent.
Store implementations : RedisSpentStore, InMemorySpentStore (dev only), or your own SpentStore implementation.
Testing helpers
from knowledge_runtime.testing import fake_verdict, InMemoryKnowledgeClient
fake = InMemoryKnowledgeClient(tenant_slug="tnt-test")
fake.set_verdict("refund.execute", "TX-*", verdict="allowed")
@governed_tool(action="refund.execute", resource="tx", bind=["amount"], client=fake)
def refund_customer(tx: str, amount: int):
return f"refunded {amount} on {tx}"
assert refund_customer("TX-1", amount=40) == "refunded 40 on TX-1"
Repo pointer
Source : apps/knowledge/src/knowledge-runtime/ in the monorepo. Test suite (24 tests) : apps/knowledge/src/knowledge-runtime/tests/.
Related
- Quickstart : governed tool - 5-minute hands-on.
- Signed verdicts and PEP - what the SDK verifies.
- MCP proxy - if your stack is MCP, use the proxy instead of decorating tools individually.
