Metadata-Version: 2.4
Name: canopy-runtime
Version: 0.2.0
Summary: Canopy Agent Safety Runtime: policy enforcement for tool-using agents
Author: Canopy
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
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: 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"
Dynamic: license-file

# Canopy Runtime
[![CI](https://github.com/Mavericksantander/Canopy/actions/workflows/ci.yml/badge.svg)](https://github.com/Mavericksantander/Canopy/actions/workflows/ci.yml)

Minimal **Agent Safety Runtime** focused on a single primitive:

`authorize_action(agent_ctx, action_type, action_payload)` → `{decision, reason, avid}`

Decisions:
- `ALLOW`
- `DENY`
- `REQUIRE_APPROVAL`

Every decision is appended to a JSONL **hash-chain audit log** (`audit.log` by default).
Audit log appends are synchronized across threads/processes using a lockfile (`audit.log.lock`).

## REQUIRE_APPROVAL behavior
`authorize_action()` always returns immediately.

`REQUIRE_APPROVAL` means: "do not proceed without human sign-off".

The caller is responsible for implementing the approval workflow.

`guard_tool()` and `guard_tool_http()` treat `REQUIRE_APPROVAL` as a block (raises `PermissionError`).

## Quickstart
```bash
pip install canopy-runtime  # v0.2
```
```python
from canopy import authorize_action

decision = authorize_action(
    agent_ctx={"env": "production"},
    action_type="execute_shell",
    action_payload={"command": "rm -rf /tmp/logs"},
)
print(decision["decision"])  # DENY
print(decision["reason"])    # Denied by policy: matched /rm\s+-rf/
print(decision["avid"])      # AVID-...
```

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

## Verify audit log integrity
Python:
```python
from canopy.audit import HashChainAuditLog
ok = HashChainAuditLog("audit.log").verify_integrity()
```

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

## Default policy pack

Works out of the box — no configuration required:

- `execute_shell`: denies destructive patterns; requires approval for network/install commands.
- `modify_file`: denies protected paths; requires approval unless path is in `agent_ctx["safe_paths"]`.
- `call_external_api`: requires approval.

See `POLICY_COOKBOOK.md` for production-ready examples.

## 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 |

Pass `agent_ctx["safe_paths"]` as a list to allowlist paths for `modify_file`:
```python
agent_ctx = {"env": "production", "safe_paths": ["/tmp/", "/repo/"]}
```

## Policy conditions (basic AND/OR)
Rules can be conditionally applied using `when_all`, `when_any`, and `when_not`.

Example: deny `rm` in production only:
```yaml
rules:
  - action_type: "execute_shell"
    when_all:
      - 'agent_ctx.env == "production"'
      - 'payload contains "rm"'
    deny_regex: 'rm\\s+-rf'
```

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

## Demo CLI
```bash
canopy-demo
canopy-demo --safe-path /tmp/
```

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

## License

MIT
