---
title: Run OpenCode with the Daytona Plugin
description: Use the Daytona OpenCode plugin to run each OpenCode session in a Daytona sandbox, with automatic git sync to local branches.
---

import { Image } from 'astro:assets'

import opencodePluginGif from '../../../../../assets/docs/images/opencode-plugin.gif'

This guide demonstrates how to run the [Daytona OpenCode plugin](https://www.npmjs.com/package/@daytona/opencode) which integrates Daytona sandboxes and OpenCode. When the plugin is active, all agent operations occur in secure sandboxes, with one sandbox per OpenCode session. The plugin also has the ability to sync changes between sandboxes and local Git branches.

### 1. Workflow Overview

When you run OpenCode with the Daytona plugin, sandboxes are created automatically inside OpenCode sessions. Operations such as running code, installing dependencies, and starting servers occur in the sandbox.

<Image
  src={opencodePluginGif}
  alt="OpenCode with Daytona plugin — sandbox toast and git branches"
  width={600}
  style="max-width: 100%; height: auto; margin: 1rem 0;"
/>

Sandboxes are preserved until you delete the OpenCode session. If a local Git repository is detected, the plugin syncs changes between the sandbox and branches with the `opencode/` prefix. 

### 2. Project Setup

#### Add the Plugin

Add the Daytona plugin to your project by creating or editing `opencode.json` in the project directory:

```json
{
  "$schema": "https://opencode.ai/config.json",
  "plugin": ["@daytona/opencode"]
}
```

OpenCode downloads the plugin automatically when it starts. To install the plugin globally instead, edit `~/.config/opencode/opencode.json` and add the same `plugin` entry.

#### Configure Environment

This plugin requires a [Daytona account](https://www.daytona.io/) and [Daytona API key](https://app.daytona.io/dashboard/keys) to create sandboxes.

Set your API key:

```bash
export DAYTONA_API_KEY="your-api-key"
```

Or create a `.env` file in your project root:

```env
DAYTONA_API_KEY=your-api-key
```

#### Run OpenCode

:::note[Git required]
Ensure your project is a Git repository to enable syncing changes between the sandbox and your machine.
:::

Initialize Git if needed, then start OpenCode:

```bash
git init
opencode
```

You can now use OpenCode as usual. As you work, you will see notifications in OpenCode indicating sandboxes are being created and changes are being synced to local branches.

To confirm the plugin is working, type `pwd` in the chat and you should see a path like `/home/daytona/project`.

To view live logs from the plugin for debugging:

```bash
tail -f ~/.local/share/opencode/log/daytona.log
```

#### Version control

In your project directory, use Git to list and check out the branches OpenCode creates:

```bash
git branch
git checkout opencode/1
```

By default, new sessions start from the branch that was checked out when OpenCode was started. After this, synchronization only goes one way: from the sandbox to your local branch. To start working from a different branch, use git to check out that branch and start OpenCode again:

```bash
git checkout opencode/1
opencode
```

You can run as many OpenCode sessions in parallel as you want. Use Git to review and merge changes.

### 3. Understanding the Plugin Architecture

The Daytona plugin consists of several modules that provide custom tools, hooks for events, connections from sessions to sandboxes and system prompt transforms. These modules ensure every agent action runs in a [Daytona sandbox](/docs/en/sandboxes) and that changes sync to local Git branches:

- **Custom tools:** Overrides bash, read, write, edit, etc., so they run in the sandbox.
- **System prompt transform:** Injects sandbox path and instructions into the agent's system prompt.
- **Event handlers:** Handles session lifecycle events including cleanup (deleting sandboxes) and idle auto-commit (syncing changes to local git branches).
- **Session management:** Manages the creation and deletion of sandboxes and the mapping of sessions to sandboxes.

#### Custom tools

The custom tools module registers overrides for OpenCode's built-in tools so that every file and process operation goes through the Daytona SDK. It receives the project `id` and `worktree` from the plugin context and returns a tool map:

```typescript
export async function customTools(ctx: PluginInput, sessionManager: DaytonaSessionManager) {
  logger.info('OpenCode started with Daytona plugin')
  const projectId = ctx.project.id
  const worktree = ctx.project.worktree
  return {
    bash: bashTool(sessionManager, projectId, worktree, ctx),
    // ... read, write, edit, multiedit, patch, ls, glob, grep, lsp, getPreviewURL
  }
}
```

For example, the plugin implementation of the bash tool uses the Daytona SDK to run the command in the sandbox:

```typescript
async execute(args: { command: string; background?: boolean }, ctx: ToolContext) {
  const sessionId = ctx.sessionID
  const sandbox = await sessionManager.getSandbox(sessionId, projectId, worktree, pluginCtx)

  if (args.background) {
    // ... create or get exec session, then:
    const result = await sandbox.process.executeSessionCommand(execSessionId, {
      command: args.command,
      runAsync: true,
    })
    return `Command started in background (cmdId: ${result.cmdId})`
  } else {
    const result = await sandbox.process.executeCommand(args.command, repoPath)
    return `Exit code: ${result.exitCode}\n${result.result}`
  }
}
```

All stateful tools (bash, read, write, edit, glob, grep, ls, lsp, multiedit, patch) are overridden the same way. The plugin also adds a custom tool for [preview links](/docs/en/preview/).

#### System prompt transform

The system prompt transform extends the system prompt to include instructions for the agent to work in the sandbox and use the background option for long-running commands:

```typescript
export async function systemPromptTransform(ctx: PluginInput, repoPath: string) {
  return async (input: ExperimentalChatSystemTransformInput, output: ExperimentalChatSystemTransformOutput) => {
    output.system.push(
      [
        '## Daytona Sandbox Integration',
        'This session is integrated with a Daytona sandbox.',
        `The main project repository is located at: ${repoPath}.`,
        'Bash commands will run in this directory.',
        'Put all projects in the project directory. Do NOT try to use the current working directory of the host system.',
        "When executing long-running commands, use the 'background' option to run them asynchronously.",
        'Before showing a preview URL, ensure the server is running in the sandbox on that port.',
      ].join('\n'),
    )
  }
}
```

#### Session events

The session events handler listens for OpenCode session lifecycle events and handles them appropriately. When you delete a session, the handler cleans up the corresponding sandbox. When a session becomes idle, it triggers auto-commit and sync:

```typescript
export async function eventHandlers(ctx: PluginInput, sessionManager: DaytonaSessionManager, repoPath: string) {
  const projectId = ctx.project.id
  const worktree = ctx.project.worktree
  return async (args: any) => {
    const event = args.event
    if (event.type === EVENT_TYPE_SESSION_DELETED) {
      const sessionId = (event as EventSessionDeleted).properties.info.id
      await sessionManager.deleteSandbox(sessionId, projectId)
      toast.show({ title: 'Session deleted', message: 'Sandbox deleted successfully.', variant: 'success' })
    } else if (event.type === EVENT_TYPE_SESSION_IDLE) {
      const sessionId = event.properties.sessionID
      const sandbox = await sessionManager.getSandbox(sessionId, projectId, worktree, ctx)
      const branchNumber = sessionManager.getBranchNumberForSandbox(projectId, sandbox.id)
      if (!branchNumber) return
      const sessionGit = new SessionGitManager(sandbox, repoPath, worktree, branchNumber)
      await sessionGit.autoCommitAndPull(ctx)
    }
  }
}
```

#### File synchronization

While OpenCode is in use, the plugin uses Git to keep session sandboxes and your local Git repository in sync. This only occurs if a git repository is detected in the project directory. On plugin start:

1. The plugin looks for a Git repository in the local directory.
2. A parallel repository is created in the sandbox with a single `opencode` branch, mirroring your current local branch.
3. A `sandbox` remote is added to your local repo using an SSH connection to the sandbox.
4. Your current `HEAD` is pushed to `opencode`, and the sandbox repo is reset to that state.
5. On session idle, the plugin commits in the sandbox on `opencode`, then pulls into a local branch (`opencode/1`, `opencode/2`, etc.) which is unique to each sandbox. A notification is shown when the sync is complete.

For more information on how the sync is implemented, see the [SessionGitManager](https://github.com/daytonaio/daytona/blob/main/libs/opencode-plugin/.opencode/plugin/daytona/git/session-git-manager.ts) class.

:::caution
When the plugin syncs to local `opencode` branches, any local changes on those branches are overwritten.
:::

#### Session storage

The session manager stores which sandbox belongs to each project in JSON files (using the same base path as OpenCode via `xdg-basedir`).

- **macOS:** `~/.local/share/opencode/storage/daytona/[projectid].json`
- **Windows:** `%LOCALAPPDATA%\opencode\storage\daytona\[projectid].json`

Each file holds sandbox metadata for that project's sessions so that sandboxes are retained between OpenCode uses.

**Key advantages:**

- Secure, isolated execution: each OpenCode session runs in its own Daytona sandbox
- Sandboxes persist until you delete the OpenCode session
- Live [preview links](/docs/en/preview/) when a server starts in the sandbox
- Automatic git sync to local branches so you can review and merge agent changes
- No script to run: add the plugin and use OpenCode as usual
