# AutoPIL — Platform Intelligence Layer

<!-- mcp-name: io.github.anil-solleti/autopil -->

Context governance middleware for autonomous AI agents.

AutoPIL sits between your agents and your data sources, enforcing who can retrieve what, under what conditions — and logging every access decision with an immutable audit trail.

```
Agent → @guard.protect(...) → Policy Engine → Data Source
                    ↓                ↓
              Audit Log       AlertEngine → webhook / email
                    ↓
              OTEL Spans + Metrics → Datadog / Grafana / Splunk
```

---

## Why AutoPIL

As enterprises deploy multi-agent AI systems, the gap that emerges isn't in the LLMs — it's in what context they're allowed to retrieve. Most governance tools operate at the output layer (content filtering, hallucination detection). AutoPIL operates at the **retrieval layer**, before sensitive data ever enters the agent's context window.

Key problems it solves:

- An underwriting agent accessing another customer's credit data
- A customer service bot retrieving internal risk models it has no business seeing
- One agent in a pipeline hijacking another agent's session context
- No audit trail of what data was retrieved and when
- No alerting when denial spikes or policy violations occur
- No observability in existing monitoring tooling

---

## Architecture

```
┌─────────────────────────────────────────────────────────┐
│                       Your Agent                        │
└────────────────────────┬────────────────────────────────┘
                         │  @guard.protect(...)
┌────────────────────────▼────────────────────────────────┐
│                    ContextGuard                         │
│  1. Cross-agent session isolation check                 │
│  2. Policy evaluation (allow / deny)          ◄──────── PolicyEngine
│  3. OTEL span + metrics emitted                         │
│  4. Execute retrieval (if allowed)                      │
│  5. Hash context for provenance                         │
│  6. Record immutable audit event              ──────────►  AlertEngine
└──────────────┬────────────────────────┬─────────────────┘
               │                        │
┌──────────────▼──────┐   ┌─────────────▼───────────────┐
│   Storage Backend   │   │   OTEL Collector             │
│   SQLite (default)  │   │   Datadog / Grafana / Splunk │
│   Postgres (prod)   │   └─────────────────────────────┘
└─────────────────────┘
```

**Enforcement mechanisms:**

| Mechanism | Description |
|---|---|
| Source allowlist | Agent can only access explicitly permitted data sources |
| Source denylist | Certain sources are always blocked, regardless of allowlist |
| Sensitivity ceiling | Each role has a maximum data sensitivity level it can access |
| Task scoping | Roles can be restricted to specific task types (`allowed_tasks` / `denied_tasks`) |
| Cross-agent isolation | A session belongs to the first agent that used it — others are denied |
| Session lifecycle | Sessions expire by TTL or can be revoked; expired/revoked sessions are denied |
| Default deny | No matching policy = access denied |

**Integration modes:**

| Mode | How it works | Best for |
|---|---|---|
| Python decorator | `@guard.protect(...)` wraps retrieval functions directly | LangGraph, LangChain, custom Python agents |
| REST API | `POST /v1/context/evaluate` from any language | Polyglot stacks, TypeScript, Java, Go |
| MCP server | `evaluate_context` / `record_action` as native MCP tools | Claude Desktop, GPT-4, Gemini, any MCP-compatible agent |
| FastAPI / ASGI middleware | `AutoPILMiddleware` intercepts requests at the API layer | Enforcing governance without touching agent code |
| AWS Bedrock Agents | `BedrockGuard.wrap_invoke_agent()` | Bedrock-hosted agents |
| LangChain / LangGraph | `LangChainGuard.wrap_tool()` | LangChain tools and graphs |
| LlamaIndex | `LlamaIndexGuard.wrap_query_engine()` | LlamaIndex query engines |
| OpenAI Agents SDK | `OpenAIAgentsGuard` — `@guard.function_tool()` decorator | OpenAI Assistants and function tools |
| Gemini | `GeminiGuard.call_if_allowed()` | Gemini-based agents |

**Integration channels — `source_type` values:**

| Channel | Class | `source_type` |
|---|---|---|
| REST API | `ContextGuard` + FastAPI | `rest` |
| MCP | `MCPGuard` / mcp server | `mcp` |
| Python SDK | `ContextGuard` | `sdk` |
| FastAPI / ASGI | `AutoPILMiddleware` | `api` |
| AWS Bedrock Agents | `BedrockGuard` | `bedrock` |
| LangChain / LangGraph | `LangChainGuard` | `langchain` |
| LlamaIndex | `LlamaIndexGuard` | `llamaindex` |
| OpenAI Agents SDK | `OpenAIAgentsGuard` | `openai_agents` |
| Gemini | `GeminiGuard` | `gemini` |

Every audit event is stamped with the `source_type` of the channel that produced it. The `/v1/audit/stats` endpoint returns a `by_source_type` breakdown showing totals, allowed, and denied counts per channel.

---

## Quickstart

### Install

```bash
pip install -e .
```

For the REST server:

```bash
pip install -e ".[server]"
```

For OpenTelemetry (gRPC — Datadog, Splunk):

```bash
pip install -e ".[otel]"
```

For OpenTelemetry (HTTP — Grafana Cloud):

```bash
pip install -e ".[otel-http]"
```

For the MCP server (Claude Desktop, GPT-4, Gemini, any MCP-compatible agent):

```bash
pip install -e ".[mcp]"
```

For Postgres:

```bash
pip install -e ".[postgres]"
```

### Define a policy

```yaml
# policies/my_policy.yaml
policies:
  - name: analyst_policy
    agent_role: analyst
    allowed_sources:
      - reports
      - market_data
    denied_sources:
      - executive_comms
    max_sensitivity: high
    allowed_tasks:       # optional — restrict to specific task types
      - summarization
      - qa
    denied_tasks:        # optional — block specific task types
      - extraction
```

### Wrap your retrieval functions

```python
from autopil import ContextGuard, SensitivityLevel

guard = ContextGuard(
    policy_path="policies/my_policy.yaml",
    audit_db="autopil.db",
)

@guard.protect(
    agent_role="analyst",
    user_id="user_123",
    source_id="reports",
    sensitivity_level=SensitivityLevel.HIGH,
    session_id="session_abc",
)
def retrieve_reports(query: str) -> list:
    return vectorstore.search(query)

# Authorized — returns results
results = retrieve_reports("Q3 performance")

# Unauthorized — raises PermissionError, logs the denial
results = retrieve_reports_from_exec_comms("board notes")
```

For async agents, use `protect_async` instead:

```python
@guard.protect_async(
    agent_role="analyst",
    user_id="user_123",
    source_id="reports",
    sensitivity_level=SensitivityLevel.HIGH,
    session_id="session_abc",
)
async def retrieve_reports_async(query: str) -> list:
    return await vectorstore.asearch(query)
```

`protect_async` uses `contextvars.ContextVar` for `last_event_id`, so concurrent asyncio Tasks never overwrite each other's state.

### View the audit trail

```python
events = guard.get_audit_trail("session_abc")
for e in events:
    print(e.decision, e.source_id, e.context_hash)
```

---

## Framework Integrations

### LangChain

```python
from autopil import LangChainGuard

guard = LangChainGuard(policy_path="policies/", audit_db="autopil.db")
safe_tool = guard.wrap_tool(my_langchain_tool, agent_role="analyst", source_id="reports")
```

### LlamaIndex

```python
from autopil import LlamaIndexGuard

guard = LlamaIndexGuard(policy_path="policies/", audit_db="autopil.db")
safe_engine = guard.wrap_query_engine(query_engine, agent_role="analyst", source_id="docs")
result = await safe_engine.aquery("Q3 summary")
```

### AWS Bedrock Agents

```python
from autopil import BedrockGuard

guard = BedrockGuard(policy_path="policies/", audit_db="autopil.db")
safe_invoke = guard.wrap_invoke_agent(agent_role="analyst", source_id="bedrock_kb")
response = safe_invoke(agentId=..., sessionId=..., inputText="...")
```

### FastAPI / ASGI Middleware

```python
from autopil import AutoPILMiddleware, RouteRule

app.add_middleware(
    AutoPILMiddleware,
    guard=guard,
    rules=[RouteRule(path="/v1/retrieve/{doc_id}", agent_role="analyst",
                     source_id="reports", sensitivity_level="high")],
)
```

Denied requests receive a `403` JSON response. Set `on_deny="log"` on a `RouteRule` for shadow/dry-run mode — the request passes through but the denial is still recorded.

---

## Running the API Server

### First start

On first start, AutoPIL creates a `default` tenant and prints a one-time superadmin API key:

```
  AutoPIL API Server
  Policy   : policies/
  Database : sqlite @ autopil_audit.db

  ⚠  First start — created 'default' tenant and superadmin key:
     apl_<your-key-here>
     Save this — it will not be shown again.

  Docs     : http://localhost:8000/docs
```

Use this key in the `X-API-Key` header for all API requests.

### Local development (two terminals)

**Terminal 1 — backend:**
```bash
cd packages/core
python3.11 -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
python3 serve.py --policy policies/
```

**Terminal 2 — dashboard:**
```bash
cd packages/dashboard
npm install
npm run dev
```

Open `http://localhost:5173` for the dashboard (Vite proxies API calls to `:8000` automatically).
Open `http://localhost:8000/docs` for the interactive API docs.

### With OpenTelemetry

```bash
export OTEL_SERVICE_NAME=autopil
export OTEL_EXPORTER_OTLP_ENDPOINT=https://otel-collector.mycompany.com:4317
python3 serve.py --policy policies/
```

The server prints `OTEL: <endpoint>` on startup when OTEL is configured.

### With Postgres

```bash
export DATABASE_URL=postgresql://user:pass@localhost:5432/autopil
python serve.py --policy policies/
```

### Docker

```bash
docker compose up --build
```

The server starts on `http://localhost:8000`.

- **Dashboard**: http://localhost:8000
- **API docs**: http://localhost:8000/docs

---

## Alerting

AutoPIL includes a rules engine that monitors the audit log and fires notifications when anomalies are detected. Rules are created via the REST API and evaluated synchronously on every audit event.

### Rule types

| Type | Fires when |
|---|---|
| `denial_spike` | X denials occur within N minutes |
| `new_source` | A `source_id` is accessed that has never appeared in the audit log before |
| `isolation_violation` | A cross-agent session isolation violation is recorded |
| `high_deny_rate` | Denial percentage exceeds a threshold within a time window |

### Create an alert rule

```bash
curl -X POST http://localhost:8000/v1/alerts/rules \
  -H "X-API-Key: apl_yourkey" \
  -H "Content-Type: application/json" \
  -d '{
    "name":             "denial spike",
    "rule_type":        "denial_spike",
    "threshold":        10,
    "window_minutes":   15,
    "cooldown_minutes": 30,
    "webhook_url":      "https://hooks.slack.com/services/..."
  }'
```

Rules can deliver to a **webhook URL** (HTTP POST with JSON payload) or an **email address** (requires `SMTP_HOST` env var). Both can be set on the same rule.

### Alert endpoints

```
POST   /v1/alerts/rules                — create alert rule
GET    /v1/alerts/rules                — list rules for this tenant
PUT    /v1/alerts/rules/{rule_id}      — update a rule
DELETE /v1/alerts/rules/{rule_id}      — delete a rule
GET    /v1/alerts/fired                — recent alert deliveries
```

---

## OpenTelemetry

AutoPIL emits OTEL spans and metrics from every policy evaluation. When `OTEL_EXPORTER_OTLP_ENDPOINT` or `OTEL_TRACES_EXPORTER` is set, the integration is active. When neither is set, there is zero overhead — all OTEL instruments are no-ops.

### Span

Every evaluation emits an `autopil.evaluate` span with attributes:

| Attribute | Value |
|---|---|
| `autopil.agent_role` | The role making the request |
| `autopil.source_id` | The data source requested |
| `autopil.tenant_id` | Tenant identifier |
| `autopil.session_id` | Session identifier |
| `autopil.sensitivity_level` | Sensitivity level (if provided) |
| `autopil.decision` | `ALLOW` or `DENY` |
| `autopil.policy_name` | Policy that matched |
| `autopil.reason` | Human-readable decision reason |
| `autopil.latency_ms` | Evaluation latency in milliseconds |

DENY decisions set `StatusCode.ERROR` on the span.

### Metrics

| Metric | Type | Description |
|---|---|---|
| `autopil.evaluate.count` | Counter | Total evaluations, tagged with `decision` and `tenant_id` |
| `autopil.evaluate.denied.count` | Counter | Denied evaluations, tagged with `decision` |
| `autopil.evaluate.duration` | Histogram (ms) | Evaluation latency |

### Configuration

| Variable | Description |
|---|---|
| `OTEL_SERVICE_NAME` | Service name in traces (default: `autopil`) |
| `OTEL_EXPORTER_OTLP_ENDPOINT` | OTLP collector endpoint (`grpc://` or `https://`) |
| `OTEL_EXPORTER_OTLP_HEADERS` | Comma-separated `key=value` auth headers |
| `OTEL_TRACES_EXPORTER` | Set to `console` for local span output |
| `OTEL_METRICS_EXPORTER` | Set to `console` for local metrics output |

---

## SDKs

### TypeScript / Node

```bash
npm install @vibrantcapital/autopil
```

```typescript
import { AutoPilClient } from "@vibrantcapital/autopil";

const client = new AutoPilClient({
  baseUrl: "https://api.autopil.ai",
  apiKey:  process.env.AUTOPIL_API_KEY!,
});

const result = await client.context.evaluate({
  query:      "Q3 earnings summary",
  agent_role: "analyst",
  user_id:    "u_123",
  source_id:  "financial_reports",
});

if (result.decision === "DENY") {
  throw new Error(`Access denied: ${result.reason}`);
}
```

Zero runtime dependencies. Requires Node 18+. See [`sdk/typescript/`](sdk/typescript/) for the full reference.

### Go

```bash
go get github.com/vibrantcapital/autopil-go
```

Stdlib only — no third-party dependencies. Covers all resources: Context, Audit, Sessions, Policies. See [`sdk/go/`](sdk/go/) for the full reference.

### Java

```xml
<dependency>
  <groupId>ai.vibrantcapital</groupId>
  <artifactId>autopil-java</artifactId>
  <version>0.1.0</version>
</dependency>
```

Jackson only. Builder-pattern models for all resources. See [`sdk/java/`](sdk/java/) for the full reference.

---

## MCP Server

AutoPIL ships an MCP (Model Context Protocol) server that exposes governance as native tools for any MCP-compatible agent — Claude, GPT-4, Gemini, or any custom agent that speaks MCP. No code changes required in the agent; the governance layer sits between the model and your data.

### Install

```bash
pip install -e ".[mcp]"
```

### Start the server

```bash
python mcp_serve.py --policy policies/ --db autopil.db
# or via the installed console script:
autopil-mcp --policy policies/ --db autopil.db
```

### Claude Desktop configuration

Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "autopil": {
      "command": "python",
      "args": ["/path/to/mcp_serve.py", "--policy", "/path/to/policies/"],
      "env": {}
    }
  }
}
```

### Available tools

| Tool | Description |
|---|---|
| `evaluate_context` | Policy check before retrieval — returns ALLOW or DENY with reason and event_id |
| `record_action` | Link a downstream decision to the audit event that authorized it |
| `query_audit_log` | Query recent audit events (filterable by role, decision, session) |
| `get_audit_stats` | Aggregate stats — totals, deny rate, top roles |
| `list_policies` | List active policies (filterable by industry, agent_role) |
| `reload_policies` | Hot-reload policies from disk without restarting |
| `get_session_status` | Session summary — owner, event count, sources accessed |

### System prompt pattern

```
Before accessing any data source, call evaluate_context with agent_role, user_id,
source_id, sensitivity_level, and session_id. Only proceed if decision is ALLOW.
After retrieval, call record_action with the event_id to log what you did.
```

See [`docs/mcp_integration.md`](docs/mcp_integration.md) for the full reference.

---

## API Authentication

All `/v1/*` endpoints require an API key in the `X-API-Key` header:

```bash
curl -H "X-API-Key: apl_yourkey" http://localhost:8000/v1/policies
```

### Key management

```bash
# Create a new key (scoped to your tenant)
curl -X POST http://localhost:8000/v1/keys \
  -H "X-API-Key: apl_yourkey" \
  -H "Content-Type: application/json" \
  -d '{"name": "production_agent"}'

# List keys for your tenant
curl http://localhost:8000/v1/keys \
  -H "X-API-Key: apl_yourkey"

# Revoke a key
curl -X DELETE http://localhost:8000/v1/keys/{key_id} \
  -H "X-API-Key: apl_yourkey"
```

Keys are hashed (SHA-256) at rest. The plaintext is returned once at creation and never stored.

---

## Multi-Tenancy

AutoPIL supports multiple tenants with row-level data isolation. Each tenant's audit events, policies, alert rules, API keys, and stats are fully isolated.

### Create a tenant (superadmin required)

```bash
curl -X POST http://localhost:8000/v1/admin/tenants \
  -H "X-API-Key: apl_superadminkey" \
  -H "Content-Type: application/json" \
  -d '{"name": "acme_corp"}'
```

Response includes `tenant_id` and `admin_key` — the initial key for that tenant. Save it; it won't be shown again.

---

## Project Structure

This is a monorepo with three packages managed via npm workspaces.

```
autopil/
├── packages/
│   ├── core/                        # Python backend (FastAPI + policy engine)
│   │   ├── autopil/
│   │   │   ├── __init__.py          # Public API: ContextGuard, Decision, SensitivityLevel
│   │   │   ├── models.py            # Dataclasses: ContextRequest, AuditEvent, AgentAction, enums
│   │   │   ├── policy_engine.py     # YAML policy loader — file or recursive directory
│   │   │   ├── audit_log.py         # Shim → SQLiteAuditLog
│   │   │   ├── guard.py             # ContextGuard decorator — core enforcement + action lineage
│   │   │   ├── alerting.py          # AlertEngine — threshold rules + webhook/email delivery
│   │   │   ├── telemetry.py         # OpenTelemetry spans and metrics (optional)
│   │   │   ├── cli.py               # autopil-serve entry point
│   │   │   ├── mcp_server.py        # MCP server — dispatch_tool(), create_mcp_server(), run_stdio_server()
│   │   │   ├── db/
│   │   │   │   ├── __init__.py      # create_stores() factory (7-tuple)
│   │   │   │   ├── base.py          # Abstract base classes for all store types
│   │   │   │   ├── sqlite.py        # SQLite backend (default)
│   │   │   │   └── postgres.py      # Postgres backend (production)
│   │   │   └── api/
│   │   │       ├── app.py           # FastAPI application factory
│   │   │       ├── schemas.py       # Pydantic request/response models
│   │   │       └── static/          # Built dashboard assets (copied at Docker build time)
│   │   ├── policies/                        # 45 pre-built policies across 4 industries
│   │   │   ├── financial_services/
│   │   │   │   ├── consumer_banking.yaml    # loan_underwriter, customer_service, fraud_analyst, collections_agent
│   │   │   │   ├── wealth.yaml              # wealth_advisor, investment_analyst, kyc_agent, intake_agent
│   │   │   │   ├── risk_compliance.yaml     # compliance_officer, aml_investigator, credit_risk_analyst, compliance_agent
│   │   │   │   └── operations.yaml          # settlement_agent, ops_support, data_pipeline_agent, risk_agent
│   │   │   ├── healthcare/
│   │   │   │   ├── clinical_operations.yaml # clinical_summary_agent, medication_review_agent, care_gap_agent, triage_agent
│   │   │   │   ├── revenue_cycle.yaml       # prior_auth_agent, claims_coding_agent, denial_management_agent, patient_billing_agent
│   │   │   │   └── compliance_privacy.yaml  # hipaa_audit_agent, consent_management_agent, breach_detection_agent
│   │   │   ├── telecom/
│   │   │   │   ├── network_operations.yaml  # network_fault_agent, capacity_planning_agent, change_management_agent
│   │   │   │   ├── customer_experience.yaml # cx_support_agent, churn_prediction_agent, provisioning_agent
│   │   │   │   └── fraud_assurance.yaml     # subscription_fraud_agent, revenue_assurance_agent, wangiri_detection_agent
│   │   │   └── logistics/
│   │   │       ├── supply_chain.yaml        # demand_forecast_agent, procurement_agent, inventory_reconciliation_agent
│   │   │       ├── fleet_operations.yaml    # route_optimization_agent, driver_compliance_agent, maintenance_scheduling_agent
│   │   │       └── customs_compliance.yaml  # trade_compliance_agent, sanctions_screening_agent, import_export_agent
│   │   ├── scripts/
│   │   │   └── simulate_data.py             # Populate dashboard with realistic multi-industry demo data
│   │   ├── examples/
│   │   │   ├── langgraph_demo.py            # Single agent with LangGraph
│   │   │   └── multi_agent_demo.py          # Cross-agent isolation in action
│   │   ├── tests/
│   │   │   ├── conftest.py                  # Shared fixtures
│   │   │   ├── test_policy_engine.py        # 23 policy evaluation tests
│   │   │   ├── test_audit_log.py            # 14 audit persistence tests
│   │   │   ├── test_guard.py                # 16 ContextGuard enforcement tests
│   │   │   ├── test_api.py                  # 86 REST API endpoint tests
│   │   │   ├── test_auth.py                 # 15 API key authentication tests
│   │   │   ├── test_tenants.py              # 13 multi-tenancy and isolation tests
│   │   │   ├── test_alerting.py             # 21 alerting rules and delivery tests
│   │   │   ├── test_telemetry.py            # 17 OTEL span and metrics tests
│   │   │   ├── test_policy_editor.py        # 12 policy CRUD and versioning tests
│   │   │   ├── test_multi_industry.py       # 65 multi-industry tests
│   │   │   ├── test_mcp_server.py           # 42 MCP server tool dispatch tests
│   │   │   └── test_postgres.py             # 11 Postgres backend tests (skipped unless DATABASE_URL set)
│   │   ├── Dockerfile                       # 3-stage build: dashboard → Python builder → runtime
│   │   ├── pyproject.toml
│   │   ├── mcp_serve.py             # MCP server entry point (autopil-mcp console script)
│   │   └── serve.py
│   │
│   ├── dashboard/                   # React + TypeScript control plane UI
│   │   ├── src/
│   │   │   ├── components/          # UI components (tabs, layout, drawers, charts)
│   │   │   ├── hooks/               # usePoll — background data refresh
│   │   │   ├── lib/                 # api.ts (authenticated fetch), utils.ts
│   │   │   ├── types/               # api.ts — TypeScript interfaces for all API shapes
│   │   │   ├── App.tsx              # Root component — auth, tab routing, data loading
│   │   │   ├── main.tsx             # React 18 entry point
│   │   │   └── index.css            # Dark theme design system
│   │   ├── vite.config.ts           # Vite config — dev proxy to :8000, configurable outDir
│   │   └── package.json
│   │
│   └── website/                     # Marketing site (static HTML)
│
├── sdk/
│   ├── typescript/                  # @vibrantcapital/autopil — TypeScript/Node SDK
│   │   ├── src/
│   │   │   ├── client.ts            # AutoPilClient with typed resource groups
│   │   │   ├── types.ts             # TypeScript interfaces for all shapes
│   │   │   ├── errors.ts            # AutoPilError
│   │   │   └── index.ts             # Public exports
│   │   └── test/
│   │       └── client.test.ts
│   ├── go/                          # github.com/vibrantcapital/autopil-go — Go SDK (stdlib only)
│   └── java/                        # ai.vibrantcapital:autopil-java:0.1.0 — Java SDK (Jackson only)
├── docker-compose.yml               # Postgres + AutoPIL container (uses packages/core/Dockerfile)
└── package.json                     # npm workspaces root
```

---

## Policy Reference

A policy file is a YAML document with a top-level `policies` list. Each entry maps to one agent role.

```yaml
policies:
  - name: my_policy          # Unique identifier, appears in audit log
    agent_role: analyst      # Must match agent_role passed to guard.protect()
    allowed_sources:         # Empty list [] = allow all non-denied sources (admin wildcard)
      - reports
      - market_data
    denied_sources:          # Always blocked, takes priority over allowed_sources
      - executive_comms
    max_sensitivity: high    # Ceiling: low | medium | high | restricted
    allowed_tasks: []        # Optional — empty = allow all non-denied tasks
    denied_tasks: []         # Optional — blocks specific task_type values
    # industry and category are auto-injected from directory path when loading from a
    # policies/<industry>/<category>.yaml structure — no need to add them manually
```

When loading from a directory (`--policy policies/`), AutoPIL recursively finds all `*.yaml` files and injects `industry` and `category` from the path. Point the server at a directory to enable multi-industry support:

```bash
python serve.py --policy policies/
```

**Evaluation order** (first match wins):

1. Is `source_id` in `denied_sources`? → **DENY**
2. Is `task_type` in `denied_tasks`? → **DENY**
3. Is `allowed_tasks` non-empty and `task_type` not in it? → **DENY**
4. Is `allowed_sources` non-empty and `source_id` not in it? → **DENY**
5. Does `sensitivity_level` exceed `max_sensitivity`? → **DENY**
6. No issues found → **ALLOW**
7. No policy matched for this `agent_role` → **DENY** (default deny)

Policies can also be managed via the REST API (`POST /v1/policies`, `PUT /v1/policies/{name}`) — changes are versioned and appear in `GET /v1/policies/{name}/history`.

---

## REST API Reference

Base URL: `http://localhost:8000`

All `/v1/*` routes require `X-API-Key` header. `/health` is exempt.

### Context

```
POST /v1/context/evaluate
```

```json
{
  "query": "Q3 loss rates",
  "agent_role": "loan_underwriter",
  "user_id": "user_001",
  "source_id": "credit_scores",
  "sensitivity_level": "high",
  "session_id": "optional-uuid",
  "task_type": "optional-task-label"
}
```

### Sessions

```
GET    /v1/sessions                         — list active sessions for this tenant
GET    /v1/sessions/{id}                    — session status (active | expired | revoked)
DELETE /v1/sessions/{id}                    — revoke a session immediately
```

### Audit

```
GET /v1/audit/sessions/{session_id}         — full audit trail for a session
GET /v1/audit/events?decision=DENY&limit=100
GET /v1/audit/export?format=csv             — bulk export (CSV or JSON)
GET /v1/audit/stats
POST /v1/audit/lineage                      — record an agent action against an audit event
GET  /v1/audit/lineage/{event_id}           — retrieve event + all linked downstream actions
```

### Policies

```
GET    /v1/policies                          — list (optional: ?industry=&category=)
POST   /v1/policies
PUT    /v1/policies/{name}
DELETE /v1/policies/{name}
GET    /v1/policies/{name}/history
POST   /v1/policies/reload
```

### Alert rules

```
POST   /v1/alerts/rules
GET    /v1/alerts/rules
PUT    /v1/alerts/rules/{rule_id}
DELETE /v1/alerts/rules/{rule_id}
GET    /v1/alerts/fired
```

### Keys

```
POST   /v1/keys
GET    /v1/keys
DELETE /v1/keys/{key_id}
```

### Tenant management (superadmin)

```
POST   /v1/admin/tenants
GET    /v1/admin/tenants
DELETE /v1/admin/tenants/{id}
```

---

## Storage Backends

### SQLite (default)

Zero configuration. Used for development and single-instance deployments.

```bash
python serve.py --policy policies/ --db /data/autopil.db
```

### Postgres (production)

```bash
pip install "autopil[postgres]"
export DATABASE_URL=postgresql://user:pass@host:5432/autopil
python3 serve.py --policy policies/
```

Schema is created automatically on first start. Supports horizontal scaling.

### Docker Compose (Postgres)

```bash
docker compose up --build
```

The default `docker-compose.yml` starts AutoPIL with a `postgres:16-alpine` service wired via `DATABASE_URL`.

---

## Docker Reference

### Build

The Dockerfile is a 3-stage build: it compiles the React dashboard, installs the Python package, then assembles the runtime image with the dashboard assets served by the API.

```bash
docker build -f packages/core/Dockerfile -t autopil:0.1.0 .
```

### Run

```bash
docker run -d \
  -p 8000:8000 \
  -v $(pwd)/packages/core/policies:/app/policies:ro \
  -v autopil-data:/data \
  -e AUTOPIL_POLICY=/app/policies \
  autopil:0.1.0
```

### Environment variables

| Variable | Default | Description |
|---|---|---|
| `DATABASE_URL` | _(unset)_ | `postgresql://...` — uses Postgres if set, otherwise SQLite |
| `AUTOPIL_POLICY` | `/app/policies` | Policy file or directory path |
| `AUTOPIL_DB` | `/data/autopil_audit.db` | SQLite audit DB path |
| `AUTOPIL_HOST` | `0.0.0.0` | Bind host |
| `AUTOPIL_PORT` | `8000` | Listen port |
| `OTEL_EXPORTER_OTLP_ENDPOINT` | _(unset)_ | OTLP collector URL — enables OTEL when set |
| `OTEL_SERVICE_NAME` | `autopil` | Service name in traces |
| `AUTOPIL_SESSION_TTL_MINUTES` | _(unset)_ | Default session TTL in minutes. Unset = sessions never expire. |
| `SMTP_HOST` | _(unset)_ | SMTP host for alert email delivery |

---

## Running the Examples

### LangGraph demo — loan underwriting agent

```bash
python examples/langgraph_demo.py
```

Runs a 3-node LangGraph pipeline that makes 4 retrieval attempts (2 allowed, 2 denied) and prints the full audit trail.

### Multi-agent demo — cross-agent isolation

```bash
python examples/multi_agent_demo.py
```

Runs a 4-agent wealth onboarding pipeline and demonstrates cross-agent isolation violations being blocked while the legitimate pipeline still reaches an APPROVED decision.

---

## Running the Test Suite

### Python

```bash
pip install -e ".[dev]"
pytest tests/ -v --ignore=tests/test_postgres.py
```

312 tests, all passing (11 skipped without Postgres):

| File | Tests | What it covers |
|---|---|---|
| `test_policy_engine.py` | 23 | Allow/deny paths, sensitivity ceiling, admin wildcard, task scoping |
| `test_audit_log.py` | 14 | Persistence, session isolation, context hash |
| `test_guard.py` | 16 | Decorator enforcement, audit creation, cross-agent isolation |
| `test_api.py` | 86 | REST endpoints, filtering, export, lineage, session lifecycle, task scoping |
| `test_auth.py` | 15 | API key creation, listing, revocation, auth enforcement |
| `test_tenants.py` | 13 | Tenant lifecycle, data isolation, superadmin enforcement |
| `test_alerting.py` | 21 | Rule CRUD, all rule types, cooldown, delivery, API integration |
| `test_telemetry.py` | 17 | OTEL spans, metrics, guard integration, API integration |
| `test_policy_editor.py` | 12 | Policy CRUD, version history, restore, reload |
| `test_multi_industry.py` | 65 | Directory loading, path metadata, 45-policy real set, industry/category filters, multi-industry evaluate, task scoping |
| `test_mcp_server.py` | 42 | All 7 MCP tools — evaluate, audit query/stats, list policies, record action, reload, session status, server wiring |
| `test_postgres.py` | 11 | Postgres backend (runs when `DATABASE_URL` is set) |

### TypeScript SDK

```bash
cd sdk/typescript
npm install
npm test
```

28 tests covering all resource groups, error handling, query params, and URL normalisation.

### Go SDK

```bash
cd sdk/go
go test ./...
```

### Java SDK

```bash
cd sdk/java
mvn test
```

---

## Sensitivity Levels

Ordered from least to most sensitive:

| Level | Value | Example data |
|---|---|---|
| Low | `low` | Public product catalogs, FAQ knowledge bases |
| Medium | `medium` | Account summaries, transaction history |
| High | `high` | Credit scores, identity records, loan history |
| Restricted | `restricted` | Regulatory filings, internal risk models, audit logs |

---

## License

AutoPIL is licensed under the [Business Source License 1.1](LICENSE).

**What this means in practice:**

- **Free to use** for development, research, evaluation, and any non-commercial purpose
- **Free for internal production use** — deploy it inside your own organization without restriction
- **Commercial restriction** — you may not offer AutoPIL or a substantially similar product as a managed service or SaaS to third parties without a commercial license
- **Converts to MIT on March 25, 2030** — the full codebase becomes open source automatically on that date

For commercial licensing or enterprise deployments: **anil@vibrantcapital.ai**

---

## Version

`0.1.0` — initial release
