Skip to content

OpenCode harness support (first non-Claude-Code adapter) #22

Description

@vessux

Goal

Make umbel run a bundle under OpenCode, end-to-end: a shimmed opencode in a project pinned to a bundle compiles that bundle for OpenCode and launches it, preserving umbel's non-mutation guarantee (graded per ADR-0008). This is the first non-Claude-Code harness, and the vertical slice that forces the Harness interface into existence — shaped by a real second adapter rather than guessed.

Spec — decisions already made

The design was settled in a grilling session; the canonical spec is the ADRs:

  • ADR-0008 — isolation is graded (non-mutation absolute; suppression strict/scoped/best-effort per harness). The launch contract is { binary, args, env }, not a flag list. The compiled cache is per-harness (hash keys on harness id).
  • ADR-0009 — the capability axes are the existing kinds (skills, agents, hooks, mcps, settings). No abstract layer, no rename. Each adapter declares a per-kind support level and maps each kind to the harness idiom or no-op+warns.
  • ADR-0010 — the harness is selected by the invoked binary; bundles stay harness-agnostic; the shim grows to one binary per harness. Precedence: --harness → invoked binary → UMBEL_HARNESS → default claude-code.

Grounded facts — OpenCode (spike, 2026-06-10)

  • Injection is env-var, not flags: OPENCODE_CONFIG (config file), OPENCODE_CONFIG_DIR (external dir searched for agents/ commands/ modes/ plugins/ skills/, same structure as .opencode/), OPENCODE_CONFIG_CONTENT (inline JSON). There is no --config flag. --dir sets the working dir. Binary opencode; one-shot mode is opencode run.
  • Suppression = best-effort (no strict mode): injected config merges over a project's own .opencode/, global config, and even ~/.claude/CLAUDE.md. umbel cannot suppress ambient config here — surface this honestly.
  • Capabilities:
    • skills ✅ — Agent Skills SKILL.md under skills/; also instructions[] / commands/.
    • agents ✅ — markdown with mode: subagent.
    • mcps ✅ — mcp block (local/remote, per-agent enable); no strict mode.
    • settings ✅ — model/env/permission, JSON, merge layering.
    • hooks ~ best-effort — OpenCode hooks exist only as JS/TS plugins (code, not declarative)no-op+warn for v1 (static-config-only; see ADR-0009 discussion).

Tasks

  1. Extract the Harness interface (was umbel-cxb). Factor src/bundle/compile.ts, src/bundle/claude-args.ts, and src/bundle/exec.ts into a Harness abstraction under src/harness/<name>/: (a) compile output layout, (b) launch spec { binary, args, env } — today's computeClaudeArgs becomes the Claude Code adapter's implementation, (c) per-kind support levels. Claude Code is the first concrete adapter; all existing tests stay green and CC behavior is unchanged. The cache hash now includes the harness id.
  2. Multi-binary shim + harness resolution (was umbel-ji2, per ADR-0010). umbel shim install installs bin/opencode alongside bin/claude, each re-exec'ing its real binary under the existing UMBEL_RESOLVED guard. Resolve the harness by the precedence above. Validate the harness binary is on PATH before compile.
  3. OpenCode adapter (was umbel-e56). Compile a bundle into an OPENCODE_CONFIG_DIR-shaped cache and launch opencode with the injection env vars set. Map skillsskills/, agentsagents/ (with mode: subagent), mcpsmcp config, settingsopencode.json keys; hooks→no-op+warn. Validate the bundle's used kinds against the support levels (warn on best-effort/no-op).
  4. Support matrix doc (was umbel-ypk), CC + OpenCode rows. Add docs/harness-support.md: rows = kinds plus a suppression-grade row; columns = harnesses; cells = support level + a one-line note. Link from README and docs/bundles-spec.md.

Acceptance criteria

  • A shimmed opencode in a project pinned to a bundle launches OpenCode with that bundle's skills/agents/mcps/settings injected from an external cache; the project tree and ~/.config/opencode/ are not written to.
  • A bundle that declares hooks warns (no-op on OpenCode) rather than failing.
  • All existing Claude Code behavior and tests are unchanged (CC now routed through the adapter).
  • docs/harness-support.md shows CC (strict) and OpenCode (best-effort/merge) with honest suppression grades.

Out of scope

  • Copilot CLI adapter (umbel-otv) and Pi adapter (umbel-2rg) — the next two vertical slices.
  • OpenCode hooks via a generated TS plugin, and any Pi MCP/hooks strategy — deferred (static-config-only for v1; revisit with an ADR if demand appears).
  • OpenCode "modes" and primary-persona agents — not modeled.

Spec refs: ADR-0008, ADR-0009, ADR-0010. Folds beads umbel-cxb, umbel-ji2, umbel-e56, umbel-ypk; umbel-750 is decided by ADR-0009.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestready-for-agentFully specified, ready for an AFK agent

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions