This document is the implementation plan for the V2 orchestration core.
Scope for this pass:
task and task_status 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
→ task_status(task_id) reports running or terminal state
→ orchestrator consumes terminal result
Important distinction:
task result means launched.task_status terminal result means finished.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_status 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:
task_status or an auto-injected completion message marks a job terminal and
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.
- Poll or wait with `task_status(wait: true, timeout_ms: ...)` 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. Poll/wait for terminal results with task_status.
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. Poll task_status 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,
todo-continuation, 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.tool.execute.before(task_status) resolves job-board aliases for polling
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_status) 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.task_status output updates the job to 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:
Add handling for task_status:
Update system-context injection:
### Background Job Board,Do not expose running background jobs as resumable sessions. A running job
alias should nudge task_status, not 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
Use task_status before consuming 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_status handling
Ownership: src/hooks/task-session-manager/**
Keep this small. The point is scheduling state, not full task transcripts.
task_status IntegrationPrimary files:
src/index.tssrc/hooks/task-session-manager/index.tsAdd hook support for the native task_status tool.
Target flow:
tool.execute.after(task_status)
→ 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 instead of an explicit task_status call. 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_status.
Initially parse verified auto-message states only. cancelled can still be
handled through explicit task_status 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
Primary file:
src/hooks/todo-continuation/index.tsRisk:
V2 rule:
If relevant background jobs are running, continuation should poll/reconcile them
instead of treating the workflow as complete.
Implementation direction:
hasRunningBackgroundJobs(parentSessionID) query from the scheduler
state,hasTerminalUnreconciledJobs(parentSessionID),task_status and reconciliation.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_status Handlingtask_status,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_status 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,task_status terminal output updates job board state,The core invariant:
task creates jobs; task_status or auto-completion finishes jobs; orchestrator
reconciles jobs.