# How OpenMAIC Manages Document Updates with Versioning: Optimistic Concurrency Deep Dive

> OpenMAIC versioning prevents lost updates using optimistic concurrency. Learn how OpenMAIC rejects stale writes and ensures data integrity for your document management.

- Repository: [MAIC/OpenMAIC](https://github.com/THU-MAIC/OpenMAIC)
- Tags: deep-dive
- Published: 2026-09-10

---

**OpenMAIC prevents lost updates and race conditions by requiring every document write to include an incrementing version number; the system rejects stale writes with a `409 FUTURE_VERSION` error, forcing clients to re-fetch the latest state before retrying.**

The THU-MAIC/OpenMAIC repository implements a robust versioning strategy for collaborative document editing. By embedding a monotonic version counter within every document's metadata and enforcing optimistic concurrency checks at the persistence layer, OpenMAIC ensures that distributed sessions never overwrite each other's changes accidentally.

## How OpenMAIC Implements Versioned Document Storage

OpenMAIC treats every editable entity—whiteboard scenes, notebooks, and user-generated documents—as an immutable record where the **version field** acts as a logical clock. This design provides three fundamental guarantees for concurrent access.

### Optimistic Concurrency via Version Checks

The system stores a `version` integer within each document's metadata structure. When a client proposes an update via `stage.setItem`, the underlying persistence layer in [`lib/store/kv-persist.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/store/kv-persist.ts) validates that the submitted version equals the latest stored version plus one. If the stored version is greater than or equal to the submitted version, the write is rejected immediately.

This mechanism prevents the "lost update" problem without requiring distributed locks. The check occurs atomically during the write operation, ensuring that only the holder of the most recent version can successfully commit changes.

### Atomic Snapshots and Metadata Consistency

Updates are persisted as atomic units containing both state and version. The `snapshot` module in [`lib/store/snapshot.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/store/snapshot.ts) generates immutable snapshots that embed the current version number, ensuring that readers always retrieve a consistent view of the document without observing partial writes. When persisted via `kv.set`, the entire payload—including the incremented version—is written in a single transaction.

## The Document Update Flow in OpenMAIC

OpenMAIC follows a strict **read-modify-write** cycle that clients must implement to maintain consistency. This pattern ensures that every mutation builds upon the most recent known state.

### Step 1: Reading the Current Version

Before editing, the client fetches the document including its version metadata through the high-level API:

```typescript
// Fetch current document from lib/store/stage.ts
const doc = await stage.getItem('my-notebook');
// doc contains: { state: {...}, version: number }

```

### Step 2: Local Mutation and Version Increment

The application applies local edits and increments the version field to signal intent to update:

```typescript
const updated = {
  ...doc,
  state: { ...doc.state, title: 'Updated Title' },
  version: doc.version + 1,  // Critical: bump version
};

```

### Step 3: Conditional Write with Conflict Detection

The client submits the updated payload through `stage.setItem`, which delegates to the persistence layer:

```typescript
await stage.setItem('my-notebook', updated);

```

Internally, [`lib/store/kv-persist.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/store/kv-persist.ts) performs the version validation before writing to the key-value store:

```typescript
// Simplified from lib/store/kv-persist.ts
export async function setItem(
  name: string, 
  payload: { state: any; version: number }
) {
  const existing = await kv.get(name);
  if (existing && existing.version >= payload.version) {
    throw new HttpRuntimeStoreError(
      409, 
      'FUTURE_VERSION', 
      'newer runtime DSL version'
    );
  }
  await kv.set(name, payload);
}

```

If the check passes, the new document state and version are persisted atomically.

### Step 4: Real-Time Synchronization

Upon successful persistence, the new version is broadcast to other active sessions through the workbench's real-time sync channel. This ensures all participants observe the same monotonic version sequence, allowing them to detect stale state immediately.

## Key Implementation Files in OpenMAIC

The versioning system spans several modules with distinct responsibilities:

- **[`lib/store/stage.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/store/stage.ts)** – Provides the public facade for document CRUD operations. This file implements `getItem` and `setItem` methods that orchestrate version checks and snapshot creation before delegating to the persistence layer.

- **[`lib/store/kv-persist.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/store/kv-persist.ts)** – Implements the low-level key-value storage wrapper that enforces version monotonicity. This module contains the critical comparison logic that throws `HttpRuntimeStoreError` with status `409` and code `FUTURE_VERSION` when detecting stale writes.

- **[`lib/store/snapshot.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/store/snapshot.ts)** – Generates immutable snapshots embedding version metadata. This utility ensures that document state captures are atomic and include the necessary version information for subsequent optimistic concurrency checks.

- **[`tests/store/stage-document-persistence.test.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/tests/store/stage-document-persistence.test.ts)** – Integration test suite verifying end-to-end version enforcement, ensuring that `stage.setItem` correctly propagates version conflicts from the underlying store.

- **[`tests/store/kv-persist.test.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/tests/store/kv-persist.test.ts)** – Unit tests for the persistence layer specifically exercising race condition handling and verifying that only strictly increasing version numbers are accepted.

## Handling Version Conflicts and Recovery

When a client attempts to write using an outdated version number, the system responds with a specific error signature that enables graceful client-side handling.

### The FUTURE_VERSION Error Contract

As implemented in [`lib/store/kv-persist.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/store/kv-persist.ts), version conflicts produce a `HttpRuntimeStoreError` with HTTP status `409` and error code `FUTURE_VERSION`. This signals that the server's current version is newer than the version supplied by the client.

```typescript
// Test case from tests/store/kv-persist.test.ts
await persist.setItem(NAME, { state: { nickname: 'Ada' }, version: 4 });
await expect(
  persist.setItem(NAME, { state: { nickname: 'Bob' }, version: 4 })
).rejects.toMatchObject({ 
  status: 409, 
  code: 'FUTURE_VERSION' 
});

```

### Client Recovery Strategy

Upon receiving a `409 FUTURE_VERSION` error, the standard recovery pattern requires the client to re-fetch the current document via `stage.getItem`, merge or reapply local changes to the new state, increment the updated version number, and retry the write operation. This enforced retry loop guarantees that no update is silently lost due to network reordering or concurrent modifications.

## Summary

OpenMAIC manages document updates through a lightweight yet robust optimistic concurrency system:

- **Version Monotonicity**: Every document carries an integer version that must strictly increase with each successful write.
- **Conflict Prevention**: The persistence layer in [`lib/store/kv-persist.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/store/kv-persist.ts) rejects stale writes with a `409 FUTURE_VERSION` error before any data corruption occurs.
- **Atomic Operations**: Document state and version are written together as immutable snapshots, ensuring readers never observe partial updates.
- **Distributed Consistency**: Real-time synchronization broadcasts version updates across sessions, maintaining a consistent view of document history without heavy locking protocols.

## Frequently Asked Questions

### What happens when two users edit the same document simultaneously in OpenMAIC?

The first write to reach the persistence layer with the correct next version number succeeds and increments the stored version. Subsequent writes from other clients supply the now-stale version number, causing [`lib/store/kv-persist.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/store/kv-persist.ts) to reject them with a `409 FUTURE_VERSION` error. Those clients must re-fetch the updated document and retry their changes.

### How does OpenMAIC recover document state after a crash or reload?

During initialization, the runtime compares the local in-memory version with the persisted version retrieved from the key-value store via `stage.getItem`. If the persisted version is newer, the system loads the external state; if the local version is newer (indicating unsaved changes before the crash), the system can attempt to reconcile or persist the local changes, depending on the specific recovery policy implemented in the `stage` and `canvas` subsystems.

### Where exactly is the version number stored within an OpenMAIC document?

The version field resides at the top level of the document object structure alongside the state payload. According to the type signatures in [`lib/store/kv-persist.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/store/kv-persist.ts), persisted documents follow the structure `{ state: any; version: number }`, where version is a monotonically increasing integer managed entirely by the storage layer and client coordination.

### Can developers disable version checking for non-critical document updates?

No, the versioning mechanism is intrinsic to the `setItem` implementation in [`lib/store/kv-persist.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/store/kv-persist.ts) and cannot be bypassed through the public API. This design invariant ensures data consistency across all document types, preventing accidental configuration of unsafe write paths that could lead to silent data loss in collaborative environments.