Skip to content
BasaltDBBasaltDB
Star

See what it did

View as Markdown

basalt log prints the record of what an agent asked for and what BasaltDB did about it. Use it to check a run before you undo it, or to see why a write was refused. It is read-only.

basalt log --agent demo_bot --run RUN_ID

It prints one line per action: when, its status, the operation, the table, the number of rows, how long it took, the change id and the run’s reason. The rows an action wrote are indented below it.

PII: 0 columns classified, 10 unclassified; dictionary of 0 values, last scan 2026-11-15T07:20:32Z
basalt: warning: 10 columns the agent can read are not classified yet: basalt_demo.lead_notes.body, basalt_demo.lead_notes.id, ...; run basalt classify
2026-11-15T07:20:47Z  REFUSED  OTHER                             0 rows     0ms  0b8c7d6e-5f4a-7b3c-8d2e-1f0a9b8c7d6e  "Clean up the leads table"
    refused: STATEMENT_REFUSED DDL_NOT_ALLOWED: DropStmt
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

The first two lines are not part of the record. The PII: line counts the columns the agent can read that have a class, and the warning lists the ones that do not. On a fresh install every column is unclassified, so you see both. Run basalt classify to classify them. The warning is shortened here.

A row shows its table and key as JSON, and only the columns that changed, as column old -> new. An insert or a delete shows the key only. A refused action has an indented refused: line with its code and reason. A write refused for a missing intent is not recorded, so it is not listed.

Some rows carry a marker:

Marker What it means
(read at xmin:N) The agent read this row before it wrote it. N is the row version it saw.
(blind write) The agent never read this row in the run. A write like that is allowed, and recorded.
(cascaded) The row changed because another row was deleted, not because the statement named it.
values hidden The agent cannot read this table, so the record shows no values.
values pruned The change is more than 30 days old. The record stays, the old and new values are gone.

Consecutive reads collapse into one line, such as 3 reads, 42 rows: leads (2), lead_notes (1).

Leave out --run to see the agent’s changes, newest first. Each change is one line with its reason.

basalt log --agent demo_bot
basalt log --agent demo_bot --sort id

--sort id puts the oldest first. The default is -id, newest first. Any other value is a usage error.

Flag What it does
--full Prints whole before and after rows, and every read on its own line.
--json Prints the raw change records, for a script or an agent. See Change record.
--full-check Checks the whole hash chain from the start. By default it checks from where it last checked.

When a page of output has more to show, the next cursor is printed on stderr, so --json output stays clean.

basalt log prints warnings on stderr, before or after the record. Each one names what to do.

  • The hash chain of the ledger is broken.
  • Tables the agent can write are not guarded.
  • The engine has sent no heartbeat for 7 days, or came back after more than 7 days of silence.
  • No passkey is active, or a passkey has not signed anything for 90 days. The warning names the passkey and says how to remove it with basalt passkey remove. See Passkeys.
  • Row-level security hides every row of a table from the agent, so the agent reads 0 rows. See Hosts.
  • Columns the agent can read are not classified yet. Run basalt classify. See the CLI reference.

--agent NAME picks the agent. If the engine runs as a background service, basalt log asks the engine for the record, so you do not need the agent’s credential file. If the engine is stopped, the command says so and prints the command to start it.

Without a service, the command reads the agent’s credential the way the engine does: --credential-file, then --agent NAME looked up in the engine config, then BASALT_DATABASE_URL. A flag always wins over the environment.

An agent reads its own record with run_history and list_runs. It sees the same changes, with the same reasons. See MCP tools.

BasaltDB · Apache-2.0