# For agents

How an AI agent should use BasaltDB. Start a run with an intent, check status, follow nextSteps, never retry a refusal blindly, and ask the person when told to.

You work on the person's database through BasaltDB's MCP tools. Every action is recorded, and a guard in their Postgres enforces the rules.

## Start

1. Call `status`. If `ready` is false, follow each problem's `nextSteps` and show the person its `personMessage`. Call it again after the person says they fixed something.
2. Call `run_start` with an `intent`: one sentence a person can review. Keep the `runId`.
3. Read with `query`. Write with `write`. Pass the `runId` to both. Give every `write` its own `intent`. One statement per call, with bound parameters.
4. Read a row before you update or delete it. If it changed since, the write is refused with `STALE_READ`: read it again and decide again.

Defaults for what goes through, what is held and what is refused: [Limits and defaults](/reference/limits/).

## Follow nextSteps

Every error and every result that needs action carries `nextSteps`. Do them in order.

- Do `AGENT` steps yourself. `CALL_TOOL` has its `tool` and `arguments` filled in. With `repeatCall: true`, merge them over the call that returned the step.
- `REVISE_CALL`: change the call as `text` says, then send it again.
- `ASK_PERSON`: ask, and wait for the answer.
- `END_TURN`: end your turn. The person acts next. Do not wait again.
- `STOP`: do not retry. Do not work around it.
- `RUN_COMMAND` as an `AGENT` step: run `command`. It appears only when `status` says `operatedBy` is `AGENT`. When `operatedBy` is `PERSON`, never start, stop, restart or upgrade anything yourself.

## Show links and messages

- Show `personMessage` verbatim. Keep its line breaks.
- Put each link on its own line exactly as given, or as `[label](url)` with the label from `links`.
- Never put a link in a code block or inline code.
- Never split, wrap, shorten, encode or rewrite a link.
- Put nothing after a link on its line.
- Show every `PERSON` step's `command` and `url` the same way, unchanged.

## When a write is held

`APPROVAL_REQUIRED` means the write has not run.

1. Show the person `personMessage`.
2. In the same turn, call `write` once more with the same `sql`, `params`, `intent` and `runId`, plus `approvalId`. It waits up to 90 seconds and applies the write when the person approves.
3. If the wait ends, end your turn and say the write is waiting. When the person says they approved, repeat the call. It applies at once.
4. You cannot approve. Lost the link? Call `list_pending`.

## When a write is refused

Nothing changed. Read `code`, `reason` and `nextSteps`, and do what they say. Never retry a refusal unchanged, never retry in a loop, and never look for another way to get the same result. Never split a write to avoid a hold. Over 50 rows: stop and ask. Split into writes of up to 50 rows only if the person says yes. Every code: [Error codes](/reference/errors/).

## Ask the person

On `ASK_PERSON`, and before you undo anything, say in plain words what you want to do, why, and how many rows.

## Undo

You can prepare an undo of your own work. Only the person's passkey signs it.

1. Find the run with `list_runs`. Look with `preview_revert`. Rows your team or your app changed since are skipped.
2. Ask the person. Call `request_revert` with the scope and an `intent`. Show its `personMessage`. Then call it once with the `revertId` to wait for the signature.
3. If it returns `PENDING`, end your turn with the link and expiry.
4. If it returns `APPLYING`, loop `revert_plan` with `pending: true` and `limit: 200`, then `apply_revert_batch` with those lines, until `pending` is 0.

Tools: [MCP tools](/reference/mcp-tools/).
