OpenWorkerDeveloper guides

Your app. Your experience.
We handle the agents.

Build an agentic app for Windows or Mac with Rust or TypeScript. This guide walks through connecting a model, adding your own tools, and packaging the result as a desktop app. Start with the source setup and prerequisites.

Start the harness

OpenWorker runs the agent loop, handles approvals and keeps sessions across restarts. Start it in your desktop app. Generate a starter using the source-checkout commands below. It wires the SDK into your desktop app.

In your app
import { Harness } from "@openworker/harness";

const harness = await Harness.start({
  sandbox: "off", // Explicit host callbacks; no OS isolation.
});

Session data stays in your app’s directory under ~/.openworker/.

Customize storage, scratch space and runtime options

These settings are optional. Supply absolute, app-specific directories in your paths configuration.

Harness options
const harness = await Harness.start({
  executable: paths.runtime, // Optional: override the bundled executable.
  stateDir: paths.state,

  sessions: {
    persist: true,
    directory: paths.sessions,
  },
  scratch: { directory: paths.scratch },
  compaction: { triggerAtContextFraction: 0.8 },
});

Sessions persist by default. Set persistence to false for in-memory sessions without restart recovery. Scratch directories are allocated only when a session requests one. Compaction summarizes older context as the conversation approaches the configured fraction of the model’s context window.

Session persistence is separate from long-term memory across sessions, logs and scratch files. Turning it off does not erase those other stores. In Rust, the harness runs inside your app’s process by default. In TypeScript, the SDK starts and manages the bundled Rust harness over private stdio. Either way, your users install and open one desktop app. Rust can also use a managed harness process when you need that separation.

Connect a model

Add a provider now or later, when the user opens Settings. Give the connection a name, choose its API, and select a model.

Connect to OpenAI with a key supplied by your user.

OpenAI
// Ask for the key through your app’s Settings screen.
const apiKey = await ui.requestApiKey("OpenAI");

await harness.setModelProvider({
  name: "OpenAI",
  api: "responses",
  baseUrl: "https://api.openai.com/v1",
  credentials: { apiKey, storage: "secure" },
});

// Read the model ID the user selected in Settings.
const selectedModel = { provider: "OpenAI", name: settings.modelId };

ui and settings are starter app helpers, not harness APIs. Pass keys to the harness for secure storage. Never put them in prompts, logs or browser storage.

Environment keys, secure storage and other providers
Keys from your environment
await harness.setModelProvider({
  name: "OpenAI",
  api: "responses",
  baseUrl: "https://api.openai.com/v1",
  credentials: {
    apiKeyEnv: "OPENAI_API_KEY",
    storage: "memory", // Supply again after a process restart.
  },
});

Secure storage uses the platform’s secret store and is the default for supplied credentials. Memory-only keys must be supplied again after a harness restart. If secure storage is unavailable, configuration fails rather than saving plaintext.

Select responses, chat-completions or anthropic for your provider’s API. Supply the matching endpoint and exact model ID. Changing a key is separate from changing the provider or model pinned to an existing session.

Create agent

Let’s build Arcade: an app that turns an idea into a playable game. Register a preview tool, give the agent its instructions, then send the first message.

AExpose an application function
Give your agent a tool
await harness.registerTools({
  namespace: "game",
  tools: {
    show_preview: {
      description: "Show a playable HTML game in the app preview.",
      inputSchema: {
        type: "object",
        properties: { html: { type: "string" } },
        required: ["html"], additionalProperties: false,
      },
      execute: async ({ html }, context) => {
        await preview.show(html, { signal: context.signal });
        return { shown: true };
      },
    },
  },
});

Here preview represents your app’s adapter for its isolated HTML view. Your callback runs in your app. The SDK handles its connection to the agent.

BCreate an agent and stream its work
Create, send, stream
const builder = harness.createAgent({
  name: "builder",
  instructions: "Build self-contained HTML games. Show each game in the preview.",
  defaultModel: selectedModel,
  tools: ["game.show_preview"],
});

const session = await builder.newSession();
const run = await session.send("Build a neon Snake game.");

for await (const event of run.events()) {
  ui.onAgentEvent(event); // Forward messages and activity to your UI.
}
const result = await run.result();

Both provider examples define the selected model used here. The starter routes approval requests and questions to its UI. Waiting for input is not a completed task.

Session overrides, recovery and tool permissions
Session options
const session = await builder.newSession({
  model: { provider: "Ollama", name: settings.localModelId },
  scratch: true, // Allocate a private workspace for this session.
});

// Save this reference in your app's project metadata.
const reference = session.reference();
// After restarting and configuring the harness:
const restored = await builder.resumeSession(reference);

defaultModel is the agent’s default, not a permanent restriction. A session can override it. Configure the referenced provider first. If neither supplies a model, session creation returns an error. The resolved model stays with the session across restarts.

Only the tools listed on the agent become available to it. Harness policy and approvals apply before invocation. Application callbacks run with your app’s privileges; they are not automatically moved into a sandbox. Keep model-generated HTML isolated from credentials, Node access and privileged app commands.

Persist the session reference before starting work. Re-register callbacks after restarting; resuming a session does not repeat its last action. Stopping an event stream does not cancel execution. Use the session’s cancel operation for a Stop button, and shut down the harness when the app exits.

Rust event iteration uses futures_util::StreamExt, included by the starter. Adapt the illustrative preview handle to your app; validate JSON input before forwarding HTML to its isolated preview.

Build your app

The starters connect chat, model settings, approvals and a game preview. Use the examples above in the generated app, then customize the experience.

Terminal
# From the repository checkout; build on the target OS.
cd harness
cargo build --release --locked -p openworker-sdk --bin openworker-harness
npm --prefix typescript-sdk ci
npm --prefix typescript-sdk run build
cd ..
node harness/desktop-starters/create.mjs ../my-arcade --template electron-game-builder
cd ../my-arcade
npm install
npm run dev

# Build the desktop package.
npm run package

Build on the target OS: Windows on Windows, Mac on macOS. The package bundles your app and native helper; validate platform runtime dependencies and signing before distribution.

YOUR FIRST PROMPT“Build a neon Snake game with a score counter and a restart button.”
Build requirements and distribution

TypeScript uses React + Electron. Rust uses React + Tauri. Both use Node.js for the frontend build; the Tauri starter also needs Rust and the platform’s native build tools. End users do not need those developer toolchains.

The ui, settings, preview and paths names illustrate your app adapters; they are not SDK exports. The generated starter contains complete UI/preview wiring that you can adapt. Callback tools execute in your app with its privileges. Sandboxed tool execution has separate platform prerequisites.

Set your app name and identity, configure code signing and macOS notarization, and test the installer on a clean machine before distributing it.

Advanced

Extend the same foundation without adding another agent loop.

Connect MCP tools

Use an existing MCP service alongside your application callbacks. Connect it to the harness, then select its qualified tool names in an agent.

Connect an MCP service
await harness.connectMcpServer({
  name: "design",
  transport: "streamable-http",
  url: settings.mcpEndpoint,
});

// Select "design.<tool_name>" in your agent's tools list.

Connecting a server does not expose every tool to every agent. The service stays outside the harness sandbox unless separately isolated. This local MCP profile does not provide remote HTTP OAuth; select integrations supported by the configured transport. To launch a separate MCP executable that the harness owns, use startMcpServer instead.

Sandbox tool execution with NVIDIA OpenShell

Choose OpenShell when tools need an isolated execution environment. Configure its filesystem and network policy for the work you want to allow.

Sandboxed execution
const harness = await Harness.start({
  sandbox: { provider: "openshell" },
});

Provision Docker and NVIDIA OpenShell before enabling this profile. For Windows, the OpenShell deployment needs a supported Linux environment; it is not a native Windows sandbox. Qualify the setup on your target platform.

This setting governs tools routed into that execution environment. It does not isolate application callbacks running in your app, or external MCP services. Sandbox limits and human approval are independent controls.

Enable the auto-approver

Let a separate reviewer model evaluate proposed tool calls against the user’s intent. Choose a configured cloud or local model for the reviewer, independently of the working agent.

Review autonomous actions
const builder = harness.createAgent({
  name: "builder",
  instructions: "Build self-contained HTML games and show the preview.",
  defaultModel: selectedModel,
  tools: ["game.show_preview"],
  approvals: {
    mode: "auto",
    reviewerModel: selectedReviewerModel,
  },
});

selectedReviewerModel is a provider-and-model reference from Settings. If the reviewer cannot confidently approve, the harness pauses for a human. Hard restrictions still apply. Automatic review helps assess alignment; it is not a guarantee of safety or a bypass mode.