# How OpenWork Handles Inference Provisioning and Model Access Control

> OpenWork manages inference provisioning with a three-tier architecture. Discover how it enforces model access control using client-side filtering and server-side validation.

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

---

**OpenWork provisions inference through a three-tier architecture combining a static model catalog, dynamic URL resolution for development versus production, and organization-specific LLM provider records that enforce access control via client-side filtering and server-side validation.**

OpenWork's approach to inference provisioning and model access control balances flexibility with security by separating the global model catalog from organization-specific permissions. The system implemented in the `different-ai/openwork` repository uses TypeScript-based resolution layers and Den API records to determine which models a user can access, ensuring that inference requests are routed only to explicitly enabled endpoints.

## The Model Catalog Foundation

At the core of OpenWork's inference system lies a static JSON catalog that defines every model the platform can route to. This catalog is processed at startup in [`ee/apps/inference/src/model-catalog.ts`](https://github.com/different-ai/openwork/blob/main/ee/apps/inference/src/model-catalog.ts) to create an array of **ModelCatalogEntry** objects that control availability and routing weights.

### Defining Model Capabilities

Each entry in the model catalog specifies metadata critical to provisioning decisions:

```ts
// ee/apps/inference/src/model-catalog.ts
export type ModelCatalogEntry = {
  alias: string;          // e.g. "moonshotai/kimi-k2.6"
  upstreamModel: string; // provider-specific identifier
  displayName: string;   // friendly name shown in UI
  enabled: boolean;      // only enabled entries are exposed to users
  usageFactor: number;   // weight for usage-based routing
};

```

The helper functions `resolveModelAlias`, `resolveModelByUpstreamModel`, and `listModelCatalog` filter exclusively on the `enabled` flag, ensuring unavailable models are never exposed to the UI or inference pipeline.

## Dynamic Catalog Source Resolution

OpenWork desktop clients automatically determine whether to connect to a local development stack or the hosted production catalog without manual configuration. This logic resides in [`apps/server/src/opencode-models-url.ts`](https://github.com/different-ai/openwork/blob/main/apps/server/src/opencode-models-url.ts) and evaluates environment variables at runtime.

The resolution hierarchy follows this priority:

1. Manual override via the `OPENCODE_MODELS_URL` environment variable
2. Local development server check when `OPENWORK_DEV_MODE === "1"`
3. Production catalog fallback

```ts
// apps/server/src/opencode-models-url.ts
export async function resolveOpencodeModelsUrl(...): Promise<string> {
  const env = options.env ?? process.env;
  const override = env.OPENCODE_MODELS_URL?.trim(); // manual override
  if (override) return override;

  // In a dev session (OPENWORK_DEV_MODE === "1") try the local stack first
  if (env.OPENWORK_DEV_MODE === "1") {
    const resp = await fetch(`${LOCAL_MODELS_URL}/api.json`, { 
      signal: AbortSignal.timeout(1_000) 
    });
    if (resp.ok) return LOCAL_MODELS_URL;
  }
  return PRODUCTION_MODELS_URL; // fallback
}

```

This automatic switching ensures developers can test against local inference stacks while production deployments seamlessly route to the managed catalog.

## Organization-Level LLM Provider Configuration

Model access control operates at the organization level through **LLM Provider** records stored in OpenWork's Den database. These records contain API credentials, whitelisted models, and permission settings that define which inference endpoints a specific organization may utilize.

The Den API exposes these configurations via the **LLM Providers** section of the OpenAPI specification in [`packages/docs/openapi.json`](https://github.com/different-ai/openwork/blob/main/packages/docs/openapi.json):

```json
{
  "name": "LLM Providers",
  "description": "Organization LLM provider catalog, configuration, and access routes."
}

```

Administrators manage these records through REST endpoints that support creating providers with specific `allowedModels` arrays:

```bash
curl -X POST https://api.openworklabs.com/den/v1/llm/providers \
  -H "Authorization: Bearer $SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
        "name": "Acme Azure",
        "type": "azure",
        "apiKey": "••••••",
        "allowedModels": ["gpt-4o", "gpt-4o-mini"]
      }'

```

Because provider records are bound to organization session tokens, every authenticated desktop client automatically receives the appropriate access constraints.

## Client-Side Access Enforcement

When users open the model picker, the UI merges the global catalog with organization-specific provider data to render only permitted options. This state management occurs in [`apps/app/src/react-app/domains/settings/state/model-controls-store.ts`](https://github.com/different-ai/openwork/blob/main/apps/app/src/react-app/domains/settings/state/model-controls-store.ts) and its accompanying provider component.

The **model-controls-store** holds the runtime state of enabled models:

```ts
// apps/app/src/react-app/domains/settings/state/model-controls-store.ts
export const useModelControls = create<ModelControlsState>((set) => ({
  // …
  enabledModels: [], // filled after fetching org LLM providers
}));

```

The **model-controls-provider.tsx** wrapper supplies filtered model lists to UI components like [`model-select.tsx`](https://github.com/different-ai/openwork/blob/main/model-select.tsx) and enforces role-based permissions. Only users possessing roles that include the **"use models"** capability can view and select specific models, providing immediate UX feedback before any network request occurs.

## Server-Side Validation and Inference Flow

The final security gate operates at the inference layer, validating every request against the organization's enabled model set. When a user submits a chat completion request, the server processes the model identifier through the same resolution logic used client-side.

The request flow follows this sequence:

1. **User selection** stores the model alias (e.g., `openwork/<alias>`) in the UI state
2. **Client request** transmits the identifier to `/v1/chat/completions` or similar endpoints
3. **Server resolution** calls `resolveModelAlias` against the **enabledModels** list loaded from the organization's LLM provider records
4. **Access verification** returns a `403 Forbidden` response if the model is not explicitly enabled

```ts
// ee/apps/inference/src/handler.ts (simplified)
const model = resolveModelAlias(request.model);
if (!model) {
  return res.status(403).json({ 
    error: 'Model not permitted for this organization' 
  });
}

```

This server-side enforcement guarantees that even if client-side checks are bypassed, unauthorized model access is blocked at the infrastructure layer.

## Summary

- **Model Catalog** ([`ee/apps/inference/src/model-catalog.ts`](https://github.com/different-ai/openwork/blob/main/ee/apps/inference/src/model-catalog.ts)) defines all available models with an `enabled` flag and usage metadata, providing the foundational whitelist for the system.
- **Source Resolution** ([`apps/server/src/opencode-models-url.ts`](https://github.com/different-ai/openwork/blob/main/apps/server/src/opencode-models-url.ts)) automatically routes clients between local development stacks and production catalogs based on `OPENWORK_DEV_MODE` and environment overrides.
- **LLM Provider Records** (defined in [`packages/docs/openapi.json`](https://github.com/different-ai/openwork/blob/main/packages/docs/openapi.json)) store organization-specific API keys and `allowedModels` arrays, creating the source of truth for access control.
- **Client Enforcement** ([`model-controls-store.ts`](https://github.com/different-ai/openwork/blob/main/model-controls-store.ts) and [`model-controls-provider.tsx`](https://github.com/different-ai/openwork/blob/main/model-controls-provider.tsx)) filters the UI model picker based on user roles and the "use models" permission capability.
- **Server Validation** performs a final lookup via `resolveModelAlias`, returning HTTP 403 for any model not present in the organization's enabled set.

## Frequently Asked Questions

### How does OpenWork determine which model catalog to use?

OpenWork uses the `resolveOpencodeModelsUrl` function in [`apps/server/src/opencode-models-url.ts`](https://github.com/different-ai/openwork/blob/main/apps/server/src/opencode-models-url.ts) to determine the catalog source. It first checks for the `OPENCODE_MODELS_URL` environment variable for manual overrides. If running in development mode (`OPENWORK_DEV_MODE === "1"`), it attempts to connect to `LOCAL_MODELS_URL`; otherwise, it falls back to `PRODUCTION_MODELS_URL`. This ensures developers automatically use local inference stacks while production deployments connect to the managed catalog.

### Where are organization API keys and allowed models stored?

Organization API keys and model whitelists are stored in **LLM Provider** records within the Den database. These records are managed via the **LLM Providers** endpoints defined in [`packages/docs/openapi.json`](https://github.com/different-ai/openwork/blob/main/packages/docs/openapi.json). Each provider object contains the authentication credentials, provider type (e.g., Azure, OpenAI), and an `allowedModels` array that explicitly defines which models the organization can access.

### What prevents a user from accessing a model they are not authorized to use?

OpenWork employs a dual-layer enforcement strategy. First, the UI layer in [`model-controls-provider.tsx`](https://github.com/different-ai/openwork/blob/main/model-controls-provider.tsx) filters the model picker to show only models listed in the organization's LLM provider records, respecting role-based permissions. Second, the inference server in [`ee/apps/inference/src/handler.ts`](https://github.com/different-ai/openwork/blob/main/ee/apps/inference/src/handler.ts) validates every request by calling `resolveModelAlias` against the enabled model set; if the model is not found, the server returns a `403 Forbidden` error, providing defense-in-depth against unauthorized access.

### How do I add a new model to my organization's available list?

To add a new model, an administrator must update the organization's LLM provider record via the Den API. Send a PATCH or POST request to `/den/v1/llm/providers` with the provider ID and an updated `allowedModels` array containing the new model identifier (e.g., `"moonshotai/kimi-k2.6"`). The [`model-controls-store.ts`](https://github.com/different-ai/openwork/blob/main/model-controls-store.ts) will automatically refresh on the next client sync, making the model available in the UI without requiring a client restart.