Summary
The full parallel-execution machinery from #284 (per-subtask worktree isolation, wave planner, merge_wave_worktrees coordinator) is built and tested, but it is opt-in at every layer. For a typical run, /map-efficient executes strictly sequentially even when the plan is perfectly parallelizable (a wave with ≥2 independent, disjoint-file subtasks). The feature is effectively dark.
mapify_version: 3.20.0
Observed
A decomposition that yields a wave of ≥2 independent subtasks still runs one Actor at a time. Parallelism never engages on the default path.
Root cause is a chain of three closed gates, each defaulting to sequential, plus two latent causes:
Gate 1 — default loop is the sequential walker; wave-loop is opt-in.
- Main loop drives off
get_next_step (single-subtask walker): src/mapify_cli/templates_src/skills/map-efficient/SKILL.md.jinja:135,409,446,464
- Explicit: "the skill defaults to the sequential walker for simplicity. Switch to the wave-loop when (a) waves have ≥2 subtasks AND (b) … disjoint files" —
SKILL.md.jinja:217-225
- So even when
set_waves produced a ≥2 wave, the default path never reads it back via get_wave_step as a parallel batch.
Gate 2 — concurrent Actor dispatch is hard-coupled to worktree.isolation, which is OFF.
- Default:
worktree.isolation → "false" — map_step_runner.py.jinja:15325
- When off,
create_subtask_worktree returns {"status":"disabled","reason":"worktree.isolation is off"} — map_step_runner.py.jinja:15420
- Concurrent dispatch requires
mode:"parallel" AND isolation enabled — efficient-reference.md.jinja:572-573
- This coupling is correct in principle: Claude Code gives subagents context isolation but NOT filesystem isolation — two Actors writing the same working tree concurrently corrupt it. So parallelism is structurally gated on the opt-in worktree feature.
Gate 3 — policy text repeatedly biases toward sequential.
parallel_tool_policy: guarded_wave_only (SKILL.md.jinja:23), "Keep execution sequential by default" (:27), "Sequential subtask execution is default" (:35), "Default to sequential execution … or explicit user-requested parallel execution" (:210).
Latent A — decomposer over-serialization. Parallelism is derived post-hoc from dependencies via Kahn (dependency_graph.py::compute_waves). Any false "logical-ordering" dependency collapses waves to size 1 → mode always "sequential". Guardrails exist (task-decomposer.md "Minimize Dependencies for Parallelism") but it's an LLM judgment, not a guarantee.
Latent B — no explicit multi-Task() dispatch mechanism. Reference says "dispatch the Actors concurrently (separate Task agents)" (efficient-reference.md.jinja:574) but never states the actual mechanism: emit one Task(actor) tool_use per subtask in a single assistant message. Claude Code has no special syntax for this — concurrency is purely the model emitting multiple tool calls in one turn. After reading "sequential by default" five times, the operator model won't.
Expected
When a wave legitimately contains ≥2 independent, disjoint-file subtasks, /map-efficient should engage parallel execution without the operator manually flipping config + switching dispatchers.
Suggested levers (each can be a slice)
- Auto-switch to the wave-loop when
set_waves yields any multi-element wave (currently opt-in "for simplicity") — opens Gate 1.
- Reconsider the worktree.isolation default (or auto-enable it when a parallel wave is about to dispatch) — opens Gate 2. Needs a fallback when not in a git repo.
- Add an explicit SKILL instruction + example: "emit one
Task(actor) per wave subtask in one message" — closes Latent B.
- Optionally surface a one-line diagnostic when a parallelizable wave is detected but executed sequentially (so the dark path is observable, not silent).
Notes
Summary
The full parallel-execution machinery from #284 (per-subtask worktree isolation, wave planner,
merge_wave_worktreescoordinator) is built and tested, but it is opt-in at every layer. For a typical run,/map-efficientexecutes strictly sequentially even when the plan is perfectly parallelizable (a wave with ≥2 independent, disjoint-file subtasks). The feature is effectively dark.mapify_version: 3.20.0Observed
A decomposition that yields a wave of ≥2 independent subtasks still runs one Actor at a time. Parallelism never engages on the default path.
Root cause is a chain of three closed gates, each defaulting to sequential, plus two latent causes:
Gate 1 — default loop is the sequential walker; wave-loop is opt-in.
get_next_step(single-subtask walker):src/mapify_cli/templates_src/skills/map-efficient/SKILL.md.jinja:135,409,446,464SKILL.md.jinja:217-225set_wavesproduced a ≥2 wave, the default path never reads it back viaget_wave_stepas a parallel batch.Gate 2 — concurrent Actor dispatch is hard-coupled to
worktree.isolation, which is OFF.worktree.isolation→"false"—map_step_runner.py.jinja:15325create_subtask_worktreereturns{"status":"disabled","reason":"worktree.isolation is off"}—map_step_runner.py.jinja:15420mode:"parallel"AND isolation enabled —efficient-reference.md.jinja:572-573Gate 3 — policy text repeatedly biases toward sequential.
parallel_tool_policy: guarded_wave_only(SKILL.md.jinja:23), "Keep execution sequential by default" (:27), "Sequential subtask execution is default" (:35), "Default to sequential execution … or explicit user-requested parallel execution" (:210).Latent A — decomposer over-serialization. Parallelism is derived post-hoc from
dependenciesvia Kahn (dependency_graph.py::compute_waves). Any false "logical-ordering" dependency collapses waves to size 1 →modealways"sequential". Guardrails exist (task-decomposer.md"Minimize Dependencies for Parallelism") but it's an LLM judgment, not a guarantee.Latent B — no explicit multi-
Task()dispatch mechanism. Reference says "dispatch the Actors concurrently (separate Task agents)" (efficient-reference.md.jinja:574) but never states the actual mechanism: emit oneTask(actor)tool_use per subtask in a single assistant message. Claude Code has no special syntax for this — concurrency is purely the model emitting multiple tool calls in one turn. After reading "sequential by default" five times, the operator model won't.Expected
When a wave legitimately contains ≥2 independent, disjoint-file subtasks,
/map-efficientshould engage parallel execution without the operator manually flipping config + switching dispatchers.Suggested levers (each can be a slice)
set_wavesyields any multi-element wave (currently opt-in "for simplicity") — opens Gate 1.Task(actor)per wave subtask in one message" — closes Latent B.Notes