# How OpenWork Manages Session and Workspace State in the Desktop Application

> Discover how OpenWork manages session and workspace state using a four-layer architecture with Zod validation, SQLite KV store, and atomic updates for reliable desktop application performance.

- Repository: [Different AI/openwork](https://github.com/different-ai/openwork)
- Tags: internals
- Published: 2026-07-31

---

**OpenWork uses a four-layer state architecture that combines Zod-validated read models for session data, a SQLite-backed workspace-scoped KV store, atomic session-group state management with serialized updates, and deterministic workspace identification to maintain reliable state across the desktop application.**

Managing complex state in desktop applications requires careful separation between transient UI data and persistent configuration. In the `different-ai/openwork` repository, the desktop application implements a robust system for handling both interactive conversation data from the OpenCode SDK and local workspace-specific settings. Understanding how session and workspace state is managed reveals a design that prioritizes type safety, data consistency, and reliable workspace identification through four complementary architectural layers.

## The Four-Layer State Architecture

OpenWork organizes state management into distinct layers, each handling specific concerns from SDK response validation to local SQLite persistence.

### Session Read Model Layer

The first layer normalizes raw JSON returned by the OpenCode SDK into typed, safe objects that the UI can rely on. Located in [`dev/apps/server/src/session-read-model.ts`](https://github.com/different-ai/openwork/blob/main/dev/apps/server/src/session-read-model.ts), this layer defines Zod schemas for time stamps, summaries, status updates, todos, and messages.

The SDK returns several response shapes including `SessionGetResponse`, `SessionMessagesResponse2`, `SessionStatusResponse`, and `SessionTodoResponse`. The `buildSessionList` and related helpers use `parseOrThrow` to validate these payloads against strict schemas. Invalid payloads raise an `ApiError` with code 502, allowing the desktop app to surface clear error messages rather than propagating malformed data.

### Workspace-KV Store Layer

For per-workspace mutable state such as session-group assignments and custom configuration, OpenWork implements a generic key-value store backed by SQLite. The implementation in [`dev/apps/server/src/workspace-kv-store.ts`](https://github.com/different-ai/openwork/blob/main/dev/apps/server/src/workspace-kv-store.ts) provides `createWorkspaceKvStore<T>`, which builds a typed store for any JSON-serializable type.

This layer creates a table with `workspace_id TEXT PRIMARY KEY` on demand and uses UPSERT statements for atomic writes. All reads and writes are asynchronous and scoped to a specific `workspaceId`, which is derived from the workspace configuration. This ensures that settings for different projects remain isolated while sharing the same database schema.

### Session-Group State Layer

Building atop the KV store, the session-group state layer provides higher-level organization of sessions through grouping, ordering, and assignment management. The `SessionGroupState` type defined in [`dev/apps/server/src/session-groups.ts`](https://github.com/different-ai/openwork/blob/main/dev/apps/server/src/session-groups.ts) consists of a list of groups plus a map of `sessionId → groupId`.

Normalisation functions enforce length limits and unique IDs before persistence. The module exports `readSessionGroupState`, `writeSessionGroupState`, and `updateSessionGroupState` functions that interact with the `session_group_states` table in the workspace-KV store. To prevent race conditions, an in-memory queue (`updateQueueByWorkspace`) guarantees serialized updates per workspace, ensuring that concurrent modifications to group assignments do not corrupt state.

### Workspace Identification and Selection

The final layer handles the transformation of workspace configurations into stable identifiers used by the KV store and the OpenCode engine. In [`dev/apps/server/src/workspaces.ts`](https://github.com/different-ai/openwork/blob/main/dev/apps/server/src/workspaces.ts), the `workspaceIdForKey` function hashes a canonical string—whether a local path, remote base URL, or OpenWork host—into a deterministic ID.

The `findManagedEngineWorkspace` function scans the workspace list and returns the first local workspace with a non-empty path, determining where the OpenCode engine actually boots. This ensures that engine state and KV store operations align with the correct project environment.

## Working with Session and Workspace State in Code

The following examples demonstrate how to interact with these state layers in practice.

### Reading and Validating Session Data

To fetch sessions from the OpenCode SDK and convert them to type-safe read models:

```typescript
import { opencode } from "@opencode-ai/sdk/v2/client";
import { buildSessionList } from "./session-read-model.js";

async function loadSessions() {
  const response = await opencode.session.list({ limit: 200 });
  // `response` is typed as SessionListResponse by the SDK.
  const sessions = buildSessionList(response);
  return sessions; // SessionInfoReadModel[]
}

```

### Persisting Workspace-Scoped Settings

To create a custom store for UI state or other workspace-specific data:

```typescript
import { createWorkspaceKvStore } from "./workspace-kv-store.js";

type UIState = { showTodos: boolean };
const uiStateStore = createWorkspaceKvStore<UIState>({
  tableName: "ui_state",
  valueColumn: "state_json",
  parse: JSON.parse,
  serialize: JSON.stringify,
});

async function saveUiState(config, workspaceId, state: UIState) {
  await uiStateStore.set(config, workspaceId, state);
}

```

### Atomically Updating Session Groups

To modify session-group assignments with automatic serialization:

```typescript
import { updateSessionGroupState } from "./session-groups.js";

async function assignSessionToGroup(config, workspaceId, sessionId, groupId) {
  await updateSessionGroupState(config, workspaceId, (current) => {
    const newAssignments = { ...current.assignments, [sessionId]: groupId };
    return { ...current, assignments: newAssignments };
  });
}

```

### Selecting the Engine Workspace

To determine which workspace the OpenCode engine should start in:

```typescript
import { findManagedEngineWorkspace } from "./workspaces.js";

const workspaces = await loadWorkspaceConfig(); // [{ id, path, workspaceType, …}]
const engineWs = findManagedEngineWorkspace(workspaces);
if (engineWs) console.log("Engine will start in:", engineWs.path);

```

## Summary

- **Session read models** in [`session-read-model.ts`](https://github.com/different-ai/openwork/blob/main/session-read-model.ts) enforce type safety through Zod validation of SDK responses including `SessionGetResponse` and `SessionMessagesResponse2`.
- **Workspace-KV store** in [`workspace-kv-store.ts`](https://github.com/different-ai/openwork/blob/main/workspace-kv-store.ts) persists mutable state per workspace using SQLite with UPSERT operations and automatic table creation.
- **Session-group state** in [`session-groups.ts`](https://github.com/different-ai/openwork/blob/main/session-groups.ts) builds higher-level UI concepts atop the KV store with an in-memory queue (`updateQueueByWorkspace`) ensuring serialized updates.
- **Workspace identification** in [`workspaces.ts`](https://github.com/different-ai/openwork/blob/main/workspaces.ts) provides deterministic ID generation via `workspaceIdForKey` and engine workspace selection via `findManagedEngineWorkspace`.

## Frequently Asked Questions

### How does OpenWork ensure type safety when handling SDK responses?

OpenWork uses Zod schemas defined in [`session-read-model.ts`](https://github.com/different-ai/openwork/blob/main/session-read-model.ts) to parse all incoming SDK responses. Helper functions like `buildSessionList` call `parseOrThrow` on the raw JSON, which validates the data against TypeScript interfaces and throws an `ApiError` (502) for invalid payloads, preventing malformed data from reaching the UI.

### What database does OpenWork use for local state persistence?

The application uses SQLite for local state storage. The `createWorkspaceKvStore` function in [`workspace-kv-store.ts`](https://github.com/different-ai/openwork/blob/main/workspace-kv-store.ts) creates tables on demand with `workspace_id TEXT PRIMARY KEY` columns and performs atomic writes using SQLite's UPSERT syntax, ensuring that per-workspace data remains consistent even during concurrent access.

### How are session groups kept consistent when multiple updates occur?

The [`session-groups.ts`](https://github.com/different-ai/openwork/blob/main/session-groups.ts) module implements an in-memory queue called `updateQueueByWorkspace` that processes updates serially per workspace. When calling `updateSessionGroupState`, the function queues the update callback, ensuring that read-modify-write cycles do not interfere with each other and preventing race conditions in group assignments.

### How does OpenWork determine which workspace to start the engine in?

The `findManagedEngineWorkspace` function in [`workspaces.ts`](https://github.com/different-ai/openwork/blob/main/workspaces.ts) scans the configured workspace list and returns the first local workspace with a defined path. This workspace serves as the anchor for the OpenCode engine, while `workspaceIdForKey` generates stable identifiers for KV store operations based on the workspace's canonical path or remote URL.