How Model Behavior Is Configured Within OpenWork: A Layered LLM Configuration System

OpenWork configures model behavior through a layered system that combines global defaults, workspace-specific variants, and session-level overrides, with normalization logic ensuring consistent variant mapping across the application.

The different-ai/openwork repository implements a sophisticated configuration architecture for controlling Large Language Model (LLM) behavior. This system allows users to specify not only which model provider to use (OpenAI, Anthropic, Google) but also how that model should operate through cost-versus-quality variants. Understanding how model behavior is configured within OpenWork requires examining five distinct configuration layers that work together to provide flexibility at the global, workspace, and session levels.

The Five Layers of Model Configuration

OpenWork's configuration hierarchy ensures that user preferences propagate correctly from global defaults down to individual chat sessions. Each layer serves a specific purpose in the decision chain.

Global Default Model

The foundation of the configuration system is the default model, which acts as a global fallback when no specific overrides exist. This value is persisted in the browser's localStorage under the key defined by MODEL_PREF_KEY.

In apps/app/src/react-app/kernel/model-config.ts, the readStoredDefaultModel function retrieves this value with proper error handling and fallback logic:

export function readStoredDefaultModel(): ModelRef {
  if (typeof window === "undefined") return DEFAULT_MODEL;
  try {
    const stored = window.localStorage.getItem(MODEL_PREF_KEY);
    return parseModelRef(stored) ?? DEFAULT_MODEL;
  } catch {
    return DEFAULT_MODEL;
  }
}

When users select a new default through the UI, writeStoredDefaultModel updates storage and dispatches a custom openwork.defaultModelChanged event. This allows reactive components to update immediately without requiring a page refresh.

Workspace Model Variants

Workspaces can define model variants that specify quality or reasoning depth (e.g., medium, high) for their default models. This configuration is stored under a namespaced key combining VARIANT_PREF_KEY with the specific workspaceId.

The parseWorkspaceModelVariants function in model-config.ts handles parsing and normalization:

export function parseWorkspaceModelVariants(
  raw: string | null,
  fallbackModel: ModelRef = DEFAULT_MODEL,
): Record<string, string> {
  // …JSON‑parse + normalizeModelBehaviorValue…
}

If parsing fails, the system falls back to a normalized variant appropriate for the fallback model, ensuring workspaces always have valid configuration even when data corruption occurs.

Session-Level Overrides

Individual chat sessions may temporarily override both the model and variant selections. These session overrides take precedence over workspace and global defaults, allowing users to experiment with different configurations without changing their persistent preferences.

The system stores these as a JSON map where each session ID maps to a SessionChoiceOverride object containing optional model and variant properties:

export function parseSessionChoiceOverrides(
  raw: string | null,
): Record<string, SessionChoiceOverride> {
  // …JSON‑parse → per‑session model/variant extraction…
}

The companion function serializeSessionChoiceOverrides handles the reverse operation when persisting changes back to storage.

Normalization and Sanitization

User input from dropdowns or imported configurations requires normalization to ensure consistency. The normalizeModelBehaviorValue function in apps/app/src/app/lib/model-behavior.ts treats synonyms like "balance", "balanced", "default", and "provider-default" as null values, indicating the system should use the provider's native default behavior.

export const normalizeModelBehaviorValue = (value: string | null) => {
  if (!value) return null;
  const normalized = value.trim().toLowerCase();
  if (["balance","balanced","default","provider-default"].includes(normalized)) {
    return null;
  }
  return normalized;
};

This normalization prevents legacy values (such as the deprecated "balance" variant) from causing runtime errors while allowing users to express their intent through multiple common terms.

Variant Metadata and UI Presentation

The final layer provides human-readable metadata for each variant. The getModelBehaviorOptions function inspects a provider's ProviderModel definition to discover available variants, sorts them using a well-known priority order, and returns structured data for UI rendering.

export const getModelBehaviorOptions = (
  providerID: string,
  model: ProviderModel,
  providerName?: string | null,
): ModelBehaviorOption[] => {
  const variantKeys = sortVariantKeys(getVariantKeys(model));
  return variantKeys.map((key) => ({
    value: key,
    label: getVariantLabel(key),
    description: getVariantDescription(providerID, key, getVariantLabel(key), providerName),
  }));
};

Descriptions are provider-specific and sourced from localization keys (e.g., t("model_behavior.desc_…")), ensuring users see relevant explanations for Anthropic's Claude variants versus OpenAI's GPT models. The getModelBehaviorSummary function combines these elements into concise UI cards that display the current configuration state.

Core Implementation Files

Understanding the file structure helps developers navigate the configuration system:

Practical Configuration Examples

Reading the current default model in a React component:

import { readStoredDefaultModel } from '@/react-app/kernel/model-config';

const defaultModel = readStoredDefaultModel(); 
// → { providerID: 'openai', modelID: 'gpt-4' }

Retrieving available behavior options for a specific provider model:

import { getModelBehaviorOptions } from '@/app/lib/model-behavior';
import type { ProviderModel } from '@/app/types';

const options = getModelBehaviorOptions('anthropic', myModel as ProviderModel);
// options = [{ value: 'low', label: 'Low', description: '...' }, …]

Persisting session-specific overrides:

import { serializeSessionChoiceOverrides } from '@/react-app/kernel/model-config';

const overrides = {
  'session-123': { 
    model: { providerID: 'anthropic', modelID: 'claude-2' }, 
    variant: 'high' 
  },
};
const json = serializeSessionChoiceOverrides(overrides);
// json → '{"session-123":{"model":"anthropic/claude-2","variant":"high"}}'

Using the reactive hook for default model management:

import { useDefaultModel } from '@/react-app/kernel/model-config';

function ModelSwitcher() {
  const [model, setModel] = useDefaultModel();

  return (
    <select value={model.modelID} onChange={e => setModel({ ...model, modelID: e.target.value })}>
      {/* options built from provider list */}
    </select>
  );
}

Summary

  • Global defaults provide the base configuration via localStorage and readStoredDefaultModel, ensuring the application always has a valid fallback model.
  • Workspace variants allow teams to specify quality preferences (low, medium, high) per workspace using parseWorkspaceModelVariants and namespaced storage keys.
  • Session overrides enable temporary experimentation through parseSessionChoiceOverrides, which takes precedence over all other configuration layers.
  • Normalization logic in normalizeModelBehaviorValue sanitizes legacy values and synonyms to prevent configuration errors.
  • Metadata generation via getModelBehaviorOptions creates provider-specific UI labels and descriptions that adapt to Anthropic, OpenAI, and Google model specifications.

Frequently Asked Questions

How does OpenWork handle invalid or legacy model behavior values?

OpenWork sanitizes user input through the normalizeModelBehaviorValue function in model-behavior.ts. Values like "balance", "balanced", "default", or "provider-default" are normalized to null, which signals the system to use the provider's native default behavior. This prevents runtime errors from deprecated configuration values while maintaining backward compatibility.

Can different workspaces use different model quality settings?

Yes. OpenWork supports workspace model variants through the parseWorkspaceModelVariants function. Each workspace can store a specific variant (e.g., high for maximum reasoning depth or low for cost savings) under a unique localStorage key constructed from VARIANT_PREF_KEY and the workspace ID. These settings apply only within that workspace context.

What happens when a user selects a different model for a single chat session?

The system creates a session override using parseSessionChoiceOverrides. This stores a temporary mapping of session IDs to specific model and variant choices. Session overrides take precedence over both workspace variants and global defaults, but they do not modify the persisted default configuration. When the session ends or the user clears overrides, the system reverts to the workspace or global default.

Where is the model configuration state managed in the React frontend?

The configuration state is managed by Zustand stores in model-controls-store.ts, with persistence handled by the functions in model-config.ts. UI components like model-picker-modal.tsx and model-behavior-select.tsx consume this state through hooks such as useDefaultModel, ensuring reactive updates across the application when configuration changes occur.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →