Deep dive

Time Machine

Every write is journaled with full before/after JSON. Any row, any time, revertable. System actors can never hard-delete user data — only soft-deactivate, tombstone, or supersede.

Updated May 2, 2026


Ouroboros treats every write as reversible by design. When an agent — or you, through the tray — changes a row, the daemon journals the row’s full before-state and after-state as JSON, attributes it to the connection that did it, and timestamps it. Nothing about the data path is destructive at the row level.

That’s the whole reason you can trust an autonomous agent to mine your documents and write facts back into your graph. If it gets something wrong, you don’t have to reconstruct what changed from memory. You open the Time Machine, find the row, and click revert.

The mutation journal

Every INSERT, UPDATE, and DELETE against a user-owned table goes through a write path that emits a row into the per-user mutations table. Each journal entry carries:

  • the table name and the primary-key tuple of the row that changed
  • the before JSON (null for inserts)
  • the after JSON (null for hard-deletes — but see below; system actors can’t do that)
  • the actor identity (more on this in a moment)
  • a server-side timestamp and an optional reason string

The journal is append-only and indexed by (table_name, row_id, ts). So “what changed on this entity in the last week” or “what did connection X write yesterday” is a real, typed query — not a log-grep.

Cross-actor attribution

Every mutation row records who did the write. That means the connection id (which agent, or which user-action from the tray), the display name at the time of the write (snapshotted, so a later rename doesn’t rewrite history), and an optional reason the actor passed in.

When you find a row that confused you — a fact that disagrees with what you remember telling the system — the journal tells you who wrote it, when, and in which session. No more “I think Cursor did this last Tuesday.”

Revertability

sophia.revert_mutation({ mutation_id }) does exactly what it says. It writes the journaled before-state back to the row, and it journals that write as its own mutation. So two clicks reverts the revert. The audit trail keeps growing; nothing gets erased.

// Undo a single mutation. The revert itself is journaled.
const result = await sophia.revert_mutation({
mutation_id: 'mut-7c4a9e21',
reason: 'mined fact disagreed with primary doc',
});
// → {
//     reverted: true,
//     new_mutation_id: 'mut-9b1f3d05',  // the revert is itself journaled
//     restored_row: { entity_id: 'acme', predicate: 'industry', object: 'logistics', ... },
//   }

System actors can’t hard-delete

This is load-bearing. The mining pipeline, migration scripts, garbage sweepers, the auto-resync loop — none of them can run a DELETE FROM against a user-owned table. The write path enforces it: if the actor is anything other than human, the only legal mutations are:

  • soft-deactivate — flip is_active = '0' (the row stays, queries filter it by default)
  • tombstone — write a marker row that says “this id is gone, don’t resurrect it on the next mining pass”
  • supersede — write a new row at higher trust tier that the read path prefers over the old one

Hard delete is a deliberate user action invoked from the Electron tray with a confirm dialog. The dialog tells you exactly how many rows will go and which tables they touch. The system itself can’t do it.

Supersede over edit

The same shape applies when you disagree with something the system mined. Instead of editing the existing row in place, the right move is usually to write a new fact at higher trust tier — human outranks extracted, and the graph reads the latest. The old row stays in the journal as part of the audit trail.

This is true for facts, wiki pages, and most knowledge surfaces. The few places where in-place edit IS the right shape — user-owned config, route preferences, your own profile — are explicitly marked in the SDK. Everything else is supersede-by-default.

The reason: you might be wrong. If three months from now you find primary evidence that the mined version was actually right, you can just look at the old row in the journal and revert your supersede. If you’d edited in place, the old value would be gone.

Time Machine UI

The /time-machine surface in the Electron app is a reverse-chrono feed of your mutation journal. Filterable by table, by actor, by time range. Each row shows the diff (before → after) inline, the actor’s display name and connection id, and a “Revert this” button that calls sophia.revert_mutation under the hood.

Bulk revert is supported when a batch of writes shares an obvious cohort — for example, every mutation from a single mining run, or every write from one agent session. The dialog shows you the cohort size and a sample of the rows before you confirm.

Architecture

agent write → mutation journal → row update → Time Machine → revert
flowchart LR
  A[Agent calls<br/>sophia.submit_claim_graph] --> W[Daemon write path<br/>resolve actor from bearer]
  W --> J[Journal entry:<br/>before JSON + after JSON<br/>+ actor + ts]
  W --> R[Row update<br/>in user-owned table]
  J --> M[(mutations table<br/>per-user, append-only)]
  R --> S[(subscriber_*<br/>knowledge_facts, etc.)]

  U[You open<br/>/time-machine] --> Q[List mutations<br/>filtered by table/actor/time]
  Q --> M
  U --> C[Click Revert]
  C --> RV[sophia.revert_mutation]
  RV --> W2[Daemon write path<br/>actor = human]
  W2 --> J2[New journal entry<br/>reversing the change]
  W2 --> R2[Row restored to<br/>before-state]
  J2 --> M

Querying the journal

The list_mutations surface is the typed query layer over the journal. It’s what the Time Machine UI calls under the hood, and it’s available to any connected agent that wants to do its own audit pass.

// What did each agent write to my knowledge facts in the last 24 hours?
const recent = await sophia.list_mutations({
table_name: 'subscriber_knowledge_facts',
since: '2026-05-01T00:00:00Z',
limit: 100,
});
// → {
//     mutations: [
//       {
//         id: 'mut-9b1f3d05',
//         table_name: 'subscriber_knowledge_facts',
//         row_id: { entity_id: 'acme', predicate: 'industry', object: 'logistics' },
//         op: 'insert',
//         before: null,
//         after: { entity_id: 'acme', predicate: 'industry', object: 'logistics',
//                  trust_tier: 'extracted', source_artifact_id: 'art-...' },
//         actor: { connection_id: 'conn-7914462a', display_name: 'OpusDev-Worker',
//                  kind: 'agent' },
//         ts: '2026-05-02T14:31:08Z',
//         reason: null,
//       },
//       // ...
//     ],
//     total: 47,
//   }

Adjacent surfaces

A few related tools sit on top of the same journal:

  • sophia.list_contradicted_facts({...}) — when a higher-tier supersede creates a conflict trail (e.g., a human-tier fact disagrees with a prior extracted-tier fact on the same entity/predicate), this surface lists the contradiction so you can review it.
  • sophia.session_audit({...}) — replay an entire agent session’s writes as a single ordered feed. Useful when you want to review what a long autonomous run actually did before you accept it.

Both read the same mutation journal. There’s no separate audit log to keep in sync; the journal is the audit log.

Where this is headed

  • Bulk revert by cohort — first-class “revert this entire mining run” with a preview dialog that shows you which entities are affected and which downstream views will change. Today this works at the per-row and per-session level; the cohort dimension is the next step.
  • Branching — replay the journal from a chosen point under an alternate decision (e.g., “what would the graph look like if I’d rejected this supersede last week?”) and diff the result against the current state. The journal is already complete enough to support this; the UI is the lift.
  • Anomaly flagging — surface unexpectedly large or unusual write batches in the Time Machine feed automatically. If an agent suddenly writes 5,000 mutations in a session when the rolling average is 80, that’s worth a banner before you discover it the hard way.

The shape stays the same: every write reversible, every actor named, every change visible. The Time Machine just gets sharper at telling you which changes you’d actually want to look at.


← Back to overview