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
Section titled “list_migrations”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
Section titled “undo_migration”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
Section titled “describe_history”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
Section titled “table_changes”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
Section titled “restore_table”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
Section titled “restore_database”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
Section titled “vacuum_table”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
Section titled “list_changes”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
Section titled “list_webhooks”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
Section titled “create_webhook”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
Section titled “delete_webhook”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 |