Skip to content

[FEATURE]: Orchestrator agent for parallel subagent fan-out/fan-in (parallel dispatch without polling) #47639

Closed as not planned

Description

@AllanSantos-DV

Feature hasn't been suggested before.

  • I have verified this feature I'm about to request hasn't been suggested before.

Describe the enhancement you want to request

Multi-phase tasks are slow in wall-clock time because independent subtasks run sequentially. The task tool already supports background=true and the BackgroundJob registry forks each job into its own scope, so parallel execution is possible — but the model has to orchestrate it manually by issuing multiple task calls in one message and then collecting each background notification. That's error-prone: the model often forgets to wait, polls, or duplicates work.

A focused orchestrator agent that does fan-out / fan-in with discipline would close the gap without a new scheduler:

  • decomposes a complex objective into 2–7 independent subtasks,
  • dispatches each via task with background: true in parallel,
  • respects concurrency: 10 (Effect.all({ concurrency: 10 })) and depth: 1 (subagents cannot spawn further subagents),
  • continues on partial failure and reports it,
  • caps cost per dispatch.

This is the upstream ask in #47105 (task-parallel fan-out) and the open PR #47107 (task-parallel tool, everest-an, non-draft, 4 files). That PR is a solid invoker: pass tasks[], run Effect.forEach, return a <parallel-tasks> summary. This request is the harness around the same idea — scoping it as an agent, not a tool, so the discipline lives in the system prompt rather than in ad-hoc tool arguments.

The two are complementary, not competing: #47107 can land as the builtin tool for explicit calls; this agent is the opt-in harness for when the model should decide to fan out on its own (keyword trigger, no behavior change when the flag is off). If maintainers prefer one path, happy to fold into #47107 as review suggestions (e.g. concurrency: 10 instead of unbounded, opt-in flag) rather than a separate effort.

Why this complements existing issues (honest scope — alivia vs resolve, verificado contra os bodies reais):

Expected gain (DEFERRED until H1-H5 measured — honest, per quality-gate):

Proposed shape (behind an opt-in experimental flag, no default behavior change — same posture as OPENCODE_EXPERIMENTAL_TRANSCRIPT_RECALL in the recall work):

  • Agent orchestrator (mode: all, steps: 7, read-only by default — no edit/write/patch/bash, allow read/grep/glob/webfetch/websearch) with a prompt that encodes the discipline above (9 bullets + 5-step protocol + cost & output format). all is needed so the harness itself is not blocked by the subagent_depth guard when delegating (see feat(opencode): add task-parallel tool to fan out independent subtasks concurrently #47107's opposite choice: concurrency: "unbounded" without a harness discipline — this agent is the disciplined alternative).
  • Agent worker (read-only, task: deny, todowrite: deny) — workers that cannot spawn further subagents (enforced by deriveSubagentSessionPermission at depth: 1; Truncate.GLOB allow preserved).
  • An opt-in flag (same enabledByExperimental pattern as OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS, e.g. OPENCODE_EXPERIMENTAL_ORCHESTRATOR, experimental || enabled semantics) + a keyword trigger (orchestrate|orchestrator|parallel|fans?out|delegate at step===1 after title and after the overflow guard, immediately before agents.get(lastUser.agent), \b boundaries, contíguo fans?out — not fan out with space nor fan-out with hyphen) — no behavior change when the flag is off.

Why a harness matters vs a tool: a tool is an invoker (the model decides when/how to call it); the harness is where discipline is enforced — background: true always, concurrency: 10 always, task: deny on workers always, steps: 7 (2–7 workers + synthesize), cost cap observability via totalSessionCost(). The recall landed the same way: substrate first, gated, lab-verified, no breaking change.

Out of scope: dynamic workflow generation (Claude writes its own harness), LangGraph-style state machines, Temporal-style multi-cluster durability, and TUI cards — all already exist or are separately tracked. The open task-parallel tool PR #47107 is complementary (not a substitute): this harness delegates to it internally if/when both land, rather than re-implementing the dispatch logic.

Related prior art: #37421 (MCP hot reload), #34492 (unified watch service), #8751 (agent/skill hot reload) — different domain, same pattern of unifying scattered issues under one design discussion.

Happy to work on this behind a design review if maintainers agree with the scope; also glad to fold it into #47105 / #47107 as review suggestions if you prefer one umbrella effort (e.g. add a cap on concurrency for the tool, an opt-in flag for the harness, and a dedicated read-only worker) rather than a separate agent.


Refs: #47105 (primary ask), #47107 (complementary tool PR, non-draft), #37548, #43594, #45417, #44748, #30308.

Activity

  1. github-actions commented on Sep 6, 2026

    @github-actions
    Contributor

    This issue might be a duplicate of existing issues. Please check:

    The feature described here is closely related to (and partially superseded by) #47105 and #47107. Please consider whether this can be folded into those existing efforts rather than tracked separately, as the issue itself suggests.

  2. AllanSantos-DV commented on Sep 26, 2026

    @AllanSantos-DV
    Author

    Answering the duplicate-check question, then reporting where this landed.

    On folding into #47105 / #47107 — I'd keep them separate, for a concrete reason: #47107 (task-parallel) is an invoker. The model decides when to call it. This is the harness that decides whether to fan out at all, plus the resiency needed for the fan-in to actually complete. In testing, the invoker alone doesn't fix the failure #47105 describes — the model still skips the fan-out, and when a subagent dies mid-flight the parent waits on a job that never reports. So the two pieces are: opt-in harness (when/whether to fan out) + dependable completion (what happens when a worker dies). Happy to be told otherwise; the harness part is a prompt and two agent definitions, so it folds cheaply if maintainers prefer one umbrella.

    Scope — two complementary capabilities, one feature:

    1. Orchestrator harness — opt-in runtime flag + keyword trigger at step === 1; orchestrator and read-only worker agents; workers cannot spawn subagents (task: deny); per-dispatch cost cap via totalSessionCost(). No behavior change when the flag is off.
    2. Subagent resume — the completion path that makes (1) usable: wake op to avoid runLoop deadlock, non-blocking notify so async subtasks don't block the parent, and sanitizing every orphaned running tool part on resume (previous code took only the first via .find()).

    Splitting these would ship the harness without the case where it matters most: a worker that dies mid-fan-out.

    State of the branch (feat/async-subagents-orchestrator, on current upstream/dev):

    • 24 commits · 10 files · +1108/−262 · no unrelated deletions
    • bun turbo typecheck 30/30 · tsc --noEmit 0 errors · 28/28 in test/tool/task.test.ts
    • E2E in an isolated lab: kill mid-flight → restart → resume by task_id, four invariants asserted (state=error, metadata.interrupted=true, time.end >= time.start, zero orphan running parts)

    No speedup numbers claimed. The issue defers H1–H5 and I'm keeping that. What I validated is crash-resumption, not wall-clock. The 2.5–4× figure stays a projection until there's a fan-out-specific dataset.

    Would welcome a design review on the harness shape before I open the PR — particularly whether the trigger and the cost cap belong in the agent definition or in the tool layer.

  3. neriousy commented on Oct 21, 2026

    @neriousy
    Member

    Thanks for the thorough write-up and testing. Your branch is built on V1 internals (BackgroundJob, runLoop) that don't exist in V2, and V2 already covers most of the runtime side: the subagent tool can run subagents in the background and notifies the parent when they finish, with no polling, and a subagent permission rule can stop workers from starting their own subagents (https://opencode.ai/v2/docs/agents). The orchestrator part is, as you say, a prompt plus two agent definitions, so it works best as custom agents you share with others (https://opencode.ai/v2/docs/agents) rather than something built into opencode. A native parallel fan-out tool is still tracked in #47105. If you hit a gap in V2's subagent completion or recovery, please open a bug with steps to reproduce on V2. Closing as not planned.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions