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:
- Manual override via the
OPENCODE_MODELS_URLenvironment variable - Local development server check when
OPENWORK_DEV_MODE === "1" - 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:
- User selection stores the model alias (e.g.,
openwork/<alias>) in the UI state - Client request transmits the identifier to
/v1/chat/completionsor similar endpoints - Server resolution calls
resolveModelAliasagainst the enabledModels list loaded from the organization's LLM provider records - Access verification returns a
403 Forbiddenresponse 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 anenabledflag 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 onOPENWORK_DEV_MODEand environment overrides. - LLM Provider Records (defined in
packages/docs/openapi.json) store organization-specific API keys andallowedModelsarrays, creating the source of truth for access control. - Client Enforcement (
model-controls-store.tsandmodel-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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →