How OpenWork Extensions and Plugin Architecture Work: A Technical Deep Dive
OpenWork’s extension system is built on OpenCode plugins that expose affordances—actions, queries, and UI commands—to the desktop app through a three-layer architecture comprising extension manifests, a Den API plugin system, and transactional store logic.
The different-ai/openwork repository implements a declarative, permission-checked plugin framework that allows agents to discover, create, and invoke extensions while maintaining secure access control. This architecture bridges the OpenCode agent runtime with the OpenWork desktop client, enabling seamless integration between AI agents and external tools.
Extension Manifest and UI Bridge
OpenWork extensions are defined by manifests that declare capabilities, UI contributions, and lifecycle detection rules. When the desktop app starts, the OpenWork-extensions-preview plugin loads built-in manifests from DEFAULT_OPENWORK_EXTENSION_MANIFESTS defined in dev/apps/server/src/opencode-plugins/openwork-extensions-preview.ts.
Each manifest specifies:
- Resources:
opencode-plugin,mcp,tool,secret - Contributions: Settings panels, side-panels, and composer prompts
- Enablement rules: Environment variables, platform checks, and permission flags
- Lifecycle detection: Patterns like
detection: ["plugin:opencode-chrome-devtools"]
Agent-Facing Tools
Agents interact with extensions through three core tool functions exposed by the UI bridge:
// Returns the full UI/affordance snapshot
openwork_context: {
description: "...",
async execute() { /* ... */ }
}
// Read-only affordance query
openwork_query: {
description: "...",
args: openworkAffordanceRequestSchema.shape,
async execute(rawArgs) { /* ... */ }
}
// Mutating command execution
openwork_execute: {
description: "...",
args: openworkAffordanceRequestSchema.shape,
async execute(rawArgs, context) { /* ... */ }
}
The UI bridge (uiBridgeRequest) discovers running OpenWork desktop processes via a JSON file (openwork-ui-control.json) in the user-specific config directory. This ensures agents operate against the actual client state rather than simulated browsers.
Plugin System API (Den)
The Den API provides REST-like endpoints for plugin lifecycle management under dev/ee/apps/den-api/src/routes/org/plugin-system/routes.ts. All routes are wrapped with withPluginArchOrgContext, which injects role-based authorization checks.
Core Endpoints
| Endpoint | Method | Purpose |
|---|---|---|
/v1/plugins |
POST | Create plugins with optional components and marketplace publishing |
/v1/plugins/:id |
GET | Retrieve plugin details including resolved memberships |
/v1/plugins/:id |
PATCH | Update name/description metadata |
/v1/plugins/:id/access |
POST | Grant access to members, teams, or org-wide |
/v1/me/library |
GET | List effective plugin library for the authenticated user |
Authorization Context
Every request processes through requirePluginArchCapability and requirePluginArchResourceRole checks. The middleware injects an actorContext containing the current member, team list, session data, and organization information, ensuring all operations respect the plugin_arch permission model.
Store Layer and Business Logic
The createPluginBundle function in dev/ee/apps/den-api/src/routes/org/plugin-system/store.ts serves as the core transaction engine for plugin creation.
Bundle Creation Workflow
The function executes six atomic steps within a database transaction (DbTransaction):
- Capability validation: Verifies
plugin.createpermission - Component parsing: Validates config object payloads (skills, agents, tools)
- Config persistence: Creates entries in
ConfigObjectTableandConfigObjectVersionTable - Plugin registration: Inserts the plugin record into
PluginTable - Marketplace publishing: Optionally adds to
MarketplacePluginTable - Org-wide distribution: Checks
isPluginArchOrgAdminbefore granting universal access
Serialization
The serializePlugin function returns a structured representation including extension metadata, marketplace summaries, and component counts (e.g., number of skill, agent, or tool objects). This serialization ensures consistent API responses across the plugin ecosystem.
Access Control and Security Model
OpenWork implements resource-grant tables to model permissions: PluginAccessGrantTable, ConfigObjectAccessGrantTable, and related entities.
Grant Types
orgWide: Grants access to all organization membersorgMembershipId: Specific member-level grantsteamId: Team-scoped access
The helper ensureGrantTargetsInOrganization validates that grants reference only members and teams within the caller’s organization, preventing cross-org privilege escalation. All grant operations are atomic and validate against the plugin_arch role hierarchy.
GitHub Import and MCP Integration
OpenWork supports importing Claude-compatible plugins from public GitHub repositories through an automated parsing pipeline.
Import Process
The system executes these steps via dev/ee/apps/den-api/src/routes/org/plugin-system/store.ts:
- Parse repository tree using
getPublicGithubRepositoryTree - Read
plugin.jsonandmarketplace.jsonconfiguration files - Validate skill front-matter using
parseSkillMarkdown - Create config objects for each discovered component
- Optionally publish to the organization's marketplace
MCP Configuration
Managed-Control-Plane (MCP) requirements follow the pluginMcpRequirementConfigureSchema. Administrators configure remote MCP servers (such as Computer Use), specify authentication types (apikey or oauth), and select credential modes (per_member or shared). The configurePluginMcpConnectionResponse endpoint stores these bindings in PluginMcpRequirementBindingTable.
Code Examples
Creating a Plugin via Den API
// Assuming 'creator' holds a valid session token
const newPlugin = await denFetch(creator, "/v1/plugins", {
method: "POST",
json: {
name: "Demo Plugin",
description: "Shows how to create a plugin",
components: [
{
type: "skill",
input: {
rawSourceText: `---\nname: demo-skill\ndescription: A demo skill.\n---\n\nReturn a greeting.`,
},
},
],
orgWide: false,
},
});
console.log("Created plugin ID:", newPlugin.body?.item?.id);
Source: dev/ee/apps/den-api/src/routes/org/plugin-system/routes.ts – route registration (line 130); dev/ee/apps/den-api/src/routes/org/plugin-system/store.ts – createPluginBundle implementation (line 2608).
Granting Member Access
await denFetch(creator, `/v1/plugins/${pluginId}/access`, {
method: "POST",
json: {
role: "viewer",
orgMembershipId: memberId,
},
});
Source: dev/ee/apps/den-api/src/routes/org/plugin-system/routes.ts – pluginAccess handler (line 722).
Querying Extension Manifests
// Inside an OpenCode agent context
const ctx = await openwork_context.execute();
const plugins = JSON.parse(ctx).availableAffordances
.filter(a => a.id.startsWith("plugin:"))
.map(a => a.id);
console.log("Available plugins:", plugins);
Source: dev/apps/server/src/opencode-plugins/openwork-extensions-preview.ts – openwork_context tool definition (line 200).
Executing Plugin Actions
await openwork_execute.execute({
id: "extension.call",
args: {
extensionId: "google-workspace",
action: "calendar.read",
args: { start: "2024-01-01", end: "2024-01-31" },
},
});
Source: dev/apps/server/src/opencode-plugins/openwork-extensions-preview.ts – executeOpenworkAffordance handling (line 560).
Summary
- OpenWork extensions rely on OpenCode plugins exposing affordances through a manifest-based system defined in
openwork-extensions-preview.ts. - The Den API provides CRUD endpoints for plugin lifecycle management under
dev/ee/apps/den-api/src/routes/org/plugin-system/routes.ts, gated byplugin_archrole-based authorization. - Transaction safety is ensured by
createPluginBundleinstore.ts, which atomically creates config objects, plugin records, and marketplace entries. - Access control uses resource-grant tables (
PluginAccessGrantTable) supporting org-wide, member-specific, and team-scoped permissions. - GitHub integration allows importing Claude-compatible plugins, while MCP configuration supports remote server authentication via
PluginMcpRequirementBindingTable.
Frequently Asked Questions
What is an affordance in OpenWork's plugin architecture?
An affordance represents an action, query, or UI command that a plugin exposes to agents. Affordances are defined in extension manifests and accessed through the openwork_query (read-only) and openwork_execute (mutating) tool functions. The system treats affordances as the primary interface between AI agents and external capabilities.
How does OpenWork secure plugin access across organizations?
OpenWork secures plugins through resource-grant tables (PluginAccessGrantTable) and the ensureGrantTargetsInOrganization helper. All grant operations validate that members and teams belong to the requesting organization, preventing cross-org privilege escalation. Additionally, the withPluginArchOrgContext middleware enforces plugin_arch capabilities on every API route.
Can OpenWork import existing Claude plugins from GitHub?
Yes. The platform imports Claude-compatible plugins by parsing public repository trees, reading plugin.json and marketplace.json files, and validating skill front-matter via parseSkillMarkdown. The createPluginBundle function then transforms these components into native OpenWork config objects and optionally publishes them to the organization's marketplace.
What is the role of the UI bridge in OpenWork extensions?
The UI bridge (uiBridgeRequest) connects agents to the actual OpenWork desktop client by discovering the process through openwork-ui-control.json in the user's config directory. This bridge ensures that agents query real UI state and execute commands against the live desktop application rather than simulated environments, maintaining synchronization between agent actions and user interface updates.
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 →