Policies and Rules¶
What you'll learn: What a policy is, how rules work, the two enforcement points, and how severity maps to actions.
What is a policy?¶
A policy is an instantiation of a rule with specific parameters, severity, enforcement point, and optional targeting at an agent or risk classification.
policy = rule + params + enforcement_point + severity + (optional agent_id) + (optional risk_classification)
The same rule can back many policies. For example, field_matches_regex can be instantiated once to check for SSNs, once for credit card numbers, and once for email domains — three policies, one rule.
What is a rule?¶
A rule (or rule function) is a pure Python function that returns True if the policy passes and False if it is violated:
def rule(data: RuleView | RegistrationView, params: dict, context: RuleContext) -> bool:
"""Return True if the policy passes; False if it is violated."""
Rules perform no I/O. They read the step's data, the policy's parameters, and the RuleContext (which provides access to task history and agent metadata). The engine ships with 25 built-in rules.
Enforcement points¶
Policies have one of two enforcement points:
agent_registration¶
Evaluated when an agent registers with the platform, and again when the agent's set of agent_registration policies changes while it is running (for example a manifest is assigned to it). A block at that point refuses every step.* of the running agent until a later evaluation no longer blocks. These policies check the agent's metadata — purpose, declared tools, risk classification.
Example: "Agent must declare a substantive purpose" checks that the purpose field matches ^.{30,}$.
step_execution¶
Evaluated before every step the agent takes. These policies check the intended Behavior against the task's history and the agent's context.
Example: "Code execution requires a preceding gate" checks that a completed step.gate with output.decision: "pass" exists earlier in the task history before allowing step.exec.
Severity and actions¶
Each policy has a severity level that maps to an action when the policy is violated:
| Severity | Risk score | Action on violation | Engine behaviour |
|---|---|---|---|
low |
0.25 | warn |
evaluate() returns normally. Incident webhook fires if configured. |
medium |
0.50 | warn |
evaluate() returns normally. Incident webhook fires if configured. |
high |
0.75 | warn |
evaluate() returns normally. Incident webhook fires if configured. |
critical |
1.0 | block |
The engine's evaluate() returns a result with action=block; the Python client's runner (@kv.step, framework handlers) turns that into a raised KyvvuBlockedError and the step does not run. Incident webhook fires if configured. |
The mapping from severity to action uses a risk score. Each violated rule's boolean result is weighted by its severity level and aggregated. The default aggregator is aggregate_max (worst-case severity wins):
- Risk score
0.0→allow(no violations) - Risk score
(0.0, 1.0)→warn(low, medium, or high severity violation) - Risk score
1.0→block(critical severity violation)
Only critical severity blocks execution. Lower severities (low, medium, high) produce warnings and incident reports but do not prevent the step from running. This is by design — most policies should warn during development and only block for truly critical safety boundaries.
Policy targeting¶
Policies can be targeted at specific agents or risk classifications:
| Field | Effect |
|---|---|
agent_id |
Policy applies only to this agent. If null, applies to all agents. |
risk_classification |
Policy applies to agents of this class or above, ordering minimal < limited < high: a limited policy covers limited and high agents, a minimal policy covers every classified agent. If null, applies to all. |
enabled |
Toggle. Disabled policies are skipped during evaluation. |
How policies are fetched¶
The engine fetches policies from the platform API and caches them in memory:
- On first evaluation, the engine calls
GET /api/v1/policieswith the agent'sagent_key(API key auth). - Policies are cached for
KV_POLICY_TTL_SECONDS(default: 300 seconds). - After the first fetch a background thread re-fetches every TTL interval, so
evaluate()never waits on the network for policies. - If the API is unreachable, or a refreshed set is refused at load, the engine keeps enforcing the previous set and reports
stale_cache.
Policy changes made in the dashboard or via the API take effect within the TTL window — no agent restart required.
Next steps¶
- Tasks and History — how task history enables path-dependent policies
- Creating Policies — create your own policies
- Compound Policies — combine rules with
all_of,any_of,not