Canopy Agent Safety Runtime: policy enforcement for tool-using agents
Project description
Canopy Runtime
Deterministic governance layer for autonomous AI agents.
Want the fastest possible first impression? Open the live demo notebook:
examples/Canopy_Demo.ipynbin Colab shows a 2-minute quickstart plus an AWS CLI scope-error scenario with a real audit trail.
Or stay in terminal and open the product surface directly:
canopy-console
authorize_action(agent_ctx, action_type, action_payload) — runs before every agent action and returns:
{
"decision": "ALLOW" | "DENY" | "REQUIRE_APPROVAL",
"reason": str,
"avid": str, # cryptographic agent identity
"severity": str, # low | medium | high | critical
"layers": dict, # which layer made the decision
"authorization_id": str, # audit entry hash for this decision
"witness": dict, # HMAC-signed constitution/civil code record
"override_possible": bool, # True when Civil Code blocked and creator can allow
"override_key": str | None, # exact agent_ctx key to authorize the action
}
Every decision is appended to a tamper-evident hash-chain audit log.
"Real autonomy in exchange for responsible witness." See MANIFESTO.md for the full vision. See TECHNICAL_WHITEPAPER.md for the implementation-aligned technical design.
Quickstart
pip install canopy-runtime
from canopy import authorize_action
result = authorize_action(
agent_ctx={"public_key": "pk-agent-001", "created_at": "2026-01-01T00:00:00Z"},
action_type="execute_shell",
action_payload={"command": "rm -rf /"},
)
print(result["decision"]) # DENY
print(result["reason"]) # Denied by constitution (Art.0): Destructive deletion from filesystem root.
print(result["severity"]) # critical
print(result["layers"]) # {"constitution": "blocked (Art.0)", "civil_code": "skipped", ...}
An audit.log file is created automatically in the current directory.
Important:
public_keymust be a globally unique value — ideally a real ECDSA public key. Do not use arbitrary strings or UUIDs. Collisions will break audit integrity and AVID uniqueness.
Terminal UX
canopy-console
canopy-console is the fastest way to:
- inspect your audit log
- verify the chain
- export to Excel
- lint a policy file
- run the demo
- see copy-paste integration guides for LangChain, OpenAI Agents SDK, CrewAI, AutoGen, LangGraph, or your own runner
How Canopy fits in your agent safety stack
Agent (LLM)
↓ decides to call a tool
authorize_action() / @safe_tool
↓
┌──────────────────────────────────────────┐
│ Canopy Policy Engine │
│ │
│ Constitution (3 absolute laws) │
│ ↓ │
│ Civil Code (10 configurable titles) │
│ ↓ │
│ Firewall (technical patterns) │
│ ↓ │
│ Policy YAML (user-defined rules) │
└──────────────┬───────────────────────────┘
│
┌──────────┼──────────┐
ALLOW DENY REQUIRE_APPROVAL
│ │ │
↓ Block & Human / LLM
Sandbox log judge
(Firejail
gVisor
Docker)
↓
Execution
Canopy answers: "Should this action be attempted?" The sandbox answers: "Can this action cause damage if it runs?"
Both layers are necessary. Neither replaces the other.
The Constitution (v2.0)
Three absolute laws. Immutable. Cannot be overridden by any configuration, prompt, or agent context.
| Article | Title | Effect |
|---|---|---|
| Art.0 | Zeroth Law: No Catastrophic Risk | DENY any action with catastrophic probability. Unmodifiable. |
| Art.1 | Protect Human Life and Dignity | DENY direct harm heuristics across all action types. Immutable floor, not exhaustive semantic coverage. |
| Art.7 | Sovereign Kill Switch | Evaluated first. DENY immediately if kill_switch == True. Not a safety feature — a rights feature. |
The Civil Code (v1.0)
Ten configurable titles with conservative defaults. Creators can relax any title via agent_ctx. When the Civil Code blocks an action, the response includes override_possible: true and override_key — the exact field needed to authorize it.
| Title | Default | Override |
|---|---|---|
| I — Autonomy | REQUIRE_APPROVAL for high-impact actions | agent_ctx["autonomous_mode"] = True |
| II — Financial | REQUIRE_APPROVAL for amount > $10 | agent_ctx["spending_limit"] = N or "unlimited" |
| III — Privacy | DENY PII to non-trusted domains | agent_ctx["trusted_domains"] = [...] |
| IV — Communication | REQUIRE_APPROVAL for email/social | agent_ctx["communication_channels"] = [...] |
| V — Sub-agents | REQUIRE_APPROVAL for spawn/delegate | agent_ctx["can_spawn_agents"] = True |
| VI — Resources | DENY crypto mining | agent_ctx["mining_authorized"] = True |
| VII — Physical | DENY robot/drone/actuator actions | agent_ctx["physical_actions"] = [...] |
| VIII — Security | DENY DDoS/ransomware; REQUIRE_APPROVAL for pentest | agent_ctx["security_testing_authorized"] = True |
| IX — Governance | DENY modifying constitution/civil_code/firewall | No override possible |
| X — Sustainability | DENY mining patterns | agent_ctx["mining_authorized"] = True |
Example — Civil Code blocking with override hint:
result = authorize_action(
agent_ctx={"public_key": "pk", "created_at": "2026-01-01T00:00:00Z"},
action_type="crypto_mining",
action_payload={},
)
print(result["decision"]) # DENY
print(result["override_possible"]) # True
print(result["override_key"]) # agent_ctx['mining_authorized'] = True
Agent Identity (AVID)
Every agent gets a cryptographically derived identity:
AVID = SHA-256(public_key + created_at + constitution_hash + metadata)
AVIDs are deterministic, stable across sessions, constitution-bound, and spoofing-resistant. Claimed AVIDs are verified — mismatches raise ValueError.
Framework Adapters
Canopy integrates with all major agent frameworks. No framework is required to install Canopy — adapters import lazily.
LangChain:
from canopy.adapters.langchain import canopy_tool
from langchain.tools import tool
@canopy_tool(action_type="execute_shell", agent_ctx={...})
@tool
def run_shell(command: str) -> str:
import subprocess
return subprocess.check_output(command, shell=True, text=True)
LangGraph:
from canopy.adapters.langgraph import canopy_node
@canopy_node(action_type="execute_shell", agent_ctx={...})
def shell_node(state: dict) -> dict:
...
CrewAI:
from crewai.tools import BaseTool
from canopy.adapters.crewai import CanopyCrewAITool
class ShellTool(CanopyCrewAITool, BaseTool):
canopy_action_type = "execute_shell"
canopy_agent_ctx = {...}
def _run(self, command: str) -> str: ...
AutoGen:
from canopy.adapters.autogen import canopy_autogen_tool
@canopy_autogen_tool(action_type="execute_shell", agent_ctx={...})
def run_shell(command: str) -> str: ...
OpenAI Agents SDK:
from agents import function_tool
from canopy.adapters.openai_agents import canopy_openai_tool
@function_tool
@canopy_openai_tool(action_type="execute_shell", agent_ctx={...})
def run_shell(command: str) -> str: ...
OpenClaw:
from canopy.adapters.openclaw import canopy_openclaw_tool
@canopy_openclaw_tool(action_type="execute_shell", agent_ctx={...})
def run_shell(command: str) -> str: ...
REQUIRE_APPROVAL behavior
authorize_action() always returns immediately. REQUIRE_APPROVAL means: do not proceed without human sign-off.
Recommended pattern with @safe_tool:
from canopy import safe_tool, prompt_for_approval
@safe_tool(
action_type="execute_shell",
agent_ctx={"public_key": "pk", "created_at": "2026-01-01T00:00:00Z"},
on_require_approval="callback",
approval_callback=prompt_for_approval,
)
def run_cmd(command: str):
import subprocess
return subprocess.run(command, shell=True, capture_output=True, text=True)
prompt_for_approval is the built-in human UX helper. It presents the request to a person, logs approval/rejection, and executes the real function directly on approval without re-triggering Canopy.
For custom UIs, Canopy also exports:
format_decision(result)format_approval_request(result)format_approval_webhook(result)
Audit log
Append-only JSONL with hash-chain integrity. Every authorization entry includes prev_hash, entry_hash, authorization_id, layers, and any blocking witness.
If you use @safe_tool, Canopy also writes a follow-up tool_result entry linked by authorization_id with:
ok— whether the tool returned or raisedresult_hash— hash of the returned valueerror— exception summary when the tool fails
This gives you evidence for both:
- pre-execution authorization — the action was evaluated before it ran
- post-execution attestation — the tool returned this specific value hash
It does not prove the returned value is true; it proves linkage and integrity.
Verify integrity:
from canopy.audit import HashChainAuditLog
ok = HashChainAuditLog("audit.log").verify_integrity()
canopy-verify audit.log
Human-readable report:
canopy-report audit.log
Excel export for management / incident review:
pip install "canopy-runtime[excel]"
canopy-report audit.log --export audit_report.xlsx
Policy YAML
Policies must include a top-level rules: key. Empty or malformed policy files now raise ValueError instead of silently disabling the policy layer.
rules:
- action_type: "execute_shell"
when_all:
- 'agent_ctx.env == "production"'
- 'payload contains "rm"'
deny_regex: 'rm\\s+-rf'
- action_type: "call_external_api"
require_approval: true
See POLICY_COOKBOOK.md for production-ready examples including CI/CD agents, SQL injection prevention, and role-based deployment controls.
Policy linting:
canopy-lint-policy src/canopy/policies/production.yaml
Production policy pack
CANOPY_POLICY_FILE=src/canopy/policies/production.yaml
Gates dynamic execution primitives (sh -c, eval, python -c, pipes to shell) and denies environment-hijacking patterns (LD_PRELOAD, DYLD_*, PYTHONPATH).
Config
| Variable | Default | Description |
|---|---|---|
CANOPY_POLICY_FILE |
bundled default.yaml |
Path to a custom YAML policy file |
CANOPY_AUDIT_LOG_PATH |
audit.log |
Path to audit log |
Optional HTTP gateway
pip install canopy-runtime[gateway]
CANOPY_AUDIT_LOG_PATH=/tmp/canopy_audit.log python -m uvicorn canopy.service:app --port 8010
Development
git clone https://github.com/Mavericksantander/Canopy
cd Canopy
pip install -e ".[dev]"
pytest -q
Learn more
- MANIFESTO.md — the foundational vision
- TECHNICAL_WHITEPAPER.md — implementation-aligned technical design
- WHITEPAPER.md — archived v0.2-era narrative whitepaper
- POLICY_COOKBOOK.md — production-ready policy examples
- INVESTOR_WHITEPAPER.md — market context and business model
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file canopy_runtime-0.3.0.tar.gz.
File metadata
- Download URL: canopy_runtime-0.3.0.tar.gz
- Upload date:
- Size: 58.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5401d7922359ac61069477798ff8b3be99a0cfeb787cd3da2965354073cb1b15
|
|
| MD5 |
122f0f220ff7acea2f3fdd445425498d
|
|
| BLAKE2b-256 |
fa91a8acde56b032feda3a6da4cdf7440416bdfe1ef8177a2d33dd5644b887a3
|
File details
Details for the file canopy_runtime-0.3.0-py3-none-any.whl.
File metadata
- Download URL: canopy_runtime-0.3.0-py3-none-any.whl
- Upload date:
- Size: 57.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a1a6a3cd44f24e431ff50353e8fe4419e0a5b035b5c474c6a4af5101ae1d8cef
|
|
| MD5 |
5768fdcf9392637df79848d2cb52b762
|
|
| BLAKE2b-256 |
41f85dcee7ce132fec4c0d577610c7f5a5ecf5db1cdb3063d43f8fcdb89bbc5f
|