Files
msd-core/docs/explanation/claude-orchestration-capability.md
Tom Boucher ff9cb6069f fix(#2285): wire claude-orchestration Workflow backend into execute-phase (#2314)
The claude-orchestration capability (#1143) shipped registered 'active'
but fully inert: detectWorkflowBackend/emitWorkflowScript had no caller
outside their own CLI router, and execute-phase.md declared an
execute:wave:pre hook point that the workflow body never rendered — so
claude_orchestration.enabled:true had zero effect on real runs.

Approach B (maintainer-chosen):
- execute-phase.md now renders the execute:wave:pre hook
  (gsd_run loop render-hooks execute:wave:pre) at a new step 2.75,
  immediately before each wave's Agent() dispatch — fixing the latent
  dead-hook gap for any pre-wave capability.
- Move the claude-orchestration contribution execute:wave:post ->
  execute:wave:pre (a pre-wave backend selector belongs before dispatch,
  not after); rename fragments/execute-wave-post.md -> execute-wave-pre.md
  with prose instructing the orchestrator to call resolve-wave-dispatch
  before step 3. Unrelated wave:post contributions (ui.safety-gate, drift,
  external-job, mempalace) untouched.
- New .cts seam resolveWaveDispatch(input) composes detectWorkflowBackend
  + emitWorkflowScript into one {backend:'inline'|'workflow', ...} result;
  exposed as gsd-tools claude-orchestration resolve-wave-dispatch. This is
  a real non-CLI-router, non-test caller of both functions.

Fail-closed: any gate miss (disabled, non-Claude runtime, Workflow tool
absent, SDK below floor, execution_backend:inline, malformed input) or an
emit failure resolves to inline with a byte-identical result shape — no
regression to the default-off execute-phase path.

Regression tests (tests/fix-2285-*) cover happy-path activation + SDK-floor
BVA, the fail-closed gate-miss table with detectWorkflowBackend parity, a
fast-check composition property, capability.json contribution assertions,
and a source-contract guard that execute:wave:pre is now actually rendered.
Dependent registry-shape assertions updated in-scope.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 19:18:28 -04:00

5.5 KiB

Claude orchestration capability (BETA)

Explanation — why this capability exists and how it fits the loop. For the step-by-step, see the capability reference; for the design record, see ADR-1143.

The problem

GSD's execute-phase is wave-based: plans carry a wave number, waves run sequentially, and plans within a wave run in parallel when their files_modified sets don't overlap. On most runtimes GSD realizes that by fanning out one backgrounded gsd-executor agent (in a worktree) per plan.

On Claude Code that fan-out degrades. Backgrounded agents on Claude Code have no Agent/Task tool, so they cannot nest subagents (#853). The autonomous loop therefore falls back to inline sequential execution — and with it silently drops wave parallelism, the plan-checker, and the verifier — on the one runtime most GSD users run.

Claude Code ships an orchestration primitive that sidesteps exactly this: the Workflow tool (the engine behind /effort ultracode, Agent SDK ≥ v0.3.149). A Workflow script is the orchestrator — it runs from the main loop and spawns subagents itself via agent(), parallel() (barrier), pipeline(), and phase(), with isolation: 'worktree', a shared token budget, and resumeFromRunId.

The capability

claude-orchestration is a default-off, BETA, claude-only capability that adopts the Workflow tool as an optional, runtime-gated parallel-execution backend, and folds the existing gsd-ultraplan-phase plan-offload under the same gate. It is blocked-on-nothing now that the ADR-857 capability system is released.

  • role: feature, runtimeCompat.supported: ["claude"], tier: full.
  • activationKey: claude_orchestration.enabled — default false. Nothing changes until you opt in.
  • Registers at two wired loop points: execute:wave:pre (into the executor) and plan:post (into the planner). Both are onError: skip and gated by the enabled key. The dispatch-backend selector fires at execute:wave:pre — the seam that runs immediately BEFORE a wave's agents are dispatched — because a selector fired after a wave already dispatched inline (the original execute:wave:post placement, #2285) is structurally too late to change how dispatch happens.

How it decides whether to activate

Detection is a pure, fail-closed function — detectWorkflowBackend. The Workflow backend activates only when every gate passes; any miss degrades to inline (today's behaviour):

  1. claude_orchestration.enabled is true.
  2. The runtime is Claude (the Workflow tool is Claude / Agent SDK-specific).
  3. claude_orchestration.execution_backend is auto or workflow (not inline).
  4. The host descriptor advertises dispatch.nested and dispatch.background (the nesting-capable Claude-Code shape — a proxy for Workflow-tool presence, meaningful only after gate 2).
  5. The Agent SDK reports a valid semver version.
  6. That version is >= claude_orchestration.min_agent_sdk_version (default 0.3.149). A pre-release of the floor (e.g. 0.3.149-rc.1) compares below the GA release per SemVer, so the preview backend stays off.

What the executor runs when the backend is active

emitWorkflowScript maps the phase's wave/plan model onto Workflow primitives:

GSD concept Workflow primitive
Wave parallel() stage barrier
Plan (use_worktree not false) agent(brief, { agentType: 'gsd-executor', isolation: 'worktree' })
Plan (use_worktree: false) agent(brief, { agentType: 'gsd-executor' }) (no isolation)
files_modified overlap forces the plans into separate sequential stages
Phase run id resumeFromRunId("<id>")
Phase token cap budget(<tokens>)

Because the emitted script composes the same gsd-executor agent the inline path uses, with worktree isolation applied per plan from the manifest's use_worktree field, it produces the same SUMMARY.md artifacts and commits — the only difference is the execution vehicle. use_worktree mirrors execute-phase.md step 2.5's per-plan submodule safety gate exactly: a plan that touches a submodule path is never forced into worktree isolation, whichever backend dispatches it (#2772).

The fallback contract

On any runtime lacking the Workflow tool — or when the capability is disabled, the SDK is too old, or detection fails for any reason — execute-phase proceeds with the standard inline wave dispatch. This is a release gate, not a nicety: a regression test asserts the inline fallback on every non-capable combination, so the capability is default-off and low-risk by construction.

BETA scope (v1)

The first slice ships detection + emission + declarative ultraplan ownership. The emitter is exercised at the contract level (structure, overlap splitting, resume, budget, anti-injection). End-to-end execution through the Workflow tool is verifiable only inside Claude Code with the tool present. Full install-profile migration of the gsd-ultraplan-phase skill into the capability's skills[] array is a follow-up (it touches the cluster/profile machinery); for v1 the manifest declares ultraplan ownership at plan:post and the existing skill's own runtime gate continues to no-op on non-Claude runtimes.