> ## Documentation Index
> Fetch the complete documentation index at: https://midplane.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# The audit trail

> Every query Midplane sees becomes a durable, append-only record — including the ones it denies — written before anything runs.

The audit trail is the record of everything an agent tried to do. Every query produces a series of append-only events, and the decisive ones are written **before** the query runs. Denials are recorded just like executions, so the trail is a complete history of what the agent attempted, not just what it managed to run.

## The five event types

A single query is identified by a `query_id` that groups all of its events. Most queries produce three or four:

| Event             | When it's written                                                                           |
| ----------------- | ------------------------------------------------------------------------------------------- |
| `ATTEMPTED`       | The agent sent a query; Midplane has it but hasn't decided yet.                             |
| `DECIDED`         | The policy verdict — allow or deny, with the rule that decided it.                          |
| `EXECUTED`        | An allowed query finished, with timing and row counts.                                      |
| `FAILED`          | An allowed query reached the database but errored there.                                    |
| `POLICY_RELOADED` | The policy changed (a hot-swap via the admin endpoint), recorded with a diff of what moved. |

A denied query produces `ATTEMPTED` and `DECIDED`, then stops — there's no `EXECUTED` event because the query never ran.

## The guarantee: audited before executed

The engine writes the `ATTEMPTED` and `DECIDED` events before the query touches the database — [the ordering that makes the audit write a precondition for execution](/docs/how-it-works#audited-before-executed). For the trail, that ordering buys two things:

* **Nothing reaches your database unaudited.** There is no path where a query executes but its attempt and decision aren't on record.
* **Post-execution events are best-effort.** The `EXECUTED` / `FAILED` events are logged after the fact and don't block — the pre-execution rows already prove intent.

## What a row carries

Every event is one row. The columns answer who, what, when, and how it was decided:

| Column          | What it holds                                                                                                 |
| --------------- | ------------------------------------------------------------------------------------------------------------- |
| `query_id`      | Groups every event of one query.                                                                              |
| `tenant_id`     | `__self_host__` for self-host; the customer's tenant for Cloud.                                               |
| `database`      | The database name from your config, or `__default__` for a single-database setup.                             |
| `agent_name`    | The MCP client name (e.g. `claude-code`).                                                                     |
| `agent_version` | The MCP client version.                                                                                       |
| `agent_intent`  | The per-call task description the agent supplied — a required arg on `query`.                                 |
| `mcp_token_id`  | The Cloud-issued token that opened the session (Cloud only).                                                  |
| `ts`            | When the event was written.                                                                                   |
| `event_type`    | One of the five types above.                                                                                  |
| `payload`       | JSON whose shape depends on `event_type` — the deciding rule on a `DECIDED` row, timing on an `EXECUTED` row. |

The `agent_intent` field is what turns the trail from "what ran" into "why."

## Read it

The schema is identical in both deployments — [Cloud mirrors the open-source shape column-for-column](/docs/cloud-vs-self-host) — only the storage and how you read it differ.

<Tabs>
  <Tab title="Cloud">
    Events are indexed into searchable Postgres and browsed in the dashboard, **scoped to one project** and **owner/admin-only**. Search across the SQL, its fingerprint, and query ID; filter by decision, database, agent, and [token](/docs/cloud/projects#machine-tokens), with every filter in the URL so a view is shareable. You can export the filtered view as CSV or JSON.

    The same timeline records account events that change a project's security posture — tokens created or revoked, policy reloads, guardrail changes, project pause/resume — each attributed to the teammate who made it. How far back it goes depends on your plan's [retention](/docs/cloud/plans).
  </Tab>

  <Tab title="Self-host">
    The control plane indexes events into your own Postgres, read in the **same dashboard** — search, filters, and full JSON detail — backed by a database you already back up. Or read the trail from the terminal with the [`midplane audit`](/docs/reference/cli) CLI: `tail` streams live, `denies` lists blocked queries, and `show <query_id>` prints one query's full event chain.
  </Tab>
</Tabs>

## Next steps

<Columns cols={2}>
  <Card title="Audit events reference" icon="file-code" href="/docs/reference/audit-events" horizontal>
    The full schema and every payload shape, per event type.
  </Card>

  <Card title="Alert on denials" icon="bell" href="/docs/self-host/operations#alert-on-denials" horizontal>
    Fire a webhook the moment a query is blocked.
  </Card>
</Columns>
