# Runs

A run is one job with one stated reason. Every write carries the reason, and the run is what you review and undo.

A run is one job your agent does, with one reason you can read. It is the unit you review, and the unit you undo.

## How a run starts

The agent starts a run with `run_start` and a sentence that says why:

```text
run_start  intent: "Qualify this week's leads"
```

BasaltDB returns a run id. The agent passes it to every `query` and `write` that belongs to the job. If a write arrives without a run id, BasaltDB starts a run of one write for it.

The intent is required and holds up to 500 characters. Write it for a person: someone will read it next to the rows it changed.

## The reason on every write

Each `write` also carries its own intent. A write without one is refused:

```json
{ "code": "INTENT_REQUIRED" }
```

The refusal does not depend on the engine. The guard inside your Postgres checks for the intent too. If an agent finds a way around the engine and writes directly with its own credential, Postgres refuses the write.

## What a run records

For each action in a run, BasaltDB records the reason, the statement, the rows it touched and their old values. It records reads as well. Each read stores the version of the rows the agent saw.

Reads are what make this rule possible: an update to a row that changed after the agent read it is refused with `STALE_READ`. The agent has to read the row again before it writes. A row the agent never read can still be written. That is called a blind write, and the record marks it.

`basalt log --run RUN_ID` shows a run as 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
```

## Tasks across agents

A task groups runs. Pass a `taskId` to `run_start` to join a task another agent started, even one from a different vendor. You can undo a whole task at once. See [Undo](/concepts/undo/).

## Why runs matter

- You read a run's reason first, then its rows.
- You undo a whole run at once. A loop of small writes is not blocked, and it all lands in one run.
- An agent can find its own runs with `list_runs`, so you can say "undo what you did yesterday on leads".

## Next

- [Rules](/concepts/rules/): what a run may and may not do.
- [The ledger](/concepts/ledger/): everything BasaltDB records about each action.
- [MCP tools](/reference/mcp-tools/): `run_start`, `query`, `write` and the others.
