Metadata-Version: 2.4
Name: canopy-runtime
Version: 0.4.0
Summary: Canopy Cloud control plane and governance runtime for autonomous agents
Author: Canopy
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: rich>=13.0
Provides-Extra: excel
Requires-Dist: openpyxl>=3.1; extra == "excel"
Provides-Extra: http
Requires-Dist: requests>=2.25; extra == "http"
Provides-Extra: gateway
Requires-Dist: fastapi>=0.100; extra == "gateway"
Requires-Dist: pydantic>=2; extra == "gateway"
Requires-Dist: uvicorn[standard]>=0.23; extra == "gateway"
Provides-Extra: enterprise
Requires-Dist: fastapi>=0.100; extra == "enterprise"
Requires-Dist: pydantic>=2; extra == "enterprise"
Requires-Dist: uvicorn[standard]>=0.23; extra == "enterprise"
Requires-Dist: requests>=2.25; extra == "enterprise"
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == "dev"
Requires-Dist: httpx>=0.24; extra == "dev"
Requires-Dist: requests>=2.25; extra == "dev"
Requires-Dist: fastapi>=0.100; extra == "dev"
Requires-Dist: pydantic>=2; extra == "dev"
Requires-Dist: uvicorn[standard]>=0.23; extra == "dev"
Requires-Dist: openpyxl>=3.1; extra == "dev"
Dynamic: license-file

  Canopy

# Canopy Runtime
[![CI](https://github.com/Mavericksantander/Canopy/actions/workflows/ci.yml/badge.svg)](https://github.com/Mavericksantander/Canopy/actions/workflows/ci.yml)
[![PyPI version](https://badge.fury.io/py/canopy-runtime.svg)](https://pypi.org/project/canopy-runtime/)
[![Python 3.9+](https://img.shields.io/badge/python-3.9+-blue.svg)](https://www.python.org/downloads/)
[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/drive/1wzWhcyyW3Un53YEFl4PKvLThJVfhj8am?usp=sharing)

**Governance for AI agents before they act, with evidence after they do.**

Canopy v0.4.0 is built around two promises:

- **Canopy Prevent** - deterministic pre-execution governance for agent actions
- **Canopy Forensics** - evidence, linkage, and incident reconstruction after execution

Canopy answers two enterprise questions:

1. **Should this action be attempted?**
2. **If something still goes wrong, what exactly happened?**

It is authorization, not sandboxing. Pair it with containment for defense in depth.

---

## What ships in v0.4.0

**Prevent**
- `authorize_action(agent_ctx, action_type, action_payload)`
- Four-layer governance pipeline:
  - Constitution
  - Civil Code
  - Firewall
  - Policy layer
- `ALLOW` / `DENY` / `REQUIRE_APPROVAL`
- Framework adapters for LangChain, LangGraph, CrewAI, AutoGen, OpenAI Agents SDK, and OpenClaw

**Forensics**
- `authorization_id` linking decisions to follow-up entries
- `tool_result` entries with `result_hash`
- append-only tamper-evident hash-chain audit log
- `trace_id` for grouping multi-step action flows
- `policy_snapshot` recording which policy was active at decision time
- `open_incident(...)` for anchoring incident investigations to the ledger
- `canopy-incident` CLI for reconstructing a timeline from `trace_id` or `incident_id`

---

## Quickstart

```bash
pip install canopy-runtime
```

Basic pre-execution governance:

```python
from canopy import authorize_action

result = authorize_action(
    agent_ctx={
        "public_key": "pk-agent-001",
        "created_at": "2026-01-01T00:00:00Z",
        "trace_id": "trace-abc123",
    },
    action_type="execute_shell",
    action_payload={"command": "rm -rf /"},
)

print(result["decision"])          # DENY
print(result["authorization_id"])  # hash of the authorization entry
print(result["trace_id"])          # trace-abc123
print(result["policy_snapshot"])   # active policy metadata
```

An `audit.log` file is created automatically in the current directory.

> `public_key` should be globally unique - ideally a real ECDSA public key. Collisions break AVID uniqueness and audit correlation.

---

## Prevent

### Core primitive

`authorize_action(agent_ctx, action_type, action_payload)` returns:

```python
{
  "decision": "ALLOW" | "DENY" | "REQUIRE_APPROVAL",
  "reason": str,
  "avid": str,
  "severity": "low" | "medium" | "high" | "critical",
  "layers": dict,
  "authorization_id": str,
  "trace_id": str | None,
  "policy_snapshot": {
    "policy_file": str,
    "policy_hash": str,
    "policy_source": "default" | "env",
  },
  "witness": dict | None,
  "override_possible": bool,
  "override_key": str | None,
}
```

### Governance pipeline

```
Agent decides to call a tool
    ↓
authorize_action() / @safe_tool
    ↓
Constitution
    ↓
Civil Code
    ↓
Firewall
    ↓
Policy layer
    ↓
ALLOW / DENY / REQUIRE_APPROVAL
```

**Canopy answers:** should this action be attempted?  
**Your sandbox answers:** if it runs, can it still cause damage?

Both layers are necessary.

### Constitution (v2.0)

Three immutable laws:

| Article | Title | Effect |
|---------|-------|--------|
| **Art.0** | Zeroth Law: No Catastrophic Risk | DENY catastrophic primitives and catastrophic risk patterns |
| **Art.1** | Protect Human Life and Dignity | DENY direct human harm heuristics |
| **Art.7** | Sovereign Kill Switch | DENY immediately when `kill_switch == True` |

### Civil Code (v1.0)

Ten configurable operational titles with conservative defaults:

| Title | Default | Override |
|-------|---------|----------|
| I - Autonomy | REQUIRE_APPROVAL for high-impact actions | `agent_ctx["autonomous_mode"] = True` |
| II - Financial | REQUIRE_APPROVAL above threshold | `agent_ctx["spending_limit"] = N` |
| III - Privacy | DENY / gate risky data actions | `agent_ctx["trusted_domains"] = [...]` |
| IV - Communication | REQUIRE_APPROVAL by default | `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 physical actions by default | `agent_ctx["physical_actions"] = [...]` |
| VIII - Security | DENY malicious patterns | `agent_ctx["security_testing_authorized"] = True` |
| IX - Governance | DENY governance tampering | no override |
| X - Sustainability | DENY abusive compute patterns | `agent_ctx["high_compute_authorized"] = True` |

### Framework adapters

Canopy integrates with major agent frameworks. No framework is required.

- LangChain
- LangGraph
- CrewAI
- AutoGen
- OpenAI Agents SDK
- OpenClaw

Example:

```python
from canopy import safe_tool
from langchain.tools import tool

@safe_tool(
    action_type="execute_shell",
    agent_ctx={
        "public_key": "pk-your-agent",
        "created_at": "2026-01-01T00:00:00Z",
        "trace_id": "deploy-trace-1",
    },
)
@tool
def run_shell(command: str) -> str:
    import subprocess
    return subprocess.check_output(command, shell=True, text=True)
```

### REQUIRE_APPROVAL

`authorize_action()` returns immediately. `REQUIRE_APPROVAL` means the caller must not proceed without human sign-off.

Recommended pattern with `@safe_tool`:

```python
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)
```

---

## Forensics

The forensic model is an extension of the same ledger, not a second system.

### What gets written

Authorization entries:
- `event="authorize_action"`
- `authorization_id`
- `avid`
- `trace_id`
- `layers`
- `policy_snapshot`
- `witness`

Execution follow-up entries:
- `event="tool_result"`
- `authorization_id`
- `trace_id`
- `ok`
- `result_hash`
- `error`

Incident entries:
- `event="incident_event"`
- `incident_id`
- `trace_id`
- `avid`
- `description`
- `severity`
- `related_authorization_ids`
- `reporter`

### Trace correlation

Use `trace_id` in `agent_ctx` to group a multi-step flow:

```python
result = authorize_action(
    agent_ctx={
        "public_key": "pk-agent-001",
        "created_at": "2026-01-01T00:00:00Z",
        "trace_id": "trace-abc123",
    },
    action_type="execute_shell",
    action_payload={"command": "echo hello"},
)
```

### Policy snapshot

Every authorization entry records the active policy metadata:

```json
{
  "policy_snapshot": {
    "policy_file": "src/canopy/policies/default.yaml",
    "policy_hash": "a3f8c21d...",
    "policy_source": "default"
  }
}
```

That means a postmortem can answer not only what happened, but which policy version was active when it happened.

### Open an incident

```python
from canopy import open_incident

incident = open_incident(
    trace_id="trace-abc123",
    agent_avid="AVID-...",
    description="Unexpected data exfiltration detected after tool execution.",
    severity="critical",
    related_authorization_ids=["9c2f3a8b...", "4d1e7f2a..."],
    reporter="ciso-team",
    audit_log_path="audit.log",
)

print(incident["incident_id"])
```

### Reconstruct the timeline

By trace:

```bash
canopy-incident --trace trace-abc123 audit.log
```

By incident:

```bash
canopy-incident --incident inc-7a3b9c audit.log
```

Export for postmortem:

```bash
pip install "canopy-runtime[excel]"
canopy-incident --trace trace-abc123 audit.log --export incident_report.xlsx
```

### Verify integrity

```python
from canopy.audit import HashChainAuditLog
ok = HashChainAuditLog("audit.log").verify_integrity()
```

```bash
canopy-verify audit.log
```

### Reports

Human-readable report:

```bash
canopy-report audit.log
```

Excel export:

```bash
canopy-report audit.log --export audit_report.xlsx
```

---

## Optional HTTP gateway / control plane

```bash
pip install canopy-runtime[gateway]
CANOPY_AUDIT_LOG_PATH=/tmp/canopy_audit.log python -m uvicorn canopy.service:app --port 8010
```

Enterprise-oriented endpoints include:

- `POST /v1/authorize`
- `POST /v1/agents/register`
- `GET /v1/agents`
- `GET /v1/audit`
- `GET /v1/dashboard`
- `POST /v1/approvals/decide`

---

## Policy YAML

Policies require a top-level `rules:` key.

```yaml
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
```

Lint a policy:

```bash
canopy-lint-policy src/canopy/policies/production.yaml
```

Use the production pack:

```bash
CANOPY_POLICY_FILE=src/canopy/policies/production.yaml
```

See [POLICY_COOKBOOK.md](POLICY_COOKBOOK.md) for examples.

---

## Terminal UX

```bash
canopy-console
```

`canopy-console` is the fastest way to:

- inspect the audit log
- verify the chain
- review pending approvals
- inspect per-agent history
- export to Excel
- open framework integration guides

---

## Development

```bash
git clone https://github.com/Mavericksantander/Canopy
cd Canopy
pip install -e ".[dev]"
pytest -q
```

---

## Learn more

- [MANIFESTO.md](MANIFESTO.md) - the foundational vision
- [TECHNICAL_WHITEPAPER.md](TECHNICAL_WHITEPAPER.md) - implementation-aligned technical design
- [WHITEPAPER.md](WHITEPAPER.md) - archived v0.2-era narrative whitepaper
- [POLICY_COOKBOOK.md](POLICY_COOKBOOK.md) - production-ready policy examples
- [INVESTOR_WHITEPAPER.md](INVESTOR_WHITEPAPER.md) - market context and business model
