Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 118 additions & 0 deletions examples/agent-frameworks/crewai/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# CrewAI APort Task Verification

APort verification decorator for CrewAI task functions — every task
execution is gated by an APort policy check before the task logic runs.

## What

`@aport_verify` wraps CrewAI task functions so that before a task executes,
the agent's APort passport is verified against a configurable policy pack.
If the agent doesn't satisfy the policy, the task is denied with a clear error.

## Install

```bash
pip install crewai # or just include src/ in your PYTHONPATH
```

No extra dependencies — the decorator uses only the standard library
(`urllib.request`) for APort API calls.

## Quick Start

```python
from src import aport_verify

@aport_verify("code.repository.merge.v1")
def merge_code(repo: str, branch: str) -> str:
return f"Merged {branch} into {repo}"
```

## Key Features

- **Works with sync and async task functions**
- **Configurable agent ID resolution**: static string → kwarg → env var
- **Fail-open / fail-closed modes** via `strict=True/False`
- **Context injection** via `context_builder` callback
- **Dependency-free** APort client (no external SDK required)

## API Reference

### `@aport_verify(policy, *, agent_id=None, client=None, strict=True, context_builder=None)`

| Argument | Type | Description |
|---|---|---|
| `policy` | `str` | APort policy pack name (required) |
| `agent_id` | `str` | Static agent ID (or resolved from kwarg/env) |
| `client` | `APortHTTPClient` | HTTP client instance (uses default if None) |
| `strict` | `bool` | If `True`, raises on denial. If `False`, just logs and proceeds. |
| `context_builder` | `callable` | Receives same args as the function; return value is sent as verification context |

### Agent ID Resolution (in priority order)

1. Static `agent_id=` kwarg to the decorator
2. `agent_id` kwarg passed to the function at call time
3. `APORT_AGENT_ID` environment variable
4. Raises `APortVerificationError` if none found

## Usage Examples

### Basic usage

```python
from src import aport_verify

@aport_verify("finance.payment.refund.v1")
def process_refund(amount: float, agent_id: str = None) -> str:
return f"Refunded ${amount}"
```

### With static agent ID

```python
@aport_verify("data.export.v1", agent_id="agent_export_001")
def export_data(format: str) -> str:
return f"Exported data as {format}"
```

### With context injection

```python
@aport_verify(
"finance.payment.refund.v1",
context_builder=lambda amount, **kw: {"amount": amount}
)
def refund_item(amount: float, item: str) -> str:
return f"Refunded {item} for ${amount}"
```

### Non-strict mode (denial doesn't block execution)

```python
@aport_verify("monitoring.alert.send", strict=False)
def send_alert(message: str) -> str:
return f"Alert: {message}"
```

### Async task

```python
from src import aport_verify

@aport_verify("code.repository.merge.v1")
async def async_merge(repo: str, branch: str) -> str:
return f"Merged {branch}"
```

## Development

```bash
# Run tests
PYTHONPATH=src python3 -m unittest discover -s tests

# Syntax check
python3 -m compileall src tests examples

# Run example
PYTHONPATH=src python3 examples/basic_usage.py
```
101 changes: 101 additions & 0 deletions examples/agent-frameworks/crewai/examples/basic_usage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
"""Basic usage examples for APort CrewAI verification decorator."""

import sys
import os

sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))

from aport_verify import (
aport_verify,
APortVerificationError,
APortHTTPClient,
VerificationResult,
)


class MockClient:
def __init__(self, allow: bool = True):
self.allow = allow

def verify(self, policy: str, agent_id: str, context=None):
return VerificationResult(
verified=self.allow,
allow=self.allow,
reasons=["mock verified"] if self.allow else ["mock denied"],
)


# Use mock client for demo
mock_client = MockClient(allow=True)


@aport_verify("code.repository.merge.v1", client=mock_client)
def merge_code(repo: str, branch: str) -> str:
return f"Merged {branch} into {repo}"


@aport_verify("finance.payment.refund.v1", agent_id="agent_finance_001", client=mock_client)
def process_refund(amount: float, agent_id: str = None) -> str:
return f"Refunded ${amount:.2f}"


@aport_verify(
"data.export.v1",
client=mock_client,
context_builder=lambda format, **kw: {"format": format},
)
def export_data(format: str) -> str:
return f"Exported data as {format}"


@aport_verify("monitoring.alert.send", strict=False, client=mock_client)
def send_alert(message: str) -> str:
return f"Alert sent: {message}"


def main():
print("=" * 60)
print("CrewAI + APort Task Verification — Examples")
print("=" * 60)
print()

print("1. Basic merge task:")
result = merge_code("main", "feature/login")
print(f" ✅ {result}")
print()

print("2. Refund task with static agent ID:")
result = process_refund(49.99)
print(f" ✅ {result}")
print()

print("3. Export task with context injection:")
result = export_data("csv")
print(f" ✅ {result}")
print()

print("4. Non-strict alert task (denial doesn't block):")
result = send_alert("high cpu usage")
print(f" ✅ {result}")
print()

print("5. Denied task (strict mode):")
deny_client = MockClient(allow=False)
deny_decorator = aport_verify("secure.action.v1", client=deny_client, strict=True)

@deny_decorator
def secure_action(agent_id=None):
return "This should not run"

try:
secure_action(agent_id="test_agent")
print(" ❌ Should have raised!")
except APortVerificationError as e:
print(f" ✅ Blocked as expected: {e}")
print()

print("All examples completed.")


if __name__ == "__main__":
main()
15 changes: 15 additions & 0 deletions examples/agent-frameworks/crewai/src/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"""APort CrewAI integration — verification decorator for CrewAI tasks."""

from src.aport_verify import (
aport_verify,
APortVerificationError,
APortHTTPClient,
VerificationResult,
)

__all__ = [
"aport_verify",
"APortVerificationError",
"APortHTTPClient",
"VerificationResult",
]
Loading