Background orchestration is the default orchestration model for oh-my-opencode-slim. It assumes native OpenCode background subagents are available and changes the orchestrator from a primary worker into a scheduler.
The old model was:
orchestrator works directly → delegates when useful → waits for result
The default background-orchestration model is:
orchestrator plans → dispatches background specialists → monitors → reconciles → verifies
This is a clean rebuild, not a compatibility layer over the old blocking model.
Background orchestration requires an OpenCode release that includes native background subagents, launched with background subagents enabled:
OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true opencode
The required native/background-control tools are:
| Tool | Purpose |
|---|---|
task(..., background: true) |
Start a specialist in the background and immediately return a task ID |
| hook-driven completion | OpenCode injects terminal background task results automatically |
cancel_task |
Plugin-provided tool to cancel a tracked background task by task ID or Background Job Board alias |
wait_for_user |
Plugin-provided orchestrator tool that pauses automatic continuation while the user performs external manual work |
If these are not available, the scheduler cannot use the default background workflow. Configure the environment variable through the installer or use the one-shot export above before starting OpenCode.
Use an OpenCode release that includes native background subagents and hook-driven completion; run opencode --version and update if background tasks are missing.
The orchestrator is not the default implementation worker.
Its job is to:
Specialists do the work. The orchestrator manages the work.
Every non-trivial request follows this loop:
Understand
↓
Plan dependency graph
↓
Dispatch independent specialists in background
↓
Track task IDs and ownership
↓
Continue only independent coordination work
↓
Wait for hook-driven completion
↓
Reconcile results and resolve conflicts
↓
Dispatch follow-up work if needed
↓
Verify
↓
Final response
The orchestrator should not act on assumptions from a still-running task. It can continue scheduling independent work, but dependent work waits for terminal task results.
Before dispatching agents, the orchestrator identifies:
This does not need to be a long plan. It should be just enough structure to avoid wasted work and conflicting edits.
Independent work should be launched with background tasks:
task(
description="Search auth flow",
subagent_type="explorer",
background=true,
prompt="Find the auth entry points, session storage, and login callback paths. Return file paths and a concise map. Do not edit files."
)
The orchestrator records the returned task ID and keeps working only on safe, independent coordination.
The scheduler must prevent write conflicts.
Rules:
fixer tasks against overlapping folders unless ownership is
explicit.Background tasks are not complete until OpenCode injects their terminal result or hook-driven completion marks them terminal.
The orchestrator should use background completion events to:
The orchestrator should use cancel_task only when the user asks, or when a
running lane is obsolete, wrong, or conflicts with a safer replacement plan.
Cancellation is not rollback: if cancelling a writer, inspect its partial file
changes before launching a replacement lane.
Terminal jobs are reconciled automatically after their result is injected into the orchestrator session. That lifecycle state is not proof the output was used; the orchestrator must still verify it consumed the relevant result before finalizing.
Specialist outputs are inputs, not final truth. The orchestrator reconciles them against each other and the original user goal.
Verification remains orchestrator-owned, but not necessarily orchestrator-run.
Examples:
designer,oracle,fixer,The final response should only happen after relevant background work is terminal and reconciled.
Read-only reconnaissance and codebase mapping. Usually the first background task for unfamiliar work.
External docs, version-specific API behavior, and real-world examples. Runs in parallel with Explorer when implementation depends on current library behavior.
Bounded implementation worker. Receives a clear objective, file ownership, constraints, and validation expectations.
User-facing UI/UX implementation and review. Owns visual polish, responsive layout, interaction quality, and design consistency.
Architecture, code review, simplification, risk analysis, and high-stakes debugging. Often used after implementation or before risky refactors.
Multi-model decision support for critical trade-offs. It is not a worker pool; it is for judgment where disagreement is useful.
Visual/media analysis isolated from the orchestrator context.
Background orchestration removes the orchestrator-as-worker default.
The orchestrator may directly:
The orchestrator should delegate:
This keeps the main context focused on coordination instead of filling it with worker detail.
Every delegated task should be self-contained.
Include:
If a task is outside a specialist's role, it must not attempt partial work. It returns a brief reason to the orchestrator. The orchestrator treats that reason as routing input to reroute or clarify the task and must not retry the unchanged task with the same specialist.
Good background task prompt:
Investigate src/hooks/task-session-manager for assumptions that a task tool
result means the child task has finished. Do not edit files. Return:
1. exact files/functions involved,
2. which assumptions break with background tasks,
3. recommended code changes,
4. tests that should be added.
Bad background task prompt:
Look into background tasks.
The prompt/runtime treats background tasks as a small job board:
| Field | Meaning |
|---|---|
| task ID | Native OpenCode background task/session ID |
| specialist | Agent type assigned |
| objective | What the task is responsible for |
| state | running, completed, error, cancelled, timed out |
| ownership | Files/folders/subsystems the task may edit |
| dependencies | Tasks that must complete first |
| result | Final task output once terminal |
The current todo list can represent user-visible work, but task IDs and file ownership need to be explicit in the orchestrator's working context.
The plugin is aware that a task return can mean "background job launched"
rather than "work complete". It tracks running task IDs, exposes recent work in
the background job board, updates aliases from task results, and keeps
multiplexer panes attached while the parent orchestrator continues scheduling.
Automatic incomplete-todo continuation is an opt-in beta feature. Idle reconciliation and background-job orchestration always run without it. Enable the beta only when you want hidden continuation prompts:
{
"backgroundJobs": {
"continueOnIdle": true
}
}
When backgroundJobs.continueOnIdle is true, after an orchestrator session
becomes idle the plugin may send at most one internal, delayed continuation
prompt when OpenCode reports incomplete todos. That limit is per session between
real external user messages (text/file/image).
Synthetic/internal inputs and subsequent idle/busy events do not rearm it. A
real user message rearms the one-shot nudge once per message identity
(chat.message messageID / message.id), shared across hook instances in the
process; observing the same message twice does not open a second epoch. Internal
prompts and todo updates do not rearm.
Continuation is suppressed when the SDK reports the parent or any direct child
as active, when a terminal child result has not yet been reconciled, during
foreground fallback, while OpenCode is waiting for a question or permission
response, after the orchestrator calls wait_for_user, or whenever SDK data is
unavailable or malformed. A matching reply, or a rejected question, clears its
tool-backed wait but does not itself inject a nudge; the normal session lifecycle
decides whether a later nudge is needed.
For external manual work, the orchestrator first gives the user concrete steps,
then calls wait_for_user as its final tool action. This explicit signal covers
text-only HITL turns without attempting to infer intent from assistant prose. The
wait remains armed across hook/plugin recreation in the same process and is
cleared only by a distinct real external user message or genuine session
deletion. Re-observing the user message that preceded the wait, synthetic/internal
messages (including foreground-fallback replays), fallback teardown, session
errors, and idle/busy events do not clear it. Immediate choices, clarifications,
and pasted command output continue to use the question tool. If
wait_for_user is intentionally listed in disabled_tools, the orchestrator
uses the question tool as the blocking boundary instead.
This is a best-effort runtime check, not a scheduler or persisted state. The
one-attempt guard and explicit wait_for_user state are process-local (shared
across hook instances in the same JS process via an internal gate). They do not
survive process restart or cross-process boundaries. Recreating a hook/plugin
instance inside the same process does not rearm a consumed or waiting epoch;
only a distinct real external user message (or genuine session deletion) does.
After a process restart, the in-memory job board cannot establish prior result
reconciliation, and the SDK's current session/todo status remains the liveness
authority.
By default, each prompt uses the latest board strategy. The hook removes prior
metadata-tagged board messages and injects the current board snapshot, preserving
the existing strip-and-replace behavior.
For checkpoint-oriented workflows, opt in to the append-only strategy:
{
"backgroundJobs": {
"strategy": "checkpoint-compatible",
"maxRetainedSnapshots": 20
}
}
checkpoint-compatible preserves prior board snapshots and appends a snapshot
anchored after the current real-message tail only when the formatted board
changes. Once created, snapshots are replayed on every managed turn, including
turns with an internal initiator or an empty board; those turns do not create a
new snapshot. Re-running injection with an unchanged board does not create a
duplicate. This changes board message history only; task coordination, storage,
terminal reconciliation, and reusable-session behavior remain unchanged. The
retained snapshot cache is in memory, is limited to 20 snapshots per cache epoch,
and is reset when OpenCode reports a session boundary or a compacted/rebased
message history. maxRetainedSnapshots controls
the epoch size and accepts integers from 1 to 100 (default 20). When a changed
snapshot would exceed the configured limit, all retained snapshots are discarded
and only the new current snapshot is kept. This intentionally creates one cache
miss at the epoch boundary, after which a fresh run of up to the configured limit
can accumulate. The cache is lost on plugin restart, so snapshots are not
restored beyond those present in the current OpenCode message history.
The installer and docs configure background subagents as a requirement for the default scheduler workflow. If background subagents are unavailable, treat it as an environment or OpenCode-version issue rather than an intentional V1 fallback:
Background orchestration requires OpenCode background subagents.
Start OpenCode with:
OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true opencode
No automatic legacy fallback keeps the mental model clean.
User asks:
Make background subagents first-class in this plugin.
The orchestrator should do something like:
At no point does the orchestrator become the main implementer.
Background orchestration is working when:
Background orchestration is not just "parallel agents." It is a scheduler-centered operating model for OpenCode's native background subagents.