Skip to content

Advanced Patterns

This page covers patterns that combine multiple Edictum features: nested boolean logic, regex composition, principal claims, template composition, wildcards, dynamic messages, comprehensive contract bundles, per-contract mode overrides, environment-based conditions, and guard merging.


Nested All/Any/Not Logic

Boolean combinators (all, any, not) nest arbitrarily. Use them to build complex access patterns from simple leaves.

When to use: Your access contract cannot be expressed as a single condition. You need AND, OR, and NOT logic combined.

apiVersion: edictum/v1
kind: ContractBundle

metadata:
  name: nested-logic

defaults:
  mode: enforce

contracts:
  - id: complex-deploy-gate
    type: pre
    tool: deploy_service
    when:
      all:
        - environment: { equals: production }
        - any:
            - principal.role: { not_in: [admin, sre] }
            - not:
                principal.ticket_ref: { exists: true }
    then:
      effect: deny
      message: "Production deploy denied. Requires (admin or sre role) AND a ticket reference."
      tags: [access-control, production]
from edictum import Verdict, precondition

@precondition("deploy_service")
def complex_deploy_gate(envelope):
    if envelope.environment != "production":
        return Verdict.pass_()
    # Requires (admin or sre role) AND a ticket reference
    role_ok = envelope.principal and envelope.principal.role in ("admin", "sre")
    has_ticket = envelope.principal and envelope.principal.ticket_ref
    if not role_ok or not has_ticket:
        return Verdict.fail(
            "Production deploy denied. Requires (admin or sre role) "
            "AND a ticket reference."
        )
    return Verdict.pass_()

How to read this: The deploy is denied when the environment is production AND (the role is not admin/sre OR there is no ticket reference). In other words, production deploys require both a privileged role and a ticket.

Gotchas: - Deeply nested trees become hard to read. If your when block exceeds three levels of nesting, consider splitting into multiple contracts with simpler conditions. - not takes a single child expression, not an array. not: [expr1, expr2] is a validation error. - Boolean combinators require at least one child in all and any arrays. An empty array is a validation error.


Regex with matches_any

Combine multiple regex patterns in a single postcondition to detect several categories of sensitive data at once.

When to use: You want one contract to catch multiple data patterns (PII, secrets, regulated content) rather than maintaining separate contracts for each.

apiVersion: edictum/v1
kind: ContractBundle

metadata:
  name: regex-composition

defaults:
  mode: enforce

contracts:
  - id: comprehensive-data-scan
    type: post
    tool: "*"
    when:
      output.text:
        matches_any:
          - '\\b\\d{3}-\\d{2}-\\d{4}\\b'
          - '\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Z|a-z]{2,}\\b'
          - '\\b\\d{4}[\\s-]?\\d{4}[\\s-]?\\d{4}[\\s-]?\\d{4}\\b'
          - '\\b\\d{3}[-.]?\\d{3}[-.]?\\d{4}\\b'
          - 'AKIA[0-9A-Z]{16}'
          - 'eyJ[A-Za-z0-9_-]+\\.eyJ[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+'
    then:
      effect: warn
      message: "Sensitive data pattern detected in output. Redact before using."
      tags: [pii, secrets, compliance]
import re
from edictum import Verdict
from edictum.contracts import postcondition

@postcondition("*")
def comprehensive_data_scan(envelope, tool_response):
    if not isinstance(tool_response, str):
        return Verdict.pass_()
    patterns = [
        r"\b\d{3}-\d{2}-\d{4}\b",                    # SSN
        r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b",  # Email
        r"\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b",  # Credit card
        r"\b\d{3}[-.]?\d{3}[-.]?\d{4}\b",             # Phone
        r"AKIA[0-9A-Z]{16}",                           # AWS key
        r"eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+",  # JWT
    ]
    for pat in patterns:
        if re.search(pat, tool_response):
            return Verdict.fail(
                "Sensitive data pattern detected in output. Redact before using."
            )
    return Verdict.pass_()

Gotchas: - matches_any short-circuits on the first matching pattern. Order patterns from most likely to least likely for performance. - All patterns are compiled at load time. Invalid regex in any element causes a validation error for the entire bundle. - Use single-quoted strings in YAML for regex. Double-quoted strings interpret backslash sequences (\b becomes backspace, \d is literal d).


Principal Claims as Dicts

The principal.claims.<key> selector accesses custom attributes from the Principal.claims dictionary. Claims support any value type: strings, numbers, booleans, and lists.

When to use: Your authorization model needs attributes beyond role, user_id, and org_id. Claims let you attach domain-specific metadata like department, clearance level, or capability entitlements.

apiVersion: edictum/v1
kind: ContractBundle

metadata:
  name: claims-patterns

defaults:
  mode: enforce

contracts:
  - id: require-clearance
    type: pre
    tool: read_file
    when:
      all:
        - args.path: { contains: "classified" }
        - principal.claims.clearance: { not_in: [secret, top-secret] }
    then:
      effect: deny
      message: "Classified file access requires secret or top-secret clearance."
      tags: [access-control, classified]

  - id: entitlement-gate
    type: pre
    tool: send_email
    when:
      not:
        principal.claims.can_send_email: { equals: true }
    then:
      effect: deny
      message: "Email capability is not enabled for this principal."
      tags: [entitlements]

The entitlement-gate contract uses principal claims for identity-based gating -- it checks a per-principal boolean attribute, not a feature flag. This is capability enforcement: the principal either has the entitlement or they don't.

from edictum import Verdict, precondition

@precondition("read_file")
def require_clearance(envelope):
    path = envelope.args.get("path", "")
    if "classified" not in path:
        return Verdict.pass_()
    clearance = (
        envelope.principal.claims.get("clearance")
        if envelope.principal else None
    )
    if clearance not in ("secret", "top-secret"):
        return Verdict.fail(
            "Classified file access requires secret or top-secret clearance."
        )
    return Verdict.pass_()

@precondition("send_email")
def entitlement_gate(envelope):
    enabled = (
        envelope.principal.claims.get("can_send_email")
        if envelope.principal else False
    )
    if not enabled:
        return Verdict.fail("Email capability is not enabled for this principal.")
    return Verdict.pass_()

Setting claims in Python:

from edictum import Principal

principal = Principal(
    user_id="user-123",
    role="analyst",
    claims={
        "clearance": "secret",
        "department": "engineering",
        "feature_flags_email": True,
    },
)

Gotchas: - Claims are set by your application. Edictum does not validate claim values against any external source. - If a claim key does not exist, the leaf evaluates to false. Use principal.claims.<key>: { exists: false } to explicitly require a claim. - Nested claims are supported. Dotted paths like principal.claims.org.team resolve through nested dicts in the Principal.claims dictionary (e.g., claims={"org": {"team": "backend"}}).


Template Composition

Edictum ships built-in templates that you can load directly. Templates are complete YAML bundles that go through the same validation and hashing path as custom bundles.

When to use: You want a ready-made contract bundle for common agent patterns without writing YAML from scratch.

from edictum import Edictum

# Load a built-in template
guard = Edictum.from_template("file-agent")

# Load with overrides
guard = Edictum.from_template(
    "devops-agent",
    environment="staging",
    mode="observe",
)

Available templates:

Template Description
file-agent Blocks sensitive file reads and destructive bash commands
research-agent Rate limits, PII detection, and sensitive file protection
devops-agent Production gates, ticket requirements, PII detection, session limits

To customize a template, copy its YAML source from src/edictum/yaml_engine/templates/ into your project and modify it. Load the customized version with Edictum.from_yaml().


Wildcards

Use tool: "*" to target all tools with a single contract. This is useful for cross-cutting concerns that apply regardless of which tool the agent calls.

When to use: Security scanning (PII, secrets), session limits, or any contract that should apply to every tool.

apiVersion: edictum/v1
kind: ContractBundle

metadata:
  name: wildcard-patterns

defaults:
  mode: enforce

contracts:
  - id: global-pii-scan
    type: post
    tool: "*"
    when:
      output.text:
        matches_any:
          - '\\b\\d{3}-\\d{2}-\\d{4}\\b'
    then:
      effect: warn
      message: "PII detected in {tool.name} output. Redact before using."
      tags: [pii]

  - id: block-all-in-maintenance
    type: pre
    tool: "*"
    when:
      environment: { equals: maintenance }
    then:
      effect: deny
      message: "System is in maintenance mode. All tool calls are denied."
      tags: [maintenance]
import re
from edictum import Verdict, precondition
from edictum.contracts import postcondition

@postcondition("*")
def global_pii_scan(envelope, tool_response):
    if not isinstance(tool_response, str):
        return Verdict.pass_()
    if re.search(r"\b\d{3}-\d{2}-\d{4}\b", tool_response):
        return Verdict.fail(
            f"PII detected in {envelope.tool_name} output. Redact before using."
        )
    return Verdict.pass_()

@precondition("*")
def block_all_in_maintenance(envelope):
    if envelope.environment == "maintenance":
        return Verdict.fail("System is in maintenance mode. All tool calls are denied.")
    return Verdict.pass_()

Gotchas: - Wildcard contracts run on every tool call. In a bundle with many wildcard contracts, each tool call triggers all of them. Keep wildcard contracts lightweight. - If you need a wildcard contract to exclude specific tools, there is no built-in exclusion syntax. Use a not combinator with tool.name: { in: [...] } to skip certain tools.


Dynamic Message Interpolation

Messages support {placeholder} expansion using the same selector paths as the expression grammar. This makes denial messages specific and actionable.

When to use: Always. Generic messages like "Access denied" give the agent no guidance on how to self-correct. Specific messages with interpolated values help the agent understand what went wrong and what to do instead.

apiVersion: edictum/v1
kind: ContractBundle

metadata:
  name: dynamic-messages

defaults:
  mode: enforce

contracts:
  - id: detailed-deny-message
    type: pre
    tool: read_file
    when:
      args.path:
        contains_any: [".env", "credentials", ".pem"]
    then:
      effect: deny
      message: "Cannot read '{args.path}' (user: {principal.user_id}, role: {principal.role}). Skip this file."
      tags: [secrets]

  - id: environment-in-message
    type: pre
    tool: deploy_service
    when:
      all:
        - environment: { equals: production }
        - principal.role: { not_in: [admin, sre] }
    then:
      effect: deny
      message: "Deploy to {environment} denied for role '{principal.role}'. Requires admin or sre."
      tags: [access-control]
from edictum import Verdict, precondition

@precondition("read_file")
def detailed_deny(envelope):
    path = envelope.args.get("path", "")
    for s in (".env", "credentials", ".pem"):
        if s in path:
            user = envelope.principal.user_id if envelope.principal else "unknown"
            role = envelope.principal.role if envelope.principal else "none"
            return Verdict.fail(
                f"Cannot read '{path}' (user: {user}, role: {role}). Skip this file."
            )
    return Verdict.pass_()

@precondition("deploy_service")
def environment_in_message(envelope):
    if envelope.environment != "production":
        return Verdict.pass_()
    if not envelope.principal or envelope.principal.role not in ("admin", "sre"):
        role = envelope.principal.role if envelope.principal else "none"
        return Verdict.fail(
            f"Deploy to {envelope.environment} denied for role '{role}'. "
            "Requires admin or sre."
        )
    return Verdict.pass_()

Available placeholders: - {args.<key>} -- tool argument values - {tool.name} -- the tool being called - {environment} -- the current environment - {principal.user_id}, {principal.role}, {principal.org_id} -- principal fields - {principal.claims.<key>} -- custom claims - {env.<VAR>} -- environment variable values

Gotchas: - If a placeholder references a missing field, it is kept as-is in the output (e.g., {principal.user_id} appears literally if no principal is attached). No error is raised. - Each placeholder expansion is capped at 200 characters. Values longer than 200 characters are truncated. - Messages have a maximum length of 500 characters. Keep messages concise.


Combining Pre + Post + Session

A comprehensive contract bundle combines all three contract types: preconditions block before execution, postconditions warn after execution, and session contracts track cumulative behavior.

When to use: Production agent deployments where you need defense in depth across all three dimensions.

apiVersion: edictum/v1
kind: ContractBundle

metadata:
  name: comprehensive-governance

defaults:
  mode: enforce

contracts:
  # --- Preconditions: block before execution ---
  - id: block-sensitive-reads
    type: pre
    tool: read_file
    when:
      args.path:
        contains_any: [".env", "credentials", ".pem", "id_rsa"]
    then:
      effect: deny
      message: "Sensitive file '{args.path}' denied."
      tags: [secrets, dlp]

  - id: prod-deploy-gate
    type: pre
    tool: deploy_service
    when:
      all:
        - environment: { equals: production }
        - principal.role: { not_in: [admin, sre] }
    then:
      effect: deny
      message: "Production deploys require admin or sre role."
      tags: [access-control, production]

  # --- Postconditions: warn after execution ---
  - id: pii-in-output
    type: post
    tool: "*"
    when:
      output.text:
        matches_any:
          - '\\b\\d{3}-\\d{2}-\\d{4}\\b'
          - '\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Z|a-z]{2,}\\b'
    then:
      effect: warn
      message: "PII detected in output. Redact before using."
      tags: [pii, compliance]

  - id: secrets-in-output
    type: post
    tool: "*"
    when:
      output.text:
        matches_any:
          - 'AKIA[0-9A-Z]{16}'
          - 'eyJ[A-Za-z0-9_-]+\\.eyJ[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+'
    then:
      effect: warn
      message: "Credentials detected in output. Do not log or reproduce."
      tags: [secrets, dlp]

  # --- Session: track cumulative behavior ---
  - id: session-limits
    type: session
    limits:
      max_tool_calls: 50
      max_attempts: 120
      max_calls_per_tool:
        deploy_service: 3
        send_email: 10
    then:
      effect: deny
      message: "Session limit reached. Summarize progress and stop."
      tags: [rate-limit]
from edictum import Edictum, OperationLimits, Verdict, precondition
from edictum.contracts import postcondition
import re

@precondition("read_file")
def block_sensitive_reads(envelope):
    path = envelope.args.get("path", "")
    for s in (".env", "credentials", ".pem", "id_rsa"):
        if s in path:
            return Verdict.fail(f"Sensitive file '{path}' denied.")
    return Verdict.pass_()

@precondition("deploy_service")
def prod_deploy_gate(envelope):
    if envelope.environment != "production":
        return Verdict.pass_()
    if not envelope.principal or envelope.principal.role not in ("admin", "sre"):
        return Verdict.fail("Production deploys require admin or sre role.")
    return Verdict.pass_()

@postcondition("*")
def pii_in_output(envelope, tool_response):
    if not isinstance(tool_response, str):
        return Verdict.pass_()
    patterns = [r"\b\d{3}-\d{2}-\d{4}\b", r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b"]
    for pat in patterns:
        if re.search(pat, tool_response):
            return Verdict.fail("PII detected in output. Redact before using.")
    return Verdict.pass_()

@postcondition("*")
def secrets_in_output(envelope, tool_response):
    if not isinstance(tool_response, str):
        return Verdict.pass_()
    patterns = [r"AKIA[0-9A-Z]{16}", r"eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+"]
    for pat in patterns:
        if re.search(pat, tool_response):
            return Verdict.fail("Credentials detected in output. Do not log or reproduce.")
    return Verdict.pass_()

guard = Edictum(
    contracts=[block_sensitive_reads, prod_deploy_gate, pii_in_output, secrets_in_output],
    limits=OperationLimits(
        max_tool_calls=50,
        max_attempts=120,
        max_calls_per_tool={"deploy_service": 3, "send_email": 10},
    ),
)

Gotchas: - Contract evaluation order within a type follows the array order in the YAML. For preconditions, the first matching deny wins and stops evaluation. - When a precondition denies a call in enforce mode, run() raises EdictumDenied immediately. The tool does not execute and postconditions are not evaluated. Postconditions only run when the tool actually executes. - Session contracts are checked after preconditions, not before. The full pre-execution order is: 1. Attempt limit, 2. Before hooks, 3. Preconditions, 4. Session contracts, 5. Execution limits.


Per-Contract Mode Override

Individual contracts can override the bundle's default mode. This lets you mix enforced and observed contracts in a single bundle.

When to use: You are adding a new contract to an existing production bundle and want to shadow-test it before enforcing.

apiVersion: edictum/v1
kind: ContractBundle

metadata:
  name: mixed-mode-bundle

defaults:
  mode: enforce

contracts:
  # Enforced (inherits bundle default)
  - id: block-sensitive-reads
    type: pre
    tool: read_file
    when:
      args.path:
        contains_any: [".env", "credentials"]
    then:
      effect: deny
      message: "Sensitive file denied."
      tags: [secrets]

  # Observe mode: shadow-testing a new contract
  - id: experimental-query-limit
    type: pre
    mode: observe
    tool: query_database
    when:
      args.query: { matches: '\\bSELECT\\s+\\*\\b' }
    then:
      effect: deny
      message: "SELECT * detected (observe mode). Use explicit column lists."
      tags: [experimental, sql-quality]
from edictum import Edictum, Verdict, precondition

@precondition("read_file")
def block_sensitive_reads(envelope):
    path = envelope.args.get("path", "")
    for s in (".env", "credentials"):
        if s in path:
            return Verdict.fail("Sensitive file denied.")
    return Verdict.pass_()

# In Python, per-contract mode override is done by running
# separate Edictum instances: one enforced, one in observe mode.
enforced_guard = Edictum(contracts=[block_sensitive_reads])

# Or use a single observe-mode instance to shadow-test:
import re

@precondition("query_database")
def experimental_query_limit(envelope):
    query = envelope.args.get("query", "")
    if re.search(r"\bSELECT\s+\*\b", query):
        return Verdict.fail("SELECT * detected. Use explicit column lists.")
    return Verdict.pass_()

observe_guard = Edictum(
    mode="observe",
    contracts=[experimental_query_limit],
)

Gotchas: - Observe mode emits CALL_WOULD_DENY audit events. The tool call proceeds normally. Review these events before switching to enforce. - The mode override is per-contract. Other contracts in the same bundle continue to use the bundle default. - For postconditions, mode: observe downgrades redact/deny effects to a warning prefixed with [observe]. The tool output is not modified. Observe mode is meaningful for all contract types.


Environment-Based Conditions

Use env.* selectors to conditionally activate contracts based on environment variables. The evaluator reads os.environ at evaluation time -- no adapter changes, no envelope modifications, no code changes.

When to use: You want a single YAML file with contracts that activate based on runtime flags like DRY_RUN, ENVIRONMENT, or FEATURE_X_ENABLED. Set the env var, and the contract activates.

apiVersion: edictum/v1
kind: ContractBundle

metadata:
  name: env-conditions

defaults:
  mode: enforce

contracts:
  # Block modifications when DRY_RUN is set
  - id: dry-run-block
    type: pre
    tool: "*"
    when:
      all:
        - env.DRY_RUN: { equals: true }
        - tool.name: { in: [Bash, Write, Edit] }
    then:
      effect: deny
      message: "Dry run mode  modifications denied."
      tags: [dry-run]

  # Block destructive commands in production
  - id: prod-destructive-block
    type: pre
    tool: Bash
    when:
      all:
        - env.ENVIRONMENT: { equals: "production" }
        - args.command: { matches: '\brm\s+(-rf?|--recursive)\b' }
    then:
      effect: deny
      message: "Destructive commands denied in {env.ENVIRONMENT}."
      tags: [destructive, production]
# Activate dry-run mode
DRY_RUN=true python agent.py

# Or set environment
ENVIRONMENT=production python agent.py

Type coercion: Env vars are strings, but the evaluator coerces them automatically:

  • "true" / "false" (case-insensitive) become True / False
  • Numeric strings like "42" or "3.14" become int or float
  • Everything else stays a string

This means env.DRY_RUN: { equals: true } works when DRY_RUN=true is set -- you compare against the boolean true, not the string "true".

Gotchas: - Unset env vars evaluate to false (the contract does not fire). This is consistent with how missing fields behave everywhere in Edictum. - Env vars are read at evaluation time, not load time. If an env var changes mid-process, the next tool call sees the new value. - All 15 operators work with env.* selectors. Use numeric operators (gt, lt, etc.) with coerced numeric env vars. - {env.VAR_NAME} works in message templates for dynamic denial messages.


Bundle Composition (Multi-File)

Edictum.from_yaml() accepts multiple paths, composing bundles left-to-right with deterministic merge semantics. This is the preferred way to combine contracts from multiple YAML files.

When to use: You have separate YAML files for different concerns (base safety, team overrides, environment-specific contracts) and want to compose them into a single guard.

from edictum import Edictum

guard = Edictum.from_yaml(
    "contracts/base.yaml",
    "contracts/team-overrides.yaml",
    "contracts/prod-overrides.yaml",
)

Merge semantics:

  • Contracts with the same id: later layer replaces the entire contract
  • Contracts with unique IDs: concatenated into the final list
  • defaults.mode, limits, observability: later layer wins
  • tools, metadata: deep merge (later keys override)

Use return_report=True to see what was overridden:

guard, report = Edictum.from_yaml(
    "contracts/base.yaml",
    "contracts/overrides.yaml",
    return_report=True,
)

for o in report.overridden_contracts:
    print(f"{o.contract_id}: overridden by {o.overridden_by}")

Shadow-testing with observe_alongside:

A second bundle with observe_alongside: true evaluates as shadow contracts -- audit events are emitted but tool calls are never denied:

guard = Edictum.from_yaml(
    "contracts/current.yaml",      # enforced
    "contracts/candidate.yaml",    # observe_alongside: true → shadow
)

See Bundle Composition for full reference.

Gotchas: - Contract replacement is by id, not position. No partial merging of conditions within a contract. - Single-path from_yaml("file.yaml") is unchanged and backward compatible. - For composing Python-defined guards at runtime, use Edictum.from_multiple().


Guard Merging (Python)

Use Edictum.from_multiple() to combine contracts from multiple instantiated guards into a single guard. This is for runtime merging of Python-defined contracts or conditionally loaded YAML guards.

When to use: You need to combine guards at the Python level -- for example, conditionally adding guards based on runtime state, or mixing YAML-loaded and Python-defined contracts.

import os
from edictum import Edictum

guards = [Edictum.from_yaml("contracts/base.yaml")]

if os.environ.get("DRY_RUN"):
    guards.append(Edictum.from_yaml("contracts/dry-run.yaml"))

guard = Edictum.from_multiple(guards)

Prefer from_yaml(*paths) for YAML composition

If you are combining multiple YAML files, use from_yaml("base.yaml", "overrides.yaml") instead of from_multiple(). Multi-path from_yaml() provides deterministic merge semantics, composition reports, and observe_alongside support. from_multiple() is for cases where you need runtime conditional loading or mixing Python-defined contracts.

Semantics:

  • Contracts are concatenated in order. The first guard's contracts evaluate first.
  • The first guard's audit config, mode, environment, and limits are used as the base.
  • Duplicate contract IDs: first occurrence wins, duplicates skipped with a warning.
  • The returned guard is a new instance. Input guards are not mutated.

Gotchas: - from_multiple([]) raises EdictumConfigError. At least one guard is required. - Hooks (before/after) are not merged -- only contracts (preconditions, postconditions, session contracts). - Duplicate IDs are checked across all contract types. A precondition ID in guard A blocks a postcondition with the same ID in guard B.