# ACP — AI Control Plane

**Deterministic governance and execution control plane for enterprise AI agents and autonomous systems.**

> **Note:** “ACP” means many things in other industries (medical, political, nonprofit, and more). In this project it always means **ACP — AI Control Plane**, not a generic acronym.

**Add governance, approvals, policy enforcement, and execution visibility to AI agents in minutes.**

Works with CrewAI, LangGraph, Strands, Google ADK, MCP tools, and custom Python workflows.

**Requires:** [Docker Desktop](https://docs.docker.com/get-docker/) (Compose v2).

---

## Quick start

Make sure Docker Desktop is already running before you start ACP.

```bash
pip install acp-ai
acp init          # creates starter policies in ~/.acp/policies
acp up
acp dashboard     # open governance UI
```

| Step | Command |
|------|---------|
| Install | `pip install acp-ai` |
| Initialize policies | `acp init` |
| Start stack | `acp up` |
| Open UI | `acp dashboard` → http://localhost:3090/dashboard/ |
| Stop | `acp down` |

For a real local integration, the usual sequence is:

1. `acp init`
2. start Docker Desktop
3. `acp up`
4. `export ACP_INTERCEPTOR_URL=http://localhost:8080`
5. `export ACP_BEARER_TOKEN=...`
6. run your governed agent

If Docker is not already running, `acp up` will fail with Docker daemon / compose connection errors.

---

## Why ACP — AI Control Plane?

Most agents call tools, APIs, and other agents directly. Teams then scatter rules across Python, workflows, and frameworks:

```python
if supplier_risk_score > threshold:
    require_human_approval()
```

That becomes inconsistent, hard to audit, easy to bypass, and duplicated everywhere.

**ACP — AI Control Plane** centralizes governance **outside** agent code:

| Capability | What you get |
|------------|----------------|
| **Centralized governance** | One place for rules, not copy-paste per team |
| **Policy enforcement** | OPA/Rego evaluates every governed call |
| **Approvals** | Escalate high-risk actions to humans |
| **A2A governance** | Governed agent-to-agent calls |
| **A2T governance** | Governed agent-to-tool calls |
| **Audit & visibility** | Decisions, traces, registry in one dashboard |

---

## Architecture

![ACP — AI Control Plane architecture](https://raw.githubusercontent.com/raja-datascientist/acp-docs/refs/heads/main/images/acp-architecture.svg)

```text
Agent runtime -> ACP SDK -> Interceptor / Gateway -> OPA / Rego -> allow | deny | escalate -> governed execution
```

ACP sits at the execution boundary:

- agent frameworks keep reasoning and orchestration
- the interceptor validates identity, routes policy, and records audit traces
- OPA / Rego returns deterministic allow, deny, or escalate decisions
- only allowed or approved requests reach tools, APIs, MCP servers, or other agents

## Request sequence

![ACP — AI Control Plane governed request sequence](https://raw.githubusercontent.com/raja-datascientist/acp-docs/refs/heads/main/images/acp-sequence.svg)

Typical governed flow:

1. the agent calls a governed function through the ACP SDK
2. the interceptor validates JWT identity and normalizes the request
3. OPA / Rego evaluates policy for the tool, action, and claims
4. ACP records the decision and exposes it in the dashboard
5. the SDK executes only on `allow`, waits for review on `escalate`, and blocks on `deny`

---

## Dashboard

The **ACP — AI Control Plane** dashboard is a core differentiator: live allow/deny/escalate decisions, approvals, agent registry, and policy catalog. Open **http://localhost:3090/dashboard/** after `acp up`.

### Overview & activity

![Overview — governance posture and live activity](https://raw.githubusercontent.com/raja-datascientist/acp-docs/refs/heads/main/images/dashboard-overview.png)

![Live activity — real-time enforcement stream](https://raw.githubusercontent.com/raja-datascientist/acp-docs/refs/heads/main/images/dashboard-live-activity.png)

### Decisions & approvals

![Governance decisions — allow / deny / escalate analytics](https://raw.githubusercontent.com/raja-datascientist/acp-docs/refs/heads/main/images/dashboard-governance-decisions.png)

![Approvals — human-in-the-loop escalation queue](https://raw.githubusercontent.com/raja-datascientist/acp-docs/refs/heads/main/images/dashboard-approvals.png)

### Registry & policies

![Agent registry — identity catalog for policy enforcement](https://raw.githubusercontent.com/raja-datascientist/acp-docs/refs/heads/main/images/dashboard-agent-registry.png)

![Policies — Rego catalog from the interceptor](https://raw.githubusercontent.com/raja-datascientist/acp-docs/refs/heads/main/images/dashboard-policies.png)

![Policy detail — mortgage underwriting rules](https://raw.githubusercontent.com/raja-datascientist/acp-docs/refs/heads/main/images/dashboard-policy-underwriting.png)

### Forensics

![Traces — governance trace investigation](https://raw.githubusercontent.com/raja-datascientist/acp-docs/refs/heads/main/images/dashboard-traces.png)

---

## Deployment modes

| Mode | How | Best for |
|------|-----|----------|
| **Local** | `acp up` via pip + Docker | Demos, dev, quickstart |
| **SDK** | `@governed_tool` in your agent code | CrewAI, LangGraph, Strands, custom Python |
| **Gateway** | Single origin on `:3090` (dashboard + API proxy) | Local unified URL; pattern for prod ingress |
| **Cloud / self-hosted** | Docker Compose, Kubernetes, ECS/EKS on AWS/Azure/GCP | Team or enterprise rollout |

**Local endpoints**

| URL | Purpose |
|-----|---------|
| http://localhost:3090/dashboard/ | Governance dashboard |
| http://localhost:8080 | Interceptor API (`/tool-call`, `/api/v1/*`) |

**Self-hosted (example)**

```text
https://acp.your-company.example
```

---

## Example: governed tool

Set `ACP_INTERCEPTOR_URL` and `ACP_BEARER_TOKEN` **before** defining a `@governed_tool`.

The decorator constructs its ACP client at decoration time, so setting those env vars later inside `main()` is too late.

```python
import os
from acp import governed_tool

# Set env vars before decorator evaluation.
os.environ.setdefault("ACP_INTERCEPTOR_URL", "http://localhost:8080")
os.environ.setdefault("ACP_BEARER_TOKEN", "<dev-jwt>")

@governed_tool(
    agent_id="texas-weather-agent",
    tool="weather_api",
    action="read_weather",
)
def get_texas_weather(city: str):
    return {"city": city, "status": "pending_review"}
```

The **AI Control Plane** intercepts the call, evaluates policy, then allows, denies, or escalates.

`tool="weather_api"` is the governed ACP tool id. It does **not** have to match the Python function name (`get_texas_weather`). ACP policy keys on `tool`, not the Python symbol name.

The JWT `agent_id` claim must match `agent_id="texas-weather-agent"` in `@governed_tool(...)`.

---

## Example: policy (Rego)

```rego
package acp.policy

allow {
    input.identity.role == "supply-chain-manager"
    input.action.tool == "supplier_approval"
    input.risk_score < 70
}
```

Edit policies in `~/.acp/policies/` after `acp init`.

---

## Adding a new governed tool

`acp init` creates starter policy files in `~/.acp/policies/`, but you still need to wire new tools into policy yourself.

For a brand-new governed tool, update all of these:

1. choose an ACP tool id, for example `weather_api`
2. add tool-to-role mapping in `~/.acp/policies/rbac.rego`
3. route the tool in `~/.acp/policies/router.rego`
4. add or reuse a domain policy file such as `weather.rego`
5. mint and export `ACP_BEARER_TOKEN`
6. make sure the JWT `agent_id` claim matches `@governed_tool(agent_id=...)`
7. run the agent

Two valid patterns:

### Pattern 1: reuse an ACP tool bucket

Use one ACP tool id for multiple Python functions in the same policy domain.

```python
@governed_tool(
    agent_id="texas-weather-agent",
    tool="weather_api",
    action="read_weather",
)
def get_texas_weather(city: str):
    ...
```

`~/.acp/policies/rbac.rego`

```rego
"weather_api": ["weather"],
```

`~/.acp/policies/router.rego`

```rego
decision = weather.decision {
    input.tool == "weather_api"
    rbac.decision.decision == "allow"
}
```

`~/.acp/policies/weather.rego`

```rego
package acp.weather

default decision = {
    "decision": "allow",
    "reason": "weather_action_allowed",
    "policy": "acp/weather",
}

decision = {
    "decision": "deny",
    "reason": "weather_action_not_permitted",
    "policy": "acp/weather",
} {
    not input.action == "read_weather"
    not input.action == "read_weather_batch"
}
```

### Pattern 2: add a brand-new ACP tool id

If you want policy to key directly on a new id such as `get_texas_weather`, add that id to both `rbac.rego` and `router.rego`, then route it to the right domain policy file.

If your starter `router.rego` or `rbac.rego` references a tool id, make sure the corresponding domain policy file also exists.

---

## Local JWT dev example

Local governed `/tool-call` requests require a bearer token when JWT auth is enabled. `ACP_INTERCEPTOR_URL` alone is not enough.

Set:

- `ACP_INTERCEPTOR_URL=http://localhost:8080`
- `ACP_BEARER_TOKEN=<signed-jwt>`

If `ACP_BEARER_TOKEN` is missing, governed calls may fail with `401 Unauthorized` / `missing bearer token`.

One dev-friendly way to mint a token is:

```bash
python -m pip install pyjwt cryptography

export ACP_BEARER_TOKEN="$(
python - <<'PY'
from pathlib import Path
import time
import jwt

private_key = (Path.home() / ".acp" / "jwt" / "private.pem").read_text()
token = jwt.encode(
    {
        "sub": "agent:texas-weather-agent",
        "agent_id": "texas-weather-agent",
        "roles": ["weather"],
        "iss": "acp-dev",
        "aud": "acp-interceptor",
        "exp": int(time.time()) + 3600,
    },
    private_key,
    algorithm="RS256",
)
print(token)
PY
)"
```

Use these claims for local development:

- `sub=agent:texas-weather-agent`
- `agent_id=texas-weather-agent`
- `roles=["weather"]`
- `iss=acp-dev`
- `aud=acp-interceptor`

`agent_id` in the JWT must match the `agent_id` passed to `@governed_tool(...)`.

---

## Example: governance flow

```text
Supply Chain Agent
    → ACP — AI Control Plane validates identity (JWT)
    → OPA evaluates policy
    → Decision: ESCALATE
    → Human approves in dashboard
    → Execution resumes
```

---

## What the AI Control Plane provides

- **Policy enforcement** — OPA/Rego (Cedar on roadmap)
- **Identity** — JWT from Okta, Auth0, Keycloak, or your IdP
- **Approvals** — human-in-the-loop for risky actions
- **Observability** — dashboard for decisions, traces, agents, tools
- **Agent registry** — lightweight catalog of agents and capabilities
- **Framework-friendly** — keep CrewAI / LangGraph / Strands for reasoning; govern execution here

---

## Philosophy

Orchestration frameworks handle **reasoning, planning, and workflows**.

**ACP — AI Control Plane** handles **governance, trust, approvals, policy, and auditability**.

Reasoning stays autonomous. Execution stays governed.

---

## Roadmap

- Gateway / proxy execution mode (production hardening)
- MCP-native governance
- Policy studio and replay
- Enterprise topology views
- Multi-cloud deployment templates

---

## License

MIT License
