allow or deny). Policies are authored in the dashboard or pushed via the API, versioned on each save, and published as a signed bundle that the SDK pulls every 30 seconds.
Anatomy
Fields
| Field | Required | Notes |
|---|---|---|
apiVersion | Yes | Always agent-governance.io/v1. |
kind | Yes | Always Policy. |
metadata.name | Yes | Human label. Doesn’t have to be unique. |
metadata.description | No | Surfaces in the dashboard. |
metadata.labels | No | Record<string, string> for ownership tagging. |
spec.defaultEffect | Yes | allow or deny. Applied if no rule matches. |
spec.rules | Yes (≥1) | Evaluated top-to-bottom; first match wins. |
Rule conditions
A rule matches when every condition in its list matches (logical AND). Condition shape:Common fields
| Field | Type | Source |
|---|---|---|
tool_name | string | First positional arg to evaluate(). |
agent_id | string | Bound to the JWT’s sub claim. |
input.<key> | any | From metadata.input passed to evaluate(). Supports nested paths. |
args.<index> | any | Positional args (LangChain adapter). |
kwargs.<key> | any | Keyword args (LangChain adapter). |
dlp_detected | bool | Set by the inline DLP scanner if enabled. |
dlp_severity | low|medium|high | Highest severity detected. |
dlp_types | string[] | Types that fired (EMAIL, SSN, CREDIT_CARD, …). |
Versioning
Every save creates a new version. Versions can be:- draft — saved but not published. Visible in the editor, not in the bundle.
- active — currently published. There is at most one active version per policy.
- archived — was active, has been replaced. Kept for audit.
Dry-run
Before publishing, click Dry-run in the editor. Rubric replays the last 1000 audit events through the candidate policy and shows you:- How many events would have been allowed vs denied.
- Which agents are most affected.
- A diff against the currently-published version.
Scope: which agents does this policy enforce against?
Scope is prescriptive and operator-controlled. Every policy carries an explicit list of agents it enforces against, stored separately from the YAML. The list is editable on the policy detail page and required at publish time.| State | Meaning | SDK behavior |
|---|---|---|
| Empty scope | Policy applies to no agents. | Bundle build excludes the policy from every per-agent slice. The policy is inert. |
| ≥ 1 agent | Policy applies only to the listed agents. | Each scoped agent’s bundle includes the policy. Unscoped agents don’t see it. |
How it ships
- Scope is not part of the policy YAML — it’s a separate per-policy agent list. Forking a template never carries scope across orgs; the fork prompts the operator for fresh agent selection.
- Each agent gets its own bundle slice. Editing scope on a published policy fans out fresh bundles to the affected agents only — no version bump, no SDK restart.
- Every scope add/remove is recorded in the audit ledger right alongside tool-call decisions, on the same tamper-evident chain. Config tampering is as detectable as decision tampering.
Required at publish
The dashboard disables the publish button until you pick at least one agent. Drafts are allowed to have empty scope — they’re not in the bundle anyway.Filtering rule logic by agent metadata
Conditions likeagent_environment eq prod still work for narrowing which calls inside a scoped agent match a rule. They’re orthogonal to scope: scope picks which agents see the policy; conditions pick which calls within those agents trigger which effect.
Default-allow vs default-deny
The defaultspec.defaultEffect: allow is the right starting point — it lets you ship an integration with zero policies and add denies incrementally.
For high-trust environments, flip to defaultEffect: deny and add explicit allow rules. The SDK supports both; nothing in the framework prefers one model.