opencode-sandbox is built opencode-first: the runner image installs opencode, the launcher starts an opencode serve daemon, provisions a merged opencode.json, checks opencode releases for upgrades, and offers worktree sessions via opencode's HTTP API. We want to support other coding agents/harnesses (pi, claude code) without losing opencode's specific features (config merging, upgrade checks, worktree).
Full design spec (2026-08-30-multi-agent-support-design.md)
Design: Multi-agent support via built-in agent profiles
Date: 2026-08-30
Status: Draft (pending review)
Problem
opencode-sandbox is built opencode-first: the runner image installs opencode, the launcher starts an
opencode serve daemon, provisions a merged opencode.json, checks opencode releases for upgrades, and
offers worktree sessions via opencode's HTTP API. The user wants to support other coding agents/harnesses
(pi, claude code) without losing opencode's specific features (config merging, upgrade checks, worktree).
This is an architectural change: it introduces an agent abstraction that restructures how the launcher
wires agent-specific behavior, and opens the door to more agents later.
Goals
- Support
opencode (default, zero-config, fully backward compatible), pi, and claude-code as
built-in agent profiles, selected via a --agent <name> flag / config key.
- Preserve opencode's config merging, upgrade checks, and worktree support.
- Each agent owns a dedicated config subdirectory (mirroring how opencode nests under
~/.config/opencode-sandbox/opencode/).
- Config merging uses a per-agent glob pattern over files in that subdirectory; the deep-merge algorithm
is shared, non-plugin code. Only JSON and YAML are supported initially.
- Agents that lack a capability degrade gracefully (e.g. no daemon/worktree for pi/claude) rather than
breaking.
Non-goals
- Dynamic loading of third-party agent plugins (external binaries, Go
plugin .so). Agents are built in.
- Interactive-login flows inside the VM.
- Writing credential files back / syncing them from the VM to the host.
- A
share/ mirror directory or verbatim-mirror beyond what the config-mirror spec already covers.
- File-permission (0600) hardening for credential files. Existing gap, tracked separately.
- Implementing pi/claude as full daemon/serve multi-client servers like opencode. They run interactively
via a PTY when the daemon capability is absent.
Context (verified)
opencode-specific surface in the codebase
internal/sandbox/image/data/Dockerfile — installs opencode via curl https://opencode.ai/install,
ARG OPENCODE_VERSION, LABEL org.opencode-sandbox.opencode-version, ENV OPENCODE_DISABLE_AUTOUPDATE.
internal/sandbox/image/{build,fetch,version_label}.go — version ARG resolution, LatestOpenCodeVersion,
the version label read-back (imageInfo.OpenCodeVersion).
internal/opencode/opencode.go — GitHub releases/latest endpoint + VersionCompare.
internal/sandbox/vm/upgrade.go — once-per-day upgrade offer against the baked version.
internal/opencodeconfig/opencodeconfig.go — deep-merges every .json/.jsonc/.json5 file in the dir
into one opencode.json.
internal/sandbox/reprovision/config_files.go — LoadConfigFiles builds ConfigFiles (merged config +
home.yaml files), provisions to OpenCodeConfigPath(home) = ~/.config/opencode/opencode.json.
internal/sandbox/vm/daemon.go — opencode serve --hostname ... --port ..., health poll on
127.0.0.1:4096/global/health, kill via pkill -f 'opencode serve'.
internal/sandbox/vm/worktree.go — ResolveTarget uses the opencode daemon HTTP API
(/experimental/worktree), opencode's slugify, JSON response shapes.
internal/sandbox/session/run.go — buildAttachCommand = opencode attach http://127.0.0.1:4096 --dir <target>.
cmd/opencode-sandbox/constants.go — --opencode-version flag; configpaths hardcodes opencode/ dirs.
Existing config-mirror spec
docs/superpowers/specs/2026-08-28-opencode-config-mirror-design.md (pending) reworks the opencode config
layout into a verbatim top-level mirror plus a config-snippets/ merge subdirectory. It keeps
auth.json out of scope (it stays on the home.yaml path). This multi-agent design builds on the same
principles: per-agent config dir, snippet merge, secrets stay on the secrets mechanism.
Agent runtime models (researched)
| Agent |
Daemon/serve |
Config dir |
Credential file |
Env-var auth |
Upgrade source |
| opencode |
yes, serve + attach, port 4096, worktree API |
~/.config/opencode/ |
~/.local/share/opencode/auth.json |
OPENCODE_API_KEY |
GitHub releases/latest |
| pi |
none native (community wrappers exist) |
~/.pi/agent/ |
~/.pi/agent/auth.json (0600) |
per-provider (ANTHROPIC_API_KEY, OPENAI_API_KEY, ...) |
pi.dev latest / PI_SKIP_VERSION_CHECK |
| claude-code |
none native (claude mcp serve stdio) |
~/.claude/ (CLAUDE_CONFIG_DIR) |
~/.claude/.credentials.json (0600, machine-managed) |
ANTHROPIC_AUTH_TOKEN, ANTHROPIC_API_KEY, CLAUDE_CODE_OAUTH_TOKEN |
none (self-manages) |
Key insight: all three agents support env-var auth as a first-class headless channel. opencode-sandbox
already injects env secrets into the VM via the microsandbox secret mechanism (never written to disk), which
is the natural and most secure way to authenticate these agents. File-based credential provisioning stays
possible via home.yaml (opt-in) but is not the primary path and is not implemented by the agent profiles.
Design
1. Package layout
internal/agent/
agent.go // Agent core interface + Registry (Register/Lookup)
capabilities.go // optional capability interfaces
image.go // ImageSpec struct
opencode.go // opencode profile (default)
pi.go // pi profile
claudecode.go // claude code profile
2. Core Agent interface
package agent
type Agent interface {
// Name is the canonical id, e.g. "opencode", "pi", "claude-code".
Name() string
// ConfigDirName is the subdirectory under the tool's config dir holding this
// agent's snippet files, e.g. "opencode", "pi", "claude".
ConfigDirName() string
// ImageSpec returns the structured bits needed to bake the agent into the
// runner image: version ARG, version label, ENV vars, and install command.
ImageSpec() ImageSpec
}
3. Registry
var registry = map[string]Agent{}
func Register(a Agent)
func Lookup(name string) (Agent, bool) // "" or "opencode" => opencode (default)
func Names() []string // for --agent flag help & docs
The three built-ins register themselves in init(). Lookup("") returns the opencode profile, so
behavior is unchanged with zero config.
4. Optional capability interfaces (type-asserted discovery)
The framework type-asserts the concrete profile to discover optional capabilities. A profile that does not
implement an interface simply lacks that capability.
// DaemonProvider runs a long-lived server that clients attach to (opencode only).
type DaemonProvider interface {
DaemonStartCmd(serveOnly bool) string
DaemonKillCmd() string
DaemonHealthCmd() string
DaemonHealthParse(stdout string) (bool, error)
WorktreeListCmd() string
WorktreeCreateCmd(spec options.WorktreeSpec) string
WorktreeParseDir(stdout string) (string, bool)
}
// UpgradeChecker can resolve and compare releases.
type UpgradeChecker interface {
LatestVersion(ctx context.Context) (string, error)
VersionCompare(a, b string) int
}
// ConfigMerger merges snippet files matching a pattern into a single config document.
type ConfigMerger interface {
SnippetPattern() string // e.g. "opencode-*.json*", "pi-*.{json,yaml}"
VMConfigPath(home string) string // e.g. ~/.config/opencode/opencode.json
BuildMerged(userDir, projectDir string) ([]byte, []string, bool, error)
}
// AttachRunner starts the client TUI/session.
type AttachRunner interface {
AttachCommand(target string, args []string) string
}
Discovery:
if p, ok := a.(DaemonProvider); ok { ensureDaemon(p, ...) }
if p, ok := a.(UpgradeChecker); ok { resolveBuildVersion(p, ...) }
if p, ok := a.(ConfigMerger); ok { mergeAndProvision(p, ...) }
5. Capability matrix
| Capability |
opencode |
pi |
claude-code |
| AttachRunner |
opencode attach http://127.0.0.1:4096 --dir <target> |
pi (interactive) |
claude (interactive) |
| DaemonProvider |
yes (serve + worktree) |
no |
no |
| UpgradeChecker |
yes (GitHub releases) |
yes (pi.dev latest) |
no |
| ConfigMerger |
opencode-*.json* → ~/.config/opencode/opencode.json |
pi-*.{json,yaml} → ~/.pi/agent/settings.json |
*.json → ~/.claude/settings.json |
| ImageSpec |
opencode install |
npm i -g @earendil-works/pi-coding-agent |
npm i -g @anthropic-ai/claude-code |
6. Default behaviors per missing capability
- No DaemonProvider (pi/claude):
setUpSandbox skips ensureDaemon/worktree; AttachCommand launches
the interactive TUI directly through the existing PTY attach path (runAttach). --worktree and
--serve-only are rejected at flag-parsing time with a clear "not supported by agent X" error.
- No UpgradeChecker (claude): the build-version resolution path is skipped; the image uses whatever
version the profile pins. No upgrade prompt/state.
- No ConfigMerger: nothing provisioned for config; only
home.yaml files are provisioned.
7. Config subdirectories per agent
configpaths gains an agent dimension. Today UserOpencodeConfigDir() / ProjectOpencodeConfigDir()
hardcode opencode/. These become UserAgentConfigDir(agent) / ProjectAgentConfigDir(agent),
returning UserConfigDir()/<agent.ConfigDirName()> (and project equivalent). opencode keeps
opencode/ as ConfigDirName, so existing layouts are unchanged.
8. Config merging (shared algorithm, pattern-based)
internal/opencodeconfig becomes agent-agnostic:
- Keep the
deepMerge algorithm unchanged (shared, non-plugin code).
- Change
scanSnippets from "all .json* files in dir" to "files matching the agent's
SnippetPattern() glob".
- Add YAML/JSON parsing (
.yaml, .yml) into map[string]any, then deep-merge maps. JSON5 stays for
opencode. JSON and YAML only (per non-goals).
BuildMerged returns the merged document + ordered source list + "has snippets" boolean.
- The VM path comes from
ConfigMerger.VMConfigPath(home).
9. Image provisioning
type ImageSpec struct {
VersionArg string // "OPENCODE_VERSION"
VersionLabel string // "org.opencode-sandbox.opencode-version"
DisableUpdateEnv string // "OPENCODE_DISABLE_AUTOUPDATE" ("" if none)
InstallCommand string // e.g. "curl ... | bash -s -- --version \"$OPENCODE_VERSION\" && cp ..."
}
The image package generates the base Dockerfile with agent.ImageSpec() spliced in, replacing the
hardcoded opencode block. Version resolution, the label read-back for imageInfo.OpenCodeVersion, and
upgrade state all become agent-parameterized.
10. Authentication (minimal, no new interface)
Authentication deliberately does not add an agent interface or new plumbing. Two existing channels
cover all three agents:
- Env-var auth (preferred): the existing microsandbox secret mechanism (
env.secret /
env.secret.yaml) injects secrets into the VM as environment variables, never written to disk. Each
agent reads env vars:
- opencode:
OPENCODE_API_KEY
- pi: per-provider vars (
ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY, ...)
- claude-code:
ANTHROPIC_AUTH_TOKEN, ANTHROPIC_API_KEY, CLAUDE_CODE_OAUTH_TOKEN
- File-based auth (opt-in): the existing
home.yaml mechanism can provision an agent's credential
file verbatim (e.g. ~/.pi/agent/auth.json). This is opt-in and remains the user's responsibility; it is
not the default path. claude's .credentials.json is machine-managed upstream; hand-provisioning it is
unsupported, so env vars are the documented path for claude.
The agent profiles do not implement any auth interface. This is documented in docs/configuration.md
per agent (which env vars to put in env.secret, or how to provision a credential file via home.yaml).
The EnvAuthSpec idea from earlier iterations is dropped: no code, only documentation.
11. What stays generic vs agent-parameterized
- Stays generic / non-plugin: deep-merge, home.yaml provisioning, env/secrets, network, VM lifecycle,
session/reap, docker, daemon orchestration (driven by the DaemonProvider interface).
- Becomes agent-parameterized: configpaths dirs, image Dockerfile generation, upgrade check/state,
daemon + worktree, attach command, the version flag.
12. CLI surface
- New
--agent <name> flag (run/build), default opencode. Stored in viperconfig.
- Replace
--opencode-version with a single --agent-version that applies to the active agent; keep
--opencode-version as a deprecated alias for backward compatibility.
--worktree and --serve-only are rejected at flag-parsing time for agents without DaemonProvider.
Phasing (milestones)
This is a large refactor. Implement in two milestones, both fully backward compatible:
- Milestone 1 — opencode-only refactor (no behavior change): introduce
internal/agent, define the
interface + capability interfaces, register only the opencode profile, and refactor
configpaths/image/upgrade/daemon/worktree/session/reprovision/CLI to drive through the
agent interface. Add the --agent flag (only opencode valid) and the pattern-based+YAML config merge.
Prove the seam: make check green, behavior identical.
- Milestone 2 — add pi and claude-code profiles: implement the two profiles, wire their
ImageSpec/AttachRunner/ConfigMerger (and pi's UpgradeChecker), and the flag-time rejection of
--worktree/--serve-only. Docs + changelog for both agents.
Testing
- Unit tests in
internal/agent: registry lookup/default, capability discovery via type assertion,
each profile's Name/ConfigDirName/ImageSpec/SnippetPattern/VMConfigPath.
- Unit tests in
internal/opencodeconfig: pattern-based merge, YAML merge, JSON5 (opencode) merge,
mixed-format merge across user+project dirs.
- Update
internal/sandbox/vm/{upgrade,daemon,worktree}_test.go to drive through the agent interface
(opencode profile) so existing behavior is preserved.
- Update
internal/sandbox/image tests for generated Dockerfile from ImageSpec.
- CLI tests in
cmd/opencode-sandbox/cli_*_test.go: --agent flag selection, default opencode, and the
rejection of --worktree/--serve-only for pi/claude.
make check passes (fmt, lint, test).
Documentation & changelog
README.md, docs/configuration.md, docs/runner-image.md: document --agent, per-agent config
subdirs, snippet patterns, and per-agent authentication (env-var secrets + opt-in home.yaml
credential files).
CHANGELOG.md: add an [Unreleased] entry for multi-agent support.
Files touched (anticipated)
internal/agent/ (new) — interface, registry, profiles.
internal/opencodeconfig/opencodeconfig.go — pattern-based + YAML merge.
internal/sandbox/image/{dockerfile,build,fetch,version_label}.go — ImageSpec-driven generation.
internal/sandbox/vm/{upgrade,daemon,worktree,reconfig,run_orchestrate}.go — drive through agent.
internal/sandbox/session/run.go — AttachCommand via agent.
internal/configpaths/configpaths.go — agent-dimensioned config dirs.
internal/sandbox/reprovision/config_files.go — agent-driven merge/provision path.
cmd/opencode-sandbox/ — --agent flag, version flag rename/alias.
README.md, docs/, CHANGELOG.md.
Motivation
opencode-sandbox is built opencode-first: the runner image installs opencode, the launcher starts an
opencode servedaemon, provisions a mergedopencode.json, checks opencode releases for upgrades, and offers worktree sessions via opencode's HTTP API. We want to support other coding agents/harnesses (pi, claude code) without losing opencode's specific features (config merging, upgrade checks, worktree).Proposed behavior
Introduce a built-in agent abstraction (
internal/agent) so thatopencode(default, zero-config, fully backward compatible),pi, andclaude-codeare supported via a--agent <name>flag / config key. The runner image installs the selected agent; the launcher drives version resolution, daemon, worktree, config merging, and the attach command through the agent profile. Agents lacking a capability (pi/claude have no native serve/attach daemon or worktree API) degrade gracefully instead of breaking.Key decisions (from design)
~/.config/opencode-sandbox/<agent>/(opencode already nests this way).opencode-*.json*,pi-*.{json,yaml}) over files in that dir; thedeepMergealgorithm stays shared, non-plugin code. JSON and YAML only (JSON5 stays for opencode).DaemonProvider,UpgradeChecker,ConfigMerger,AttachRunner. opencode implements all; pi implements upgrade+config+attach; claude implements config+attach.ImageSpec(version ARG / label / ENV / install command) rendered into the runner Dockerfile.auth.json/.credentials.jsonstays an opt-inhome.yamlprovisioning concern. Documented per agent, no code.--worktree/--serve-onlyrejected at flag parse.Phasing
internal/agent, register only opencode, refactor image/upgrade/daemon/worktree/session/reprovision/CLI to drive through the interface, add--agentflag (onlyopencodevalid) and the pattern-based+YAML merge.make checkgreen, behavior identical.Alternatives considered
plugin.so packages — fragile across Go versions, not portable to all platforms.--agent-versionvs--opencode-version— a single--agent-versionwith--opencode-versionkept as a deprecated alias.Design spec
Full design spec (2026-08-30-multi-agent-support-design.md)
Design: Multi-agent support via built-in agent profiles
Date: 2026-08-30
Status: Draft (pending review)
Problem
opencode-sandbox is built opencode-first: the runner image installs opencode, the launcher starts an
opencode servedaemon, provisions a mergedopencode.json, checks opencode releases for upgrades, andoffers worktree sessions via opencode's HTTP API. The user wants to support other coding agents/harnesses
(pi, claude code) without losing opencode's specific features (config merging, upgrade checks, worktree).
This is an architectural change: it introduces an agent abstraction that restructures how the launcher
wires agent-specific behavior, and opens the door to more agents later.
Goals
opencode(default, zero-config, fully backward compatible),pi, andclaude-codeasbuilt-in agent profiles, selected via a
--agent <name>flag / config key.~/.config/opencode-sandbox/opencode/).is shared, non-plugin code. Only JSON and YAML are supported initially.
breaking.
Non-goals
plugin.so). Agents are built in.share/mirror directory or verbatim-mirror beyond what the config-mirror spec already covers.via a PTY when the daemon capability is absent.
Context (verified)
opencode-specific surface in the codebase
internal/sandbox/image/data/Dockerfile— installs opencode viacurl https://opencode.ai/install,ARG OPENCODE_VERSION,LABEL org.opencode-sandbox.opencode-version,ENV OPENCODE_DISABLE_AUTOUPDATE.internal/sandbox/image/{build,fetch,version_label}.go— version ARG resolution,LatestOpenCodeVersion,the version label read-back (
imageInfo.OpenCodeVersion).internal/opencode/opencode.go— GitHub releases/latest endpoint +VersionCompare.internal/sandbox/vm/upgrade.go— once-per-day upgrade offer against the baked version.internal/opencodeconfig/opencodeconfig.go— deep-merges every.json/.jsonc/.json5file in the dirinto one
opencode.json.internal/sandbox/reprovision/config_files.go—LoadConfigFilesbuildsConfigFiles(merged config +home.yamlfiles), provisions toOpenCodeConfigPath(home)=~/.config/opencode/opencode.json.internal/sandbox/vm/daemon.go—opencode serve --hostname ... --port ..., health poll on127.0.0.1:4096/global/health, kill viapkill -f 'opencode serve'.internal/sandbox/vm/worktree.go—ResolveTargetuses the opencode daemon HTTP API(
/experimental/worktree), opencode's slugify, JSON response shapes.internal/sandbox/session/run.go—buildAttachCommand=opencode attach http://127.0.0.1:4096 --dir <target>.cmd/opencode-sandbox/constants.go—--opencode-versionflag;configpathshardcodesopencode/dirs.Existing config-mirror spec
docs/superpowers/specs/2026-08-28-opencode-config-mirror-design.md(pending) reworks the opencode configlayout into a verbatim top-level mirror plus a
config-snippets/merge subdirectory. It keepsauth.jsonout of scope (it stays on thehome.yamlpath). This multi-agent design builds on the sameprinciples: per-agent config dir, snippet merge, secrets stay on the secrets mechanism.
Agent runtime models (researched)
serve+attach, port 4096, worktree API~/.config/opencode/~/.local/share/opencode/auth.jsonOPENCODE_API_KEY~/.pi/agent/~/.pi/agent/auth.json(0600)ANTHROPIC_API_KEY,OPENAI_API_KEY, ...)pi.devlatest /PI_SKIP_VERSION_CHECKclaude mcp servestdio)~/.claude/(CLAUDE_CONFIG_DIR)~/.claude/.credentials.json(0600, machine-managed)ANTHROPIC_AUTH_TOKEN,ANTHROPIC_API_KEY,CLAUDE_CODE_OAUTH_TOKENKey insight: all three agents support env-var auth as a first-class headless channel. opencode-sandbox
already injects env secrets into the VM via the microsandbox secret mechanism (never written to disk), which
is the natural and most secure way to authenticate these agents. File-based credential provisioning stays
possible via
home.yaml(opt-in) but is not the primary path and is not implemented by the agent profiles.Design
1. Package layout
2. Core
Agentinterface3. Registry
The three built-ins register themselves in
init().Lookup("")returns the opencode profile, sobehavior is unchanged with zero config.
4. Optional capability interfaces (type-asserted discovery)
The framework type-asserts the concrete profile to discover optional capabilities. A profile that does not
implement an interface simply lacks that capability.
Discovery:
5. Capability matrix
opencode attach http://127.0.0.1:4096 --dir <target>pi(interactive)claude(interactive)pi.devlatest)opencode-*.json*→~/.config/opencode/opencode.jsonpi-*.{json,yaml}→~/.pi/agent/settings.json*.json→~/.claude/settings.jsonnpm i -g @earendil-works/pi-coding-agentnpm i -g @anthropic-ai/claude-code6. Default behaviors per missing capability
setUpSandboxskipsensureDaemon/worktree;AttachCommandlaunchesthe interactive TUI directly through the existing PTY attach path (
runAttach).--worktreeand--serve-onlyare rejected at flag-parsing time with a clear "not supported by agent X" error.version the profile pins. No upgrade prompt/state.
home.yamlfiles are provisioned.7. Config subdirectories per agent
configpathsgains an agent dimension. TodayUserOpencodeConfigDir()/ProjectOpencodeConfigDir()hardcode
opencode/. These becomeUserAgentConfigDir(agent)/ProjectAgentConfigDir(agent),returning
UserConfigDir()/<agent.ConfigDirName()>(and project equivalent). opencode keepsopencode/asConfigDirName, so existing layouts are unchanged.8. Config merging (shared algorithm, pattern-based)
internal/opencodeconfigbecomes agent-agnostic:deepMergealgorithm unchanged (shared, non-plugin code).scanSnippetsfrom "all.json*files in dir" to "files matching the agent'sSnippetPattern()glob"..yaml,.yml) intomap[string]any, then deep-merge maps. JSON5 stays foropencode. JSON and YAML only (per non-goals).
BuildMergedreturns the merged document + ordered source list + "has snippets" boolean.ConfigMerger.VMConfigPath(home).9. Image provisioning
The image package generates the base Dockerfile with
agent.ImageSpec()spliced in, replacing thehardcoded opencode block. Version resolution, the label read-back for
imageInfo.OpenCodeVersion, andupgrade state all become agent-parameterized.
10. Authentication (minimal, no new interface)
Authentication deliberately does not add an agent interface or new plumbing. Two existing channels
cover all three agents:
env.secret/env.secret.yaml) injects secrets into the VM as environment variables, never written to disk. Eachagent reads env vars:
OPENCODE_API_KEYANTHROPIC_API_KEY,OPENAI_API_KEY,GEMINI_API_KEY, ...)ANTHROPIC_AUTH_TOKEN,ANTHROPIC_API_KEY,CLAUDE_CODE_OAUTH_TOKENhome.yamlmechanism can provision an agent's credentialfile verbatim (e.g.
~/.pi/agent/auth.json). This is opt-in and remains the user's responsibility; it isnot the default path. claude's
.credentials.jsonis machine-managed upstream; hand-provisioning it isunsupported, so env vars are the documented path for claude.
The agent profiles do not implement any auth interface. This is documented in
docs/configuration.mdper agent (which env vars to put in
env.secret, or how to provision a credential file viahome.yaml).The
EnvAuthSpecidea from earlier iterations is dropped: no code, only documentation.11. What stays generic vs agent-parameterized
session/reap, docker, daemon orchestration (driven by the
DaemonProviderinterface).daemon + worktree, attach command, the version flag.
12. CLI surface
--agent <name>flag (run/build), defaultopencode. Stored in viperconfig.--opencode-versionwith a single--agent-versionthat applies to the active agent; keep--opencode-versionas a deprecated alias for backward compatibility.--worktreeand--serve-onlyare rejected at flag-parsing time for agents withoutDaemonProvider.Phasing (milestones)
This is a large refactor. Implement in two milestones, both fully backward compatible:
internal/agent, define theinterface + capability interfaces, register only the opencode profile, and refactor
configpaths/image/upgrade/daemon/worktree/session/reprovision/CLI to drive through theagent interface. Add the
--agentflag (onlyopencodevalid) and the pattern-based+YAML config merge.Prove the seam:
make checkgreen, behavior identical.ImageSpec/AttachRunner/ConfigMerger(and pi'sUpgradeChecker), and the flag-time rejection of--worktree/--serve-only. Docs + changelog for both agents.Testing
internal/agent: registry lookup/default, capability discovery via type assertion,each profile's
Name/ConfigDirName/ImageSpec/SnippetPattern/VMConfigPath.internal/opencodeconfig: pattern-based merge, YAML merge, JSON5 (opencode) merge,mixed-format merge across user+project dirs.
internal/sandbox/vm/{upgrade,daemon,worktree}_test.goto drive through the agent interface(opencode profile) so existing behavior is preserved.
internal/sandbox/imagetests for generated Dockerfile fromImageSpec.cmd/opencode-sandbox/cli_*_test.go:--agentflag selection, default opencode, and therejection of
--worktree/--serve-onlyfor pi/claude.make checkpasses (fmt, lint, test).Documentation & changelog
README.md,docs/configuration.md,docs/runner-image.md: document--agent, per-agent configsubdirs, snippet patterns, and per-agent authentication (env-var secrets + opt-in
home.yamlcredential files).
CHANGELOG.md: add an[Unreleased]entry for multi-agent support.Files touched (anticipated)
internal/agent/(new) — interface, registry, profiles.internal/opencodeconfig/opencodeconfig.go— pattern-based + YAML merge.internal/sandbox/image/{dockerfile,build,fetch,version_label}.go—ImageSpec-driven generation.internal/sandbox/vm/{upgrade,daemon,worktree,reconfig,run_orchestrate}.go— drive through agent.internal/sandbox/session/run.go—AttachCommandvia agent.internal/configpaths/configpaths.go— agent-dimensioned config dirs.internal/sandbox/reprovision/config_files.go— agent-driven merge/provision path.cmd/opencode-sandbox/—--agentflag, version flag rename/alias.README.md,docs/,CHANGELOG.md.