Metadata-Version: 2.4
Name: edictum
Version: 0.9.0
Summary: Runtime safety for AI agents. Stop agents before they break things.
Project-URL: Homepage, https://docs.edictum.dev
Project-URL: Documentation, https://docs.edictum.dev
Project-URL: Repository, https://github.com/acartag7/edictum
Project-URL: Changelog, https://github.com/acartag7/edictum/releases
Author-email: Arnold Cartagena <cartagena.arnold@gmail.com>
License-Expression: MIT
License-File: LICENSE
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries
Requires-Python: >=3.11
Provides-Extra: agno
Requires-Dist: agno>=1.0; extra == 'agno'
Provides-Extra: all
Requires-Dist: agno>=1.0; extra == 'all'
Requires-Dist: click>=8.0; extra == 'all'
Requires-Dist: crewai>=0.80; extra == 'all'
Requires-Dist: jsonschema>=4.20; extra == 'all'
Requires-Dist: langchain-core>=0.3; extra == 'all'
Requires-Dist: openai-agents>=0.1; extra == 'all'
Requires-Dist: opentelemetry-api>=1.20.0; extra == 'all'
Requires-Dist: opentelemetry-exporter-otlp>=1.20.0; extra == 'all'
Requires-Dist: opentelemetry-sdk>=1.20.0; extra == 'all'
Requires-Dist: pyyaml>=6.0; extra == 'all'
Requires-Dist: rich>=13.0; extra == 'all'
Requires-Dist: semantic-kernel>=1.0; extra == 'all'
Provides-Extra: cli
Requires-Dist: click>=8.0; extra == 'cli'
Requires-Dist: jsonschema>=4.20; extra == 'cli'
Requires-Dist: pyyaml>=6.0; extra == 'cli'
Requires-Dist: rich>=13.0; extra == 'cli'
Provides-Extra: crewai
Requires-Dist: crewai>=0.80; extra == 'crewai'
Provides-Extra: dev
Requires-Dist: coverage>=7.0; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.4; extra == 'dev'
Provides-Extra: langchain
Requires-Dist: langchain-core>=0.3; extra == 'langchain'
Provides-Extra: openai-agents
Requires-Dist: openai-agents>=0.1; extra == 'openai-agents'
Provides-Extra: otel
Requires-Dist: opentelemetry-api>=1.20.0; extra == 'otel'
Requires-Dist: opentelemetry-exporter-otlp>=1.20.0; extra == 'otel'
Requires-Dist: opentelemetry-sdk>=1.20.0; extra == 'otel'
Provides-Extra: semantic-kernel
Requires-Dist: semantic-kernel>=1.0; extra == 'semantic-kernel'
Provides-Extra: yaml
Requires-Dist: jsonschema>=4.20; extra == 'yaml'
Requires-Dist: pyyaml>=6.0; extra == 'yaml'
Description-Content-Type: text/markdown

# Edictum

[![PyPI](https://img.shields.io/pypi/v/edictum?cacheSeconds=3600)](https://pypi.org/project/edictum/)
[![License](https://img.shields.io/pypi/l/edictum?cacheSeconds=86400)](LICENSE)
[![Python](https://img.shields.io/pypi/pyversions/edictum?cacheSeconds=86400)](https://pypi.org/project/edictum/)

Runtime contract enforcement for AI agent tool calls.

Prompts are suggestions. Contracts are enforcement.
The LLM cannot talk its way past a contract.

## Why This Exists

An agent says "I won't read sensitive files" -- then calls `read_file(".env")` and leaks your API keys.

A DevOps agent recognizes a jailbreak attempt, writes "I should NOT comply" in its reasoning -- then reads four production database credentials in the next tool call.

A medical assistant promises to respect patient privacy -- then dumps an entire clinical record into a chat response because nothing actually stops it.

Prompt engineering doesn't fix this. You need enforcement at the tool-call layer.

## Without Edictum / With Edictum

**Without Edictum** -- the agent reads your secrets:

```python
# Agent decides to read .env
result = await read_file(".env")
# => "OPENAI_API_KEY=sk-abc123..."
```

**With Edictum** -- the call is denied before it executes:

```python
from edictum import Edictum, EdictumDenied

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

try:
    result = await guard.run("read_file", {"path": ".env"}, read_file)
except EdictumDenied as e:
    print(e.reason)
    # => "Sensitive file '.env' denied."
```

**The contract that makes it happen** -- `contracts.yaml`:

```yaml
apiVersion: edictum/v1
kind: ContractBundle
metadata:
  name: file-safety
defaults:
  mode: enforce
contracts:
  - id: block-sensitive-reads
    type: pre
    tool: read_file
    when:
      args.path:
        contains_any: [".env", ".secret", "credentials", ".pem", "id_rsa"]
    then:
      effect: deny
      message: "Sensitive file '{args.path}' denied."
```

Contracts are YAML. Enforcement is deterministic. The agent cannot bypass it.

## How It Compares

| Approach | Scope | Runtime enforcement | Audit trail |
|---|---|---|---|
| Prompt/output guardrails | Input/output text | No -- advisory only | No |
| API gateways / MCP proxies | Network transport | Yes -- at the proxy | Partial |
| Security scanners | Post-hoc analysis | No -- detection only | Yes |
| Manual if-statements | Per-tool, ad hoc | Yes -- scattered logic | No |
| **Edictum** | **Tool call contracts** | **Yes -- deterministic pipeline** | **Yes -- structured + redacted** |

## Quick Start

One line with a built-in template:

```python
guard = Edictum.from_template("file-agent")  # secret protection, destructive command denial
```

Or write your own contracts in YAML:

```python
guard = Edictum.from_yaml("contracts.yaml")
```

See the [quickstart guide](https://docs.edictum.dev/quickstart/) to write your first contract and deny a dangerous call in five minutes.

## What You Can Do

**Write and enforce contracts**

- Preconditions deny dangerous calls before execution
- Postconditions scan tool output -- warn, redact PII, or deny entirely
- Session contracts cap total calls, per-tool calls, and retry attempts
- Role-gate tools with `principal` claims and `env.*` environment context
- `on_deny` / `on_allow` callbacks for deny logging, alerting, or approval workflows
- `set_principal()` for mid-session role changes (analyst -> operator escalation)

**Test and validate**

- `edictum validate` -- catch schema errors at load time, not runtime
- `edictum test` -- YAML test cases with expected verdicts
- `guard.evaluate()` -- dry-run contracts without executing the tool
- CI/CD exit codes for pipeline gating

**Ship safely**

- Observe mode -- log what would be denied, tune contracts, then enforce
- `observe_alongside` -- shadow-test new contracts next to production
- `edictum diff` -- see what changed between contract bundle versions
- `edictum replay` -- replay audit logs against updated contracts to predict drift
- Multi-file composition with `from_yaml()` and deterministic merge
- Custom YAML operators and selectors for domain-specific contract logic

**Automatic security (zero config)**

- Secret values auto-redacted in audit events and denial messages
- Bash commands sanitized (passwords, tokens, connection strings)
- Regex inputs capped to prevent ReDoS
- Audit payloads capped at 32KB
- All contract errors fail closed -- never silently pass

**Observe everything**

- Structured audit events on every evaluation
- OpenTelemetry spans and metrics
- Policy version hash on every event
- File and stdout sinks included; `CompositeSink` fans out to multiple destinations
- `--json` CLI output for CI/CD pipeline integration

**Works with 6 frameworks**

| Framework | Integration | PII Redaction | Complexity |
|-----------|------------|---------------|------------|
| LangChain + LangGraph | `as_tool_wrapper()` | Full interception | Low |
| OpenAI Agents SDK | `as_guardrails()` | Logged only | Medium |
| Agno | `as_tool_hook()` | Full interception | Low |
| Semantic Kernel | `register()` | Full interception | Medium-High |
| CrewAI | `register()` | Partial | High |
| Claude Agent SDK | `to_hook_callables()` | Logged only | Low |

See [Adapter Docs](https://docs.edictum.dev/adapters/overview/) for setup, known limitations, and recommendations.

## Use Cases

| Domain | What Edictum enforces |
|--------|----------------------|
| Coding agents | Secret protection, destructive command denial, write scope |
| Healthcare | Patient data access control, role-gated queries |
| Finance | PII redaction in query results, transaction limits |
| DevOps | Production deploy gates, ticket requirements, bash safety |
| Education | Student data protection, session limits per assignment |
| Legal | Privileged document access, audit trail for compliance |

See [Use Cases](https://docs.edictum.dev/use-cases/) for complete YAML bundles and wiring code.

## Install

Requires Python 3.11+. Current version: **v0.9.0**.

```bash
pip install edictum              # core (zero deps)
pip install edictum[yaml]        # + YAML contract parsing
pip install edictum[otel]        # + OpenTelemetry span emission
pip install edictum[cli]         # + validate/check/diff/replay CLI
pip install edictum[all]         # everything
```

## Built-in Templates

```python
guard = Edictum.from_template("file-agent")      # secret file protection, destructive cmd denial
guard = Edictum.from_template("research-agent")   # output PII detection, session limits
guard = Edictum.from_template("devops-agent")     # role gates, ticket requirements, bash safety
```

## Demos & Examples

- **[edictum-demo](https://github.com/acartag7/edictum-demo)** -- Full scenario demos, adversarial tests, benchmarks, and Grafana observability
- **[Contract Patterns](https://docs.edictum.dev/contracts/patterns/)** -- Real-world contract recipes by concern
- **[Framework Adapters](https://docs.edictum.dev/adapters/overview/)** -- Integration guides for six frameworks

## Research

Edictum was evaluated across six regulated domains in the GAP benchmark.

[Paper](https://arxiv.org/abs/2602.16943) · [Benchmark](https://github.com/acartag7/gap-benchmark)

## Links

- [Documentation](https://docs.edictum.dev/)
- [GitHub](https://github.com/acartag7/edictum)
- [PyPI](https://pypi.org/project/edictum/)
- [Changelog](CHANGELOG.md)
- [License](LICENSE) (MIT)
