Skip to content

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:

  1. On first evaluation, the engine calls GET /api/v1/policies with the agent's agent_key (API key auth).
  2. Policies are cached for KV_POLICY_TTL_SECONDS (default: 300 seconds).
  3. After the first fetch a background thread re-fetches every TTL interval, so evaluate() never waits on the network for policies.
  4. 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