# Agendex SDK

**Resource-Level Governance for AI Agents** - Dynamic task-scoped access control, parameter inspection, and tamper-evident audit trails.

## What Makes Agendex Different

| Traditional IAM | Agendex |
|-----------------|---------|
| "Agent can use payment tool" | "Agent doing `vendor_payment` task can call payments API **if amount < $1000**" |
| Static permissions | Dynamic, task-scoped |
| No parameter inspection | Full body/query inspection |
| Tool-level control | Resource-level control |

## Installation

```bash
pip install agendex
```

With framework extras:

```bash
pip install agendex[langchain]   # LangChain integration
pip install agendex[crewai]      # CrewAI integration
pip install agendex[all]         # All integrations
```

## Quick Start: Resource-Level Governance (~30 lines)

```python
from agendex import AgendexClient, GovernedDBClient, GovernedS3Client, GovernedHTTPSession
from agendex.errors import DeniedError, PendingApprovalError

# 1. Create client (reads config from env vars)
client = AgendexClient()

# 2. Create governed resource clients
db = GovernedDBClient(client, task="monthly_report")
s3 = GovernedS3Client(client, task="monthly_report")
http = GovernedHTTPSession(client, task="monthly_report")

# 3. Use in your tools - governance happens automatically
def fetch_revenue(month: str) -> str:
    try:
        result = db.query("monthly_revenue", {"month": month})
        return json.dumps(result.get("rows", []))
    except DeniedError as e:
        return f"[DENIED] {e.reason}"
    except PendingApprovalError as e:
        return f"[APPROVAL REQUIRED] ID: {e.approval_id}"

def initiate_payment(amount: float, vendor: str) -> str:
    try:
        result = http.post(
            "https://payments.api/v1/pay",
            json={"amount": amount, "vendor": vendor}
        )
        return json.dumps(result)
    except DeniedError as e:
        return f"[DENIED] {e.reason}"  # e.g., "amount > 5000"
```

## Environment Variables

| Variable | Description | Default |
|----------|-------------|---------|
| `AGENDEX_URL` | Proxy URL | `http://localhost:8000` |
| `AGENDEX_AGENT_ID` | Agent identifier | *required* |
| `AGENDEX_TOKEN` | Bearer token | *required* |

## How It Works

```
+-----------------------------------------------------------------+
|  Agent Process                                                   |
|                                                                  |
|  Tool: fetch_revenue("2025-01")                                  |
|         |                                                        |
|         v                                                        |
|  GovernedDBClient.query("monthly_revenue", {month: "2025-01"})   |
|         |                                                        |
+---------+--------------------------------------------------------+
          |
          v
+-----------------------------------------------------------------+
|  Agendex Proxy                                                   |
|                                                                  |
|  /evaluate: {                                                    |
|    action: "db.query.run",                                       |
|    params: {query_id: "monthly_revenue"},                        |
|    task: "monthly_report"                                        |
|  }                                                               |
|         |                                                        |
|         v                                                        |
|  Policy: Allow db.query.run IF query_id=monthly_revenue          |
|          AND task IN [monthly_report, demo]                      |
|         |                                                        |
|         v                                                        |
|  /invoke -> DBQueryAdapter -> SQLite -> rows                     |
|                                                                  |
+-----------------------------------------------------------------+
```

## Policy Examples

```yaml
agents:
  finance_bot:
    allow:
      # DB: Only specific queries, scoped by task
      - action: "db.query.run"
        resources:
          - type: db_query
            query_id: "monthly_revenue"
        tasks: ["monthly_report", "demo"]

      # S3: Only specific buckets/prefixes
      - action: "s3.reports.fetch"
        resources:
          - type: s3
            bucket: "reports-bucket"
            key_prefix: "monthly/"

      # HTTP: Parameter-aware decisions
      - action: "http.service.call"
        condition: "body.amount < 1000"

      - action: "http.service.call"
        condition: "1000 <= body.amount <= 5000"
        require_approval: true

      # CRM: Scoped to lookup only
      - action: "crm.lookup"
        tasks: ["support"]

    deny:
      - action: "http.service.call"
        condition: "body.amount > 5000"
```

## Resource Clients

| Client | Action | What It Governs |
|--------|--------|-----------------|
| `GovernedDBClient` | `db.query.run` | Query ID, parameters |
| `GovernedS3Client` | `s3.reports.fetch` | Bucket, key prefix |
| `GovernedHTTPSession` | `http.service.call` | Domain, path, method, body |
| `GovernedCRMClient` | `crm.lookup`, `crm.update` | Account ID, status changes |
| `GovernedTool` | *any custom action* | Arbitrary params |

## GovernedTool: Custom Actions

For actions that don't fit a built-in client, use `GovernedTool`:

```python
from agendex import GovernedTool

# Wrap any action
slack = GovernedTool(client, action="slack.post", task="alerts")
slack.invoke(channel="#ops", message="System alert!")

# Or call it directly
email = GovernedTool(client, action="email.send", task="notifications")
email(to="admin@co.com", subject="Report ready")
```

## Configuration

Override default action names globally:

```python
from agendex import configure_actions

configure_actions(
    db_query="database.execute",    # db.query -> database.execute
    http_request="api.call",        # http.request -> api.call
    crm_lookup="salesforce.lookup", # crm.lookup -> salesforce.lookup
)
```

## Integration Overhead

**~31 lines** to add Agendex to an existing agent:
- 2 lines: imports
- 3 lines: config
- 3 lines: tool signatures
- 18 lines: error handling (6 per tool)
- 5 lines: initialization

## Documentation

- [Quickstart Guide](docs/quickstart.md) - Step-by-step integration
- [API Reference](docs/api-reference.md) - Complete SDK docs

## License

MIT
