# How Supersession Tracking Preserves Page Version History in ai-memory

> Learn how supersession tracking in akitaonrails/ai-memory preserves page version history. Discover the immutable, backwards-traversable chain created by SQLite row linking for every edit.

- Repository: [Fabio Akita/ai-memory](https://github.com/akitaonrails/ai-memory)
- Tags: internals
- Published: 2026-08-19

---

**Supersession tracking preserves page version history by inserting a new SQLite row for every distinct edit and linking each row to the previous version through a supersession ID, creating an immutable, backwards-traversable chain.**

The `ai-memory` wiki engine stores every page modification as an append-only event rather than performing an in-place update. By maintaining each version as a distinct row and threading them together through supersession metadata, the system guarantees a complete, queryable audit trail for every wiki page. This architecture is implemented across the Rust crates `ai-memory-wiki` and `ai-memory-store` with strict transactional boundaries.

## How New Page Versions Are Created

When a user edits a page, `Wiki::write_page` delegates to `Writer::upsert_page` inside [`crates/ai-memory-wiki/src/wiki.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-wiki/src/wiki.rs). Before persisting anything, the system computes a **SHA-256 hash** of the new body and compares it against the latest stored hash. If the content is unchanged, the write is a no-op; if the hash differs, a new version row is inserted. The inline comment spanning lines 1245–1629 explicitly describes this behavior as the “sha256 short-circuit + supersession dance.”

```rust
// Write a page – creates a new version if the body changed
let page_id = wiki
    .write_page(ws, proj, "notes/todo.md".into(), "My Todo", "First version")
    .await?;

// Later edit – a new row is inserted; the old row gets is_latest = 0
let new_id = wiki
    .write_page(ws, proj, "notes/todo.md".into(), "My Todo", "Second version")
    .await?;

```

## The is_latest Flag and the Supersession Chain

Every row in the pages table carries an **`is_latest`** integer flag. As noted in [`crates/ai-memory-store/src/reader.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-store/src/reader.rs) at lines 819–820, the current version is marked with `is_latest = 1`, while all older rows hold `is_latest = 0`. Each newer row also stores the ID of the row it supersedes, forming a **linked list** that can be walked from the newest version back to the original.

The architecture documentation in [`docs/lifecycle-ops.md`](https://github.com/akitaonrails/ai-memory/blob/main/docs/lifecycle-ops.md) (lines 327–329) and the project [`README.md`](https://github.com/akitaonrails/ai-memory/blob/main/README.md) (lines 379–380) both describe this structure as the versioned supersession chain that mirrors the Git history of the wiki.

## Decay Tombstones vs. Regular Supersession

Ordinary edits never delete or overwrite data, but the system does support **soft-deletion** through a decay sweep. When a page is evicted by decay logic, the `superseded_at` column is populated with a timestamp, acting as a “decay-tombstone” marker. This mechanism is implemented in [`crates/ai-memory-store/src/ops.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-store/src/ops.rs) at lines 1627–1630. In contrast, standard supersession rows retain `superseded_at IS NULL`; only the decay process triggers this tombstone state, distinguishing routine version history from reclaimed storage.

## Atomic Updates for Chain Consistency

To prevent a broken supersession chain during concurrent writes, all flag updates and insertions happen inside a **single SQLite transaction**. The `ops::upsert_page` routine in [`crates/ai-memory-store/src/ops.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-store/src/ops.rs) (lines 1881–1884) performs the atomic steps of clearing `is_latest` on the previous row and inserting the new version with `is_latest = 1` and the correct supersession pointer. This guarantees that readers always see a consistent latest version and that the historical chain remains intact.

## Querying Page History in Practice

Because every version is preserved and linked, client code can traverse the entire edit history by following supersession IDs. The example below demonstrates fetching the current page and then walking the chain backwards:

```rust
// Query the current version
let latest = store
    .reader
    .get_page_by_path(ws, proj, "notes/todo.md")
    .await?; // latest.is_latest == true

// Walk the supersession chain backwards
let mut cur = latest.id;
while let Some(prev) = store.reader.get_superseded_by(cur)? {
    println!("previous version id: {}", prev);
    cur = prev;
}

```

## Summary

- **Append-only edits:** `Wiki::write_page` inserts a new SQLite row for every content change instead of overwriting the existing record.
- **SHA-256 short-circuit:** Identical content is detected via hash comparison, preventing unnecessary version rows.
- **is_latest flag:** The current version is flagged with `is_latest = 1`; historical rows are marked `0`.
- **Linked chain:** Each version stores the ID of the row it supersedes, enabling backwards traversal of the full history.
- **Decay tombstones:** The `superseded_at` column is set only by the decay sweep, separating normal versioning from soft-deletion.
- **Transactional safety:** `ops::upsert_page` wraps flag updates and inserts in one SQLite transaction to keep the chain consistent.

## Frequently Asked Questions

### What triggers a new version row in ai-memory?

A new row is created only when `Wiki::write_page` detects a different SHA-256 hash from the latest stored version. If the submitted body matches the existing hash, the write is skipped entirely, making the operation a no-op.

### How does ai-memory distinguish the current page version from older ones?

The system relies on the **`is_latest`** column in the SQLite table. The newest version carries `is_latest = 1`, while every prior version in the supersession chain is set to `0`. This convention is documented in [`crates/ai-memory-store/src/reader.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-store/src/reader.rs).

### What is the difference between supersession and soft-deletion by decay?

Standard supersession links a new version to its predecessor while leaving `superseded_at` as `NULL`. Soft-deletion occurs only when the decay sweep evicts a page, at which point `superseded_at` is filled to mark the row as a decay tombstone.

### Are page version updates in ai-memory atomic?

Yes. The `ops::upsert_page` function in [`crates/ai-memory-store/src/ops.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-store/src/ops.rs) executes the update to the previous row’s `is_latest` flag and the insertion of the new version inside a single SQLite transaction, ensuring the supersession chain never becomes inconsistent.