Edictum

Quickstart

Install Edictum, write a ruleset, and block a dangerous tool call in under a minute.

AI Assistance

Right page if: you want to install Edictum, write your first ruleset, and block a tool call in Python, TypeScript, or Go. Wrong page if: you need the full schema reference -- see https://docs.edictum.ai/docs/rulesets/yaml-reference. For ordered process enforcement, see https://docs.edictum.ai/docs/guides/workflow-gates. Gotcha: Python needs `pip install edictum[yaml]` for YAML rulesets. TypeScript and Go ship YAML support by default.

Agents can refuse in text and still call the tool. Edictum gives you rules that block the tool call at the decision-to-action seam.

This page shows the shortest path: install Edictum, write rules.yaml, and see .env access get blocked.

Install

pip install edictum[yaml]
npm install @edictum/core
go get github.com/edictum-ai/edictum-go

Write A Ruleset

Save this as rules.yaml:

apiVersion: edictum/v1
kind: Ruleset
metadata:
  name: my-first-ruleset
defaults:
  mode: enforce
rules:
  - id: block-dotenv
    type: pre
    tool: read_file
    when:
      args.path: { contains: ".env" }
    then:
      action: block
      message: "Read of sensitive file blocked: {args.path}"

This is a single check rule. If the agent calls read_file with a path containing .env, the call is blocked before the tool runs.

Dry-Run It

from edictum import Edictum

guard = Edictum.from_yaml("rules.yaml")
result = guard.evaluate("read_file", {"path": ".env"})

print(result.decision)          # "block"
print(result.block_reasons[0])  # "Read of sensitive file blocked: .env"
import { Edictum } from '@edictum/core'

const guard = Edictum.fromYaml('rules.yaml')
const result = await guard.evaluate('read_file', { path: '.env' })

console.log(result.decision)        // "deny"
console.log(result.denyReasons[0])  // "Read of sensitive file blocked: .env"
package main

import (
	"context"
	"fmt"

	"github.com/edictum-ai/edictum-go/guard"
)

func main() {
	g, _ := guard.FromYAML("rules.yaml")
	result := g.Evaluate(context.Background(), "read_file", map[string]any{"path": ".env"})

	fmt.Println(result.Decision)         // "block"
	fmt.Println(result.BlockReasons[0])  // "Read of sensitive file blocked: .env"
}

The tool never runs in dry-run mode. You just get the decision and the matching rules.

Run It For Real

import asyncio
from edictum import Edictum, EdictumDenied

guard = Edictum.from_yaml("rules.yaml")

async def read_file(path: str) -> str:
    return f"contents of {path}"

async def main() -> None:
    print(await guard.run("read_file", {"path": "README.md"}, read_file))

    try:
        await guard.run("read_file", {"path": ".env"}, read_file)
    except EdictumDenied as exc:
        print(exc.reason)

asyncio.run(main())
import { Edictum, EdictumDenied } from '@edictum/core'

const guard = Edictum.fromYaml('rules.yaml')

async function readFile(args: Record<string, unknown>) {
  return `contents of ${args.path}`
}

console.log(await guard.run('read_file', { path: 'README.md' }, readFile))

try {
  await guard.run('read_file', { path: '.env' }, readFile)
} catch (error) {
  if (error instanceof EdictumDenied) {
    console.log(error.reason)
  }
}
package main

import (
	"context"
	"errors"
	"fmt"

	edictum "github.com/edictum-ai/edictum-go"
	"github.com/edictum-ai/edictum-go/guard"
)

func main() {
	g, _ := guard.FromYAML("rules.yaml")
	ctx := context.Background()

	readFile := func(args map[string]any) (any, error) {
		return fmt.Sprintf("contents of %s", args["path"]), nil
	}

	_, _ = g.Run(ctx, "read_file", map[string]any{"path": "README.md"}, readFile)

	_, err := g.Run(ctx, "read_file", map[string]any{"path": ".env"}, readFile)
	var blocked *edictum.DeniedError
	if errors.As(err, &blocked) {
		fmt.Println(blocked.Reason)
	}
}

Add More Rules

One rule handles one behavior. Real agents usually need a few layers: input checks, sandbox boundaries, and session limits.

apiVersion: edictum/v1
kind: Ruleset
metadata:
  name: agent-safety
defaults:
  mode: enforce
rules:
  - id: block-dotenv
    type: pre
    tool: read_file
    when:
      args.path: { contains: ".env" }
    then:
      action: block
      message: "Read of sensitive file blocked: {args.path}"

  - id: workspace-sandbox
    type: sandbox
    tools: [read_file, write_file]
    within:
      - /workspace
      - /tmp
    not_within:
      - /workspace/.git
    outside: block
    message: "File access outside workspace: {args.path}"

  - id: session-limits
    type: session
    limits:
      max_tool_calls: 100
      max_calls_per_tool:
        bash: 20
    then:
      action: block
      message: "Session limit reached. Summarize progress and stop."

Observe Before You Enforce

Not ready to block production traffic yet? Change one line:

defaults:
  mode: observe

Calls that would be blocked are logged to the decision log as observed events, but the tool still runs. That gives you live traffic before you flip back to enforce.

Need Ordered Process Instead Of Single-Call Rules?

Rules answer "should this tool call happen?" Workflow Gates answer "is the agent at the right stage yet?"

Use Workflow Gates when you need:

  • required reads before edits
  • command evidence like git diff
  • exit checks like exec("pnpm test", exit_code=0)
  • approval pauses before the next stage

See Workflow Gates Runtime.

Next Steps

Last updated on

On this page