Metadata-Version: 2.4
Name: agent-action-policy
Version: 0.1.1
Summary: Declarative action policies for AI agents — approve, deny, or escalate any tool call before execution
Project-URL: Homepage, https://github.com/QuartzUnit/agent-action-policy
Project-URL: Repository, https://github.com/QuartzUnit/agent-action-policy
Project-URL: Issues, https://github.com/QuartzUnit/agent-action-policy/issues
Author-email: hmj <hmj@quartzunit.com>
License-Expression: MIT
License-File: LICENSE
Keywords: agent,ai-agent,guardrail,llm,policy,safety,security,tool-use
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Security
Classifier: Typing :: Typed
Requires-Python: >=3.9
Provides-Extra: yaml
Requires-Dist: pyyaml>=6.0; extra == 'yaml'
Description-Content-Type: text/markdown

# agent-action-policy

> [한국어 문서](README.ko.md)

Declarative action policies for AI agents — approve, deny, or escalate any tool call before execution.

## Install

```bash
pip install agent-action-policy
pip install agent-action-policy[yaml]  # for YAML policy files
```

## Quick Start

```python
from action_policy import PolicyEngine, Action

engine = PolicyEngine.from_dict({
    "policies": [{
        "name": "no-force-push",
        "match": {"tool": "bash", "args_pattern": "git push --force"},
        "action": "deny",
        "reason": "Force push requires human approval",
    }]
})

decision = engine.evaluate(tool="bash", args={"command": "git push --force origin main"})
print(decision.denied)  # True
print(decision.reason)  # "Force push requires human approval"
```

## Sandboxing vs Policy

| | Sandboxing (containers) | Policy (this library) |
|---|---|---|
| **Controls** | *Where* code runs | *What* the agent can do |
| **Granularity** | Process-level | Per-tool-call |
| **Configuration** | Infrastructure | YAML/Python |
| **Use with** | Any runtime | Any agent framework |

Sandboxing and policies are complementary. Use both.

## Policy Definition (YAML)

```yaml
policies:
  - name: no-destructive-git
    match:
      tool: bash
      args_pattern: "git (push --force|reset --hard|branch -D)"
    action: deny
    reason: "Destructive git operations require human approval"

  - name: escalate-system-files
    match:
      tool: "~(file_write|write_file)"
      path_patterns:
        - "/etc/*"
        - "/usr/*"
    action: escalate
    reason: "System file modification needs confirmation"

  - name: approve-reads
    match:
      tool: "~(read|search|grep)"
    action: approve
    priority: 10  # lower = higher priority
```

## Built-in Templates

```python
engine = PolicyEngine.from_template("safe_coding")
```

| Template | What it protects |
|----------|-----------------|
| `safe_coding` | Blocks force-push, rm -rf, system file writes, credential access, hook skipping |
| `safe_browsing` | Blocks internal URLs, file:// protocol, escalates downloads |
| `safe_database` | Blocks DDL (DROP/TRUNCATE), escalates DELETE and WHERE-less UPDATE |
| `strict` | Whitelist mode — only read operations allowed, everything else denied |

## Python API

```python
# From YAML file
engine = PolicyEngine.from_yaml("policies.yaml")

# From dict
engine = PolicyEngine.from_dict({"policies": [...]})

# From template
engine = PolicyEngine.from_template("safe_coding")

# Evaluate
decision = engine.evaluate(tool="bash", args={"command": "rm -rf /"})
decision.action     # Action.DENY
decision.denied     # True
decision.reason     # "..."
decision.policy_name  # "no-rm-rf"

# Fail-closed mode (deny by default)
engine = PolicyEngine.from_template("strict", default_action=Action.DENY)

# Decorator
@engine.guard
def execute_tool(tool: str, args: dict = None):
    ...  # raises PolicyDenied or PolicyEscalated
```

## Pattern Matching

| Pattern type | Syntax | Example |
|-------------|--------|---------|
| Exact match | `tool_name` | `"bash"` |
| Glob | `*`, `?`, `[...]` | `"file_*"` |
| Regex | `~pattern` | `"~(bash\|shell\|exec)"` |
| Args regex | any regex | `"git\\s+push\\s+--force"` |
| Path glob | glob or `~regex` | `"/etc/*"`, `"~\\.env$"` |

## License

MIT
