# Best Practices for Developing with OpenWork: Local-First AI Workflow Architecture

> Master OpenWork development with local-first AI workflows. Learn best practices for desktop UI, server-side MCP, infrastructure, strict typing, and secure data access.

- Repository: [Different AI/openwork](https://github.com/different-ai/openwork)
- Tags: best-practices
- Published: 2026-08-13

---

**OpenWork development requires a local-first, cloud-ready approach that separates concerns across desktop UI, server-side MCP tools, and infrastructure layers while enforcing strict TypeScript typing, owner-scoped data access, and feature-flag gating for experimental functionality.**

OpenWork is a modular, local-first, cloud-ready platform for building and sharing AI-driven workflows. Maintained by the `different-ai/openwork` repository, this architecture enables developers to create durable AI agents with persistent memory capabilities. Following the best practices for developing with OpenWork ensures your contributions align with strict typing requirements, security boundaries, and the composable UI standards defined in the codebase.

## Understanding the Three-Layer Architecture

OpenWork separates functionality into distinct layers to maintain clean boundaries between presentation, business logic, and data persistence.

### Desktop and Client Layer

The Electron-based desktop application provides a thin UI that communicates with the server exclusively through the OpenWork MCP. This layer uses a **feature-flag** system to toggle UI-only experiments—such as the experimental Memory panel—and relies on **shadcn/Base UI** components to maintain visual consistency across platforms.

### Server and den-api Layer

The server hosts the durable runtime, persistence layer, and MCP-exposed tools. It exposes capabilities through **`search_capabilities`** and **`execute_capability`**, with all routes protected by **owner-scoping** (user-id enforcement) via the [`mcp/auth.ts`](https://github.com/different-ai/openwork/blob/main/mcp/auth.ts) guard. The desktop app never re-implements server logic; instead, it consumes MCP tools like `postMemory` and `getMemorySearch`.

### Infrastructure Layer

Data storage uses MySQL/PlanetScale with **Drizzle ORM** and optional full-text search. The system uses **type-ids** (registered in [`packages/utils/src/typeid.ts`](https://github.com/different-ai/openwork/blob/main/packages/utils/src/typeid.ts)) to ensure all entities carry globally unique prefixes such as `memory` and `memctx`. The **FULLTEXT** index is created idempotently at bootstrap to guarantee searchable content on fresh installations.

## Core Development Principles

Adhering to these five principles ensures code quality and architectural alignment:

1. **Local-first, cloud-ready** – Develop against the local dev profile using `pnpm dev` or `pnpm dev:worktree`, then deploy to a shared cloud-hosted Den without code changes.
2. **Server-consumption first** – The desktop app never duplicates server logic; it strictly consumes MCP-exposed tools.
3. **Composable UI** – Build interface elements with **shadcn/ui** and the shared component library (`@/components`) to maintain consistency across desktop, web, and mobile.
4. **Strict typing** – The codebase enforces a "no `any`" rule; all TypeScript definitions generate from OpenAPI specs (see [`openapi.ts`](https://github.com/different-ai/openwork/blob/main/openapi.ts)).
5. **Feature-flag gating** – Experimental features (like the Memory bank UI) live behind client-only flags (`featureFlags.memory`) before server-side hard gates are implemented.

## Step-by-Step Development Workflow

### Configure Local Development

Start with an isolated development profile to prevent conflicts with production data. Run the following command to create an auto-generated profile:

```bash
OPENWORK_DEV_PROFILE=auto pnpm dev:worktree

```

This workflow is documented in the [`README.md`](https://github.com/different-ai/openwork/blob/main/README.md) "Local development" section and creates an isolated worktree for safe experimentation.

### Add New MCP Capabilities

To expose new server functionality:

1. Define the RPC endpoint in [`ee/apps/den-api/src/routes/workers/core.ts`](https://github.com/different-ai/openwork/blob/main/ee/apps/den-api/src/routes/workers/core.ts)
2. Assign a unique `operationId` (e.g., `postMemory`)
3. Tag the capability with `"Memory"` in [`mcp/policy.ts`](https://github.com/different-ai/openwork/blob/main/mcp/policy.ts)

All server routes must enforce owner-scoping through the [`mcp/auth.ts`](https://github.com/different-ai/openwork/blob/main/mcp/auth.ts) guard to ensure `user_id = principal.userId` filters prevent cross-user data leakage.

### Register Type Identifiers

New persistent entities require globally unique prefixes. Register these in [`packages/utils/src/typeid.ts`](https://github.com/different-ai/openwork/blob/main/packages/utils/src/typeid.ts):

```ts
export const typeIdMapNameToPrefix = {
  // …existing entries…
  memory: 'mem',
  memctx: 'mcx',
};

```

This registration provides compile-time safety and ensures all database records carry identifiable prefixes.

### Implement Feature Flags

For experimental UI components:

1. Toggle the flag in [`feature-flags-preferences.ts`](https://github.com/different-ai/openwork/blob/main/feature-flags-preferences.ts) (e.g., `featureFlags.memory`)
2. Build the panel UI under [`apps/app/src/react-app/.../memory-panel.tsx`](https://github.com/different-ai/openwork/blob/main/apps/app/src/react-app/.../memory-panel.tsx)
3. Access flags through the `useLocal` hook in [`apps/app/src/react-app/kernel/local-provider.tsx`](https://github.com/different-ai/openwork/blob/main/apps/app/src/react-app/kernel/local-provider.tsx):

```tsx
import { useLocal } from '@/components/feature-flags';
const { featureFlags } = useLocal();

return (
  <Switch
    checked={featureFlags.memory}
    onCheckedChange={(c) => setFeatureFlags({ ...featureFlags, memory: c })}
  >
    Enable Memory Bank UI
  </Switch>
);

```

### Validate with Tests and Fraimz

Before submitting changes:

1. Run the test suite: `pnpm test` (or the `run-tests` skill)
2. Capture screenshots or short video demonstrations
3. Attach the [`fraimz.html`](https://github.com/different-ai/openwork/blob/main/fraimz.html) proof to your pull request

As documented in [`AGENTS.md`](https://github.com/different-ai/openwork/blob/main/AGENTS.md), the "Pull Request Expectations" section requires visual proof for all UI changes.

## Security and Data Integrity Patterns

### Owner-Scoped Data Access

Every database query must filter by `user_id` to prevent cross-user leakage. For example, the Memory search endpoint explicitly enforces:

```ts
.where(and(
  sql`user_id = ${userId}`,
  sql`MATCH(content) AGAINST(${q} IN NATURAL LANGUAGE MODE)`
))

```

This pattern appears in the retrieval flow documented in [`memory-bank-architecture.md`](https://github.com/different-ai/openwork/blob/main/memory-bank-architecture.md) section 8.

### Idempotent Full-Text Indexing

The infrastructure guarantees searchable content through an idempotent **FULLTEXT** index creation at bootstrap. This ensures fresh database installations immediately support lexical search without manual migration steps, as detailed in section 3 of the Memory Bank Architecture document.

### Progressive Feature Gating

The feature-flag pattern allows shipping experimental UI without affecting existing users. Client-only flags enable A/B testing, while server-side hard gates can be added later without breaking the MCP contract.

## Code Implementation Examples

### Connecting to the OpenWork MCP

Configure your agent to communicate with the OpenWork server:

```jsonc
// Add to your opencode.json (client side)
{
  "mcp": {
    "openwork": {
      "type": "remote",
      "enabled": true,
      "url": "https://api.openworklabs.com/mcp/agent",
      "oauth": {}
    }
  }
}

```

### Saving Memories with Owner Scope

Implement the server-side POST endpoint in [`ee/apps/den-api/src/routes/workers/core.ts`](https://github.com/different-ai/openwork/blob/main/ee/apps/den-api/src/routes/workers/core.ts):

```ts
router.post('/v1/memory', async (c) => {
  const { content, tags, contexts } = c.req.body;
  const userId = c.get('user').id;
  // Force user-scoped storage
  const memory = await db.insert(memoryTable).values({
    user_id: userId,
    content,
    tags,
    source: 'chat',
    scope: 'user',   // overrides any client value
  }).returning();
  // Insert optional contexts in the same transaction …
  return c.json(memory);
});

```

### Searching Memories with Full-Text

Implement lexical search using MySQL's MATCH...AGAINST syntax:

```ts
router.get('/v1/memory/search', async (c) => {
  const q = c.req.query('q');
  const userId = c.get('user').id;
  const results = await db
    .select()
    .from(memoryTable)
    .where(and(
      sql`user_id = ${userId}`,
      sql`MATCH(content) AGAINST(${q} IN NATURAL LANGUAGE MODE)`
    ))
    .limit(20);
  return c.json({ results });
});

```

## Essential Source Files and References

Understanding these key files accelerates development:

- **[`README.md`](https://github.com/different-ai/openwork/blob/main/README.md)** – Contains overview, installation commands, and the local dev workflow (`pnpm dev:worktree`).
- **[`AGENTS.md`](https://github.com/different-ai/openwork/blob/main/AGENTS.md)** – Defines core philosophy, coding standards, and PR expectations including [`fraimz.html`](https://github.com/different-ai/openwork/blob/main/fraimz.html) requirements.
- **[`docs/memory-bank-architecture.md`](https://github.com/different-ai/openwork/blob/main/docs/memory-bank-architecture.md)** – Details the persistent Memory feature design, including type-id usage, FULLTEXT indexing, and owner-scoping requirements.
- **[`apps/server/src/openwork-runtime-config.ts`](https://github.com/different-ai/openwork/blob/main/apps/server/src/openwork-runtime-config.ts)** – Location where agent prompts are injected, including the "## Memory Bank" section.

- **[`ee/apps/den-api/src/routes/workers/core.ts`](https://github.com/different-ai/openwork/blob/main/ee/apps/den-api/src/routes/workers/core.ts)** – Implements the `/v1/memory` CRUD endpoints and MCP tool handlers.
- **[`packages/utils/src/typeid.ts`](https://github.com/different-ai/openwork/blob/main/packages/utils/src/typeid.ts)** – Central registry for type-id prefixes (`memory` → `mem`, `memctx` → `mcx`).
- **[`apps/app/src/react-app/kernel/local-provider.tsx`](https://github.com/different-ai/openwork/blob/main/apps/app/src/react-app/kernel/local-provider.tsx)** – Handles feature-flag state management (e.g., `featureFlags.memory`).

## Summary

Following best practices for developing with OpenWork ensures secure, maintainable, and scalable contributions:

- **Develop locally first** using `OPENWORK_DEV_PROFILE=auto` to maintain isolation from production environments.
- **Consume server logic** through MCP tools rather than duplicating business logic in the desktop client.
- **Enforce owner-scoping** on all database queries via `user_id` filters to prevent data leakage.
- **Register type-ids** for new entities in [`typeid.ts`](https://github.com/different-ai/openwork/blob/main/typeid.ts) to maintain global uniqueness.
- **Gate experiments** behind feature flags before implementing server-side restrictions.
- **Submit proof** with [`fraimz.html`](https://github.com/different-ai/openwork/blob/main/fraimz.html) attachments when opening pull requests.

## Frequently Asked Questions

### What is the local-first development workflow in OpenWork?

The local-first workflow uses `pnpm dev:worktree` with `OPENWORK_DEV_PROFILE=auto` to create isolated development environments. This allows developers to test changes against a local server and database before deploying to the cloud-hosted Den, ensuring no code changes are required when transitioning between environments.

### How do I add a new MCP capability to the OpenWork server?

Define the RPC endpoint in [`ee/apps/den-api/src/routes/workers/core.ts`](https://github.com/different-ai/openwork/blob/main/ee/apps/den-api/src/routes/workers/core.ts), assign a unique `operationId` (such as `postMemory`), and tag it in [`mcp/policy.ts`](https://github.com/different-ai/openwork/blob/main/mcp/policy.ts). Ensure the route enforces owner-scoping through the [`mcp/auth.ts`](https://github.com/different-ai/openwork/blob/main/mcp/auth.ts) guard to validate that `user_id` matches the authenticated principal.

### How does OpenWork prevent cross-user data leakage?

All server routes implement **owner-scoping** by filtering database queries with `user_id = principal.userId`. This pattern is enforced by the authentication guard in [`mcp/auth.ts`](https://github.com/different-ai/openwork/blob/main/mcp/auth.ts) and appears in every data retrieval and storage operation, ensuring users can only access their own memory records and contexts.

### What is the correct way to implement experimental UI features?

Experimental features should be implemented behind **feature flags** defined in [`feature-flags-preferences.ts`](https://github.com/different-ai/openwork/blob/main/feature-flags-preferences.ts). Toggle the flag through the `useLocal` hook in [`apps/app/src/react-app/kernel/local-provider.tsx`](https://github.com/different-ai/openwork/blob/main/apps/app/src/react-app/kernel/local-provider.tsx) to show or hide UI components. This allows safe A/B testing before adding server-side enforcement or releasing to all users.