Internal historical planning doc. This file records the V2 implementation plan that led to the current default background-orchestration release. It is not the beta user guide and may mention branch or rollout details that are now historical. For user-facing setup, see Installation and Background Orchestration.
This archival document records the implementation plan for the V2 orchestration core. It is retained for maintainers who need the design history.
Scope for this pass:
task and hook-driven completion integration,Out of scope for this pass:
V2 assumes native OpenCode background subagents are available and enabled.
V2 changes the orchestrator from a worker-with-delegation into a scheduler.
V1 mental model:
orchestrator works directly → delegates when useful → waits for result
V2 mental model:
orchestrator plans → dispatches background specialists → monitors jobs
→ reconciles terminal results → verifies final state
The orchestrator should not be the default implementation worker. Specialists do the work; the orchestrator manages the work.
OpenCode background task semantics are the foundation:
task(background: true)
→ returns immediately with task_id
→ child session continues elsewhere
→ OpenCode injects completion when terminal
→ orchestrator consumes terminal result
Important distinction:
task result means launched.V2 must model these as separate states.
Introduce a small scheduler/job-board model for background delegates.
Suggested state shape:
type BackgroundJobState =
| 'running'
| 'completed'
| 'error'
| 'cancelled'
| 'reconciled';
interface BackgroundJobRecord {
taskID: string;
parentSessionID: string;
agent: string;
description: string;
objective: string;
ownership?: string[];
dependencies?: string[];
state: BackgroundJobState;
launchedAt: number;
updatedAt: number;
completedAt?: number;
timedOut?: boolean;
terminalUnreconciled?: boolean;
resultSummary?: string;
}
Native task output states are running, completed, error, and
cancelled. A wait timeout is not a terminal native state; represent it as a
timedOut overlay while the job remains running.
This does not need to be persisted initially. Start in memory, scoped to the parent orchestrator session.
Start with minimal reliable fields:
{
taskID,
parentSessionID,
agent,
description,
objective,
state,
timedOut,
terminalUnreconciled,
launchedAt,
updatedAt,
completedAt,
resultSummary,
}
Keep ownership and dependencies advisory until there is a reliable data
source. Native task arguments do not contain those fields, so initial V2 should
not pretend the plugin can infer them perfectly.
Do not bury this state inside task-session-manager.
Create a small shared utility, for example:
src/utils/background-job-board.ts, orsrc/hooks/scheduler-state/ if it grows into a hook-owned subsystem.It should expose methods such as:
registerLaunch(record)
updateStatus(taskID, status)
markReconciled(taskID)
hasRunning(parentSessionID)
hasTerminalUnreconciled(parentSessionID)
formatForPrompt(parentSessionID)
Then pass the shared state into:
The plugin needs one concrete reconciliation transition.
Initial rule:
terminalUnreconciled: true.reconciled.This is intentionally simple. It avoids terminal jobs living forever while still forcing at least one orchestrator turn to see and account for each result.
Important: Idle-based reconciliation is a heuristic. Reconciled status means a terminal result was injected into an orchestrator turn that completed and the parent returned to idle; it is not proof the result was explicitly acknowledged or used by the orchestrator. Initial V2 should not try to infer from free text whether the orchestrator mentioned, ignored, blocked, or failed a job. If a more precise protocol is needed later, add an explicit marker/tool for reconciliation.
Primary file:
src/agents/orchestrator.tsRelated reminder file:
src/config/constants.tsReplace the current role framing with scheduler-first language:
You are a workflow manager for coding work. Your job is to plan, schedule,
delegate, monitor, reconcile, and verify specialist-agent work. You are not the
default implementation worker.
The orchestrator may directly:
The orchestrator should delegate:
Remove the V1 text that says delegated specialists block the parent until result.
New execution model:
### OpenCode V2 scheduler model
- Delegated specialists should be launched as background tasks whenever work can
run independently using `task(..., background: true)`.
- A dispatch returns a task/session ID immediately; it does not mean completion.
- Track each task ID with specialist, objective, state, and any advisory
ownership/dependency labels available from the dispatch plan.
- Continue orchestration while tasks run: planning, scheduling independent lanes,
preparing synthesis, and asking needed user questions.
- Wait for hook-driven completion before consuming
outputs or starting dependent work.
- Parallel background tasks are allowed only when their write scopes do not
conflict.
- Final response requires relevant tasks to be terminal and reconciled.
V2 workflow should be:
## Dispatch
1. Split work into independent and dependency-ordered lanes.
2. Plan advisory ownership for write-capable lanes.
3. Dispatch independent specialists as background tasks.
4. Record task IDs, state, and advisory ownership/dependency labels.
5. Continue only independent orchestration while jobs run.
6. Wait for terminal results via hook-driven completion.
7. Reconcile results, resolve conflicts, and gate dependent lanes.
8. Dispatch follow-up jobs if needed.
9. Verify final state.
Update PHASE_REMINDER_TEXT so it reinforces scheduler behavior:
Build a short work graph with independent lanes, dependencies, and advisory
ownership.
Dispatch independent specialists as background tasks, record task/session IDs,
then continue orchestration. Wait for hook-driven completion and only consume outputs or advance
dependent work when results are terminal.
Each background task prompt should be self-contained and bounded.
Include:
Good prompt:
Inspect src/hooks/task-session-manager for assumptions that a task result means
child work is finished. Do not edit files. Return exact files/functions,
background-task risks, and recommended changes.
Bad prompt:
Look into background tasks.
Primary files:
src/hooks/task-session-manager/index.tssrc/utils/task.tssrc/utils/background-job-board.tsCurrent behavior:
src/index.ts creates one shared BackgroundJobBoard using
backgroundJobs caps/context config and passes it to task-session-manager,
cancel-task, and multiplexer integration.tool.execute.before(task) validates subagent_type, strips stale/invalid
task_id aliases when they cannot safely resolve, and only resolves reusable
aliases for matching completed/reconciled jobs.task_status hook is installed; upstream no longer exposes that tool
running or terminal tasks.tool.execute.after(task) parses native launch output and records running
jobs in the shared board; it does not treat launch as completion.tool.execute.after(task) and synthetic completion messages parse
status output into running/terminal job-board state.### Background Job Board; completed/reconciled jobs appear
only in the reusable section.V2 behavior:
task tool output creates or updates a job as launched or running.running, completed, error, or
cancelled; timeout is metadata while the job remains running.Split parsing helpers:
parseTaskLaunchOutput(output) → { taskID, state: 'running' | ... }
parseTaskStatusOutput(output) → { taskID, state, result? }
Store background job records in src/utils/background-job-board.ts, scoped by
parent orchestrator session.
Update tool.execute.after for task:
Handle task output and injected completions:
Update system-context injection:
### Background Job Board,Do not expose running background jobs as resumable sessions. A running job
alias should not be used with running task(task_id=...). Only completed and
reconciled sessions should enter the reusable section.
The orchestrator needs a compact view of active work.
Target injected shape:
### Background Job Board
Do not poll running jobs. Reconcile terminal jobs before
final response.
- exp-4 / ses_abc / explorer / running
Objective: Map multiplexer flow
Ownership: read-only
Dependencies: none
- fix-2 / ses_def / fixer / completed, unreconciled
Objective: Update task-session-manager task output handling
Ownership: src/hooks/task-session-manager/**
Keep this small. The point is scheduling state, not full task transcripts.
Primary files:
src/index.tssrc/hooks/task-session-manager/index.tsTrack native task output and injected completion messages.
Target flow:
tool.execute.after(task)
→ parse task_id + state
→ update job board
→ if terminal, attach compact result summary
→ mark as terminal/unreconciled
The orchestrator prompt should then see terminal jobs and reconcile them before continuing dependent work.
Do not rely only on OpenCode auto-resume notifications. The plugin should build its own compact scheduler state from tool results and events.
Native background tasks can also complete through an OpenCode-injected parent message as a synthetic completion message. V2 must ingest that path too.
Parse this in the chat/message transform path that already inspects parent
conversation messages, most likely experimental.chat.messages.transform in the
same hook family as task-session-manager context injection. If native OpenCode
adds a dedicated event later, move the parser to that event path.
Add parsing for synthetic completion content containing fields like:
Background task completed: <description>
task_id: <id>
state: completed | error
<task_result>
...
</task_result>
That path should update the same shared job-board state as task output.
Initially parse verified auto-message states only. cancelled can still be
handled through task output unless verified in auto-injected
messages.
auto-injected completion message
→ parse task_id + state + result
→ update job board
→ mark terminal/unreconciled
Primary files:
src/multiplexer/session-manager.tssrc/multiplexer/tmux/index.tssrc/multiplexer/zellij/index.tssrc/index.tsCurrent multiplexer behavior is already close to V2:
/session/status.V2 requirements:
Likely first implementation can keep close-on-idle if native child sessions emit accurate idle events. Verify with real background tasks before changing cleanup semantics.
Potential later improvement:
[BG explorer] exp-4 Map multiplexer flow
[BG fixer] fix-2 task-session-manager
V2 should describe specialists as execution lanes, not optional helpers.
The orchestrator schedules lanes according to dependency and ownership.
Before changing the prompt, build enough parser/job-board behavior that the prompt can rely on visible scheduler state.
task output with state: running as a background launch,running, not a native state,task output,Start here:
src/utils/task.ts
src/utils/background-job-board.ts or equivalent shared scheduler module
src/hooks/task-session-manager/index.ts
src/index.ts
task after-hooks into the task-session-manager hook.src/agents/orchestrator.ts
src/config/constants.ts
src/multiplexer/session-manager.test.ts
Core V2 is working when:
task output registers running jobs,The core invariant:
task creates jobs; task output or auto-completion finishes jobs; orchestrator
reconciles jobs.