The OpenCode backend is a thin lifecycle adapter. It does not configure providers,
models, endpoint routing, /connect, credentials, agents, plugins, skills, or
permissions. OpenCode owns those concerns through its active profile.
For local production smoke testing, OpenCode is the preferred first backend. Retinue should reuse the installed OpenCode profile instead of creating a separate child-agent profile.
Recovery follows the product boundary in Retinue Attempt Recovery. Retinue may send steering prompts or start bounded fresh attempts through OpenCode's existing session APIs, but it does not require OpenCode source changes, duplicate OpenCode's session model, or treat stalled child text as trusted evidence.
The default v0.2.0 user path assumes the official OpenCode install script:
curl -fsSL https://opencode.ai/install | bashThe script installs the CLI under $HOME/.opencode/bin/opencode. Retinue first honors
an explicit RETINUE_OPENCODE_COMMAND, then the inherited PATH, then common default
install locations. On Windows that fallback includes
%USERPROFILE%\.opencode\bin\opencode before package-manager shims such as
pnpm/npm/bun.
On POSIX systems, including WSL and macOS, Retinue also checks
$HOME/.opencode/bin/opencode even when the installer did not add it to PATH.
The default plugin integration mode is Retinue-managed auto-serve:
RETINUE_OPENCODE_AUTO_SERVE=1
RETINUE_OPENCODE_HOST=127.0.0.1
Retinue prefers 127.0.0.1:4096 and tries local fallback ports 4097 through 4127
when the preferred port is occupied by an external service. A running OpenCode server
from another environment is treated as external unless the deployment sets
RETINUE_OPENCODE_BASE_URL.
Managed OpenCode auto-serve binds to loopback only by default.
RETINUE_OPENCODE_HOST=0.0.0.0 or another non-loopback host is rejected unless
RETINUE_OPENCODE_ALLOW_NON_LOOPBACK=1 is set for an explicitly isolated environment.
Retinue does not add authentication to the OpenCode server it starts.
Retinue-managed OpenCode servers are not intended to live forever. After the last job
using a managed server becomes terminal, Retinue schedules an idle shutdown; the default
grace period is 30 seconds and can be changed with RETINUE_OPENCODE_SERVER_IDLE_MS.
Shutdown uses process-tree termination so OpenCode helper processes such as language
servers do not keep repository folders locked after the top-level opencode serve
process exits. The trace records opencode_server_idle_shutdown_scheduled,
opencode_server_stopped, or opencode_server_stop_failed for lifecycle debugging.
Retinue also exposes agent-facing runtime lifecycle tools for servers it auto-served.
Use restart_runtime with an absolute cwd to refresh an OpenCode server after
provider/profile changes. Use stop_runtime with cwd or all: true for maintenance
stops. Both tools refuse to stop a server with running or unresolved stalled Retinue
jobs unless force: true is set; forced stops mark matching running or stalled job
metadata as killed.
Explicit attach remains available:
RETINUE_OPENCODE_BASE_URL=http://127.0.0.1:4096
The explicit URL must be loopback HTTP. Paths are ignored and normalized to the origin.
If both RETINUE_OPENCODE_BASE_URL and RETINUE_OPENCODE_AUTO_SERVE=1 are set, Retinue
treats the explicit URL as the preferred attach target and falls back to managed
auto-serve when that target is unreachable or does not look like OpenCode. Set only
RETINUE_OPENCODE_BASE_URL for strict externally managed attach mode.
Model override is optional, and persistent deployment defaults should come from environment variables:
RETINUE_OPENCODE_MODEL=litellm/pro-router
CLI/MCP request fields win over environment variables, and environment variables win
over the packaged Retinue JSON defaults. If none is set, retinue does not send model;
for plugin installs, the packaged fallback may provide an OpenCode agent.
Retinue v0.2.0 plugin deployments ship opencode.agent: "explore" in
retinue.config.json as an installation-cache fallback. Do not edit the cached file for
persistent deployment state; plugin refreshes can overwrite it. Use
RETINUE_OPENCODE_AGENT when a deployment needs a persistent default. Retinue follows
the active OpenCode profile and selected OpenCode agent semantics. spawn_agent can
override the OpenCode agent for one child with agent; it does not expose a separate
Retinue access-mode layer. For OpenCode-native semantics, explore is the default
read-only subagent and general is the built-in writable subagent for multi-step work.
build is a primary agent/root candidate, not the default writable subagent. If the
runtime exposes /agent, Retinue validates the requested OpenCode agent before creating
the child session and rejects unknown values such as Codex model or Codex native
subagent names.
OpenCode profile state is OpenCode-owned. Permissions, plugins, skills, model defaults, provider config, and agent defaults are profile details, not separate Retinue product controls.
Phase 1 should make the local E2E run reliably with the effective OpenCode deployment profile. If the active profile blocks unattended child-agent execution, adjust the deployment-level OpenCode profile and record the non-sensitive fact in the local runbook. Do not expose profile selection or permission mode as a normal Codex-facing tool argument.
OpenCode MCP config uses the top-level mcp key. Do not copy Claude/Codex-style
mcpServers into opencode.json; current OpenCode rejects it as an unrecognized key.
When a model override is provided, retinue accepts the OpenCode CLI-style
provider/model string and sends the server API shape:
{
"model": {
"providerID": "litellm",
"modelID": "pro-router"
}
}Local server start command shape:
opencode serve --hostname 127.0.0.1 --port 4096The corresponding environment knobs are:
RETINUE_OPENCODE_AUTO_SERVE=1
RETINUE_OPENCODE_COMMAND=opencode
RETINUE_OPENCODE_HOST=127.0.0.1
RETINUE_OPENCODE_PORT=4096
RETINUE_OPENCODE_SERVER_IDLE_MS=30000
When RETINUE_OPENCODE_PORT is explicit, Retinue does not silently fall back to another
port. Set it to a concrete loopback port in the 1..65535 range; 0 is rejected
because Retinue cannot discover the random port that opencode serve --port 0 would
bind.
MCP hosts commonly enforce their own per-tool timeout. Retinue therefore clamps
wait_agent and opencode_wait calls to a host-safe maximum of 180 seconds by default.
This gives the default 45-second OpenCode soft-stall detectors enough room to submit one
final-answer rescue prompt and wait for the rescue to complete before the MCP wait
returns stalled. Complex OpenCode tasks can still be polled with repeated wait calls;
set RETINUE_MCP_WAIT_MAX_MS only when the host timeout is known to be different.
Retinue's local HTTP clients also apply a 30-second transport timeout to OpenCode and
daemon requests. Set RETINUE_HTTP_TIMEOUT_MS when a deployment needs a different local
request ceiling; set it to 0 only when another layer already enforces a reliable
timeout.
Product spawn_agent calls are agent-aware on the OpenCode backend. OpenCode owns the
effective profile, permissions, tools, plugins, and agent behavior. Retinue does not
inject a read-only prompt contract, does not send prompt-level tool overrides, and does
not expose access_mode or bash_policy on the product MCP tool. The normal OpenCode
child-agent choices are OpenCode's own subagents: explore for read-only research and
general for writable multi-step work.
Retinue does not submit OpenCode SubtaskPartInput as the normal spawn path because
that path runs inside the parent prompt loop and can wake the parent agent/model after
the subtask completes. Instead, Retinue keeps a direct child session topology and
mirrors the observable OpenCode TaskTool child-session behavior: it reads /agent
metadata, creates the requested child session under the shared root parent, inherits
the parent session's deny and external_directory rules, and denies child
todowrite/task unless the requested OpenCode subagent explicitly allows them. Parent
agent restrictions do not transfer to the child; the selected OpenCode subagent owns its
own capabilities. OpenCode TaskTool features that depend on the parent prompt loop,
such as native background and task_id, are not exposed as Retinue equivalents.
Retinue job handles and task-level recovery attempts belong to the MCP lifecycle and
must not be described as OpenCode task continuation.
The OpenCode backend defaults to shared_root: one Retinue MCP server session reuses
one inert OpenCode root session for multiple child jobs with the same server URL and
cwd. The structural root has no agent binding. Set
RETINUE_OPENCODE_ROOT_BINDING_MODE=per_spawn only for legacy
compatibility, isolation probes, or debugging. In per_spawn, each Retinue child job
creates its own unprompted root session plus one prompted child session.
The Codex plugin ships an installation-cache retinue.config.json for package defaults
such as maxConcurrentAgents and opencode.agent. Treat it as read-only packaged
fallback, not user configuration. Persistent deployment choices should use environment
variables such as RETINUE_MAX_CONCURRENT_AGENTS, RETINUE_GLOBAL_AGENT_BUDGET,
RETINUE_OVERFLOW_STRATEGY, RETINUE_MAX_QUEUED_AGENTS, and RETINUE_OPENCODE_AGENT.
RETINUE_MAX_CONCURRENT_AGENTS is scoped to one MCP server session and defaults to 3.
RETINUE_GLOBAL_AGENT_BUDGET is scoped to the shared RETINUE_STATE_DIR and prevents
multiple Codex/Hermes sessions from multiplying local child-agent budgets; when omitted
it defaults to max(5, RETINUE_MAX_CONCURRENT_AGENTS). The default overflow strategy is
queue: when session or global active slots are full, spawn_agent returns a queued
job handle and later wait/list/close/spawn calls opportunistically promote queued jobs
when slots open. Set RETINUE_OVERFLOW_STRATEGY=evict only when the deployment
explicitly wants the old same-session oldest-running eviction behavior.
RETINUE_MAX_QUEUED_AGENTS bounds queued jobs and defaults to 20; queue exhaustion
returns resource_exhausted with reason: "queue_full". A single spawn_agent call
may still pass agent to choose another OpenCode agent for that child. Backend,
profile, model, provider, OpenCode server, access_mode, and bash_policy are not
normal product tool arguments.
If a wait call returns status: "running", keep the same jobId and call wait again.
Do not spawn a replacement job only because one wait window elapsed.
OpenCode prompt_async can spend time in upstream tool-call setup before the HTTP call
returns. Retinue therefore persists job metadata and returns the jobId after creating
the OpenCode session, while the prompt submission continues in the background if it does
not complete immediately. If prompt submission fails immediately, the spawn response
returns the updated failed job. If prompt submission later fails after the spawn
response has returned, the job moves to failed and writes opencode_job_prompt_failed
diagnostics under the job directory and global trace.
If OpenCode returns assistant rounds with no visible text, Retinue keeps them out of
successful results. No-final-text stalls include blank provider placeholders,
zero-progress assistant placeholders, incomplete latest assistant rounds, completed
tool-call loops, and empty finish=stop assistant rounds.
Retinue does not submit a same-session no-tools recovery prompt, does not switch the
OpenCode agent behind the caller's back, and does not override the child's tool set to
simulate read-only behavior. OpenCode owns the active session, agent profile,
permission engine, and tool availability. Retinue observes the resulting session and,
when configured retry policy allows it, may start bounded fresh task-level attempts with
new OpenCode child jobs/sessions. The original job remains stalled and non-evidence;
the wait response is re-keyed to the selected attempt and includes requestedJobId,
selectedAttemptJobId, and attemptChain provenance.
If RETINUE_OPENCODE_TASK_ATTEMPT_MAX is unset, malformed tool-call stalls get up to
2 fresh attempts and other recoverable stalls get up to 1. Set
RETINUE_OPENCODE_TASK_ATTEMPT_MAX=0 to disable fresh task attempts, or set an explicit
number to override the reason-specific defaults for controlled experiments. If the
selected fresh attempt also exhausts the budget, the stalled result states that no
usable child-agent conclusion is available. If the wait window expires without usable
final text or a selected attempt, the job returns stalled with diagnostics so the
caller can inspect logs or close the child agent.
Blank provider placeholders, incomplete latest assistant rounds,
pending/running read tool calls, and completed tool-call loops with no final text
use 45-second default windows. Zero-progress assistant placeholders also use a
45-second default window unless they are the latest round after completed tool-call
progress. That finalization-after-tool-progress case uses a 120-second default window
so Retinue does not interrupt a normal OpenCode tool chain while the final answer is
still being generated.
Empty finish=stop assistant rounds are classified after one empty round by default.
Deployments that need a different cutoff can set
RETINUE_OPENCODE_STALL_BLANK_ASSISTANT_MS,
RETINUE_OPENCODE_STALL_ZERO_PROGRESS_ASSISTANT_MS,
RETINUE_OPENCODE_STALL_FINALIZATION_AFTER_TOOL_PROGRESS_MS,
RETINUE_OPENCODE_STALL_INCOMPLETE_ASSISTANT_MS,
RETINUE_OPENCODE_STALL_READ_TOOL_MS,
RETINUE_OPENCODE_STALL_COMPLETED_TOOL_LOOP_MS, or
RETINUE_OPENCODE_STALL_EMPTY_ASSISTANT_ROUNDS. Non-empty reasoning parts are
treated as OpenCode/provider progress for an unfinished assistant round, even when
visible text has not started yet. Retinue still does not return reasoning text as
trusted stdout; it only avoids interrupting the active OpenCode chain with a
final-answer classification while reasoning is the only observed progress. An
unfinished assistant round that already contains completed tool parts is also treated
as OpenCode tool-chain finalization progress and uses the longer finalization window
instead of the shorter incomplete-round window. If OpenCode
attaches a provider/API error to an assistant message, Retinue classifies it as
provider_error and includes the redacted error preview in the stalled result, so
authentication or router failures are not mislabeled as child write intent. OpenCode
may attach patch parts from its snapshot system or report write-capable tool parts
in its message stream. Retinue reports those as neutral diagnostics (patchPartCount,
writeIntentToolPartCount) but does not enforce a separate read-only policy on top of
OpenCode's native agent/profile permissions.
stalled jobs are attention-required terminal jobs for MCP
slot accounting: they do not occupy Retinue's active child-agent pool, but cleanup still
preserves their artifacts until the caller explicitly closes or removes them. If only
status has observed a recoverable stall, a later OpenCode final answer can still
promote the job to completed; once result has persisted a stalled stdout, later
OpenCode messages do not promote that same Retinue job to completed or overwrite the
persisted stalled result.
Stall diagnostics include a compact stallReason and stallSummary when Retinue can
classify the failure. Current reason values are provider_error,
provider_reasoning_content_error, provider_blank_assistant,
provider_zero_progress, read_tool_stalled, read_tool_invalid_input,
tool_invalid_input,
incomplete_assistant_round, backend_no_final_text, tool_loop_no_completion, and
external_directory_permission_pending. A read_tool_stalled diagnostic also includes
runningReadToolParts, runningReadToolCallIds, and runningReadToolPartSummaries so
the operator can identify the stuck OpenCode read call without first expanding the full
message history. read_tool_invalid_input means the provider/model emitted a malformed
read tool call, such as input {} with no usable filePath. tool_invalid_input
means a non-read tool call, such as grep, had explicitly empty or null input. Treat
both as stalled provider/tool-call output, not review evidence. provider_reasoning_content_error means
a thinking-mode route rejected missing reasoning_content continuity and should be
handled as provider/router configuration evidence. When provider_blank_assistant or
provider_zero_progress also has finalizationAfterToolProgress: true, it means
OpenCode made tool-call progress and then did not produce a final answer inside the
longer finalization window; it is not evidence that the provider returned an empty
final answer. Retinue also treats OpenCode step-start plus empty text assistant
messages after completed tool calls, and unfinished assistant rounds with completed
tool parts, as finalization placeholders for this window. If a
selected fresh attempt also
stalls, the result keeps status: "stalled" and includes the attempt-exhausted
explanation in stdout/stderr rather than manufacturing a trusted answer from incomplete
evidence. Stalled result diagnostics also expose selectedAssistantTextBytes and
selectedAssistantSha256 when Retinue captured visible assistant text. These are
backend-observation labels, not provider-specific
configuration; for example, provider_zero_progress can describe any OpenCode provider
or model router that produces zero useful assistant text after tool calls.
Provider/no-final-text and malformed-tool stall reasons may trigger the separate fresh
task-level attempt policy when the attempt budget allows it. read_tool_stalled,
provider_error, and provider_reasoning_content_error return as stalled diagnostics
without a recovery prompt. Fresh attempts create new jobs rather than promoting the
original stalled job.
Retinue writes OpenCode backend diagnostics to the Retinue state directory:
<stateDir>/logs/retinue.jsonl
<stateDir>/jobs/<jobId>/meta.json
<stateDir>/jobs/<jobId>/stdout.log
<stateDir>/jobs/<jobId>/stderr.log
If RETINUE_STATE_DIR is unset, Linux/WSL/macOS defaults to $XDG_STATE_HOME/retinue
or $HOME/.local/state/retinue; Windows defaults to %LOCALAPPDATA%\retinue.
When wait_agent returns running or stalled, its response includes a compact
diagnostic object plus tracePath or job artifact paths where available. Use
diagnostic for the immediate decision, then inspect the trace path for full OpenCode
message summaries, selected model/provider metadata, server URL, and stall diagnostics.
OpenCode permission requests remain OpenCode-owned. Retinue exposes them through an MCP
bridge for supervising agents: wait_agent marks pending permission work with
attentionRequired.kind: "permission" plus compatibility fields permissionRequired: true and permissions; list_permissions lists pending request ids for a Retinue job
or all known jobs when jobId is omitted; and reply_permission replies through
OpenCode's native permission API with once, always, or reject. Each listed
permission includes an approval object with a title, display lines, guidance, and
reply option semantics so the supervising agent can make an explicit decision without
treating the child output as evidence. Permission wait responses keep raw stderr compact
and rely on paths plus structured diagnostics for deeper inspection, so permission
events do not drown out the required supervisor action. Retinue validates that the
request belongs to the job's OpenCode session or child sessions before replying. It does
not auto-approve external directories and does not define a separate permission DSL.
The opencode-* CLI commands and opt-in opencode_* MCP tools are raw backend adapter
surfaces. They exist for development probes, backend debugging, and compatibility
runbooks. They are not the product-level Codex or Hermes delegation contract.
Normal agent delegation should use spawn_agent, wait_agent, close_agent,
list_agents, list_permissions, and reply_permission. Those tools apply the
deployment-selected backend, Retinue-managed OpenCode server lifecycle, bounded
diagnostics, optional per-spawn OpenCode agent, active child-agent pool semantics, and
the OpenCode-native permission bridge. Only expose backend tools with
RETINUE_EXPOSE_BACKEND_TOOLS=1 when intentionally debugging an adapter-specific issue.
Use Verification for deterministic gates and real backend probes. Keep real OpenCode evidence in runbooks or release planning notes, not in this backend reference.