YAML Rule Reference
Current `edictum/v1` Ruleset reference.
Right page if: you need the current `kind: Ruleset` shape, rule types, and action semantics. Wrong page if: you want workflow syntax -- see https://docs.edictum.ai/docs/reference/workflows. Gotcha: the current schema is `kind: Ruleset`, top-level `rules:`, and `then.action`. Do not use retired bundle-era fields.
This page covers the current edictum/v1 ruleset shape shared by the Python,
TypeScript, and Go SDKs.
Minimal Ruleset
apiVersion: edictum/v1
kind: Ruleset
metadata:
name: file-safety
defaults:
mode: enforce
rules:
- id: block-dotenv
type: pre
tool: read_file
when:
args.path:
contains: '.env'
then:
action: block
message: 'Sensitive file blocked.'Top-Level Fields
| Field | Required | Notes |
|---|---|---|
apiVersion | Yes | Must be edictum/v1 |
kind | Yes | Must be Ruleset |
metadata.name | Yes | Ruleset identifier |
metadata.description | No | Human-readable description |
defaults.mode | Yes | enforce or observe |
tools | No | Tool side-effect classifications for postconditions |
observe_alongside | No | Observe-test a layered ruleset without changing real decisions |
rules | Yes | Array of pre, post, session, or sandbox rules |
Loading Rulesets
from edictum import Edictum
guard = Edictum.from_yaml("rules.yaml")Multiple files compose left-to-right:
guard = Edictum.from_yaml("rules/base.yaml", "rules/overrides.yaml")String/bytes content uses from_yaml_string().
Every loaded ruleset stamps a policy_version hash into audit events and
telemetry.
Tool Classifications
The optional tools: block controls how postconditions behave after execution.
tools:
read_file:
side_effect: read
write_file:
side_effect: write
deploy:
side_effect: irreversible| Field | Required | Notes |
|---|---|---|
side_effect | Yes | pure, read, write, or irreversible |
idempotent | No | Defaults to false |
Unclassified tools default to irreversible.
Common Rule Fields
| Field | Required | Notes |
|---|---|---|
id | Yes | Unique within one ruleset |
type | Yes | pre, post, session, or sandbox |
enabled | No | Defaults to true |
mode | No | Per-rule override of defaults.mode |
then | Conditional | Used by pre, post, and session rules |
Pre Rules
Pre rules run before the tool executes.
- id: block-sensitive-reads
type: pre
tool: read_file
when:
args.path:
contains_any: ['.env', '.pem', 'id_rsa']
then:
action: block
message: "Sensitive file '{args.path}' blocked."Current pre-rule actions:
blockask
Notes:
toolaccepts an exact tool name, a glob such as"mcp__*", or"*"output.textis invalid intype: preaction: askuses the configured approval backendtimeoutandtimeout_actionare only valid foraction: ask
Post Rules
Post rules run after the tool executes and inspect output.text.
- id: pii-in-output
type: post
tool: "*"
when:
output.text:
matches_any:
- '\b\d{3}-\d{2}-\d{4}\b'
- 'AKIA[0-9A-Z]{16}'
then:
action: redact
message: 'Sensitive data redacted from output.'Current post-rule actions:
warnredactblock
Notes:
redactandblockonly stay enforced forpure/readtools- for
write/irreversibletools, those actions degrade to warnings because the side effect already happened - observe mode always downgrades postcondition enforcement to warning-only behavior
Session Rules
Session rules enforce cumulative limits across a session.
- id: session-limits
type: session
limits:
max_tool_calls: 50
max_attempts: 120
max_calls_per_tool:
deploy: 2
then:
action: block
message: 'Session limit reached.'Session rules:
- do not use
tool - do not use
when - only support
then.action: block
Sandbox Rules
Sandbox rules define allowlists instead of deny-lists.
- id: file-sandbox
type: sandbox
tools: [read_file, write_file, edit_file]
within:
- /workspace
- /tmp
not_within:
- /workspace/.env
outside: block
message: "File access outside workspace: {args.path}"Supported sandbox boundaries:
within/not_withinfor file pathsallows.commandsfor exec toolsallows.domains/not_allows.domainsfor network tools
outside currently supports:
blockask
Selectors
Common selectors:
environmenttool.nameargs.*principal.user_idprincipal.service_idprincipal.org_idprincipal.roleprincipal.ticket_refprincipal.claims.*env.*metadata.*output.text(post rules only)
Missing fields evaluate to false, not to an exception.
Operators
Common operators:
existsequalsnot_equalsinnot_incontainscontains_anystarts_withends_withmatchesmatches_anygtgteltlte
See Operators for full examples.
Action Block
The then block is used by pre, post, and session rules:
then:
action: block
message: 'Explain what happened.'
tags: [security, secrets]| Field | Required | Notes |
|---|---|---|
action | Yes | Constrained by rule type |
message | No | Human-readable message with {placeholder} expansion |
tags | No | String tags copied into audit metadata |
timeout | No | Approval timeout in seconds for action: ask |
timeout_action | No | block or allow for approval timeout |
Allowed actions by rule type:
| Rule type | Actions |
|---|---|
pre | block, ask |
post | warn, redact, block |
session | block |
sandbox | use outside, not then |
Observe Mode
Observe mode logs what would have happened without enforcing it.
defaults:
mode: observeor per rule:
mode: observeIn observe mode:
- matching pre rules emit would-block audit events instead of blocking
- matching post rules emit warnings instead of changing the real tool result
- matching sandbox or session findings are recorded but do not change the real decision
Composition And Inheritance
Multi-file composition
Edictum.from_yaml("base.yaml", "overrides.yaml") composes multiple files
left-to-right.
- later files replace earlier rules by
id - later files win for
defaults observe_alongside: truekeeps a layer as observe-only instead of replacing the enforced rule
extends inheritance
Ruleset inheritance exists as a low-level helper, not as magical auto-resolution in every loader path.
Python:
from edictum.yaml_engine import resolve_ruleset_extends
merged = resolve_ruleset_extends(rulesets, "child")TypeScript:
import { resolveRulesetExtends } from '@edictum/core'
const merged = resolveRulesetExtends(rulesets, 'child')Go:
merged, err := yaml.ResolveExtendsFromRegistry(rulesets, "child")Inheritance semantics:
- parent rules come before child rules
- child metadata/defaults win on conflict
- circular references are rejected
- missing parents are rejected
Validation Notes
The current loaders reject:
- legacy pre-ruleset YAML syntax
- invalid selectors for the rule type
- invalid action names
- duplicate rule IDs
- malformed regex
- malformed sandbox boundaries
Related
Last updated on