Tutorial: Writing Rules
Turn a plain-English requirement into a ruleset, validate it, test it, and roll it out safely.
Right page if: you are turning a requirement into a working `rules.yaml` file for the first time. Wrong page if: you already have a ruleset and just need tests -- see https://docs.edictum.ai/docs/guides/testing-rules. For ordered process enforcement, see https://docs.edictum.ai/docs/guides/workflow-gates. Gotcha: YAML double-quoted regex strings treat \b as backspace. Use single quotes for regex. Missing principal fields evaluate to false, so role-based rules silently stop matching.
This is the shortest reliable workflow for authoring rules: write the requirement, translate it to YAML, validate it, dry-run it, ship it in observe mode, then enforce it.
Step 1: Start With a Requirement
Suppose your team has this requirement:
Analysts should not be able to read secret files like
.env,.pem, or credential files.
This is a check rule. You want to block the tool call before it executes.
Step 2: Translate It To YAML
Create a file called rules.yaml with a complete Ruleset:
apiVersion: edictum/v1
kind: Ruleset
metadata:
name: analyst-file-policy
description: "Block analysts from reading secret files."
defaults:
mode: observe
rules:
- id: block-secret-reads
type: pre
tool: read_file
when:
all:
- args.path:
contains_any: [".env", ".secret", "credentials", ".pem", "id_rsa"]
- principal.role:
equals: analyst
then:
action: block
message: "Analysts cannot read '{args.path}'. Ask an admin for help."
tags: [secrets, dlp]Why this works:
type: premeans check before execution.tool: read_filescopes the rule to one tool.allmeans both conditions must match.action: blockblocks the call. Check rules also supportaction: ask.mode: observelets you verify the rule before enforcing it.
Step 3: Validate The Ruleset
Run the validator before you ship anything:
edictum validate rules.yamlValidation catches schema problems like duplicate IDs, bad action names, invalid selectors, and malformed regex.
Step 4: Dry-Run It
Spot-check the rule without executing the tool:
edictum check rules.yaml \
--tool read_file \
--args '{"path": ".env"}' \
--principal-role analystOr do the same in code:
from edictum import Edictum, Principal
guard = Edictum.from_yaml("rules.yaml")
result = guard.evaluate(
"read_file",
{"path": ".env"},
principal=Principal(user_id="alice", role="analyst"),
)
assert result.decision == "block"
assert result.block_reasons[0] == "Analysts cannot read '.env'. Ask an admin for help."Make sure the allowed case still passes:
allowed = guard.evaluate(
"read_file",
{"path": "readme.txt"},
principal=Principal(user_id="alice", role="analyst"),
)
assert allowed.decision == "allow"Step 5: Ship In Observe Mode
Notice that defaults.mode is set to observe. In this mode, Edictum logs what would be blocked without actually blocking anything. This is the safe rollout path.
from edictum import Edictum, Principal
from edictum.adapters.langchain import LangChainAdapter
guard = Edictum.from_yaml("rules.yaml")
adapter = LangChainAdapter(
guard=guard,
principal=Principal(user_id="alice", role="analyst"),
)
middleware = adapter.as_middleware()Step 6: Review The Decision Log
In observe mode, calls that would have been blocked still run, but Edictum records the event. Review those events before switching to enforce:
{
"action": "call_would_block",
"tool_name": "read_file",
"decision_name": "block-secret-reads",
"tool_args": {"path": ".env"},
"principal": {"user_id": "alice", "role": "analyst"},
"reason": "Analysts cannot read '.env'. Ask an admin for help."
}Check for:
- False positives: legitimate calls that would be blocked.
- False negatives: calls that should be blocked but are not.
- Missing principal fields: if
principal.roleis null, the rule never matches.
Step 7: Flip To Enforce
Once the observed traffic looks right, switch to enforce:
defaults:
mode: enforceNow the tool call is blocked for real.
Common Mistakes
Wrong operator for the shape of the data
Using equals when you need contains:
# Wrong -- only matches if the entire path is literally ".env"
args.path:
equals: ".env"
# Right -- matches any path containing ".env"
args.path:
contains: ".env"Missing principal field
If the principal does not have a role field set, selectors like principal.role resolve to null. That makes the rule stop matching.
Fix: ensure the principal is populated when creating the adapter:
principal = Principal(user_id="alice", role="analyst")
adapter = LangChainAdapter(guard=guard, principal=principal)Regex escaping in YAML
YAML double-quoted strings interpret escape sequences. "\b" is a backspace character, not a word boundary. Always use single quotes for regex:
# Wrong -- "\b" is backspace
args.command:
matches: "\brm\b"
# Right -- '\b' is literal backslash-b (word boundary)
args.command:
matches: '\brm\b'Using output.text in a check rule
output.text only exists in check_output rules. Using it in type: pre is a load-time validation error:
# Wrong -- output.text does not exist before the tool runs
- id: bad-pre
type: pre
tool: read_file
when:
output.text:
contains: "SECRET"
then:
action: block
message: "..."Picking the wrong output action
Check output rules support three actions:
| Action | What happens |
|---|---|
warn | Emit a violation. Output passes through unchanged. |
redact | Replace regex-matched patterns in the output with [REDACTED]. |
block | Replace the entire tool output with [OUTPUT SUPPRESSED] .... |
- id: redact-ssn
type: post
tool: "*"
when:
output.text:
matches: '\b\d{3}-\d{2}-\d{4}\b'
then:
action: redact
message: "SSN pattern detected in output."Use warn when you want a violation only, redact when cleanup is safe, and block when any matching output should be suppressed.
When To Reach For Sandbox Rules
If the deny-list keeps growing, stop adding more check rules and move the boundary into a sandbox rule.
Example: File Path Sandbox
Requirement: the agent should only read/write files in /workspace and /tmp, never in /workspace/.git.
- id: file-sandbox
type: sandbox
tools: [read_file, write_file, edit_file]
within:
- /workspace
- /tmp
not_within:
- /workspace/.git
outside: block
message: "File access outside workspace: {args.path}"Use sandbox rules for files, shell commands, and domains. See sandbox rules for the full model.
Next Steps
Last updated on