How OpenWork Handles Inference Provisioning and Model Access Control

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 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:

// 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 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
// 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:

{
  "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:

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 and its accompanying provider component.

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

// 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 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
// 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) 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) 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) store organization-specific API keys and allowedModels arrays, creating the source of truth for access control.
  • Client Enforcement (model-controls-store.ts and 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 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. 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 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 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 will automatically refresh on the next client sync, making the model available in the UI without requiring a client restart.

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 →