Record history
Every field change on a record is logged: what changed, from what to what, who it was for, and what made the change.
The source label is recorded on every change and returned by
GET /workspaces/{ws}/databases/{db}/records/{rec}/versions/changes (and by the MCP
get_history tool), and it now renders in the record’s activity panel too, as a small badge
next to the actor’s name.
human renders no badge at all. It’s the default a reader already assumes, and marking every
ordinary edit would bury the exceptions the badge exists to surface — agent, automation and
mcp each get their own labelled badge.
A row with no source recorded gets its own explicit badge, never a silent fold into human.
Anything written before this tracking shipped, or from an insert site an early rollout missed, has
source: null — and shows as “Source not recorded” rather than reading as an ordinary human
edit. Treating “unknown” and “human” as the same badge is the single easiest way to get this
wrong, so the two stay visually distinct.
Two different questions
Section titled “Two different questions”History answers two questions that are easy to confuse, and keeps them as separate columns.
Who — always a person. A member’s name is on every row, including rows an agent or an automation wrote. That is deliberate: an agent acts as the member who ran it, bounded by that member’s permissions, so the person who authorised the change is the person accountable for it. Agents never appear as actors and never accumulate a permission surface of their own.
What made it — one of four:
| Badge | Means |
|---|---|
| human | Somebody typed it, in the app. |
| agent | An agent run produced it — including Tyron. |
| automation | An automation rule or a button action. |
| mcp | A write that arrived over the API with a personal access token. |
So “Ada changed Status to Done · automation” is not a contradiction. It means a rule Ada owns fired, and the change is hers in the sense that matters for permissions and accountability.
An agent write names the agent, not just its owner
Section titled “An agent write names the agent, not just its owner”An agent-badged row carries a third fact alongside who and what: which configured Agent
(from your workspace’s Agents database) actually made the write — its name at the moment it wrote,
snapshotted so a later rename or deletion never rewrites what a historical row appears to say.
The human owner is resolved from the credential, live, every request — never from a stale token field and never something the agent supplies about itself. An agent-scoped token whose Agent record has been deleted, or whose owner is no longer an active member, is refused outright — there is no fallback identity an agent’s writes fall back to instead. A person is never blocked by this; only an agent-scoped credential is.
Query one agent’s activity in bulk — GET /workspaces/:ws/agents/:agent/activity (from/to
date range) or the list_agent_activity MCP tool — for “show me everything this agent did last
week,” rather than reading one record’s history at a time.
Where the label comes from
Section titled “Where the label comes from”It is derived from how the request authenticated, never claimed by the caller:
- A browser session →
human. - The agent runtime →
agent. - The automation executor →
automation. - An API token →
agentif the token was minted as an agent’s,mcpotherwise.
That last one is the interesting case. The label lives on the token row, not in a request header, precisely because a header is forgeable by any client — and provenance that its own subject can claim is not provenance.
One thing to know if you use the API
Section titled “One thing to know if you use the API”An ordinary personal access token reads as mcp — and so does an AI connected
through OAuth (for example Claude), which is not badged human even though a person signed in to
connect it. If you write records from your own script
with a PAT, those changes are badged mcp — not because they came from an MCP client, but because
an unmarked token is treated as one. The badge tells you a change came in over the API with a
token; it does not tell you which program held it.
human means a browser session and nothing else, so it is a reliable answer to “was a person at
the keyboard” — which is the question the badge exists to answer.

Above: a record created by a script authenticating with a personal access token — badged mcp —
next to an ordinary edit made in the app, which carries no badge at all.
What a change looks like
Section titled “What a change looks like”Every change is stored exactly as it was written — a select’s option id, not its label. That faithfulness is the one thing a change log cannot give up: a stored label would quietly rewrite itself every time somebody renamed an option.
The translating happens when you read it, so history shows you the same thing the record shows
you: field names, option labels, values rendered by type. Both old_value / new_value (the raw
stored values) and old_display / new_display (rendered for a human) come back, so you can have
either.
Deleted fields and deleted options still render by name. A field outliving the field is the whole point of a change log, and a row that renders as a bare uuid because its column was removed is exactly the moment you start doubting the history.
Finding it: the History dialog
Section titled “Finding it: the History dialog”A record’s ⋯ menu → History opens a dialog with three tabs:
- Changes — the field-by-field log above: who, what made it, and the before and after of each field. A field that has since changed again offers Revert this field, which writes the old value back as an ordinary edit (so the revert shows up in the log too).
- Versions — whole-record snapshots. Each shows who made it and what made it — a person or an agent badge, readable at a glance without decoding an id.
- Document — the record’s rich-text description, which has its own history (below).
Restoring a whole record
Section titled “Restoring a whole record”Pick a version and Restore shows a diff preview first — exactly what would change from the
record as it is now — before you confirm. The restore is itself a change: it appears in the log
like any other, badged by whatever made it, and can be undone the same way. Over the API a version
restores with POST …/versions/{version}/restore, and a read-only preview is
GET …/versions/{version}.
The description has a history too
Section titled “The description has a history too”A record’s rich-text description keeps its own versions, separate from its fields. Each time the description is saved after the first, the previous content is kept; the Document tab lists them (newest first, with the same person/agent badge), previews any one as a block-level diff against the current text, and restores it. Restoring a description version is reversible too — the text you restored over is saved first.
- This is not the editing-conflict counter. A save that was based on a stale copy of the
description is still refused with a
409, exactly as before; history is a separate record of past content. - The same retention rule as the field log (below) — one policy, not two.
- Over MCP:
get_historywithkind: "document_versions"lists them, andrestore_document_versionrestores one. There is no preview tool — an agent lists, restores, and can undo.
How long it’s kept
Section titled “How long it’s kept”History is plan-gated and capture only runs while it’s on: Free keeps none (nothing is captured, so there is nothing to restore), Pro keeps 1 day, Business 7 days, Enterprise 30 days. The History dialog states the policy in general terms rather than showing your exact number, since the figure lives on the billing side that only admins can read. This is record history, not a workspace backup — it won’t bring back a deleted database; see trash and restore for that.
Workspace-wide: the admin audit log
Section titled “Workspace-wide: the admin audit log”Everything above is scoped to one record. GET /workspaces/{ws}/audit-log (the MCP
list_audit_log tool) reads the same underlying data — admin-only, across every user and
every record in the workspace, filterable by actor, by one entity, and by date range (default: the
last 30 days).
- In the app: Settings → Audit log, visible to admins only. Filter by Actor, by one record, and by a From / To date, and use Load more to page back. A non-admin who opens the address sees only “Only workspace admins can view the audit log”, and the page makes no request for the data. It also reads from the same API and MCP tool above.
- A removed member still appears by name on their historical rows, the same no-FK design this whole page is built on.
- Known, stated gap: structural deletions aren’t captured. Deleting a database, a view, or a space doesn’t yet write a row here — only record-level create/update/delete/restore does. “Who deleted this record” is answerable today; “who deleted this database” isn’t yet, and that’s a named, separate piece of work, not an oversight quietly worked around.