How Desktop App Restrictions Sync from the Den Control Plane in OpenWork

OpenWork syncs desktop app restrictions from the Den control plane through a policy-based architecture where the Den "org" service stores JSON policies, the MCP exposes them via getDesktopPolicies, and the desktop client calculates an effective policy that controls feature gates locally.

The OpenWork ecosystem separates policy management from policy enforcement. Rather than embedding restrictions directly in the desktop application, all Desktop Policies live in the Den control plane—the multi-tenant "org" service that administrators interact with through the web UI. This architecture ensures centralized governance while allowing real-time restriction updates across distributed desktop clients.

What Is a Desktop Policy in OpenWork?

A Desktop Policy is a JSON document that defines which features a user can access in the OpenWork desktop application. Policies follow the schema defined in packages/types/src/den/desktop-policies.ts and control flags such as allowCustomProviders, allowLocalModels, or allowExternalIntegrations.

Administrators manage two types of policies:

  • Default policy — applied organization-wide when no specific assignment exists
  • Assigned policies — targeted to individual members or teams, overriding or extending the default

The desktop application never hard-codes these values. Instead, it treats Den as the single source of truth and computes permissions dynamically at runtime.

The Five-Step Synchronization Flow

The sync process from Den to desktop follows a predictable pipeline:

Step Action Key Component
1 Admin creates or edits a policy in Den UI desktop-policy-editor-screen.tsx
2 Den persists policy to database desktopPolicy and desktopPolicyMember tables
3 Desktop app fetches via MCP getDesktopPolicies capability
4 Client calculates effective policy calculateEffectiveDesktopPolicy
5 App caches and enforces locally desktop-config store with hourly refresh

Step 1: Policy Creation and Storage

When an org owner modifies restrictions through the Den dashboard, the web client sends requests to the REST API at /v1/desktop-policies. The route handler in ee/apps/den-api/src/routes/org/desktop-policies.ts supports full CRUD operations:

// From den-api/src/routes/org/desktop-policies.ts
// GET /v1/desktop-policies - list policies
// POST /v1/desktop-policies - create new policy
// PATCH /v1/desktop-policies/:id - update existing policy
// DELETE /v1/desktop-policies/:id - remove policy

Policies are stored in two tables: desktopPolicy holds the policy documents, while desktopPolicyMember tracks which users or teams receive assigned policies.

Step 2: Desktop Client Discovery via MCP

The OpenWork desktop application communicates with Den through the Model Context Protocol (MCP). During authentication, or when triggered by specific events, the desktop client searches for and invokes the getDesktopPolicies capability:

// Using the OpenWork MCP client from any agent
import { searchCapabilities, executeCapability } from "openwork-mcp";

async function loadDesktopPolicy() {
  // Discover the capability
  const caps = await searchCapabilities("desktop policy");
  if (!caps.includes("getDesktopPolicies")) {
    throw new Error("MCP does not expose desktop-policy tools");
  }

  // Execute the tool
  const result = await executeCapability("getDesktopPolicies");
  if (result.error) {
    throw new Error(`MCP error: ${result.error.message}`);
  }
  // result.body contains { desktopPolicy: … }
  return result.body.desktopPolicy;
}

This flow is validated by the end-to-end test in evals/flows/desktop-policies-cloud-mcp.flow.ts, which proves capability discovery and policy retrieval work correctly across the network boundary.

Step 3: Effective Policy Calculation

Raw policies from Den are not applied directly. The desktop client runs calculateEffectiveDesktopPolicy to merge multiple policy sources into a single authoritative configuration:

import {
  calculateEffectiveDesktopPolicy,
  normalizeDesktopPolicyDocument,
} from "@/packages/types/src/den/desktop-policies";

function effectivePolicy(defaultPolicy: unknown, assignedPolicies: unknown[]) {
  const effective = calculateEffectiveDesktopPolicy({
    orgPolicyCount: assignedPolicies.length,
    defaultPolicy,
    assignedPolicies,
  });
  return effective; // => Required<DesktopPolicyValue>
}

The algorithm in desktop-policies.ts implements an "allow-style" Boolean merge where true wins. If any assigned policy grants a permission, the user receives that permission—even if the default policy denies it. This enables flexible, layered access control.

Step 4: Local Enforcement and Caching

The computed Required<DesktopPolicyValue> is stored in the desktop-config cache. Desktop components access this cache through hooks like useDesktopConfig:

import { useDesktopConfig } from "@/hooks/useDesktopConfig";

export function ProviderSelector() {
  const cfg = useDesktopConfig(); // returns the effective policy
  if (!cfg.allowCustomProviders) {
    return null; // UI is hidden because the org disabled custom providers
  }
  return <CustomProviderList />;
}

This pattern appears throughout desktop-policy-editor-screen.tsx and related components. Feature gates consult the cached policy synchronously, ensuring UI responsiveness.

Step 5: Automatic Refresh Triggers

The desktop client refreshes its policy cache without user intervention when any of these events occur:

  • The active Cloud organization changes
  • The Cloud account token changes
  • The hourly desktop-config background refresh runs

When an admin updates a policy in Den, the change propagates on the next fetch cycle. The UI immediately reflects new restrictions—features disappear or become enabled based on the recalculated effective policy.

Key Source Files for Desktop Policy Sync

Understanding the complete flow requires familiarity with these files:

File Path Purpose
packages/types/src/den/desktop-policies.ts Schema definitions, default values, normalization, and calculateEffectiveDesktopPolicy implementation
ee/apps/den-api/src/routes/org/desktop-policies.ts REST API endpoints for policy CRUD operations
ee/apps/den-web/app/(den)/dashboard/_components/desktop-policy-editor-screen.tsx Admin UI for policy management
packages/docs/cloud/share-with-your-team/desktop-policies.mdx User-facing documentation explaining policy controls
evals/flows/desktop-policies-cloud-mcp.flow.ts Integration test validating MCP-based policy synchronization

Updating Policies Programmatically

Organizations with custom tooling can modify desktop restrictions directly against the Den API:

import fetch from "node-fetch";

async function patchDesktopPolicy(policyId: string, patch: any, token: string) {
  const resp = await fetch(
    `https://api.openworklabs.com/v1/desktop-policies/${policyId}`,
    {
      method: "PATCH",
      headers: {
        Authorization: `Bearer ${token}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify(patch),
    },
  );
  if (!resp.ok) {
    const err = await resp.json();
    throw new Error(`Den error ${resp.status}: ${err.error}`);
  }
  return await resp.json(); // Returns the updated policy
}

Changes made through this endpoint become visible to desktop clients on their next policy fetch.

Summary

  • Den control plane stores all desktop policies in desktopPolicy and desktopPolicyMember tables
  • MCP capability getDesktopPolicies exposes organization policies to authenticated desktop clients
  • Effective policy calculation merges default and assigned policies with an allow-override algorithm
  • Local caching in desktop-config enables fast feature gate checks with hourly refresh
  • Automatic propagation ensures restriction changes reach clients without manual action

This architecture centralizes governance while maintaining performant, offline-capable enforcement—critical for desktop applications that must respect organizational boundaries regardless of network conditions.

Frequently Asked Questions

How quickly do policy changes reach desktop clients?

Policy changes propagate on the next fetch cycle, which occurs when the user switches organizations, refreshes their authentication token, or during the automatic hourly refresh. Most clients receive updates within 60 minutes, though active users will see changes immediately if they trigger a manual refresh.

Can desktop clients override or ignore Den policies?

No. The desktop application treats Den as the authoritative source. While policies are cached locally for performance, the cache is read-only from the client's perspective. The calculateEffectiveDesktopPolicy function runs client-side for latency reasons, but it operates strictly on data retrieved from the MCP—no local overrides exist in the source code.

What happens if the desktop client cannot reach Den?

The client continues operating with its cached effective policy from the last successful fetch. Feature gates use the stale cache rather than failing open or closed. Once connectivity restores, the next successful getDesktopPolicies call refreshes the cache and applies any pending restriction changes.

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 →