feat(map-efficient): per-subtask git worktree isolation (Part of #284)#296
Merged
Conversation
Opt-in, OFF by default (`worktree.isolation: true`). When enabled,
`/map-efficient` runs each subtask's Actor in a dedicated throwaway git
worktree and squash-merges the result back into the working branch ONLY
after the configured `verification_checks` pass IN the worktree (pre-merge
gate). A rejected attempt (Monitor valid=false / Evaluator fail) is
discarded, so the working branch is never touched by a bad attempt.
Runner-owned (not harness-native isolation="worktree"); the step runner owns
the full lifecycle + every safety guard, returning structured {kind,message}:
- create_subtask_worktree: crash-safe remove-and-recreate; guards for
not-a-repo, protected-ref, nested-worktree refusal, active-git-op,
subtask_id ref/path sanitization, dirty-main (excludes MAP runtime state),
submodule init.
- merge_subtask_worktree: base-divergence, runtime-state-in-diff,
configurable bulk-deletion (worktree.max_deletions), submodule-pointer,
detached-HEAD guards run BEFORE the working branch is touched; accept =
git merge --squash + one runner-authored commit (never --no-ff).
- discard_subtask_worktree: atomic reject, idempotent, optional --save-patch.
- worktree_isolation_status: reconcile recorded vs live worktrees.
Worktrees are stored out of the working tree under the repo's git common dir,
immune to git clean -fdx / recursive scanners / accidental commits. MAP state
(.map/<branch>/...) always resolves against the main checkout. New `worktree`
manifest stage + .map/<branch>/worktrees.json sidecar.
Config keys worktree.{isolation,max_deletions} (dotted-YAML alias). Skill
wiring in map-efficient (active branches; full recipe in
efficient-reference.md). Docs: CHANGELOG/USAGE/ARCHITECTURE. 30 new tests
exercise the lifecycle + every guard against real throwaway git repos.
Design was llm-council-reviewed (runner-owned over harness-native;
squash-merge over --no-ff; always-discard on reject; pre-merge verify +
crash-safe retry + atomic reject folded in so the slice is not a no-op;
explicit state-root separation).
Phase 2 (wave/DAG parallelism) and Phase 3 (context-budget hooks) remain
open on #284.
This was referenced Jun 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #284 — Phase 1, Slice 1.
What
Opt-in (
worktree.isolation: true), OFF by default, per-subtask git worktree isolation for/map-efficient. Each subtask's Actor runs in a dedicated throwaway git worktree; the result is squash-merged back into the working branch only after the configuredverification_checkspass IN the worktree (a pre-merge gate). A rejected attempt (Monitorvalid=false/ Evaluator fail) is discarded, so the working branch is never touched by a bad attempt.Why
/map-efficientshares one accumulated session context across all subtasks; #284 is "the second-largest structural gap after spec-driven pipeline". This lands the foundational Phase-1 slice: filesystem isolation + atomic accept/reject + safe rollback (the base Phase 2 parallelism needs).Design (llm-council-reviewed, conv
461b92f9)isolation="worktree"— native hides the worktree path, making deterministic safety gates and explicit merge-back impossible. The two mechanisms must never both be active.git merge --squash+ one runner-authored commit (never--no-ff, which would break one-commit-per-subtask).Step-runner commands (producer-owns-parse; structured
{kind,message})create_subtask_worktree— crash-safe; guards: not-a-repo, protected-ref, nested-worktree, active-git-op,subtask_idref/path sanitization, dirty-main (excludes MAP runtime state), submodule init.merge_subtask_worktree— base-divergence, runtime-state-in-diff, bulk-deletion (worktree.max_deletions), submodule-pointer, detached-HEAD guards BEFORE touching the working branch, then pre-merge verify, then squash-merge.discard_subtask_worktree— atomic reject, idempotent, optional--save-patch.worktree_isolation_status— reconcile recorded vs live worktrees.New
worktreemanifest stage +.map/<branch>/worktrees.jsonsidecar. Config keysworktree.{isolation,max_deletions}.Tests
tests/test_worktree_isolation.py— 30 tests: config (defaults/aliasing/validation/doc), full lifecycle, disabled no-op, and every guard (VERIFY_FAILED leaves main untouched, BULK_DELETION, BASE_DIVERGED, PROTECTED_REF, DIRTY_MAIN + runtime-state exclusion, INVALID_SUBTASK_ID, no_changes detection, crash-safe recreate) against real throwaway git repos.Gate
Local
make checkgreen: ruff + mypy (Success) + pyright (0 errors/0 warnings/0 informations) + 2910 tests passed, 3 skipped + render check (generated trees matchtemplates_src). Single-source render invariant honored (edited.jinja, ranmake render-templates; the always-loaded SKILL.md body budget was bumped 508→515 for the irreducible active wiring, full recipe inefficient-reference.md).Deferred (issue stays open)
files_modifiedoverlap detection.