Are you an LLM? You can read better optimized documentation at /docs/agent-guard/policies/policy-schema.md for this page in Markdown format
Policy YAML schema
Every Agent Guard policy file conforms to the same top-level shape. This page covers the ToolPolicy schema in full and the smaller set of ArtifactPolicy fields needed for the local-refs use case. For a walkthrough that builds a policy from scratch, see Write your first policy.
Top-level shape
yaml
apiVersion: gerty.jozu.dev/v1
kind: <ToolPolicy | ArtifactPolicy | GuardrailPolicy>
metadata:
name: <string>
spec:
match: <match-block>
action: <Enforce | Elicit | Audit | Redact>
rules:
- name: <string>
assert: |
<CEL expression>
message: <string>Required fields: apiVersion, kind, metadata.name, spec.match, spec.action, spec.rules. Every rule needs a name and an assert. message is optional but strongly recommended because it is what the agent sees on a block.
The assertion convention is the inverse of OPA/Rego deny rules: assert describes the safe case, and the action fires when the assertion is false. See Writing assertions: the convention for the full explanation.
apiVersion
yaml
apiVersion: gerty.jozu.dev/v1The only supported value today. The policy engine rejects anything else. Future versions will be additive and will declare a different value.
kind
The three valid kinds correspond to three evaluation points in the agent lifecycle.
| Kind | Evaluated at | What it controls |
|---|---|---|
ToolPolicy | Every tool invocation | Which tools the agent can call and what arguments those calls can have |
ArtifactPolicy | Artifact admission (when an OCI artifact is pulled) | Which agents, MCP servers, skills, and policies can load into the runtime |
GuardrailPolicy | Inference request and response | Content of prompts and completions: PII, toxicity, prompt injection patterns |
See How policies work for the conceptual overview.
ToolPolicy is the kind you will write directly. ArtifactPolicy comes into play for the local-refs case below. GuardrailPolicy is consumed through pre-built tiers in v1; this page does not cover its schema.
metadata
yaml
metadata:
name: no-force-push| Field | Required | What it does |
|---|---|---|
name | yes | Identifies the policy in logs and audit output. Pick something descriptive (no-force-push rather than policy-1). |
ToolPolicy spec
yaml
spec:
match:
tool:
names:
- "Bash"
- "Write"
action: Enforce
rules:
- name: no-force-push
assert: |
!tool.arguments.command.contains("git push --force") &&
!tool.arguments.command.contains("git push -f")
message: "Force push is prohibited by security policy"spec.match.tool.names
A list of tool names the policy applies to. Both built-in agent tools (Bash, Read, Write, Edit, WebFetch) and MCP-exposed tools are valid here. If the match list does not include the tool that fired, the rules are skipped entirely.
spec.action
What to do when any rule's assertion returns false.
| Action | Behavior |
|---|---|
Enforce | Block the operation. Return an error to the agent. |
Elicit | Pause and ask the user for confirmation. See Elicit support below. |
Audit | Allow the operation, but write a record to the audit log. |
A single policy has one action. If you need different actions for different cases, write multiple policies.
spec.rules
A list of named rules. Every rule is evaluated; if any rule's assert returns false, the policy's action fires.
| Field | Required | What it does |
|---|---|---|
name | yes | Identifies the rule in audit output. The audit record captures which rule fired. |
assert | yes | CEL expression that must return true for the rule to pass. |
message | no | Human-readable string returned to the agent on a block. Write it as instructions for an audience that is half-human, half-agent. |
CEL evaluation context (ToolPolicy)
CEL assertions for ToolPolicy have access to the tool call's arguments through tool.arguments.
Common argument shapes:
| Tool | Arguments |
|---|---|
Bash | command (string) |
Read | file_path (string), offset (int, optional), limit (int, optional) |
Write | file_path (string), content (string) |
Edit | file_path (string), old_string (string), new_string (string) |
WebFetch | url (string) |
CEL helpers and gotchas
orValue for missing arguments
Not every tool call carries every argument. tool.arguments.command is set for Bash calls but not for Read calls. Calling .contains() on a missing field throws an evaluation error, which the policy engine treats as fail-closed and blocks the call.
Use orValue("") to default missing fields to an empty string:
yaml
assert: |
!tool.arguments.command.orValue("").contains("rm -rf") &&
!tool.arguments.file_path.orValue("").contains("/etc/")This pattern lets one assertion cover multiple tool types without throwing.
String methods
CEL strings expose .contains(substring), .startsWith(prefix), .endsWith(suffix), .matches(regex), .size(), and standard concatenation with +.
.contains is substring matching, not regex. tool.arguments.command.contains("curl") matches both curl https://example.com and concurly. For host-anchored matching use .matches(regex):
yaml
assert: |
!tool.arguments.command.matches("\\bcurl\\b")Booleans in assert
The assert field is parsed as a CEL expression, but YAML parses the bare values true and false as YAML booleans. To write an always-failing assertion (for example, an Audit-everything rule), quote the literal so the engine parses it as CEL:
yaml
rules:
- name: audit-all
assert: "false"
message: "audit-everything"Path traversal
Substring checks against paths do not normalize. A literal-string check against /etc/passwd does not block /etc/../etc/passwd. If the policy is security-critical, block any path containing ..:
yaml
assert: |
!tool.arguments.file_path.orValue("").contains("..") &&
!tool.arguments.file_path.orValue("").startsWith("/etc/")Combining assertions
Rules within a policy are AND-ed: every rule must pass. To express OR semantics within a single rule, use CEL's || operator inside one assertion. To express OR semantics across rules, write the rules as a single combined assertion.
ArtifactPolicy for local refs
By default, Agent Guard only trusts artifacts pulled from jozu.ml. To use local kit refs (artifacts you packed with kit pack and have not pushed to a registry), load an ArtifactPolicy that matches on the local registry sentinel.
yaml
apiVersion: gerty.jozu.dev/v1
kind: ArtifactPolicy
metadata:
name: allow-local-refs
spec:
match:
artifact:
kinds:
- "agent"
- "policy"
action: Audit
rules:
- name: allow-localhost
assert: |
artifact.registry == "localhost"
message: "Local refs are allowed for development"artifact.registry is "localhost" for any ref resolved from the kitops on-disk store, including refs written as localhost/foo:v1 or short refs without a host (my-agent:v1). See the README's "Local refs" section for the full set of resolution rules.
This is the only ArtifactPolicy use case covered in v1. The full ArtifactPolicy schema will be documented in a later pass.
File layout
Policy files live under ~/.agentguard/policies/ in a structured layout. Agent Guard manages this layout when you use agentguard policy add, but you can also drop YAML files directly into the right location for fast iteration.
~/.agentguard/policies/
├── global/
│ └── <sanitized-ref>/
│ └── <any>.yaml
└── workspaces/
└── <sha256-of-workspace-path>/
└── <sanitized-ref>/
└── <any>.yamlA single source directory can hold multiple YAML files. Every file is loaded and evaluated.
Versioning
apiVersion: gerty.jozu.dev/v1 is the current schema. Future schema changes will use a new API version (v1alpha2, v2) and the engine will support both during a deprecation window. Mixing API versions within a single source directory is allowed.
Elicit support
Elicit returns a "confirmation required" message back to the agent's hook. The intent is that the hook surfaces the prompt to the user and waits for approval. Whether that surfacing actually happens depends on the agent.
Today, agent hook support for elicit is incomplete. Without hook support, elicit behaves like a hard block. Track progress at issue #116.
If you need confirmation prompts to work end-to-end today, write the rule as Enforce and explain the alternative in the message.
