Skip to main content
Glama
Mesya82
by Mesya82
README.md
# OpenCode MCP Orchestrator

OpenCode MCP Orchestrator adds sandboxed delegated coding agents to MCP-capable
coding assistants.

It uses OpenCode as the delegated-agent runtime and exposes three focused MCP
tools:

- `scout` - read-only repository investigation
- `worker` - bounded repository implementation
- `runner` - noisy command execution and output analysis

The parent coding agent remains responsible for architecture, integration,
high-risk operations, and final decisions.

## Why

Large coding agents are often capable of delegating work, but delegation can
become expensive when the parent model repeatedly reads repository files,
large build logs, test output, and implementation details.

This project moves suitable work into cheaper or otherwise independently
selected OpenCode models while returning concise results to the parent agent.

Typical uses include:

- locating code paths and tracing behavior
- implementing bounded changes
- running tests, builds, linters, and other noisy commands
- extracting relevant errors from large logs

## Architecture

    Codex / Claude Code / other MCP client
                     |
                     v
             MCP orchestrator
                     |
           +---------+---------+
           |         |         |
         scout     worker    runner
           |         |         |
           +---- OpenCode -----+
                     |
            user-selected models

Model selection is independent for each role.

The project does not hardcode Muse, OpenCode Go, or any other provider.
Available choices are discovered from the user's OpenCode installation.

## Current client integrations

- OpenAI Codex CLI
- Claude Code

The core MCP server is client-agnostic, so other MCP-capable coding clients can
be added without changing the delegated-agent architecture.

## Requirements

Linux is currently required.

Runtime requirements:

- Node.js 20 or newer
- OpenCode
- Bubblewrap (`bwrap`)
- Git

For the corresponding parent integrations:

- Codex CLI, optional
- Claude Code, optional

npm is not required on machines installing a release. Runtime JavaScript
dependencies are bundled into the release artifacts.

## Installation

Install the latest GitHub release with:

    curl -fsSL https://github.com/Mesya82/opencode-mcp-orchestrator/releases/latest/download/install.sh | bash

The bootstrap installer:

1. downloads `SHA256SUMS`
2. downloads the release archive
3. verifies the SHA-256 checksum
4. validates archive paths
5. extracts the verified archive
6. launches the interactive installer

The interactive installer then:

1. checks prerequisites
2. detects and removes an existing project-owned installation, if present,
   while preserving user configuration
3. installs the requested release as a fresh core payload
4. discovers models and model-specific variants available through OpenCode
5. lets the user choose models, optional variants, and a step-limit profile
6. detects supported parent coding clients
7. lets the user select integrations
8. renders and installs the OpenCode agents plus sandbox plugin
9. installs MCP and skill integrations for the requested configuration
10. runs the installation doctor

## Model selection

Scout, Worker, and Runner may use the same model or different models. Each role
may also choose its own OpenCode model variant, even when multiple roles use the
same model.

The selector reads the user's actual OpenCode model catalog instead of
maintaining a project-specific list. When OpenCode exposes structured variant
metadata, the installer offers exactly those variants plus `Default`. Choosing
`Default`, or omitting a role from `modelVariants`, leaves variant/reasoning
selection to OpenCode. Existing configurations without `modelVariants` remain
valid and keep the historical default behavior.

Configuration is stored at:

    ${XDG_CONFIG_HOME:-~/.config}/opencode-mcp-orchestrator/config.json

Example:

    {
      "version": 1,
      "models": {
        "scout": "provider/model-a",
        "worker": "provider/model-b",
        "runner": "provider/model-c"
      },
      "modelVariants": {
        "scout": "low",
        "worker": "low",
        "runner": "minimal"
      },
      "stepLimits": {
        "profile": "standard"
      },
      "timeoutLimits": {
        "profile": "standard"
      },
      "integrations": [
        "codex",
        "claude"
      ]
    }

Variant IDs are model-specific and discovered from OpenCode. The example names
above are illustrative, not a project-maintained compatibility list. See
`docs/model-variants.md` for discovery, fallback, and backward-compatibility
details.

## Step-limit profiles

The installer configures the maximum number of model steps available to each
delegated role. A model step is one agent iteration and may contain several
parallel tool calls.

Three profiles are available:

| Profile | Scout | Worker | Runner | Intended use |
| --- | ---: | ---: | ---: | --- |
| Standard | 16 | 32 | 40 | Raised defaults for normal focused delegation |
| Extended | 32 | 48 | 64 | Broad investigations and tool-heavy models such as Muse |
| Custom | User-selected | User-selected | User-selected | Explicit per-role control from 4 through 256 steps |

Each generated agent prompt states its actual limit and reserves the final 20%
of the budget, with a minimum of two steps, for synthesis. OpenCode's final
configured step is text-only, so the reserve reduces the chance that a model
reaches provider-incompatible forced termination without returning a report.

Configurations created by older releases do not need manual migration. If
`stepLimits` is absent, setup uses the new Standard profile. Running the
installer again lets interactive users select another profile and regenerates
the managed OpenCode agent definitions from that setting.

A non-interactive custom configuration uses:

    {
      "stepLimits": {
        "profile": "custom",
        "scout": 24,
        "worker": 40,
        "runner": 48
      }
    }

## Timeout profiles

Wall-clock timeouts are independent from model-step limits. The installer
selects both profiles together by default, while allowing either to be changed
independently.

| Profile | Scout | Worker | Runner | Codex parent | Intended use |
| --- | ---: | ---: | ---: | ---: | --- |
| Standard | 300s | 600s | 1,200s | 1,500s | Focused delegation |
| Extended | 900s | 1,500s | 1,800s | 2,100s | Muse and tool-heavy work |
| Custom | User-selected | User-selected | User-selected | User-selected | Explicit per-role and parent control |

The parent timeout must be at least 60 seconds longer than every role timeout.
Runner command timeouts must leave at least 60 seconds inside the Runner
operation deadline for analysis, synthesis, and cleanup. Impossible
combinations fail before an OpenCode session is created.

The bridge also enforces a configured caller-budget preflight on every
operation: the actual operation timeout (including
`OPENCODE_MCP_ORCHESTRATOR_BRIDGE_TIMEOUT_MS` or a test `timeoutMs`
override) plus a 40-second cleanup and result reserve (30 seconds cleanup,
10 seconds result) must fit within the configured parent timeout. The
check runs before writer-lock acquisition and before any session or client
work, and its error names the concrete operation, caller budget, and
reserve without echoing prompts or config secrets. It enforces only the
configured parent budget; the SDK context exposes an MCP request
`AbortSignal` but no reliable live host deadline.

The OpenCode client implements `session.wait()` as a response-header long
poll. The bridge refreshes only that HTTP wait request every 240 seconds so
Node/Undici's approximately 300-second response-header boundary cannot mask a
still-running session as `Transport`. Refreshes do not interrupt the OpenCode
session or reset the operation deadline. A failure before the refresh timer, or
after caller/operation cancellation, remains a real error and is not retried.

Writable work is fail-closed per canonical worktree with states
`active`, `cleaning`, and `quarantined`. A second worker or writable
runner cannot start while any of those states is present. The state moves
through `cleaning` on success, error, timeout, or cancellation, and is
cleared only after `session.remove` is confirmed within the cleanup
deadline. A throw or timeout during removal quarantines the directory with
an actionable restart-and-verify error on the originating writable call. A
timeout or cancellation before
session creation also quarantines until late-session reconciliation
confirms removal, at which point it may clear. Quarantine is in-memory
and clears on process restart; there is no force-clear API in this batch.
Scout and read-only runner paths never consult writer state.

When Codex integration is selected, installation writes the profile's parent
deadline to `mcp_servers.opencode-agents.tool_timeout_sec` in Codex
`config.toml`. An existing Extended step profile without `timeoutLimits`
automatically receives the Extended timeout defaults; other older
configurations receive Standard defaults.

A non-interactive custom timeout configuration uses:

    {
      "timeoutLimits": {
        "profile": "custom",
        "scout": 600,
        "worker": 1200,
        "runner": 1800,
        "parent": 2100
      }
    }

`OPENCODE_MCP_ORCHESTRATOR_BRIDGE_TIMEOUT_MS` remains available as a
deployment-wide compatibility override. When set, it replaces the configured
per-role operation deadline and is validated against the existing 1-second to
1-hour bounds.

## Installed layout

The installed release uses an XDG data directory:

    ${XDG_DATA_HOME:-~/.local/share}/opencode-mcp-orchestrator/
    ├── current/
    │   ├── libexec/
    │   ├── opencode/
    │   ├── skills/
    │   └── manifest.json
    └── install-manifest.json

`current/` is a real directory containing the single installed release payload.
Parent integrations reference this stable path.

Installing another release replaces the existing project-owned installation
rather than retaining multiple local versions.

User configuration lives separately under the XDG config directory and is
preserved across replacement installs.

## Updating or installing another version

Run the normal installer again to install the latest release:

    curl -fsSL https://github.com/Mesya82/opencode-mcp-orchestrator/releases/latest/download/install.sh | bash

The requested release is downloaded, checksum-verified, validated, and
extracted before the existing installation is changed.

If an installation already exists, its project-owned payload and integrations
are removed while user configuration is preserved. The requested release is
then installed fresh.

The same mechanism can install an older release. For example:

    curl -fsSL https://github.com/Mesya82/opencode-mcp-orchestrator/releases/download/v0.1.2/install.sh | bash

There is no local version archive or version manager. GitHub Releases provide
the version archive, and running a release's installer makes that release the
single locally installed version.

## OpenCode integration

The installer adds:

    ~/.config/opencode/agents/opencode-orchestrator-scout.md
    ~/.config/opencode/agents/opencode-orchestrator-worker.md
    ~/.config/opencode/agents/opencode-orchestrator-runner.md

    ~/.config/opencode/plugins/opencode-mcp-orchestrator/index.ts

Equivalent XDG paths are used when `XDG_CONFIG_HOME` is set.

For orchestrator-owned sessions using OpenCode Console/Zen Muse Spark models,
the plugin omits hidden reasoning parts from subsequent provider requests. This
avoids replaying caller-bound encrypted reasoning state that Console may reject
after tool use. On the final configured agent step, after OpenCode has removed
all tools, the plugin also omits the unsupported `tool_choice: "none"` field so
Console can use its `auto` default and return the text-only final report. The
absence of tools preserves the hard step boundary. Visible text and tool
history are retained. Both workarounds are scoped to `opencode-orchestrator-*`
agents with `opencode/muse-spark-*`; they do not alter ordinary OpenCode
sessions or silently select another provider.
The upstream defect is tracked as
[anomalyco/opencode#48741](https://github.com/anomalyco/opencode/issues/48741);
see `docs/orchestration-friction.md` for status, validation, and workaround
removal criteria.

## Codex integration

The installer:

- registers the MCP server as `opencode-agents`
- installs the `orchestrate` skill under the user's agent skills directory

The MCP registration points at the stable `current/libexec/mcp-server.mjs`
path.

## Claude Code integration

The installer:

- registers `opencode-agents` as a user-scoped stdio MCP server
- installs the `orchestrate` skill as a personal Claude Code skill

The registration is therefore available across Claude projects.

## Integration desired state

The selected integration set describes the desired resulting state.

On a later setup run:

- selected integrations are installed or refreshed
- already-selected integrations are safe to reinstall
- integrations previously managed by this project but now deselected are announced
  before removal
- only MCP registrations and skills owned by this project are removed
- locally modified managed skill files are preserved rather than deleted

For example, changing from Codex + Claude Code to Claude Code only removes this
project's Codex MCP registration and managed Codex skill. It does not uninstall
Codex itself or touch unrelated Codex configuration.

Running setup again for the currently active release reuses that release's core
payload and reconciles the requested integrations instead of failing because the
version directory already exists.

## Delegated roles

### Scout

Scout is intended for broad but read-only repository investigation.

It is useful for questions such as:

- where is a behavior implemented?
- which functions participate in this flow?
- where is a value parsed or transformed?
- what exact code path leads to this operation?

It cannot modify the repository.

### Worker

Worker performs bounded implementation tasks.

Its sandbox permits normal workspace edits but protects Git metadata and blocks
access to files outside the allowed workspace.

The worker must not perform Git-mutating operations.

### Runner

Runner executes noisy local commands and analyzes their output.

Examples:

- test suites
- builds
- linters
- type checking
- local application commands
- log inspection

The parent receives a concise analysis rather than the entire command output.

Runner `workspace_access` defaults to `read_only`; request `"writable"`
explicitly when the command must write the workspace. Details and log
retention are in docs/orchestration-friction.md.

Deployments and similarly high-risk operations are intentionally not delegated
by the orchestration policy.

## Sandbox properties

The delegated command environment has been designed and tested so that:

- Scout cannot write the workspace.
- Worker can edit ordinary workspace files.
- Git metadata is read-only for Worker and Runner tooling.
- delegated shell commands have no outbound network access
- orchestrator/provider credentials are not exposed to delegated commands
- secret files outside the sandbox are not readable
- Runner can persist and analyze large command logs without returning the full
  log to the parent model

### Sandbox toolchains

`sandbox_shell` and `sandbox_run` expose only the workspace and safe system
paths by default. Inherited `PATH` entries that canonicalize beneath `/usr`
are retained automatically, so version-manager aliases into the existing
read-only system tree remain usable without restarting OpenCode.

Additional installations use the optional, tool-agnostic `sandboxRuntime`
configuration. Each trusted root is mounted read-only. `pathEntries` and
path-valued environment variables are resolved relative to that root and must
remain inside it:

    {
      "sandboxRuntime": {
        "trustedRoots": [
          {
            "root": "/opt/example-runtime",
            "pathEntries": ["bin"],
            "environment": {
              "EXAMPLE_HOME": "."
            }
          }
        ]
      }
    }

The plugin reloads this file for every sandbox invocation, so changing trusted
roots does not require reinstalling the orchestrator or restarting the shared
OpenCode service. Broad system roots, the home root, Git metadata, runtime
pseudo-filesystems, and common credential directories are rejected. Core
sandbox variables such as `HOME` and `PATH` cannot be overridden.

`OPENCODE_SANDBOX_TOOLCHAIN_DIRS` remains supported for backward compatibility
as an additive list of read-only directories that are also appended to PATH.

### Sandbox resource limits

Sandbox timeouts and caps have safe defaults and hard bounds. Each value is a
strict integer; unset variables preserve the default. Invalid, non-integer, or
out-of-range values fail closed.

| Variable | Default | Allowed range |
| --- | ---: | ---: |
| `OPENCODE_SANDBOX_SHELL_TIMEOUT_MS` | `120000` | `1000`..`900000` |
| `OPENCODE_SANDBOX_SHELL_MAX_OUTPUT_BYTES` | `30000` | `4096`..`1048576` |
| `OPENCODE_SANDBOX_RUNNER_LOG_LIMIT_BYTES` | `134217728` | `1048576`..`536870912` |
| `OPENCODE_SANDBOX_RUN_RETENTION_HOURS` | `24` | `1`..`168` |
| `OPENCODE_SANDBOX_RUN_RETENTION_COUNT` | `20` | `1`..`200` |

See SECURITY.md for the threat model and limitations.

See docs/orchestration-friction.md for observed caller-vs-bridge timeouts,
provider compatibility, sandbox toolchains, Runner access modes, and log
retention.

## Troubleshooting

For observed runtime friction, see docs/orchestration-friction.md: caller-side
versus bridge-level timeouts, provider compatibility, sandbox toolchains,
Runner access modes, and log retention.

For the detailed subagent reliability review and proposed synchronous versus
asynchronous timeout architecture, see
docs/subagent-usage-friction-review.md.

## Managed-file safety

The installer records hashes of files it owns.

During replacement cleanup, a managed file is removed only if it still matches
the hash recorded by the existing installation.

Locally modified files are preserved rather than deleted. A subsequent fresh
installation will not silently overwrite such preserved files.

The same conservative behavior applies to normal uninstall.

This prevents replacement installation or uninstall from silently destroying
local edits.

## Doctor

Installed releases contain:

    node ~/.local/share/opencode-mcp-orchestrator/current/libexec/doctor.mjs

The doctor checks:

- runtime prerequisites
- installed core files
- configured role models and optional per-role variants
- the step-limit profile and per-role values
- the timeout profile, per-role values, and parent MCP deadline
- the installed Codex MCP timeout matches the orchestrator configuration
- installed agent definitions match the configured limits
- OpenCode agents/plugin
- selected Codex integration
- selected Claude Code integration

A healthy installation ends with:

    DOCTOR_HEALTHY

## Uninstall

The bundled uninstaller is:

    node ~/.local/share/opencode-mcp-orchestrator/current/libexec/uninstall.mjs

By default, model/integration configuration is preserved for future
reinstallation.

To remove configuration as well:

    node ~/.local/share/opencode-mcp-orchestrator/current/libexec/uninstall.mjs --purge-config

Files modified by the user are preserved.

## Building from source

Development requires Node.js and npm.

    npm ci
    npm test

`npm test` builds the release payload and runs the unit and installer
integration suites. The CI workflow uses this as its focused test gate.

Create a local release:

    node scripts/package-release.mjs \
      --version 0.1.0 \
      --repository Mesya82/opencode-mcp-orchestrator

Generated assets:

    release/
    ├── install.sh
    ├── opencode-mcp-orchestrator-0.1.0.tar.gz
    └── SHA256SUMS

## End-to-end testing

Run the clean-container E2E suite with:

    npm run test:e2e

The suite first runs the same focused tests as CI, builds a release artifact,
starts a clean Linux container, installs
the latest Codex CLI, Claude Code, and OpenCode, and exercises the real
curl-based bootstrap installer.

It verifies:

- Codex-only installation
- clean replacement from Codex to Claude Code
- transition from Claude Code to both integrations
- same-release clean replacement reinstall
- real Codex and Claude Code MCP registrations
- MCP `initialize` and `tools/list`
- the `scout`, `worker`, and `runner` tool contract
- configured step limits are rendered into installed agent definitions
- installation doctor health
- uninstall cleanup
- preservation of user configuration

Docker or Podman may be used locally. The GitHub E2E workflow also runs daily so
changes in the latest supported client CLIs can surface even when this repository
has not changed.

The scheduled E2E workflow and release publication both run this entrypoint, so
the focused and clean-container suites are mandatory in all pipelines.

To exercise the built MCP server against the currently configured live Scout
model and existing OpenCode authentication, run:

    npm run build
    npm run test:live:scout

This sends a real provider request and may incur provider usage. Install the
same configuration first so the managed agent definition and its configured
step limit match the values read by the live test.

To probe live MCP cancellation through the current source bridge, run:

    npm run build
    npm run test:live:cancel

This starts a read-only `runner` task (`sleep 60`), aborts it after a short
delay (`ABORT_DELAY_MS` argument or `OPENCODE_MCP_CANCEL_PROBE_ABORT_MS`,
default `3000ms`), and passes only when the call rejects promptly as a
cancellation (under `30000ms`), `tools/list` still works, and
`git status --porcelain` is unchanged. Success prints
`LIVE_MCP_CANCELLATION_PASS` with elapsed time; server stderr is printed only
on failure. This is not part of normal CI because it uses a live provider.

This probes the current source bridge against the currently installed
OpenCode backend/agent/plugin unless an isolated deployment is explicitly
supplied (isolated configuration, home directory, and executable path).

## Releases

Pushing a version tag such as:

    git tag v0.1.0
    git push origin v0.1.0

triggers the release workflow, which:

1. installs dependencies using `npm ci`
2. builds bundled runtime artifacts
3. validates entrypoints
4. constructs the release archive
5. generates and validates the unsigned SPDX SBOM
6. verifies its checksum and contents
7. renders the GitHub-specific bootstrap installer
8. publishes the four release assets

Release assets are:

- `install.sh`
- `opencode-mcp-orchestrator-${VERSION}.tar.gz`
- `SHA256SUMS`
- `opencode-mcp-orchestrator-${VERSION}.spdx.json`

The SPDX SBOM is unsigned and is published without provenance or
attestation. `SHA256SUMS` provides SHA-256 integrity checking for the
release archive; it is not signing or provenance. The bootstrap
`SHA256SUMS` lookup is unchanged: it selects the single release archive
entry.

## Project status

The current implementation has been exercised against:

- isolated OpenCode Scout sessions
- bounded Worker edits and verification
- large Runner logs
- Codex MCP integration
- Claude Code user-scoped MCP integration
- clean installation
- migration from the public v0.1.2 versioned layout to the single-install layout
- conservative uninstall behavior
- checksum-verified bootstrap installation
- clean-container E2E with real latest Codex CLI, Claude Code, and OpenCode
- integration selection changes across clean replacement installs

The project is still pre-1.0. Interfaces and installation details may evolve.