# The ledger

What BasaltDB records for every action an agent asks for, where it is kept, and for how long.

The ledger is the record of every action an agent asks for. It is what lets BasaltDB preview a change, stop one, ask you, and undo it.

## What one record holds

One record per action, whether it was applied, held or refused:

- **Who.** The agent, its run and its task.
- **Why.** The intent the agent gave.
- **What.** The statement and its parameters, the kind of action, the rows touched and how long it took.
- **Before and after.** For each row written, the old values and the new ones, cascaded rows included.
- **The decision.** `ALLOW`, `HOLD` or `REFUSE`, and the rule behind it.
- **What it saw.** For updates and deletes, the version of the row the run had read.

Reads through BasaltDB are recorded too. Each read keeps the rows returned and the version of each, so the ledger knows what the agent was looking at when it wrote.

## Statuses

| Status             | Meaning                                            |
| ------------------ | -------------------------------------------------- |
| `APPLIED`          | The change ran.                                    |
| `APPROVAL_PENDING` | The change is held and waits for you.              |
| `REFUSED`          | The change did not run. The record has the reason. |
| `APPROVAL_EXPIRED` | The hold ran out after 24 hours.                   |
| `REVERTED`         | Every row of it was undone.                        |

The full shape is on [Change record](/reference/change-record/).

## Where it lives

In the `basalt_meta` schema inside your own Postgres. BasaltDB has no server of its own that holds your data. `DROP SCHEMA basalt_meta CASCADE` removes it. See [Remove BasaltDB](/guides/remove/).

## How long it is kept

- Old values: **30 days**. This is how long an undo can restore a row.
- Everything else about each action: **1 year**.

## Tamper evidence

Each ledger row is chained to the one before it with a hash. `basalt log` checks the chain and warns if it is broken. The ledger is append-only: an undo is a new record and never edits an old one.

## Reading it

```sh
basalt log --run RUN_ID
```

One line per action, with the rows it wrote indented below:

```text
2026-11-15T07:20:47Z  APPLIED  UPDATE basalt_demo.leads          3 rows     9ms  7c3e4f0a-5b1d-4c8e-9a62-0d1f2e3a4b5c  "Qualify leads 1 to 3"
    UPDATE basalt_demo.leads (blind write) {"id":1} score 37 -> 47, status NEW -> QUALIFIED
    UPDATE basalt_demo.leads (blind write) {"id":2} score 74 -> 84, status NEW -> QUALIFIED
    UPDATE basalt_demo.leads (blind write) {"id":3} score 11 -> 21, status NEW -> QUALIFIED
```

Consecutive reads collapse into one line, such as `3 reads, 42 rows: leads (2), lead_notes (1)`. Add `--full` for whole before and after rows. `--json` gives the raw records. An agent reads its own runs with `run_history` and `list_runs`.

For a table the agent cannot read, the record shows `values hidden` instead of the rows. After 30 days it shows `values pruned`.

## Next

- [See what it did](/guides/see-what-it-did/): `basalt log` in detail.
- [Runs](/concepts/runs/): how actions are grouped.
- [Undo](/concepts/undo/): what the old values are for.
