edictum 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- edictum-0.4.0/.github/dependabot.yml +6 -0
- edictum-0.4.0/.github/workflows/ci.yml +34 -0
- edictum-0.4.0/.github/workflows/docs.yml +38 -0
- edictum-0.4.0/.github/workflows/pr-title.yml +24 -0
- edictum-0.4.0/.github/workflows/publish.yml +17 -0
- edictum-0.4.0/.gitignore +18 -0
- edictum-0.4.0/.pre-commit-config.yaml +7 -0
- edictum-0.4.0/ARCHITECTURE.md +114 -0
- edictum-0.4.0/LICENSE +21 -0
- edictum-0.4.0/PKG-INFO +180 -0
- edictum-0.4.0/README.md +123 -0
- edictum-0.4.0/REVIEW.md +450 -0
- edictum-0.4.0/docs/adapters/claude-sdk.md +196 -0
- edictum-0.4.0/docs/adapters/crewai.md +118 -0
- edictum-0.4.0/docs/adapters/langchain.md +189 -0
- edictum-0.4.0/docs/adapters/others.md +342 -0
- edictum-0.4.0/docs/adapters/overview.md +144 -0
- edictum-0.4.0/docs/adapters.md +201 -0
- edictum-0.4.0/docs/architecture.md +259 -0
- edictum-0.4.0/docs/audit/sinks.md +397 -0
- edictum-0.4.0/docs/audit/telemetry.md +176 -0
- edictum-0.4.0/docs/cli.md +197 -0
- edictum-0.4.0/docs/compliance.md +116 -0
- edictum-0.4.0/docs/contracts/operators.md +427 -0
- edictum-0.4.0/docs/contracts/templates.md +324 -0
- edictum-0.4.0/docs/contracts/yaml-reference.md +446 -0
- edictum-0.4.0/docs/index.md +87 -0
- edictum-0.4.0/docs/quickstart.md +384 -0
- edictum-0.4.0/examples/README.md +16 -0
- edictum-0.4.0/examples/contract_cookbook.py +1036 -0
- edictum-0.4.0/examples/contracts.py +76 -0
- edictum-0.4.0/examples/demo_agno.py +220 -0
- edictum-0.4.0/examples/demo_claude_agent_sdk.py +217 -0
- edictum-0.4.0/examples/demo_crewai.py +217 -0
- edictum-0.4.0/examples/demo_langchain.py +207 -0
- edictum-0.4.0/examples/demo_openai_agents.py +210 -0
- edictum-0.4.0/examples/demo_semantic_kernel.py +211 -0
- edictum-0.4.0/examples/otel_config.py +20 -0
- edictum-0.4.0/examples/setup.sh +42 -0
- edictum-0.4.0/examples/tools.py +201 -0
- edictum-0.4.0/mkdocs.yml +72 -0
- edictum-0.4.0/pyproject.toml +58 -0
- edictum-0.4.0/schemas/edictum-v1.schema.json +285 -0
- edictum-0.4.0/src/edictum/__init__.py +440 -0
- edictum-0.4.0/src/edictum/adapters/__init__.py +0 -0
- edictum-0.4.0/src/edictum/adapters/agno.py +269 -0
- edictum-0.4.0/src/edictum/adapters/claude_agent_sdk.py +224 -0
- edictum-0.4.0/src/edictum/adapters/crewai.py +237 -0
- edictum-0.4.0/src/edictum/adapters/langchain.py +247 -0
- edictum-0.4.0/src/edictum/adapters/openai_agents.py +266 -0
- edictum-0.4.0/src/edictum/adapters/semantic_kernel.py +239 -0
- edictum-0.4.0/src/edictum/audit.py +218 -0
- edictum-0.4.0/src/edictum/builtins.py +72 -0
- edictum-0.4.0/src/edictum/cli/__init__.py +0 -0
- edictum-0.4.0/src/edictum/cli/main.py +413 -0
- edictum-0.4.0/src/edictum/contracts.py +75 -0
- edictum-0.4.0/src/edictum/envelope.py +240 -0
- edictum-0.4.0/src/edictum/hooks.py +27 -0
- edictum-0.4.0/src/edictum/limits.py +21 -0
- edictum-0.4.0/src/edictum/pipeline.py +278 -0
- edictum-0.4.0/src/edictum/py.typed +0 -0
- edictum-0.4.0/src/edictum/session.py +52 -0
- edictum-0.4.0/src/edictum/sinks/__init__.py +13 -0
- edictum-0.4.0/src/edictum/sinks/_base.py +99 -0
- edictum-0.4.0/src/edictum/sinks/datadog.py +77 -0
- edictum-0.4.0/src/edictum/sinks/splunk.py +68 -0
- edictum-0.4.0/src/edictum/sinks/webhook.py +59 -0
- edictum-0.4.0/src/edictum/storage.py +53 -0
- edictum-0.4.0/src/edictum/telemetry.py +85 -0
- edictum-0.4.0/src/edictum/types.py +25 -0
- edictum-0.4.0/src/edictum/yaml_engine/__init__.py +31 -0
- edictum-0.4.0/src/edictum/yaml_engine/compiler.py +287 -0
- edictum-0.4.0/src/edictum/yaml_engine/edictum-v1.schema.json +285 -0
- edictum-0.4.0/src/edictum/yaml_engine/evaluator.py +303 -0
- edictum-0.4.0/src/edictum/yaml_engine/loader.py +192 -0
- edictum-0.4.0/src/edictum/yaml_engine/templates/devops-agent.yaml +80 -0
- edictum-0.4.0/src/edictum/yaml_engine/templates/file-agent.yaml +45 -0
- edictum-0.4.0/src/edictum/yaml_engine/templates/research-agent.yaml +43 -0
- edictum-0.4.0/tests/__init__.py +0 -0
- edictum-0.4.0/tests/conftest.py +58 -0
- edictum-0.4.0/tests/test_adapter_agno.py +192 -0
- edictum-0.4.0/tests/test_adapter_claude.py +258 -0
- edictum-0.4.0/tests/test_adapter_crewai.py +156 -0
- edictum-0.4.0/tests/test_adapter_langchain.py +193 -0
- edictum-0.4.0/tests/test_adapter_openai_agents.py +154 -0
- edictum-0.4.0/tests/test_adapter_semantic_kernel.py +142 -0
- edictum-0.4.0/tests/test_audit.py +291 -0
- edictum-0.4.0/tests/test_builtins.py +118 -0
- edictum-0.4.0/tests/test_cli/__init__.py +0 -0
- edictum-0.4.0/tests/test_cli/test_cli.py +901 -0
- edictum-0.4.0/tests/test_contract_cookbook.py +759 -0
- edictum-0.4.0/tests/test_contracts.py +80 -0
- edictum-0.4.0/tests/test_envelope.py +172 -0
- edictum-0.4.0/tests/test_hooks.py +41 -0
- edictum-0.4.0/tests/test_integration/__init__.py +0 -0
- edictum-0.4.0/tests/test_integration/test_end_to_end.py +1180 -0
- edictum-0.4.0/tests/test_limits.py +27 -0
- edictum-0.4.0/tests/test_pipeline.py +323 -0
- edictum-0.4.0/tests/test_principal.py +244 -0
- edictum-0.4.0/tests/test_review_fixes.py +633 -0
- edictum-0.4.0/tests/test_session.py +81 -0
- edictum-0.4.0/tests/test_sinks/__init__.py +0 -0
- edictum-0.4.0/tests/test_sinks/test_datadog.py +154 -0
- edictum-0.4.0/tests/test_sinks/test_splunk.py +145 -0
- edictum-0.4.0/tests/test_sinks/test_webhook.py +175 -0
- edictum-0.4.0/tests/test_storage.py +72 -0
- edictum-0.4.0/tests/test_telemetry.py +55 -0
- edictum-0.4.0/tests/test_yaml_engine/__init__.py +0 -0
- edictum-0.4.0/tests/test_yaml_engine/fixtures/invalid_bad_effect.yaml +19 -0
- edictum-0.4.0/tests/test_yaml_engine/fixtures/invalid_bad_regex.yaml +19 -0
- edictum-0.4.0/tests/test_yaml_engine/fixtures/invalid_duplicate_ids.yaml +29 -0
- edictum-0.4.0/tests/test_yaml_engine/fixtures/invalid_empty_contracts.yaml +10 -0
- edictum-0.4.0/tests/test_yaml_engine/fixtures/invalid_missing_apiversion.yaml +18 -0
- edictum-0.4.0/tests/test_yaml_engine/fixtures/invalid_output_text_in_pre.yaml +19 -0
- edictum-0.4.0/tests/test_yaml_engine/fixtures/valid_bundle.yaml +42 -0
- edictum-0.4.0/tests/test_yaml_engine/test_compiler.py +308 -0
- edictum-0.4.0/tests/test_yaml_engine/test_evaluator.py +422 -0
- edictum-0.4.0/tests/test_yaml_engine/test_integration.py +217 -0
- edictum-0.4.0/tests/test_yaml_engine/test_loader.py +118 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
test:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
strategy:
|
|
16
|
+
matrix:
|
|
17
|
+
python-version: ["3.11", "3.12", "3.13"]
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v4
|
|
20
|
+
- uses: actions/setup-python@v5
|
|
21
|
+
with:
|
|
22
|
+
python-version: ${{ matrix.python-version }}
|
|
23
|
+
- run: pip install -e ".[dev,yaml,sinks,cli]"
|
|
24
|
+
- run: pytest tests/ -v
|
|
25
|
+
|
|
26
|
+
lint:
|
|
27
|
+
runs-on: ubuntu-latest
|
|
28
|
+
steps:
|
|
29
|
+
- uses: actions/checkout@v4
|
|
30
|
+
- uses: actions/setup-python@v5
|
|
31
|
+
with:
|
|
32
|
+
python-version: "3.11"
|
|
33
|
+
- run: pip install ruff
|
|
34
|
+
- run: ruff check src/ tests/
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
name: Deploy docs
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
pages: write
|
|
10
|
+
id-token: write
|
|
11
|
+
|
|
12
|
+
concurrency:
|
|
13
|
+
group: pages
|
|
14
|
+
cancel-in-progress: false
|
|
15
|
+
|
|
16
|
+
jobs:
|
|
17
|
+
build:
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@v4
|
|
21
|
+
- uses: actions/setup-python@v5
|
|
22
|
+
with:
|
|
23
|
+
python-version: "3.12"
|
|
24
|
+
- run: pip install mkdocs-material
|
|
25
|
+
- run: mkdocs build --strict
|
|
26
|
+
- uses: actions/upload-pages-artifact@v3
|
|
27
|
+
with:
|
|
28
|
+
path: site
|
|
29
|
+
|
|
30
|
+
deploy:
|
|
31
|
+
needs: build
|
|
32
|
+
runs-on: ubuntu-latest
|
|
33
|
+
environment:
|
|
34
|
+
name: github-pages
|
|
35
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
36
|
+
steps:
|
|
37
|
+
- id: deployment
|
|
38
|
+
uses: actions/deploy-pages@v4
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
name: PR Title
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
types: [opened, edited, synchronize]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
pull-requests: read
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
validate:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
steps:
|
|
14
|
+
- uses: amannn/action-semantic-pull-request@v5
|
|
15
|
+
with:
|
|
16
|
+
types: |
|
|
17
|
+
feat
|
|
18
|
+
fix
|
|
19
|
+
docs
|
|
20
|
+
test
|
|
21
|
+
refactor
|
|
22
|
+
chore
|
|
23
|
+
env:
|
|
24
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
publish:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
environment: pypi
|
|
11
|
+
permissions:
|
|
12
|
+
id-token: write
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
- uses: astral-sh/setup-uv@v5
|
|
16
|
+
- run: uv build
|
|
17
|
+
- run: uv publish
|
edictum-0.4.0/.gitignore
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.pyc
|
|
3
|
+
*.pyo
|
|
4
|
+
*.egg-info/
|
|
5
|
+
dist/
|
|
6
|
+
build/
|
|
7
|
+
site/
|
|
8
|
+
.coverage
|
|
9
|
+
.pytest_cache/
|
|
10
|
+
*.egg
|
|
11
|
+
|
|
12
|
+
# Claude Code (personal config)
|
|
13
|
+
CLAUDE.local
|
|
14
|
+
.claude/
|
|
15
|
+
|
|
16
|
+
# Internal research notes
|
|
17
|
+
docs/ADAPTER_INSIGHTS.md
|
|
18
|
+
docs/adapter-research.md
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# Edictum Architecture
|
|
2
|
+
|
|
3
|
+
> Runtime safety for AI agents. Stop agents before they break things.
|
|
4
|
+
|
|
5
|
+
## What It Does
|
|
6
|
+
|
|
7
|
+
Edictum sits between an agent's decision to call a tool and actual execution. It enforces contracts, hooks, audit trails, and operation limits. When a rule is violated, it tells the agent **why** so it can self-correct.
|
|
8
|
+
|
|
9
|
+
## Package Structure
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
src/edictum/
|
|
13
|
+
├── __init__.py # Edictum class, guard.run(), exceptions, re-exports
|
|
14
|
+
├── envelope.py # ToolEnvelope (frozen), SideEffect, ToolRegistry, BashClassifier
|
|
15
|
+
├── hooks.py # HookDecision (ALLOW/DENY)
|
|
16
|
+
├── contracts.py # Verdict, @precondition, @postcondition, @session_contract
|
|
17
|
+
├── limits.py # OperationLimits (attempt + execution + per-tool caps)
|
|
18
|
+
├── pipeline.py # GovernancePipeline — single source of governance logic
|
|
19
|
+
├── session.py # Session (atomic counters via StorageBackend)
|
|
20
|
+
├── storage.py # StorageBackend protocol + MemoryBackend
|
|
21
|
+
├── audit.py # AuditEvent, RedactionPolicy, Stdout/File sinks
|
|
22
|
+
├── telemetry.py # OpenTelemetry (graceful no-op if absent)
|
|
23
|
+
├── builtins.py # deny_sensitive_reads()
|
|
24
|
+
├── types.py # Internal types (HookRegistration, ToolConfig)
|
|
25
|
+
└── adapters/
|
|
26
|
+
├── langchain.py # LangChain adapter (pre/post tool call hooks)
|
|
27
|
+
├── crewai.py # CrewAI adapter (before/after hooks)
|
|
28
|
+
├── agno.py # Agno adapter (wrap-around hook)
|
|
29
|
+
├── semantic_kernel.py # Semantic Kernel adapter (filter pattern)
|
|
30
|
+
├── openai_agents.py # OpenAI Agents SDK adapter (guardrails)
|
|
31
|
+
└── claude_agent_sdk.py # Claude Agent SDK adapter (hook dict)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## The Flow
|
|
35
|
+
|
|
36
|
+
Every tool call passes through:
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
Agent decides to call tool
|
|
40
|
+
│
|
|
41
|
+
▼
|
|
42
|
+
Adapter creates ToolEnvelope (deep-copied, classified)
|
|
43
|
+
Increments attempt_count (BEFORE governance)
|
|
44
|
+
│
|
|
45
|
+
▼
|
|
46
|
+
Pipeline.pre_execute() — 5 steps:
|
|
47
|
+
1. Attempt limit (>= max_attempts?)
|
|
48
|
+
2. Before hooks (user-defined, can DENY)
|
|
49
|
+
3. Preconditions (contract checks, can DENY)
|
|
50
|
+
4. Session contracts (cross-turn state, can DENY)
|
|
51
|
+
5. Execution limits (>= max_tool_calls? per-tool?)
|
|
52
|
+
│
|
|
53
|
+
├── DENY → audit event → tell agent why → agent self-corrects
|
|
54
|
+
│
|
|
55
|
+
└── ALLOW → tool executes
|
|
56
|
+
│
|
|
57
|
+
▼
|
|
58
|
+
Pipeline.post_execute():
|
|
59
|
+
1. Postconditions (observe-only, warnings)
|
|
60
|
+
2. After hooks
|
|
61
|
+
3. Session record (exec count, consecutive failures)
|
|
62
|
+
│
|
|
63
|
+
▼
|
|
64
|
+
Audit event (CALL_EXECUTED or CALL_FAILED)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Key Design Decisions
|
|
68
|
+
|
|
69
|
+
**Pipeline owns ALL governance logic.** Adapters are thin translation layers. Adding a second adapter doesn't fork governance behavior.
|
|
70
|
+
|
|
71
|
+
**Two counter types:**
|
|
72
|
+
- `max_attempts` — caps ALL PreToolUse events (including denied). Catches denial loops.
|
|
73
|
+
- `max_tool_calls` — caps executions only (PostToolUse). Caps total work done.
|
|
74
|
+
|
|
75
|
+
**Postconditions are observe-only.** They emit warnings, never block. For pure/read tools: suggest retry. For write/irreversible: warn only.
|
|
76
|
+
|
|
77
|
+
**Observe mode** (`mode="observe"`): full pipeline runs, audit emits `CALL_WOULD_DENY`, but tool executes anyway. For shadow deployment.
|
|
78
|
+
|
|
79
|
+
**Zero runtime deps.** OpenTelemetry via optional `edictum[otel]`.
|
|
80
|
+
|
|
81
|
+
**Redaction at write time.** Destructive by design — no recovery. Sensitive keys, secret value patterns (OpenAI/AWS/JWT/GitHub/Slack), 32KB payload cap.
|
|
82
|
+
|
|
83
|
+
**BashClassifier is a heuristic, not a security boundary.** Conservative READ allowlist + shell operator detection. Defense in depth with `deny_sensitive_reads()`.
|
|
84
|
+
|
|
85
|
+
## Usage Modes
|
|
86
|
+
|
|
87
|
+
**1. Framework-agnostic (`guard.run()`):**
|
|
88
|
+
```python
|
|
89
|
+
guard = Edictum(contracts=[deny_sensitive_reads()])
|
|
90
|
+
result = await guard.run("Bash", {"command": "ls"}, my_bash_fn)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**2. Framework adapters (6 supported):**
|
|
94
|
+
|
|
95
|
+
All adapters are thin translation layers — governance logic stays in the pipeline.
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
from edictum.adapters.langchain import LangChainAdapter
|
|
99
|
+
from edictum.adapters.crewai import CrewAIAdapter
|
|
100
|
+
from edictum.adapters.agno import AgnoAdapter
|
|
101
|
+
from edictum.adapters.semantic_kernel import SemanticKernelAdapter
|
|
102
|
+
from edictum.adapters.openai_agents import OpenAIAgentsAdapter
|
|
103
|
+
from edictum.adapters.claude_agent_sdk import ClaudeAgentSDKAdapter
|
|
104
|
+
|
|
105
|
+
adapter = LangChainAdapter(guard, session_id="session-1")
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## What This Is NOT
|
|
109
|
+
|
|
110
|
+
- Not prompt injection defense
|
|
111
|
+
- Not content safety filtering
|
|
112
|
+
- Not network egress control
|
|
113
|
+
- Not a security boundary for Bash
|
|
114
|
+
- Not concurrency-safe across workers (MemoryBackend is single-process)
|
edictum-0.4.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Arnold Cartagena
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
edictum-0.4.0/PKG-INFO
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: edictum
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: Runtime safety for AI agents. Stop agents before they break things.
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Classifier: Development Status :: 3 - Alpha
|
|
8
|
+
Classifier: Intended Audience :: Developers
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
14
|
+
Requires-Python: >=3.11
|
|
15
|
+
Provides-Extra: agno
|
|
16
|
+
Requires-Dist: agno>=1.0; extra == 'agno'
|
|
17
|
+
Provides-Extra: all
|
|
18
|
+
Requires-Dist: agno>=1.0; extra == 'all'
|
|
19
|
+
Requires-Dist: aiohttp>=3.9; extra == 'all'
|
|
20
|
+
Requires-Dist: click>=8.0; extra == 'all'
|
|
21
|
+
Requires-Dist: crewai>=0.80; extra == 'all'
|
|
22
|
+
Requires-Dist: jsonschema>=4.20; extra == 'all'
|
|
23
|
+
Requires-Dist: langchain-core>=0.3; extra == 'all'
|
|
24
|
+
Requires-Dist: openai-agents>=0.1; extra == 'all'
|
|
25
|
+
Requires-Dist: opentelemetry-api>=1.20; extra == 'all'
|
|
26
|
+
Requires-Dist: opentelemetry-sdk>=1.20; extra == 'all'
|
|
27
|
+
Requires-Dist: pyyaml>=6.0; extra == 'all'
|
|
28
|
+
Requires-Dist: rich>=13.0; extra == 'all'
|
|
29
|
+
Requires-Dist: semantic-kernel>=1.0; extra == 'all'
|
|
30
|
+
Provides-Extra: cli
|
|
31
|
+
Requires-Dist: click>=8.0; extra == 'cli'
|
|
32
|
+
Requires-Dist: jsonschema>=4.20; extra == 'cli'
|
|
33
|
+
Requires-Dist: pyyaml>=6.0; extra == 'cli'
|
|
34
|
+
Requires-Dist: rich>=13.0; extra == 'cli'
|
|
35
|
+
Provides-Extra: crewai
|
|
36
|
+
Requires-Dist: crewai>=0.80; extra == 'crewai'
|
|
37
|
+
Provides-Extra: dev
|
|
38
|
+
Requires-Dist: coverage>=7.0; extra == 'dev'
|
|
39
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
40
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
41
|
+
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
42
|
+
Provides-Extra: langchain
|
|
43
|
+
Requires-Dist: langchain-core>=0.3; extra == 'langchain'
|
|
44
|
+
Provides-Extra: openai-agents
|
|
45
|
+
Requires-Dist: openai-agents>=0.1; extra == 'openai-agents'
|
|
46
|
+
Provides-Extra: otel
|
|
47
|
+
Requires-Dist: opentelemetry-api>=1.20; extra == 'otel'
|
|
48
|
+
Requires-Dist: opentelemetry-sdk>=1.20; extra == 'otel'
|
|
49
|
+
Provides-Extra: semantic-kernel
|
|
50
|
+
Requires-Dist: semantic-kernel>=1.0; extra == 'semantic-kernel'
|
|
51
|
+
Provides-Extra: sinks
|
|
52
|
+
Requires-Dist: aiohttp>=3.9; extra == 'sinks'
|
|
53
|
+
Provides-Extra: yaml
|
|
54
|
+
Requires-Dist: jsonschema>=4.20; extra == 'yaml'
|
|
55
|
+
Requires-Dist: pyyaml>=6.0; extra == 'yaml'
|
|
56
|
+
Description-Content-Type: text/markdown
|
|
57
|
+
|
|
58
|
+
# Edictum
|
|
59
|
+
|
|
60
|
+
[](https://pypi.org/project/edictum/)
|
|
61
|
+
[](LICENSE)
|
|
62
|
+
[](https://pypi.org/project/edictum/)
|
|
63
|
+
|
|
64
|
+
**Runtime contracts for AI agents.**
|
|
65
|
+
|
|
66
|
+
AI agents make tool calls. Tool calls have side effects. Nobody governs what happens between "agent decides" and "tool executes." Edictum is that governance layer — preconditions, postconditions, session limits, and a full audit trail, enforced at the point where decision becomes action.
|
|
67
|
+
|
|
68
|
+
## Show Me
|
|
69
|
+
|
|
70
|
+
**contracts.yaml**
|
|
71
|
+
|
|
72
|
+
```yaml
|
|
73
|
+
apiVersion: edictum/v1
|
|
74
|
+
kind: ContractBundle
|
|
75
|
+
|
|
76
|
+
metadata:
|
|
77
|
+
name: my-policy
|
|
78
|
+
|
|
79
|
+
defaults:
|
|
80
|
+
mode: enforce
|
|
81
|
+
|
|
82
|
+
contracts:
|
|
83
|
+
- id: block-sensitive-reads
|
|
84
|
+
type: pre
|
|
85
|
+
tool: read_file
|
|
86
|
+
when:
|
|
87
|
+
args.path:
|
|
88
|
+
contains_any: [".env", ".secret", "credentials", ".pem", "id_rsa"]
|
|
89
|
+
then:
|
|
90
|
+
effect: deny
|
|
91
|
+
message: "Sensitive file '{args.path}' blocked."
|
|
92
|
+
tags: [secrets, dlp]
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**Python**
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
import asyncio
|
|
99
|
+
from edictum import Edictum, EdictumDenied
|
|
100
|
+
|
|
101
|
+
async def main():
|
|
102
|
+
guard = Edictum.from_yaml("contracts.yaml")
|
|
103
|
+
|
|
104
|
+
try:
|
|
105
|
+
result = await guard.run("read_file", {"path": "/app/config.json"}, read_file_fn)
|
|
106
|
+
print(result)
|
|
107
|
+
except EdictumDenied as e:
|
|
108
|
+
print(f"Denied: {e}")
|
|
109
|
+
|
|
110
|
+
asyncio.run(main())
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
**CLI**
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
$ edictum validate contracts.yaml
|
|
117
|
+
✓ contracts.yaml — 1 contract (1 pre)
|
|
118
|
+
|
|
119
|
+
$ edictum check contracts.yaml --tool read_file --args '{"path": ".env"}'
|
|
120
|
+
⛔ DENIED by block-sensitive-reads
|
|
121
|
+
Message: Sensitive file '.env' blocked.
|
|
122
|
+
Tags: secrets, dlp
|
|
123
|
+
Rules evaluated: 1
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**Framework integration (one adapter, same guard)**
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
from edictum.adapters.langchain import EdictumMiddleware
|
|
130
|
+
|
|
131
|
+
middleware = EdictumMiddleware(guard)
|
|
132
|
+
# Wraps any LangChain tool — preconditions, audit, and session limits apply automatically
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Features
|
|
136
|
+
|
|
137
|
+
- **YAML contracts** — Preconditions, postconditions, and session limits declared in version-controlled YAML files
|
|
138
|
+
- **6 framework adapters** — LangChain, CrewAI, Agno, Semantic Kernel, OpenAI Agents SDK, Claude Agent SDK
|
|
139
|
+
- **Audit trail** — Structured JSON events with automatic redaction of secrets (OpenAI keys, AWS creds, JWTs, GitHub tokens)
|
|
140
|
+
- **Observe mode** — Shadow-deploy contracts without blocking; review `CALL_WOULD_DENY` events before enforcing
|
|
141
|
+
- **CLI tooling** — `validate`, `check`, `diff`, and `replay` commands for CI/CD integration
|
|
142
|
+
- **Principal context** — Role, ticket ref, and claims propagated through every decision and audit event
|
|
143
|
+
- **Session limits** — Cap total calls, attempts, and per-tool executions to catch runaway agents
|
|
144
|
+
- **Zero runtime deps** — Pure Python 3.11+. OTel, sinks, and adapters are optional extras
|
|
145
|
+
|
|
146
|
+
## How It Compares
|
|
147
|
+
|
|
148
|
+
| Approach | Scope | Runtime enforcement | Audit trail |
|
|
149
|
+
|---|---|---|---|
|
|
150
|
+
| Prompt/output guardrails | Input/output text | No — advisory only | No |
|
|
151
|
+
| API gateways / MCP proxies | Network transport | Yes — at the proxy | Partial |
|
|
152
|
+
| Security scanners | Post-hoc analysis | No — detection only | Yes |
|
|
153
|
+
| Manual if-statements | Per-tool, ad hoc | Yes — scattered logic | No |
|
|
154
|
+
| **Edictum** | **Tool call contracts** | **Yes — deterministic pipeline** | **Yes — structured + redacted** |
|
|
155
|
+
|
|
156
|
+
## Install
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
pip install edictum # core (zero deps)
|
|
160
|
+
pip install edictum[yaml] # + YAML contract engine
|
|
161
|
+
pip install edictum[sinks] # + webhook, Splunk, Datadog sinks
|
|
162
|
+
pip install edictum[cli] # + validate/check/diff/replay CLI
|
|
163
|
+
pip install edictum[all] # everything
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Built-in Templates
|
|
167
|
+
|
|
168
|
+
```python
|
|
169
|
+
guard = Edictum.from_template("file-agent") # secret file protection, destructive cmd blocking
|
|
170
|
+
guard = Edictum.from_template("research-agent") # output PII detection, session limits
|
|
171
|
+
guard = Edictum.from_template("devops-agent") # role gates, ticket requirements, bash safety
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Links
|
|
175
|
+
|
|
176
|
+
- [Documentation](https://acartag7.github.io/edictum/)
|
|
177
|
+
- [GitHub](https://github.com/acartag7/edictum)
|
|
178
|
+
- [PyPI](https://pypi.org/project/edictum/)
|
|
179
|
+
- [Changelog](CHANGELOG.md)
|
|
180
|
+
- [License](LICENSE) (MIT)
|
edictum-0.4.0/README.md
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# Edictum
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/edictum/)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
[](https://pypi.org/project/edictum/)
|
|
6
|
+
|
|
7
|
+
**Runtime contracts for AI agents.**
|
|
8
|
+
|
|
9
|
+
AI agents make tool calls. Tool calls have side effects. Nobody governs what happens between "agent decides" and "tool executes." Edictum is that governance layer — preconditions, postconditions, session limits, and a full audit trail, enforced at the point where decision becomes action.
|
|
10
|
+
|
|
11
|
+
## Show Me
|
|
12
|
+
|
|
13
|
+
**contracts.yaml**
|
|
14
|
+
|
|
15
|
+
```yaml
|
|
16
|
+
apiVersion: edictum/v1
|
|
17
|
+
kind: ContractBundle
|
|
18
|
+
|
|
19
|
+
metadata:
|
|
20
|
+
name: my-policy
|
|
21
|
+
|
|
22
|
+
defaults:
|
|
23
|
+
mode: enforce
|
|
24
|
+
|
|
25
|
+
contracts:
|
|
26
|
+
- id: block-sensitive-reads
|
|
27
|
+
type: pre
|
|
28
|
+
tool: read_file
|
|
29
|
+
when:
|
|
30
|
+
args.path:
|
|
31
|
+
contains_any: [".env", ".secret", "credentials", ".pem", "id_rsa"]
|
|
32
|
+
then:
|
|
33
|
+
effect: deny
|
|
34
|
+
message: "Sensitive file '{args.path}' blocked."
|
|
35
|
+
tags: [secrets, dlp]
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
**Python**
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
import asyncio
|
|
42
|
+
from edictum import Edictum, EdictumDenied
|
|
43
|
+
|
|
44
|
+
async def main():
|
|
45
|
+
guard = Edictum.from_yaml("contracts.yaml")
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
result = await guard.run("read_file", {"path": "/app/config.json"}, read_file_fn)
|
|
49
|
+
print(result)
|
|
50
|
+
except EdictumDenied as e:
|
|
51
|
+
print(f"Denied: {e}")
|
|
52
|
+
|
|
53
|
+
asyncio.run(main())
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**CLI**
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
$ edictum validate contracts.yaml
|
|
60
|
+
✓ contracts.yaml — 1 contract (1 pre)
|
|
61
|
+
|
|
62
|
+
$ edictum check contracts.yaml --tool read_file --args '{"path": ".env"}'
|
|
63
|
+
⛔ DENIED by block-sensitive-reads
|
|
64
|
+
Message: Sensitive file '.env' blocked.
|
|
65
|
+
Tags: secrets, dlp
|
|
66
|
+
Rules evaluated: 1
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**Framework integration (one adapter, same guard)**
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
from edictum.adapters.langchain import EdictumMiddleware
|
|
73
|
+
|
|
74
|
+
middleware = EdictumMiddleware(guard)
|
|
75
|
+
# Wraps any LangChain tool — preconditions, audit, and session limits apply automatically
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Features
|
|
79
|
+
|
|
80
|
+
- **YAML contracts** — Preconditions, postconditions, and session limits declared in version-controlled YAML files
|
|
81
|
+
- **6 framework adapters** — LangChain, CrewAI, Agno, Semantic Kernel, OpenAI Agents SDK, Claude Agent SDK
|
|
82
|
+
- **Audit trail** — Structured JSON events with automatic redaction of secrets (OpenAI keys, AWS creds, JWTs, GitHub tokens)
|
|
83
|
+
- **Observe mode** — Shadow-deploy contracts without blocking; review `CALL_WOULD_DENY` events before enforcing
|
|
84
|
+
- **CLI tooling** — `validate`, `check`, `diff`, and `replay` commands for CI/CD integration
|
|
85
|
+
- **Principal context** — Role, ticket ref, and claims propagated through every decision and audit event
|
|
86
|
+
- **Session limits** — Cap total calls, attempts, and per-tool executions to catch runaway agents
|
|
87
|
+
- **Zero runtime deps** — Pure Python 3.11+. OTel, sinks, and adapters are optional extras
|
|
88
|
+
|
|
89
|
+
## How It Compares
|
|
90
|
+
|
|
91
|
+
| Approach | Scope | Runtime enforcement | Audit trail |
|
|
92
|
+
|---|---|---|---|
|
|
93
|
+
| Prompt/output guardrails | Input/output text | No — advisory only | No |
|
|
94
|
+
| API gateways / MCP proxies | Network transport | Yes — at the proxy | Partial |
|
|
95
|
+
| Security scanners | Post-hoc analysis | No — detection only | Yes |
|
|
96
|
+
| Manual if-statements | Per-tool, ad hoc | Yes — scattered logic | No |
|
|
97
|
+
| **Edictum** | **Tool call contracts** | **Yes — deterministic pipeline** | **Yes — structured + redacted** |
|
|
98
|
+
|
|
99
|
+
## Install
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
pip install edictum # core (zero deps)
|
|
103
|
+
pip install edictum[yaml] # + YAML contract engine
|
|
104
|
+
pip install edictum[sinks] # + webhook, Splunk, Datadog sinks
|
|
105
|
+
pip install edictum[cli] # + validate/check/diff/replay CLI
|
|
106
|
+
pip install edictum[all] # everything
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Built-in Templates
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
guard = Edictum.from_template("file-agent") # secret file protection, destructive cmd blocking
|
|
113
|
+
guard = Edictum.from_template("research-agent") # output PII detection, session limits
|
|
114
|
+
guard = Edictum.from_template("devops-agent") # role gates, ticket requirements, bash safety
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Links
|
|
118
|
+
|
|
119
|
+
- [Documentation](https://acartag7.github.io/edictum/)
|
|
120
|
+
- [GitHub](https://github.com/acartag7/edictum)
|
|
121
|
+
- [PyPI](https://pypi.org/project/edictum/)
|
|
122
|
+
- [Changelog](CHANGELOG.md)
|
|
123
|
+
- [License](LICENSE) (MIT)
|