Agentic Tool-Call Policy Schema v1
A distinct policy category. OPA/Rego is generic policy. Cedar is authorization. Kyverno is K8s admission. Sentinel is Terraform. This is the schema for the shape none of them target: rules that govern what an AI agent is allowed to do when it invokes a tool (file edit, shell, HTTP, MCP tool call, workflow action).
Rules speak two dialects: JSON for runtime evaluation, Cedar for portability. Both semantically equivalent. Import existing Cedar policies from AWS Verified Permissions; export any Conduct pack back to Cedar. No lock-in.
Why a distinct category
Policy engines already exist. None target the shape we need. The industry has good tools for adjacent problems:
- OPA/Rego — generic policy over arbitrary JSON input
- Cedar — authorization (who can do what to which resource)
- Kyverno — Kubernetes admission control
- Sentinel — HashiCorp infra provisioning
- XACML — enterprise access control (legacy)
- MITRE ATLAS / OWASP Agentic Top 10 — threat catalogs, not enforceable rules
Agentic Tool-Call Policy is its own shape. The actor is an autonomous AI agent, the object is a tool invocation, decisions land in milliseconds pre-execution, and portability across enforcement surfaces (proxy, hooks, MCP, runtime) is table-stakes. This schema names that shape. See the mapping table below for how our vocabulary aligns with each of the above.
Why not just adopt Cedar (or OPA) directly?
Fair question — and the one we asked ourselves before writing a line of schema. We adopted every standard that fit and named only the gap they leave. Here is the honest audit.
What Cedar cannot express without heavy encoding
- Non-binary decisions. Cedar has
permitandforbid. Agentic policy needswarn(proceed but flag),inject(prepend context to prompt),audit(record decision),approval(pause for human). Encoding these as Cedar obligations pushes semantics out of the policy language and into runtime convention — the exact anti-pattern Cedar was built to avoid. - Free-text pattern matching. Cedar's
likeis glob, not regex. Its condition grammar targets structural attribute checks, not scanning LLM prompts for PANs, secrets, or PII. Regex overtool_input.promptis our common case; wedging it into Cedar loses linter and IDE support. - Enforcement-surface metadata. Our
enforcement.{proxy, hook, mcp, runtime}declares which of four surfaces can hard-block vs advise for a given rule. Cedar assumes one PDP; it has no concept of "which enforcement point evaluates me." - Compliance frameworks as first-class. Buyers query "show me all rules tagged
NIST_AI_RMF:MG-2.6." Cedar can carry that as@annotation, but annotations are opaque strings — no schema, no validation, no searchable field. - Pack container. We ship bundles — slug, version, tier, UI hints, rules array. Cedar policies are individual documents. Pack-as-unit is what customers install, version, and publish.
What we adopted instead of inventing
- JSON Schema 2020-12 as the meta-format — linters, IDEs, and CI validators just work.
- Cedar as a first-class export target. Any pack renders as annotated Cedar text.
- Cedar as an import source. Bring your Verified Permissions policies in, roundtrip them back out unchanged.
- Existing compliance vocabularies verbatim — MITRE ATLAS, OWASP Agentic Top 10, NIST AI RMF, ISO 42001, PCI DSS, SOC 2, HIPAA, GDPR, SR 11-7 — as tag prefixes on the
frameworks[]array. - Namespaced
annotationsmap so metadata from OPA, Kyverno, Cedar, or your own tooling roundtrips through Conduct without loss.
The one-liner
Cedar is the right interchange format. It is not the right authoring format for agentic tool-call policy. The vocabulary mismatch is real, not stylistic — you cannot express warn or inject in Cedar without inventing runtime conventions Cedar itself would consider a bug. So we authored a schema for the shape none of the incumbents target, and made Cedar a peer for portability. No lock-in either direction.
Why two dialects
Different audiences read policies differently. Engineers want grep-friendly JSON that embeds cleanly in pack files. Auditors want human-readable Cedar with recognizable annotations. You want a promise: nothing here is stuck in a proprietary language.
- Runtime evaluates JSON — every pack file under
apps/api/app/modules/guard/skill_packs/*.jsonships as JSON. - Cedar is a view — rendered on demand from the same source. Auditors read it, security teams import policies from Cedar-native tools using it.
- Bidirectional and lossless — round-trip a rule through Cedar and back with no information loss on schema v1.
Same rule, two forms
One PCI-DSS rule from the conduct-pci-dss pack.
JSON (runtime)
{
"id": "pci_pan_guard",
"description": "Block card number patterns (PCI DSS Req 3)",
"match_tool": "edit,write,bash",
"match_pattern": "\\b4[0-9]{12}(?:[0-9]{3})?\\b|\\b5[1-5][0-9]{14}\\b",
"action": "block",
"message": "Card number detected — never log or store PANs in plaintext.",
"severity": "critical",
"frameworks": ["PCI_DSS:3.4", "SOC2:CC6.1", "GDPR:Art32"],
"iso_control": "A.8.11"
}Cedar (portable)
@id("pci_pan_guard")
@description("Block card number patterns (PCI DSS Req 3)")
@message("Card number detected — never log or store PANs in plaintext.")
@severity("critical")
@iso_control("A.8.11")
@compliance("PCI_DSS:3.4", "SOC2:CC6.1", "GDPR:Art32")
forbid (
principal is Agent,
action in [Action::"edit", Action::"write", Action::"bash"],
resource
)
when {
context.prompt matches "\\b4[0-9]{12}(?:[0-9]{3})?\\b|\\b5[1-5][0-9]{14}\\b"
};Get the live Cedar rendering with GET /guard/registry/packs/{slug}/cedar or click ⤓ Cedar on any pack tile at /packs.
Bidirectional interchange
Cedar → Guard
Import Cedar JSON policies from AWS Verified Permissions, Dogwood, or any Cedar-native IAM stack.
POST /guard/registry/import-cedarGuard → Cedar
Render any installed pack as Cedar text — annotated with severity, ISO control, and compliance framework tags.
GET /guard/registry/packs/{slug}/cedarFramework tags
The frameworks array is free-form but conventionally uses these prefixes for compliance-surface parity.
| Prefix | Example | Standard |
|---|---|---|
| PCI_DSS: | PCI_DSS:3.4 | PCI Data Security Standard |
| SOC2: | SOC2:CC6.1 | SOC 2 Trust Services Criteria |
| ISO_27001: | ISO_27001:A.8.24 | ISO 27001 Annex A |
| ISO_42001: | ISO_42001:8.24 | ISO 42001 (AI management) |
| GDPR: | GDPR:Art32 | GDPR article |
| HIPAA: | HIPAA:164.308 | HIPAA Security Rule |
| NIST_AI_RMF: | NIST_AI_RMF:MG-2.6 | NIST AI Risk Management Framework |
| MITRE_ATLAS: | MITRE_ATLAS:AML.T0051 | MITRE ATLAS (adversarial ML) |
| OWASP_AGENTIC: | OWASP_AGENTIC:A01 | OWASP Agentic Top 10 |
| SR_11_7: | SR_11_7:V.A.1 | Fed Reserve model risk |
Mapping to other policy languages
The underlying concepts overlap. What differs is the shape of the object being governed — none of the standards below were designed for agentic tool invocations. This table shows how our fields correspond so teams already using these engines see the alignment.
Conduct (Agentic Tool-Call Policy)
what this schema targets
Agent invokes tool. Rule matches on tool + pattern. Decision fires pre-execution.
- Rule id
- id
- Decision
- action
- Actor
- persona_affinity + match.mcp_tool
- Resource
- match.tool + match.path_pattern
- Match cond.
- match.pattern (regex)
- Severity
- severity enum
- Compliance
- frameworks[] tags
- Metadata
- annotations.<namespace>
- Enforcement pt.
- enforcement.{proxy,hook,mcp,runtime}
OPA/Rego
Generic policy over arbitrary JSON input
- Rule id
- package + rule
- Decision
- deny / allow sets
- Actor
- input.subject
- Resource
- input.resource
- Match cond.
- contains / startswith
- Severity
- annotation
- Compliance
- annotation
- Metadata
- package doc
- Enforcement pt.
- evaluator context
Kyverno
K8s admission control
- Rule id
- policy metadata name
- Decision
- validate.deny / mutate
- Actor
- resource kind
- Resource
- match.resources.kinds
- Match cond.
- match.resources.selector
- Severity
- policy.severity
- Compliance
- policy.categories
- Metadata
- annotations
- Enforcement pt.
- policy webhook
Sentinel
HashiCorp infra provisioning (Terraform)
- Rule id
- policy name
- Decision
- main = rule bool
- Actor
- input.subject
- Resource
- input.resource
- Match cond.
- matches function
- Severity
- metadata
- Compliance
- metadata
- Metadata
- scope description
- Enforcement pt.
- Terraform stage
Cedar
Authorization (who can do what)
- Rule id
- policy id
- Decision
- forbid / permit
- Actor
- principal is <Type>
- Resource
- resource
- Match cond.
- when { ... } clause
- Severity
- @severity annotation
- Compliance
- @compliance annotation
- Metadata
- @<name> annotation
- Enforcement pt.
- authorization boundary
XACML
Enterprise access control (legacy)
- Rule id
- PolicyId
- Decision
- Effect Deny/Permit
- Actor
- Subject
- Resource
- Resource
- Match cond.
- Condition element
- Severity
- Obligation
- Compliance
- Obligation reference
- Metadata
- attributes
- Enforcement pt.
- PDP context
MITRE ATLAS
Adversarial ML threat catalog
- Rule id
- technique ID
- Decision
- control category
- Actor
- technique target
- Resource
- attack surface
- Match cond.
- detection signature
- Severity
- severity rating
- Compliance
- technique IDs
- Metadata
- technique references
- Enforcement pt.
- detection layer
What this is saying: vocabulary overlap is high; problem shape differs. OPA governs arbitrary JSON. Kyverno governs K8s resources. Sentinel governs Terraform plans. Cedar governs authorization requests. Ours governs agentic tool invocations — an object shape none of the above are optimized for.
What we don't try to do: we're not building a general policy engine. Runtime enforcement stays Conduct-specific. What ships is a portable representation — via Cedar for interchange, JSON Schema for tooling, and namespaced annotations for round-tripping foreign metadata.
Extensible (v1.1)
Two optional additions on top of v1. Backward compat: every v1 rule stays valid.
Extensible match map
Put every match dimension under a single map. Custom keys (e.g. mcp_server, webhook_source) allowed; surfaces that don't understand a key ignore it.
{
"id": "block_prod_writes",
"action": "block",
"match": {
"tool": "write,edit,bash",
"pattern": "PROD_SECRET",
"path_pattern": "^prod/config\\.yaml$",
"http_method": "POST",
"mcp_tool": "guard_check"
}
}Namespaced annotations map
Free-form metadata by namespace. Runtime ignores unknown namespaces; exporters pass them through (Cedar → @annotation, OPA → package comments, custom → your own tooling).
{
"id": "block_prod_writes",
"action": "block",
"annotations": {
"cedar": { "principal_type": "Agent" },
"opa": { "package": "conduct.pci" },
"kyverno": { "match_kinds": ["Pod"] },
"custom.acme": "internal-tracker-#1234"
}
}