# Reference architecture

How ACP works: a hook in the agent harness sends every tool call to the gateway before it runs. The gateway records it, applies your rules per tool and per agent tier, and returns allow, flag, ask, or deny. The console shows the record.

# Reference architecture

ACP is two pieces: a hook and a gateway. The hook lives in the agent harness and sends every tool call to the gateway before it runs. The gateway records the call, applies the workspace's rules, and returns a decision. The console shows the record.

## The call path

<div class="arch-diagram">
  <div class="arch-row">
    <div class="arch-node">
      <div class="arch-node-title">agent harness</div>
      <div class="arch-node-sub">Claude Code, Codex, Cursor, &hellip;</div>
    </div>
    <div class="arch-arrow">&rarr;</div>
    <div class="arch-node arch-node-gateway">
      <div class="arch-node-title" style="color:rgba(129,140,248,1);">hook</div>
      <div class="arch-node-sub">fires before the tool runs</div>
    </div>
    <div class="arch-arrow">&rarr;</div>
    <div class="arch-node arch-node-gateway">
      <div class="arch-node-title" style="color:rgba(129,140,248,1);">gateway</div>
      <div class="arch-node-sub">record &middot; classify &middot; rules</div>
    </div>
    <div class="arch-arrow">&rarr;</div>
    <div class="arch-node arch-node-gateway">
      <div class="arch-node-title" style="color:rgba(129,140,248,1);">decision</div>
      <div class="arch-node-sub">allow &middot; flag &middot; ask &middot; deny</div>
    </div>
  </div>
  <div class="arch-row" style="margin-top:12px;">
    <div class="arch-node">
      <div class="arch-node-title">the tool</div>
      <div class="arch-node-sub">runs, or doesn't</div>
    </div>
    <div class="arch-arrow">&rarr;</div>
    <div class="arch-node">
      <div class="arch-node-title">console</div>
      <div class="arch-node-sub">the record &middot; rules &middot; approvals &middot; export</div>
    </div>
  </div>
  <div class="arch-caption" style="text-align:center;">Every tool call is recorded and decided before it runs.</div>
</div>

One request per tool call. Nothing is sampled, and nothing is reconstructed after the fact.

## 1. The hook

The hook uses the harness's own pre-tool-call seam: `PreToolUse` in Claude Code, `hooks.json` in Cursor, `codex_hooks` in Codex, an in-process plugin where the harness has no shell hook. Harnesses covered today: Claude Code, Codex, Cursor, opencode, antigravity, dsh, Hermes, pi, Muse Code, Prime Agent, Grok Build, OpenClaw. Each has a page under [/controls](/controls) that says exactly what the plugin registers and what the harness does on its own.

Before a tool runs, the hook sends the gateway the tool name and its arguments, the agent identity, the session, and the tier the agent is running under. Then it waits for the decision.

One command installs it:

```bash
curl -sf https://agenticcontrolplane.com/install.sh | bash
```

Add `--local` and the hook evaluates rules on the machine and never calls the gateway. Which harnesses support that is listed on [install, explained](/install-explained).

## 2. The gateway

The hook calls one endpoint, `POST /govern/tool-use`. The gateway does three things, in order:

1. **Record.** The call is written before anything else happens. A denied call is a row; so is an allowed one.
2. **Classify.** The raw call is normalized to a name a rule can match: `Bash.git`, `Write`, `notion.readPage`. A shell command and an MCP tool get the same treatment.
3. **Decide.** The workspace's rules are matched against the classified tool and the agent's tier, and one outcome comes back.

### Tiers

| Tier | Who is running |
|---|---|
| `interactive` | A person is at the terminal and can answer a prompt. |
| `subagent` | Spawned by another agent. The person may never see its calls. |
| `background` | Unattended: cron, CI, a long-running loop. |
| `api` | Calls arriving with a workspace key and no session. |

Rules are set per tool and per tier. `rm -rf` can be allowed for the person at the keyboard and denied for the background job.

### Outcomes

| Outcome | What happens |
|---|---|
| `allow` | The tool runs. The row is recorded. |
| `flag` | The tool runs. The row is marked for review. |
| `ask` | The tool waits for a person. |
| `deny` | The tool does not run. The agent is told why and continues. |

`ask` is the one that matters unattended. If someone is attending the session, the harness prompts them. If nobody is (a background tier, a subagent, a loop that has been running for an hour) the call does not run, and it appears in the console as a pending approval with the full arguments. Answer it there. Any row, including a pending approval, can be turned into a rule, so the second time is a decision you already made.

## 3. The record and the console

Each row carries the tool as called and as classified, the arguments, the agent and its tier, the rule that matched, the outcome, the session, and the timestamp. [cloud.agenticcontrolplane.com](https://cloud.agenticcontrolplane.com) shows it. From the console you filter the record, set a rule from any row, answer pending approvals, and export.

## Where it runs

- **Cloud, the default.** The gateway is `api.agenticcontrolplane.com`; the console is `cloud.agenticcontrolplane.com`. Free for small teams; see [pricing](/pricing).
- **Local.** `install.sh --local` keeps rules on the machine. Nothing phones home.
- **Frameworks.** For agents you write in code, the SDKs (`pip install acp-crewai`, `npm i @agenticcontrolplane/governance`) call the same endpoint from a decorator or a wrapper. See [frameworks](/frameworks).
- **Model traffic.** The proxy records LLM calls and cost per agent. It is a separate path from the tool-call hook. See [cost tracking](/cost-tracking).

## Packages and source

Harness plugins are published under `@agenticcontrolplane/*` on npm and as `acp-*` packages on PyPI. Source is one repo per harness under [github.com/agentic-control-plane](https://github.com/agentic-control-plane), MIT licensed. See [community](/community).

[Get started &rarr;](/getting-started) &middot; [Install, explained &rarr;](/install-explained) &middot; [Controls by harness &rarr;](/controls)
