Skip to content

History operations

Operations on the journal, a table’s versions, the change feed and webhooks: undo, time travel, restore and vacuum. The overview lists every operation by kind. The MCP server, datanotes mcp, offers each operation as an MCP tool of the same name, and engineTools() of datanotes/mcp gives the same definitions to your own MCP server. DatanotesClient.call(name, args) runs one from code. Over HTTP each one is POST /datanotes/operations/<name>/, with its arguments as the JSON body: the HTTP API reference lists them.

Every write is validated. Writes to rows and to the schema are journaled and undone with undo_migration; configure_database, the webhook operations, vacuum_table and sync_semantic_index are not journaled.

List Migrations · read-only

The operation journal, newest first: total (the matching operations) and migrations, each with migration_id (for undo_migration), op, summary, table (when it was about one), executed_at, notes_changed, files_changed, undone, undoes, files_failed, by (the agent), and members (a batch’s operations) or batch (the batch of an operation). Filters: op (e.g. “update_rows”), table (substring of the table name, e.g. “personal.tasks.task”), since (ISO date). limit default 50. The journal is kept in the engine’s store, outside the vault (store:journal/); entries older than 90 days are dropped.

Examples: Hand edits, Journal and undo.

Parameter Type Required Default Description
limit integer \ null null
op string \ null null
table string \ null null
since string \ null null

Undo Migration · destructive write

Revert a journaled table operation by its migration_id. Refused (nothing written) when any touched note changed since; the conflicts are listed. Also refused when the revert would leave rows breaking a CHECK that follows links or a ref_where / ref_by (rows it writes, or rows that link to them), listed like the refusals of other writes with the way out (fix the row, or drop the rule). The rules are those of the schema the revert puts back: undoing a rename_value / rename_column puts the old names back with the rows, and a rule the revert puts back must hold on every row. A batch is checked as a whole first (nothing written when refused), then reverted operation by operation, from the last; when one finds a note changed since, those after it stay reverted and a retry skips them. An undo is journaled like any operation: undoing it (the migration_id of its answer; undoes names the operation undone) is a redo, whatever the operation (a batch included). The revert is a new version (UNDO) in the transaction log; undoing a table’s rename or move takes its whole log back to the old name, the versions made under the new one included.

The schema changes only through operations like this one: never edit metastore.yaml by hand (no validation, no undo).

Examples: Columns and types, Views, Journal and undo, Restore a table or the database.

Parameter Type Required Default Description
migration_id string yes

Describe History · read-only

DESCRIBE HISTORY: the table’s versions, newest first — version, timestamp, operation (CREATE TABLE, WRITE, UPDATE, DELETE, MERGE, ALTER TABLE, ADD COLUMNS, RESTORE, UNDO, …; EXTERNAL_EDIT = changed outside the engine: an editor, git, sync), operation_metrics and migration_id (undo). An EXTERNAL_EDIT’s operation_metrics.violations counts the rules this table’s rows break because of the hand edits it holds (each once): the edited rows whose front matter changed and the rows of the table linking to them, a removed or renamed row included (rows of other tables count in their table; a body-only edit is not checked); every rule its rows break when its definition was edited in metastore.yaml. Absent when nothing was checked, or when the edits were logged right before an engine write (the change feed has them); check_table lists the rows invalid now. Works for dropped tables too (full catalog.schema.table). Use a version with select_rows (time travel), table_changes or restore_table. Without table: the history of the whole database — the commits of every table with a log (dropped ones included), newest first, each with its table (the result’s tables lists the logged tables). An operation that changed several tables (a cascade, inverse links, restore_database) commits to each with the same migration_id, and the operations of a batch carry its id as batch: their commits list in tables every table that operation (or batch) committed to. Pick a moment for restore_database from it (timestamp, or before_migration with a migration_id or a batch). limit (default 50) counts commits; since (ISO date or date-time): only commits at or after it.

Examples: Table history and time travel, Restore a table or the database.

Parameter Type Required Default Description
table string \ null null
limit integer \ null null
since string \ null null

Table Changes · read-only

Change data feed between two versions (both included, end defaults to latest): one entry per row change with its values plus _change_type (insert, delete, update_preimage, update_postimage, metadata), _commit_version, _commit_timestamp, _operation, path. Use it to answer “what changed since …” without diffing full tables.

Examples: Table history and time travel.

Parameter Type Required Default Description
table string yes
start_version integer yes
end_version integer \ null null

Restore Table · destructive write

RESTORE TABLE to a version or timestamp: rows and definition return to that state — rows added since are deleted, changed rows get their old content, removed rows come back (a dropped table is re-created). History is not rewritten: the restore is a new version, and undo_migration reverts it. Refused (rows listed, with the way out) when the rows it writes, or rows that link to them, would break a CHECK that follows links or a ref_where / ref_by (as the restored definition has them: a rule added since does not hold the old rows). Run with dry_run first and show the user what changes.

The schema changes only through operations like this one: never edit metastore.yaml by hand (no validation, no undo).

Examples: Restore a table or the database.

Parameter Type Required Default Description
table string yes
version integer \ null null
timestamp string \ null null
dry_run boolean false

Restore Database · destructive write

RESTORE DATABASE to a timestamp (each table as of its last version committed at or before it) or to before_migration (a migration_id or a batch: every table as it was just before that operation ran — to rewind an agent’s run). Every table that keeps a transaction log goes back, rows and definition: rows added since are deleted, changed rows get their old content, removed rows come back; a table created since is dropped with its rows moved to its archive folder (as drop_table with mode “archive”: nothing is hard-deleted), a table dropped since comes back. Tables already as they were are left alone (unchanged, no new version). One operation: each restored table gets a RESTORE version, all with one migration_id, and undo_migration on it reverts them all. Refused as a whole, nothing written (rows listed, with the way out), when a row it leaves would break a CHECK that follows links or a ref_where / ref_by, across tables, as the restored definitions have them. Tables without a log (history off, or vacuumed before that time) cannot be rewound: they stay as they are, listed in skipped with the reason (tell the user). Views, bases and the database settings are not rewound (the logs keep tables), nor a rename or move of a table (undo_migration on it does that). Returns restored_to, tables (per table: action restore / drop / recreate, version, creates, texts, deletes, archived), unchanged and skipped. Run with dry_run first and show the user what changes; describe_history without table shows the moments.

The schema changes only through operations like this one: never edit metastore.yaml by hand (no validation, no undo).

Examples: Restore a table or the database.

Parameter Type Required Default Description
timestamp string \ null null
before_migration string \ null null
dry_run boolean false

Vacuum Table · destructive write

Delete transaction-log history older than retain_from_version (default: keep only the latest version); time travel before it becomes impossible. Rows are not touched. dry_run defaults to true: it lists the log files that would go. Only on the user’s explicit request.

Examples: Vacuum old history.

Parameter Type Required Default Description
table string yes
retain_from_version integer \ null null
dry_run boolean true

List Changes · read-only

Change feed across all tables: numbered row changes (seq), oldest first, from engine writes and from hand edits (source: external). Each has table, kind (insert, update, delete, move, metastore, unreadable), op (the operation that made it), path, row_id, changes ({column: {before, after}}), actor, migration_id. Hand edits are compared in batches (0.7 s after the last vault change, at most 5 s after the first), in the order they happened, renames included. A hand edit that creates, changes, renames or removes a row is checked against the tables’ rules (as check_table does) with the rows that link to it: the last event of each row in the batch has violations ([{path, violations: [{code, column | constraint (the CHECK’s name), index, message}]}]), what the edit breaks (a rule a row already broke is not listed); empty when it breaks nothing, absent when nothing was checked (engine events, a check that failed). A hand edit of metastore.yaml that changes tables’ entries checks every row of those tables: on its metastore event. At most 100 per event; violations_total then counts them all. A note renamed or moved by hand (a row or a plain note) has the links to its old path rewritten across the vault once the vault is quiet (4 s without a change; not while git pulls, rebases or merges; setting repairLinksOnHandMove, on by default), and its move event does not report them as broken_link: that repair’s events (op: repair_links) follow the batch and name the moves in cause ([{path, old_path, seq}], seq of the row’s move event). A hand edit that leaves a row’s front matter unreadable (broken YAML) is a kind: unreadable event with one violation of code unreadable: every read leaves the row out until the note is fixed, and the fix is an update of the row’s last readable state (an insert for a new note). Agents’ tool calls (reported by MCP servers) are kind: tool_call events (call: tool, args, ok, duration_ms, value, served_by), returned only when kinds includes tool_call. Pass the returned last as since next time (head is the newest number); wait_ms (max 60000) waits for a new change (long poll); limit max 5000. gap: true means older changes were dropped: re-read the tables. where filters rows as in select_rows (on the values after the change). Per-table versions: table_changes.

Examples: Hand edits, Change data feed.

Parameter Type Required Default Description
since integer 0
tables string[] \ null null
kinds "insert" \ "update" \ "delete" \ "move" \
columns string[] \ null null
where object \ null null
exclude_actor string \ null null
limit integer 500
wait_ms integer 0

List Webhooks · read-only

Webhooks of the change feed: id, url, filter (tables, ops, columns, exclude_actor), cursor (last change delivered), status (ok, failing, disabled), last_delivery, last_error. Secrets are not shown.

Examples: Change data feed.

Create Webhook · destructive write

Push row changes to a URL (a service that would rather be called than poll list_changes): batches of up to 100 events POSTed as {webhook, events, last}, signed with X-Datanotes-Signature: sha256=<HMAC of the body>, retried with backoff until a 2xx. Events are those of list_changes (hand edits with their violations; the hand edits the service has seen are recorded before it stops). Starts from now unless since. Returns the webhook with its secret — shown only here: give it to the receiver. Only on the user’s explicit request: a webhook sends the database’s changes (and, with since, its past ones) to that address.

Examples: Change data feed.

Parameter Type Required Default Description
url string yes
tables string[] \ null null
kinds "insert" \ "update" \ "delete" \ "move" \
columns string[] \ null null
exclude_actor string \ null null
where object \ null null
since integer \ null null

Delete Webhook · destructive write

Stop and remove a webhook (by id from list_webhooks).

Examples: Change data feed.

Parameter Type Required Default Description
id string yes