Back to Articles

Accomplish: Why Wrapping OpenCode Instead of Building an Agent Runtime Was the Right Bet

[ View on GitHub ]
35
AI-Assisted Full Provenance Report →
Claude Code
AI Provenance badge [![AI Provenance](https://starlog.is/badge/provenance/accomplish-ai/coworker.svg)](https://starlog.is/provenance/accomplish-ai/coworker)

Accomplish: Why Wrapping OpenCode Instead of Building an Agent Runtime Was the Right Bet

Hook

Accomplish didn't build an AI agent—it built a desktop shell around someone else's. That architectural surrender is precisely why it shipped in months instead of years.

Context

The AI agent graveyard is full of projects that died building execution sandboxes. AutoGPT spent months wrestling with Python subprocess isolation. AgentGPT rewrote browser automation three times. Open Interpreter still warns users about file system risks in bold red text. The pattern is consistent: teams burn 60% of development time on infrastructure—sandboxing code execution, managing browser contexts, preventing infinite loops—before writing a single line of agent logic.

Accomplish took a different bet. Instead of building a custom agent runtime, it delegates all task execution to OpenCode (sst/opencode), a maintained browser automation framework, and focuses exclusively on desktop orchestration. The Electron app becomes a credential manager, permission broker, and daemon supervisor while OpenCode handles the dangerous work of running LLM-generated automation scripts. This isn't just pragmatic reuse—it's a fundamental architectural stance that local-first AI tools should compose maintained primitives rather than reinvent isolation boundaries. The tradeoff is constraint: Accomplish inherits OpenCode's browser-first assumptions and can only automate what Puppeteer reaches. But in exchange, the team avoided the security nightmares that killed most desktop agent projects.

Technical Insight

The core architectural choice is a three-layer isolation model: Electron UI → persistent daemon → ephemeral OpenCode workers. When you ask Accomplish to organize your Downloads folder, the request flows through Electron's main process to a long-lived Node daemon, which spawns an OpenCode child process with explicit folder permissions. The daemon survives even if you close the Electron window, so closing your laptop mid-task doesn't kill the automation.

Here's the daemon initialization that shows the separation:

// packages/daemon/src/index.ts (simplified)
import { OpenCodeClient } from '@opencode-ai/sdk';
import { TaskQueue } from './queue';

class AccomplishDaemon {
  private openCodeClients: Map<string, OpenCodeClient> = new Map();
  private queue: TaskQueue;

  async executeTask(task: Task, permissions: FolderPermissions) {
    const client = new OpenCodeClient({
      apiKey: await this.getProviderKey(task.provider),
      model: task.model,
      sandbox: {
        allowedPaths: permissions.folders,
        networkAccess: permissions.network
      }
    });

    this.openCodeClients.set(task.id, client);
    
    try {
      await client.run(task.prompt, {
        onProgress: (update) => this.notifyUI(task.id, update)
      });
    } finally {
      client.cleanup();
      this.openCodeClients.delete(task.id);
    }
  }
}

The daemon doesn't execute tasks itself—it's a lifecycle manager. Each OpenCode client runs in a separate child process with sandboxed filesystem access. When a task completes or crashes, the daemon cleans up the worker but stays alive for the next request. This is why Accomplish can survive Electron crashes that would kill monolithic agent apps.

The multi-provider LLM layer is more interesting than it appears. Rather than hardcoding Anthropic or OpenAI SDKs, Accomplish uses a provider abstraction that normalizes 16+ LLM APIs into a single interface:

// packages/shared/src/llm/provider.ts
export interface LLMProvider {
  name: string;
  models: ModelDefinition[];
  createClient(config: ProviderConfig): LLMClient;
}

export interface LLMClient {
  complete(prompt: string, options?: CompletionOptions): Promise<string>;
  stream(prompt: string): AsyncIterator<string>;
}

// User switches from GPT-4 to Claude without code changes
const provider = getProvider(userSettings.provider);
const client = provider.createClient({
  apiKey: await keychain.get(`${provider.name}_api_key`)
});

This abstraction means you can hot-swap from OpenAI to Bedrock to a local Ollama instance mid-workflow. The UI exposes a model dropdown; changing it re-initializes the OpenCode client with a different provider but the same task context. No restart required. This is critical for users who hit rate limits or want to test prompt changes across models without restarting automations.

The permission model uses OS-native folder pickers rather than full filesystem access. When an agent needs to read your project directory, Accomplish shows a native macOS/Windows folder picker, stores the granted path, and passes it to OpenCode's sandbox config. The implementation uses Electron's dialog API:

// packages/desktop/src/permissions.ts
import { dialog } from 'electron';

export async function requestFolderAccess(): Promise<string | null> {
  const result = await dialog.showOpenDialog({
    properties: ['openDirectory', 'createDirectory']
  });
  
  if (result.canceled) return null;
  
  const folderPath = result.filePaths[0];
  await permissionStore.grant(folderPath);
  return folderPath;
}

This creates UX friction—if your agent needs to traverse from /Users/you/projects to /Users/you/Downloads, it requires two separate permission grants. There's no recursive permission inheritance visible in the codebase, so deeply nested workflows hit permission dialogs repeatedly. But the blast radius is contained: a malicious prompt can only wreck folders you explicitly granted.

API keys never touch the filesystem. Accomplish uses Keytar (Electron's credential store wrapper) to save keys in macOS Keychain or Windows Credential Vault. This prevents the classic security anti-pattern where developers screenshot their terminal and leak OPENAI_API_KEY from a visible .env file. Keys are retrieved on-demand when spawning OpenCode workers, then immediately discarded from memory.

Gotcha

The OpenCode dependency is both Accomplish's strength and ceiling. Because core agent execution happens in sst's codebase, you inherit their roadmap priorities and architectural constraints. If OpenCode doesn't support a browser API—say, WebRTC for automating video calls—Accomplish can't add it without forking. The project is betting that browser automation primitives (Puppeteer, Playwright) cover 80% of desktop automation needs, which is true for file management and web scraping but breaks down for native app control or system-level tasks like managing Docker containers.

The permission model lacks transactional safety. If an agent renames 500 files based on a misunderstood prompt, there's no undo button. Accomplish doesn't snapshot filesystem state before operations or provide rollback mechanisms. You're trusting the LLM to interpret correctly and the OpenCode sandbox to limit scope, but once write operations execute, they're permanent. Compare this to tools like Cursor or GitHub Copilot, which show diffs before applying code changes—Accomplish executes file operations immediately after LLM confirmation, with no preview step visible in the UI flow. For high-stakes workflows (organizing financial records, batch renaming production assets), this is genuinely risky.

Verdict

Use if: You're a developer or power user who already has LLM API keys, needs desktop filesystem access that browser agents can't provide, and wants to avoid managing Python environments or building custom automation scripts. Accomplish excels at grunt work—organizing Downloads folders, batch renaming files, scraping data into local CSVs—where browser automation primitives suffice and you value a GUI over writing code. The local-first architecture means your data and prompts never hit Accomplish's servers, which matters if you're handling proprietary codebases or sensitive documents. Skip if: You need programmatic agent APIs (there's no SDK for embedding Accomplish in other tools), multi-agent collaboration, production-grade error recovery with rollback, or automation beyond what Puppeteer reaches (native app control, system administration tasks). Also skip if you're pointing this at critical data without auditing OpenCode's sandbox implementation first—the permission model is permissive-by-default within granted folders, and the lack of undo makes mistakes permanent. This is a 0.5.x GUI tool for personal productivity, not an agent framework for building production systems.

// ADD TO YOUR README
[![Featured on Starlog](https://starlog.is/api/badge/developer-tools/accomplish-ai-coworker.svg)](https://starlog.is/api/badge-click/developer-tools/accomplish-ai-coworker)